activerecord 3.2.22.5 → 4.2.11.3

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

Potentially problematic release.


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

Files changed (236) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1632 -609
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +37 -41
  5. data/examples/performance.rb +31 -19
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +56 -42
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -36
  10. data/lib/active_record/associations/association.rb +73 -55
  11. data/lib/active_record/associations/association_scope.rb +143 -82
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +125 -31
  15. data/lib/active_record/associations/builder/belongs_to.rb +89 -61
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +23 -17
  21. data/lib/active_record/associations/collection_association.rb +251 -177
  22. data/lib/active_record/associations/collection_proxy.rb +963 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +113 -22
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -39
  26. data/lib/active_record/associations/has_one_association.rb +43 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +62 -33
  38. data/lib/active_record/associations/preloader.rb +101 -79
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +30 -16
  41. data/lib/active_record/associations.rb +463 -345
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +142 -151
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +137 -57
  47. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +73 -106
  50. data/lib/active_record/attribute_methods/serialization.rb +44 -94
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
  52. data/lib/active_record/attribute_methods/write.rb +57 -44
  53. data/lib/active_record/attribute_methods.rb +301 -141
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +246 -217
  58. data/lib/active_record/base.rb +70 -474
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
  75. data/lib/active_record/connection_adapters/column.rb +31 -245
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
  114. data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +157 -105
  119. data/lib/active_record/dynamic_matchers.rb +119 -63
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +94 -36
  122. data/lib/active_record/explain.rb +15 -63
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +9 -5
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +302 -215
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +143 -70
  129. data/lib/active_record/integration.rb +65 -12
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +73 -52
  133. data/lib/active_record/locking/pessimistic.rb +5 -5
  134. data/lib/active_record/log_subscriber.rb +24 -21
  135. data/lib/active_record/migration/command_recorder.rb +124 -32
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +511 -213
  138. data/lib/active_record/model_schema.rb +91 -117
  139. data/lib/active_record/nested_attributes.rb +184 -130
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +276 -117
  143. data/lib/active_record/query_cache.rb +19 -37
  144. data/lib/active_record/querying.rb +28 -18
  145. data/lib/active_record/railtie.rb +73 -40
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +4 -3
  148. data/lib/active_record/railties/databases.rake +141 -416
  149. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  150. data/lib/active_record/readonly_attributes.rb +1 -4
  151. data/lib/active_record/reflection.rb +513 -154
  152. data/lib/active_record/relation/batches.rb +91 -43
  153. data/lib/active_record/relation/calculations.rb +199 -161
  154. data/lib/active_record/relation/delegation.rb +116 -25
  155. data/lib/active_record/relation/finder_methods.rb +362 -248
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -43
  160. data/lib/active_record/relation/query_methods.rb +928 -167
  161. data/lib/active_record/relation/spawn_methods.rb +48 -149
  162. data/lib/active_record/relation.rb +352 -207
  163. data/lib/active_record/result.rb +101 -10
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +56 -59
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +106 -63
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +50 -57
  170. data/lib/active_record/scoping/named.rb +73 -109
  171. data/lib/active_record/scoping.rb +58 -123
  172. data/lib/active_record/serialization.rb +6 -2
  173. data/lib/active_record/serializers/xml_serializer.rb +12 -22
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +168 -15
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +23 -16
  181. data/lib/active_record/transactions.rb +125 -79
  182. data/lib/active_record/type/big_integer.rb +13 -0
  183. data/lib/active_record/type/binary.rb +50 -0
  184. data/lib/active_record/type/boolean.rb +31 -0
  185. data/lib/active_record/type/date.rb +50 -0
  186. data/lib/active_record/type/date_time.rb +54 -0
  187. data/lib/active_record/type/decimal.rb +64 -0
  188. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  189. data/lib/active_record/type/decorator.rb +14 -0
  190. data/lib/active_record/type/float.rb +19 -0
  191. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  192. data/lib/active_record/type/integer.rb +59 -0
  193. data/lib/active_record/type/mutable.rb +16 -0
  194. data/lib/active_record/type/numeric.rb +36 -0
  195. data/lib/active_record/type/serialized.rb +62 -0
  196. data/lib/active_record/type/string.rb +40 -0
  197. data/lib/active_record/type/text.rb +11 -0
  198. data/lib/active_record/type/time.rb +26 -0
  199. data/lib/active_record/type/time_value.rb +38 -0
  200. data/lib/active_record/type/type_map.rb +64 -0
  201. data/lib/active_record/type/unsigned_integer.rb +15 -0
  202. data/lib/active_record/type/value.rb +110 -0
  203. data/lib/active_record/type.rb +23 -0
  204. data/lib/active_record/validations/associated.rb +24 -16
  205. data/lib/active_record/validations/presence.rb +67 -0
  206. data/lib/active_record/validations/uniqueness.rb +123 -64
  207. data/lib/active_record/validations.rb +36 -29
  208. data/lib/active_record/version.rb +5 -7
  209. data/lib/active_record.rb +66 -46
  210. data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
  211. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
  212. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  213. data/lib/rails/generators/active_record/migration.rb +11 -8
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
  215. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  216. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  217. data/lib/rails/generators/active_record.rb +3 -11
  218. metadata +101 -45
  219. data/examples/associations.png +0 -0
  220. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  221. data/lib/active_record/associations/join_helper.rb +0 -55
  222. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  223. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  226. data/lib/active_record/dynamic_finder_match.rb +0 -68
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/fixtures/file.rb +0 -65
  229. data/lib/active_record/identity_map.rb +0 -162
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -360
  232. data/lib/active_record/test_case.rb +0 -73
  233. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  234. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  235. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  236. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,78 +1,118 @@
1
1
  module ActiveRecord
2
- # = Active Record Belongs To Associations
2
+ # = Active Record Belongs To Association
3
3
  module Associations
4
4
  class BelongsToAssociation < SingularAssociation #:nodoc:
5
- def replace(record)
6
- raise_on_type_mismatch(record) if record
7
5
 
8
- update_counters(record)
9
- replace_keys(record)
10
- set_inverse_instance(record)
6
+ def handle_dependency
7
+ target.send(options[:dependent]) if load_target
8
+ end
11
9
 
12
- @updated = true if record
10
+ def replace(record)
11
+ if record
12
+ raise_on_type_mismatch!(record)
13
+ update_counters(record)
14
+ replace_keys(record)
15
+ set_inverse_instance(record)
16
+ @updated = true
17
+ else
18
+ decrement_counters
19
+ remove_keys
20
+ end
13
21
 
14
22
  self.target = record
15
23
  end
16
24
 
25
+ def reset
26
+ super
27
+ @updated = false
28
+ end
29
+
17
30
  def updated?
18
31
  @updated
19
32
  end
20
33
 
34
+ def decrement_counters # :nodoc:
35
+ with_cache_name { |name| decrement_counter name }
36
+ end
37
+
38
+ def increment_counters # :nodoc:
39
+ with_cache_name { |name| increment_counter name }
40
+ end
41
+
21
42
  private
22
43
 
23
44
  def find_target?
24
45
  !loaded? && foreign_key_present? && klass
25
46
  end
26
47
 
27
- def update_counters(record)
48
+ def with_cache_name
28
49
  counter_cache_name = reflection.counter_cache_column
50
+ return unless counter_cache_name && owner.persisted?
51
+ yield counter_cache_name
52
+ end
29
53
 
30
- if counter_cache_name && owner.persisted? && different_target?(record)
31
- if record
32
- record.class.increment_counter(counter_cache_name, record.id)
33
- end
54
+ def update_counters(record)
55
+ with_cache_name do |name|
56
+ return unless different_target? record
57
+ record.class.increment_counter(name, record.id)
58
+ decrement_counter name
59
+ end
60
+ end
61
+
62
+ def decrement_counter(counter_cache_name)
63
+ if foreign_key_present?
64
+ klass.decrement_counter(counter_cache_name, target_id)
65
+ end
66
+ end
34
67
 
35
- if foreign_key_present?
36
- klass.decrement_counter(counter_cache_name, target_id)
68
+ def increment_counter(counter_cache_name)
69
+ if foreign_key_present?
70
+ klass.increment_counter(counter_cache_name, target_id)
71
+ if target && !stale_target? && counter_cache_available_in_memory?(counter_cache_name)
72
+ target.increment(counter_cache_name)
37
73
  end
38
74
  end
39
75
  end
40
76
 
41
77
  # Checks whether record is different to the current target, without loading it
42
78
  def different_target?(record)
43
- record.nil? && owner[reflection.foreign_key] ||
44
- record && record.id != owner[reflection.foreign_key]
79
+ record.id != owner._read_attribute(reflection.foreign_key)
45
80
  end
46
81
 
47
82
  def replace_keys(record)
48
- if record
49
- owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
50
- else
51
- owner[reflection.foreign_key] = nil
52
- end
83
+ owner[reflection.foreign_key] = record._read_attribute(reflection.association_primary_key(record.class))
84
+ end
85
+
86
+ def remove_keys
87
+ owner[reflection.foreign_key] = nil
53
88
  end
54
89
 
55
90
  def foreign_key_present?
56
- owner[reflection.foreign_key]
91
+ owner._read_attribute(reflection.foreign_key)
57
92
  end
58
93
 
59
94
  # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
60
95
  # has_one associations.
61
96
  def invertible_for?(record)
62
97
  inverse = inverse_reflection_for(record)
63
- inverse && inverse.macro == :has_one
98
+ inverse && inverse.has_one?
64
99
  end
65
100
 
66
101
  def target_id
67
102
  if options[:primary_key]
68
103
  owner.send(reflection.name).try(:id)
69
104
  else
70
- owner[reflection.foreign_key]
105
+ owner._read_attribute(reflection.foreign_key)
71
106
  end
72
107
  end
73
108
 
74
109
  def stale_state
75
- owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
110
+ result = owner._read_attribute(reflection.foreign_key)
111
+ result && result.to_s
112
+ end
113
+
114
+ def counter_cache_available_in_memory?(counter_cache_name)
115
+ target.respond_to?(counter_cache_name)
76
116
  end
77
117
  end
78
118
  end
@@ -11,7 +11,12 @@ module ActiveRecord
11
11
 
12
12
  def replace_keys(record)
13
13
  super
14
- owner[reflection.foreign_type] = record && record.class.base_class.name
14
+ owner[reflection.foreign_type] = record.class.base_class.name
15
+ end
16
+
17
+ def remove_keys
18
+ super
19
+ owner[reflection.foreign_type] = nil
15
20
  end
16
21
 
17
22
  def different_target?(record)
@@ -22,7 +27,7 @@ module ActiveRecord
22
27
  reflection.polymorphic_inverse_of(record.class)
23
28
  end
24
29
 
25
- def raise_on_type_mismatch(record)
30
+ def raise_on_type_mismatch!(record)
26
31
  # A polymorphic association cannot have a type mismatch, by definition
27
32
  end
28
33
 
@@ -1,55 +1,149 @@
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+
3
+ # This is the parent Association class which defines the variables
4
+ # used by all associations.
5
+ #
6
+ # The hierarchy is defined as follows:
7
+ # Association
8
+ # - SingularAssociation
9
+ # - BelongsToAssociation
10
+ # - HasOneAssociation
11
+ # - CollectionAssociation
12
+ # - HasManyAssociation
13
+
1
14
  module ActiveRecord::Associations::Builder
2
15
  class Association #:nodoc:
3
- class_attribute :valid_options
4
- self.valid_options = [:class_name, :foreign_key, :select, :conditions, :include, :extend, :readonly, :validate]
16
+ class << self
17
+ attr_accessor :extensions
18
+ # TODO: This class accessor is needed to make activerecord-deprecated_finders work.
19
+ # We can move it to a constant in 5.0.
20
+ attr_accessor :valid_options
21
+ end
22
+ self.extensions = []
5
23
 
6
- # Set by subclasses
7
- class_attribute :macro
24
+ self.valid_options = [:class_name, :anonymous_class, :foreign_key, :validate]
8
25
 
9
- attr_reader :model, :name, :options, :reflection
26
+ attr_reader :name, :scope, :options
10
27
 
11
- def self.build(model, name, options)
12
- new(model, name, options).build
13
- end
28
+ def self.build(model, name, scope, options, &block)
29
+ if model.dangerous_attribute_method?(name)
30
+ raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
31
+ "this will conflict with a method #{name} already defined by Active Record. " \
32
+ "Please choose a different association name."
33
+ end
14
34
 
15
- def initialize(model, name, options)
16
- @model, @name, @options = model, name, options
35
+ builder = create_builder model, name, scope, options, &block
36
+ reflection = builder.build(model)
37
+ define_accessors model, reflection
38
+ define_callbacks model, reflection
39
+ define_validations model, reflection
40
+ builder.define_extensions model
41
+ reflection
17
42
  end
18
43
 
19
- def mixin
20
- @model.generated_feature_methods
44
+ def self.create_builder(model, name, scope, options, &block)
45
+ raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
46
+
47
+ new(model, name, scope, options, &block)
21
48
  end
22
49
 
23
- def build
50
+ def initialize(model, name, scope, options)
51
+ # TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders.
52
+ if scope.is_a?(Hash)
53
+ options = scope
54
+ scope = nil
55
+ end
56
+
57
+ # TODO: Remove this model argument as soon we drop support to activerecord-deprecated_finders.
58
+ @name = name
59
+ @scope = scope
60
+ @options = options
61
+
24
62
  validate_options
25
- reflection = model.create_reflection(self.class.macro, name, options, model)
26
- define_accessors
27
- reflection
63
+
64
+ if scope && scope.arity == 0
65
+ @scope = proc { instance_exec(&scope) }
66
+ end
28
67
  end
29
68
 
30
- private
69
+ def build(model)
70
+ ActiveRecord::Reflection.create(macro, name, scope, options, model)
71
+ end
31
72
 
32
- def validate_options
33
- options.assert_valid_keys(self.class.valid_options)
73
+ def macro
74
+ raise NotImplementedError
75
+ end
76
+
77
+ def valid_options
78
+ Association.valid_options + Association.extensions.flat_map(&:valid_options)
79
+ end
80
+
81
+ def validate_options
82
+ options.assert_valid_keys(valid_options)
83
+ end
84
+
85
+ def define_extensions(model)
86
+ end
87
+
88
+ def self.define_callbacks(model, reflection)
89
+ if dependent = reflection.options[:dependent]
90
+ check_dependent_options(dependent)
91
+ add_destroy_callbacks(model, reflection)
34
92
  end
35
93
 
36
- def define_accessors
37
- define_readers
38
- define_writers
94
+ Association.extensions.each do |extension|
95
+ extension.build model, reflection
39
96
  end
97
+ end
98
+
99
+ # Defines the setter and getter methods for the association
100
+ # class Post < ActiveRecord::Base
101
+ # has_many :comments
102
+ # end
103
+ #
104
+ # Post.first.comments and Post.first.comments= methods are defined by this method...
105
+ def self.define_accessors(model, reflection)
106
+ mixin = model.generated_association_methods
107
+ name = reflection.name
108
+ define_readers(mixin, name)
109
+ define_writers(mixin, name)
110
+ end
40
111
 
41
- def define_readers
42
- name = self.name
43
- mixin.redefine_method(name) do |*params|
44
- association(name).reader(*params)
112
+ def self.define_readers(mixin, name)
113
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
114
+ def #{name}(*args)
115
+ association(:#{name}).reader(*args)
45
116
  end
46
- end
117
+ CODE
118
+ end
47
119
 
48
- def define_writers
49
- name = self.name
50
- mixin.redefine_method("#{name}=") do |value|
51
- association(name).writer(value)
120
+ def self.define_writers(mixin, name)
121
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
122
+ def #{name}=(value)
123
+ association(:#{name}).writer(value)
52
124
  end
125
+ CODE
126
+ end
127
+
128
+ def self.define_validations(model, reflection)
129
+ # noop
130
+ end
131
+
132
+ def self.valid_dependent_options
133
+ raise NotImplementedError
134
+ end
135
+
136
+ private
137
+
138
+ def self.check_dependent_options(dependent)
139
+ unless valid_dependent_options.include? dependent
140
+ raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
53
141
  end
142
+ end
143
+
144
+ def self.add_destroy_callbacks(model, reflection)
145
+ name = reflection.name
146
+ model.before_destroy lambda { |o| o.association(name).handle_dependency }
147
+ end
54
148
  end
55
149
  end
@@ -1,88 +1,116 @@
1
- require 'active_support/core_ext/object/inclusion'
2
-
3
1
  module ActiveRecord::Associations::Builder
4
2
  class BelongsTo < SingularAssociation #:nodoc:
5
- self.macro = :belongs_to
3
+ def macro
4
+ :belongs_to
5
+ end
6
6
 
7
- self.valid_options += [:foreign_type, :polymorphic, :touch]
7
+ def valid_options
8
+ super + [:foreign_type, :polymorphic, :touch, :counter_cache]
9
+ end
8
10
 
9
- def constructable?
10
- !options[:polymorphic]
11
+ def self.valid_dependent_options
12
+ [:destroy, :delete]
11
13
  end
12
14
 
13
- def build
14
- reflection = super
15
- add_counter_cache_callbacks(reflection) if options[:counter_cache]
16
- add_touch_callbacks(reflection) if options[:touch]
17
- configure_dependency
18
- reflection
15
+ def self.define_callbacks(model, reflection)
16
+ super
17
+ add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache]
18
+ add_touch_callbacks(model, reflection) if reflection.options[:touch]
19
+ end
20
+
21
+ def self.define_accessors(mixin, reflection)
22
+ super
23
+ add_counter_cache_methods mixin
19
24
  end
20
25
 
21
26
  private
22
27
 
23
- def add_counter_cache_callbacks(reflection)
24
- cache_column = reflection.counter_cache_column
25
- name = self.name
28
+ def self.add_counter_cache_methods(mixin)
29
+ return if mixin.method_defined? :belongs_to_counter_cache_after_update
26
30
 
27
- method_name = "belongs_to_counter_cache_after_create_for_#{name}"
28
- mixin.redefine_method(method_name) do
29
- record = send(name)
30
- record.class.increment_counter(cache_column, record.id) unless record.nil?
31
- end
32
- model.after_create(method_name)
31
+ mixin.class_eval do
32
+ def belongs_to_counter_cache_after_update(reflection)
33
+ foreign_key = reflection.foreign_key
34
+ cache_column = reflection.counter_cache_column
33
35
 
34
- method_name = "belongs_to_counter_cache_before_destroy_for_#{name}"
35
- mixin.redefine_method(method_name) do
36
- record = send(name)
36
+ if (@_after_create_counter_called ||= false)
37
+ @_after_create_counter_called = false
38
+ elsif attribute_changed?(foreign_key) && !new_record? && reflection.constructable?
39
+ model = reflection.klass
40
+ foreign_key_was = attribute_was foreign_key
41
+ foreign_key = attribute foreign_key
37
42
 
38
- if record && !self.destroyed?
39
- record.class.decrement_counter(cache_column, record.id)
43
+ if foreign_key && model.respond_to?(:increment_counter)
44
+ model.increment_counter(cache_column, foreign_key)
45
+ end
46
+ if foreign_key_was && model.respond_to?(:decrement_counter)
47
+ model.decrement_counter(cache_column, foreign_key_was)
48
+ end
40
49
  end
41
50
  end
42
- model.before_destroy(method_name)
43
-
44
- model.send(:module_eval,
45
- "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)", __FILE__, __LINE__
46
- )
47
51
  end
52
+ end
48
53
 
49
- def add_touch_callbacks(reflection)
50
- name = self.name
51
- method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
52
- touch = options[:touch]
54
+ def self.add_counter_cache_callbacks(model, reflection)
55
+ cache_column = reflection.counter_cache_column
53
56
 
54
- mixin.redefine_method(method_name) do
55
- record = send(name)
57
+ model.after_update lambda { |record|
58
+ record.belongs_to_counter_cache_after_update(reflection)
59
+ }
56
60
 
57
- unless record.nil?
58
- if touch == true
59
- record.touch
60
- else
61
- record.touch(touch)
62
- end
63
- end
64
- end
61
+ klass = reflection.class_name.safe_constantize
62
+ klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
63
+ end
65
64
 
66
- model.after_save(method_name)
67
- model.after_touch(method_name)
68
- model.after_destroy(method_name)
69
- end
65
+ def self.touch_record(o, foreign_key, name, touch) # :nodoc:
66
+ old_foreign_id = o.changed_attributes[foreign_key]
67
+
68
+ if old_foreign_id
69
+ association = o.association(name)
70
+ reflection = association.reflection
71
+ if reflection.polymorphic?
72
+ klass = o.public_send("#{reflection.foreign_type}_was").constantize
73
+ else
74
+ klass = association.klass
75
+ end
76
+ old_record = klass.find_by(klass.primary_key => old_foreign_id)
70
77
 
71
- def configure_dependency
72
- if options[:dependent]
73
- unless options[:dependent].in?([:destroy, :delete])
74
- raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{options[:dependent].inspect})"
78
+ if old_record
79
+ if touch != true
80
+ old_record.touch touch
81
+ else
82
+ old_record.touch
75
83
  end
84
+ end
85
+ end
76
86
 
77
- method_name = "belongs_to_dependent_#{options[:dependent]}_for_#{name}"
78
- model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
79
- def #{method_name}
80
- association = #{name}
81
- association.#{options[:dependent]} if association
82
- end
83
- eoruby
84
- model.after_destroy method_name
87
+ record = o.send name
88
+ if record && record.persisted?
89
+ if touch != true
90
+ record.touch touch
91
+ else
92
+ record.touch
85
93
  end
86
94
  end
95
+ end
96
+
97
+ def self.add_touch_callbacks(model, reflection)
98
+ foreign_key = reflection.foreign_key
99
+ n = reflection.name
100
+ touch = reflection.options[:touch]
101
+
102
+ callback = lambda { |record|
103
+ BelongsTo.touch_record(record, foreign_key, n, touch)
104
+ }
105
+
106
+ model.after_save callback, if: :changed?
107
+ model.after_touch callback
108
+ model.after_destroy callback
109
+ end
110
+
111
+ def self.add_destroy_callbacks(model, reflection)
112
+ name = reflection.name
113
+ model.after_destroy lambda { |o| o.association(name).handle_dependency }
114
+ end
87
115
  end
88
116
  end
@@ -1,75 +1,95 @@
1
+ # This class is inherited by the has_many and has_many_and_belongs_to_many association classes
2
+
3
+ require 'active_record/associations'
4
+
1
5
  module ActiveRecord::Associations::Builder
2
6
  class CollectionAssociation < Association #:nodoc:
7
+
3
8
  CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
4
9
 
5
- self.valid_options += [
6
- :table_name, :order, :group, :having, :limit, :offset, :uniq, :finder_sql,
7
- :counter_sql, :before_add, :after_add, :before_remove, :after_remove
8
- ]
10
+ def valid_options
11
+ super + [:table_name, :before_add,
12
+ :after_add, :before_remove, :after_remove, :extend]
13
+ end
9
14
 
10
15
  attr_reader :block_extension
11
16
 
12
- def self.build(model, name, options, &extension)
13
- new(model, name, options, &extension).build
14
- end
15
-
16
- def initialize(model, name, options, &extension)
17
- super(model, name, options)
18
- @block_extension = extension
17
+ def initialize(model, name, scope, options)
18
+ super
19
+ @mod = nil
20
+ if block_given?
21
+ @mod = Module.new(&Proc.new)
22
+ @scope = wrap_scope @scope, @mod
23
+ end
19
24
  end
20
25
 
21
- def build
22
- wrap_block_extension
23
- reflection = super
24
- CALLBACKS.each { |callback_name| define_callback(callback_name) }
25
- reflection
26
+ def self.define_callbacks(model, reflection)
27
+ super
28
+ name = reflection.name
29
+ options = reflection.options
30
+ CALLBACKS.each { |callback_name|
31
+ define_callback(model, callback_name, name, options)
32
+ }
26
33
  end
27
34
 
28
- def writable?
29
- true
35
+ def define_extensions(model)
36
+ if @mod
37
+ extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
38
+ model.parent.const_set(extension_module_name, @mod)
39
+ end
30
40
  end
31
41
 
32
- private
33
-
34
- def wrap_block_extension
35
- options[:extend] = Array.wrap(options[:extend])
36
-
37
- if block_extension
38
- silence_warnings do
39
- model.parent.const_set(extension_module_name, Module.new(&block_extension))
40
- end
41
- options[:extend].push("#{model.parent}::#{extension_module_name}".constantize)
42
+ def self.define_callback(model, callback_name, name, options)
43
+ full_callback_name = "#{callback_name}_for_#{name}"
44
+
45
+ # TODO : why do i need method_defined? I think its because of the inheritance chain
46
+ model.class_attribute full_callback_name unless model.method_defined?(full_callback_name)
47
+ callbacks = Array(options[callback_name.to_sym]).map do |callback|
48
+ case callback
49
+ when Symbol
50
+ ->(method, owner, record) { owner.send(callback, record) }
51
+ when Proc
52
+ ->(method, owner, record) { callback.call(owner, record) }
53
+ else
54
+ ->(method, owner, record) { callback.send(method, owner, record) }
42
55
  end
43
56
  end
57
+ model.send "#{full_callback_name}=", callbacks
58
+ end
44
59
 
45
- def extension_module_name
46
- @extension_module_name ||= "#{model.to_s.demodulize}#{name.to_s.camelize}AssociationExtension"
47
- end
48
-
49
- def define_callback(callback_name)
50
- full_callback_name = "#{callback_name}_for_#{name}"
60
+ # Defines the setter and getter methods for the collection_singular_ids.
61
+ def self.define_readers(mixin, name)
62
+ super
51
63
 
52
- # TODO : why do i need method_defined? I think its because of the inheritance chain
53
- model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
54
- model.send("#{full_callback_name}=", Array.wrap(options[callback_name.to_sym]))
55
- end
64
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
65
+ def #{name.to_s.singularize}_ids
66
+ association(:#{name}).ids_reader
67
+ end
68
+ CODE
69
+ end
56
70
 
57
- def define_readers
58
- super
71
+ def self.define_writers(mixin, name)
72
+ super
59
73
 
60
- name = self.name
61
- mixin.redefine_method("#{name.to_s.singularize}_ids") do
62
- association(name).ids_reader
74
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
75
+ def #{name.to_s.singularize}_ids=(ids)
76
+ association(:#{name}).ids_writer(ids)
63
77
  end
64
- end
78
+ CODE
79
+ end
65
80
 
66
- def define_writers
67
- super
81
+ private
68
82
 
69
- name = self.name
70
- mixin.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
71
- association(name).ids_writer(ids)
83
+ def wrap_scope(scope, mod)
84
+ if scope
85
+ if scope.arity > 0
86
+ proc { |owner| instance_exec(owner, &scope).extending(mod) }
87
+ else
88
+ proc { instance_exec(&scope).extending(mod) }
72
89
  end
90
+ else
91
+ proc { extending(mod) }
73
92
  end
93
+ end
74
94
  end
75
95
  end