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
@@ -1,78 +1,107 @@
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_on_replace(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
+ update_counters(-1)
36
+ end
37
+
38
+ def increment_counters # :nodoc:
39
+ update_counters(1)
40
+ end
41
+
21
42
  private
22
43
 
44
+ def update_counters(by)
45
+ if require_counter_update? && foreign_key_present?
46
+ if target && !stale_target?
47
+ target.increment!(reflection.counter_cache_column, by)
48
+ else
49
+ klass.update_counters(target_id, reflection.counter_cache_column => by)
50
+ end
51
+ end
52
+ end
53
+
23
54
  def find_target?
24
55
  !loaded? && foreign_key_present? && klass
25
56
  end
26
57
 
27
- def update_counters(record)
28
- counter_cache_name = reflection.counter_cache_column
29
-
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
58
+ def require_counter_update?
59
+ reflection.counter_cache_column && owner.persisted?
60
+ end
34
61
 
35
- if foreign_key_present?
36
- klass.decrement_counter(counter_cache_name, target_id)
37
- end
62
+ def update_counters_on_replace(record)
63
+ if require_counter_update? && different_target?(record)
64
+ owner.instance_variable_set :@_after_replace_counter_called, true
65
+ record.increment!(reflection.counter_cache_column)
66
+ decrement_counters
38
67
  end
39
68
  end
40
69
 
41
70
  # Checks whether record is different to the current target, without loading it
42
71
  def different_target?(record)
43
- record.nil? && owner[reflection.foreign_key] ||
44
- record && record.id != owner[reflection.foreign_key]
72
+ record.id != owner._read_attribute(reflection.foreign_key)
45
73
  end
46
74
 
47
75
  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
76
+ owner[reflection.foreign_key] = record._read_attribute(reflection.association_primary_key(record.class))
77
+ end
78
+
79
+ def remove_keys
80
+ owner[reflection.foreign_key] = nil
53
81
  end
54
82
 
55
83
  def foreign_key_present?
56
- owner[reflection.foreign_key]
84
+ owner._read_attribute(reflection.foreign_key)
57
85
  end
58
86
 
59
87
  # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
60
88
  # has_one associations.
61
89
  def invertible_for?(record)
62
90
  inverse = inverse_reflection_for(record)
63
- inverse && inverse.macro == :has_one
91
+ inverse && inverse.has_one?
64
92
  end
65
93
 
66
94
  def target_id
67
95
  if options[:primary_key]
68
96
  owner.send(reflection.name).try(:id)
69
97
  else
70
- owner[reflection.foreign_key]
98
+ owner._read_attribute(reflection.foreign_key)
71
99
  end
72
100
  end
73
101
 
74
102
  def stale_state
75
- owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
103
+ result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
104
+ result && result.to_s
76
105
  end
77
106
  end
78
107
  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,143 @@
1
- module ActiveRecord::Associations::Builder
1
+ # This is the parent Association class which defines the variables
2
+ # used by all associations.
3
+ #
4
+ # The hierarchy is defined as follows:
5
+ # Association
6
+ # - SingularAssociation
7
+ # - BelongsToAssociation
8
+ # - HasOneAssociation
9
+ # - CollectionAssociation
10
+ # - HasManyAssociation
11
+
12
+ module ActiveRecord::Associations::Builder # :nodoc:
2
13
  class Association #:nodoc:
3
- class_attribute :valid_options
4
- self.valid_options = [:class_name, :foreign_key, :select, :conditions, :include, :extend, :readonly, :validate]
14
+ class << self
15
+ attr_accessor :extensions
16
+ end
17
+ self.extensions = []
18
+
19
+ VALID_OPTIONS = [:class_name, :anonymous_class, :foreign_key, :validate] # :nodoc:
20
+
21
+ def self.build(model, name, scope, options, &block)
22
+ if model.dangerous_attribute_method?(name)
23
+ raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
24
+ "this will conflict with a method #{name} already defined by Active Record. " \
25
+ "Please choose a different association name."
26
+ end
27
+
28
+ extension = define_extensions model, name, &block
29
+ reflection = create_reflection model, name, scope, options, extension
30
+ define_accessors model, reflection
31
+ define_callbacks model, reflection
32
+ define_validations model, reflection
33
+ reflection
34
+ end
35
+
36
+ def self.create_reflection(model, name, scope, options, extension = nil)
37
+ raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
5
38
 
6
- # Set by subclasses
7
- class_attribute :macro
39
+ if scope.is_a?(Hash)
40
+ options = scope
41
+ scope = nil
42
+ end
43
+
44
+ validate_options(options)
8
45
 
9
- attr_reader :model, :name, :options, :reflection
46
+ scope = build_scope(scope, extension)
10
47
 
11
- def self.build(model, name, options)
12
- new(model, name, options).build
48
+ ActiveRecord::Reflection.create(macro, name, scope, options, model)
13
49
  end
14
50
 
15
- def initialize(model, name, options)
16
- @model, @name, @options = model, name, options
51
+ def self.build_scope(scope, extension)
52
+ new_scope = scope
53
+
54
+ if scope && scope.arity == 0
55
+ new_scope = proc { instance_exec(&scope) }
56
+ end
57
+
58
+ if extension
59
+ new_scope = wrap_scope new_scope, extension
60
+ end
61
+
62
+ new_scope
17
63
  end
18
64
 
19
- def mixin
20
- @model.generated_feature_methods
65
+ def self.wrap_scope(scope, extension)
66
+ scope
21
67
  end
22
68
 
23
- def build
24
- validate_options
25
- reflection = model.create_reflection(self.class.macro, name, options, model)
26
- define_accessors
27
- reflection
69
+ def self.macro
70
+ raise NotImplementedError
71
+ end
72
+
73
+ def self.valid_options(options)
74
+ VALID_OPTIONS + Association.extensions.flat_map(&:valid_options)
28
75
  end
29
76
 
30
- private
77
+ def self.validate_options(options)
78
+ options.assert_valid_keys(valid_options(options))
79
+ end
31
80
 
32
- def validate_options
33
- options.assert_valid_keys(self.class.valid_options)
81
+ def self.define_extensions(model, name)
82
+ end
83
+
84
+ def self.define_callbacks(model, reflection)
85
+ if dependent = reflection.options[:dependent]
86
+ check_dependent_options(dependent)
87
+ add_destroy_callbacks(model, reflection)
34
88
  end
35
89
 
36
- def define_accessors
37
- define_readers
38
- define_writers
90
+ Association.extensions.each do |extension|
91
+ extension.build model, reflection
39
92
  end
93
+ end
40
94
 
41
- def define_readers
42
- name = self.name
43
- mixin.redefine_method(name) do |*params|
44
- association(name).reader(*params)
95
+ # Defines the setter and getter methods for the association
96
+ # class Post < ActiveRecord::Base
97
+ # has_many :comments
98
+ # end
99
+ #
100
+ # Post.first.comments and Post.first.comments= methods are defined by this method...
101
+ def self.define_accessors(model, reflection)
102
+ mixin = model.generated_association_methods
103
+ name = reflection.name
104
+ define_readers(mixin, name)
105
+ define_writers(mixin, name)
106
+ end
107
+
108
+ def self.define_readers(mixin, name)
109
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
110
+ def #{name}(*args)
111
+ association(:#{name}).reader(*args)
45
112
  end
46
- end
113
+ CODE
114
+ end
47
115
 
48
- def define_writers
49
- name = self.name
50
- mixin.redefine_method("#{name}=") do |value|
51
- association(name).writer(value)
116
+ def self.define_writers(mixin, name)
117
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
118
+ def #{name}=(value)
119
+ association(:#{name}).writer(value)
52
120
  end
121
+ CODE
122
+ end
123
+
124
+ def self.define_validations(model, reflection)
125
+ # noop
126
+ end
127
+
128
+ def self.valid_dependent_options
129
+ raise NotImplementedError
130
+ end
131
+
132
+ def self.check_dependent_options(dependent)
133
+ unless valid_dependent_options.include? dependent
134
+ raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
53
135
  end
136
+ end
137
+
138
+ def self.add_destroy_callbacks(model, reflection)
139
+ name = reflection.name
140
+ model.before_destroy lambda { |o| o.association(name).handle_dependency }
141
+ end
54
142
  end
55
143
  end
@@ -1,88 +1,141 @@
1
- require 'active_support/core_ext/object/inclusion'
2
-
3
- module ActiveRecord::Associations::Builder
1
+ module ActiveRecord::Associations::Builder # :nodoc:
4
2
  class BelongsTo < SingularAssociation #:nodoc:
5
- self.macro = :belongs_to
3
+ def self.macro
4
+ :belongs_to
5
+ end
6
6
 
7
- self.valid_options += [:foreign_type, :polymorphic, :touch]
7
+ def self.valid_options(options)
8
+ super + [:polymorphic, :touch, :counter_cache, :optional]
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
19
  end
20
20
 
21
- private
21
+ def self.define_accessors(mixin, reflection)
22
+ super
23
+ add_counter_cache_methods mixin
24
+ end
22
25
 
23
- def add_counter_cache_callbacks(reflection)
24
- cache_column = reflection.counter_cache_column
25
- name = self.name
26
+ def self.add_counter_cache_methods(mixin)
27
+ return if mixin.method_defined? :belongs_to_counter_cache_after_update
28
+
29
+ mixin.class_eval do
30
+ def belongs_to_counter_cache_after_update(reflection)
31
+ foreign_key = reflection.foreign_key
32
+ cache_column = reflection.counter_cache_column
33
+
34
+ if (@_after_create_counter_called ||= false)
35
+ @_after_create_counter_called = false
36
+ elsif (@_after_replace_counter_called ||= false)
37
+ @_after_replace_counter_called = false
38
+ elsif attribute_changed?(foreign_key) && !new_record?
39
+ if reflection.polymorphic?
40
+ model = attribute(reflection.foreign_type).try(:constantize)
41
+ model_was = attribute_was(reflection.foreign_type).try(:constantize)
42
+ else
43
+ model = reflection.klass
44
+ model_was = reflection.klass
45
+ end
26
46
 
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)
47
+ foreign_key_was = attribute_was foreign_key
48
+ foreign_key = attribute foreign_key
33
49
 
34
- method_name = "belongs_to_counter_cache_before_destroy_for_#{name}"
35
- mixin.redefine_method(method_name) do
36
- record = send(name)
50
+ if foreign_key && model.respond_to?(:increment_counter)
51
+ model.increment_counter(cache_column, foreign_key)
52
+ end
37
53
 
38
- if record && !self.destroyed?
39
- record.class.decrement_counter(cache_column, record.id)
54
+ if foreign_key_was && model_was.respond_to?(:decrement_counter)
55
+ model_was.decrement_counter(cache_column, foreign_key_was)
56
+ end
40
57
  end
41
58
  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
59
  end
60
+ end
48
61
 
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]
62
+ def self.add_counter_cache_callbacks(model, reflection)
63
+ cache_column = reflection.counter_cache_column
53
64
 
54
- mixin.redefine_method(method_name) do
55
- record = send(name)
65
+ model.after_update lambda { |record|
66
+ record.belongs_to_counter_cache_after_update(reflection)
67
+ }
56
68
 
57
- unless record.nil?
58
- if touch == true
59
- record.touch
60
- else
61
- record.touch(touch)
62
- end
69
+ klass = reflection.class_name.safe_constantize
70
+ klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
71
+ end
72
+
73
+ def self.touch_record(o, foreign_key, name, touch, touch_method) # :nodoc:
74
+ old_foreign_id = o.changed_attributes[foreign_key]
75
+
76
+ if old_foreign_id
77
+ association = o.association(name)
78
+ reflection = association.reflection
79
+ if reflection.polymorphic?
80
+ klass = o.public_send("#{reflection.foreign_type}_was").constantize
81
+ else
82
+ klass = association.klass
83
+ end
84
+ old_record = klass.find_by(klass.primary_key => old_foreign_id)
85
+
86
+ if old_record
87
+ if touch != true
88
+ old_record.send(touch_method, touch)
89
+ else
90
+ old_record.send(touch_method)
63
91
  end
64
92
  end
93
+ end
65
94
 
66
- model.after_save(method_name)
67
- model.after_touch(method_name)
68
- model.after_destroy(method_name)
95
+ record = o.send name
96
+ if record && record.persisted?
97
+ if touch != true
98
+ record.send(touch_method, touch)
99
+ else
100
+ record.send(touch_method)
101
+ end
69
102
  end
103
+ end
70
104
 
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})"
75
- end
105
+ def self.add_touch_callbacks(model, reflection)
106
+ foreign_key = reflection.foreign_key
107
+ n = reflection.name
108
+ touch = reflection.options[:touch]
76
109
 
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
85
- end
110
+ callback = lambda { |record|
111
+ BelongsTo.touch_record(record, foreign_key, n, touch, belongs_to_touch_method)
112
+ }
113
+
114
+ model.after_save callback, if: :changed?
115
+ model.after_touch callback
116
+ model.after_destroy callback
117
+ end
118
+
119
+ def self.add_destroy_callbacks(model, reflection)
120
+ model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency }
121
+ end
122
+
123
+ def self.define_validations(model, reflection)
124
+ if reflection.options.key?(:required)
125
+ reflection.options[:optional] = !reflection.options.delete(:required)
126
+ end
127
+
128
+ if reflection.options[:optional].nil?
129
+ required = model.belongs_to_required_by_default
130
+ else
131
+ required = !reflection.options[:optional]
86
132
  end
133
+
134
+ super
135
+
136
+ if required
137
+ model.validates_presence_of reflection.name, message: :required
138
+ end
139
+ end
87
140
  end
88
141
  end
@@ -1,75 +1,83 @@
1
- module ActiveRecord::Associations::Builder
2
- class CollectionAssociation < Association #:nodoc:
3
- CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
1
+ # This class is inherited by the has_many and has_many_and_belongs_to_many association classes
4
2
 
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
- ]
3
+ require 'active_record/associations'
9
4
 
10
- attr_reader :block_extension
5
+ module ActiveRecord::Associations::Builder # :nodoc:
6
+ class CollectionAssociation < Association #:nodoc:
11
7
 
12
- def self.build(model, name, options, &extension)
13
- new(model, name, options, &extension).build
14
- end
8
+ CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
15
9
 
16
- def initialize(model, name, options, &extension)
17
- super(model, name, options)
18
- @block_extension = extension
10
+ def self.valid_options(options)
11
+ super + [:table_name, :before_add,
12
+ :after_add, :before_remove, :after_remove, :extend]
19
13
  end
20
14
 
21
- def build
22
- wrap_block_extension
23
- reflection = super
24
- CALLBACKS.each { |callback_name| define_callback(callback_name) }
25
- reflection
15
+ def self.define_callbacks(model, reflection)
16
+ super
17
+ name = reflection.name
18
+ options = reflection.options
19
+ CALLBACKS.each { |callback_name|
20
+ define_callback(model, callback_name, name, options)
21
+ }
26
22
  end
27
23
 
28
- def writable?
29
- true
24
+ def self.define_extensions(model, name)
25
+ if block_given?
26
+ extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
27
+ extension = Module.new(&Proc.new)
28
+ model.parent.const_set(extension_module_name, extension)
29
+ end
30
30
  end
31
31
 
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)
32
+ def self.define_callback(model, callback_name, name, options)
33
+ full_callback_name = "#{callback_name}_for_#{name}"
34
+
35
+ # TODO : why do i need method_defined? I think its because of the inheritance chain
36
+ model.class_attribute full_callback_name unless model.method_defined?(full_callback_name)
37
+ callbacks = Array(options[callback_name.to_sym]).map do |callback|
38
+ case callback
39
+ when Symbol
40
+ ->(method, owner, record) { owner.send(callback, record) }
41
+ when Proc
42
+ ->(method, owner, record) { callback.call(owner, record) }
43
+ else
44
+ ->(method, owner, record) { callback.send(method, owner, record) }
42
45
  end
43
46
  end
47
+ model.send "#{full_callback_name}=", callbacks
48
+ end
44
49
 
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}"
50
+ # Defines the setter and getter methods for the collection_singular_ids.
51
+ def self.define_readers(mixin, name)
52
+ super
51
53
 
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
54
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
55
+ def #{name.to_s.singularize}_ids
56
+ association(:#{name}).ids_reader
57
+ end
58
+ CODE
59
+ end
56
60
 
57
- def define_readers
58
- super
61
+ def self.define_writers(mixin, name)
62
+ super
59
63
 
60
- name = self.name
61
- mixin.redefine_method("#{name.to_s.singularize}_ids") do
62
- association(name).ids_reader
64
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
65
+ def #{name.to_s.singularize}_ids=(ids)
66
+ association(:#{name}).ids_writer(ids)
63
67
  end
64
- end
65
-
66
- def define_writers
67
- super
68
+ CODE
69
+ end
68
70
 
69
- name = self.name
70
- mixin.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
71
- association(name).ids_writer(ids)
71
+ def self.wrap_scope(scope, mod)
72
+ if scope
73
+ if scope.arity > 0
74
+ proc { |owner| instance_exec(owner, &scope).extending(mod) }
75
+ else
76
+ proc { instance_exec(&scope).extending(mod) }
72
77
  end
78
+ else
79
+ proc { extending(mod) }
73
80
  end
81
+ end
74
82
  end
75
83
  end