activerecord 3.2.22.5 → 4.2.11.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (236) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1632 -609
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +37 -41
  5. data/examples/performance.rb +31 -19
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +56 -42
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -36
  10. data/lib/active_record/associations/association.rb +73 -55
  11. data/lib/active_record/associations/association_scope.rb +143 -82
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +125 -31
  15. data/lib/active_record/associations/builder/belongs_to.rb +89 -61
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +23 -17
  21. data/lib/active_record/associations/collection_association.rb +251 -177
  22. data/lib/active_record/associations/collection_proxy.rb +963 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +113 -22
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -39
  26. data/lib/active_record/associations/has_one_association.rb +43 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +62 -33
  38. data/lib/active_record/associations/preloader.rb +101 -79
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +30 -16
  41. data/lib/active_record/associations.rb +463 -345
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +142 -151
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +137 -57
  47. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +73 -106
  50. data/lib/active_record/attribute_methods/serialization.rb +44 -94
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
  52. data/lib/active_record/attribute_methods/write.rb +57 -44
  53. data/lib/active_record/attribute_methods.rb +301 -141
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +246 -217
  58. data/lib/active_record/base.rb +70 -474
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
  75. data/lib/active_record/connection_adapters/column.rb +31 -245
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
  114. data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +157 -105
  119. data/lib/active_record/dynamic_matchers.rb +119 -63
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +94 -36
  122. data/lib/active_record/explain.rb +15 -63
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +9 -5
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +302 -215
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +143 -70
  129. data/lib/active_record/integration.rb +65 -12
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +73 -52
  133. data/lib/active_record/locking/pessimistic.rb +5 -5
  134. data/lib/active_record/log_subscriber.rb +24 -21
  135. data/lib/active_record/migration/command_recorder.rb +124 -32
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +511 -213
  138. data/lib/active_record/model_schema.rb +91 -117
  139. data/lib/active_record/nested_attributes.rb +184 -130
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +276 -117
  143. data/lib/active_record/query_cache.rb +19 -37
  144. data/lib/active_record/querying.rb +28 -18
  145. data/lib/active_record/railtie.rb +73 -40
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +4 -3
  148. data/lib/active_record/railties/databases.rake +141 -416
  149. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  150. data/lib/active_record/readonly_attributes.rb +1 -4
  151. data/lib/active_record/reflection.rb +513 -154
  152. data/lib/active_record/relation/batches.rb +91 -43
  153. data/lib/active_record/relation/calculations.rb +199 -161
  154. data/lib/active_record/relation/delegation.rb +116 -25
  155. data/lib/active_record/relation/finder_methods.rb +362 -248
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -43
  160. data/lib/active_record/relation/query_methods.rb +928 -167
  161. data/lib/active_record/relation/spawn_methods.rb +48 -149
  162. data/lib/active_record/relation.rb +352 -207
  163. data/lib/active_record/result.rb +101 -10
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +56 -59
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +106 -63
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +50 -57
  170. data/lib/active_record/scoping/named.rb +73 -109
  171. data/lib/active_record/scoping.rb +58 -123
  172. data/lib/active_record/serialization.rb +6 -2
  173. data/lib/active_record/serializers/xml_serializer.rb +12 -22
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +168 -15
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +23 -16
  181. data/lib/active_record/transactions.rb +125 -79
  182. data/lib/active_record/type/big_integer.rb +13 -0
  183. data/lib/active_record/type/binary.rb +50 -0
  184. data/lib/active_record/type/boolean.rb +31 -0
  185. data/lib/active_record/type/date.rb +50 -0
  186. data/lib/active_record/type/date_time.rb +54 -0
  187. data/lib/active_record/type/decimal.rb +64 -0
  188. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  189. data/lib/active_record/type/decorator.rb +14 -0
  190. data/lib/active_record/type/float.rb +19 -0
  191. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  192. data/lib/active_record/type/integer.rb +59 -0
  193. data/lib/active_record/type/mutable.rb +16 -0
  194. data/lib/active_record/type/numeric.rb +36 -0
  195. data/lib/active_record/type/serialized.rb +62 -0
  196. data/lib/active_record/type/string.rb +40 -0
  197. data/lib/active_record/type/text.rb +11 -0
  198. data/lib/active_record/type/time.rb +26 -0
  199. data/lib/active_record/type/time_value.rb +38 -0
  200. data/lib/active_record/type/type_map.rb +64 -0
  201. data/lib/active_record/type/unsigned_integer.rb +15 -0
  202. data/lib/active_record/type/value.rb +110 -0
  203. data/lib/active_record/type.rb +23 -0
  204. data/lib/active_record/validations/associated.rb +24 -16
  205. data/lib/active_record/validations/presence.rb +67 -0
  206. data/lib/active_record/validations/uniqueness.rb +123 -64
  207. data/lib/active_record/validations.rb +36 -29
  208. data/lib/active_record/version.rb +5 -7
  209. data/lib/active_record.rb +66 -46
  210. data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
  211. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
  212. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  213. data/lib/rails/generators/active_record/migration.rb +11 -8
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
  215. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  216. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  217. data/lib/rails/generators/active_record.rb +3 -11
  218. metadata +101 -45
  219. data/examples/associations.png +0 -0
  220. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  221. data/lib/active_record/associations/join_helper.rb +0 -55
  222. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  223. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  226. data/lib/active_record/dynamic_finder_match.rb +0 -68
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/fixtures/file.rb +0 -65
  229. data/lib/active_record/identity_map.rb +0 -162
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -360
  232. data/lib/active_record/test_case.rb +0 -73
  233. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  234. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  235. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  236. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -18,14 +18,8 @@ module ActiveRecord
18
18
  # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
19
19
  # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
20
20
  #
21
- # This class has most of the basic instance methods removed, and delegates
22
- # unknown methods to <tt>@target</tt> via <tt>method_missing</tt>. As a
23
- # corner case, it even removes the +class+ method and that's why you get
24
- #
25
- # blog.posts.class # => Array
26
- #
27
- # though the object behind <tt>blog.posts</tt> is not an Array, but an
28
- # ActiveRecord::Associations::HasManyAssociation.
21
+ # This class delegates unknown methods to <tt>@target</tt> via
22
+ # <tt>method_missing</tt>.
29
23
  #
30
24
  # The <tt>@target</tt> object is not \loaded until needed. For example,
31
25
  #
@@ -33,101 +27,1007 @@ module ActiveRecord
33
27
  #
34
28
  # is computed directly through SQL and does not trigger by itself the
35
29
  # instantiation of the actual post records.
36
- class CollectionProxy # :nodoc:
37
- alias :proxy_extend :extend
30
+ class CollectionProxy < Relation
31
+ delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
32
+ delegate :find_nth, to: :scope
38
33
 
39
- instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }
34
+ def initialize(klass, association) #:nodoc:
35
+ @association = association
36
+ super klass, klass.arel_table
37
+ merge! association.scope(nullify: false)
38
+ end
40
39
 
41
- delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from,
42
- :lock, :readonly, :having, :pluck, :to => :scoped
40
+ def target
41
+ @association.target
42
+ end
43
43
 
44
- delegate :target, :load_target, :loaded?, :to => :@association
44
+ def load_target
45
+ @association.load_target
46
+ end
45
47
 
46
- delegate :select, :find, :first, :last,
47
- :build, :create, :create!,
48
- :concat, :replace, :delete_all, :destroy_all, :delete, :destroy, :uniq,
49
- :sum, :count, :size, :length, :empty?,
50
- :any?, :many?, :include?,
51
- :to => :@association
48
+ # Returns +true+ if the association has been loaded, otherwise +false+.
49
+ #
50
+ # person.pets.loaded? # => false
51
+ # person.pets
52
+ # person.pets.loaded? # => true
53
+ def loaded?
54
+ @association.loaded?
55
+ end
52
56
 
53
- def initialize(association)
54
- @association = association
55
- Array.wrap(association.options[:extend]).each { |ext| proxy_extend(ext) }
57
+ # Works in two ways.
58
+ #
59
+ # *First:* Specify a subset of fields to be selected from the result set.
60
+ #
61
+ # class Person < ActiveRecord::Base
62
+ # has_many :pets
63
+ # end
64
+ #
65
+ # person.pets
66
+ # # => [
67
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
68
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
69
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
70
+ # # ]
71
+ #
72
+ # person.pets.select(:name)
73
+ # # => [
74
+ # # #<Pet id: nil, name: "Fancy-Fancy">,
75
+ # # #<Pet id: nil, name: "Spook">,
76
+ # # #<Pet id: nil, name: "Choo-Choo">
77
+ # # ]
78
+ #
79
+ # person.pets.select(:id, :name )
80
+ # # => [
81
+ # # #<Pet id: 1, name: "Fancy-Fancy">,
82
+ # # #<Pet id: 2, name: "Spook">,
83
+ # # #<Pet id: 3, name: "Choo-Choo">
84
+ # # ]
85
+ #
86
+ # Be careful because this also means you're initializing a model
87
+ # object with only the fields that you've selected. If you attempt
88
+ # to access a field except +id+ that is not in the initialized record you'll
89
+ # receive:
90
+ #
91
+ # person.pets.select(:name).first.person_id
92
+ # # => ActiveModel::MissingAttributeError: missing attribute: person_id
93
+ #
94
+ # *Second:* You can pass a block so it can be used just like Array#select.
95
+ # This builds an array of objects from the database for the scope,
96
+ # converting them into an array and iterating through them using
97
+ # Array#select.
98
+ #
99
+ # person.pets.select { |pet| pet.name =~ /oo/ }
100
+ # # => [
101
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
102
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
103
+ # # ]
104
+ #
105
+ # person.pets.select(:name) { |pet| pet.name =~ /oo/ }
106
+ # # => [
107
+ # # #<Pet id: 2, name: "Spook">,
108
+ # # #<Pet id: 3, name: "Choo-Choo">
109
+ # # ]
110
+ def select(*fields, &block)
111
+ @association.select(*fields, &block)
56
112
  end
57
113
 
114
+ # Finds an object in the collection responding to the +id+. Uses the same
115
+ # rules as <tt>ActiveRecord::Base.find</tt>. Returns <tt>ActiveRecord::RecordNotFound</tt>
116
+ # error if the object cannot be found.
117
+ #
118
+ # class Person < ActiveRecord::Base
119
+ # has_many :pets
120
+ # end
121
+ #
122
+ # person.pets
123
+ # # => [
124
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
125
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
126
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
127
+ # # ]
128
+ #
129
+ # person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
130
+ # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=4
131
+ #
132
+ # person.pets.find(2) { |pet| pet.name.downcase! }
133
+ # # => #<Pet id: 2, name: "fancy-fancy", person_id: 1>
134
+ #
135
+ # person.pets.find(2, 3)
136
+ # # => [
137
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
138
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
139
+ # # ]
140
+ def find(*args, &block)
141
+ @association.find(*args, &block)
142
+ end
143
+
144
+ # Returns the first record, or the first +n+ records, from the collection.
145
+ # If the collection is empty, the first form returns +nil+, and the second
146
+ # form returns an empty array.
147
+ #
148
+ # class Person < ActiveRecord::Base
149
+ # has_many :pets
150
+ # end
151
+ #
152
+ # person.pets
153
+ # # => [
154
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
155
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
156
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
157
+ # # ]
158
+ #
159
+ # person.pets.first # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
160
+ #
161
+ # person.pets.first(2)
162
+ # # => [
163
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
164
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
165
+ # # ]
166
+ #
167
+ # another_person_without.pets # => []
168
+ # another_person_without.pets.first # => nil
169
+ # another_person_without.pets.first(3) # => []
170
+ def first(*args)
171
+ @association.first(*args)
172
+ end
173
+
174
+ # Same as +first+ except returns only the second record.
175
+ def second(*args)
176
+ @association.second(*args)
177
+ end
178
+
179
+ # Same as +first+ except returns only the third record.
180
+ def third(*args)
181
+ @association.third(*args)
182
+ end
183
+
184
+ # Same as +first+ except returns only the fourth record.
185
+ def fourth(*args)
186
+ @association.fourth(*args)
187
+ end
188
+
189
+ # Same as +first+ except returns only the fifth record.
190
+ def fifth(*args)
191
+ @association.fifth(*args)
192
+ end
193
+
194
+ # Same as +first+ except returns only the forty second record.
195
+ # Also known as accessing "the reddit".
196
+ def forty_two(*args)
197
+ @association.forty_two(*args)
198
+ end
199
+
200
+ # Returns the last record, or the last +n+ records, from the collection.
201
+ # If the collection is empty, the first form returns +nil+, and the second
202
+ # form returns an empty array.
203
+ #
204
+ # class Person < ActiveRecord::Base
205
+ # has_many :pets
206
+ # end
207
+ #
208
+ # person.pets
209
+ # # => [
210
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
211
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
212
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
213
+ # # ]
214
+ #
215
+ # person.pets.last # => #<Pet id: 3, name: "Choo-Choo", person_id: 1>
216
+ #
217
+ # person.pets.last(2)
218
+ # # => [
219
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
220
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
221
+ # # ]
222
+ #
223
+ # another_person_without.pets # => []
224
+ # another_person_without.pets.last # => nil
225
+ # another_person_without.pets.last(3) # => []
226
+ def last(*args)
227
+ @association.last(*args)
228
+ end
229
+
230
+ def take(n = nil)
231
+ @association.take(n)
232
+ end
233
+
234
+ # Returns a new object of the collection type that has been instantiated
235
+ # with +attributes+ and linked to this object, but have not yet been saved.
236
+ # You can pass an array of attributes hashes, this will return an array
237
+ # with the new objects.
238
+ #
239
+ # class Person
240
+ # has_many :pets
241
+ # end
242
+ #
243
+ # person.pets.build
244
+ # # => #<Pet id: nil, name: nil, person_id: 1>
245
+ #
246
+ # person.pets.build(name: 'Fancy-Fancy')
247
+ # # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
248
+ #
249
+ # person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
250
+ # # => [
251
+ # # #<Pet id: nil, name: "Spook", person_id: 1>,
252
+ # # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
253
+ # # #<Pet id: nil, name: "Brain", person_id: 1>
254
+ # # ]
255
+ #
256
+ # person.pets.size # => 5 # size of the collection
257
+ # person.pets.count # => 0 # count from database
258
+ def build(attributes = {}, &block)
259
+ @association.build(attributes, &block)
260
+ end
58
261
  alias_method :new, :build
59
262
 
60
- def proxy_association
61
- @association
263
+ # Returns a new object of the collection type that has been instantiated with
264
+ # attributes, linked to this object and that has already been saved (if it
265
+ # passes the validations).
266
+ #
267
+ # class Person
268
+ # has_many :pets
269
+ # end
270
+ #
271
+ # person.pets.create(name: 'Fancy-Fancy')
272
+ # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
273
+ #
274
+ # person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}])
275
+ # # => [
276
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
277
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
278
+ # # ]
279
+ #
280
+ # person.pets.size # => 3
281
+ # person.pets.count # => 3
282
+ #
283
+ # person.pets.find(1, 2, 3)
284
+ # # => [
285
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
286
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
287
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
288
+ # # ]
289
+ def create(attributes = {}, &block)
290
+ @association.create(attributes, &block)
291
+ end
292
+
293
+ # Like +create+, except that if the record is invalid, raises an exception.
294
+ #
295
+ # class Person
296
+ # has_many :pets
297
+ # end
298
+ #
299
+ # class Pet
300
+ # validates :name, presence: true
301
+ # end
302
+ #
303
+ # person.pets.create!(name: nil)
304
+ # # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
305
+ def create!(attributes = {}, &block)
306
+ @association.create!(attributes, &block)
307
+ end
308
+
309
+ # Add one or more records to the collection by setting their foreign keys
310
+ # to the association's primary key. Since << flattens its argument list and
311
+ # inserts each record, +push+ and +concat+ behave identically. Returns +self+
312
+ # so method calls may be chained.
313
+ #
314
+ # class Person < ActiveRecord::Base
315
+ # has_many :pets
316
+ # end
317
+ #
318
+ # person.pets.size # => 0
319
+ # person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
320
+ # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
321
+ # person.pets.size # => 3
322
+ #
323
+ # person.id # => 1
324
+ # person.pets
325
+ # # => [
326
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
327
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
328
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
329
+ # # ]
330
+ #
331
+ # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
332
+ # person.pets.size # => 5
333
+ def concat(*records)
334
+ @association.concat(*records)
62
335
  end
63
336
 
64
- def scoped
65
- association = @association
66
- association.scoped.extending do
67
- define_method(:proxy_association) { association }
68
- end
337
+ # Replaces this collection with +other_array+. This will perform a diff
338
+ # and delete/add only records that have changed.
339
+ #
340
+ # class Person < ActiveRecord::Base
341
+ # has_many :pets
342
+ # end
343
+ #
344
+ # person.pets
345
+ # # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
346
+ #
347
+ # other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
348
+ #
349
+ # person.pets.replace(other_pets)
350
+ #
351
+ # person.pets
352
+ # # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
353
+ #
354
+ # If the supplied array has an incorrect association type, it raises
355
+ # an <tt>ActiveRecord::AssociationTypeMismatch</tt> error:
356
+ #
357
+ # person.pets.replace(["doo", "ggie", "gaga"])
358
+ # # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
359
+ def replace(other_array)
360
+ @association.replace(other_array)
69
361
  end
70
362
 
71
- def respond_to?(name, include_private = false)
72
- super ||
73
- (load_target && target.respond_to?(name, include_private)) ||
74
- proxy_association.klass.respond_to?(name, include_private)
363
+ # Deletes all the records from the collection according to the strategy
364
+ # specified by the +:dependent+ option. If no +:dependent+ option is given,
365
+ # then it will follow the default strategy.
366
+ #
367
+ # For +has_many :through+ associations, the default deletion strategy is
368
+ # +:delete_all+.
369
+ #
370
+ # For +has_many+ associations, the default deletion strategy is +:nullify+.
371
+ # This sets the foreign keys to +NULL+.
372
+ #
373
+ # class Person < ActiveRecord::Base
374
+ # has_many :pets # dependent: :nullify option by default
375
+ # end
376
+ #
377
+ # person.pets.size # => 3
378
+ # person.pets
379
+ # # => [
380
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
381
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
382
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
383
+ # # ]
384
+ #
385
+ # person.pets.delete_all
386
+ # # => [
387
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
388
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
389
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
390
+ # # ]
391
+ #
392
+ # person.pets.size # => 0
393
+ # person.pets # => []
394
+ #
395
+ # Pet.find(1, 2, 3)
396
+ # # => [
397
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>,
398
+ # # #<Pet id: 2, name: "Spook", person_id: nil>,
399
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
400
+ # # ]
401
+ #
402
+ # Both +has_many+ and +has_many :through+ dependencies default to the
403
+ # +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+.
404
+ # Records are not instantiated and callbacks will not be fired.
405
+ #
406
+ # class Person < ActiveRecord::Base
407
+ # has_many :pets, dependent: :destroy
408
+ # end
409
+ #
410
+ # person.pets.size # => 3
411
+ # person.pets
412
+ # # => [
413
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
414
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
415
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
416
+ # # ]
417
+ #
418
+ # person.pets.delete_all
419
+ #
420
+ # Pet.find(1, 2, 3)
421
+ # # => ActiveRecord::RecordNotFound
422
+ #
423
+ # If it is set to <tt>:delete_all</tt>, all the objects are deleted
424
+ # *without* calling their +destroy+ method.
425
+ #
426
+ # class Person < ActiveRecord::Base
427
+ # has_many :pets, dependent: :delete_all
428
+ # end
429
+ #
430
+ # person.pets.size # => 3
431
+ # person.pets
432
+ # # => [
433
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
434
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
435
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
436
+ # # ]
437
+ #
438
+ # person.pets.delete_all
439
+ #
440
+ # Pet.find(1, 2, 3)
441
+ # # => ActiveRecord::RecordNotFound
442
+ def delete_all(dependent = nil)
443
+ @association.delete_all(dependent)
75
444
  end
76
445
 
77
- def method_missing(method, *args, &block)
78
- match = DynamicFinderMatch.match(method)
79
- if match && match.instantiator?
80
- send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |record|
81
- proxy_association.send :set_owner_attributes, record
82
- proxy_association.send :add_to_target, record
83
- yield(record) if block_given?
84
- end.tap do |record|
85
- proxy_association.send :set_inverse_instance, record
86
- end
446
+ # Deletes the records of the collection directly from the database
447
+ # ignoring the +:dependent+ option. Records are instantiated and it
448
+ # invokes +before_remove+, +after_remove+ , +before_destroy+ and
449
+ # +after_destroy+ callbacks.
450
+ #
451
+ # class Person < ActiveRecord::Base
452
+ # has_many :pets
453
+ # end
454
+ #
455
+ # person.pets.size # => 3
456
+ # person.pets
457
+ # # => [
458
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
459
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
460
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
461
+ # # ]
462
+ #
463
+ # person.pets.destroy_all
464
+ #
465
+ # person.pets.size # => 0
466
+ # person.pets # => []
467
+ #
468
+ # Pet.find(1) # => Couldn't find Pet with id=1
469
+ def destroy_all
470
+ @association.destroy_all
471
+ end
87
472
 
88
- elsif target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
89
- if load_target
90
- if target.respond_to?(method)
91
- target.send(method, *args, &block)
92
- else
93
- begin
94
- super
95
- rescue NoMethodError => e
96
- raise e, e.message.sub(/ for #<.*$/, " via proxy for #{target}")
97
- end
98
- end
99
- end
473
+ # Deletes the +records+ supplied from the collection according to the strategy
474
+ # specified by the +:dependent+ option. If no +:dependent+ option is given,
475
+ # then it will follow the default strategy. Returns an array with the
476
+ # deleted records.
477
+ #
478
+ # For +has_many :through+ associations, the default deletion strategy is
479
+ # +:delete_all+.
480
+ #
481
+ # For +has_many+ associations, the default deletion strategy is +:nullify+.
482
+ # This sets the foreign keys to +NULL+.
483
+ #
484
+ # class Person < ActiveRecord::Base
485
+ # has_many :pets # dependent: :nullify option by default
486
+ # end
487
+ #
488
+ # person.pets.size # => 3
489
+ # person.pets
490
+ # # => [
491
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
492
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
493
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
494
+ # # ]
495
+ #
496
+ # person.pets.delete(Pet.find(1))
497
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
498
+ #
499
+ # person.pets.size # => 2
500
+ # person.pets
501
+ # # => [
502
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
503
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
504
+ # # ]
505
+ #
506
+ # Pet.find(1)
507
+ # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>
508
+ #
509
+ # If it is set to <tt>:destroy</tt> all the +records+ are removed by calling
510
+ # their +destroy+ method. See +destroy+ for more information.
511
+ #
512
+ # class Person < ActiveRecord::Base
513
+ # has_many :pets, dependent: :destroy
514
+ # end
515
+ #
516
+ # person.pets.size # => 3
517
+ # person.pets
518
+ # # => [
519
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
520
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
521
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
522
+ # # ]
523
+ #
524
+ # person.pets.delete(Pet.find(1), Pet.find(3))
525
+ # # => [
526
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
527
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
528
+ # # ]
529
+ #
530
+ # person.pets.size # => 1
531
+ # person.pets
532
+ # # => [#<Pet id: 2, name: "Spook", person_id: 1>]
533
+ #
534
+ # Pet.find(1, 3)
535
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 3)
536
+ #
537
+ # If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
538
+ # *without* calling their +destroy+ method.
539
+ #
540
+ # class Person < ActiveRecord::Base
541
+ # has_many :pets, dependent: :delete_all
542
+ # end
543
+ #
544
+ # person.pets.size # => 3
545
+ # person.pets
546
+ # # => [
547
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
548
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
549
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
550
+ # # ]
551
+ #
552
+ # person.pets.delete(Pet.find(1))
553
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
554
+ #
555
+ # person.pets.size # => 2
556
+ # person.pets
557
+ # # => [
558
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
559
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
560
+ # # ]
561
+ #
562
+ # Pet.find(1)
563
+ # # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
564
+ #
565
+ # You can pass +Integer+ or +String+ values, it finds the records
566
+ # responding to the +id+ and executes delete on them.
567
+ #
568
+ # class Person < ActiveRecord::Base
569
+ # has_many :pets
570
+ # end
571
+ #
572
+ # person.pets.size # => 3
573
+ # person.pets
574
+ # # => [
575
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
576
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
577
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
578
+ # # ]
579
+ #
580
+ # person.pets.delete("1")
581
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
582
+ #
583
+ # person.pets.delete(2, 3)
584
+ # # => [
585
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
586
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
587
+ # # ]
588
+ def delete(*records)
589
+ @association.delete(*records)
590
+ end
100
591
 
101
- else
102
- scoped.readonly(nil).send(method, *args, &block)
103
- end
592
+ # Destroys the +records+ supplied and removes them from the collection.
593
+ # This method will _always_ remove record from the database ignoring
594
+ # the +:dependent+ option. Returns an array with the removed records.
595
+ #
596
+ # class Person < ActiveRecord::Base
597
+ # has_many :pets
598
+ # end
599
+ #
600
+ # person.pets.size # => 3
601
+ # person.pets
602
+ # # => [
603
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
604
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
605
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
606
+ # # ]
607
+ #
608
+ # person.pets.destroy(Pet.find(1))
609
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
610
+ #
611
+ # person.pets.size # => 2
612
+ # person.pets
613
+ # # => [
614
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
615
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
616
+ # # ]
617
+ #
618
+ # person.pets.destroy(Pet.find(2), Pet.find(3))
619
+ # # => [
620
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
621
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
622
+ # # ]
623
+ #
624
+ # person.pets.size # => 0
625
+ # person.pets # => []
626
+ #
627
+ # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3)
628
+ #
629
+ # You can pass +Integer+ or +String+ values, it finds the records
630
+ # responding to the +id+ and then deletes them from the database.
631
+ #
632
+ # person.pets.size # => 3
633
+ # person.pets
634
+ # # => [
635
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
636
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
637
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
638
+ # # ]
639
+ #
640
+ # person.pets.destroy("4")
641
+ # # => #<Pet id: 4, name: "Benny", person_id: 1>
642
+ #
643
+ # person.pets.size # => 2
644
+ # person.pets
645
+ # # => [
646
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
647
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
648
+ # # ]
649
+ #
650
+ # person.pets.destroy(5, 6)
651
+ # # => [
652
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
653
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
654
+ # # ]
655
+ #
656
+ # person.pets.size # => 0
657
+ # person.pets # => []
658
+ #
659
+ # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6)
660
+ def destroy(*records)
661
+ @association.destroy(*records)
104
662
  end
105
663
 
106
- # Forwards <tt>===</tt> explicitly to the \target because the instance method
107
- # removal above doesn't catch it. Loads the \target if needed.
108
- def ===(other)
109
- other === load_target
664
+ # Specifies whether the records should be unique or not.
665
+ #
666
+ # class Person < ActiveRecord::Base
667
+ # has_many :pets
668
+ # end
669
+ #
670
+ # person.pets.select(:name)
671
+ # # => [
672
+ # # #<Pet name: "Fancy-Fancy">,
673
+ # # #<Pet name: "Fancy-Fancy">
674
+ # # ]
675
+ #
676
+ # person.pets.select(:name).distinct
677
+ # # => [#<Pet name: "Fancy-Fancy">]
678
+ def distinct
679
+ @association.distinct
110
680
  end
681
+ alias uniq distinct
111
682
 
683
+ # Count all records using SQL.
684
+ #
685
+ # class Person < ActiveRecord::Base
686
+ # has_many :pets
687
+ # end
688
+ #
689
+ # person.pets.count # => 3
690
+ # person.pets
691
+ # # => [
692
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
693
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
694
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
695
+ # # ]
696
+ def count(column_name = nil, options = {})
697
+ # TODO: Remove options argument as soon we remove support to
698
+ # activerecord-deprecated_finders.
699
+ @association.count(column_name, options)
700
+ end
701
+
702
+ # Returns the size of the collection. If the collection hasn't been loaded,
703
+ # it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
704
+ #
705
+ # If the collection has been already loaded +size+ and +length+ are
706
+ # equivalent. If not and you are going to need the records anyway
707
+ # +length+ will take one less query. Otherwise +size+ is more efficient.
708
+ #
709
+ # class Person < ActiveRecord::Base
710
+ # has_many :pets
711
+ # end
712
+ #
713
+ # person.pets.size # => 3
714
+ # # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1
715
+ #
716
+ # person.pets # This will execute a SELECT * FROM query
717
+ # # => [
718
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
719
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
720
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
721
+ # # ]
722
+ #
723
+ # person.pets.size # => 3
724
+ # # Because the collection is already loaded, this will behave like
725
+ # # collection.size and no SQL count query is executed.
726
+ def size
727
+ @association.size
728
+ end
729
+
730
+ # Returns the size of the collection calling +size+ on the target.
731
+ # If the collection has been already loaded, +length+ and +size+ are
732
+ # equivalent. If not and you are going to need the records anyway this
733
+ # method will take one less query. Otherwise +size+ is more efficient.
734
+ #
735
+ # class Person < ActiveRecord::Base
736
+ # has_many :pets
737
+ # end
738
+ #
739
+ # person.pets.length # => 3
740
+ # # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1
741
+ #
742
+ # # Because the collection is loaded, you can
743
+ # # call the collection with no additional queries:
744
+ # person.pets
745
+ # # => [
746
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
747
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
748
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
749
+ # # ]
750
+ def length
751
+ @association.length
752
+ end
753
+
754
+ # Returns +true+ if the collection is empty. If the collection has been
755
+ # loaded it is equivalent
756
+ # to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
757
+ # it is equivalent to <tt>collection.exists?</tt>. If the collection has
758
+ # not already been loaded and you are going to fetch the records anyway it
759
+ # is better to check <tt>collection.length.zero?</tt>.
760
+ #
761
+ # class Person < ActiveRecord::Base
762
+ # has_many :pets
763
+ # end
764
+ #
765
+ # person.pets.count # => 1
766
+ # person.pets.empty? # => false
767
+ #
768
+ # person.pets.delete_all
769
+ #
770
+ # person.pets.count # => 0
771
+ # person.pets.empty? # => true
772
+ def empty?
773
+ @association.empty?
774
+ end
775
+
776
+ # Returns +true+ if the collection is not empty.
777
+ #
778
+ # class Person < ActiveRecord::Base
779
+ # has_many :pets
780
+ # end
781
+ #
782
+ # person.pets.count # => 0
783
+ # person.pets.any? # => false
784
+ #
785
+ # person.pets << Pet.new(name: 'Snoop')
786
+ # person.pets.count # => 0
787
+ # person.pets.any? # => true
788
+ #
789
+ # You can also pass a +block+ to define criteria. The behavior
790
+ # is the same, it returns true if the collection based on the
791
+ # criteria is not empty.
792
+ #
793
+ # person.pets
794
+ # # => [#<Pet name: "Snoop", group: "dogs">]
795
+ #
796
+ # person.pets.any? do |pet|
797
+ # pet.group == 'cats'
798
+ # end
799
+ # # => false
800
+ #
801
+ # person.pets.any? do |pet|
802
+ # pet.group == 'dogs'
803
+ # end
804
+ # # => true
805
+ def any?(&block)
806
+ @association.any?(&block)
807
+ end
808
+
809
+ # Returns true if the collection has more than one record.
810
+ # Equivalent to <tt>collection.size > 1</tt>.
811
+ #
812
+ # class Person < ActiveRecord::Base
813
+ # has_many :pets
814
+ # end
815
+ #
816
+ # person.pets.count # => 1
817
+ # person.pets.many? # => false
818
+ #
819
+ # person.pets << Pet.new(name: 'Snoopy')
820
+ # person.pets.count # => 2
821
+ # person.pets.many? # => true
822
+ #
823
+ # You can also pass a +block+ to define criteria. The
824
+ # behavior is the same, it returns true if the collection
825
+ # based on the criteria has more than one record.
826
+ #
827
+ # person.pets
828
+ # # => [
829
+ # # #<Pet name: "Gorby", group: "cats">,
830
+ # # #<Pet name: "Puff", group: "cats">,
831
+ # # #<Pet name: "Snoop", group: "dogs">
832
+ # # ]
833
+ #
834
+ # person.pets.many? do |pet|
835
+ # pet.group == 'dogs'
836
+ # end
837
+ # # => false
838
+ #
839
+ # person.pets.many? do |pet|
840
+ # pet.group == 'cats'
841
+ # end
842
+ # # => true
843
+ def many?(&block)
844
+ @association.many?(&block)
845
+ end
846
+
847
+ # Returns +true+ if the given +record+ is present in the collection.
848
+ #
849
+ # class Person < ActiveRecord::Base
850
+ # has_many :pets
851
+ # end
852
+ #
853
+ # person.pets # => [#<Pet id: 20, name: "Snoop">]
854
+ #
855
+ # person.pets.include?(Pet.find(20)) # => true
856
+ # person.pets.include?(Pet.find(21)) # => false
857
+ def include?(record)
858
+ !!@association.include?(record)
859
+ end
860
+
861
+ def arel
862
+ scope.arel
863
+ end
864
+
865
+ def proxy_association
866
+ @association
867
+ end
868
+
869
+ # We don't want this object to be put on the scoping stack, because
870
+ # that could create an infinite loop where we call an @association
871
+ # method, which gets the current scope, which is this object, which
872
+ # delegates to @association, and so on.
873
+ def scoping
874
+ @association.scope.scoping { yield }
875
+ end
876
+
877
+ # Returns a <tt>Relation</tt> object for the records in this association
878
+ def scope
879
+ @association.scope
880
+ end
881
+ alias spawn scope
882
+
883
+ # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
884
+ # contain the same number of elements and if each element is equal
885
+ # to the corresponding element in the +other+ array, otherwise returns
886
+ # +false+.
887
+ #
888
+ # class Person < ActiveRecord::Base
889
+ # has_many :pets
890
+ # end
891
+ #
892
+ # person.pets
893
+ # # => [
894
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
895
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
896
+ # # ]
897
+ #
898
+ # other = person.pets.to_ary
899
+ #
900
+ # person.pets == other
901
+ # # => true
902
+ #
903
+ # other = [Pet.new(id: 1), Pet.new(id: 2)]
904
+ #
905
+ # person.pets == other
906
+ # # => false
907
+ def ==(other)
908
+ load_target == other
909
+ end
910
+
911
+ # Returns a new array of objects from the collection. If the collection
912
+ # hasn't been loaded, it fetches the records from the database.
913
+ #
914
+ # class Person < ActiveRecord::Base
915
+ # has_many :pets
916
+ # end
917
+ #
918
+ # person.pets
919
+ # # => [
920
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
921
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
922
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
923
+ # # ]
924
+ #
925
+ # other_pets = person.pets.to_ary
926
+ # # => [
927
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
928
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
929
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
930
+ # # ]
931
+ #
932
+ # other_pets.replace([Pet.new(name: 'BooGoo')])
933
+ #
934
+ # other_pets
935
+ # # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
936
+ #
937
+ # person.pets
938
+ # # This is not affected by replace
939
+ # # => [
940
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
941
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
942
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
943
+ # # ]
112
944
  def to_ary
113
945
  load_target.dup
114
946
  end
115
947
  alias_method :to_a, :to_ary
116
948
 
949
+ # Adds one or more +records+ to the collection by setting their foreign keys
950
+ # to the association's primary key. Returns +self+, so several appends may be
951
+ # chained together.
952
+ #
953
+ # class Person < ActiveRecord::Base
954
+ # has_many :pets
955
+ # end
956
+ #
957
+ # person.pets.size # => 0
958
+ # person.pets << Pet.new(name: 'Fancy-Fancy')
959
+ # person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
960
+ # person.pets.size # => 3
961
+ #
962
+ # person.id # => 1
963
+ # person.pets
964
+ # # => [
965
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
966
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
967
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
968
+ # # ]
117
969
  def <<(*records)
118
970
  proxy_association.concat(records) && self
119
971
  end
120
972
  alias_method :push, :<<
973
+ alias_method :append, :<<
974
+
975
+ def prepend(*args)
976
+ raise NoMethodError, "prepend on association is not defined. Please use << or append"
977
+ end
121
978
 
979
+ # Equivalent to +delete_all+. The difference is that returns +self+, instead
980
+ # of an array with the deleted objects, so methods can be chained. See
981
+ # +delete_all+ for more information.
122
982
  def clear
123
983
  delete_all
124
984
  self
125
985
  end
126
986
 
987
+ # Reloads the collection from the database. Returns +self+.
988
+ # Equivalent to <tt>collection(true)</tt>.
989
+ #
990
+ # class Person < ActiveRecord::Base
991
+ # has_many :pets
992
+ # end
993
+ #
994
+ # person.pets # fetches pets from the database
995
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
996
+ #
997
+ # person.pets # uses the pets cache
998
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
999
+ #
1000
+ # person.pets.reload # fetches pets from the database
1001
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
1002
+ #
1003
+ # person.pets(true) # fetches pets from the database
1004
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
127
1005
  def reload
128
1006
  proxy_association.reload
129
1007
  self
130
1008
  end
1009
+
1010
+ # Unloads the association. Returns +self+.
1011
+ #
1012
+ # class Person < ActiveRecord::Base
1013
+ # has_many :pets
1014
+ # end
1015
+ #
1016
+ # person.pets # fetches pets from the database
1017
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
1018
+ #
1019
+ # person.pets # uses the pets cache
1020
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
1021
+ #
1022
+ # person.pets.reset # clears the pets cache
1023
+ #
1024
+ # person.pets # fetches pets from the database
1025
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
1026
+ def reset
1027
+ proxy_association.reset
1028
+ proxy_association.reset_scope
1029
+ self
1030
+ end
131
1031
  end
132
1032
  end
133
1033
  end