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 Model#all method.
Returns the Query the Collection is scoped with
@return [Query]
the Query the Collection is scoped with
@api semipublic
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 1039 1039: def initialize(query, resources = nil) 1040: raise "#{self.class}#new with a block is deprecated" if block_given? 1041: 1042: @query = query 1043: @identity_map = IdentityMap.new 1044: @removed = Set.new 1045: 1046: super() 1047: 1048: # TODO: change LazyArray to not use a load proc at all 1049: remove_instance_variable(:@load_with_proc) 1050: 1051: set(resources) if resources 1052: end
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 535 535: def <<(resource) 536: super(resource_added(resource)) 537: 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 389: def [](*args) 390: offset, limit = extract_slice_arguments(*args) 391: 392: if args.size == 1 && args.first.kind_of?(Integer) 393: return at(offset) 394: end 395: 396: query = sliced_query(offset, limit) 397: 398: if loaded? || partially_loaded?(offset, limit) 399: new_collection(query, super) 400: else 401: new_collection(query) 402: end 403: 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 453: def []=(*args) 454: orphans = Array(superclass_slice(*args[0..2])) 455: 456: # relate new resources 457: resources = resources_added(super) 458: 459: # mark resources as removed 460: resources_removed(orphans - loaded_entries) 461: 462: resources 463: 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 213: def all(query = Undefined) 214: if query.equal?(Undefined) || (query.kind_of?(Hash) && query.empty?) 215: dup 216: else 217: # TODO: if there is no order parameter, and the Collection is not loaded 218: # check to see if the query can be satisfied by the head/tail 219: new_collection(scoped_query(query)) 220: end 221: 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 346: def at(offset) 347: if loaded? || partially_loaded?(offset) 348: super 349: elsif offset == 0 350: first 351: elsif offset > 0 352: first(:offset => offset) 353: elsif offset == 1 354: last 355: else 356: last(:offset => offset.abs - 1) 357: end 358: 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 954 954: def clean? 955: !dirty? 956: 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 726 726: def clear 727: if loaded? 728: resources_removed(self) 729: end 730: super 731: 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 521 521: def collect! 522: super { |resource| resource_added(yield(resource_removed(resource))) } 523: 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 547 547: def concat(resources) 548: super(resources_added(resources)) 549: 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 792 792: def create(attributes = {}) 793: _create(attributes) 794: 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 805 805: def create!(attributes = {}) 806: _create(attributes, false) 807: 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 634 634: def delete(resource) 635: if resource = super 636: resource_removed(resource) 637: end 638: 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 655 655: def delete_at(offset) 656: if resource = super 657: resource_removed(resource) 658: end 659: 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 668 668: def delete_if 669: super { |resource| yield(resource) && resource_removed(resource) } 670: 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 894 894: def destroy 895: if destroyed = all? { |resource| resource.destroy } 896: clear 897: end 898: 899: destroyed 900: 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 912 912: def destroy! 913: repository = self.repository 914: deleted = repository.delete(self) 915: 916: if loaded? 917: unless deleted == size 918: return false 919: end 920: 921: each do |resource| 922: resource.persistence_state = Resource::PersistenceState::Immutable.new(resource) 923: end 924: 925: clear 926: else 927: mark_loaded 928: end 929: 930: true 931: 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 123: def difference(other) 124: set_operation(:-, other) 125: 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 964 964: def dirty? 965: loaded_entries.any? { |resource| resource.dirty? } || @removed.any? 966: end
Iterate over each Resource
@yield [Resource] Each resource in the collection
@return [self]
@api public
# File lib/dm-core/collection.rb, line 503 503: def each 504: super do |resource| 505: begin 506: original, resource.collection = resource.collection, self 507: yield resource 508: ensure 509: resource.collection = original 510: end 511: end 512: 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 240: def first(*args) 241: first_arg = args.first 242: last_arg = args.last 243: 244: limit_specified = first_arg.kind_of?(Integer) 245: with_query = (last_arg.kind_of?(Hash) && !last_arg.empty?) || last_arg.kind_of?(Query) 246: 247: limit = limit_specified ? first_arg : 1 248: query = with_query ? last_arg : {} 249: 250: query = self.query.slice(0, limit).update(query) 251: 252: # TODO: when a query provided, and there are enough elements in head to 253: # satisfy the query.limit, filter the head with the query, and make 254: # sure it matches the limit exactly. if so, use that result instead 255: # of calling all() 256: # - this can probably only be done if there is no :order parameter 257: 258: loaded = loaded? 259: head = self.head 260: 261: collection = if !with_query && (loaded || lazy_possible?(head, limit)) 262: new_collection(query, super(limit)) 263: else 264: all(query) 265: end 266: 267: return collection if limit_specified 268: 269: resource = collection.to_a.first 270: 271: if with_query || loaded 272: resource 273: elsif resource 274: head[0] = resource 275: end 276: 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 764 764: def first_or_create(conditions = {}, attributes = {}) 765: first(conditions) || create(conditions.merge(attributes)) 766: 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 749 749: def first_or_new(conditions = {}, attributes = {}) 750: first(conditions) || new(conditions.merge(attributes)) 751: 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 147: def get(*key) 148: assert_valid_key_size(key) 149: 150: key = model_key.typecast(key) 151: query = self.query 152: 153: @identity_map[key] || if !loaded? && (query.limit || query.offset > 0) 154: # current query is exclusive, find resource within the set 155: 156: # TODO: use a subquery to retrieve the Collection and then match 157: # it up against the key. This will require some changes to 158: # how subqueries are generated, since the key may be a 159: # composite key. In the case of DO adapters, it means subselects 160: # like the form "(a, b) IN(SELECT a, b FROM ...)", which will 161: # require making it so the Query condition key can be a 162: # Property or an Array of Property objects 163: 164: # use the brute force approach until subquery lookups work 165: lazy_load 166: @identity_map[key] 167: else 168: # current query is all inclusive, lookup using normal approach 169: first(model.key_conditions(repository, key).update(:order => nil)) 170: end 171: 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 189: def get!(*key) 190: get(*key) || raise(ObjectNotFoundError, "Could not find #{model.name} with key #{key.inspect}") 191: end
@api semipublic
# File lib/dm-core/collection.rb, line 980 980: def hash 981: self.class.hash ^ query.hash 982: 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 591 591: def insert(offset, *resources) 592: super(offset, *resources_added(resources)) 593: 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 975 975: def inspect 976: "[#{map { |resource| resource.inspect }.join(', ')}]" 977: 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 108: def intersection(other) 109: set_operation(:&, other) 110: 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 295: def last(*args) 296: first_arg = args.first 297: last_arg = args.last 298: 299: limit_specified = first_arg.kind_of?(Integer) 300: with_query = (last_arg.kind_of?(Hash) && !last_arg.empty?) || last_arg.kind_of?(Query) 301: 302: limit = limit_specified ? first_arg : 1 303: query = with_query ? last_arg : {} 304: 305: query = self.query.slice(0, limit).update(query).reverse! 306: 307: # tell the Query to prepend each result from the adapter 308: query.update(:add_reversed => !query.add_reversed?) 309: 310: # TODO: when a query provided, and there are enough elements in tail to 311: # satisfy the query.limit, filter the tail with the query, and make 312: # sure it matches the limit exactly. if so, use that result instead 313: # of calling all() 314: 315: loaded = loaded? 316: tail = self.tail 317: 318: collection = if !with_query && (loaded || lazy_possible?(tail, limit)) 319: new_collection(query, super(limit)) 320: else 321: all(query) 322: end 323: 324: return collection if limit_specified 325: 326: resource = collection.to_a.last 327: 328: if with_query || loaded 329: resource 330: elsif resource 331: tail[tail.empty? ? 0 : 1] = resource 332: end 333: end
Returns the Model
@return [Model]
the Model the Collection is associated with
@api semipublic
# File lib/dm-core/collection.rb, line 45 45: def model 46: query.model 47: 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 777 777: def new(attributes = {}) 778: resource = repository.scope { model.new(attributes) } 779: self << resource 780: resource 781: 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 601 601: def pop(*) 602: if removed = super 603: resources_removed(removed) 604: end 605: 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 562 562: def push(*resources) 563: super(*resources_added(resources)) 564: 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 682 682: def reject! 683: super { |resource| yield(resource) && resource_removed(resource) } 684: 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 64: def reload(other_query = Undefined) 65: query = self.query 66: query = other_query.equal?(Undefined) ? query.dup : query.merge(other_query) 67: 68: # make sure the Identity Map contains all the existing resources 69: identity_map = repository.identity_map(model) 70: 71: loaded_entries.each do |resource| 72: identity_map[resource.key] = resource 73: end 74: 75: # sort fields based on declared order, for more consistent reload queries 76: properties = self.properties 77: fields = properties & (query.fields | model_key | [ properties.discriminator ].compact) 78: 79: # replace the list of resources 80: replace(all(query.update(:fields => fields, :reload => true))) 81: 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 700 700: def replace(other) 701: other = resources_added(other) 702: resources_removed(entries - other) 703: super(other) 704: end
Returns the Repository
@return [Repository]
the Repository this Collection is associated with
@api semipublic
# File lib/dm-core/collection.rb, line 35 35: def repository 36: query.repository 37: 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 944 944: def respond_to?(method, include_private = false) 945: super || model.respond_to?(method) || relationships.named?(method) 946: 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 473: def reverse 474: dup.reverse! 475: end
Return the Collection sorted in reverse
@return [self]
@api public
# File lib/dm-core/collection.rb, line 482 482: def reverse! 483: query.reverse! 484: 485: # reverse without kicking if possible 486: if loaded? 487: @array.reverse! 488: else 489: # reverse and swap the head and tail 490: @head, @tail = tail.reverse!, head.reverse! 491: end 492: 493: self 494: 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 871 871: def save 872: _save 873: 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 881 881: def save! 882: _save(false) 883: 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 714 714: def set(resources) 715: superclass_replace(resources_added(resources)) 716: self 717: 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 613 613: def shift(*) 614: if removed = super 615: resources_removed(removed) 616: end 617: 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 419: def slice!(*args) 420: removed = super 421: 422: resources_removed(removed) unless removed.nil? 423: 424: # Workaround for Ruby <= 1.8.6 425: compact! if RUBY_VERSION <= '1.8.6' 426: 427: unless removed.kind_of?(Enumerable) 428: return removed 429: end 430: 431: offset, limit = extract_slice_arguments(*args) 432: 433: query = sliced_query(offset, limit) 434: 435: new_collection(query, removed) 436: end
Access LazyArray#replace directly
@api private
Access LazyArray#slice directly
Collection#[]= uses this to bypass Collection#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 92: def union(other) 93: set_operation(:|, other) 94: 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 577 577: def unshift(*resources) 578: super(*resources_added(resources)) 579: 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 820 820: def update(attributes) 821: assert_update_clean_only(:update) 822: 823: dirty_attributes = model.new(attributes).dirty_attributes 824: dirty_attributes.empty? || all? { |resource| resource.update(attributes) } 825: 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 838 838: def update!(attributes) 839: assert_update_clean_only(:update!) 840: 841: model = self.model 842: 843: dirty_attributes = model.new(attributes).dirty_attributes 844: 845: if dirty_attributes.empty? 846: true 847: elsif dirty_attributes.any? { |property, value| !property.valid?(value) } 848: false 849: else 850: unless _update(dirty_attributes) 851: return false 852: end 853: 854: if loaded? 855: each do |resource| 856: dirty_attributes.each { |property, value| property.set!(resource, value) } 857: repository.identity_map(model)[resource.key] = resource 858: end 859: end 860: 861: true 862: end 863: end
Loaded Resources in the collection
@return [Array
Resources in the collection
@api private
# File lib/dm-core/collection.rb, line 1002 1002: def loaded_entries 1003: (loaded? ? self : head + tail).reject { |resource| resource.destroyed? } 1004: end
Returns the model key
@return [PropertySet]
the model key
@api private
# File lib/dm-core/collection.rb, line 992 992: def model_key 993: model.key(repository_name) 994: 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 1012 1012: def properties 1013: model.properties(repository_name) 1014: 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 1023 1023: def relationships 1024: model.relationships(repository_name) 1025: end
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 1210 1210: def _create(attributes, execute_hooks = true) 1211: resource = repository.scope { model.send(execute_hooks ? :create : :create!, default_attributes.merge(attributes)) } 1212: self << resource if resource.saved? 1213: resource 1214: 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 1236 1236: def _save(execute_hooks = true) 1237: loaded_entries = self.loaded_entries 1238: loaded_entries.each { |resource| set_default_attributes(resource) } 1239: @removed.clear 1240: loaded_entries.all? { |resource| resource.__send__(execute_hooks ? :save : :save!) } 1241: end
Updates a collection
@return [Boolean]
Returns true if collection was updated
@api private
# File lib/dm-core/collection.rb, line 1222 1222: def _update(dirty_attributes) 1223: repository.update(dirty_attributes, self) 1224: true 1225: end
Raises an exception if # 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 1488 1488: def assert_update_clean_only(method) 1489: if dirty? 1490: raise UpdateConflictError, "#{self.class}##{method} cannot be called on a dirty collection" 1491: end 1492: end
Raises an exception if # 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 1505 1505: def assert_valid_key_size(key) 1506: expected_key_size = model_key.size 1507: actual_key_size = key.size 1508: 1509: if actual_key_size != expected_key_size 1510: raise ArgumentError, "The number of arguments for the key is invalid, expected #{expected_key_size} but was #{actual_key_size}" 1511: end 1512: 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 1248 1248: def default_attributes 1249: return @default_attributes if @default_attributes 1250: 1251: default_attributes = {} 1252: 1253: conditions = query.conditions 1254: 1255: if conditions.slug == :and 1256: model_properties = properties.dup 1257: model_key = self.model_key 1258: 1259: if model_properties.to_set.superset?(model_key.to_set) 1260: model_properties -= model_key 1261: end 1262: 1263: conditions.each do |condition| 1264: next unless condition.slug == :eql 1265: 1266: subject = condition.subject 1267: next unless model_properties.include?(subject) || (condition.relationship? && subject.source_model == model) 1268: 1269: default_attributes[subject] = condition.loaded_value 1270: end 1271: end 1272: 1273: @default_attributes = default_attributes.freeze 1274: 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 1463 1463: def delegate_to_model(method, *args, &block) 1464: model = self.model 1465: model.send(:with_scope, query) do 1466: model.send(method, *args, &block) 1467: end 1468: end
Delegate the method to the Relationship
@return [Collection]
the associated Resources
@api private
# File lib/dm-core/collection.rb, line 1476 1476: def delegate_to_relationship(relationship, query = nil) 1477: relationship.eager_load(self, query) 1478: 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 1375 1375: def filter(other_query) 1376: query = self.query 1377: fields = query.fields.to_set 1378: unique = other_query.unique? 1379: 1380: # TODO: push this into a Query#subset? method 1381: if other_query.links.empty? && 1382: (unique || (!unique && !query.unique?)) && 1383: !other_query.reload? && 1384: !other_query.raw? && 1385: other_query.fields.to_set.subset?(fields) && 1386: other_query.condition_properties.subset?(fields) 1387: then 1388: other_query.filter_records(to_a.dup) 1389: end 1390: 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 1062 1062: def initialize_copy(original) 1063: super 1064: @query = @query.dup 1065: @identity_map = @identity_map.dup 1066: @removed = @removed.dup 1067: 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 1078 1078: def initialize_resource(resource) 1079: resource.kind_of?(Hash) ? new(resource) : resource 1080: end
Lazy loads a Collection
@return [self]
@api private
# File lib/dm-core/collection.rb, line 1106 1106: def lazy_load 1107: if loaded? 1108: return self 1109: end 1110: 1111: mark_loaded 1112: 1113: head = self.head 1114: tail = self.tail 1115: query = self.query 1116: 1117: resources = repository.read(query) 1118: 1119: # remove already known results 1120: resources -= head if head.any? 1121: resources -= tail if tail.any? 1122: resources -= @removed.to_a if @removed.any? 1123: 1124: query.add_reversed? ? unshift(*resources.reverse) : concat(resources) 1125: 1126: # TODO: DRY this up with LazyArray 1127: @array.unshift(*head) 1128: @array.concat(tail) 1129: 1130: @head = @tail = nil 1131: @reapers.each { |resource| @array.delete_if(&resource) } if @reapers 1132: @array.freeze if frozen? 1133: 1134: self 1135: 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 1440 1440: def method_missing(method, *args, &block) 1441: relationships = self.relationships 1442: 1443: if model.respond_to?(method) 1444: delegate_to_model(method, *args, &block) 1445: elsif relationship = relationships[method] || relationships[DataMapper::Inflector.singularize(method.to_s).to_sym] 1446: delegate_to_relationship(relationship, *args) 1447: else 1448: super 1449: end 1450: end
Initializes a new Collection
@return [Collection]
A new Collection object
@api private
# File lib/dm-core/collection.rb, line 1153 1153: def new_collection(query, resources = nil, &block) 1154: if loaded? 1155: resources ||= filter(query) 1156: end 1157: 1158: # TOOD: figure out a way to pass not-yet-saved Resources to this newly 1159: # created Collection. If the new resource matches the conditions, then 1160: # it should be added to the collection (keep in mind limit/offset too) 1161: 1162: self.class.new(query, resources, &block) 1163: 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 1093 1093: def partially_loaded?(offset, limit = 1) 1094: if offset >= 0 1095: lazy_possible?(head, offset + limit) 1096: else 1097: lazy_possible?(tail, offset.abs) 1098: end 1099: end
Returns the Query Repository name
@return [Symbol]
the repository name
@api private
# File lib/dm-core/collection.rb, line 1143 1143: def repository_name 1144: repository.name 1145: 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 1299 1299: def resource_added(resource) 1300: resource = initialize_resource(resource) 1301: 1302: if resource.saved? 1303: @identity_map[resource.key] = resource 1304: @removed.delete(resource) 1305: else 1306: set_default_attributes(resource) 1307: end 1308: 1309: resource 1310: 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 1338 1338: def resource_removed(resource) 1339: if resource.saved? 1340: @identity_map.delete(resource.key) 1341: @removed << resource 1342: end 1343: 1344: resource 1345: end
Track the added resources
@param [Array
the resources that were added
@return [Array
the resources that were added
@api private
# File lib/dm-core/collection.rb, line 1321 1321: def resources_added(resources) 1322: if resources.kind_of?(Enumerable) 1323: resources.map { |resource| resource_added(resource) } 1324: else 1325: resource_added(resources) 1326: end 1327: end
Track the removed resources
@param [Array
the resources that were removed
@return [Array
the resources that were removed
@api private
# File lib/dm-core/collection.rb, line 1356 1356: def resources_removed(resources) 1357: if resources.kind_of?(Enumerable) 1358: resources.each { |resource| resource_removed(resource) } 1359: else 1360: resource_removed(resources) 1361: end 1362: 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 1401 1401: def scoped_query(query) 1402: if query.kind_of?(Query) 1403: query.dup 1404: else 1405: self.query.relative(query) 1406: end 1407: 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 1284 1284: def set_default_attributes(resource) 1285: unless resource.readonly? 1286: resource.attributes = default_attributes 1287: end 1288: 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 1176 1176: def set_operation(operation, other) 1177: resources = set_operation_resources(operation, other) 1178: other_query = Query.target_query(repository, model, other) 1179: new_collection(query.send(operation, other_query), resources) 1180: 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 1195 1195: def set_operation_resources(operation, other) 1196: entries.send(operation, other.entries) if loaded? 1197: end
@api private
# File lib/dm-core/collection.rb, line 1410 1410: def sliced_query(offset, limit) 1411: query = self.query 1412: 1413: if offset >= 0 1414: query.slice(offset, limit) 1415: else 1416: query = query.slice((limit + offset).abs, limit).reverse! 1417: 1418: # tell the Query to prepend each result from the adapter 1419: query.update(:add_reversed => !query.add_reversed?) 1420: end 1421: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.