class DataMapper::Query
Query class represents a query which will be run against the data-store. Generally Query objects can be found inside Collection objects.
Public Class Methods
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 def initialize(repository, model, options = {}) assert_kind_of 'repository', repository, Repository assert_kind_of 'model', model, Model @repository = repository @model = model @options = options.dup.freeze repository_name = repository.name @properties = @model.properties(repository_name) @relationships = @model.relationships(repository_name) assert_valid_options(@options) @fields = @options.fetch :fields, @properties.defaults @links = @options.key?(:links) ? @options[:links].dup : [] @conditions = Conditions::Operation.new(:null) @offset = @options.fetch :offset, 0 @limit = @options.fetch :limit, nil @order = @options.fetch :order, @model.default_order(repository_name) @unique = @options.fetch :unique, true @add_reversed = @options.fetch :add_reversed, false @reload = @options.fetch :reload, false @raw = false merge_conditions([ DataMapper::Ext::Hash.except(@options, *OPTIONS), @options[:conditions] ]) normalize_options 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 def self.target_conditions(source, source_key, target_key) target_key_size = target_key.size source_values = [] if source.nil? source_values << [ nil ] * target_key_size else Array(source).each do |resource| next unless source_key.loaded?(resource) source_value = source_key.get!(resource) next unless target_key.valid?(source_value) source_values << source_value end end source_values.uniq! if target_key_size == 1 target_key = target_key.first source_values.flatten! if source_values.size == 1 Conditions::EqualToComparison.new(target_key, source_values.first) else Conditions::InclusionComparison.new(target_key, source_values) end else or_operation = Conditions::OrOperation.new source_values.each do |source_value| and_operation = Conditions::AndOperation.new target_key.zip(source_value) do |property, value| and_operation << Conditions::EqualToComparison.new(property, value) end or_operation << and_operation end or_operation end end
@param [Repository] repository
the default repository to scope the query within
@param [Model] model
the default model for the query
@param [#query, 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 def self.target_query(repository, model, source) if source.respond_to?(:query) source.query elsif source.kind_of?(Enumerable) key = model.key(repository.name) conditions = Query.target_conditions(source, key, key) repository.new_query(model, :conditions => conditions) else raise ArgumentError, "+source+ must respond to #query or be an Enumerable, but was #{source.class}" end end
Public Instance Methods
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 def add_reversed? @add_reversed end
Clear conditions
@return [self]
@api semipublic
# File lib/dm-core/query.rb, line 472 def clear @conditions = Conditions::Operation.new(:null) self end
Get the properties used in the conditions
@return [Set<Property>]
Set of properties used in the conditions
@api private
# File lib/dm-core/query.rb, line 627 def condition_properties properties = Set.new each_comparison do |comparison| next unless comparison.respond_to?(:subject) subject = comparison.subject properties << subject if subject.kind_of?(Property) end properties 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 def difference(other) set_operation(:difference, other) 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 def filter_records(records) records = records.uniq if unique? records = match_records(records) if conditions records = sort_records(records) if order records = limit_records(records) if limit || offset > 0 records 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 def inspect attrs = [ [ :repository, repository.name ], [ :model, model ], [ :fields, fields ], [ :links, links ], [ :conditions, conditions ], [ :order, order ], [ :limit, limit ], [ :offset, offset ], [ :reload, reload? ], [ :unique, unique? ], ] "#<#{self.class.name} #{attrs.map { |key, value| "@#{key}=#{value.inspect}" }.join(' ')}>" 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 def intersection(other) return dup if self == other set_operation(:intersection, other) 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 def limit_records(records) offset = self.offset limit = self.limit size = records.size if offset > size - 1 [] elsif (limit && limit != size) || offset > 0 records[offset, limit || size] || [] else records.dup end 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 def match_records(records) conditions = self.conditions records.select { |record| conditions.matches?(record) } end
Similar to #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 def merge(other) dup.update(other) 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 def raw? @raw 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 def relative(options) options = options.to_hash offset = nil limit = self.limit if options.key?(:offset) && (options.key?(:limit) || limit) options = options.dup offset = options.delete(:offset) limit = options.delete(:limit) || limit - offset end query = merge(options) query = query.slice!(offset, limit) if offset query 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 def reload? @reload 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 def reverse dup.reverse! 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 def reverse! # reverse the sort order @order.map! { |direction| direction.dup.reverse! } # copy the order to the options @options = @options.merge(:order => @order).freeze self 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 def slice(*args) dup.slice!(*args) 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 def slice!(*args) offset, limit = extract_slice_arguments(*args) if self.limit || self.offset > 0 offset, limit = get_relative_position(offset, limit) end update(:offset => offset, :limit => limit) 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 def sort_records(records) sort_order = order.map { |direction| [ direction.target, direction.operator == :asc ] } records.sort_by do |record| sort_order.map do |(property, ascending)| Sort.new(record_value(record, property), ascending) end end end
Return a list of fields in predictable order
@return [Array<Property>]
list of fields sorted in deterministic order
@api private
# File lib/dm-core/query.rb, line 645 def sorted_fields fields.sort_by { |property| property.hash } end
Hash representation of a Query
@return [Hash]
Hash representation of a Query
@api private
# File lib/dm-core/query.rb, line 666 def to_hash { :repository => repository.name, :model => model.name, :fields => fields, :links => links, :conditions => conditions, :offset => offset, :limit => limit, :order => order, :unique => unique?, :add_reversed => add_reversed?, :reload => reload?, } 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 def to_relative_hash DataMapper::Ext::Hash.only(to_hash, :fields, :order, :unique, :add_reversed, :reload) end
Transform Query into subquery conditions
@return [AndOperation]
a subquery for the Query
@api private
# File lib/dm-core/query.rb, line 655 def to_subquery collection = model.all(merge(:fields => model_key)) Conditions::Operation.new(:and, Conditions::Comparison.new(:in, self_relationship, collection)) 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 def union(other) return dup if self == other set_operation(:union, other) 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 def unique? @unique 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 def update(other) other_options = if kind_of?(other.class) return self if self.eql?(other) assert_valid_other(other) other.options else other = other.to_hash return self if other.empty? other end @options = @options.merge(other_options).freeze assert_valid_options(@options) normalize = DataMapper::Ext::Hash.only(other_options, *OPTIONS - [ :conditions ]).map do |attribute, value| instance_variable_set("@#{attribute}", DataMapper::Ext.try_dup(value)) attribute end merge_conditions([ DataMapper::Ext::Hash.except(other_options, *OPTIONS), other_options[:conditions] ]) normalize_options(normalize | [ :links, :unique ]) self 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 def valid? conditions.valid? end
Private Instance Methods
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 def add_condition(condition) @conditions = Conditions::Operation.new(:and) if @conditions.nil? @conditions << condition 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 def append_condition(subject, bind_value, model = self.model, operator = :eql) case subject when Property, Associations::Relationship then append_property_condition(subject, bind_value, operator) when Symbol then append_symbol_condition(subject, bind_value, model, operator) when String then append_string_condition(subject, bind_value, model, operator) when Operator then append_operator_conditions(subject, bind_value, model) when Path then append_path(subject, bind_value, model, operator) else raise ArgumentError, "#{subject} is an invalid instance: #{subject.class}" end end
@api private
# File lib/dm-core/query.rb, line 1218 def append_operator_conditions(operator, bind_value, model) append_condition(operator.target, bind_value, model, operator.operator) end
@api private
# File lib/dm-core/query.rb, line 1223 def append_path(path, bind_value, model, operator) path.relationships.each do |relationship| inverse = relationship.inverse @links.unshift(inverse) unless @links.include?(inverse) end append_condition(path.property, bind_value, path.model, operator) end
@api private
# File lib/dm-core/query.rb, line 1169 def append_property_condition(subject, bind_value, operator) negated = operator == :not if operator == :eql || negated # transform :relationship => nil into :relationship.not => association if subject.respond_to?(:collection_for) && bind_value.nil? negated = !negated bind_value = collection_for_nil(subject) end operator = equality_operator_for_type(bind_value) end condition = Conditions::Comparison.new(operator, subject, bind_value) if negated condition = Conditions::Operation.new(:not, condition) end add_condition(condition) end
@api private
# File lib/dm-core/query.rb, line 1197 def append_string_condition(string, bind_value, model, operator) if string.include?('.') query_path = model target_components = string.split('.') last_component = target_components.last operator = target_components.pop.to_sym if DataMapper::Query::Conditions::Comparison.slugs.any? { |slug| slug.to_s == last_component } target_components.each { |method| query_path = query_path.send(method) } append_condition(query_path, bind_value, model, operator) else repository_name = repository.name subject = model.properties(repository_name)[string] || model.relationships(repository_name)[string] append_condition(subject, bind_value, model, operator) end end
@api private
# File lib/dm-core/query.rb, line 1192 def append_symbol_condition(symbol, bind_value, model, operator) append_condition(symbol.to_s, bind_value, model, operator) end
Used to verify value of boolean properties in conditions @api private
# File lib/dm-core/query.rb, line 975 def assert_valid_boolean(name, value) if value != true && value != false raise ArgumentError, "+#{name}+ should be true or false, but was #{value.inspect}" end end
Verifies that value of :conditions option refers to existing properties
@api private
# File lib/dm-core/query.rb, line 848 def assert_valid_conditions(conditions) assert_kind_of 'options[:conditions]', conditions, Conditions::AbstractOperation, Conditions::AbstractComparison, Hash, Array case conditions when Hash conditions.each do |subject, bind_value| case subject when Symbol, ::String original = subject subject = subject.to_s name = subject[0, subject.index('.') || subject.length] unless @properties.named?(name) || @relationships.named?(name) raise ArgumentError, "condition #{original.inspect} does not map to a property or relationship in #{model}" end when Property unless @properties.include?(subject) raise ArgumentError, "condition #{subject.name.inspect} does not map to a property in #{model}, but belongs to #{subject.model}" end when Operator operator = subject.operator unless Conditions::Comparison.slugs.include?(operator) || operator == :not raise ArgumentError, "condition #{subject.inspect} used an invalid operator #{operator}" end assert_valid_conditions(subject.target => bind_value) when Path assert_valid_links(subject.relationships) when Associations::Relationship # TODO: validate that it belongs to the current model #unless subject.source_model.equal?(model) # raise ArgumentError, "condition #{subject.name.inspect} is not a valid relationship for #{model}, it's source model was #{subject.source_model}" #end else raise ArgumentError, "condition #{subject.inspect} of an unsupported object #{subject.class}" end end when Array if conditions.empty? raise ArgumentError, '+options[:conditions]+ should not be empty' end first_condition = conditions.first unless first_condition.kind_of?(String) && !DataMapper::Ext.blank?(first_condition) raise ArgumentError, '+options[:conditions]+ should have a statement for the first entry' end end end
Verifies that value of :fields option refers to existing properties
@api private
# File lib/dm-core/query.rb, line 785 def assert_valid_fields(fields, unique) fields = fields.to_ary model = self.model valid_properties = model.properties model.descendants.each do |descendant| valid_properties += descendant.properties end fields.each do |field| case field when Symbol, String unless valid_properties.named?(field) raise ArgumentError, "+options[:fields]+ entry #{field.inspect} does not map to a property in #{model}" end when Property unless valid_properties.include?(field) raise ArgumentError, "+options[:field]+ entry #{field.name.inspect} does not map to a property in #{model}" end else raise ArgumentError, "+options[:fields]+ entry #{field.inspect} of an unsupported object #{field.class}" end end 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 def assert_valid_limit(limit) limit = limit.to_int unless limit >= 0 raise ArgumentError, "+options[:limit]+ must be greater than or equal to 0, but was #{limit.inspect}" end end
Verifies that value of :links option refers to existing associations
@api private
# File lib/dm-core/query.rb, line 818 def assert_valid_links(links) links = links.to_ary if links.empty? raise ArgumentError, '+options[:links]+ should not be empty' end links.each do |link| case link when Symbol, String unless @relationships.named?(link.to_sym) raise ArgumentError, "+options[:links]+ entry #{link.inspect} does not map to a relationship in #{model}" end when Associations::Relationship # TODO: figure out how to validate links from other models #unless @relationships.value?(link) # raise ArgumentError, "+options[:links]+ entry #{link.name.inspect} does not map to a relationship in #{model}" #end else raise ArgumentError, "+options[:links]+ entry #{link.inspect} of an unsupported object #{link.class}" end end end
Verifies that query offset is non-negative and only used together with limit @api private
# File lib/dm-core/query.rb, line 907 def assert_valid_offset(offset, limit) offset = offset.to_int unless offset >= 0 raise ArgumentError, "+options[:offset]+ must be greater than or equal to 0, but was #{offset.inspect}" end if offset > 0 && limit.nil? raise ArgumentError, '+options[:offset]+ cannot be greater than 0 if limit is not specified' end end
Validate the options
@param [#each] 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 def assert_valid_options(options) options = options.to_hash options.each do |attribute, value| case attribute when :fields then assert_valid_fields(value, options[:unique]) when :links then assert_valid_links(value) when :conditions then assert_valid_conditions(value) when :offset then assert_valid_offset(value, options[:limit]) when :limit then assert_valid_limit(value) when :order then assert_valid_order(value, options[:fields]) when :unique, :add_reversed, :reload then assert_valid_boolean("options[:#{attribute}]", value) else assert_valid_conditions(attribute => value) end end end
Verifies that :order option uses proper operator and refers to existing property
@api private
# File lib/dm-core/query.rb, line 937 def assert_valid_order(order, fields) return if order.nil? order = Array(order) if order.empty? && fields && fields.any? { |property| !property.kind_of?(Operator) } raise ArgumentError, '+options[:order]+ should not be empty if +options[:fields] contains a non-operator' end model = self.model order.each do |order_entry| case order_entry when Symbol, String unless @properties.named?(order_entry) raise ArgumentError, "+options[:order]+ entry #{order_entry.inspect} does not map to a property in #{model}" end when Property, Path # Allow any arbitrary property, since it may map to a model # that has been included via the :links option when Operator, Direction operator = order_entry.operator unless operator == :asc || operator == :desc raise ArgumentError, "+options[:order]+ entry #{order_entry.inspect} used an invalid operator #{operator}" end assert_valid_order([ order_entry.target ], fields) else raise ArgumentError, "+options[:order]+ entry #{order_entry.inspect} of an unsupported object #{order_entry.class}" end end 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 def assert_valid_other(other) other_repository = other.repository repository = self.repository other_class = other.class unless other_repository == repository raise ArgumentError, "+other+ #{other_class} must be for the #{repository.name} repository, not #{other_repository.name}" end other_model = other.model model = self.model unless other_model >= model raise ArgumentError, "+other+ #{other_class} must be for the #{model.name} model, not #{other_model.name}" end end
@api private
# File lib/dm-core/query.rb, line 1316 def collection_for_nil(relationship) query = relationship.query.dup relationship.target_key.each do |target_key| query[target_key.name.not] = nil if target_key.allow_nil? end relationship.target_model.all(query) end
@api private
# File lib/dm-core/query.rb, line 1327 def each_comparison operands = conditions.operands.to_a while operand = operands.shift if operand.respond_to?(:operands) operands.unshift(*operand.operands) else yield operand end end end
@api private
# File lib/dm-core/query.rb, line 1159 def equality_operator_for_type(bind_value) case bind_value when Model, String then :eql when Enumerable then :in when Regexp then :regexp else :eql end end
@api private
# File lib/dm-core/query.rb, line 1279 def extract_offset_limit_from_integer(integer) [ integer, 1 ] end
@api private
# File lib/dm-core/query.rb, line 1271 def extract_offset_limit_from_one_argument(arg) case arg when Integer then extract_offset_limit_from_integer(arg) when Range then extract_offset_limit_from_range(arg) end end
@api private
# File lib/dm-core/query.rb, line 1284 def extract_offset_limit_from_range(range) offset = range.first limit = range.last - offset limit = limit.succ unless range.exclude_end? return offset, limit end
@api private
# File lib/dm-core/query.rb, line 1266 def extract_offset_limit_from_two_arguments(*args) args if args.all? { |arg| arg.kind_of?(Integer) } end
Extract arguments for slice and slice! 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 def extract_slice_arguments(*args) offset, limit = case args.size when 2 then extract_offset_limit_from_two_arguments(*args) when 1 then extract_offset_limit_from_one_argument(*args) end return offset, limit if offset && limit raise ArgumentError, "arguments may be 1 or 2 Integers, or 1 Range object, was: #{args.inspect}" end
@api private
# File lib/dm-core/query.rb, line 1292 def get_relative_position(offset, limit) self_offset = self.offset self_limit = self.limit new_offset = self_offset + offset if limit <= 0 || (self_limit && new_offset + limit > self_offset + self_limit) raise RangeError, "offset #{offset} and limit #{limit} are outside allowed range" end return new_offset, limit end
Copying contructor, called for Query#dup
@api semipublic
# File lib/dm-core/query.rb, line 747 def initialize_copy(*) @fields = @fields.dup @links = @links.dup @conditions = @conditions.dup @order = DataMapper::Ext.try_dup(@order) 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 def merge_conditions(conditions) @conditions = Conditions::Operation.new(:and) << @conditions unless @conditions.nil? conditions.compact! conditions.each do |condition| case condition when Conditions::AbstractOperation, Conditions::AbstractComparison add_condition(condition) when Hash condition.each { |key, value| append_condition(key, value) } when Array statement, *bind_values = *condition raw_condition = [ statement ] raw_condition << bind_values if bind_values.size > 0 add_condition(raw_condition) @raw = true end end end
Return the model key
@return [PropertySet]
the model key
@api private
# File lib/dm-core/query.rb, line 1440 def model_key @properties.key end
Normalize fields to Property instances
@api private
# File lib/dm-core/query.rb, line 1081 def normalize_fields @fields = @fields.map do |field| case field when Symbol, String @properties[field] when Property, Operator field end end 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 def normalize_links stack = @links.dup @links.clear while link = stack.pop relationship = case link when Symbol, String then @relationships[link] when Associations::Relationship then link end if relationship.respond_to?(:links) stack.concat(relationship.links) elsif !@links.include?(relationship) @links << relationship end end @links.reverse! end
Normalize options
@param [Array<Symbol>] options
the options to normalize
@return [undefined]
@api private
# File lib/dm-core/query.rb, line 1040 def normalize_options(options = OPTIONS) normalize_order if options.include? :order normalize_fields if options.include? :fields normalize_links if options.include? :links normalize_unique if options.include? :unique end
Normalize order elements to Query::Direction instances
@api private
# File lib/dm-core/query.rb, line 1050 def normalize_order return if @order.nil? @order = Array(@order) @order = @order.map do |order| case order when Direction order.dup when Operator target = order.target property = target.kind_of?(Property) ? target : @properties[target] Direction.new(property, order.operator) when Symbol, String Direction.new(@properties[order]) when Property Direction.new(order) when Path Direction.new(order.property) end end 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 def normalize_unique @unique = links.any? unless @options.key?(:unique) 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 def other_conditions(other, operation) self_conditions = query_conditions(self) unless self_conditions.kind_of?(Conditions::Operation) operation_slug = case operation when :intersection, :difference then :and when :union then :or end self_conditions = Conditions::Operation.new(operation_slug, self_conditions) end self_conditions.send(operation, query_conditions(other)) 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 def query_conditions(query) if query.limit || query.links.any? query.to_subquery else query.conditions end end
TODO: DRY this up with conditions @api private
# File lib/dm-core/query.rb, line 1306 def record_value(record, property) case record when Hash record.fetch(property, record[property.field]) when Resource property.get!(record) end 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 def self_relationship @self_relationship ||= begin model = self.model Associations::OneToMany::Relationship.new( :self, model, model, self_relationship_options ) end 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 def self_relationship_options keys = model_key.map { |property| property.name } repository = self.repository { :child_key => keys, :parent_key => keys, :child_repository_name => repository.name, :parent_repository_name => repository.name, } 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 def set_operation(operation, other) assert_valid_other(other) query = self.class.new(@repository, @model, other.to_relative_hash) query.instance_variable_set(:@conditions, other_conditions(other, operation)) query end