class DataMapper::Collection
The Collection class represents a list of resources persisted in a repository and identified by a query.
A Collection should act like an Array in every way, except that it will attempt to defer loading until the results from the repository are needed.
A Collection is typically returned by the DataMapper::Model#all method.
Attributes
Returns the Query the Collection is scoped with
@return [Query]
the Query the Collection is scoped with
@api semipublic
Public Class Methods
Initializes a new Collection identified by the query
@param [Query] query
Scope the results of the Collection
@param [Enumerable] resources (optional)
List of resources to initialize the Collection with
@return [self]
@api private
# File lib/dm-core/collection.rb, line 1040 def initialize(query, resources = nil) raise "#{self.class}#new with a block is deprecated" if block_given? @query = query @identity_map = IdentityMap.new @removed = Set.new super() # TODO: change LazyArray to not use a load proc at all remove_instance_variable(:@load_with_proc) set(resources) if resources end
Public Instance Methods
Append one Resource to the Collection and relate it
@param [Resource] resource
the resource to add to this collection
@return [self]
@api public
# File lib/dm-core/collection.rb, line 536 def <<(resource) super(resource_added(resource)) end
Simulates Array#slice and returns a new Collection whose query has a new offset or limit according to the arguments provided.
If you provide a range, the min is used as the offset and the max minues the offset is used as the limit.
@param [Integer, Array(Integer), Range] *args
the offset, offset and limit, or range indicating first and last position
@return [Resource, Collection, nil]
The entry which resides at that offset and limit, or a new Collection object with the set limits and offset
@return [nil]
The offset (or starting offset) is out of range
@raise [ArgumentError] “arguments may be 1 or 2 Integers,
or 1 Range object, was: #{args.inspect}"
@api public
# File lib/dm-core/collection.rb, line 389 def [](*args) offset, limit = extract_slice_arguments(*args) if args.size == 1 && args.first.kind_of?(Integer) return at(offset) end query = sliced_query(offset, limit) if loaded? || partially_loaded?(offset, limit) new_collection(query, super) else new_collection(query) end end
Splice a list of Resources at a given offset or range
When nil is provided instead of a Resource or a list of Resources this will remove all of the Resources at the specified position.
@param [Integer, Array(Integer), Range] *args
The offset, offset and limit, or range indicating first and last position. The last argument may be a Resource, a list of Resources or nil.
@return [Resource, Enumerable]
the Resource or list of Resources that was spliced into the Collection
@return [nil]
If nil was used to delete the entries
@api public
# File lib/dm-core/collection.rb, line 453 def []=(*args) orphans = Array(superclass_slice(*args[0..-2])) # relate new resources resources = resources_added(super) # mark resources as removed resources_removed(orphans - loaded_entries) resources end
Returns a new Collection optionally scoped by
query
This returns a new Collection scoped relative to the current Collection.
cars_from_91 = Cars.all(:year_manufactured => 1991) toyotas_91 = cars_from_91.all(:manufacturer => 'Toyota') toyotas_91.all? { |car| car.year_manufactured == 1991 } #=> true toyotas_91.all? { |car| car.manufacturer == 'Toyota' } #=> true
If query
is a Hash, results will be found by merging
query
with this Collection's query. If query
is a Query, results will be found using
query
as an absolute query.
@param [Hash, Query] query
optional parameters to scope results with
@return [Collection]
Collection scoped by +query+
@api public
# File lib/dm-core/collection.rb, line 213 def all(query = Undefined) if query.equal?(Undefined) || (query.kind_of?(Hash) && query.empty?) dup else # TODO: if there is no order parameter, and the Collection is not loaded # check to see if the query can be satisfied by the head/tail new_collection(scoped_query(query)) end end
Lookup a Resource from the Collection by offset
@param [Integer] offset
offset of the Resource in the Collection
@return [Resource]
Resource which matches the supplied offset
@return [nil]
No Resource matches the supplied offset
@api public
# File lib/dm-core/collection.rb, line 346 def at(offset) if loaded? || partially_loaded?(offset) super elsif offset == 0 first elsif offset > 0 first(:offset => offset) elsif offset == -1 last else last(:offset => offset.abs - 1) end end
Checks if all the resources have no changes to save
@return [Boolean]
true if the resource may not be persisted
@api public
# File lib/dm-core/collection.rb, line 955 def clean? !dirty? end
Removes all Resources from the Collection
This should remove and orphan each Resource from the Collection
@return [self]
@api public
# File lib/dm-core/collection.rb, line 727 def clear if loaded? resources_removed(self) end super end
Invoke the block for each resource and replace it the return value
@yield [Resource] Each resource in the collection
@return [self]
@api public
# File lib/dm-core/collection.rb, line 522 def collect! super { |resource| resource_added(yield(resource_removed(resource))) } end
Appends the resources to self
@param [Enumerable] resources
List of Resources to append to the collection
@return [self]
@api public
# File lib/dm-core/collection.rb, line 548 def concat(resources) super(resources_added(resources)) end
Create a Resource in the Collection
@param [Hash(Symbol => Object)] attributes
attributes to set
@return [Resource]
the newly created Resource instance
@api public
# File lib/dm-core/collection.rb, line 793 def create(attributes = {}) _create(attributes) end
Create a Resource in the Collection, bypassing hooks
@param [Hash(Symbol => Object)] attributes
attributes to set
@return [Resource]
the newly created Resource instance
@api public
# File lib/dm-core/collection.rb, line 806 def create!(attributes = {}) _create(attributes, false) end
Remove Resource from the Collection
This should remove an included Resource from the Collection and orphan it from the Collection. If the Resource is not within the Collection, it should return nil.
@param [Resource] resource the Resource to remove from
the Collection
@return [Resource]
If +resource+ is within the Collection
@return [nil]
If +resource+ is not within the Collection
@api public
# File lib/dm-core/collection.rb, line 635 def delete(resource) if resource = super resource_removed(resource) end end
Remove Resource from the Collection by offset
This should remove the Resource from the Collection at a given offset and orphan it from the Collection. If the offset is out of range return nil.
@param [Integer] offset
the offset of the Resource to remove from the Collection
@return [Resource]
If +offset+ is within the Collection
@return [nil]
If +offset+ is not within the Collection
@api public
# File lib/dm-core/collection.rb, line 656 def delete_at(offset) if resource = super resource_removed(resource) end end
Deletes every Resource for which block evaluates to true.
@yield [Resource] Each resource in the Collection
@return [self]
@api public
# File lib/dm-core/collection.rb, line 669 def delete_if super { |resource| yield(resource) && resource_removed(resource) } end
Remove every Resource in the Collection from the repository
This performs a deletion of each Resource in the Collection from the repository and clears the Collection.
@return [Boolean]
true if the resources were successfully destroyed
@api public
# File lib/dm-core/collection.rb, line 895 def destroy if destroyed = all? { |resource| resource.destroy } clear end destroyed end
Remove all Resources from the repository, bypassing validation
This performs a deletion of each Resource in the Collection from the repository and clears the Collection while skipping validation.
@return [Boolean]
true if the resources were successfully destroyed
@api public
# File lib/dm-core/collection.rb, line 913 def destroy! repository = self.repository deleted = repository.delete(self) if loaded? unless deleted == size return false end each do |resource| resource.persistence_state = Resource::PersistenceState::Immutable.new(resource) end clear else mark_loaded end true end
Return the difference with another collection
@param [Collection] other
the other collection
@return [Collection]
the difference of the collection and other
@api public
# File lib/dm-core/collection.rb, line 123 def difference(other) set_operation(:-, other) end
Checks if any resources have unsaved changes
@return [Boolean]
true if the resources have unsaved changed
@api public
# File lib/dm-core/collection.rb, line 965 def dirty? loaded_entries.any? { |resource| resource.dirty? } || @removed.any? end
Iterate over each Resource
@yield [Resource] Each resource in the collection
@return [self]
@api public
# File lib/dm-core/collection.rb, line 503 def each return to_enum unless block_given? super do |resource| begin original, resource.collection = resource.collection, self yield resource ensure resource.collection = original end end end
Return the first Resource or the first N Resources in the Collection with an optional query
When there are no arguments, return the first Resource in the Collection. When the first argument is an Integer, return a Collection containing the first N Resources. When the last (optional) argument is a Hash scope the results to the query.
@param [Integer] limit (optional)
limit the returned Collection to a specific number of entries
@param [Hash] query (optional)
scope the returned Resource or Collection to the supplied query
@return [Resource, Collection]
The first resource in the entries of this collection, or a new collection whose query has been merged
@api public
# File lib/dm-core/collection.rb, line 240 def first(*args) first_arg = args.first last_arg = args.last limit_specified = first_arg.kind_of?(Integer) with_query = (last_arg.kind_of?(Hash) && !last_arg.empty?) || last_arg.kind_of?(Query) limit = limit_specified ? first_arg : 1 query = with_query ? last_arg : {} query = self.query.slice(0, limit).update(query) # TODO: when a query provided, and there are enough elements in head to # satisfy the query.limit, filter the head with the query, and make # sure it matches the limit exactly. if so, use that result instead # of calling all() # - this can probably only be done if there is no :order parameter loaded = loaded? head = self.head collection = if !with_query && (loaded || lazy_possible?(head, limit)) new_collection(query, super(limit)) else all(query) end return collection if limit_specified resource = collection.to_a.first if with_query || loaded resource elsif resource head[0] = resource end end
Finds the first Resource by conditions, or creates a new Resource with the attributes if none found
@param [Hash] conditions
The conditions to be used to search
@param [Hash] attributes
The attributes to be used to create the resource with if none found
@return [Resource]
The instance found by +query+, or created with +attributes+ if none found
@api public
# File lib/dm-core/collection.rb, line 765 def first_or_create(conditions = {}, attributes = {}) first(conditions) || create(conditions.merge(attributes)) end
Finds the first Resource by conditions, or initializes a new Resource with the attributes if none found
@param [Hash] conditions
The conditions to be used to search
@param [Hash] attributes
The attributes to be used to initialize the resource with if none found
@return [Resource]
The instance found by +query+, or created with +attributes+ if none found
@api public
# File lib/dm-core/collection.rb, line 750 def first_or_new(conditions = {}, attributes = {}) first(conditions) || new(conditions.merge(attributes)) end
Lookup a Resource in the Collection by key
This looksup a Resource by key, typecasting the key to the proper object if necessary.
toyotas = Cars.all(:manufacturer => 'Toyota') toyo = Cars.first(:manufacturer => 'Toyota') toyotas.get(toyo.id) == toyo #=> true
@param [Enumerable] *key
keys which uniquely identify a resource in the Collection
@return [Resource]
Resource which matches the supplied key
@return [nil]
No Resource matches the supplied key
@api public
# File lib/dm-core/collection.rb, line 147 def get(*key) assert_valid_key_size(key) key = model_key.typecast(key) query = self.query @identity_map[key] || if !loaded? && (query.limit || query.offset > 0) # current query is exclusive, find resource within the set # TODO: use a subquery to retrieve the Collection and then match # it up against the key. This will require some changes to # how subqueries are generated, since the key may be a # composite key. In the case of DO adapters, it means subselects # like the form "(a, b) IN(SELECT a, b FROM ...)", which will # require making it so the Query condition key can be a # Property or an Array of Property objects # use the brute force approach until subquery lookups work lazy_load @identity_map[key] else # current query is all inclusive, lookup using normal approach first(model.key_conditions(repository, key).update(:order => nil)) end end
Lookup a Resource in the Collection by key, raising an exception if not found
This looksup a Resource by key, typecasting the key to the proper object if necessary.
@param [Enumerable] *key
keys which uniquely identify a resource in the Collection
@return [Resource]
Resource which matches the supplied key
@return [nil]
No Resource matches the supplied key
@raise [ObjectNotFoundError] Resource could not be found by key
@api public
# File lib/dm-core/collection.rb, line 189 def get!(*key) get(*key) || raise(ObjectNotFoundError, "Could not find #{model.name} with key #{key.inspect}") end
@api semipublic
# File lib/dm-core/collection.rb, line 981 def hash self.class.hash ^ query.hash end
Inserts the Resources before the Resource at the offset (which may be negative).
@param [Integer] offset
The offset to insert the Resources before
@param [Enumerable] *resources
List of Resources to insert
@return [self]
@api public
# File lib/dm-core/collection.rb, line 592 def insert(offset, *resources) super(offset, *resources_added(resources)) end
Gets a Human-readable representation of this collection, showing all elements contained in it
@return [String]
Human-readable representation of this collection, showing all elements
@api public
# File lib/dm-core/collection.rb, line 976 def inspect "[#{map { |resource| resource.inspect }.join(', ')}]" end
Return the intersection with another collection
@param [Collection] other
the other collection
@return [Collection]
the intersection of the collection and other
@api public
# File lib/dm-core/collection.rb, line 108 def intersection(other) set_operation(:&, other) end
Return the last Resource or the last N Resources in the Collection with an optional query
When there are no arguments, return the last Resource in the Collection. When the first argument is an Integer, return a Collection containing the last N Resources. When the last (optional) argument is a Hash scope the results to the query.
@param [Integer] limit (optional)
limit the returned Collection to a specific number of entries
@param [Hash] query (optional)
scope the returned Resource or Collection to the supplied query
@return [Resource, Collection]
The last resource in the entries of this collection, or a new collection whose query has been merged
@api public
# File lib/dm-core/collection.rb, line 295 def last(*args) first_arg = args.first last_arg = args.last limit_specified = first_arg.kind_of?(Integer) with_query = (last_arg.kind_of?(Hash) && !last_arg.empty?) || last_arg.kind_of?(Query) limit = limit_specified ? first_arg : 1 query = with_query ? last_arg : {} query = self.query.slice(0, limit).update(query).reverse! # tell the Query to prepend each result from the adapter query.update(:add_reversed => !query.add_reversed?) # TODO: when a query provided, and there are enough elements in tail to # satisfy the query.limit, filter the tail with the query, and make # sure it matches the limit exactly. if so, use that result instead # of calling all() loaded = loaded? tail = self.tail collection = if !with_query && (loaded || lazy_possible?(tail, limit)) new_collection(query, super(limit)) else all(query) end return collection if limit_specified resource = collection.to_a.last if with_query || loaded resource elsif resource tail[tail.empty? ? 0 : -1] = resource end end
Returns the Model
@return [Model]
the Model the Collection is associated with
@api semipublic
# File lib/dm-core/collection.rb, line 45 def model query.model end
Initializes a Resource and appends it to the Collection
@param [Hash] attributes
Attributes with which to initialize the new resource
@return [Resource]
a new Resource initialized with +attributes+
@api public
# File lib/dm-core/collection.rb, line 778 def new(attributes = {}) resource = repository.scope { model.new(attributes) } self << resource resource end
Removes and returns the last Resource in the Collection
@return [Resource]
the last Resource in the Collection
@api public
# File lib/dm-core/collection.rb, line 602 def pop(*) if removed = super resources_removed(removed) end end
Append one or more Resources to the Collection
This should append one or more Resources to the Collection and relate each to the Collection.
@param [Enumerable] *resources
List of Resources to append
@return [self]
@api public
# File lib/dm-core/collection.rb, line 563 def push(*resources) super(*resources_added(resources)) end
Deletes every Resource for which block evaluates to true
@yield [Resource] Each resource in the Collection
@return [Collection]
If resources were removed
@return [nil]
If no resources were removed
@api public
# File lib/dm-core/collection.rb, line 683 def reject! super { |resource| yield(resource) && resource_removed(resource) } end
Reloads the Collection from the repository
If query
is provided, updates this Collection's query with
its conditions
cars_from_91 = Cars.all(:year_manufactured => 1991) cars_from_91.first.year_manufactured = 2001 # note: not saved cars_from_91.reload cars_from_91.first.year #=> 1991
@param [Query, Hash] query (optional)
further restrict results with query
@return [self]
@api public
# File lib/dm-core/collection.rb, line 64 def reload(other_query = Undefined) query = self.query query = other_query.equal?(Undefined) ? query.dup : query.merge(other_query) # make sure the Identity Map contains all the existing resources identity_map = repository.identity_map(model) loaded_entries.each do |resource| identity_map[resource.key] = resource end # sort fields based on declared order, for more consistent reload queries properties = self.properties fields = properties & (query.fields | model_key | [ properties.discriminator ].compact) # replace the list of resources replace(all(query.update(:fields => fields, :reload => true))) end
Replace the Resources within the Collection
@param [Enumerable] other
List of other Resources to replace with
@return [self]
@api public
# File lib/dm-core/collection.rb, line 701 def replace(other) other = resources_added(other) resources_removed(entries - other) super(other) end
Returns the Repository
@return [Repository]
the Repository this Collection is associated with
@api semipublic
# File lib/dm-core/collection.rb, line 35 def repository query.repository end
Check to see if collection can respond to the method
@param [Symbol] method
method to check in the object
@param [Boolean] include_private
if set to true, collection will check private methods
@return [Boolean]
true if method can be responded to
@api public
# File lib/dm-core/collection.rb, line 945 def respond_to?(method, include_private = false) super || model.respond_to?(method) || relationships.named?(method) end
Return a copy of the Collection sorted in reverse
@return [Collection]
Collection equal to +self+ but ordered in reverse
@api public
# File lib/dm-core/collection.rb, line 473 def reverse dup.reverse! end
Return the Collection sorted in reverse
@return [self]
@api public
# File lib/dm-core/collection.rb, line 482 def reverse! query.reverse! # reverse without kicking if possible if loaded? @array.reverse! else # reverse and swap the head and tail @head, @tail = tail.reverse!, head.reverse! end self end
Save every Resource in the Collection
@return [Boolean]
true if the resources were successfully saved
@api public
# File lib/dm-core/collection.rb, line 872 def save _save end
Save every Resource in the Collection bypassing validation
@return [Boolean]
true if the resources were successfully saved
@api public
# File lib/dm-core/collection.rb, line 882 def save! _save(false) end
(Private) Set the Collection
@param [Array] resources
resources to add to the collection
@return [self]
@api private
# File lib/dm-core/collection.rb, line 715 def set(resources) superclass_replace(resources_added(resources)) self end
Removes and returns the first Resource in the Collection
@return [Resource]
the first Resource in the Collection
@api public
# File lib/dm-core/collection.rb, line 614 def shift(*) if removed = super resources_removed(removed) end end
Deletes and Returns the Resources given by an offset or a Range
@param [Integer, Array(Integer), Range] *args
the offset, offset and limit, or range indicating first and last position
@return [Resource, Collection]
The entry which resides at that offset and limit, or a new Collection object with the set limits and offset
@return [Resource, Collection, nil]
The offset is out of range
@api public
# File lib/dm-core/collection.rb, line 419 def slice!(*args) removed = super resources_removed(removed) unless removed.nil? # Workaround for Ruby <= 1.8.6 compact! if RUBY_VERSION <= '1.8.6' unless removed.kind_of?(Enumerable) return removed end offset, limit = extract_slice_arguments(*args) query = sliced_query(offset, limit) new_collection(query, removed) end
Access LazyArray#slice directly
#[]= uses this to bypass #slice and access the resources directly so that it can orphan them properly.
@api private
Return the union with another collection
@param [Collection] other
the other collection
@return [Collection]
the union of the collection and other
@api public
# File lib/dm-core/collection.rb, line 92 def union(other) set_operation(:|, other) end
Prepend one or more Resources to the Collection
This should prepend one or more Resources to the Collection and relate each to the Collection.
@param [Enumerable] *resources
The Resources to prepend
@return [self]
@api public
# File lib/dm-core/collection.rb, line 578 def unshift(*resources) super(*resources_added(resources)) end
Update every Resource in the Collection
Person.all(:age.gte => 21).update(:allow_beer => true)
@param [Hash] attributes
attributes to update with
@return [Boolean]
true if the resources were successfully updated
@api public
# File lib/dm-core/collection.rb, line 821 def update(attributes) assert_update_clean_only(:update) dirty_attributes = model.new(attributes).dirty_attributes dirty_attributes.empty? || all? { |resource| resource.update(attributes) } end
Update every Resource in the Collection bypassing validation
Person.all(:age.gte => 21).update!(:allow_beer => true)
@param [Hash] attributes
attributes to update
@return [Boolean]
true if the resources were successfully updated
@api public
# File lib/dm-core/collection.rb, line 839 def update!(attributes) assert_update_clean_only(:update!) model = self.model dirty_attributes = model.new(attributes).dirty_attributes if dirty_attributes.empty? true elsif dirty_attributes.any? { |property, value| !property.valid?(value) } false else unless _update(dirty_attributes) return false end if loaded? each do |resource| dirty_attributes.each { |property, value| property.set!(resource, value) } repository.identity_map(model)[resource.key] = resource end end true end end
Protected Instance Methods
Loaded Resources in the collection
@return [Array<Resource>]
Resources in the collection
@api private
# File lib/dm-core/collection.rb, line 1003 def loaded_entries (loaded? ? self : head + tail).reject { |resource| resource.destroyed? } end
Returns the model key
@return [PropertySet]
the model key
@api private
# File lib/dm-core/collection.rb, line 993 def model_key model.key(repository_name) end
Returns the PropertySet representing the fields in the Collection scope
@return [PropertySet]
The set of properties this Collection's query will retrieve
@api private
# File lib/dm-core/collection.rb, line 1013 def properties model.properties(repository_name) end
Returns the Relationships for the Collection's Model
@return [Hash]
The model's relationships, mapping the name to the Associations::Relationship object
@api private
# File lib/dm-core/collection.rb, line 1024 def relationships model.relationships(repository_name) end
Private Instance Methods
Creates a resource in the collection
@param [Boolean] execute_hooks
Whether to execute hooks or not
@param [Hash] attributes
Attributes with which to create the new resource
@return [Resource]
a saved Resource
@api private
# File lib/dm-core/collection.rb, line 1211 def _create(attributes, execute_hooks = true) resource = repository.scope { model.send(execute_hooks ? :create : :create!, default_attributes.merge(attributes)) } self << resource if resource.saved? resource end
Saves a collection
@param [Boolean] execute_hooks
Whether to execute hooks or not
@return [Boolean]
Returns true if collection was updated
@api private
# File lib/dm-core/collection.rb, line 1237 def _save(execute_hooks = true) loaded_entries = self.loaded_entries loaded_entries.each { |resource| set_default_attributes(resource) } @removed.clear loaded_entries.all? { |resource| resource.__send__(execute_hooks ? :save : :save!) } end
Updates a collection
@return [Boolean]
Returns true if collection was updated
@api private
# File lib/dm-core/collection.rb, line 1223 def _update(dirty_attributes) repository.update(dirty_attributes, self) true end
Raises an exception if update is performed on a dirty resource
@raise [UpdateConflictError]
raise if the resource is dirty
@return [undefined]
@api private
# File lib/dm-core/collection.rb, line 1489 def assert_update_clean_only(method) if dirty? raise UpdateConflictError, "#{self.class}##{method} cannot be called on a dirty collection" end end
Raises an exception if get receives the wrong number of arguments
@param [Array] key
the key value
@return [undefined]
@raise [UpdateConflictError]
raise if the resource is dirty
@api private
# File lib/dm-core/collection.rb, line 1506 def assert_valid_key_size(key) expected_key_size = model_key.size actual_key_size = key.size if actual_key_size != expected_key_size raise ArgumentError, "The number of arguments for the key is invalid, expected #{expected_key_size} but was #{actual_key_size}" end end
Returns default values to initialize new Resources in the Collection
@return [Hash] The default attributes for new instances in this Collection
@api private
# File lib/dm-core/collection.rb, line 1249 def default_attributes return @default_attributes if @default_attributes default_attributes = {} conditions = query.conditions if conditions.slug == :and model_properties = properties.dup model_key = self.model_key if model_properties.to_set.superset?(model_key.to_set) model_properties -= model_key end conditions.each do |condition| next unless condition.slug == :eql subject = condition.subject next unless model_properties.include?(subject) || (condition.relationship? && subject.source_model == model) default_attributes[subject] = condition.loaded_value end end @default_attributes = default_attributes.freeze end
Delegate the method to the Model
@param [Symbol] method
the name of the method in the model to execute
@param [Array] *args
the arguments for the method
@return [Object]
the return value of the model method
@api private
# File lib/dm-core/collection.rb, line 1464 def delegate_to_model(method, *args, &block) model = self.model model.send(:with_scope, query) do model.send(method, *args, &block) end end
Delegate the method to the Relationship
@return [Collection]
the associated Resources
@api private
# File lib/dm-core/collection.rb, line 1477 def delegate_to_relationship(relationship, query = nil) relationship.eager_load(self, query) end
Filter resources in the collection based on a Query
@param [Query] query
the query to match each resource in the collection
@return [Array]
the resources that match the Query
@return [nil]
nil if no resources match the Query
@api private
# File lib/dm-core/collection.rb, line 1376 def filter(other_query) query = self.query fields = query.fields.to_set unique = other_query.unique? # TODO: push this into a Query#subset? method if other_query.links.empty? && (unique || (!unique && !query.unique?)) && !other_query.reload? && !other_query.raw? && other_query.fields.to_set.subset?(fields) && other_query.condition_properties.subset?(fields) then other_query.filter_records(to_a.dup) end end
Copies the original Collection state
@param [Collection] original
the original collection to copy from
@return [undefined]
@api private
# File lib/dm-core/collection.rb, line 1063 def initialize_copy(original) super @query = @query.dup @identity_map = @identity_map.dup @removed = @removed.dup end
Initialize a resource from a Hash
@param [Resource, Hash] resource
resource to process
@return [Resource]
an initialized resource
@api private
# File lib/dm-core/collection.rb, line 1079 def initialize_resource(resource) resource.kind_of?(Hash) ? new(resource) : resource end
Lazy loads a Collection
@return [self]
@api private
# File lib/dm-core/collection.rb, line 1107 def lazy_load if loaded? return self end mark_loaded head = self.head tail = self.tail query = self.query resources = repository.read(query) # remove already known results resources -= head if head.any? resources -= tail if tail.any? resources -= @removed.to_a if @removed.any? query.add_reversed? ? unshift(*resources.reverse) : concat(resources) # TODO: DRY this up with LazyArray @array.unshift(*head) @array.concat(tail) @head = @tail = nil @reapers.each { |resource| @array.delete_if(&resource) } if @reapers @array.freeze if frozen? self end
Delegates to Model, Relationships or the superclass (LazyArray)
When this receives a method that belongs to the Model the Collection is scoped to, it will execute the method within the same scope as the Collection and return the results.
When this receives a method that is a relationship the Model has defined, it will execute the association method within the same scope as the Collection and return the results.
Otherwise this method will delegate to a method in the superclass (LazyArray) and return the results.
@return [Object]
the return values of the delegated methods
@api public
# File lib/dm-core/collection.rb, line 1441 def method_missing(method, *args, &block) relationships = self.relationships if model.respond_to?(method) delegate_to_model(method, *args, &block) elsif relationship = relationships[method] || relationships[DataMapper::Inflector.singularize(method.to_s).to_sym] delegate_to_relationship(relationship, *args) else super end end
Initializes a new Collection
@return [Collection]
A new Collection object
@api private
# File lib/dm-core/collection.rb, line 1154 def new_collection(query, resources = nil, &block) if loaded? resources ||= filter(query) end # TOOD: figure out a way to pass not-yet-saved Resources to this newly # created Collection. If the new resource matches the conditions, then # it should be added to the collection (keep in mind limit/offset too) self.class.new(query, resources, &block) end
Test if the collection is loaded between the offset and limit
@param [Integer] offset
the offset of the collection to test
@param [Integer] limit
optional limit for how many entries to be loaded
@return [Boolean]
true if the collection is loaded from the offset to the limit
@api private
# File lib/dm-core/collection.rb, line 1094 def partially_loaded?(offset, limit = 1) if offset >= 0 lazy_possible?(head, offset + limit) else lazy_possible?(tail, offset.abs) end end
Returns the Query Repository name
@return [Symbol]
the repository name
@api private
# File lib/dm-core/collection.rb, line 1144 def repository_name repository.name end
Track the added resource
@param [Resource] resource
the resource that was added
@return [Resource]
the resource that was added
@api private
# File lib/dm-core/collection.rb, line 1300 def resource_added(resource) resource = initialize_resource(resource) if resource.saved? @identity_map[resource.key] = resource @removed.delete(resource) else set_default_attributes(resource) end resource end
Track the removed resource
@param [Resource] resource
the resource that was removed
@return [Resource]
the resource that was removed
@api private
# File lib/dm-core/collection.rb, line 1339 def resource_removed(resource) if resource.saved? @identity_map.delete(resource.key) @removed << resource end resource end
Track the added resources
@param [Array<Resource>] resources
the resources that were added
@return [Array<Resource>]
the resources that were added
@api private
# File lib/dm-core/collection.rb, line 1322 def resources_added(resources) if resources.kind_of?(Enumerable) resources.map { |resource| resource_added(resource) } else resource_added(resources) end end
Track the removed resources
@param [Array<Resource>] resources
the resources that were removed
@return [Array<Resource>]
the resources that were removed
@api private
# File lib/dm-core/collection.rb, line 1357 def resources_removed(resources) if resources.kind_of?(Enumerable) resources.each { |resource| resource_removed(resource) } else resource_removed(resources) end end
Return the absolute or relative scoped query
@param [Query, Hash] query
the query to scope the collection with
@return [Query]
the absolute or relative scoped query
@api private
# File lib/dm-core/collection.rb, line 1402 def scoped_query(query) if query.kind_of?(Query) query.dup else self.query.relative(query) end end
Set the default attributes for a non-frozen resource
@param [Resource] resource
the resource to set the default attributes for
@return [undefined]
@api private
# File lib/dm-core/collection.rb, line 1285 def set_default_attributes(resource) unless resource.readonly? resource.attributes = default_attributes end end
Apply a set operation on self and another collection
@param [Symbol] operation
the set operation to apply
@param [Collection] other
the other collection to apply the set operation on
@return [Collection]
the collection that was created for the set operation
@api private
# File lib/dm-core/collection.rb, line 1177 def set_operation(operation, other) resources = set_operation_resources(operation, other) other_query = Query.target_query(repository, model, other) new_collection(query.send(operation, other_query), resources) end
Prepopulate the set operation if the collection is loaded
@param [Symbol] operation
the set operation to apply
@param [Collection] other
the other collection to apply the set operation on
@return [nil]
nil if the Collection is not loaded
@return [Array]
the resources to prepopulate the set operation results with
@api private
# File lib/dm-core/collection.rb, line 1196 def set_operation_resources(operation, other) entries.send(operation, other.entries) if loaded? end
@api private
# File lib/dm-core/collection.rb, line 1411 def sliced_query(offset, limit) query = self.query if offset >= 0 query.slice(offset, limit) else query = query.slice((limit + offset).abs, limit).reverse! # tell the Query to prepend each result from the adapter query.update(:add_reversed => !query.add_reversed?) end end