activerecord 4.2.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 (221) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1372 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +218 -0
  5. data/examples/performance.rb +184 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +173 -0
  8. data/lib/active_record/aggregations.rb +266 -0
  9. data/lib/active_record/association_relation.rb +22 -0
  10. data/lib/active_record/associations.rb +1724 -0
  11. data/lib/active_record/associations/alias_tracker.rb +87 -0
  12. data/lib/active_record/associations/association.rb +253 -0
  13. data/lib/active_record/associations/association_scope.rb +194 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +111 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +149 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +116 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +91 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +124 -0
  20. data/lib/active_record/associations/builder/has_many.rb +15 -0
  21. data/lib/active_record/associations/builder/has_one.rb +23 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +38 -0
  23. data/lib/active_record/associations/collection_association.rb +634 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1027 -0
  25. data/lib/active_record/associations/has_many_association.rb +184 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +238 -0
  27. data/lib/active_record/associations/has_one_association.rb +105 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +282 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +122 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +22 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  33. data/lib/active_record/associations/preloader.rb +203 -0
  34. data/lib/active_record/associations/preloader/association.rb +162 -0
  35. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  36. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  37. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  38. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  39. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  40. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  41. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  42. data/lib/active_record/associations/preloader/through_association.rb +96 -0
  43. data/lib/active_record/associations/singular_association.rb +86 -0
  44. data/lib/active_record/associations/through_association.rb +96 -0
  45. data/lib/active_record/attribute.rb +149 -0
  46. data/lib/active_record/attribute_assignment.rb +212 -0
  47. data/lib/active_record/attribute_decorators.rb +66 -0
  48. data/lib/active_record/attribute_methods.rb +439 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +71 -0
  50. data/lib/active_record/attribute_methods/dirty.rb +181 -0
  51. data/lib/active_record/attribute_methods/primary_key.rb +128 -0
  52. data/lib/active_record/attribute_methods/query.rb +40 -0
  53. data/lib/active_record/attribute_methods/read.rb +103 -0
  54. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  55. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
  56. data/lib/active_record/attribute_methods/write.rb +83 -0
  57. data/lib/active_record/attribute_set.rb +77 -0
  58. data/lib/active_record/attribute_set/builder.rb +86 -0
  59. data/lib/active_record/attributes.rb +139 -0
  60. data/lib/active_record/autosave_association.rb +439 -0
  61. data/lib/active_record/base.rb +317 -0
  62. data/lib/active_record/callbacks.rb +313 -0
  63. data/lib/active_record/coders/json.rb +13 -0
  64. data/lib/active_record/coders/yaml_column.rb +38 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +659 -0
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +373 -0
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +133 -0
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +574 -0
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +991 -0
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +219 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -0
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +883 -0
  78. data/lib/active_record/connection_adapters/column.rb +82 -0
  79. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  80. data/lib/active_record/connection_adapters/mysql2_adapter.rb +282 -0
  81. data/lib/active_record/connection_adapters/mysql_adapter.rb +491 -0
  82. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  111. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  112. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +588 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +754 -0
  117. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +628 -0
  119. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  120. data/lib/active_record/connection_handling.rb +132 -0
  121. data/lib/active_record/core.rb +566 -0
  122. data/lib/active_record/counter_cache.rb +175 -0
  123. data/lib/active_record/dynamic_matchers.rb +140 -0
  124. data/lib/active_record/enum.rb +198 -0
  125. data/lib/active_record/errors.rb +252 -0
  126. data/lib/active_record/explain.rb +38 -0
  127. data/lib/active_record/explain_registry.rb +30 -0
  128. data/lib/active_record/explain_subscriber.rb +29 -0
  129. data/lib/active_record/fixture_set/file.rb +56 -0
  130. data/lib/active_record/fixtures.rb +1007 -0
  131. data/lib/active_record/gem_version.rb +15 -0
  132. data/lib/active_record/inheritance.rb +247 -0
  133. data/lib/active_record/integration.rb +113 -0
  134. data/lib/active_record/locale/en.yml +47 -0
  135. data/lib/active_record/locking/optimistic.rb +204 -0
  136. data/lib/active_record/locking/pessimistic.rb +77 -0
  137. data/lib/active_record/log_subscriber.rb +75 -0
  138. data/lib/active_record/migration.rb +1051 -0
  139. data/lib/active_record/migration/command_recorder.rb +197 -0
  140. data/lib/active_record/migration/join_table.rb +15 -0
  141. data/lib/active_record/model_schema.rb +340 -0
  142. data/lib/active_record/nested_attributes.rb +548 -0
  143. data/lib/active_record/no_touching.rb +52 -0
  144. data/lib/active_record/null_relation.rb +81 -0
  145. data/lib/active_record/persistence.rb +532 -0
  146. data/lib/active_record/query_cache.rb +56 -0
  147. data/lib/active_record/querying.rb +68 -0
  148. data/lib/active_record/railtie.rb +162 -0
  149. data/lib/active_record/railties/console_sandbox.rb +5 -0
  150. data/lib/active_record/railties/controller_runtime.rb +50 -0
  151. data/lib/active_record/railties/databases.rake +391 -0
  152. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  153. data/lib/active_record/readonly_attributes.rb +23 -0
  154. data/lib/active_record/reflection.rb +881 -0
  155. data/lib/active_record/relation.rb +681 -0
  156. data/lib/active_record/relation/batches.rb +138 -0
  157. data/lib/active_record/relation/calculations.rb +403 -0
  158. data/lib/active_record/relation/delegation.rb +140 -0
  159. data/lib/active_record/relation/finder_methods.rb +528 -0
  160. data/lib/active_record/relation/merger.rb +170 -0
  161. data/lib/active_record/relation/predicate_builder.rb +126 -0
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +47 -0
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  164. data/lib/active_record/relation/query_methods.rb +1176 -0
  165. data/lib/active_record/relation/spawn_methods.rb +75 -0
  166. data/lib/active_record/result.rb +131 -0
  167. data/lib/active_record/runtime_registry.rb +22 -0
  168. data/lib/active_record/sanitization.rb +191 -0
  169. data/lib/active_record/schema.rb +64 -0
  170. data/lib/active_record/schema_dumper.rb +251 -0
  171. data/lib/active_record/schema_migration.rb +56 -0
  172. data/lib/active_record/scoping.rb +87 -0
  173. data/lib/active_record/scoping/default.rb +134 -0
  174. data/lib/active_record/scoping/named.rb +164 -0
  175. data/lib/active_record/serialization.rb +22 -0
  176. data/lib/active_record/serializers/xml_serializer.rb +193 -0
  177. data/lib/active_record/statement_cache.rb +111 -0
  178. data/lib/active_record/store.rb +205 -0
  179. data/lib/active_record/tasks/database_tasks.rb +296 -0
  180. data/lib/active_record/tasks/mysql_database_tasks.rb +145 -0
  181. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  182. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  183. data/lib/active_record/timestamp.rb +121 -0
  184. data/lib/active_record/transactions.rb +417 -0
  185. data/lib/active_record/translation.rb +22 -0
  186. data/lib/active_record/type.rb +23 -0
  187. data/lib/active_record/type/big_integer.rb +13 -0
  188. data/lib/active_record/type/binary.rb +50 -0
  189. data/lib/active_record/type/boolean.rb +30 -0
  190. data/lib/active_record/type/date.rb +46 -0
  191. data/lib/active_record/type/date_time.rb +43 -0
  192. data/lib/active_record/type/decimal.rb +40 -0
  193. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  194. data/lib/active_record/type/decorator.rb +14 -0
  195. data/lib/active_record/type/float.rb +19 -0
  196. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  197. data/lib/active_record/type/integer.rb +55 -0
  198. data/lib/active_record/type/mutable.rb +16 -0
  199. data/lib/active_record/type/numeric.rb +36 -0
  200. data/lib/active_record/type/serialized.rb +56 -0
  201. data/lib/active_record/type/string.rb +36 -0
  202. data/lib/active_record/type/text.rb +11 -0
  203. data/lib/active_record/type/time.rb +26 -0
  204. data/lib/active_record/type/time_value.rb +38 -0
  205. data/lib/active_record/type/type_map.rb +64 -0
  206. data/lib/active_record/type/unsigned_integer.rb +15 -0
  207. data/lib/active_record/type/value.rb +101 -0
  208. data/lib/active_record/validations.rb +90 -0
  209. data/lib/active_record/validations/associated.rb +51 -0
  210. data/lib/active_record/validations/presence.rb +67 -0
  211. data/lib/active_record/validations/uniqueness.rb +229 -0
  212. data/lib/active_record/version.rb +8 -0
  213. data/lib/rails/generators/active_record.rb +17 -0
  214. data/lib/rails/generators/active_record/migration.rb +18 -0
  215. data/lib/rails/generators/active_record/migration/migration_generator.rb +70 -0
  216. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +22 -0
  217. data/lib/rails/generators/active_record/migration/templates/migration.rb +45 -0
  218. data/lib/rails/generators/active_record/model/model_generator.rb +52 -0
  219. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  220. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  221. metadata +309 -0
@@ -0,0 +1,124 @@
1
+ module ActiveRecord::Associations::Builder
2
+ class HasAndBelongsToMany # :nodoc:
3
+ class JoinTableResolver
4
+ KnownTable = Struct.new :join_table
5
+
6
+ class KnownClass
7
+ def initialize(lhs_class, rhs_class_name)
8
+ @lhs_class = lhs_class
9
+ @rhs_class_name = rhs_class_name
10
+ @join_table = nil
11
+ end
12
+
13
+ def join_table
14
+ @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
15
+ end
16
+
17
+ private
18
+
19
+ def klass
20
+ @lhs_class.send(:compute_type, @rhs_class_name)
21
+ end
22
+ end
23
+
24
+ def self.build(lhs_class, name, options)
25
+ if options[:join_table]
26
+ KnownTable.new options[:join_table].to_s
27
+ else
28
+ class_name = options.fetch(:class_name) {
29
+ name.to_s.camelize.singularize
30
+ }
31
+ KnownClass.new lhs_class, class_name
32
+ end
33
+ end
34
+ end
35
+
36
+ attr_reader :lhs_model, :association_name, :options
37
+
38
+ def initialize(association_name, lhs_model, options)
39
+ @association_name = association_name
40
+ @lhs_model = lhs_model
41
+ @options = options
42
+ end
43
+
44
+ def through_model
45
+ habtm = JoinTableResolver.build lhs_model, association_name, options
46
+
47
+ join_model = Class.new(ActiveRecord::Base) {
48
+ class << self;
49
+ attr_accessor :class_resolver
50
+ attr_accessor :name
51
+ attr_accessor :table_name_resolver
52
+ attr_accessor :left_reflection
53
+ attr_accessor :right_reflection
54
+ end
55
+
56
+ def self.table_name
57
+ table_name_resolver.join_table
58
+ end
59
+
60
+ def self.compute_type(class_name)
61
+ class_resolver.compute_type class_name
62
+ end
63
+
64
+ def self.add_left_association(name, options)
65
+ belongs_to name, options
66
+ self.left_reflection = _reflect_on_association(name)
67
+ end
68
+
69
+ def self.add_right_association(name, options)
70
+ rhs_name = name.to_s.singularize.to_sym
71
+ belongs_to rhs_name, options
72
+ self.right_reflection = _reflect_on_association(rhs_name)
73
+ end
74
+
75
+ }
76
+
77
+ join_model.name = "HABTM_#{association_name.to_s.camelize}"
78
+ join_model.table_name_resolver = habtm
79
+ join_model.class_resolver = lhs_model
80
+
81
+ join_model.add_left_association :left_side, class: lhs_model
82
+ join_model.add_right_association association_name, belongs_to_options(options)
83
+ join_model
84
+ end
85
+
86
+ def middle_reflection(join_model)
87
+ middle_name = [lhs_model.name.downcase.pluralize,
88
+ association_name].join('_').gsub(/::/, '_').to_sym
89
+ middle_options = middle_options join_model
90
+ hm_builder = HasMany.create_builder(lhs_model,
91
+ middle_name,
92
+ nil,
93
+ middle_options)
94
+ hm_builder.build lhs_model
95
+ end
96
+
97
+ private
98
+
99
+ def middle_options(join_model)
100
+ middle_options = {}
101
+ middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
102
+ middle_options[:source] = join_model.left_reflection.name
103
+ if options.key? :foreign_key
104
+ middle_options[:foreign_key] = options[:foreign_key]
105
+ end
106
+ middle_options
107
+ end
108
+
109
+ def belongs_to_options(options)
110
+ rhs_options = {}
111
+
112
+ if options.key? :class_name
113
+ rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key
114
+ rhs_options[:class_name] = options[:class_name]
115
+ end
116
+
117
+ if options.key? :association_foreign_key
118
+ rhs_options[:foreign_key] = options[:association_foreign_key]
119
+ end
120
+
121
+ rhs_options
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,15 @@
1
+ module ActiveRecord::Associations::Builder
2
+ class HasMany < CollectionAssociation #:nodoc:
3
+ def macro
4
+ :has_many
5
+ end
6
+
7
+ def valid_options
8
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type]
9
+ end
10
+
11
+ def self.valid_dependent_options
12
+ [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ module ActiveRecord::Associations::Builder
2
+ class HasOne < SingularAssociation #:nodoc:
3
+ def macro
4
+ :has_one
5
+ end
6
+
7
+ def valid_options
8
+ valid = super + [:as, :foreign_type]
9
+ valid += [:through, :source, :source_type] if options[:through]
10
+ valid
11
+ end
12
+
13
+ def self.valid_dependent_options
14
+ [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
15
+ end
16
+
17
+ private
18
+
19
+ def self.add_destroy_callbacks(model, reflection)
20
+ super unless reflection.options[:through]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ # This class is inherited by the has_one and belongs_to association classes
2
+
3
+ module ActiveRecord::Associations::Builder
4
+ class SingularAssociation < Association #:nodoc:
5
+ def valid_options
6
+ super + [:dependent, :primary_key, :inverse_of, :required]
7
+ end
8
+
9
+ def self.define_accessors(model, reflection)
10
+ super
11
+ define_constructors(model.generated_association_methods, reflection.name) if reflection.constructable?
12
+ end
13
+
14
+ # Defines the (build|create)_association methods for belongs_to or has_one association
15
+ def self.define_constructors(mixin, name)
16
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
17
+ def build_#{name}(*args, &block)
18
+ association(:#{name}).build(*args, &block)
19
+ end
20
+
21
+ def create_#{name}(*args, &block)
22
+ association(:#{name}).create(*args, &block)
23
+ end
24
+
25
+ def create_#{name}!(*args, &block)
26
+ association(:#{name}).create!(*args, &block)
27
+ end
28
+ CODE
29
+ end
30
+
31
+ def self.define_validations(model, reflection)
32
+ super
33
+ if reflection.options[:required]
34
+ model.validates_presence_of reflection.name
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,634 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ # = Active Record Association Collection
4
+ #
5
+ # CollectionAssociation is an abstract class that provides common stuff to
6
+ # ease the implementation of association proxies that represent
7
+ # collections. See the class hierarchy in Association.
8
+ #
9
+ # CollectionAssociation:
10
+ # HasManyAssociation => has_many
11
+ # HasManyThroughAssociation + ThroughAssociation => has_many :through
12
+ #
13
+ # CollectionAssociation class provides common methods to the collections
14
+ # defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
15
+ # +:through association+ option.
16
+ #
17
+ # You need to be careful with assumptions regarding the target: The proxy
18
+ # does not fetch records from the database until it needs them, but new
19
+ # ones created with +build+ are added to the target. So, the target may be
20
+ # non-empty and still lack children waiting to be read from the database.
21
+ # If you look directly to the database you cannot assume that's the entire
22
+ # collection because new records may have been added to the target, etc.
23
+ #
24
+ # If you need to work on all current children, new and existing records,
25
+ # +load_target+ and the +loaded+ flag are your friends.
26
+ class CollectionAssociation < Association #:nodoc:
27
+
28
+ # Implements the reader method, e.g. foo.items for Foo.has_many :items
29
+ def reader(force_reload = false)
30
+ if force_reload
31
+ klass.uncached { reload }
32
+ elsif stale_target?
33
+ reload
34
+ end
35
+
36
+ if owner.new_record?
37
+ # Cache the proxy separately before the owner has an id
38
+ # or else a post-save proxy will still lack the id
39
+ @new_record_proxy ||= CollectionProxy.create(klass, self)
40
+ else
41
+ @proxy ||= CollectionProxy.create(klass, self)
42
+ end
43
+ end
44
+
45
+ # Implements the writer method, e.g. foo.items= for Foo.has_many :items
46
+ def writer(records)
47
+ replace(records)
48
+ end
49
+
50
+ # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
51
+ def ids_reader
52
+ if loaded?
53
+ load_target.map do |record|
54
+ record.send(reflection.association_primary_key)
55
+ end
56
+ else
57
+ column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
58
+ scope.pluck(column)
59
+ end
60
+ end
61
+
62
+ # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
63
+ def ids_writer(ids)
64
+ pk_type = reflection.primary_key_type
65
+ ids = Array(ids).reject { |id| id.blank? }
66
+ ids.map! { |i| pk_type.type_cast_from_user(i) }
67
+ replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
68
+ end
69
+
70
+ def reset
71
+ super
72
+ @target = []
73
+ end
74
+
75
+ def select(*fields)
76
+ if block_given?
77
+ load_target.select.each { |e| yield e }
78
+ else
79
+ scope.select(*fields)
80
+ end
81
+ end
82
+
83
+ def find(*args)
84
+ if block_given?
85
+ load_target.find(*args) { |*block_args| yield(*block_args) }
86
+ else
87
+ if options[:inverse_of] && loaded?
88
+ args_flatten = args.flatten
89
+ raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
90
+ result = find_by_scan(*args)
91
+
92
+ result_size = Array(result).size
93
+ if !result || result_size != args_flatten.size
94
+ scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
95
+ else
96
+ result
97
+ end
98
+ else
99
+ scope.find(*args)
100
+ end
101
+ end
102
+ end
103
+
104
+ def first(*args)
105
+ first_nth_or_last(:first, *args)
106
+ end
107
+
108
+ def second(*args)
109
+ first_nth_or_last(:second, *args)
110
+ end
111
+
112
+ def third(*args)
113
+ first_nth_or_last(:third, *args)
114
+ end
115
+
116
+ def fourth(*args)
117
+ first_nth_or_last(:fourth, *args)
118
+ end
119
+
120
+ def fifth(*args)
121
+ first_nth_or_last(:fifth, *args)
122
+ end
123
+
124
+ def forty_two(*args)
125
+ first_nth_or_last(:forty_two, *args)
126
+ end
127
+
128
+ def last(*args)
129
+ first_nth_or_last(:last, *args)
130
+ end
131
+
132
+ def build(attributes = {}, &block)
133
+ if attributes.is_a?(Array)
134
+ attributes.collect { |attr| build(attr, &block) }
135
+ else
136
+ add_to_target(build_record(attributes)) do |record|
137
+ yield(record) if block_given?
138
+ end
139
+ end
140
+ end
141
+
142
+ def create(attributes = {}, &block)
143
+ _create_record(attributes, &block)
144
+ end
145
+
146
+ def create!(attributes = {}, &block)
147
+ _create_record(attributes, true, &block)
148
+ end
149
+
150
+ # Add +records+ to this association. Returns +self+ so method calls may
151
+ # be chained. Since << flattens its argument list and inserts each record,
152
+ # +push+ and +concat+ behave identically.
153
+ def concat(*records)
154
+ if owner.new_record?
155
+ load_target
156
+ concat_records(records)
157
+ else
158
+ transaction { concat_records(records) }
159
+ end
160
+ end
161
+
162
+ # Starts a transaction in the association class's database connection.
163
+ #
164
+ # class Author < ActiveRecord::Base
165
+ # has_many :books
166
+ # end
167
+ #
168
+ # Author.first.books.transaction do
169
+ # # same effect as calling Book.transaction
170
+ # end
171
+ def transaction(*args)
172
+ reflection.klass.transaction(*args) do
173
+ yield
174
+ end
175
+ end
176
+
177
+ # Removes all records from the association without calling callbacks
178
+ # on the associated records. It honors the +:dependent+ option. However
179
+ # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
180
+ # deletion strategy for the association is applied.
181
+ #
182
+ # You can force a particular deletion strategy by passing a parameter.
183
+ #
184
+ # Example:
185
+ #
186
+ # @author.books.delete_all(:nullify)
187
+ # @author.books.delete_all(:delete_all)
188
+ #
189
+ # See delete for more info.
190
+ def delete_all(dependent = nil)
191
+ if dependent && ![:nullify, :delete_all].include?(dependent)
192
+ raise ArgumentError, "Valid values are :nullify or :delete_all"
193
+ end
194
+
195
+ dependent = if dependent
196
+ dependent
197
+ elsif options[:dependent] == :destroy
198
+ :delete_all
199
+ else
200
+ options[:dependent]
201
+ end
202
+
203
+ delete_or_nullify_all_records(dependent).tap do
204
+ reset
205
+ loaded!
206
+ end
207
+ end
208
+
209
+ # Destroy all the records from this association.
210
+ #
211
+ # See destroy for more info.
212
+ def destroy_all
213
+ destroy(load_target).tap do
214
+ reset
215
+ loaded!
216
+ end
217
+ end
218
+
219
+ # Count all records using SQL. Construct options and pass them with
220
+ # scope to the target class's +count+.
221
+ def count(column_name = nil, count_options = {})
222
+ # TODO: Remove count_options argument as soon we remove support to
223
+ # activerecord-deprecated_finders.
224
+ column_name, count_options = nil, column_name if column_name.is_a?(Hash)
225
+
226
+ relation = scope
227
+ if association_scope.distinct_value
228
+ # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
229
+ column_name ||= reflection.klass.primary_key
230
+ relation = relation.distinct
231
+ end
232
+
233
+ value = relation.count(column_name)
234
+
235
+ limit = options[:limit]
236
+ offset = options[:offset]
237
+
238
+ if limit || offset
239
+ [ [value - offset.to_i, 0].max, limit.to_i ].min
240
+ else
241
+ value
242
+ end
243
+ end
244
+
245
+ # Removes +records+ from this association calling +before_remove+ and
246
+ # +after_remove+ callbacks.
247
+ #
248
+ # This method is abstract in the sense that +delete_records+ has to be
249
+ # provided by descendants. Note this method does not imply the records
250
+ # are actually removed from the database, that depends precisely on
251
+ # +delete_records+. They are in any case removed from the collection.
252
+ def delete(*records)
253
+ return if records.empty?
254
+ _options = records.extract_options!
255
+ dependent = _options[:dependent] || options[:dependent]
256
+
257
+ records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
258
+ delete_or_destroy(records, dependent)
259
+ end
260
+
261
+ # Deletes the +records+ and removes them from this association calling
262
+ # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
263
+ #
264
+ # Note that this method removes records from the database ignoring the
265
+ # +:dependent+ option.
266
+ def destroy(*records)
267
+ return if records.empty?
268
+ records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
269
+ delete_or_destroy(records, :destroy)
270
+ end
271
+
272
+ # Returns the size of the collection by executing a SELECT COUNT(*)
273
+ # query if the collection hasn't been loaded, and calling
274
+ # <tt>collection.size</tt> if it has.
275
+ #
276
+ # If the collection has been already loaded +size+ and +length+ are
277
+ # equivalent. If not and you are going to need the records anyway
278
+ # +length+ will take one less query. Otherwise +size+ is more efficient.
279
+ #
280
+ # This method is abstract in the sense that it relies on
281
+ # +count_records+, which is a method descendants have to provide.
282
+ def size
283
+ if !find_target? || loaded?
284
+ if association_scope.distinct_value
285
+ target.uniq.size
286
+ else
287
+ target.size
288
+ end
289
+ elsif !loaded? && !association_scope.group_values.empty?
290
+ load_target.size
291
+ elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
292
+ unsaved_records = target.select { |r| r.new_record? }
293
+ unsaved_records.size + count_records
294
+ else
295
+ count_records
296
+ end
297
+ end
298
+
299
+ # Returns the size of the collection calling +size+ on the target.
300
+ #
301
+ # If the collection has been already loaded +length+ and +size+ are
302
+ # equivalent. If not and you are going to need the records anyway this
303
+ # method will take one less query. Otherwise +size+ is more efficient.
304
+ def length
305
+ load_target.size
306
+ end
307
+
308
+ # Returns true if the collection is empty.
309
+ #
310
+ # If the collection has been loaded
311
+ # it is equivalent to <tt>collection.size.zero?</tt>. If the
312
+ # collection has not been loaded, it is equivalent to
313
+ # <tt>collection.exists?</tt>. If the collection has not already been
314
+ # loaded and you are going to fetch the records anyway it is better to
315
+ # check <tt>collection.length.zero?</tt>.
316
+ def empty?
317
+ if loaded?
318
+ size.zero?
319
+ else
320
+ @target.blank? && !scope.exists?
321
+ end
322
+ end
323
+
324
+ # Returns true if the collections is not empty.
325
+ # Equivalent to +!collection.empty?+.
326
+ def any?
327
+ if block_given?
328
+ load_target.any? { |*block_args| yield(*block_args) }
329
+ else
330
+ !empty?
331
+ end
332
+ end
333
+
334
+ # Returns true if the collection has more than 1 record.
335
+ # Equivalent to +collection.size > 1+.
336
+ def many?
337
+ if block_given?
338
+ load_target.many? { |*block_args| yield(*block_args) }
339
+ else
340
+ size > 1
341
+ end
342
+ end
343
+
344
+ def distinct
345
+ seen = {}
346
+ load_target.find_all do |record|
347
+ seen[record.id] = true unless seen.key?(record.id)
348
+ end
349
+ end
350
+ alias uniq distinct
351
+
352
+ # Replace this collection with +other_array+. This will perform a diff
353
+ # and delete/add only records that have changed.
354
+ def replace(other_array)
355
+ other_array.each { |val| raise_on_type_mismatch!(val) }
356
+ original_target = load_target.dup
357
+
358
+ if owner.new_record?
359
+ replace_records(other_array, original_target)
360
+ else
361
+ replace_common_records_in_memory(other_array, original_target)
362
+ if other_array != original_target
363
+ transaction { replace_records(other_array, original_target) }
364
+ end
365
+ end
366
+ end
367
+
368
+ def include?(record)
369
+ if record.is_a?(reflection.klass)
370
+ if record.new_record?
371
+ include_in_memory?(record)
372
+ else
373
+ loaded? ? target.include?(record) : scope.exists?(record.id)
374
+ end
375
+ else
376
+ false
377
+ end
378
+ end
379
+
380
+ def load_target
381
+ if find_target?
382
+ @target = merge_target_lists(find_target, target)
383
+ end
384
+
385
+ loaded!
386
+ target
387
+ end
388
+
389
+ def add_to_target(record, skip_callbacks = false, &block)
390
+ if association_scope.distinct_value
391
+ index = @target.index(record)
392
+ end
393
+ replace_on_target(record, index, skip_callbacks, &block)
394
+ end
395
+
396
+ def replace_on_target(record, index, skip_callbacks)
397
+ callback(:before_add, record) unless skip_callbacks
398
+ yield(record) if block_given?
399
+
400
+ if index
401
+ @target[index] = record
402
+ else
403
+ @target << record
404
+ end
405
+
406
+ callback(:after_add, record) unless skip_callbacks
407
+ set_inverse_instance(record)
408
+
409
+ record
410
+ end
411
+
412
+ def scope(opts = {})
413
+ scope = super()
414
+ scope.none! if opts.fetch(:nullify, true) && null_scope?
415
+ scope
416
+ end
417
+
418
+ def null_scope?
419
+ owner.new_record? && !foreign_key_present?
420
+ end
421
+
422
+ private
423
+ def get_records
424
+ if reflection.scope_chain.any?(&:any?) ||
425
+ scope.eager_loading? ||
426
+ klass.current_scope ||
427
+ klass.default_scopes.any?
428
+
429
+ return scope.to_a
430
+ end
431
+
432
+ conn = klass.connection
433
+ sc = reflection.association_scope_cache(conn, owner) do
434
+ StatementCache.create(conn) { |params|
435
+ as = AssociationScope.create { params.bind }
436
+ target_scope.merge as.scope(self, conn)
437
+ }
438
+ end
439
+
440
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
441
+ sc.execute binds, klass, klass.connection
442
+ end
443
+
444
+ def find_target
445
+ records = get_records
446
+ records.each { |record| set_inverse_instance(record) }
447
+ records
448
+ end
449
+
450
+ # We have some records loaded from the database (persisted) and some that are
451
+ # in-memory (memory). The same record may be represented in the persisted array
452
+ # and in the memory array.
453
+ #
454
+ # So the task of this method is to merge them according to the following rules:
455
+ #
456
+ # * The final array must not have duplicates
457
+ # * The order of the persisted array is to be preserved
458
+ # * Any changes made to attributes on objects in the memory array are to be preserved
459
+ # * Otherwise, attributes should have the value found in the database
460
+ def merge_target_lists(persisted, memory)
461
+ return persisted if memory.empty?
462
+ return memory if persisted.empty?
463
+
464
+ persisted.map! do |record|
465
+ if mem_record = memory.delete(record)
466
+
467
+ ((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
468
+ mem_record[name] = record[name]
469
+ end
470
+
471
+ mem_record
472
+ else
473
+ record
474
+ end
475
+ end
476
+
477
+ persisted + memory
478
+ end
479
+
480
+ def _create_record(attributes, raise = false, &block)
481
+ unless owner.persisted?
482
+ raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
483
+ end
484
+
485
+ if attributes.is_a?(Array)
486
+ attributes.collect { |attr| _create_record(attr, raise, &block) }
487
+ else
488
+ transaction do
489
+ add_to_target(build_record(attributes)) do |record|
490
+ yield(record) if block_given?
491
+ insert_record(record, true, raise)
492
+ end
493
+ end
494
+ end
495
+ end
496
+
497
+ # Do the relevant stuff to insert the given record into the association collection.
498
+ def insert_record(record, validate = true, raise = false)
499
+ raise NotImplementedError
500
+ end
501
+
502
+ def create_scope
503
+ scope.scope_for_create.stringify_keys
504
+ end
505
+
506
+ def delete_or_destroy(records, method)
507
+ records = records.flatten
508
+ records.each { |record| raise_on_type_mismatch!(record) }
509
+ existing_records = records.reject { |r| r.new_record? }
510
+
511
+ if existing_records.empty?
512
+ remove_records(existing_records, records, method)
513
+ else
514
+ transaction { remove_records(existing_records, records, method) }
515
+ end
516
+ end
517
+
518
+ def remove_records(existing_records, records, method)
519
+ records.each { |record| callback(:before_remove, record) }
520
+
521
+ delete_records(existing_records, method) if existing_records.any?
522
+ records.each { |record| target.delete(record) }
523
+
524
+ records.each { |record| callback(:after_remove, record) }
525
+ end
526
+
527
+ # Delete the given records from the association, using one of the methods :destroy,
528
+ # :delete_all or :nullify (or nil, in which case a default is used).
529
+ def delete_records(records, method)
530
+ raise NotImplementedError
531
+ end
532
+
533
+ def replace_records(new_target, original_target)
534
+ delete(target - new_target)
535
+
536
+ unless concat(new_target - target)
537
+ @target = original_target
538
+ raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
539
+ "new records could not be saved."
540
+ end
541
+
542
+ target
543
+ end
544
+
545
+ def replace_common_records_in_memory(new_target, original_target)
546
+ common_records = new_target & original_target
547
+ common_records.each do |record|
548
+ skip_callbacks = true
549
+ replace_on_target(record, @target.index(record), skip_callbacks)
550
+ end
551
+ end
552
+
553
+ def concat_records(records, should_raise = false)
554
+ result = true
555
+
556
+ records.flatten.each do |record|
557
+ raise_on_type_mismatch!(record)
558
+ add_to_target(record) do |rec|
559
+ result &&= insert_record(rec, true, should_raise) unless owner.new_record?
560
+ end
561
+ end
562
+
563
+ result && records
564
+ end
565
+
566
+ def callback(method, record)
567
+ callbacks_for(method).each do |callback|
568
+ callback.call(method, owner, record)
569
+ end
570
+ end
571
+
572
+ def callbacks_for(callback_name)
573
+ full_callback_name = "#{callback_name}_for_#{reflection.name}"
574
+ owner.class.send(full_callback_name)
575
+ end
576
+
577
+ # Should we deal with assoc.first or assoc.last by issuing an independent query to
578
+ # the database, or by getting the target, and then taking the first/last item from that?
579
+ #
580
+ # If the args is just a non-empty options hash, go to the database.
581
+ #
582
+ # Otherwise, go to the database only if none of the following are true:
583
+ # * target already loaded
584
+ # * owner is new record
585
+ # * target contains new or changed record(s)
586
+ def fetch_first_nth_or_last_using_find?(args)
587
+ if args.first.is_a?(Hash)
588
+ true
589
+ else
590
+ !(loaded? ||
591
+ owner.new_record? ||
592
+ target.any? { |record| record.new_record? || record.changed? })
593
+ end
594
+ end
595
+
596
+ def include_in_memory?(record)
597
+ if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
598
+ assoc = owner.association(reflection.through_reflection.name)
599
+ assoc.reader.any? { |source|
600
+ target = source.send(reflection.source_reflection.name)
601
+ target.respond_to?(:include?) ? target.include?(record) : target == record
602
+ } || target.include?(record)
603
+ else
604
+ target.include?(record)
605
+ end
606
+ end
607
+
608
+ # If the :inverse_of option has been
609
+ # specified, then #find scans the entire collection.
610
+ def find_by_scan(*args)
611
+ expects_array = args.first.kind_of?(Array)
612
+ ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
613
+
614
+ if ids.size == 1
615
+ id = ids.first
616
+ record = load_target.detect { |r| id == r.id.to_s }
617
+ expects_array ? [ record ] : record
618
+ else
619
+ load_target.select { |r| ids.include?(r.id.to_s) }
620
+ end
621
+ end
622
+
623
+ # Fetches the first/last using SQL if possible, otherwise from the target array.
624
+ def first_nth_or_last(type, *args)
625
+ args.shift if args.first.is_a?(Hash) && args.first.empty?
626
+
627
+ collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
628
+ collection.send(type, *args).tap do |record|
629
+ set_inverse_instance record if record.is_a? ActiveRecord::Base
630
+ end
631
+ end
632
+ end
633
+ end
634
+ end