activerecord 3.2.22.5 → 5.2.8

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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,88 +1,163 @@
1
- require 'active_support/core_ext/object/inclusion'
1
+ # frozen_string_literal: true
2
2
 
3
- module ActiveRecord::Associations::Builder
3
+ module ActiveRecord::Associations::Builder # :nodoc:
4
4
  class BelongsTo < SingularAssociation #:nodoc:
5
- self.macro = :belongs_to
5
+ def self.macro
6
+ :belongs_to
7
+ end
6
8
 
7
- self.valid_options += [:foreign_type, :polymorphic, :touch]
9
+ def self.valid_options(options)
10
+ super + [:polymorphic, :touch, :counter_cache, :optional, :default]
11
+ end
8
12
 
9
- def constructable?
10
- !options[:polymorphic]
13
+ def self.valid_dependent_options
14
+ [:destroy, :delete]
11
15
  end
12
16
 
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
17
+ def self.define_callbacks(model, reflection)
18
+ super
19
+ add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache]
20
+ add_touch_callbacks(model, reflection) if reflection.options[:touch]
21
+ add_default_callbacks(model, reflection) if reflection.options[:default]
19
22
  end
20
23
 
21
- private
24
+ def self.define_accessors(mixin, reflection)
25
+ super
26
+ add_counter_cache_methods mixin
27
+ end
22
28
 
23
- def add_counter_cache_callbacks(reflection)
24
- cache_column = reflection.counter_cache_column
25
- name = self.name
29
+ def self.add_counter_cache_methods(mixin)
30
+ return if mixin.method_defined? :belongs_to_counter_cache_after_update
26
31
 
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)
32
+ mixin.class_eval do
33
+ def belongs_to_counter_cache_after_update(reflection)
34
+ foreign_key = reflection.foreign_key
35
+ cache_column = reflection.counter_cache_column
36
+
37
+ if (@_after_replace_counter_called ||= false)
38
+ @_after_replace_counter_called = false
39
+ elsif association(reflection.name).target_changed?
40
+ if reflection.polymorphic?
41
+ model = attribute_in_database(reflection.foreign_type).try(:constantize)
42
+ model_was = attribute_before_last_save(reflection.foreign_type).try(:constantize)
43
+ else
44
+ model = reflection.klass
45
+ model_was = reflection.klass
46
+ end
33
47
 
34
- method_name = "belongs_to_counter_cache_before_destroy_for_#{name}"
35
- mixin.redefine_method(method_name) do
36
- record = send(name)
48
+ foreign_key_was = attribute_before_last_save foreign_key
49
+ foreign_key = attribute_in_database foreign_key
37
50
 
38
- if record && !self.destroyed?
39
- record.class.decrement_counter(cache_column, record.id)
51
+ if foreign_key && model.respond_to?(:increment_counter)
52
+ foreign_key = counter_cache_target(reflection, model, foreign_key)
53
+ model.increment_counter(cache_column, foreign_key)
54
+ end
55
+
56
+ if foreign_key_was && model_was.respond_to?(:decrement_counter)
57
+ foreign_key_was = counter_cache_target(reflection, model_was, foreign_key_was)
58
+ model_was.decrement_counter(cache_column, foreign_key_was)
59
+ end
40
60
  end
41
61
  end
42
- model.before_destroy(method_name)
43
62
 
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
- )
63
+ private
64
+ def counter_cache_target(reflection, model, foreign_key)
65
+ primary_key = reflection.association_primary_key(model)
66
+ model.unscoped.where!(primary_key => foreign_key)
67
+ end
47
68
  end
69
+ end
48
70
 
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]
71
+ def self.add_counter_cache_callbacks(model, reflection)
72
+ cache_column = reflection.counter_cache_column
53
73
 
54
- mixin.redefine_method(method_name) do
55
- record = send(name)
74
+ model.after_update lambda { |record|
75
+ record.belongs_to_counter_cache_after_update(reflection)
76
+ }
56
77
 
57
- unless record.nil?
58
- if touch == true
59
- record.touch
60
- else
61
- record.touch(touch)
62
- end
78
+ klass = reflection.class_name.safe_constantize
79
+ klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
80
+ end
81
+
82
+ def self.touch_record(o, changes, foreign_key, name, touch, touch_method) # :nodoc:
83
+ old_foreign_id = changes[foreign_key] && changes[foreign_key].first
84
+
85
+ if old_foreign_id
86
+ association = o.association(name)
87
+ reflection = association.reflection
88
+ if reflection.polymorphic?
89
+ foreign_type = reflection.foreign_type
90
+ klass = changes[foreign_type] && changes[foreign_type].first || o.public_send(foreign_type)
91
+ klass = klass.constantize
92
+ else
93
+ klass = association.klass
94
+ end
95
+ primary_key = reflection.association_primary_key(klass)
96
+ old_record = klass.find_by(primary_key => old_foreign_id)
97
+
98
+ if old_record
99
+ if touch != true
100
+ old_record.send(touch_method, touch)
101
+ else
102
+ old_record.send(touch_method)
63
103
  end
64
104
  end
105
+ end
106
+
107
+ record = o.send name
108
+ if record && record.persisted?
109
+ if touch != true
110
+ record.send(touch_method, touch)
111
+ else
112
+ record.send(touch_method)
113
+ end
114
+ end
115
+ end
116
+
117
+ def self.add_touch_callbacks(model, reflection)
118
+ foreign_key = reflection.foreign_key
119
+ n = reflection.name
120
+ touch = reflection.options[:touch]
121
+
122
+ callback = lambda { |changes_method| lambda { |record|
123
+ BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method)
124
+ }}
65
125
 
66
- model.after_save(method_name)
67
- model.after_touch(method_name)
68
- model.after_destroy(method_name)
126
+ unless reflection.counter_cache_column
127
+ model.after_create callback.(:saved_changes), if: :saved_changes?
128
+ model.after_destroy callback.(:changes_to_save)
69
129
  end
70
130
 
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
131
+ model.after_update callback.(:saved_changes), if: :saved_changes?
132
+ model.after_touch callback.(:changes_to_save)
133
+ end
76
134
 
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
135
+ def self.add_default_callbacks(model, reflection)
136
+ model.before_validation lambda { |o|
137
+ o.association(reflection.name).default(&reflection.options[:default])
138
+ }
139
+ end
140
+
141
+ def self.add_destroy_callbacks(model, reflection)
142
+ model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency }
143
+ end
144
+
145
+ def self.define_validations(model, reflection)
146
+ if reflection.options.key?(:required)
147
+ reflection.options[:optional] = !reflection.options.delete(:required)
148
+ end
149
+
150
+ if reflection.options[:optional].nil?
151
+ required = model.belongs_to_required_by_default
152
+ else
153
+ required = !reflection.options[:optional]
86
154
  end
155
+
156
+ super
157
+
158
+ if required
159
+ model.validates_presence_of reflection.name, message: :required
160
+ end
161
+ end
87
162
  end
88
163
  end
@@ -1,75 +1,82 @@
1
- module ActiveRecord::Associations::Builder
2
- class CollectionAssociation < Association #:nodoc:
3
- CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
4
-
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
- ]
1
+ # frozen_string_literal: true
9
2
 
10
- attr_reader :block_extension
3
+ require "active_record/associations"
11
4
 
12
- def self.build(model, name, options, &extension)
13
- new(model, name, options, &extension).build
14
- end
5
+ module ActiveRecord::Associations::Builder # :nodoc:
6
+ class CollectionAssociation < Association #:nodoc:
7
+ CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
15
8
 
16
- def initialize(model, name, options, &extension)
17
- super(model, name, options)
18
- @block_extension = extension
9
+ def self.valid_options(options)
10
+ super + [:table_name, :before_add,
11
+ :after_add, :before_remove, :after_remove, :extend]
19
12
  end
20
13
 
21
- def build
22
- wrap_block_extension
23
- reflection = super
24
- CALLBACKS.each { |callback_name| define_callback(callback_name) }
25
- reflection
14
+ def self.define_callbacks(model, reflection)
15
+ super
16
+ name = reflection.name
17
+ options = reflection.options
18
+ CALLBACKS.each { |callback_name|
19
+ define_callback(model, callback_name, name, options)
20
+ }
26
21
  end
27
22
 
28
- def writable?
29
- true
23
+ def self.define_extensions(model, name, &block)
24
+ if block_given?
25
+ extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
26
+ extension = Module.new(&block)
27
+ model.parent.const_set(extension_module_name, extension)
28
+ end
30
29
  end
31
30
 
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)
31
+ def self.define_callback(model, callback_name, name, options)
32
+ full_callback_name = "#{callback_name}_for_#{name}"
33
+
34
+ # TODO : why do i need method_defined? I think its because of the inheritance chain
35
+ model.class_attribute full_callback_name unless model.method_defined?(full_callback_name)
36
+ callbacks = Array(options[callback_name.to_sym]).map do |callback|
37
+ case callback
38
+ when Symbol
39
+ ->(method, owner, record) { owner.send(callback, record) }
40
+ when Proc
41
+ ->(method, owner, record) { callback.call(owner, record) }
42
+ else
43
+ ->(method, owner, record) { callback.send(method, owner, record) }
42
44
  end
43
45
  end
46
+ model.send "#{full_callback_name}=", callbacks
47
+ end
44
48
 
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}"
49
+ # Defines the setter and getter methods for the collection_singular_ids.
50
+ def self.define_readers(mixin, name)
51
+ super
51
52
 
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
53
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
54
+ def #{name.to_s.singularize}_ids
55
+ association(:#{name}).ids_reader
56
+ end
57
+ CODE
58
+ end
56
59
 
57
- def define_readers
58
- super
60
+ def self.define_writers(mixin, name)
61
+ super
59
62
 
60
- name = self.name
61
- mixin.redefine_method("#{name.to_s.singularize}_ids") do
62
- association(name).ids_reader
63
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
64
+ def #{name.to_s.singularize}_ids=(ids)
65
+ association(:#{name}).ids_writer(ids)
63
66
  end
64
- end
65
-
66
- def define_writers
67
- super
67
+ CODE
68
+ end
68
69
 
69
- name = self.name
70
- mixin.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
71
- association(name).ids_writer(ids)
70
+ def self.wrap_scope(scope, mod)
71
+ if scope
72
+ if scope.arity > 0
73
+ proc { |owner| instance_exec(owner, &scope).extending(mod) }
74
+ else
75
+ proc { instance_exec(&scope).extending(mod) }
72
76
  end
77
+ else
78
+ proc { extending(mod) }
73
79
  end
80
+ end
74
81
  end
75
82
  end
@@ -1,57 +1,135 @@
1
- module ActiveRecord::Associations::Builder
2
- class HasAndBelongsToMany < CollectionAssociation #:nodoc:
3
- self.macro = :has_and_belongs_to_many
1
+ # frozen_string_literal: true
4
2
 
5
- self.valid_options += [:join_table, :association_foreign_key, :delete_sql, :insert_sql]
3
+ module ActiveRecord::Associations::Builder # :nodoc:
4
+ class HasAndBelongsToMany # :nodoc:
5
+ class JoinTableResolver # :nodoc:
6
+ KnownTable = Struct.new :join_table
6
7
 
7
- def build
8
- reflection = super
9
- check_validity(reflection)
10
- define_destroy_hook
11
- reflection
12
- end
8
+ class KnownClass # :nodoc:
9
+ def initialize(lhs_class, rhs_class_name)
10
+ @lhs_class = lhs_class
11
+ @rhs_class_name = rhs_class_name
12
+ @join_table = nil
13
+ end
13
14
 
14
- private
15
+ def join_table
16
+ @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
17
+ end
18
+
19
+ private
15
20
 
16
- def define_destroy_hook
17
- name = self.name
18
- model.send(:include, Module.new {
19
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
20
- def destroy_associations
21
- association(#{name.to_sym.inspect}).delete_all_on_destroy
22
- super
23
- end
24
- RUBY
25
- })
21
+ def klass
22
+ @lhs_class.send(:compute_type, @rhs_class_name)
23
+ end
26
24
  end
27
25
 
28
- # TODO: These checks should probably be moved into the Reflection, and we should not be
29
- # redefining the options[:join_table] value - instead we should define a
30
- # reflection.join_table method.
31
- def check_validity(reflection)
32
- if reflection.association_foreign_key == reflection.foreign_key
33
- raise ActiveRecord::HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
26
+ def self.build(lhs_class, name, options)
27
+ if options[:join_table]
28
+ KnownTable.new options[:join_table].to_s
29
+ else
30
+ class_name = options.fetch(:class_name) {
31
+ name.to_s.camelize.singularize
32
+ }
33
+ KnownClass.new lhs_class, class_name.to_s
34
+ end
35
+ end
36
+ end
37
+
38
+ attr_reader :lhs_model, :association_name, :options
39
+
40
+ def initialize(association_name, lhs_model, options)
41
+ @association_name = association_name
42
+ @lhs_model = lhs_model
43
+ @options = options
44
+ end
45
+
46
+ def through_model
47
+ habtm = JoinTableResolver.build lhs_model, association_name, options
48
+
49
+ join_model = Class.new(ActiveRecord::Base) {
50
+ class << self
51
+ attr_accessor :left_model
52
+ attr_accessor :name
53
+ attr_accessor :table_name_resolver
54
+ attr_accessor :left_reflection
55
+ attr_accessor :right_reflection
56
+ end
57
+
58
+ def self.table_name
59
+ table_name_resolver.join_table
60
+ end
61
+
62
+ def self.compute_type(class_name)
63
+ left_model.compute_type class_name
64
+ end
65
+
66
+ def self.add_left_association(name, options)
67
+ belongs_to name, required: false, **options
68
+ self.left_reflection = _reflect_on_association(name)
34
69
  end
35
70
 
36
- reflection.options[:join_table] ||= join_table_name(
37
- model.send(:undecorated_table_name, model.to_s),
38
- model.send(:undecorated_table_name, reflection.class_name)
39
- )
71
+ def self.add_right_association(name, options)
72
+ rhs_name = name.to_s.singularize.to_sym
73
+ belongs_to rhs_name, required: false, **options
74
+ self.right_reflection = _reflect_on_association(rhs_name)
75
+ end
76
+
77
+ def self.retrieve_connection
78
+ left_model.retrieve_connection
79
+ end
80
+
81
+ private
82
+
83
+ def self.suppress_composite_primary_key(pk)
84
+ pk unless pk.is_a?(Array)
85
+ end
86
+ }
87
+
88
+ join_model.name = "HABTM_#{association_name.to_s.camelize}"
89
+ join_model.table_name_resolver = habtm
90
+ join_model.left_model = lhs_model
91
+
92
+ join_model.add_left_association :left_side, anonymous_class: lhs_model
93
+ join_model.add_right_association association_name, belongs_to_options(options)
94
+ join_model
95
+ end
96
+
97
+ def middle_reflection(join_model)
98
+ middle_name = [lhs_model.name.downcase.pluralize,
99
+ association_name].join("_".freeze).gsub("::".freeze, "_".freeze).to_sym
100
+ middle_options = middle_options join_model
101
+
102
+ HasMany.create_reflection(lhs_model,
103
+ middle_name,
104
+ nil,
105
+ middle_options)
106
+ end
107
+
108
+ private
109
+
110
+ def middle_options(join_model)
111
+ middle_options = {}
112
+ middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
113
+ middle_options[:source] = join_model.left_reflection.name
114
+ if options.key? :foreign_key
115
+ middle_options[:foreign_key] = options[:foreign_key]
116
+ end
117
+ middle_options
40
118
  end
41
119
 
42
- # Generates a join table name from two provided table names.
43
- # The names in the join table names end up in lexicographic order.
44
- #
45
- # join_table_name("members", "clubs") # => "clubs_members"
46
- # join_table_name("members", "special_clubs") # => "members_special_clubs"
47
- def join_table_name(first_table_name, second_table_name)
48
- if first_table_name < second_table_name
49
- join_table = "#{first_table_name}_#{second_table_name}"
50
- else
51
- join_table = "#{second_table_name}_#{first_table_name}"
120
+ def belongs_to_options(options)
121
+ rhs_options = {}
122
+
123
+ if options.key? :class_name
124
+ rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key
125
+ rhs_options[:class_name] = options[:class_name]
126
+ end
127
+
128
+ if options.key? :association_foreign_key
129
+ rhs_options[:foreign_key] = options[:association_foreign_key]
52
130
  end
53
131
 
54
- model.table_name_prefix + join_table + model.table_name_suffix
132
+ rhs_options
55
133
  end
56
134
  end
57
135
  end
@@ -1,71 +1,17 @@
1
- require 'active_support/core_ext/object/inclusion'
1
+ # frozen_string_literal: true
2
2
 
3
- module ActiveRecord::Associations::Builder
3
+ module ActiveRecord::Associations::Builder # :nodoc:
4
4
  class HasMany < CollectionAssociation #:nodoc:
5
- self.macro = :has_many
6
-
7
- self.valid_options += [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of]
8
-
9
- def build
10
- reflection = super
11
- configure_dependency
12
- reflection
5
+ def self.macro
6
+ :has_many
13
7
  end
14
8
 
15
- private
16
-
17
- def configure_dependency
18
- if options[:dependent]
19
- unless options[:dependent].in?([:destroy, :delete_all, :nullify, :restrict])
20
- raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, " \
21
- ":nullify or :restrict (#{options[:dependent].inspect})"
22
- end
23
-
24
- send("define_#{options[:dependent]}_dependency_method")
25
- model.before_destroy dependency_method_name
26
- end
27
- end
28
-
29
- def define_destroy_dependency_method
30
- name = self.name
31
- mixin.redefine_method(dependency_method_name) do
32
- send(name).each do |o|
33
- # No point in executing the counter update since we're going to destroy the parent anyway
34
- counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym
35
- if o.respond_to?(counter_method)
36
- class << o
37
- self
38
- end.send(:define_method, counter_method, Proc.new {})
39
- end
40
- end
41
-
42
- send(name).delete_all
43
- end
44
- end
45
-
46
- def define_delete_all_dependency_method
47
- name = self.name
48
- mixin.redefine_method(dependency_method_name) do
49
- association(name).delete_all_on_destroy
50
- end
51
- end
52
-
53
- def define_nullify_dependency_method
54
- name = self.name
55
- mixin.redefine_method(dependency_method_name) do
56
- send(name).delete_all
57
- end
58
- end
59
-
60
- def define_restrict_dependency_method
61
- name = self.name
62
- mixin.redefine_method(dependency_method_name) do
63
- raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).empty?
64
- end
65
- end
9
+ def self.valid_options(options)
10
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type, :index_errors]
11
+ end
66
12
 
67
- def dependency_method_name
68
- "has_many_dependent_for_#{name}"
69
- end
13
+ def self.valid_dependent_options
14
+ [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
15
+ end
70
16
  end
71
17
  end