activerecord 4.0.13 → 4.1.0.beta1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +745 -2700
- data/README.rdoc +2 -2
- data/examples/performance.rb +30 -18
- data/examples/simple.rb +4 -4
- data/lib/active_record.rb +2 -6
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/association_relation.rb +0 -4
- data/lib/active_record/associations.rb +87 -43
- data/lib/active_record/associations/alias_tracker.rb +1 -3
- data/lib/active_record/associations/association.rb +8 -16
- data/lib/active_record/associations/association_scope.rb +5 -16
- data/lib/active_record/associations/belongs_to_association.rb +34 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +78 -54
- data/lib/active_record/associations/builder/belongs_to.rb +91 -58
- data/lib/active_record/associations/builder/collection_association.rb +47 -45
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -25
- data/lib/active_record/associations/builder/has_many.rb +2 -2
- data/lib/active_record/associations/builder/has_one.rb +5 -7
- data/lib/active_record/associations/builder/singular_association.rb +6 -7
- data/lib/active_record/associations/collection_association.rb +68 -105
- data/lib/active_record/associations/collection_proxy.rb +12 -15
- data/lib/active_record/associations/has_many_association.rb +11 -9
- data/lib/active_record/associations/has_many_through_association.rb +16 -12
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +204 -165
- data/lib/active_record/associations/join_dependency/join_association.rb +43 -101
- data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
- data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
- data/lib/active_record/associations/join_helper.rb +2 -11
- data/lib/active_record/associations/preloader.rb +89 -34
- data/lib/active_record/associations/preloader/association.rb +43 -25
- data/lib/active_record/associations/preloader/collection_association.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +58 -26
- data/lib/active_record/associations/singular_association.rb +6 -5
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +5 -2
- data/lib/active_record/attribute_methods.rb +45 -40
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
- data/lib/active_record/attribute_methods/dirty.rb +8 -22
- data/lib/active_record/attribute_methods/primary_key.rb +1 -7
- data/lib/active_record/attribute_methods/read.rb +55 -28
- data/lib/active_record/attribute_methods/serialization.rb +12 -33
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -13
- data/lib/active_record/attribute_methods/write.rb +37 -12
- data/lib/active_record/autosave_association.rb +207 -207
- data/lib/active_record/base.rb +5 -1
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -7
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +11 -22
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -14
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -5
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +84 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +52 -83
- data/lib/active_record/connection_adapters/abstract/transaction.rb +0 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +14 -97
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +58 -60
- data/lib/active_record/connection_adapters/column.rb +1 -35
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +3 -4
- data/lib/active_record/connection_adapters/mysql_adapter.rb +16 -15
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +24 -18
- data/lib/active_record/connection_adapters/postgresql/cast.rb +20 -16
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +23 -43
- data/lib/active_record/connection_adapters/postgresql/oid.rb +19 -12
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +28 -23
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +8 -30
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +92 -75
- data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +31 -64
- data/lib/active_record/connection_handling.rb +2 -2
- data/lib/active_record/core.rb +22 -43
- data/lib/active_record/counter_cache.rb +7 -7
- data/lib/active_record/enum.rb +100 -0
- data/lib/active_record/errors.rb +10 -5
- data/lib/active_record/fixture_set/file.rb +2 -1
- data/lib/active_record/fixtures.rb +171 -74
- data/lib/active_record/inheritance.rb +16 -22
- data/lib/active_record/integration.rb +52 -1
- data/lib/active_record/locking/optimistic.rb +7 -2
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -12
- data/lib/active_record/migration.rb +62 -46
- data/lib/active_record/migration/command_recorder.rb +7 -13
- data/lib/active_record/model_schema.rb +7 -14
- data/lib/active_record/nested_attributes.rb +10 -8
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +3 -3
- data/lib/active_record/persistence.rb +16 -34
- data/lib/active_record/querying.rb +14 -12
- data/lib/active_record/railtie.rb +0 -50
- data/lib/active_record/railties/databases.rake +12 -15
- data/lib/active_record/readonly_attributes.rb +0 -6
- data/lib/active_record/reflection.rb +189 -75
- data/lib/active_record/relation.rb +69 -94
- data/lib/active_record/relation/batches.rb +57 -23
- data/lib/active_record/relation/calculations.rb +36 -43
- data/lib/active_record/relation/delegation.rb +54 -39
- data/lib/active_record/relation/finder_methods.rb +107 -62
- data/lib/active_record/relation/merger.rb +7 -20
- data/lib/active_record/relation/predicate_builder.rb +57 -38
- data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +110 -98
- data/lib/active_record/relation/spawn_methods.rb +1 -2
- data/lib/active_record/result.rb +45 -6
- data/lib/active_record/runtime_registry.rb +5 -0
- data/lib/active_record/sanitization.rb +6 -8
- data/lib/active_record/schema_dumper.rb +16 -5
- data/lib/active_record/schema_migration.rb +24 -25
- data/lib/active_record/scoping/default.rb +5 -18
- data/lib/active_record/scoping/named.rb +8 -29
- data/lib/active_record/store.rb +56 -28
- data/lib/active_record/tasks/database_tasks.rb +8 -4
- data/lib/active_record/timestamp.rb +4 -4
- data/lib/active_record/transactions.rb +8 -10
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +1 -6
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record.rb +2 -8
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
- metadata +32 -45
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
- data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
- data/lib/active_record/test_case.rb +0 -102
@@ -13,7 +13,6 @@ module ActiveRecord
|
|
13
13
|
# BelongsToAssociation
|
14
14
|
# BelongsToPolymorphicAssociation
|
15
15
|
# CollectionAssociation
|
16
|
-
# HasAndBelongsToManyAssociation
|
17
16
|
# HasManyAssociation
|
18
17
|
# HasManyThroughAssociation + ThroughAssociation
|
19
18
|
class Association #:nodoc:
|
@@ -87,11 +86,6 @@ module ActiveRecord
|
|
87
86
|
target_scope.merge(association_scope)
|
88
87
|
end
|
89
88
|
|
90
|
-
def scoped
|
91
|
-
ActiveSupport::Deprecation.warn "#scoped is deprecated. use #scope instead."
|
92
|
-
scope
|
93
|
-
end
|
94
|
-
|
95
89
|
# The scope for this association.
|
96
90
|
#
|
97
91
|
# Note that the association_scope is merged into the target_scope only when the
|
@@ -110,11 +104,12 @@ module ActiveRecord
|
|
110
104
|
|
111
105
|
# Set the inverse association, if possible
|
112
106
|
def set_inverse_instance(record)
|
113
|
-
if
|
107
|
+
if invertible_for?(record)
|
114
108
|
inverse = record.association(inverse_reflection_for(record).name)
|
115
109
|
inverse.target = owner
|
116
110
|
inverse.inversed = true
|
117
111
|
end
|
112
|
+
record
|
118
113
|
end
|
119
114
|
|
120
115
|
# Returns the class of the target. belongs_to polymorphic overrides this to look at the
|
@@ -126,11 +121,7 @@ module ActiveRecord
|
|
126
121
|
# Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
|
127
122
|
# through association's scope)
|
128
123
|
def target_scope
|
129
|
-
|
130
|
-
scope = AssociationRelation.new(klass, klass.arel_table, self)
|
131
|
-
scope.merge! all
|
132
|
-
scope.default_scoped = all.default_scoped?
|
133
|
-
scope
|
124
|
+
AssociationRelation.create(klass, klass.arel_table, self).merge!(klass.all)
|
134
125
|
end
|
135
126
|
|
136
127
|
# Loads the \target if needed and returns it.
|
@@ -204,13 +195,14 @@ module ActiveRecord
|
|
204
195
|
creation_attributes.each { |key, value| record[key] = value }
|
205
196
|
end
|
206
197
|
|
207
|
-
#
|
198
|
+
# Returns true if there is a foreign key present on the owner which
|
208
199
|
# references the target. This is used to determine whether we can load
|
209
200
|
# the target if the owner is currently a new record (and therefore
|
210
|
-
# without a key).
|
201
|
+
# without a key). If the owner is a new record then foreign_key must
|
202
|
+
# be present in order to load target.
|
211
203
|
#
|
212
204
|
# Currently implemented by belongs_to (vanilla and polymorphic) and
|
213
|
-
# has_one/has_many :through associations which go through a belongs_to
|
205
|
+
# has_one/has_many :through associations which go through a belongs_to.
|
214
206
|
def foreign_key_present?
|
215
207
|
false
|
216
208
|
end
|
@@ -240,7 +232,7 @@ module ActiveRecord
|
|
240
232
|
|
241
233
|
# Returns true if record contains the foreign_key
|
242
234
|
def foreign_key_for?(record)
|
243
|
-
record.
|
235
|
+
record.attributes.has_key? reflection.foreign_key
|
244
236
|
end
|
245
237
|
|
246
238
|
# This should be implemented to return the values of the relevant key(s) on the owner,
|
@@ -44,18 +44,6 @@ module ActiveRecord
|
|
44
44
|
chain.each_with_index do |reflection, i|
|
45
45
|
table, foreign_table = tables.shift, tables.first
|
46
46
|
|
47
|
-
if reflection.source_macro == :has_and_belongs_to_many
|
48
|
-
join_table = tables.shift
|
49
|
-
|
50
|
-
scope = scope.joins(join(
|
51
|
-
join_table,
|
52
|
-
table[reflection.association_primary_key].
|
53
|
-
eq(join_table[reflection.association_foreign_key])
|
54
|
-
))
|
55
|
-
|
56
|
-
table, foreign_table = join_table, tables.first
|
57
|
-
end
|
58
|
-
|
59
47
|
if reflection.source_macro == :belongs_to
|
60
48
|
if reflection.options[:polymorphic]
|
61
49
|
key = reflection.association_primary_key(self.klass)
|
@@ -82,8 +70,9 @@ module ActiveRecord
|
|
82
70
|
constraint = table[key].eq(foreign_table[foreign_key])
|
83
71
|
|
84
72
|
if reflection.type
|
85
|
-
|
86
|
-
|
73
|
+
value = chain[i + 1].klass.base_class.name
|
74
|
+
bind_val = bind scope, table.table_name, reflection.type.to_s, value
|
75
|
+
scope = scope.where(table[reflection.type].eq(bind_val))
|
87
76
|
end
|
88
77
|
|
89
78
|
scope = scope.joins(join(foreign_table, constraint))
|
@@ -98,7 +87,7 @@ module ActiveRecord
|
|
98
87
|
item = eval_scope(klass, scope_chain_item)
|
99
88
|
|
100
89
|
if scope_chain_item == self.reflection.scope
|
101
|
-
scope.merge! item.except(:where, :includes)
|
90
|
+
scope.merge! item.except(:where, :includes, :bind)
|
102
91
|
end
|
103
92
|
|
104
93
|
if is_first_chain
|
@@ -124,7 +113,7 @@ module ActiveRecord
|
|
124
113
|
# the owner
|
125
114
|
klass.table_name
|
126
115
|
else
|
127
|
-
|
116
|
+
super
|
128
117
|
end
|
129
118
|
end
|
130
119
|
|
@@ -8,13 +8,16 @@ module ActiveRecord
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def replace(record)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
18
21
|
|
19
22
|
self.target = record
|
20
23
|
end
|
@@ -34,35 +37,41 @@ module ActiveRecord
|
|
34
37
|
!loaded? && foreign_key_present? && klass
|
35
38
|
end
|
36
39
|
|
37
|
-
def
|
40
|
+
def with_cache_name
|
38
41
|
counter_cache_name = reflection.counter_cache_column
|
42
|
+
return unless counter_cache_name && owner.persisted?
|
43
|
+
yield counter_cache_name
|
44
|
+
end
|
45
|
+
|
46
|
+
def update_counters(record)
|
47
|
+
with_cache_name do |name|
|
48
|
+
return unless different_target? record
|
49
|
+
record.class.increment_counter(name, record.id)
|
50
|
+
decrement_counter name
|
51
|
+
end
|
52
|
+
end
|
39
53
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
54
|
+
def decrement_counters
|
55
|
+
with_cache_name { |name| decrement_counter name }
|
56
|
+
end
|
44
57
|
|
45
|
-
|
46
|
-
|
47
|
-
|
58
|
+
def decrement_counter counter_cache_name
|
59
|
+
if foreign_key_present?
|
60
|
+
klass.decrement_counter(counter_cache_name, target_id)
|
48
61
|
end
|
49
62
|
end
|
50
63
|
|
51
64
|
# Checks whether record is different to the current target, without loading it
|
52
65
|
def different_target?(record)
|
53
|
-
|
54
|
-
owner[reflection.foreign_key]
|
55
|
-
else
|
56
|
-
record.id != owner[reflection.foreign_key]
|
57
|
-
end
|
66
|
+
record.id != owner[reflection.foreign_key]
|
58
67
|
end
|
59
68
|
|
60
69
|
def replace_keys(record)
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
70
|
+
owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
|
71
|
+
end
|
72
|
+
|
73
|
+
def remove_keys
|
74
|
+
owner[reflection.foreign_key] = nil
|
66
75
|
end
|
67
76
|
|
68
77
|
def foreign_key_present?
|
@@ -1,50 +1,66 @@
|
|
1
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
+
|
3
|
+
# This is the parent Association class which defines the variables
|
4
|
+
# used by all associations.
|
5
|
+
#
|
6
|
+
# The hierarchy is defined as follows:
|
7
|
+
# Association
|
8
|
+
# - SingularAssociation
|
9
|
+
# - BelongsToAssociation
|
10
|
+
# - HasOneAssociation
|
11
|
+
# - CollectionAssociation
|
12
|
+
# - HasManyAssociation
|
13
|
+
|
1
14
|
module ActiveRecord::Associations::Builder
|
2
15
|
class Association #:nodoc:
|
3
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.
|
4
20
|
attr_accessor :valid_options
|
5
21
|
end
|
22
|
+
self.extensions = []
|
6
23
|
|
7
|
-
self.valid_options = [:class_name, :foreign_key, :validate]
|
24
|
+
self.valid_options = [:class_name, :class, :foreign_key, :validate]
|
8
25
|
|
9
|
-
attr_reader :
|
26
|
+
attr_reader :name, :scope, :options
|
10
27
|
|
11
|
-
def self.build(
|
12
|
-
|
28
|
+
def self.build(model, name, scope, options, &block)
|
29
|
+
builder = create_builder model, name, scope, options, &block
|
30
|
+
reflection = builder.build(model)
|
31
|
+
define_accessors model, reflection
|
32
|
+
define_callbacks model, reflection
|
33
|
+
builder.define_extensions model
|
34
|
+
reflection
|
13
35
|
end
|
14
36
|
|
15
|
-
def
|
37
|
+
def self.create_builder(model, name, scope, options, &block)
|
16
38
|
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
|
17
39
|
|
18
|
-
|
19
|
-
|
40
|
+
new(model, name, scope, options, &block)
|
41
|
+
end
|
20
42
|
|
43
|
+
def initialize(model, name, scope, options)
|
44
|
+
# TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders.
|
21
45
|
if scope.is_a?(Hash)
|
22
|
-
|
23
|
-
|
24
|
-
else
|
25
|
-
@scope = scope
|
26
|
-
@options = options
|
46
|
+
options = scope
|
47
|
+
scope = nil
|
27
48
|
end
|
28
49
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
50
|
+
# TODO: Remove this model argument as soon we drop support to activerecord-deprecated_finders.
|
51
|
+
@name = name
|
52
|
+
@scope = scope
|
53
|
+
@options = options
|
34
54
|
|
35
|
-
|
36
|
-
@model.generated_feature_methods
|
37
|
-
end
|
55
|
+
validate_options
|
38
56
|
|
39
|
-
|
57
|
+
if scope && scope.arity == 0
|
58
|
+
@scope = proc { instance_exec(&scope) }
|
59
|
+
end
|
60
|
+
end
|
40
61
|
|
41
|
-
def build
|
42
|
-
|
43
|
-
define_accessors
|
44
|
-
configure_dependency if options[:dependent]
|
45
|
-
@reflection = model.create_reflection(macro, name, scope, options, model)
|
46
|
-
super # provides an extension point
|
47
|
-
@reflection
|
62
|
+
def build(model)
|
63
|
+
ActiveRecord::Reflection.create(macro, name, scope, options, model)
|
48
64
|
end
|
49
65
|
|
50
66
|
def macro
|
@@ -52,19 +68,37 @@ module ActiveRecord::Associations::Builder
|
|
52
68
|
end
|
53
69
|
|
54
70
|
def valid_options
|
55
|
-
Association.valid_options
|
71
|
+
Association.valid_options + Association.extensions.flat_map(&:valid_options)
|
56
72
|
end
|
57
73
|
|
58
74
|
def validate_options
|
59
75
|
options.assert_valid_keys(valid_options)
|
60
76
|
end
|
61
77
|
|
62
|
-
def
|
63
|
-
define_readers
|
64
|
-
define_writers
|
78
|
+
def define_extensions(model)
|
65
79
|
end
|
66
80
|
|
67
|
-
def
|
81
|
+
def self.define_callbacks(model, reflection)
|
82
|
+
add_before_destroy_callbacks(model, reflection) if reflection.options[:dependent]
|
83
|
+
Association.extensions.each do |extension|
|
84
|
+
extension.build model, reflection
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Defines the setter and getter methods for the association
|
89
|
+
# class Post < ActiveRecord::Base
|
90
|
+
# has_many :comments
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# Post.first.comments and Post.first.comments= methods are defined by this method...
|
94
|
+
def self.define_accessors(model, reflection)
|
95
|
+
mixin = model.generated_association_methods
|
96
|
+
name = reflection.name
|
97
|
+
define_readers(mixin, name)
|
98
|
+
define_writers(mixin, name)
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.define_readers(mixin, name)
|
68
102
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
69
103
|
def #{name}(*args)
|
70
104
|
association(:#{name}).reader(*args)
|
@@ -72,7 +106,7 @@ module ActiveRecord::Associations::Builder
|
|
72
106
|
CODE
|
73
107
|
end
|
74
108
|
|
75
|
-
def define_writers
|
109
|
+
def self.define_writers(mixin, name)
|
76
110
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
77
111
|
def #{name}=(value)
|
78
112
|
association(:#{name}).writer(value)
|
@@ -80,29 +114,19 @@ module ActiveRecord::Associations::Builder
|
|
80
114
|
CODE
|
81
115
|
end
|
82
116
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
if options[:dependent] == :restrict
|
89
|
-
ActiveSupport::Deprecation.warn(
|
90
|
-
"The :restrict option is deprecated. Please use :restrict_with_exception instead, which " \
|
91
|
-
"provides the same functionality."
|
92
|
-
)
|
93
|
-
end
|
117
|
+
def self.valid_dependent_options
|
118
|
+
raise NotImplementedError
|
119
|
+
end
|
94
120
|
|
95
|
-
|
96
|
-
def #{macro}_dependent_for_#{name}
|
97
|
-
association(:#{name}).handle_dependency
|
98
|
-
end
|
99
|
-
CODE
|
121
|
+
private
|
100
122
|
|
101
|
-
|
102
|
-
|
123
|
+
def self.add_before_destroy_callbacks(model, reflection)
|
124
|
+
unless valid_dependent_options.include? reflection.options[:dependent]
|
125
|
+
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{reflection.options[:dependent]}"
|
126
|
+
end
|
103
127
|
|
104
|
-
|
105
|
-
|
128
|
+
name = reflection.name
|
129
|
+
model.before_destroy lambda { |o| o.association(name).handle_dependency }
|
106
130
|
end
|
107
131
|
end
|
108
132
|
end
|
@@ -8,98 +8,131 @@ module ActiveRecord::Associations::Builder
|
|
8
8
|
super + [:foreign_type, :polymorphic, :touch, :counter_cache]
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
|
11
|
+
def self.valid_dependent_options
|
12
|
+
[:destroy, :delete]
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
|
17
|
-
add_counter_cache_callbacks(reflection) if options[:counter_cache]
|
18
|
-
add_touch_callbacks(reflection) if options[:touch]
|
19
|
-
reflection
|
15
|
+
def self.define_callbacks(model, reflection)
|
16
|
+
super
|
17
|
+
add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache]
|
18
|
+
add_touch_callbacks(model, reflection) if reflection.options[:touch]
|
20
19
|
end
|
21
20
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
def self.define_accessors(mixin, reflection)
|
22
|
+
super
|
23
|
+
add_counter_cache_methods mixin
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
def self.add_counter_cache_methods(mixin)
|
29
|
+
return if mixin.method_defined? :belongs_to_counter_cache_after_create
|
30
|
+
|
31
|
+
mixin.class_eval do
|
32
|
+
def belongs_to_counter_cache_after_create(reflection)
|
33
|
+
if record = send(reflection.name)
|
34
|
+
cache_column = reflection.counter_cache_column
|
35
|
+
record.class.increment_counter(cache_column, record.id)
|
31
36
|
@_after_create_counter_called = true
|
32
37
|
end
|
33
38
|
end
|
34
39
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
40
|
+
def belongs_to_counter_cache_before_destroy(reflection)
|
41
|
+
foreign_key = reflection.foreign_key.to_sym
|
42
|
+
unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
|
43
|
+
record = send reflection.name
|
38
44
|
if record && !self.destroyed?
|
39
|
-
|
45
|
+
cache_column = reflection.counter_cache_column
|
46
|
+
record.class.decrement_counter(cache_column, record.id)
|
40
47
|
end
|
41
48
|
end
|
42
49
|
end
|
43
50
|
|
44
|
-
def
|
51
|
+
def belongs_to_counter_cache_after_update(reflection)
|
52
|
+
foreign_key = reflection.foreign_key
|
53
|
+
cache_column = reflection.counter_cache_column
|
54
|
+
|
45
55
|
if (@_after_create_counter_called ||= false)
|
46
56
|
@_after_create_counter_called = false
|
47
|
-
elsif
|
48
|
-
model
|
49
|
-
foreign_key_was =
|
50
|
-
foreign_key
|
57
|
+
elsif attribute_changed?(foreign_key) && !new_record? && reflection.constructable?
|
58
|
+
model = reflection.klass
|
59
|
+
foreign_key_was = attribute_was foreign_key
|
60
|
+
foreign_key = attribute foreign_key
|
51
61
|
|
52
62
|
if foreign_key && model.respond_to?(:increment_counter)
|
53
|
-
model.increment_counter(
|
63
|
+
model.increment_counter(cache_column, foreign_key)
|
54
64
|
end
|
55
65
|
if foreign_key_was && model.respond_to?(:decrement_counter)
|
56
|
-
model.decrement_counter(
|
66
|
+
model.decrement_counter(cache_column, foreign_key_was)
|
57
67
|
end
|
58
68
|
end
|
59
69
|
end
|
60
|
-
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.add_counter_cache_callbacks(model, reflection)
|
74
|
+
cache_column = reflection.counter_cache_column
|
75
|
+
|
76
|
+
model.after_create lambda { |record|
|
77
|
+
record.belongs_to_counter_cache_after_create(reflection)
|
78
|
+
}
|
61
79
|
|
62
|
-
model.
|
63
|
-
|
64
|
-
|
80
|
+
model.before_destroy lambda { |record|
|
81
|
+
record.belongs_to_counter_cache_before_destroy(reflection)
|
82
|
+
}
|
83
|
+
|
84
|
+
model.after_update lambda { |record|
|
85
|
+
record.belongs_to_counter_cache_after_update(reflection)
|
86
|
+
}
|
87
|
+
|
88
|
+
klass = reflection.class_name.safe_constantize
|
65
89
|
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
|
66
90
|
end
|
67
91
|
|
68
|
-
def
|
69
|
-
|
70
|
-
def belongs_to_touch_after_save_or_destroy_for_#{name}
|
71
|
-
foreign_key_field = #{reflection.foreign_key.inspect}
|
72
|
-
old_foreign_id = changed_attributes[foreign_key_field]
|
73
|
-
|
74
|
-
if old_foreign_id
|
75
|
-
association = association(:#{name})
|
76
|
-
reflection = association.reflection
|
77
|
-
if reflection.polymorphic?
|
78
|
-
klass = send("#{reflection.foreign_type}_was").constantize
|
79
|
-
else
|
80
|
-
klass = association.klass
|
81
|
-
end
|
82
|
-
old_record = klass.find_by(klass.primary_key => old_foreign_id)
|
92
|
+
def self.touch_record(o, foreign_key, name, touch) # :nodoc:
|
93
|
+
old_foreign_id = o.changed_attributes[foreign_key]
|
83
94
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
95
|
+
if old_foreign_id
|
96
|
+
association = o.association(name)
|
97
|
+
reflection = association.reflection
|
98
|
+
if reflection.polymorphic?
|
99
|
+
klass = o.public_send("#{reflection.foreign_type}_was").constantize
|
100
|
+
else
|
101
|
+
klass = association.klass
|
102
|
+
end
|
103
|
+
old_record = klass.find_by(klass.primary_key => old_foreign_id)
|
88
104
|
|
89
|
-
|
90
|
-
if
|
91
|
-
|
105
|
+
if old_record
|
106
|
+
if touch != true
|
107
|
+
old_record.touch touch
|
108
|
+
else
|
109
|
+
old_record.touch
|
92
110
|
end
|
93
111
|
end
|
94
|
-
|
112
|
+
end
|
95
113
|
|
96
|
-
|
97
|
-
|
98
|
-
|
114
|
+
record = o.send name
|
115
|
+
unless record.nil? || record.new_record?
|
116
|
+
if touch != true
|
117
|
+
record.touch touch
|
118
|
+
else
|
119
|
+
record.touch
|
120
|
+
end
|
121
|
+
end
|
99
122
|
end
|
100
123
|
|
101
|
-
def
|
102
|
-
|
124
|
+
def self.add_touch_callbacks(model, reflection)
|
125
|
+
foreign_key = reflection.foreign_key
|
126
|
+
n = reflection.name
|
127
|
+
touch = reflection.options[:touch]
|
128
|
+
|
129
|
+
callback = lambda { |record|
|
130
|
+
BelongsTo.touch_record(record, foreign_key, n, touch)
|
131
|
+
}
|
132
|
+
|
133
|
+
model.after_save callback
|
134
|
+
model.after_touch callback
|
135
|
+
model.after_destroy callback
|
103
136
|
end
|
104
137
|
end
|
105
138
|
end
|