Class: DataMapper::Collection
- Inherits:
-
LazyArray
- Object
- LazyArray
- DataMapper::Collection
- Extended by:
- Deprecate
- Defined in:
- lib/dm-core/collection.rb
Overview
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.
Direct Known Subclasses
Instance Attribute Summary collapse
-
#query ⇒ Query
readonly
Returns the Query the Collection is scoped with.
Instance Method Summary collapse
-
#<<(resource) ⇒ self
Append one Resource to the Collection and relate it.
-
#[](*args) ⇒ Resource, ...
(also: #slice)
Simulates Array#slice and returns a new Collection whose query has a new offset or limit according to the arguments provided.
-
#[]=(*args) ⇒ Resource, ...
(also: #splice)
Splice a list of Resources at a given offset or range.
-
#all(query = nil) ⇒ Collection
Returns a new Collection optionally scoped by
query
. -
#at(offset) ⇒ Resource?
Lookup a Resource from the Collection by offset.
-
#clean? ⇒ Boolean
Checks if all the resources have no changes to save.
-
#clear ⇒ self
Removes all Resources from the Collection.
-
#collect! {|Resource| ... } ⇒ self
(also: #map!)
Invoke the block for each resource and replace it the return value.
-
#concat(resources) ⇒ self
Appends the resources to self.
-
#create(attributes = {}) ⇒ Resource
Create a Resource in the Collection.
-
#create!(attributes = {}) ⇒ Resource
Create a Resource in the Collection, bypassing hooks.
-
#delete(resource) ⇒ Resource?
Remove Resource from the Collection.
-
#delete_at(offset) ⇒ Resource?
Remove Resource from the Collection by offset.
-
#delete_if {|Resource| ... } ⇒ self
Deletes every Resource for which block evaluates to true.
-
#destroy ⇒ Boolean
Remove every Resource in the Collection from the repository.
-
#destroy! ⇒ Boolean
Remove all Resources from the repository, bypassing validation.
-
#dirty? ⇒ Boolean
Checks if any resources have unsaved changes.
-
#first(*args) ⇒ Resource, Collection
Return the first Resource or the first N Resources in the Collection with an optional query.
-
#first_or_create(conditions = {}, attributes = {}) ⇒ Resource
Finds the first Resource by conditions, or creates a new Resource with the attributes if none found.
-
#first_or_new(conditions = {}, attributes = {}) ⇒ Resource
Finds the first Resource by conditions, or initializes a new Resource with the attributes if none found.
-
#get(*key) ⇒ Resource?
Lookup a Resource in the Collection by key.
-
#get!(*key) ⇒ Resource?
Lookup a Resource in the Collection by key, raising an exception if not found.
-
#insert(offset, *resources) ⇒ self
Inserts the Resources before the Resource at the offset (which may be negative).
-
#inspect ⇒ String
Gets a Human-readable representation of this collection, showing all elements contained in it.
-
#last(*args) ⇒ Resource, Collection
Return the last Resource or the last N Resources in the Collection with an optional query.
-
#model ⇒ Model
Returns the Model.
-
#new(attributes = {}) ⇒ Resource
Initializes a Resource and appends it to the Collection.
-
#pop ⇒ Resource
Removes and returns the last Resource in the Collection.
-
#properties ⇒ PropertySet
Returns the PropertySet representing the fields in the Collection scope.
-
#push(*resources) ⇒ self
Append one or more Resources to the Collection.
-
#reject! {|Resource| ... } ⇒ Collection?
Deletes every Resource for which block evaluates to true.
-
#relationships ⇒ Hash
Returns the Relationships for the Collection’s Model.
-
#reload(query = nil) ⇒ self
Reloads the Collection from the repository.
-
#replace(other) ⇒ self
(also: #collection_replace)
Replace the Resources within the Collection.
-
#repository ⇒ Repository
Returns the Repository.
-
#respond_to?(method, include_private = false) ⇒ Boolean
Check to see if collection can respond to the method.
-
#reverse ⇒ Collection
Return a copy of the Collection sorted in reverse.
-
#reverse! ⇒ self
Return the Collection sorted in reverse.
-
#save ⇒ Boolean
Save every Resource in the Collection.
-
#save! ⇒ Boolean
Save every Resource in the Collection bypassing validation.
-
#shift ⇒ Resource
Removes and returns the first Resource in the Collection.
-
#slice!(*args) ⇒ Resource, ...
Deletes and Returns the Resources given by an offset or a Range.
-
#unshift(*resources) ⇒ self
Prepend one or more Resources to the Collection.
-
#update(attributes = {}) ⇒ Boolean
Update every Resource in the Collection.
-
#update!(attributes = {}) ⇒ Boolean
Update every Resource in the Collection bypassing validation.
Methods included from Deprecate
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method, *args, &block) ⇒ Object (private)
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.
1342 1343 1344 1345 1346 1347 1348 1349 1350 |
# File 'lib/dm-core/collection.rb', line 1342 def method_missing(method, *args, &block) if model.model_method_defined?(method) delegate_to_model(method, *args, &block) elsif relationship = relationships[method] || relationships[method.to_s.singular.to_sym] delegate_to_relationship(relationship, *args) else super end end |
Instance Attribute Details
#query ⇒ Query (readonly)
Returns the Query the Collection is scoped with
31 32 33 |
# File 'lib/dm-core/collection.rb', line 31 def query @query end |
Instance Method Details
#<<(resource) ⇒ self
Append one Resource to the Collection and relate it
462 463 464 465 466 467 468 469 |
# File 'lib/dm-core/collection.rb', line 462 def <<(resource) if resource.kind_of?(Hash) resource = new(resource) end resource_added(resource) super end |
#[](*args) ⇒ Resource, ... Also known as: slice
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.
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/dm-core/collection.rb', line 331 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 |
#[]=(*args) ⇒ Resource, ... Also known as: splice
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.
395 396 397 398 399 400 401 402 403 404 405 406 407 408 |
# File 'lib/dm-core/collection.rb', line 395 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) # ensure remaining orphans are still related (orphans & loaded_entries).each { |resource| relate_resource(resource) } resources end |
#all(query = nil) ⇒ Collection
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.
177 178 179 180 181 182 183 184 185 186 |
# File 'lib/dm-core/collection.rb', line 177 def all(query = nil) if query.nil? || (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 |
#at(offset) ⇒ Resource?
Lookup a Resource from the Collection by offset
291 292 293 294 295 296 297 298 299 300 |
# File 'lib/dm-core/collection.rb', line 291 def at(offset) if loaded? || partially_loaded?(offset) return unless resource = super orphan_resource(resource) elsif offset >= 0 first(:offset => offset) else last(:offset => offset.abs - 1) end end |
#clean? ⇒ Boolean
Checks if all the resources have no changes to save
880 881 882 |
# File 'lib/dm-core/collection.rb', line 880 def clean? !dirty? end |
#clear ⇒ self
Removes all Resources from the Collection
This should remove and orphan each Resource from the Collection
659 660 661 662 663 664 |
# File 'lib/dm-core/collection.rb', line 659 def clear if loaded? resources_removed(self) end super end |
#collect! {|Resource| ... } ⇒ self Also known as: map!
Invoke the block for each resource and replace it the return value
448 449 450 |
# File 'lib/dm-core/collection.rb', line 448 def collect! super { |resource| resource_added(yield(resource_removed(resource))) } end |
#concat(resources) ⇒ self
Appends the resources to self
479 480 481 482 |
# File 'lib/dm-core/collection.rb', line 479 def concat(resources) resources_added(resources) super end |
#create(attributes = {}) ⇒ Resource
Create a Resource in the Collection
720 721 722 |
# File 'lib/dm-core/collection.rb', line 720 def create(attributes = {}) _create(true, attributes) end |
#create!(attributes = {}) ⇒ Resource
Create a Resource in the Collection, bypassing hooks
733 734 735 |
# File 'lib/dm-core/collection.rb', line 733 def create!(attributes = {}) _create(false, attributes) end |
#delete(resource) ⇒ Resource?
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.
570 571 572 573 574 |
# File 'lib/dm-core/collection.rb', line 570 def delete(resource) if resource = super resource_removed(resource) end end |
#delete_at(offset) ⇒ Resource?
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.
591 592 593 594 595 |
# File 'lib/dm-core/collection.rb', line 591 def delete_at(offset) if resource = super resource_removed(resource) end end |
#delete_if {|Resource| ... } ⇒ self
Deletes every Resource for which block evaluates to true.
604 605 606 |
# File 'lib/dm-core/collection.rb', line 604 def delete_if super { |resource| yield(resource) && resource_removed(resource) } end |
#destroy ⇒ Boolean
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.
820 821 822 823 824 825 826 |
# File 'lib/dm-core/collection.rb', line 820 def destroy if destroyed = all? { |resource| resource.destroy } clear end destroyed end |
#destroy! ⇒ Boolean
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.
838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 |
# File 'lib/dm-core/collection.rb', line 838 def destroy! if query.limit || query.offset > 0 || query.links.any? key = model.key(repository.name) conditions = Query.target_conditions(self, key, key) unless model.all(:repository => repository, :conditions => conditions).destroy! return false end else repository.delete(self) mark_loaded end if loaded? each { |resource| resource.reset } clear end true end |
#dirty? ⇒ Boolean
Checks if any resources have unsaved changes
890 891 892 |
# File 'lib/dm-core/collection.rb', line 890 def dirty? loaded_entries.any? { |resource| resource.dirty? } end |
#first(*args) ⇒ Resource, Collection
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.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/dm-core/collection.rb', line 205 def first(*args) last_arg = args.last limit = args.first if args.first.kind_of?(Integer) with_query = last_arg.respond_to?(:merge) && !last_arg.blank? query = with_query ? last_arg : {} query = self.query.slice(0, limit || 1).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 collection = if !with_query && (loaded? || lazy_possible?(head, query.limit)) new_collection(query, super(query.limit)) else all(query) end if limit collection else collection.to_a.first end end |
#first_or_create(conditions = {}, attributes = {}) ⇒ Resource
Finds the first Resource by conditions, or creates a new Resource with the attributes if none found
692 693 694 |
# File 'lib/dm-core/collection.rb', line 692 def first_or_create(conditions = {}, attributes = {}) first(conditions) || create(conditions.merge(attributes)) end |
#first_or_new(conditions = {}, attributes = {}) ⇒ Resource
Finds the first Resource by conditions, or initializes a new Resource with the attributes if none found
677 678 679 |
# File 'lib/dm-core/collection.rb', line 677 def first_or_new(conditions = {}, attributes = {}) first(conditions) || new(conditions.merge(attributes)) end |
#get(*key) ⇒ Resource?
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
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/dm-core/collection.rb', line 110 def get(*key) key = model.key(repository.name).typecast(key) resource = @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)) end return if resource.nil? orphan_resource(resource) end |
#get!(*key) ⇒ Resource?
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.
153 154 155 |
# File 'lib/dm-core/collection.rb', line 153 def get!(*key) get(*key) || raise(ObjectNotFoundError, "Could not find #{model.name} with key #{key.inspect}") end |
#insert(offset, *resources) ⇒ self
Inserts the Resources before the Resource at the offset (which may be negative).
526 527 528 529 |
# File 'lib/dm-core/collection.rb', line 526 def insert(offset, *resources) resources_added(resources) super end |
#inspect ⇒ String
Gets a Human-readable representation of this collection, showing all elements contained in it
901 902 903 |
# File 'lib/dm-core/collection.rb', line 901 def inspect "[#{map { |resource| resource.inspect }.join(', ')}]" end |
#last(*args) ⇒ Resource, Collection
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.
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/dm-core/collection.rb', line 250 def last(*args) last_arg = args.last limit = args.first if args.first.kind_of?(Integer) with_query = last_arg.respond_to?(:merge) && !last_arg.blank? query = with_query ? last_arg : {} query = self.query.slice(0, limit || 1).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() collection = if !with_query && (loaded? || lazy_possible?(tail, query.limit)) new_collection(query, super(query.limit)) else all(query) end if limit collection else collection.to_a.last end end |
#model ⇒ Model
Returns the Model
49 50 51 |
# File 'lib/dm-core/collection.rb', line 49 def model query.model end |
#new(attributes = {}) ⇒ Resource
Initializes a Resource and appends it to the Collection
705 706 707 708 709 |
# File 'lib/dm-core/collection.rb', line 705 def new(attributes = {}) resource = repository.scope { model.new(attributes) } self << resource resource end |
#pop ⇒ Resource
Removes and returns the last Resource in the Collection
537 538 539 540 541 |
# File 'lib/dm-core/collection.rb', line 537 def pop(*) if removed = super resources_removed(removed) end end |
#properties ⇒ PropertySet
Returns the PropertySet representing the fields in the Collection scope
911 912 913 |
# File 'lib/dm-core/collection.rb', line 911 def properties PropertySet.new(query.fields) end |
#push(*resources) ⇒ self
Append one or more Resources to the Collection
This should append one or more Resources to the Collection and relate each to the Collection.
495 496 497 498 |
# File 'lib/dm-core/collection.rb', line 495 def push(*resources) resources_added(resources) super end |
#reject! {|Resource| ... } ⇒ Collection?
Deletes every Resource for which block evaluates to true
618 619 620 |
# File 'lib/dm-core/collection.rb', line 618 def reject! super { |resource| yield(resource) && resource_removed(resource) } end |
#relationships ⇒ Hash
Returns the Relationships for the Collection’s Model
922 923 924 |
# File 'lib/dm-core/collection.rb', line 922 def relationships model.relationships(repository.name) end |
#reload(query = nil) ⇒ self
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
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/dm-core/collection.rb', line 68 def reload(query = nil) query = query.nil? ? self.query.dup : self.query.merge(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 properties = model.properties(repository.name) fields = properties.key | query.fields if discriminator = properties.discriminator fields |= [ discriminator ] end # sort fields based on declared order, for more consistent reload queries fields = properties & fields # replace the list of resources replace(all(query.update(:fields => fields, :reload => true))) end |
#replace(other) ⇒ self Also known as: collection_replace
Replace the Resources within the Collection
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 |
# File 'lib/dm-core/collection.rb', line 630 def replace(other) other = other.map do |resource| if resource.kind_of?(Hash) new(resource) else resource end end if loaded? resources_removed(self - other) end super(resources_added(other)) end |
#repository ⇒ Repository
Returns the Repository
39 40 41 |
# File 'lib/dm-core/collection.rb', line 39 def repository query.repository end |
#respond_to?(method, include_private = false) ⇒ Boolean
Check to see if collection can respond to the method
870 871 872 |
# File 'lib/dm-core/collection.rb', line 870 def respond_to?(method, include_private = false) super || model.respond_to?(method) || relationships.key?(method) end |
#reverse ⇒ Collection
Return a copy of the Collection sorted in reverse
418 419 420 |
# File 'lib/dm-core/collection.rb', line 418 def reverse dup.reverse! end |
#reverse! ⇒ self
Return the Collection sorted in reverse
427 428 429 430 431 432 433 434 435 436 437 438 439 |
# File 'lib/dm-core/collection.rb', line 427 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 ⇒ Boolean
Save every Resource in the Collection
797 798 799 |
# File 'lib/dm-core/collection.rb', line 797 def save _save(true) end |
#save! ⇒ Boolean
Save every Resource in the Collection bypassing validation
807 808 809 |
# File 'lib/dm-core/collection.rb', line 807 def save! _save(false) end |
#shift ⇒ Resource
Removes and returns the first Resource in the Collection
549 550 551 552 553 |
# File 'lib/dm-core/collection.rb', line 549 def shift(*) if removed = super resources_removed(removed) end end |
#slice!(*args) ⇒ Resource, ...
Deletes and Returns the Resources given by an offset or a Range
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 |
# File 'lib/dm-core/collection.rb', line 361 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 |
#unshift(*resources) ⇒ self
Prepend one or more Resources to the Collection
This should prepend one or more Resources to the Collection and relate each to the Collection.
511 512 513 514 |
# File 'lib/dm-core/collection.rb', line 511 def unshift(*resources) resources_added(resources) super end |
#update(attributes = {}) ⇒ Boolean
Update every Resource in the Collection
Person.all(:age.gte => 21).update(:allow_beer => true)
748 749 750 751 752 753 |
# File 'lib/dm-core/collection.rb', line 748 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!(attributes = {}) ⇒ Boolean
Update every Resource in the Collection bypassing validation
Person.all(:age.gte => 21).update!(:allow_beer => true)
766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 |
# File 'lib/dm-core/collection.rb', line 766 def update!(attributes = {}) assert_update_clean_only(:update!) dirty_attributes = model.new(attributes).dirty_attributes if dirty_attributes.empty? true elsif dirty_attributes.any? { |property, value| !property.nullable? && value.nil? } 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 |