Query class represents a query which will be run against the data-store. Generally Query objects can be found inside Collection objects.
Returns the repository query should be executed in
Set in cases like the following:
@example
Document.all(:repository => :medline)
@return [Repository]
the Repository to retrieve results from
@api semipublic
Returns model (class) that is used to instantiate objects from query result returned by adapter
@return [Model]
the Model to retrieve results from
@api semipublic
Returns the fields
Set in cases like the following:
@example
Document.all(:fields => [:title, :vernacular_title, :abstract])
@return [PropertySet]
the properties in the Model that will be retrieved
@api semipublic
Returns the links (associations) query fetches
@return [Array
the relationships that will be used to scope the results
@api private
Returns the conditions of the query
In the following example:
@example
Team.all(:wins.gt => 30, :conference => 'East')
Conditions are “greater than” operator for “wins” field and exact match operator for “conference”.
@return [Array]
the conditions that will be used to scope the results
@api semipublic
Returns the offset query uses
Set in cases like the following:
@example
Document.all(:offset => page.offset)
@return [Integer]
the offset of the results
@api semipublic
Returns the limit query uses
Set in cases like the following:
@example
Document.all(:limit => 10)
@return [Integer, nil]
the maximum number of results
@api semipublic
Initializes a Query instance
@example
JournalIssue.all(:repository => :medline, :created_on.gte => Date.today - 7)
initialized a query with repository defined with name :medline, model JournalIssue and options { :created_on.gte => Date.today - 7 }
@param [Repository] repository
the Repository to retrieve results from
@param [Model] model
the Model to retrieve results from
@param [Hash] options
the conditions and scope
@api semipublic
# File lib/dm-core/query.rb, line 714 714: def initialize(repository, model, options = {}) 715: assert_kind_of 'repository', repository, Repository 716: assert_kind_of 'model', model, Model 717: 718: @repository = repository 719: @model = model 720: @options = options.dup.freeze 721: 722: repository_name = repository.name 723: 724: @properties = @model.properties(repository_name) 725: @relationships = @model.relationships(repository_name) 726: 727: assert_valid_options(@options) 728: 729: @fields = @options.fetch :fields, @properties.defaults 730: @links = @options.key?(:links) ? @options[:links].dup : [] 731: @conditions = Conditions::Operation.new(:null) 732: @offset = @options.fetch :offset, 0 733: @limit = @options.fetch :limit, nil 734: @order = @options.fetch :order, @model.default_order(repository_name) 735: @unique = @options.fetch :unique, true 736: @add_reversed = @options.fetch :add_reversed, false 737: @reload = @options.fetch :reload, false 738: @raw = false 739: 740: merge_conditions([ DataMapper::Ext::Hash.except(@options, *OPTIONS), @options[:conditions] ]) 741: normalize_options 742: end
Extract conditions to match a Resource or Collection
@param [Array, Collection, Resource] source
the source to extract the values from
@param [ProperySet] source_key
the key to extract the value from the resource
@param [ProperySet] target_key
the key to match the resource with
@return [AbstractComparison, AbstractOperation]
the conditions to match the resources with
@api private
# File lib/dm-core/query.rb, line 49 49: def self.target_conditions(source, source_key, target_key) 50: target_key_size = target_key.size 51: source_values = [] 52: 53: if source.nil? 54: source_values << [ nil ] * target_key_size 55: else 56: Array(source).each do |resource| 57: next unless source_key.loaded?(resource) 58: source_value = source_key.get!(resource) 59: next unless target_key.valid?(source_value) 60: source_values << source_value 61: end 62: end 63: 64: source_values.uniq! 65: 66: if target_key_size == 1 67: target_key = target_key.first 68: source_values.flatten! 69: 70: if source_values.size == 1 71: Conditions::EqualToComparison.new(target_key, source_values.first) 72: else 73: Conditions::InclusionComparison.new(target_key, source_values) 74: end 75: else 76: or_operation = Conditions::OrOperation.new 77: 78: source_values.each do |source_value| 79: and_operation = Conditions::AndOperation.new 80: 81: target_key.zip(source_value) do |property, value| 82: and_operation << Conditions::EqualToComparison.new(property, value) 83: end 84: 85: or_operation << and_operation 86: end 87: 88: or_operation 89: end 90: end
@param [Repository] repository
the default repository to scope the query within
@param [Model] model
the default model for the query
@param [#, Enumerable] source
the source to generate the query with
@return [Query]
the query to match the resources with
@api private
# File lib/dm-core/query.rb, line 103 103: def self.target_query(repository, model, source) 104: if source.respond_to?(:query) 105: source.query 106: elsif source.kind_of?(Enumerable) 107: key = model.key(repository.name) 108: conditions = Query.target_conditions(source, key, key) 109: repository.new_query(model, :conditions => conditions) 110: else 111: raise ArgumentError, "+source+ must respond to #query or be an Enumerable, but was #{source.class}" 112: end 113: end
Indicates if each result should be returned in reverse order
Set in cases like the following:
@example
Document.all(:limit => 5).reverse
Note that :add_reversed option may be used in conditions directly, but this is rarely the case
@return [Boolean]
true if the results should be reversed, false if not
@api private
# File lib/dm-core/query.rb, line 248 248: def add_reversed? 249: @add_reversed 250: end
Clear conditions
@return [self]
@api semipublic
# File lib/dm-core/query.rb, line 472 472: def clear 473: @conditions = Conditions::Operation.new(:null) 474: self 475: end
Get the properties used in the conditions
@return [Set
Set of properties used in the conditions
@api private
# File lib/dm-core/query.rb, line 627 627: def condition_properties 628: properties = Set.new 629: 630: each_comparison do |comparison| 631: next unless comparison.respond_to?(:subject) 632: subject = comparison.subject 633: properties << subject if subject.kind_of?(Property) 634: end 635: 636: properties 637: end
Return the difference with another query
@param [Query] other
the other query
@return [Query]
the difference of the query and other
@api semipublic
# File lib/dm-core/query.rb, line 461 461: def difference(other) 462: set_operation(:difference, other) 463: end
Takes an Enumerable of records, and destructively filters it. First finds all matching conditions, then sorts it, then does offset & limit
@param [Enumerable] records
The set of records to be filtered
@return [Enumerable]
Whats left of the given array after the filtering
@api semipublic
# File lib/dm-core/query.rb, line 488 488: def filter_records(records) 489: records = records.uniq if unique? 490: records = match_records(records) if conditions 491: records = sort_records(records) if order 492: records = limit_records(records) if limit || offset > 0 493: records 494: end
Returns detailed human readable string representation of the query
@return [String] detailed string representation of the query
@api semipublic
# File lib/dm-core/query.rb, line 604 604: def inspect 605: attrs = [ 606: [ :repository, repository.name ], 607: [ :model, model ], 608: [ :fields, fields ], 609: [ :links, links ], 610: [ :conditions, conditions ], 611: [ :order, order ], 612: [ :limit, limit ], 613: [ :offset, offset ], 614: [ :reload, reload? ], 615: [ :unique, unique? ], 616: ] 617: 618: "#<#{self.class.name} #{attrs.map { |key, value| "@#{key}=#{value.inspect}" }.join(' ')}>" 619: end
Return the intersection with another query
@param [Query] other
the other query
@return [Query]
the intersection of the query and other
@api semipublic
# File lib/dm-core/query.rb, line 445 445: def intersection(other) 446: return dup if self == other 447: set_operation(:intersection, other) 448: end
Limits a set of records by the offset and/or limit
@param [Enumerable] records
A list of records to sort
@return [Enumerable]
The offset & limited records
@api semipublic
# File lib/dm-core/query.rb, line 538 538: def limit_records(records) 539: offset = self.offset 540: limit = self.limit 541: size = records.size 542: 543: if offset > size - 1 544: [] 545: elsif (limit && limit != size) || offset > 0 546: records[offset, limit || size] || [] 547: else 548: records.dup 549: end 550: end
Filter a set of records by the conditions
@param [Enumerable] records
The set of records to be filtered
@return [Enumerable]
Whats left of the given array after the matching
@api semipublic
# File lib/dm-core/query.rb, line 505 505: def match_records(records) 506: conditions = self.conditions 507: records.select { |record| conditions.matches?(record) } 508: end
Similar to Query#update, but acts on a duplicate.
@param [Query, Hash] other
other query to merge with
@return [Query]
updated duplicate of original query
@api semipublic
# File lib/dm-core/query.rb, line 385 385: def merge(other) 386: dup.update(other) 387: end
Indicates if the Query has raw conditions
@return [Boolean]
true if the query has raw conditions, false if not
@api semipublic
# File lib/dm-core/query.rb, line 282 282: def raw? 283: @raw 284: end
Builds and returns new query that merges original with one given, and slices the result with respect to :limit and :offset options
This method is used by Collection to concatenate options from multiple chained calls in cases like the following:
@example
author.books.all(:year => 2009).all(:published => false)
@api semipublic
# File lib/dm-core/query.rb, line 402 402: def relative(options) 403: options = options.to_hash 404: 405: offset = nil 406: limit = self.limit 407: 408: if options.key?(:offset) && (options.key?(:limit) || limit) 409: options = options.dup 410: offset = options.delete(:offset) 411: limit = options.delete(:limit) || limit - offset 412: end 413: 414: query = merge(options) 415: query = query.slice!(offset, limit) if offset 416: query 417: end
Indicates if the Query results should replace the results in the Identity Map
TODO: needs example
@return [Boolean]
true if the results should be reloaded, false if not
@api semipublic
# File lib/dm-core/query.rb, line 260 260: def reload? 261: @reload 262: end
Returns a new Query with a reversed order
@example
Document.all(:limit => 5).reverse
Will execute a single query with correct order
@return [Query]
new Query with reversed order
@api semipublic
# File lib/dm-core/query.rb, line 308 308: def reverse 309: dup.reverse! 310: end
Reverses the sort order of the Query
@example
Document.all(:limit => 5).reverse
Will execute a single query with original order and then reverse collection in the Ruby space
@return [Query]
self
@api semipublic
# File lib/dm-core/query.rb, line 325 325: def reverse! 326: # reverse the sort order 327: @order.map! { |direction| direction.dup.reverse! } 328: 329: # copy the order to the options 330: @options = @options.merge(:order => @order).freeze 331: 332: self 333: end
Slices collection by adding limit and offset to the query, so a single query is executed
@example
Journal.all(:limit => 10).slice(3, 5)
will execute query with the following limit and offset (when repository uses DataObjects adapter, and thus queries use SQL):
LIMIT 5 OFFSET 3
@api semipublic
# File lib/dm-core/query.rb, line 566 566: def slice(*args) 567: dup.slice!(*args) 568: end
Slices collection by adding limit and offset to the query, so a single query is executed
@example
Journal.all(:limit => 10).slice!(3, 5)
will execute query with the following limit (when repository uses DataObjects adapter, and thus queries use SQL):
LIMIT 10
and then takes a slice of collection in the Ruby space
@api semipublic
# File lib/dm-core/query.rb, line 588 588: def slice!(*args) 589: offset, limit = extract_slice_arguments(*args) 590: 591: if self.limit || self.offset > 0 592: offset, limit = get_relative_position(offset, limit) 593: end 594: 595: update(:offset => offset, :limit => limit) 596: end
Sorts a list of Records by the order
@param [Enumerable] records
A list of Resources to sort
@return [Enumerable]
The sorted records
@api semipublic
# File lib/dm-core/query.rb, line 519 519: def sort_records(records) 520: sort_order = order.map { |direction| [ direction.target, direction.operator == :asc ] } 521: 522: records.sort_by do |record| 523: sort_order.map do |(property, ascending)| 524: Sort.new(record_value(record, property), ascending) 525: end 526: end 527: end
Return a list of fields in predictable order
@return [Array
list of fields sorted in deterministic order
@api private
# File lib/dm-core/query.rb, line 645 645: def sorted_fields 646: fields.sort_by { |property| property.hash } 647: end
Hash representation of a Query
@return [Hash]
Hash representation of a Query
@api private
# File lib/dm-core/query.rb, line 666 666: def to_hash 667: { 668: :repository => repository.name, 669: :model => model.name, 670: :fields => fields, 671: :links => links, 672: :conditions => conditions, 673: :offset => offset, 674: :limit => limit, 675: :order => order, 676: :unique => unique?, 677: :add_reversed => add_reversed?, 678: :reload => reload?, 679: } 680: end
Extract options from a Query
@param [Query] query
the query to extract options from
@return [Hash]
the options to use to initialize the new query
@api private
# File lib/dm-core/query.rb, line 691 691: def to_relative_hash 692: DataMapper::Ext::Hash.only(to_hash, :fields, :order, :unique, :add_reversed, :reload) 693: end
Transform Query into subquery conditions
@return [AndOperation]
a subquery for the Query
@api private
# File lib/dm-core/query.rb, line 655 655: def to_subquery 656: collection = model.all(merge(:fields => model_key)) 657: Conditions::Operation.new(:and, Conditions::Comparison.new(:in, self_relationship, collection)) 658: end
Return the union with another query
@param [Query] other
the other query
@return [Query]
the union of the query and other
@api semipublic
# File lib/dm-core/query.rb, line 428 428: def union(other) 429: return dup if self == other 430: set_operation(:union, other) 431: end
Indicates if the Query results should be unique
TODO: needs example
@return [Boolean]
true if the results should be unique, false if not
@api semipublic
# File lib/dm-core/query.rb, line 272 272: def unique? 273: @unique 274: end
Updates the Query with another Query or conditions
Pretty unrealistic example:
@example
Journal.all(:limit => 2).query.limit # => 2 Journal.all(:limit => 2).query.update(:limit => 3).limit # => 3
@param [Query, Hash] other
other Query or conditions
@return [Query]
self
@api semipublic
# File lib/dm-core/query.rb, line 351 351: def update(other) 352: other_options = if kind_of?(other.class) 353: return self if self.eql?(other) 354: assert_valid_other(other) 355: other.options 356: else 357: other = other.to_hash 358: return self if other.empty? 359: other 360: end 361: 362: @options = @options.merge(other_options).freeze 363: assert_valid_options(@options) 364: 365: normalize = DataMapper::Ext::Hash.only(other_options, *OPTIONS - [ :conditions ]).map do |attribute, value| 366: instance_variable_set("@#{attribute}", DataMapper::Ext.try_dup(value)) 367: attribute 368: end 369: 370: merge_conditions([ DataMapper::Ext::Hash.except(other_options, *OPTIONS), other_options[:conditions] ]) 371: normalize_options(normalize | [ :links, :unique ]) 372: 373: self 374: end
Indicates if the Query is valid
@return [Boolean]
true if the query is valid
@api semipublic
# File lib/dm-core/query.rb, line 292 292: def valid? 293: conditions.valid? 294: end
Add a condition to the Query
@param [AbstractOperation, AbstractComparison]
the condition to add to the Query
@return [undefined]
@api private
# File lib/dm-core/query.rb, line 1240 1240: def add_condition(condition) 1241: @conditions = Conditions::Operation.new(:and) if @conditions.nil? 1242: @conditions << condition 1243: end
Append conditions to this Query
TODO: needs example
@param [Property, Symbol, String, Operator, Associations::Relationship, Path] subject
the subject to match
@param [Object] bind_value
the value to match on
@param [Symbol] operator
the operator to match with
@return [Query::Conditions::AbstractOperation]
the Query conditions
@api private
# File lib/dm-core/query.rb, line 1146 1146: def append_condition(subject, bind_value, model = self.model, operator = :eql) 1147: case subject 1148: when Property, Associations::Relationship then append_property_condition(subject, bind_value, operator) 1149: when Symbol then append_symbol_condition(subject, bind_value, model, operator) 1150: when String then append_string_condition(subject, bind_value, model, operator) 1151: when Operator then append_operator_conditions(subject, bind_value, model) 1152: when Path then append_path(subject, bind_value, model, operator) 1153: else 1154: raise ArgumentError, "#{subject} is an invalid instance: #{subject.class}" 1155: end 1156: end
@api private
# File lib/dm-core/query.rb, line 1218 1218: def append_operator_conditions(operator, bind_value, model) 1219: append_condition(operator.target, bind_value, model, operator.operator) 1220: end
@api private
# File lib/dm-core/query.rb, line 1223 1223: def append_path(path, bind_value, model, operator) 1224: path.relationships.each do |relationship| 1225: inverse = relationship.inverse 1226: @links.unshift(inverse) unless @links.include?(inverse) 1227: end 1228: 1229: append_condition(path.property, bind_value, path.model, operator) 1230: end
@api private
# File lib/dm-core/query.rb, line 1169 1169: def append_property_condition(subject, bind_value, operator) 1170: negated = operator == :not 1171: 1172: if operator == :eql || negated 1173: # transform :relationship => nil into :relationship.not => association 1174: if subject.respond_to?(:collection_for) && bind_value.nil? 1175: negated = !negated 1176: bind_value = collection_for_nil(subject) 1177: end 1178: 1179: operator = equality_operator_for_type(bind_value) 1180: end 1181: 1182: condition = Conditions::Comparison.new(operator, subject, bind_value) 1183: 1184: if negated 1185: condition = Conditions::Operation.new(:not, condition) 1186: end 1187: 1188: add_condition(condition) 1189: end
@api private
# File lib/dm-core/query.rb, line 1197 1197: def append_string_condition(string, bind_value, model, operator) 1198: if string.include?('.') 1199: query_path = model 1200: 1201: target_components = string.split('.') 1202: last_component = target_components.last 1203: operator = target_components.pop.to_sym if DataMapper::Query::Conditions::Comparison.slugs.any? { |slug| slug.to_s == last_component } 1204: 1205: target_components.each { |method| query_path = query_path.send(method) } 1206: 1207: append_condition(query_path, bind_value, model, operator) 1208: else 1209: repository_name = repository.name 1210: subject = model.properties(repository_name)[string] || 1211: model.relationships(repository_name)[string] 1212: 1213: append_condition(subject, bind_value, model, operator) 1214: end 1215: end
@api private
# File lib/dm-core/query.rb, line 1192 1192: def append_symbol_condition(symbol, bind_value, model, operator) 1193: append_condition(symbol.to_s, bind_value, model, operator) 1194: end
Used to verify value of boolean properties in conditions @api private
# File lib/dm-core/query.rb, line 975 975: def assert_valid_boolean(name, value) 976: if value != true && value != false 977: raise ArgumentError, "+#{name}+ should be true or false, but was #{value.inspect}" 978: end 979: end
Verifies that value of :conditions option refers to existing properties
@api private
# File lib/dm-core/query.rb, line 848 848: def assert_valid_conditions(conditions) 849: assert_kind_of 'options[:conditions]', conditions, Conditions::AbstractOperation, Conditions::AbstractComparison, Hash, Array 850: 851: case conditions 852: when Hash 853: conditions.each do |subject, bind_value| 854: case subject 855: when Symbol, ::String 856: original = subject 857: subject = subject.to_s 858: name = subject[0, subject.index('.') || subject.length] 859: 860: unless @properties.named?(name) || @relationships.named?(name) 861: raise ArgumentError, "condition #{original.inspect} does not map to a property or relationship in #{model}" 862: end 863: 864: when Property 865: unless @properties.include?(subject) 866: raise ArgumentError, "condition #{subject.name.inspect} does not map to a property in #{model}, but belongs to #{subject.model}" 867: end 868: 869: when Operator 870: operator = subject.operator 871: 872: unless Conditions::Comparison.slugs.include?(operator) || operator == :not 873: raise ArgumentError, "condition #{subject.inspect} used an invalid operator #{operator}" 874: end 875: 876: assert_valid_conditions(subject.target => bind_value) 877: 878: when Path 879: assert_valid_links(subject.relationships) 880: 881: when Associations::Relationship 882: # TODO: validate that it belongs to the current model 883: #unless subject.source_model.equal?(model) 884: # raise ArgumentError, "condition #{subject.name.inspect} is not a valid relationship for #{model}, it's source model was #{subject.source_model}" 885: #end 886: 887: else 888: raise ArgumentError, "condition #{subject.inspect} of an unsupported object #{subject.class}" 889: end 890: end 891: 892: when Array 893: if conditions.empty? 894: raise ArgumentError, '+options[:conditions]+ should not be empty' 895: end 896: 897: first_condition = conditions.first 898: 899: unless first_condition.kind_of?(String) && !DataMapper::Ext.blank?(first_condition) 900: raise ArgumentError, '+options[:conditions]+ should have a statement for the first entry' 901: end 902: end 903: end
Verifies that value of :fields option refers to existing properties
@api private
# File lib/dm-core/query.rb, line 785 785: def assert_valid_fields(fields, unique) 786: fields = fields.to_ary 787: 788: model = self.model 789: 790: valid_properties = model.properties 791: 792: model.descendants.each do |descendant| 793: valid_properties += descendant.properties 794: end 795: 796: fields.each do |field| 797: case field 798: when Symbol, String 799: unless valid_properties.named?(field) 800: raise ArgumentError, "+options[:fields]+ entry #{field.inspect} does not map to a property in #{model}" 801: end 802: 803: when Property 804: unless valid_properties.include?(field) 805: raise ArgumentError, "+options[:field]+ entry #{field.name.inspect} does not map to a property in #{model}" 806: end 807: 808: else 809: raise ArgumentError, "+options[:fields]+ entry #{field.inspect} of an unsupported object #{field.class}" 810: end 811: end 812: end
Verifies the limit is equal to or greater than 0
@raise [ArgumentError]
raised if the limit is not an Integer or less than 0
@api private
# File lib/dm-core/query.rb, line 925 925: def assert_valid_limit(limit) 926: limit = limit.to_int 927: 928: unless limit >= 0 929: raise ArgumentError, "+options[:limit]+ must be greater than or equal to 0, but was #{limit.inspect}" 930: end 931: end
Verifies that value of :links option refers to existing associations
@api private
# File lib/dm-core/query.rb, line 818 818: def assert_valid_links(links) 819: links = links.to_ary 820: 821: if links.empty? 822: raise ArgumentError, '+options[:links]+ should not be empty' 823: end 824: 825: links.each do |link| 826: case link 827: when Symbol, String 828: unless @relationships.named?(link.to_sym) 829: raise ArgumentError, "+options[:links]+ entry #{link.inspect} does not map to a relationship in #{model}" 830: end 831: 832: when Associations::Relationship 833: # TODO: figure out how to validate links from other models 834: #unless @relationships.value?(link) 835: # raise ArgumentError, "+options[:links]+ entry #{link.name.inspect} does not map to a relationship in #{model}" 836: #end 837: 838: else 839: raise ArgumentError, "+options[:links]+ entry #{link.inspect} of an unsupported object #{link.class}" 840: end 841: end 842: end
Verifies that query offset is non-negative and only used together with limit @api private
# File lib/dm-core/query.rb, line 907 907: def assert_valid_offset(offset, limit) 908: offset = offset.to_int 909: 910: unless offset >= 0 911: raise ArgumentError, "+options[:offset]+ must be greater than or equal to 0, but was #{offset.inspect}" 912: end 913: 914: if offset > 0 && limit.nil? 915: raise ArgumentError, '+options[:offset]+ cannot be greater than 0 if limit is not specified' 916: end 917: end
Validate the options
@param [#] options
the options to validate
@raise [ArgumentError]
if any pairs in +options+ are invalid options
@api private
# File lib/dm-core/query.rb, line 763 763: def assert_valid_options(options) 764: options = options.to_hash 765: 766: options.each do |attribute, value| 767: case attribute 768: when :fields then assert_valid_fields(value, options[:unique]) 769: when :links then assert_valid_links(value) 770: when :conditions then assert_valid_conditions(value) 771: when :offset then assert_valid_offset(value, options[:limit]) 772: when :limit then assert_valid_limit(value) 773: when :order then assert_valid_order(value, options[:fields]) 774: when :unique, :add_reversed, :reload then assert_valid_boolean("options[:#{attribute}]", value) 775: else 776: assert_valid_conditions(attribute => value) 777: end 778: end 779: end
Verifies that :order option uses proper operator and refers to existing property
@api private
# File lib/dm-core/query.rb, line 937 937: def assert_valid_order(order, fields) 938: return if order.nil? 939: 940: order = Array(order) 941: if order.empty? && fields && fields.any? { |property| !property.kind_of?(Operator) } 942: raise ArgumentError, '+options[:order]+ should not be empty if +options[:fields] contains a non-operator' 943: end 944: 945: model = self.model 946: 947: order.each do |order_entry| 948: case order_entry 949: when Symbol, String 950: unless @properties.named?(order_entry) 951: raise ArgumentError, "+options[:order]+ entry #{order_entry.inspect} does not map to a property in #{model}" 952: end 953: 954: when Property, Path 955: # Allow any arbitrary property, since it may map to a model 956: # that has been included via the :links option 957: 958: when Operator, Direction 959: operator = order_entry.operator 960: 961: unless operator == :asc || operator == :desc 962: raise ArgumentError, "+options[:order]+ entry #{order_entry.inspect} used an invalid operator #{operator}" 963: end 964: 965: assert_valid_order([ order_entry.target ], fields) 966: 967: else 968: raise ArgumentError, "+options[:order]+ entry #{order_entry.inspect} of an unsupported object #{order_entry.class}" 969: end 970: end 971: end
Verifies that associations given in conditions belong to the same repository as query’s model
@api private
# File lib/dm-core/query.rb, line 985 985: def assert_valid_other(other) 986: other_repository = other.repository 987: repository = self.repository 988: other_class = other.class 989: 990: unless other_repository == repository 991: raise ArgumentError, "+other+ #{other_class} must be for the #{repository.name} repository, not #{other_repository.name}" 992: end 993: 994: other_model = other.model 995: model = self.model 996: 997: unless other_model >= model 998: raise ArgumentError, "+other+ #{other_class} must be for the #{model.name} model, not #{other_model.name}" 999: end 1000: end
@api private
# File lib/dm-core/query.rb, line 1316 1316: def collection_for_nil(relationship) 1317: query = relationship.query.dup 1318: 1319: relationship.target_key.each do |target_key| 1320: query[target_key.name.not] = nil if target_key.allow_nil? 1321: end 1322: 1323: relationship.target_model.all(query) 1324: end
@api private
# File lib/dm-core/query.rb, line 1327 1327: def each_comparison 1328: operands = conditions.operands.to_a 1329: 1330: while operand = operands.shift 1331: if operand.respond_to?(:operands) 1332: operands.unshift(*operand.operands) 1333: else 1334: yield operand 1335: end 1336: end 1337: end
@api private
# File lib/dm-core/query.rb, line 1159 1159: def equality_operator_for_type(bind_value) 1160: case bind_value 1161: when Model, String then :eql 1162: when Enumerable then :in 1163: when Regexp then :regexp 1164: else :eql 1165: end 1166: end
@api private
# File lib/dm-core/query.rb, line 1279 1279: def extract_offset_limit_from_integer(integer) 1280: [ integer, 1 ] 1281: end
@api private
# File lib/dm-core/query.rb, line 1271 1271: def extract_offset_limit_from_one_argument(arg) 1272: case arg 1273: when Integer then extract_offset_limit_from_integer(arg) 1274: when Range then extract_offset_limit_from_range(arg) 1275: end 1276: end
@api private
# File lib/dm-core/query.rb, line 1284 1284: def extract_offset_limit_from_range(range) 1285: offset = range.first 1286: limit = range.last - offset 1287: limit = limit.succ unless range.exclude_end? 1288: return offset, limit 1289: end
@api private
# File lib/dm-core/query.rb, line 1266 1266: def extract_offset_limit_from_two_arguments(*args) 1267: args if args.all? { |arg| arg.kind_of?(Integer) } 1268: end
Extract arguments for # and # then return offset and limit
@param [Integer, Array(Integer), Range] *args the offset,
offset and limit, or range indicating first and last position
@return [Integer] the offset @return [Integer, nil] the limit, if any
@api private
# File lib/dm-core/query.rb, line 1254 1254: def extract_slice_arguments(*args) 1255: offset, limit = case args.size 1256: when 2 then extract_offset_limit_from_two_arguments(*args) 1257: when 1 then extract_offset_limit_from_one_argument(*args) 1258: end 1259: 1260: return offset, limit if offset && limit 1261: 1262: raise ArgumentError, "arguments may be 1 or 2 Integers, or 1 Range object, was: #{args.inspect}" 1263: end
@api private
# File lib/dm-core/query.rb, line 1292 1292: def get_relative_position(offset, limit) 1293: self_offset = self.offset 1294: self_limit = self.limit 1295: new_offset = self_offset + offset 1296: 1297: if limit <= 0 || (self_limit && new_offset + limit > self_offset + self_limit) 1298: raise RangeError, "offset #{offset} and limit #{limit} are outside allowed range" 1299: end 1300: 1301: return new_offset, limit 1302: end
Copying contructor, called for Query#dup
@api semipublic
# File lib/dm-core/query.rb, line 747 747: def initialize_copy(*) 748: @fields = @fields.dup 749: @links = @links.dup 750: @conditions = @conditions.dup 751: @order = DataMapper::Ext.try_dup(@order) 752: end
Handle all the conditions options provided
@param [Array<Conditions::AbstractOperation, Conditions::AbstractComparison, Hash, Array>]
a list of conditions
@return [undefined]
@api private
# File lib/dm-core/query.rb, line 1010 1010: def merge_conditions(conditions) 1011: @conditions = Conditions::Operation.new(:and) << @conditions unless @conditions.nil? 1012: 1013: conditions.compact! 1014: conditions.each do |condition| 1015: case condition 1016: when Conditions::AbstractOperation, Conditions::AbstractComparison 1017: add_condition(condition) 1018: 1019: when Hash 1020: condition.each { |kv| append_condition(*kv) } 1021: 1022: when Array 1023: statement, *bind_values = *condition 1024: raw_condition = [ statement ] 1025: raw_condition << bind_values if bind_values.size > 0 1026: add_condition(raw_condition) 1027: @raw = true 1028: end 1029: end 1030: end
Return the model key
@return [PropertySet]
the model key
@api private
# File lib/dm-core/query.rb, line 1440 1440: def model_key 1441: @properties.key 1442: end
Normalize fields to Property instances
@api private
# File lib/dm-core/query.rb, line 1081 1081: def normalize_fields 1082: @fields = @fields.map do |field| 1083: case field 1084: when Symbol, String 1085: @properties[field] 1086: 1087: when Property, Operator 1088: field 1089: end 1090: end 1091: end
Normalize links to Query::Path
Normalization means links given as symbols are replaced with relationships they refer to, intermediate links are “followed” and duplicates are removed
@api private
# File lib/dm-core/query.rb, line 1100 1100: def normalize_links 1101: stack = @links.dup 1102: 1103: @links.clear 1104: 1105: while link = stack.pop 1106: relationship = case link 1107: when Symbol, String then @relationships[link] 1108: when Associations::Relationship then link 1109: end 1110: 1111: if relationship.respond_to?(:links) 1112: stack.concat(relationship.links) 1113: elsif !@links.include?(relationship) 1114: @links << relationship 1115: end 1116: end 1117: 1118: @links.reverse! 1119: end
Normalize options
@param [Array
the options to normalize
@return [undefined]
@api private
# File lib/dm-core/query.rb, line 1040 1040: def normalize_options(options = OPTIONS) 1041: normalize_order if options.include? :order 1042: normalize_fields if options.include? :fields 1043: normalize_links if options.include? :links 1044: normalize_unique if options.include? :unique 1045: end
Normalize order elements to Query::Direction instances
@api private
# File lib/dm-core/query.rb, line 1050 1050: def normalize_order 1051: return if @order.nil? 1052: 1053: @order = Array(@order) 1054: @order = @order.map do |order| 1055: case order 1056: when Operator 1057: target = order.target 1058: property = target.kind_of?(Property) ? target : @properties[target] 1059: 1060: Direction.new(property, order.operator) 1061: 1062: when Symbol, String 1063: Direction.new(@properties[order]) 1064: 1065: when Property 1066: Direction.new(order) 1067: 1068: when Direction 1069: order.dup 1070: 1071: when Path 1072: Direction.new(order.property) 1073: 1074: end 1075: end 1076: end
Normalize the unique attribute
If any links are present, and the unique attribute was not explicitly specified, then make sure the query is marked as unique
@api private
# File lib/dm-core/query.rb, line 1127 1127: def normalize_unique 1128: @unique = links.any? unless @options.key?(:unique) 1129: end
Return the union with another query’s conditions
@param [Query] other
the query conditions to union with
@return [OrOperation]
the union of the query conditions and other conditions
@api private
# File lib/dm-core/query.rb, line 1366 1366: def other_conditions(other, operation) 1367: self_conditions = query_conditions(self) 1368: 1369: unless self_conditions.kind_of?(Conditions::Operation) 1370: operation_slug = case operation 1371: when :intersection, :difference then :and 1372: when :union then :or 1373: end 1374: 1375: self_conditions = Conditions::Operation.new(operation_slug, self_conditions) 1376: end 1377: 1378: self_conditions.send(operation, query_conditions(other)) 1379: end
Extract conditions from a Query
@param [Query] query
the query with conditions
@return [AbstractOperation]
the operation
@api private
# File lib/dm-core/query.rb, line 1390 1390: def query_conditions(query) 1391: if query.limit || query.links.any? 1392: query.to_subquery 1393: else 1394: query.conditions 1395: end 1396: end
TODO: DRY this up with conditions @api private
# File lib/dm-core/query.rb, line 1306 1306: def record_value(record, property) 1307: case record 1308: when Hash 1309: record.fetch(property, record[property.field]) 1310: when Resource 1311: property.get!(record) 1312: end 1313: end
Return a self referrential relationship
@return [Associations::OneToMany::Relationship]
the 1:m association to the same model
@api private
# File lib/dm-core/query.rb, line 1404 1404: def self_relationship 1405: @self_relationship ||= 1406: begin 1407: model = self.model 1408: Associations::OneToMany::Relationship.new( 1409: :self, 1410: model, 1411: model, 1412: self_relationship_options 1413: ) 1414: end 1415: end
Return options for the self referrential relationship
@return [Hash]
the options to use with the self referrential relationship
@api private
# File lib/dm-core/query.rb, line 1423 1423: def self_relationship_options 1424: keys = model_key.map { |property| property.name } 1425: repository = self.repository 1426: { 1427: :child_key => keys, 1428: :parent_key => keys, 1429: :child_repository_name => repository.name, 1430: :parent_repository_name => repository.name, 1431: } 1432: end
Apply a set operation on self and another query
@param [Symbol] operation
the set operation to apply
@param [Query] other
the other query to apply the set operation on
@return [Query]
the query that was created for the set operation
@api private
# File lib/dm-core/query.rb, line 1350 1350: def set_operation(operation, other) 1351: assert_valid_other(other) 1352: query = self.class.new(@repository, @model, other.to_relative_hash) 1353: query.instance_variable_set(:@conditions, other_conditions(other, operation)) 1354: query 1355: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.