Class: Common::Collection
- Inherits:
-
Object
- Object
- Common::Collection
- Extended by:
- ActiveModel::Naming, Forwardable
- Includes:
- ActiveModel::Serialization
- Defined in:
- lib/common/models/collection.rb
Overview
Wrapper for collection to keep aggregates
Constant Summary collapse
- CACHE_NAMESPACE =
'common_collection'
- CACHE_DEFAULT_TTL =
default to 1 hour
3600
- OPERATIONS_MAP =
{ 'eq' => '==', 'lteq' => '<=', 'gteq' => '>=', 'not_eq' => '!=', 'match' => 'match' }.with_indifferent_access.freeze
Instance Attribute Summary collapse
-
#attributes ⇒ Object
(also: #to_h, #to_hash)
readonly
Returns the value of attribute attributes.
-
#data ⇒ Object
(also: #members)
Returns the value of attribute data.
-
#errors ⇒ Object
Returns the value of attribute errors.
-
#metadata ⇒ Object
Returns the value of attribute metadata.
-
#type ⇒ Object
Returns the value of attribute type.
Class Method Summary collapse
- .bust(cache_keys) ⇒ Object
- .cache(json_hash, cache_key, ttl) ⇒ Object
- .fetch(klass, cache_key: nil, ttl: CACHE_DEFAULT_TTL) ⇒ Object
- .redis_namespace ⇒ Object
Instance Method Summary collapse
- #bust ⇒ Object
- #cached? ⇒ Boolean
- #convert_fields_to_ordered_hash(fields) ⇒ Object private
- #find_by(filter = {}) ⇒ Object
- #find_first_by(filter = {}) ⇒ Object
-
#finder(object, filter) ⇒ Object
private
rubocop:disable Metrics/MethodLength.
-
#initialize(klass = Array, data: [], metadata: {}, errors: {}, cache_key: nil) ⇒ Collection
constructor
A new instance of Collection.
- #mock_comparator_object ⇒ Object private
- #paginate(page: nil, per_page: nil) ⇒ Object
- #pagination_meta(page, per_page) ⇒ Object private
-
#paginator(page, per_page) ⇒ Object
private
rubocop:enable Metrics/MethodLength.
- #redis_namespace ⇒ Object
- #serialize ⇒ Object
- #sort(sort_params) ⇒ Object
- #sort_fields(params) ⇒ Object private
- #sort_type_allowed?(sort_param) ⇒ Boolean private
- #ttl ⇒ Object
- #verify_filter_keys!(filter) ⇒ Object private
Constructor Details
#initialize(klass = Array, data: [], metadata: {}, errors: {}, cache_key: nil) ⇒ Collection
Returns a new instance of Collection.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/common/models/collection.rb', line 33 def initialize(klass = Array, data: [], metadata: {}, errors: {}, cache_key: nil) data = Array.wrap(data) # If data is passed in as nil, wrap it as an empty array @type = klass @attributes = data @metadata = @errors = errors @cache_key = cache_key (@data = data) && return if defined?(::WillPaginate::Collection) && data.is_a?(WillPaginate::Collection) @data = data.collect do |element| element.is_a?(Hash) ? klass.new(element) : element end end |
Instance Attribute Details
#attributes ⇒ Object (readonly) Also known as: to_h, to_hash
Returns the value of attribute attributes.
16 17 18 |
# File 'lib/common/models/collection.rb', line 16 def attributes @attributes end |
#data ⇒ Object Also known as: members
Returns the value of attribute data.
17 18 19 |
# File 'lib/common/models/collection.rb', line 17 def data @data end |
#errors ⇒ Object
Returns the value of attribute errors.
17 18 19 |
# File 'lib/common/models/collection.rb', line 17 def errors @errors end |
#metadata ⇒ Object
Returns the value of attribute metadata.
17 18 19 |
# File 'lib/common/models/collection.rb', line 17 def @metadata end |
#type ⇒ Object
Returns the value of attribute type.
17 18 19 |
# File 'lib/common/models/collection.rb', line 17 def type @type end |
Class Method Details
.bust(cache_keys) ⇒ Object
79 80 81 82 |
# File 'lib/common/models/collection.rb', line 79 def self.bust(cache_keys) cache_keys = Array.wrap(cache_keys) cache_keys.map { |cache_key| redis_namespace.del(cache_key) } end |
.cache(json_hash, cache_key, ttl) ⇒ Object
74 75 76 77 |
# File 'lib/common/models/collection.rb', line 74 def self.cache(json_hash, cache_key, ttl) redis_namespace.set(cache_key, json_hash) redis_namespace.expire(cache_key, ttl) end |
.fetch(klass, cache_key: nil, ttl: CACHE_DEFAULT_TTL) ⇒ Object
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/common/models/collection.rb', line 56 def self.fetch(klass, cache_key: nil, ttl: CACHE_DEFAULT_TTL) raise 'No Block Given' unless block_given? if cache_key json_string = redis_namespace.get(cache_key) if json_string.nil? collection = new(klass, **yield.merge(cache_key:)) cache(collection.serialize, cache_key, ttl) collection else json_hash = Oj.load(json_string) new(klass, **json_hash.merge('cache_key' => cache_key).symbolize_keys) end else new(klass, **yield) end end |
.redis_namespace ⇒ Object
48 49 50 |
# File 'lib/common/models/collection.rb', line 48 def self.redis_namespace @redis_namespace ||= Redis::Namespace.new(CACHE_NAMESPACE, redis: $redis) end |
Instance Method Details
#bust ⇒ Object
84 85 86 |
# File 'lib/common/models/collection.rb', line 84 def bust self.class.bust(@cache_key) if cached? end |
#cached? ⇒ Boolean
88 89 90 |
# File 'lib/common/models/collection.rb', line 88 def cached? @cache_key.present? end |
#convert_fields_to_ordered_hash(fields) ⇒ Object (private)
209 210 211 212 213 214 215 216 217 218 |
# File 'lib/common/models/collection.rb', line 209 def convert_fields_to_ordered_hash(fields) fields.each_with_object({}) do |field, hash| if field.start_with?('-') field = field[1..] hash[field] = 'DESC' else hash[field] = 'ASC' end end end |
#find_by(filter = {}) ⇒ Object
96 97 98 99 100 101 |
# File 'lib/common/models/collection.rb', line 96 def find_by(filter = {}) verify_filter_keys!(filter) result = @data.select { |item| finder(item, filter) } = @metadata.merge(filter:) Collection.new(type, data: result, metadata:, errors:) end |
#find_first_by(filter = {}) ⇒ Object
103 104 105 106 107 108 109 110 |
# File 'lib/common/models/collection.rb', line 103 def find_first_by(filter = {}) verify_filter_keys!(filter) result = @data.detect { |item| finder(item, filter) } return nil if result.nil? result. = result end |
#finder(object, filter) ⇒ Object (private)
rubocop:disable Metrics/MethodLength
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/common/models/collection.rb', line 143 def finder(object, filter) filter.to_hash.all? do |attribute, predicates| actual_value = object.send(attribute) predicates.all? do |operator, expected_value| valid_operation = type.filterable_attributes[attribute].include?(operator.to_s) raise Common::Exceptions::FilterNotAllowed, "#{operator} for #{attribute}" unless valid_operation op = OPERATIONS_MAP.fetch(operator) parsed_value = expected_value.try(:split, ',') || expected_value results = Array.wrap(parsed_value).collect do |item| mock_comparator_object.send("#{attribute}=", item) if op == 'match' actual_value.downcase.include?(item.downcase) else actual_value.send(op, mock_comparator_object.send(attribute)) end end results.any? end end rescue => e raise e if e.is_a?(Common::Exceptions::BaseError) raise Common::Exceptions::InvalidFiltersSyntax.new(nil, detail: 'The syntax for your filters is invalid') end |
#mock_comparator_object ⇒ Object (private)
138 139 140 |
# File 'lib/common/models/collection.rb', line 138 def mock_comparator_object @mock_comparator_object ||= type.new end |
#paginate(page: nil, per_page: nil) ⇒ Object
124 125 126 127 128 129 130 |
# File 'lib/common/models/collection.rb', line 124 def paginate(page: nil, per_page: nil) page = page.try(:to_i) || 1 max_per_page = type.max_per_page || 100 per_page = [per_page.try(:to_i) || type.per_page || 10, max_per_page].min collection = paginator(page, per_page) Collection.new(type, data: collection, metadata: .merge((page, per_page)), errors:) end |
#pagination_meta(page, per_page) ⇒ Object (private)
186 187 188 189 190 |
# File 'lib/common/models/collection.rb', line 186 def (page, per_page) total_entries = @data.size total_pages = total_entries.zero? ? 1 : (total_entries / per_page.to_f).ceil { pagination: { current_page: page, per_page:, total_pages:, total_entries: } } end |
#paginator(page, per_page) ⇒ Object (private)
rubocop:enable Metrics/MethodLength
174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/common/models/collection.rb', line 174 def paginator(page, per_page) if defined?(::WillPaginate::Collection) WillPaginate::Collection.create(page, per_page, @data.length) do |pager| raise Common::Exceptions::InvalidPaginationParams.new({ page:, per_page: }) if pager.out_of_bounds? pager.replace @data[pager.offset, pager.per_page] end else @data[((page - 1) * per_page)...(page * per_page)] end end |
#redis_namespace ⇒ Object
52 53 54 |
# File 'lib/common/models/collection.rb', line 52 def redis_namespace @redis_namespace ||= self.class.redis_namespace end |
#serialize ⇒ Object
132 133 134 |
# File 'lib/common/models/collection.rb', line 132 def serialize { data:, metadata:, errors: }.to_json end |
#sort(sort_params) ⇒ Object
112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/common/models/collection.rb', line 112 def sort(sort_params) fields = sort_fields(sort_params || type.default_sort) result = @data.sort_by do |item| fields.map do |k, v| v == 'ASC' ? Ascending.new(item.send(k)) : Descending.new(item.send(k)) end end = @metadata.merge(sort: fields) Collection.new(type, data: result, metadata:, errors:) end |
#sort_fields(params) ⇒ Object (private)
192 193 194 195 196 197 198 |
# File 'lib/common/models/collection.rb', line 192 def sort_fields(params) params = Array.wrap(params) not_allowed = params.select { |p| sort_type_allowed?(p) }.join(', ') raise Common::Exceptions::InvalidSortCriteria.new(type.name, not_allowed) unless not_allowed.empty? convert_fields_to_ordered_hash(params) end |
#sort_type_allowed?(sort_param) ⇒ Boolean (private)
200 201 202 |
# File 'lib/common/models/collection.rb', line 200 def sort_type_allowed?(sort_param) type.sortable_attributes.exclude?(sort_param.delete('-')) end |
#ttl ⇒ Object
92 93 94 |
# File 'lib/common/models/collection.rb', line 92 def ttl @cache_key.present? ? redis_namespace.ttl(@cache_key) : nil end |
#verify_filter_keys!(filter) ⇒ Object (private)
204 205 206 207 |
# File 'lib/common/models/collection.rb', line 204 def verify_filter_keys!(filter) failed_attributes = (filter.keys.map(&:to_s) - type.filterable_attributes.keys).join(', ') raise Common::Exceptions::FilterNotAllowed, failed_attributes unless failed_attributes.empty? end |