Class: Lockstep::Query
- Inherits:
-
Object
- Object
- Lockstep::Query
- Defined in:
- app/concepts/lockstep/query.rb
Constant Summary collapse
- PREDICATES =
{ _not_eq: "NE", _eq: "EQ", _gteq: "GE", _gt: "GT", _lteq: "LE", _lt: "LT", _in: "IN", _contains: "CONTAINS", _starts_with: "STARTSWITH", _ends_with: "ENDSWITH", _is: "IS", }.with_indifferent_access
Instance Method Summary collapse
- #additional_query_params(args) ⇒ Object
- #all ⇒ Object
-
#build_filter ⇒ Object
def related_to(obj, key) query = { “$relatedTo” => { “object” => obj.to_pointer, “key” => key } } criteria.merge!(query) clone end.
- #build_filter_condition(filter, merge_predicate, condition) ⇒ Object
- #build_filter_query(key, value) ⇒ Object
- #build_params ⇒ Object
-
#chunk(count = 100) ⇒ Object
Divides the query into multiple chunks if you’re running into RestClient::BadRequest errors.
- #chunk_results(params = {}) ⇒ Object
-
#clone ⇒ Object
Clone is used as return object as it messes with the OR query in situations like these unscoped = Lockstep::Connection.unscoped unscoped.where(a: 1).or(unscoped.where(b: 1)) both the where conditions in the above scenario is modifying the same object causing stack-overflow.
- #convert_arg(arg) ⇒ Object
- #count ⇒ Object
- #criteria ⇒ Object
- #execute ⇒ Object
-
#find(id) ⇒ Lockstep::ApiRecord
Find a Lockstep::ApiRecord object by ID.
- #first ⇒ Object
- #get_relation_objects(objects) ⇒ Object
- #get_results(params = {}) ⇒ Object
- #include_object(*objects) ⇒ Object
-
#initialize(klass) ⇒ Query
constructor
A new instance of Query.
- #limit(limit) ⇒ Object
- #method_missing(meth, *args, &block) ⇒ Object
- #model ⇒ Object
- #none ⇒ Object
- #or(query) ⇒ Object
-
#order(*attr) ⇒ Object
attr: :desc.
- #page(page_number) ⇒ Object
-
#reorder(attr) ⇒ Object
attr: :desc.
- #respond_to?(meth) ⇒ Boolean
- #unscoped ⇒ Object
- #where(args) ⇒ Object
- #with_clone(&block) ⇒ Object
- #with_count(value) ⇒ Object
Constructor Details
#initialize(klass) ⇒ Query
Returns a new instance of Query.
2 3 4 |
# File 'app/concepts/lockstep/query.rb', line 2 def initialize(klass) @klass = klass end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(meth, *args, &block) ⇒ Object
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 |
# File 'app/concepts/lockstep/query.rb', line 354 def method_missing(meth, *args, &block) method_name = method_name.to_s if method_name.start_with?("find_by_") attrib = method_name.gsub(/^find_by_/, "") finder_name = "find_all_by_#{attrib}" define_singleton_method(finder_name) do |target_value| where({ attrib.to_sym => target_value }).first end send(finder_name, args[0]) elsif method_name.start_with?("find_all_by_") attrib = method_name.gsub(/^find_all_by_/, "") finder_name = "find_all_by_#{attrib}" define_singleton_method(finder_name) do |target_value| where({ attrib.to_sym => target_value }).all end send(finder_name, args[0]) end if @klass.scopes[meth].present? instance_exec *args, &@klass.scopes[meth] elsif Array.method_defined?(meth) all.send(meth, *args, &block) else super end end |
Instance Method Details
#additional_query_params(args) ⇒ Object
54 55 56 57 58 |
# File 'app/concepts/lockstep/query.rb', line 54 def additional_query_params(args) with_clone do criteria[:additional_query_params] ||= args end end |
#all ⇒ Object
335 336 337 |
# File 'app/concepts/lockstep/query.rb', line 335 def all execute end |
#build_filter ⇒ Object
def related_to(obj, key)
query = { "$relatedTo" => { "object" => obj.to_pointer, "key" => key } }
criteria[:conditions].merge!(query)
clone
end
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'app/concepts/lockstep/query.rb', line 138 def build_filter filter = "" criteria[:conditions].each do |condition| if condition.is_a?(Hash) if condition[:type] == "or" filter = build_filter_condition(filter, "OR", condition[:query].build_filter) else condition.each do |key, value| filter = build_filter_condition(filter, "AND", build_filter_query(key, value)) end end elsif condition.is_a?(String) filter = build_filter_condition(filter, "AND", condition) end end filter end |
#build_filter_condition(filter, merge_predicate, condition) ⇒ Object
156 157 158 159 160 161 162 163 |
# File 'app/concepts/lockstep/query.rb', line 156 def build_filter_condition(filter, merge_predicate, condition) if filter.present? filter = "(#{filter}) #{merge_predicate} (#{condition})" else filter += condition end filter end |
#build_filter_query(key, value) ⇒ Object
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'app/concepts/lockstep/query.rb', line 179 def build_filter_query(key, value) key = key.to_s unless key.is_a?(String) predicate = "eq" # default PREDICATES.each do |k, p| if key.ends_with?(k) key = key.gsub(k, "") predicate = p break end end # Build value if value.is_a?(Array) value = "(#{value.map { |v| v.is_a?(String) ? "'#{v}'" : v }.join(",")})" predicate = "in" if predicate == "eq" elsif value.is_a?(String) and !["NULL", "NOT NULL"].include?(value) value = "'#{value}'" elsif value.nil? predicate = "IS" if predicate == "eq" value = "NULL" end "#{key.camelize(:lower)} #{predicate} #{value}" end |
#build_params ⇒ Object
204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'app/concepts/lockstep/query.rb', line 204 def build_params params = {} params.merge!({ :filter => build_filter }) if criteria[:conditions] # Lockstep Platform API does not support a page size of 1 params.merge!({ :pageSize => (criteria[:limit] == 1 ? 2 : criteria[:limit]) }) if criteria[:limit] params.merge!({ :pageNumber => criteria[:page_number] }) if criteria[:page_number] params.merge!({ :include => criteria[:include].join(",") }) if criteria[:include] params.merge!({ :order => criteria[:order].join(",") }) if criteria[:order] params.merge!({ :pageSize => 2 }) if criteria[:count] params.merge!(criteria[:additional_query_params]) if criteria[:additional_query_params] params.reject! { |k, v| v.blank? } params end |
#chunk(count = 100) ⇒ Object
Divides the query into multiple chunks if you’re running into RestClient::BadRequest errors.
126 127 128 129 130 |
# File 'app/concepts/lockstep/query.rb', line 126 def chunk(count = 100) with_clone do criteria[:chunk] = count end end |
#chunk_results(params = {}) ⇒ Object
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'app/concepts/lockstep/query.rb', line 308 def chunk_results(params = {}) page = criteria[:page] || 0 limit = criteria[:limit] || 0 # completed = false records = [] loop do params[:pageNumber] = page params[:pageSize] = limit.positive? ? [limit - records.size, criteria[:chunk]].min : criteria[:chunk] break if params[:pageSize].zero? results = get_results(params) break unless results.present? records += results break if limit.positive? && records.size >= limit page += 1 end records.uniq(&:attributes) end |
#clone ⇒ Object
Clone is used as return object as it messes with the OR query in situations like these
unscoped = Lockstep::Connection.unscoped
unscoped.where(a: 1).or(unscoped.where(b: 1))
both the where conditions in the above scenario is the same object causing stack-overflow
14 15 16 17 18 |
# File 'app/concepts/lockstep/query.rb', line 14 def clone c = Lockstep::Query.new(@klass) c.criteria.deep_merge!(self.criteria.deep_dup) c end |
#convert_arg(arg) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'app/concepts/lockstep/query.rb', line 60 def convert_arg(arg) return arg.to_pointer if arg.is_a?(Lockstep::ApiRecord) return Lockstep::ApiRecord.to_date_object(arg) if arg.is_a?(Time) || arg.is_a?(Date) if arg.is_a?(Hash) arg.keys.each do |key| @klass.valid_attribute?(key, raise_exception: true) end return arg.update(arg) { |key, inner_arg| convert_arg(inner_arg) } end arg end |
#count ⇒ Object
339 340 341 |
# File 'app/concepts/lockstep/query.rb', line 339 def count with_clone { criteria[:count] = true }.execute end |
#criteria ⇒ Object
26 27 28 |
# File 'app/concepts/lockstep/query.rb', line 26 def criteria @criteria ||= { :conditions => [] } end |
#execute ⇒ Object
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'app/concepts/lockstep/query.rb', line 218 def execute return [] if criteria[:none] # This code automatically adds all related objects # # if @klass.has_many_relations # relations = @klass.has_many_relations.select {|k, val| val[:included]}.keys.map { |relation| relation.to_s } # include_object(*relations) # end params = build_params return chunk_results(params) if criteria[:chunk] get_results(params) end |
#find(id) ⇒ Lockstep::ApiRecord
Find a Lockstep::ApiRecord object by ID
347 348 349 350 351 352 |
# File 'app/concepts/lockstep/query.rb', line 347 def find(id) raise Lockstep::Exceptions::RecordNotFound, "Couldn't find #{name} without an ID" if id.blank? record = where(@klass.id_ref => id).first raise Lockstep::Exceptions::RecordNotFound, "Couldn't find #{name} with id: #{id}" if record.blank? record end |
#first ⇒ Object
330 331 332 333 |
# File 'app/concepts/lockstep/query.rb', line 330 def first limit(1) execute.first end |
#get_relation_objects(objects) ⇒ Object
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'app/concepts/lockstep/query.rb', line 266 def get_relation_objects(objects) return objects if criteria[:include].blank? included_objects = criteria[:include].map { |item| item.to_s.downcase } if @klass.has_many_relations objects.each do |item| @klass.has_many_relations.each do |relation, relation_config| next unless included_objects.include?(relation.to_s.downcase) if !item.attributes.has_key?(relation.to_s) item.attributes[relation.to_s] = Lockstep::RelationArray.new(@klass, [], relation, relation_config[:class_name]) elsif !item.attributes[relation].is_a?(Lockstep::RelationArray) item.attributes[relation.to_s] = Lockstep::RelationArray.new(@klass, item.attributes[relation], relation, relation_config[:class_name]) end end end end objects.each do |item| item.attributes.each do |key, value| if value.is_a?(Array) relation = @klass.has_many_relations[key] next if relation.blank? or !included_objects.include?(key.to_s.downcase) value.each do |relation_hash| relation_obj = turn_relation_hash_into_object(relation, relation_hash) value[value.index(relation_hash)] = relation_obj end elsif value.is_a?(Hash) relation = @klass.belongs_to_relations[key] next if relation.blank? relation_hash = value relation_obj = turn_relation_hash_into_object(relation, relation_hash) item.attributes[key] = relation_obj end end end objects end |
#get_results(params = {}) ⇒ Object
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'app/concepts/lockstep/query.rb', line 234 def get_results(params = {}) resp = @klass.resource.get(@klass.query_path, :params => params) return [] if %w(404).include?(resp.code.to_s) # TODO handle non 200 response code. Throwing an exception for now raise StandardError.new("#{resp.code} error while fetching: #{resp.body}") unless %w(201 200).include?(resp.code.to_s) parsed_response = JSON.parse(resp.body) if criteria[:count] raise StandardError.new("Count is not supported for #{@klass}") if parsed_response.is_a?(Array) results = parsed_response["totalCount"] return results.to_i else results = parsed_response.is_a?(Array) ? parsed_response : parsed_response["records"] return [] if results.blank? results = results[0..(criteria[:limit] - 1)] if criteria[:limit] records = get_relation_objects results.map { |r| # Convert camelcase to snake-case r = r.transform_keys { |key| key.underscore } @klass.model_name.to_s.constantize.new(r, false) } if criteria[:with_count] [records, parsed_response["totalCount"]] else records end end end |
#include_object(*objects) ⇒ Object
87 88 89 90 91 92 93 |
# File 'app/concepts/lockstep/query.rb', line 87 def include_object(*objects) with_clone do criteria[:include] ||= [] criteria[:include] += objects criteria[:include].uniq! end end |
#limit(limit) ⇒ Object
73 74 75 76 77 78 79 |
# File 'app/concepts/lockstep/query.rb', line 73 def limit(limit) with_clone do # If > 1000, set chunking, because large queries over 1000 need it with Parse # criteria[:chunk] = 1000 if limit > 1000 criteria[:limit] = limit end end |
#model ⇒ Object
6 7 8 |
# File 'app/concepts/lockstep/query.rb', line 6 def model @klass end |
#none ⇒ Object
36 37 38 39 40 |
# File 'app/concepts/lockstep/query.rb', line 36 def none with_clone do criteria[:none] = true end end |
#or(query) ⇒ Object
30 31 32 33 34 |
# File 'app/concepts/lockstep/query.rb', line 30 def or(query) with_clone do criteria[:conditions] << { type: "or", query: query } end end |
#order(*attr) ⇒ Object
attr: :desc
102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'app/concepts/lockstep/query.rb', line 102 def order(*attr) with_clone do criteria[:order] ||= [] attr.each do |item| if item.is_a?(Hash) item.each do |k, v| criteria[:order] << "#{k.to_s.camelize.upcase} #{v}" end elsif item.is_a?(String) criteria[:order] << item end end end end |
#page(page_number) ⇒ Object
81 82 83 84 85 |
# File 'app/concepts/lockstep/query.rb', line 81 def page(page_number) with_clone do criteria[:page_number] = page_number end end |
#reorder(attr) ⇒ Object
attr: :desc
118 119 120 121 122 123 |
# File 'app/concepts/lockstep/query.rb', line 118 def reorder(attr) with_clone do criteria[:order] = [] order(attr) end end |
#respond_to?(meth) ⇒ Boolean
385 386 387 388 389 390 391 |
# File 'app/concepts/lockstep/query.rb', line 385 def respond_to?(meth) if Array.method_defined?(meth) true else super end end |
#unscoped ⇒ Object
393 394 395 |
# File 'app/concepts/lockstep/query.rb', line 393 def unscoped Lockstep::Query.new(@klass) end |
#where(args) ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 |
# File 'app/concepts/lockstep/query.rb', line 42 def where(args) if args.is_a?(Hash) args.each do |k, v| return none if v.is_a?(Array) and v.blank? end end with_clone do criteria[:conditions] << convert_arg(args) end end |
#with_clone(&block) ⇒ Object
20 21 22 23 24 |
# File 'app/concepts/lockstep/query.rb', line 20 def with_clone(&block) c = clone c.instance_exec(&block) c end |
#with_count(value) ⇒ Object
95 96 97 98 99 |
# File 'app/concepts/lockstep/query.rb', line 95 def with_count(value) with_clone do criteria[:with_count] = value end end |