activerecord 3.2.19 → 5.0.0

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