activerecord 4.2.0

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

Potentially problematic release.


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

Files changed (221) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1372 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +218 -0
  5. data/examples/performance.rb +184 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +173 -0
  8. data/lib/active_record/aggregations.rb +266 -0
  9. data/lib/active_record/association_relation.rb +22 -0
  10. data/lib/active_record/associations.rb +1724 -0
  11. data/lib/active_record/associations/alias_tracker.rb +87 -0
  12. data/lib/active_record/associations/association.rb +253 -0
  13. data/lib/active_record/associations/association_scope.rb +194 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +111 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +149 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +116 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +91 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +124 -0
  20. data/lib/active_record/associations/builder/has_many.rb +15 -0
  21. data/lib/active_record/associations/builder/has_one.rb +23 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +38 -0
  23. data/lib/active_record/associations/collection_association.rb +634 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1027 -0
  25. data/lib/active_record/associations/has_many_association.rb +184 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +238 -0
  27. data/lib/active_record/associations/has_one_association.rb +105 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +282 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +122 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +22 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  33. data/lib/active_record/associations/preloader.rb +203 -0
  34. data/lib/active_record/associations/preloader/association.rb +162 -0
  35. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  36. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  37. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  38. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  39. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  40. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  41. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  42. data/lib/active_record/associations/preloader/through_association.rb +96 -0
  43. data/lib/active_record/associations/singular_association.rb +86 -0
  44. data/lib/active_record/associations/through_association.rb +96 -0
  45. data/lib/active_record/attribute.rb +149 -0
  46. data/lib/active_record/attribute_assignment.rb +212 -0
  47. data/lib/active_record/attribute_decorators.rb +66 -0
  48. data/lib/active_record/attribute_methods.rb +439 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +71 -0
  50. data/lib/active_record/attribute_methods/dirty.rb +181 -0
  51. data/lib/active_record/attribute_methods/primary_key.rb +128 -0
  52. data/lib/active_record/attribute_methods/query.rb +40 -0
  53. data/lib/active_record/attribute_methods/read.rb +103 -0
  54. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  55. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
  56. data/lib/active_record/attribute_methods/write.rb +83 -0
  57. data/lib/active_record/attribute_set.rb +77 -0
  58. data/lib/active_record/attribute_set/builder.rb +86 -0
  59. data/lib/active_record/attributes.rb +139 -0
  60. data/lib/active_record/autosave_association.rb +439 -0
  61. data/lib/active_record/base.rb +317 -0
  62. data/lib/active_record/callbacks.rb +313 -0
  63. data/lib/active_record/coders/json.rb +13 -0
  64. data/lib/active_record/coders/yaml_column.rb +38 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +659 -0
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +373 -0
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +133 -0
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +574 -0
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +991 -0
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +219 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -0
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +883 -0
  78. data/lib/active_record/connection_adapters/column.rb +82 -0
  79. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  80. data/lib/active_record/connection_adapters/mysql2_adapter.rb +282 -0
  81. data/lib/active_record/connection_adapters/mysql_adapter.rb +491 -0
  82. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  111. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  112. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +588 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +754 -0
  117. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +628 -0
  119. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  120. data/lib/active_record/connection_handling.rb +132 -0
  121. data/lib/active_record/core.rb +566 -0
  122. data/lib/active_record/counter_cache.rb +175 -0
  123. data/lib/active_record/dynamic_matchers.rb +140 -0
  124. data/lib/active_record/enum.rb +198 -0
  125. data/lib/active_record/errors.rb +252 -0
  126. data/lib/active_record/explain.rb +38 -0
  127. data/lib/active_record/explain_registry.rb +30 -0
  128. data/lib/active_record/explain_subscriber.rb +29 -0
  129. data/lib/active_record/fixture_set/file.rb +56 -0
  130. data/lib/active_record/fixtures.rb +1007 -0
  131. data/lib/active_record/gem_version.rb +15 -0
  132. data/lib/active_record/inheritance.rb +247 -0
  133. data/lib/active_record/integration.rb +113 -0
  134. data/lib/active_record/locale/en.yml +47 -0
  135. data/lib/active_record/locking/optimistic.rb +204 -0
  136. data/lib/active_record/locking/pessimistic.rb +77 -0
  137. data/lib/active_record/log_subscriber.rb +75 -0
  138. data/lib/active_record/migration.rb +1051 -0
  139. data/lib/active_record/migration/command_recorder.rb +197 -0
  140. data/lib/active_record/migration/join_table.rb +15 -0
  141. data/lib/active_record/model_schema.rb +340 -0
  142. data/lib/active_record/nested_attributes.rb +548 -0
  143. data/lib/active_record/no_touching.rb +52 -0
  144. data/lib/active_record/null_relation.rb +81 -0
  145. data/lib/active_record/persistence.rb +532 -0
  146. data/lib/active_record/query_cache.rb +56 -0
  147. data/lib/active_record/querying.rb +68 -0
  148. data/lib/active_record/railtie.rb +162 -0
  149. data/lib/active_record/railties/console_sandbox.rb +5 -0
  150. data/lib/active_record/railties/controller_runtime.rb +50 -0
  151. data/lib/active_record/railties/databases.rake +391 -0
  152. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  153. data/lib/active_record/readonly_attributes.rb +23 -0
  154. data/lib/active_record/reflection.rb +881 -0
  155. data/lib/active_record/relation.rb +681 -0
  156. data/lib/active_record/relation/batches.rb +138 -0
  157. data/lib/active_record/relation/calculations.rb +403 -0
  158. data/lib/active_record/relation/delegation.rb +140 -0
  159. data/lib/active_record/relation/finder_methods.rb +528 -0
  160. data/lib/active_record/relation/merger.rb +170 -0
  161. data/lib/active_record/relation/predicate_builder.rb +126 -0
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +47 -0
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  164. data/lib/active_record/relation/query_methods.rb +1176 -0
  165. data/lib/active_record/relation/spawn_methods.rb +75 -0
  166. data/lib/active_record/result.rb +131 -0
  167. data/lib/active_record/runtime_registry.rb +22 -0
  168. data/lib/active_record/sanitization.rb +191 -0
  169. data/lib/active_record/schema.rb +64 -0
  170. data/lib/active_record/schema_dumper.rb +251 -0
  171. data/lib/active_record/schema_migration.rb +56 -0
  172. data/lib/active_record/scoping.rb +87 -0
  173. data/lib/active_record/scoping/default.rb +134 -0
  174. data/lib/active_record/scoping/named.rb +164 -0
  175. data/lib/active_record/serialization.rb +22 -0
  176. data/lib/active_record/serializers/xml_serializer.rb +193 -0
  177. data/lib/active_record/statement_cache.rb +111 -0
  178. data/lib/active_record/store.rb +205 -0
  179. data/lib/active_record/tasks/database_tasks.rb +296 -0
  180. data/lib/active_record/tasks/mysql_database_tasks.rb +145 -0
  181. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  182. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  183. data/lib/active_record/timestamp.rb +121 -0
  184. data/lib/active_record/transactions.rb +417 -0
  185. data/lib/active_record/translation.rb +22 -0
  186. data/lib/active_record/type.rb +23 -0
  187. data/lib/active_record/type/big_integer.rb +13 -0
  188. data/lib/active_record/type/binary.rb +50 -0
  189. data/lib/active_record/type/boolean.rb +30 -0
  190. data/lib/active_record/type/date.rb +46 -0
  191. data/lib/active_record/type/date_time.rb +43 -0
  192. data/lib/active_record/type/decimal.rb +40 -0
  193. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  194. data/lib/active_record/type/decorator.rb +14 -0
  195. data/lib/active_record/type/float.rb +19 -0
  196. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  197. data/lib/active_record/type/integer.rb +55 -0
  198. data/lib/active_record/type/mutable.rb +16 -0
  199. data/lib/active_record/type/numeric.rb +36 -0
  200. data/lib/active_record/type/serialized.rb +56 -0
  201. data/lib/active_record/type/string.rb +36 -0
  202. data/lib/active_record/type/text.rb +11 -0
  203. data/lib/active_record/type/time.rb +26 -0
  204. data/lib/active_record/type/time_value.rb +38 -0
  205. data/lib/active_record/type/type_map.rb +64 -0
  206. data/lib/active_record/type/unsigned_integer.rb +15 -0
  207. data/lib/active_record/type/value.rb +101 -0
  208. data/lib/active_record/validations.rb +90 -0
  209. data/lib/active_record/validations/associated.rb +51 -0
  210. data/lib/active_record/validations/presence.rb +67 -0
  211. data/lib/active_record/validations/uniqueness.rb +229 -0
  212. data/lib/active_record/version.rb +8 -0
  213. data/lib/rails/generators/active_record.rb +17 -0
  214. data/lib/rails/generators/active_record/migration.rb +18 -0
  215. data/lib/rails/generators/active_record/migration/migration_generator.rb +70 -0
  216. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +22 -0
  217. data/lib/rails/generators/active_record/migration/templates/migration.rb +45 -0
  218. data/lib/rails/generators/active_record/model/model_generator.rb +52 -0
  219. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  220. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  221. metadata +309 -0
@@ -0,0 +1,111 @@
1
+ module ActiveRecord
2
+ # = Active Record Belongs To Association
3
+ module Associations
4
+ class BelongsToAssociation < SingularAssociation #:nodoc:
5
+
6
+ def handle_dependency
7
+ target.send(options[:dependent]) if load_target
8
+ end
9
+
10
+ def replace(record)
11
+ if record
12
+ raise_on_type_mismatch!(record)
13
+ update_counters(record)
14
+ replace_keys(record)
15
+ set_inverse_instance(record)
16
+ @updated = true
17
+ else
18
+ decrement_counters
19
+ remove_keys
20
+ end
21
+
22
+ self.target = record
23
+ end
24
+
25
+ def reset
26
+ super
27
+ @updated = false
28
+ end
29
+
30
+ def updated?
31
+ @updated
32
+ end
33
+
34
+ def decrement_counters # :nodoc:
35
+ with_cache_name { |name| decrement_counter name }
36
+ end
37
+
38
+ def increment_counters # :nodoc:
39
+ with_cache_name { |name| increment_counter name }
40
+ end
41
+
42
+ private
43
+
44
+ def find_target?
45
+ !loaded? && foreign_key_present? && klass
46
+ end
47
+
48
+ def with_cache_name
49
+ counter_cache_name = reflection.counter_cache_column
50
+ return unless counter_cache_name && owner.persisted?
51
+ yield counter_cache_name
52
+ end
53
+
54
+ def update_counters(record)
55
+ with_cache_name do |name|
56
+ return unless different_target? record
57
+ record.class.increment_counter(name, record.id)
58
+ decrement_counter name
59
+ end
60
+ end
61
+
62
+ def decrement_counter(counter_cache_name)
63
+ if foreign_key_present?
64
+ klass.decrement_counter(counter_cache_name, target_id)
65
+ end
66
+ end
67
+
68
+ def increment_counter(counter_cache_name)
69
+ if foreign_key_present?
70
+ klass.increment_counter(counter_cache_name, target_id)
71
+ end
72
+ end
73
+
74
+ # Checks whether record is different to the current target, without loading it
75
+ def different_target?(record)
76
+ record.id != owner[reflection.foreign_key]
77
+ end
78
+
79
+ def replace_keys(record)
80
+ owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
81
+ end
82
+
83
+ def remove_keys
84
+ owner[reflection.foreign_key] = nil
85
+ end
86
+
87
+ def foreign_key_present?
88
+ owner[reflection.foreign_key]
89
+ end
90
+
91
+ # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
92
+ # has_one associations.
93
+ def invertible_for?(record)
94
+ inverse = inverse_reflection_for(record)
95
+ inverse && inverse.has_one?
96
+ end
97
+
98
+ def target_id
99
+ if options[:primary_key]
100
+ owner.send(reflection.name).try(:id)
101
+ else
102
+ owner[reflection.foreign_key]
103
+ end
104
+ end
105
+
106
+ def stale_state
107
+ owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,40 @@
1
+ module ActiveRecord
2
+ # = Active Record Belongs To Polymorphic Association
3
+ module Associations
4
+ class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
5
+ def klass
6
+ type = owner[reflection.foreign_type]
7
+ type.presence && type.constantize
8
+ end
9
+
10
+ private
11
+
12
+ def replace_keys(record)
13
+ super
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
20
+ end
21
+
22
+ def different_target?(record)
23
+ super || record.class != klass
24
+ end
25
+
26
+ def inverse_reflection_for(record)
27
+ reflection.polymorphic_inverse_of(record.class)
28
+ end
29
+
30
+ def raise_on_type_mismatch!(record)
31
+ # A polymorphic association cannot have a type mismatch, by definition
32
+ end
33
+
34
+ def stale_state
35
+ foreign_key = super
36
+ foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,149 @@
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+
3
+ # This is the parent Association class which defines the variables
4
+ # used by all associations.
5
+ #
6
+ # The hierarchy is defined as follows:
7
+ # Association
8
+ # - SingularAssociation
9
+ # - BelongsToAssociation
10
+ # - HasOneAssociation
11
+ # - CollectionAssociation
12
+ # - HasManyAssociation
13
+
14
+ module ActiveRecord::Associations::Builder
15
+ class Association #:nodoc:
16
+ class << self
17
+ attr_accessor :extensions
18
+ # TODO: This class accessor is needed to make activerecord-deprecated_finders work.
19
+ # We can move it to a constant in 5.0.
20
+ attr_accessor :valid_options
21
+ end
22
+ self.extensions = []
23
+
24
+ self.valid_options = [:class_name, :class, :foreign_key, :validate]
25
+
26
+ attr_reader :name, :scope, :options
27
+
28
+ def self.build(model, name, scope, options, &block)
29
+ if model.dangerous_attribute_method?(name)
30
+ raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
31
+ "this will conflict with a method #{name} already defined by Active Record. " \
32
+ "Please choose a different association name."
33
+ end
34
+
35
+ builder = create_builder model, name, scope, options, &block
36
+ reflection = builder.build(model)
37
+ define_accessors model, reflection
38
+ define_callbacks model, reflection
39
+ define_validations model, reflection
40
+ builder.define_extensions model
41
+ reflection
42
+ end
43
+
44
+ def self.create_builder(model, name, scope, options, &block)
45
+ raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
46
+
47
+ new(model, name, scope, options, &block)
48
+ end
49
+
50
+ def initialize(model, name, scope, options)
51
+ # TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders.
52
+ if scope.is_a?(Hash)
53
+ options = scope
54
+ scope = nil
55
+ end
56
+
57
+ # TODO: Remove this model argument as soon we drop support to activerecord-deprecated_finders.
58
+ @name = name
59
+ @scope = scope
60
+ @options = options
61
+
62
+ validate_options
63
+
64
+ if scope && scope.arity == 0
65
+ @scope = proc { instance_exec(&scope) }
66
+ end
67
+ end
68
+
69
+ def build(model)
70
+ ActiveRecord::Reflection.create(macro, name, scope, options, model)
71
+ end
72
+
73
+ def macro
74
+ raise NotImplementedError
75
+ end
76
+
77
+ def valid_options
78
+ Association.valid_options + Association.extensions.flat_map(&:valid_options)
79
+ end
80
+
81
+ def validate_options
82
+ options.assert_valid_keys(valid_options)
83
+ end
84
+
85
+ def define_extensions(model)
86
+ end
87
+
88
+ def self.define_callbacks(model, reflection)
89
+ if dependent = reflection.options[:dependent]
90
+ check_dependent_options(dependent)
91
+ add_destroy_callbacks(model, reflection)
92
+ end
93
+
94
+ Association.extensions.each do |extension|
95
+ extension.build model, reflection
96
+ end
97
+ end
98
+
99
+ # Defines the setter and getter methods for the association
100
+ # class Post < ActiveRecord::Base
101
+ # has_many :comments
102
+ # end
103
+ #
104
+ # Post.first.comments and Post.first.comments= methods are defined by this method...
105
+ def self.define_accessors(model, reflection)
106
+ mixin = model.generated_association_methods
107
+ name = reflection.name
108
+ define_readers(mixin, name)
109
+ define_writers(mixin, name)
110
+ end
111
+
112
+ def self.define_readers(mixin, name)
113
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
114
+ def #{name}(*args)
115
+ association(:#{name}).reader(*args)
116
+ end
117
+ CODE
118
+ end
119
+
120
+ def self.define_writers(mixin, name)
121
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
122
+ def #{name}=(value)
123
+ association(:#{name}).writer(value)
124
+ end
125
+ CODE
126
+ end
127
+
128
+ def self.define_validations(model, reflection)
129
+ # noop
130
+ end
131
+
132
+ def self.valid_dependent_options
133
+ raise NotImplementedError
134
+ end
135
+
136
+ private
137
+
138
+ def self.check_dependent_options(dependent)
139
+ unless valid_dependent_options.include? dependent
140
+ raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
141
+ end
142
+ end
143
+
144
+ def self.add_destroy_callbacks(model, reflection)
145
+ name = reflection.name
146
+ model.before_destroy lambda { |o| o.association(name).handle_dependency }
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,116 @@
1
+ module ActiveRecord::Associations::Builder
2
+ class BelongsTo < SingularAssociation #:nodoc:
3
+ def macro
4
+ :belongs_to
5
+ end
6
+
7
+ def valid_options
8
+ super + [:foreign_type, :polymorphic, :touch, :counter_cache]
9
+ end
10
+
11
+ def self.valid_dependent_options
12
+ [:destroy, :delete]
13
+ end
14
+
15
+ def self.define_callbacks(model, reflection)
16
+ super
17
+ add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache]
18
+ add_touch_callbacks(model, reflection) if reflection.options[:touch]
19
+ end
20
+
21
+ def self.define_accessors(mixin, reflection)
22
+ super
23
+ add_counter_cache_methods mixin
24
+ end
25
+
26
+ private
27
+
28
+ def self.add_counter_cache_methods(mixin)
29
+ return if mixin.method_defined? :belongs_to_counter_cache_after_update
30
+
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
35
+
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
42
+
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
51
+ end
52
+ end
53
+
54
+ def self.add_counter_cache_callbacks(model, reflection)
55
+ cache_column = reflection.counter_cache_column
56
+
57
+ model.after_update lambda { |record|
58
+ record.belongs_to_counter_cache_after_update(reflection)
59
+ }
60
+
61
+ klass = reflection.class_name.safe_constantize
62
+ klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
63
+ end
64
+
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)
77
+
78
+ if old_record
79
+ if touch != true
80
+ old_record.touch touch
81
+ else
82
+ old_record.touch
83
+ end
84
+ end
85
+ end
86
+
87
+ record = o.send name
88
+ if record && record.persisted?
89
+ if touch != true
90
+ record.touch touch
91
+ else
92
+ record.touch
93
+ end
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
115
+ end
116
+ end
@@ -0,0 +1,91 @@
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
+
5
+ module ActiveRecord::Associations::Builder
6
+ class CollectionAssociation < Association #:nodoc:
7
+
8
+ CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
9
+
10
+ def valid_options
11
+ super + [:table_name, :before_add,
12
+ :after_add, :before_remove, :after_remove, :extend]
13
+ end
14
+
15
+ attr_reader :block_extension
16
+
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
24
+ end
25
+
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
+ }
33
+ end
34
+
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
40
+ end
41
+
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) }
55
+ end
56
+ end
57
+ model.send "#{full_callback_name}=", callbacks
58
+ end
59
+
60
+ # Defines the setter and getter methods for the collection_singular_ids.
61
+ def self.define_readers(mixin, name)
62
+ super
63
+
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
70
+
71
+ def self.define_writers(mixin, name)
72
+ super
73
+
74
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
75
+ def #{name.to_s.singularize}_ids=(ids)
76
+ association(:#{name}).ids_writer(ids)
77
+ end
78
+ CODE
79
+ end
80
+
81
+ private
82
+
83
+ def wrap_scope(scope, mod)
84
+ if scope
85
+ proc { |owner| instance_exec(owner, &scope).extending(mod) }
86
+ else
87
+ proc { extending(mod) }
88
+ end
89
+ end
90
+ end
91
+ end