activerecord 3.1.10 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  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 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  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 -102
  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 +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  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 +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  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 +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  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 +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  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 +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  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 +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  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 +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  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 +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  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 +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  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 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  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 +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,85 +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
+
7
+ def valid_options
8
+ super + [:foreign_type, :polymorphic, :touch, :counter_cache]
9
+ end
6
10
 
7
- self.valid_options += [:foreign_type, :polymorphic, :touch]
11
+ def self.valid_dependent_options
12
+ [:destroy, :delete]
13
+ end
8
14
 
9
- def constructable?
10
- !options[:polymorphic]
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]
11
19
  end
12
20
 
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
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
- model.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
- model.redefine_method(method_name) do
36
- record = send(name)
37
- record.class.decrement_counter(cache_column, record.id) unless record.nil?
38
- end
39
- model.before_destroy(method_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
40
42
 
41
- model.send(:module_eval,
42
- "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)", __FILE__, __LINE__
43
- )
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
49
+ end
50
+ end
44
51
  end
52
+ end
45
53
 
46
- def add_touch_callbacks(reflection)
47
- name = self.name
48
- method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
49
- touch = options[:touch]
54
+ def self.add_counter_cache_callbacks(model, reflection)
55
+ cache_column = reflection.counter_cache_column
50
56
 
51
- model.redefine_method(method_name) do
52
- record = send(name)
57
+ model.after_update lambda { |record|
58
+ record.belongs_to_counter_cache_after_update(reflection)
59
+ }
53
60
 
54
- unless record.nil?
55
- if touch == true
56
- record.touch
57
- else
58
- record.touch(touch)
59
- end
60
- end
61
- end
61
+ klass = reflection.class_name.safe_constantize
62
+ klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
63
+ end
62
64
 
63
- model.after_save(method_name)
64
- model.after_touch(method_name)
65
- model.after_destroy(method_name)
66
- 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)
67
77
 
68
- def configure_dependency
69
- if options[:dependent]
70
- unless options[:dependent].in?([:destroy, :delete])
71
- 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
72
83
  end
84
+ end
85
+ end
73
86
 
74
- method_name = "belongs_to_dependent_#{options[:dependent]}_for_#{name}"
75
- model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
76
- def #{method_name}
77
- association = #{name}
78
- association.#{options[:dependent]} if association
79
- end
80
- eoruby
81
- 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
82
93
  end
83
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
84
115
  end
85
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
- model.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
- model.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
@@ -1,57 +1,128 @@
1
1
  module ActiveRecord::Associations::Builder
2
- class HasAndBelongsToMany < CollectionAssociation #:nodoc:
3
- self.macro = :has_and_belongs_to_many
2
+ class HasAndBelongsToMany # :nodoc:
3
+ class JoinTableResolver
4
+ KnownTable = Struct.new :join_table
4
5
 
5
- self.valid_options += [:join_table, :association_foreign_key, :delete_sql, :insert_sql]
6
+ class KnownClass
7
+ def initialize(lhs_class, rhs_class_name)
8
+ @lhs_class = lhs_class
9
+ @rhs_class_name = rhs_class_name
10
+ @join_table = nil
11
+ end
6
12
 
7
- def build
8
- reflection = super
9
- check_validity(reflection)
10
- define_destroy_hook
11
- reflection
12
- end
13
+ def join_table
14
+ @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
15
+ end
13
16
 
14
- private
17
+ private
15
18
 
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
- })
19
+ def klass
20
+ @lhs_class.send(:compute_type, @rhs_class_name)
21
+ end
26
22
  end
27
23
 
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)
24
+ def self.build(lhs_class, name, options)
25
+ if options[:join_table]
26
+ KnownTable.new options[:join_table].to_s
27
+ else
28
+ class_name = options.fetch(:class_name) {
29
+ name.to_s.camelize.singularize
30
+ }
31
+ KnownClass.new lhs_class, class_name
34
32
  end
35
-
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
- )
40
33
  end
34
+ end
41
35
 
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}"
36
+ attr_reader :lhs_model, :association_name, :options
37
+
38
+ def initialize(association_name, lhs_model, options)
39
+ @association_name = association_name
40
+ @lhs_model = lhs_model
41
+ @options = options
42
+ end
43
+
44
+ def through_model
45
+ habtm = JoinTableResolver.build lhs_model, association_name, options
46
+
47
+ join_model = Class.new(ActiveRecord::Base) {
48
+ class << self;
49
+ attr_accessor :left_model
50
+ attr_accessor :name
51
+ attr_accessor :table_name_resolver
52
+ attr_accessor :left_reflection
53
+ attr_accessor :right_reflection
54
+ end
55
+
56
+ def self.table_name
57
+ table_name_resolver.join_table
52
58
  end
53
59
 
54
- model.table_name_prefix + join_table + model.table_name_suffix
60
+ def self.compute_type(class_name)
61
+ left_model.compute_type class_name
62
+ end
63
+
64
+ def self.add_left_association(name, options)
65
+ belongs_to name, options
66
+ self.left_reflection = _reflect_on_association(name)
67
+ end
68
+
69
+ def self.add_right_association(name, options)
70
+ rhs_name = name.to_s.singularize.to_sym
71
+ belongs_to rhs_name, options
72
+ self.right_reflection = _reflect_on_association(rhs_name)
73
+ end
74
+
75
+ def self.retrieve_connection
76
+ left_model.retrieve_connection
77
+ end
78
+
79
+ }
80
+
81
+ join_model.name = "HABTM_#{association_name.to_s.camelize}"
82
+ join_model.table_name_resolver = habtm
83
+ join_model.left_model = lhs_model
84
+
85
+ join_model.add_left_association :left_side, anonymous_class: lhs_model
86
+ join_model.add_right_association association_name, belongs_to_options(options)
87
+ join_model
88
+ end
89
+
90
+ def middle_reflection(join_model)
91
+ middle_name = [lhs_model.name.downcase.pluralize,
92
+ association_name].join('_').gsub(/::/, '_').to_sym
93
+ middle_options = middle_options join_model
94
+ hm_builder = HasMany.create_builder(lhs_model,
95
+ middle_name,
96
+ nil,
97
+ middle_options)
98
+ hm_builder.build lhs_model
99
+ end
100
+
101
+ private
102
+
103
+ def middle_options(join_model)
104
+ middle_options = {}
105
+ middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
106
+ middle_options[:source] = join_model.left_reflection.name
107
+ if options.key? :foreign_key
108
+ middle_options[:foreign_key] = options[:foreign_key]
109
+ end
110
+ middle_options
111
+ end
112
+
113
+ def belongs_to_options(options)
114
+ rhs_options = {}
115
+
116
+ if options.key? :class_name
117
+ rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key
118
+ rhs_options[:class_name] = options[:class_name]
55
119
  end
120
+
121
+ if options.key? :association_foreign_key
122
+ rhs_options[:foreign_key] = options[:association_foreign_key]
123
+ end
124
+
125
+ rhs_options
126
+ end
56
127
  end
57
128
  end
@@ -1,71 +1,15 @@
1
- require 'active_support/core_ext/object/inclusion'
2
-
3
1
  module ActiveRecord::Associations::Builder
4
2
  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
3
+ def macro
4
+ :has_many
13
5
  end
14
6
 
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
- model.send(:define_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
- model.send(:define_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
- model.send(:define_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
- model.send(:define_method, dependency_method_name) do
63
- raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).empty?
64
- end
65
- end
7
+ def valid_options
8
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type]
9
+ end
66
10
 
67
- def dependency_method_name
68
- "has_many_dependent_for_#{name}"
69
- end
11
+ def self.valid_dependent_options
12
+ [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
13
+ end
70
14
  end
71
15
  end
@@ -1,63 +1,23 @@
1
- require 'active_support/core_ext/object/inclusion'
2
-
3
1
  module ActiveRecord::Associations::Builder
4
2
  class HasOne < SingularAssociation #:nodoc:
5
- self.macro = :has_one
6
-
7
- self.valid_options += [:order, :as]
8
-
9
- class_attribute :through_options
10
- self.through_options = [:through, :source, :source_type]
3
+ def macro
4
+ :has_one
5
+ end
11
6
 
12
- def constructable?
13
- !options[:through]
7
+ def valid_options
8
+ valid = super + [:as, :foreign_type]
9
+ valid += [:through, :source, :source_type] if options[:through]
10
+ valid
14
11
  end
15
12
 
16
- def build
17
- reflection = super
18
- configure_dependency unless options[:through]
19
- reflection
13
+ def self.valid_dependent_options
14
+ [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
20
15
  end
21
16
 
22
17
  private
23
18
 
24
- def validate_options
25
- valid_options = self.class.valid_options
26
- valid_options += self.class.through_options if options[:through]
27
- options.assert_valid_keys(valid_options)
28
- end
29
-
30
- def configure_dependency
31
- if options[:dependent]
32
- unless options[:dependent].in?([:destroy, :delete, :nullify, :restrict])
33
- raise ArgumentError, "The :dependent option expects either :destroy, :delete, " \
34
- ":nullify or :restrict (#{options[:dependent].inspect})"
35
- end
36
-
37
- send("define_#{options[:dependent]}_dependency_method")
38
- model.before_destroy dependency_method_name
39
- end
40
- end
41
-
42
- def dependency_method_name
43
- "has_one_dependent_#{options[:dependent]}_for_#{name}"
44
- end
45
-
46
- def define_destroy_dependency_method
47
- model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
48
- def #{dependency_method_name}
49
- association(#{name.to_sym.inspect}).delete
50
- end
51
- eoruby
52
- end
53
- alias :define_delete_dependency_method :define_destroy_dependency_method
54
- alias :define_nullify_dependency_method :define_destroy_dependency_method
55
-
56
- def define_restrict_dependency_method
57
- name = self.name
58
- model.redefine_method(dependency_method_name) do
59
- raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).nil?
60
- end
61
- end
19
+ def self.add_destroy_callbacks(model, reflection)
20
+ super unless reflection.options[:through]
21
+ end
62
22
  end
63
23
  end