Class: DataMapper::Query

Inherits:
Object
  • Object
show all
Extended by:
Equalizer
Includes:
Extlib::Assertions
Defined in:
lib/dm-core/query.rb,
lib/dm-core/query/path.rb,
lib/dm-core/query/sort.rb,
lib/dm-core/query/operator.rb,
lib/dm-core/query/direction.rb,
lib/dm-core/query/conditions/operation.rb,
lib/dm-core/query/conditions/comparison.rb

Overview

Query class represents a query which will be run against the data-store. Generally Query objects can be found inside Collection objects.

Defined Under Namespace

Modules: Conditions Classes: Direction, Operator, Path, Sort

Constant Summary collapse

OPTIONS =
[ :fields, :links, :conditions, :offset, :limit, :order, :unique, :add_reversed, :reload ].to_set.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Equalizer

equalize

Instance Attribute Details

#conditionsArray (readonly)

Returns the conditions of the query

In the following example:

Conditions are “greater than” operator for “wins” field and exact match operator for “conference”.

Examples:


Team.all(:wins.gt => 30, :conference => 'East')

Returns:

  • (Array)

    the conditions that will be used to scope the results



152
153
154
# File 'lib/dm-core/query.rb', line 152

def conditions
  @conditions
end

#fieldsPropertySet (readonly)

Returns the fields

Set in cases like the following:

Examples:


Document.all(:fields => [:title, :vernacular_title, :abstract])

Returns:

  • (PropertySet)

    the properties in the Model that will be retrieved



127
128
129
# File 'lib/dm-core/query.rb', line 127

def fields
  @fields
end

#limitInteger, NilClass (readonly)

Returns the limit query uses

Set in cases like the following:

Examples:


Document.all(:limit => 10)

Returns:

  • (Integer, NilClass)

    the maximum number of results



180
181
182
# File 'lib/dm-core/query.rb', line 180

def limit
  @limit
end

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the links (associations) query fetches

Returns:



135
136
137
# File 'lib/dm-core/query.rb', line 135

def links
  @links
end

#modelModel (readonly)

Returns model (class) that is used to instantiate objects from query result returned by adapter

Returns:

  • (Model)

    the Model to retrieve results from



113
114
115
# File 'lib/dm-core/query.rb', line 113

def model
  @model
end

#offsetInteger (readonly)

Returns the offset query uses

Set in cases like the following:

Examples:


Document.all(:offset => page.offset)

Returns:

  • (Integer)

    the offset of the results



166
167
168
# File 'lib/dm-core/query.rb', line 166

def offset
  @offset
end

#optionsHash (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the original options

Returns:

  • (Hash)

    the original options



205
206
207
# File 'lib/dm-core/query.rb', line 205

def options
  @options
end

#orderArray (readonly)

Returns the order

Set in cases like the following:

query order is a set of two ordering rules, descending on “created_at” field and descending again on “length” field

Examples:


Document.all(:order => [:created_at.desc, :length.desc])

Returns:

  • (Array)

    the order of results



197
198
199
# File 'lib/dm-core/query.rb', line 197

def order
  @order
end

#repositoryRepository (readonly)

Returns the repository query should be executed in

Set in cases like the following:

Examples:


Document.all(:repository => :medline)

Returns:

  • (Repository)

    the Repository to retrieve results from



103
104
105
# File 'lib/dm-core/query.rb', line 103

def repository
  @repository
end

Class Method Details

.target_conditions(source, source_key, target_key) ⇒ AbstractComparison, AbstractOperation

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Extract conditions to match a Resource or Collection

Parameters:

  • source (Array, Collection, Resource)

    the source to extract the values from

  • source_key (ProperySet)

    the key to extract the value from the resource

  • target_key (ProperySet)

    the key to match the resource with

Returns:

  • (AbstractComparison, AbstractOperation)

    the conditions to match the resources with



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/dm-core/query.rb', line 49

def self.target_conditions(source, source_key, target_key)
  source_values = []

  if source.nil?
    source_values << [ nil ] * target_key.size
  else
    Array(source).each do |resource|
      next unless source_key.loaded?(resource)
      source_values << source_key.get!(resource)
    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

Instance Method Details

#add_reversed?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Indicates if each result should be returned in reverse order

Set in cases like the following:

Note that :add_reversed option may be used in conditions directly, but this is rarely the case

Examples:


Document.all(:limit => 5).reverse

Returns:

  • (Boolean)

    true if the results should be reversed, false if not



222
223
224
# File 'lib/dm-core/query.rb', line 222

def add_reversed?
  @add_reversed
end

#condition_propertiesSet<Property>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Get the properties used in the conditions

Returns:

  • (Set<Property>)

    Set of properties used in the conditions



546
547
548
549
550
551
552
553
554
# File 'lib/dm-core/query.rb', line 546

def condition_properties
  properties = Set.new

  each_comparison do |comparison|
    properties << comparison.subject if comparison.subject.kind_of?(Property)
  end

  properties
end

#filter_records(records) ⇒ Enumerable

Takes an Enumerable of records, and destructively filters it. First finds all matching conditions, then sorts it, then does offset & limit

Parameters:

  • records (Enumerable)

    The set of records to be filtered

Returns:

  • (Enumerable)

    Whats left of the given array after the filtering



407
408
409
410
411
412
413
# File 'lib/dm-core/query.rb', line 407

def filter_records(records)
  records = records.uniq if unique?
  records = match_records(records)
  records = sort_records(records)
  records = limit_records(records)
  records
end

#inspectString

Returns detailed human readable string representation of the query

Returns:

  • (String)

    detailed string representation of the query



523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
# File 'lib/dm-core/query.rb', line 523

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

#limit_records(records) ⇒ Enumerable

Limits a set of records by the offset and/or limit

Parameters:

  • records (Enumerable)

    A list of Recrods to sort

Returns:

  • (Enumerable)

    The offset & limited records



459
460
461
462
463
464
465
466
467
468
469
# File 'lib/dm-core/query.rb', line 459

def limit_records(records)
  size = records.size

  if offset > size - 1
    []
  elsif (limit && limit != size) || offset > 0
    records[offset, limit || size] || []
  else
    records.dup
  end
end

#match_records(records) ⇒ Enumerable

Filter a set of records by the conditions

Parameters:

  • records (Enumerable)

    The set of records to be filtered

Returns:

  • (Enumerable)

    Whats left of the given array after the matching



424
425
426
427
428
429
# File 'lib/dm-core/query.rb', line 424

def match_records(records)
  return records if conditions.nil?
  records.select do |record|
    conditions.matches?(record)
  end
end

#merge(other) ⇒ Query

Similar to Query#update, but acts on a duplicate.

Parameters:

  • other (Query, Hash)

    other query to merge with

Returns:

  • (Query)

    updated duplicate of original query



358
359
360
# File 'lib/dm-core/query.rb', line 358

def merge(other)
  dup.update(other)
end

#raw?Boolean

Indicates if the Query has raw conditions

Returns:

  • (Boolean)

    true if the query has raw conditions, false if not



256
257
258
# File 'lib/dm-core/query.rb', line 256

def raw?
  @raw
end

#relative(options) ⇒ Object

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:

Examples:


author.books.all(:year => 2009).all(:published => false)


375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/dm-core/query.rb', line 375

def relative(options)
  assert_kind_of 'options', options, Hash

  options = options.dup

  repository = options.delete(:repository) || self.repository

  if repository.kind_of?(Symbol)
    repository = DataMapper.repository(repository)
  end

  if options.key?(:offset) && (options.key?(:limit) || self.limit)
    offset = options.delete(:offset)
    limit  = options.delete(:limit) || self.limit - offset

    self.class.new(repository, model, @options.merge(options)).slice!(offset, limit)
  else
    self.class.new(repository, model, @options.merge(options))
  end
end

#reload?Boolean

Indicates if the Query results should replace the results in the Identity Map

TODO: needs example

Returns:

  • (Boolean)

    true if the results should be reloaded, false if not



234
235
236
# File 'lib/dm-core/query.rb', line 234

def reload?
  @reload
end

#reverseQuery

Returns a new Query with a reversed order

Will execute a single query with correct order

Examples:


Document.all(:limit => 5).reverse

Returns:

  • (Query)

    new Query with reversed order



282
283
284
# File 'lib/dm-core/query.rb', line 282

def reverse
  dup.reverse!
end

#reverse!Query

Reverses the sort order of the Query

Will execute a single query with original order and then reverse collection in the Ruby space

Examples:


Document.all(:limit => 5).reverse

Returns:



299
300
301
302
303
304
305
306
307
# File 'lib/dm-core/query.rb', line 299

def reverse!
  # reverse the sort order
  @order.map! { |direction| direction.reverse! }

  # copy the order to the options
  @options = @options.merge(:order => @order.map { |direction| direction.dup }).freeze

  self
end

#slice(*args) ⇒ Object Also known as: []

Slices collection by adding limit and offset to the query, so a single query is executed

will execute query with the following limit and offset (when repository uses DataObjects adapter, and thus queries use SQL):

LIMIT 5 OFFSET 3

Examples:


Journal.all(:limit => 10).slice(3, 5)


485
486
487
# File 'lib/dm-core/query.rb', line 485

def slice(*args)
  dup.slice!(*args)
end

#slice!(*args) ⇒ Object

Slices collection by adding limit and offset to the query, so a single query is executed

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

Examples:


Journal.all(:limit => 10).slice!(3, 5)


507
508
509
510
511
512
513
514
515
# File 'lib/dm-core/query.rb', line 507

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

#sort_records(records) ⇒ Enumerable

Sorts a list of Records by the order

Parameters:

  • records (Enumerable)

    A list of Resources to sort

Returns:

  • (Enumerable)

    The sorted records



440
441
442
443
444
445
446
447
448
# File 'lib/dm-core/query.rb', line 440

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

#sorted_fieldsArray<Property>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return a list of fields in predictable order

Returns:

  • (Array<Property>)

    list of fields sorted in deterministic order



562
563
564
# File 'lib/dm-core/query.rb', line 562

def sorted_fields
  fields.sort_by { |property| property.hash }
end

#unique?Boolean

Indicates if the Query results should be unique

TODO: needs example

Returns:

  • (Boolean)

    true if the results should be unique, false if not



246
247
248
# File 'lib/dm-core/query.rb', line 246

def unique?
  @unique
end

#update(other) ⇒ Query

Updates the Query with another Query or conditions

Pretty unrealistic example:

Examples:


Journal.all(:limit => 2).query.limit                     # => 2
Journal.all(:limit => 2).query.update(:limit => 3).limit # => 3

Parameters:

  • other (Query, Hash)

    other Query or conditions

Returns:



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/dm-core/query.rb', line 325

def update(other)
  assert_kind_of 'other', other, self.class, Hash

  other_options = if other.kind_of? self.class
    if self.eql?(other)
      return self
    end
    assert_valid_other(other)
    other.options
  else
    other
  end

  unless other_options.empty?
    options = @options.merge(other_options)
    if @options[:conditions] and other_options[:conditions]
      options[:conditions] = @options[:conditions].dup << other_options[:conditions]
    end
    initialize(repository, model, options)
  end

  self
end

#valid?Boolean

Indicates if the Query is valid

Returns:

  • (Boolean)

    true if the query is valid



266
267
268
# File 'lib/dm-core/query.rb', line 266

def valid?
  conditions.valid?
end