activerecord 3.1.10 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  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 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  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 -102
  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 +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  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 +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  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 +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  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 +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  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 +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  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 +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  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 +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  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 +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  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 +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  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 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  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 +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,12 +1,5 @@
1
- require 'active_support/deprecation'
2
-
3
1
  module ActiveRecord
4
2
  module Associations
5
- AssociationCollection = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
6
- 'ActiveRecord::Associations::AssociationCollection',
7
- 'ActiveRecord::Associations::CollectionProxy'
8
- )
9
-
10
3
  # Association proxies in Active Record are middlemen between the object that
11
4
  # holds the association, known as the <tt>@owner</tt>, and the actual associated
12
5
  # object, known as the <tt>@target</tt>. The kind of association any proxy is
@@ -25,14 +18,8 @@ module ActiveRecord
25
18
  # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
26
19
  # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
27
20
  #
28
- # This class has most of the basic instance methods removed, and delegates
29
- # unknown methods to <tt>@target</tt> via <tt>method_missing</tt>. As a
30
- # corner case, it even removes the +class+ method and that's why you get
31
- #
32
- # blog.posts.class # => Array
33
- #
34
- # though the object behind <tt>blog.posts</tt> is not an Array, but an
35
- # ActiveRecord::Associations::HasManyAssociation.
21
+ # This class delegates unknown methods to <tt>@target</tt> via
22
+ # <tt>method_missing</tt>.
36
23
  #
37
24
  # The <tt>@target</tt> object is not \loaded until needed. For example,
38
25
  #
@@ -40,126 +27,1006 @@ module ActiveRecord
40
27
  #
41
28
  # is computed directly through SQL and does not trigger by itself the
42
29
  # instantiation of the actual post records.
43
- class CollectionProxy # :nodoc:
44
- alias :proxy_extend :extend
30
+ class CollectionProxy < Relation
31
+ delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
32
+ delegate :find_nth, to: :scope
33
+
34
+ def initialize(klass, association) #:nodoc:
35
+ @association = association
36
+ super klass, klass.arel_table
37
+ merge! association.scope(nullify: false)
38
+ end
39
+
40
+ def target
41
+ @association.target
42
+ end
45
43
 
46
- instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }
44
+ def load_target
45
+ @association.load_target
46
+ end
47
47
 
48
- delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from,
49
- :lock, :readonly, :having, :to => :scoped
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
50
56
 
51
- delegate :target, :load_target, :loaded?, :to => :@association
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)
112
+ end
52
113
 
53
- delegate :select, :find, :first, :last,
54
- :build, :create, :create!,
55
- :concat, :replace, :delete_all, :destroy_all, :delete, :destroy, :uniq,
56
- :sum, :count, :size, :length, :empty?,
57
- :any?, :many?, :include?,
58
- :to => :@association
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
59
143
 
60
- def initialize(association)
61
- @association = association
62
- Array.wrap(association.options[:extend]).each { |ext| proxy_extend(ext) }
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)
63
182
  end
64
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
65
261
  alias_method :new, :build
66
262
 
67
- def proxy_association
68
- @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)
69
335
  end
70
336
 
71
- def scoped
72
- association = @association
73
- association.scoped.extending do
74
- define_method(:proxy_association) { association }
75
- 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)
76
361
  end
77
362
 
78
- def respond_to?(name, include_private = false)
79
- super ||
80
- (load_target && target.respond_to?(name, include_private)) ||
81
- 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)
82
444
  end
83
445
 
84
- def method_missing(method, *args, &block)
85
- match = DynamicFinderMatch.match(method)
86
- if match && match.instantiator?
87
- send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
88
- proxy_association.send :set_owner_attributes, r
89
- proxy_association.send :add_to_target, r
90
- yield(r) if block_given?
91
- end
92
- 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
472
+
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
591
+
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)
662
+ end
663
+
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
680
+ end
681
+ alias uniq distinct
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
93
701
 
94
- if target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
95
- if load_target
96
- if target.respond_to?(method)
97
- target.send(method, *args, &block)
98
- else
99
- begin
100
- super
101
- rescue NoMethodError => e
102
- raise e, e.message.sub(/ for #<.*$/, " via proxy for #{target}")
103
- end
104
- end
105
- end
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
106
846
 
107
- else
108
- scoped.readonly(nil).send(method, *args, &block)
109
- end
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)
110
859
  end
111
860
 
112
- # Forwards <tt>===</tt> explicitly to the \target because the instance method
113
- # removal above doesn't catch it. Loads the \target if needed.
114
- def ===(other)
115
- other === load_target
861
+ def arel
862
+ scope.arel
116
863
  end
117
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
+ # # ]
118
944
  def to_ary
119
945
  load_target.dup
120
946
  end
121
947
  alias_method :to_a, :to_ary
122
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
+ # # ]
123
969
  def <<(*records)
124
970
  proxy_association.concat(records) && self
125
971
  end
126
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
127
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.
128
982
  def clear
129
983
  delete_all
130
984
  self
131
985
  end
132
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>]
133
1005
  def reload
134
1006
  proxy_association.reload
135
1007
  self
136
1008
  end
137
1009
 
138
- def proxy_owner
139
- ActiveSupport::Deprecation.warn(
140
- "Calling record.#{@association.reflection.name}.proxy_owner is deprecated. Please use " \
141
- "record.association(:#{@association.reflection.name}).owner instead. Or, from an " \
142
- "association extension you can access proxy_association.owner."
143
- )
144
- proxy_association.owner
145
- end
146
-
147
- def proxy_target
148
- ActiveSupport::Deprecation.warn(
149
- "Calling record.#{@association.reflection.name}.proxy_target is deprecated. Please use " \
150
- "record.association(:#{@association.reflection.name}).target instead. Or, from an " \
151
- "association extension you can access proxy_association.target."
152
- )
153
- proxy_association.target
154
- end
155
-
156
- def proxy_reflection
157
- ActiveSupport::Deprecation.warn(
158
- "Calling record.#{@association.reflection.name}.proxy_reflection is deprecated. Please use " \
159
- "record.association(:#{@association.reflection.name}).reflection instead. Or, from an " \
160
- "association extension you can access proxy_association.reflection."
161
- )
162
- proxy_association.reflection
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
163
1030
  end
164
1031
  end
165
1032
  end