activerecord 3.2.22.4 → 4.0.13
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2799 -617
- data/MIT-LICENSE +1 -1
- data/README.rdoc +23 -32
- data/examples/performance.rb +1 -1
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/association_relation.rb +22 -0
- data/lib/active_record/associations/alias_tracker.rb +4 -2
- data/lib/active_record/associations/association.rb +60 -46
- data/lib/active_record/associations/association_scope.rb +46 -40
- data/lib/active_record/associations/belongs_to_association.rb +17 -4
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +73 -56
- data/lib/active_record/associations/builder/collection_association.rb +54 -40
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +13 -50
- data/lib/active_record/associations/builder/singular_association.rb +13 -13
- data/lib/active_record/associations/collection_association.rb +130 -96
- data/lib/active_record/associations/collection_proxy.rb +916 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
- data/lib/active_record/associations/has_many_association.rb +35 -8
- data/lib/active_record/associations/has_many_through_association.rb +37 -17
- data/lib/active_record/associations/has_one_association.rb +42 -19
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
- data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
- data/lib/active_record/associations/join_dependency.rb +30 -9
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader/association.rb +29 -33
- data/lib/active_record/associations/preloader/collection_association.rb +1 -1
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/through_association.rb +13 -17
- data/lib/active_record/associations/preloader.rb +20 -43
- data/lib/active_record/associations/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +3 -3
- data/lib/active_record/associations.rb +223 -282
- data/lib/active_record/attribute_assignment.rb +134 -154
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +36 -29
- data/lib/active_record/attribute_methods/primary_key.rb +45 -31
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +67 -90
- data/lib/active_record/attribute_methods/serialization.rb +133 -70
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
- data/lib/active_record/attribute_methods/write.rb +34 -39
- data/lib/active_record/attribute_methods.rb +268 -108
- data/lib/active_record/autosave_association.rb +80 -73
- data/lib/active_record/base.rb +54 -451
- data/lib/active_record/callbacks.rb +60 -22
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
- data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
- data/lib/active_record/connection_adapters/column.rb +67 -36
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
- data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
- data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +472 -0
- data/lib/active_record/counter_cache.rb +107 -108
- data/lib/active_record/dynamic_matchers.rb +115 -63
- data/lib/active_record/errors.rb +36 -18
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +8 -4
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +159 -155
- data/lib/active_record/inheritance.rb +93 -59
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +39 -43
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration/command_recorder.rb +102 -33
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +411 -173
- data/lib/active_record/model_schema.rb +81 -94
- data/lib/active_record/nested_attributes.rb +173 -131
- data/lib/active_record/null_relation.rb +67 -0
- data/lib/active_record/persistence.rb +254 -106
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +113 -38
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +4 -3
- data/lib/active_record/railties/databases.rake +115 -368
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +7 -3
- data/lib/active_record/reflection.rb +110 -61
- data/lib/active_record/relation/batches.rb +29 -29
- data/lib/active_record/relation/calculations.rb +155 -125
- data/lib/active_record/relation/delegation.rb +94 -18
- data/lib/active_record/relation/finder_methods.rb +151 -203
- data/lib/active_record/relation/merger.rb +188 -0
- data/lib/active_record/relation/predicate_builder.rb +85 -42
- data/lib/active_record/relation/query_methods.rb +793 -146
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/relation.rb +293 -173
- data/lib/active_record/result.rb +48 -7
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +41 -54
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +41 -41
- data/lib/active_record/schema_migration.rb +46 -0
- data/lib/active_record/scoping/default.rb +56 -52
- data/lib/active_record/scoping/named.rb +78 -103
- data/lib/active_record/scoping.rb +54 -124
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +131 -15
- data/lib/active_record/tasks/database_tasks.rb +204 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +67 -38
- data/lib/active_record/timestamp.rb +16 -11
- data/lib/active_record/transactions.rb +73 -51
- data/lib/active_record/validations/associated.rb +19 -13
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +110 -57
- data/lib/active_record/validations.rb +18 -17
- data/lib/active_record/version.rb +7 -6
- data/lib/active_record.rb +63 -45
- data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
- data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -5
- metadata +43 -29
- data/examples/associations.png +0 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -360
- data/lib/rails/generators/active_record/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -6,7 +6,7 @@ module ActiveRecord
|
|
6
6
|
attr_reader :association, :alias_tracker
|
7
7
|
|
8
8
|
delegate :klass, :owner, :reflection, :interpolate, :to => :association
|
9
|
-
delegate :chain, :
|
9
|
+
delegate :chain, :scope_chain, :options, :source_options, :active_record, :to => :reflection
|
10
10
|
|
11
11
|
def initialize(association)
|
12
12
|
@association = association
|
@@ -15,23 +15,28 @@ module ActiveRecord
|
|
15
15
|
|
16
16
|
def scope
|
17
17
|
scope = klass.unscoped
|
18
|
-
scope
|
19
|
-
|
20
|
-
|
21
|
-
# association supports that option; this is enforced by the association builder.
|
22
|
-
scope = scope.apply_finder_options(options.slice(
|
23
|
-
:readonly, :include, :order, :limit, :joins, :group, :having, :offset, :select))
|
18
|
+
scope.extending! Array(options[:extend])
|
19
|
+
add_constraints(scope)
|
20
|
+
end
|
24
21
|
|
25
|
-
|
26
|
-
scope = scope.includes(source_options[:include])
|
27
|
-
end
|
22
|
+
private
|
28
23
|
|
29
|
-
|
24
|
+
def column_for(table_name, column_name)
|
25
|
+
columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
|
26
|
+
columns[column_name]
|
27
|
+
end
|
30
28
|
|
31
|
-
|
29
|
+
def bind_value(scope, column, value)
|
30
|
+
substitute = alias_tracker.connection.substitute_at(
|
31
|
+
column, scope.bind_values.length)
|
32
|
+
scope.bind_values += [[column, value]]
|
33
|
+
substitute
|
32
34
|
end
|
33
35
|
|
34
|
-
|
36
|
+
def bind(scope, table_name, column_name, value)
|
37
|
+
column = column_for table_name, column_name
|
38
|
+
bind_value scope, column, value
|
39
|
+
end
|
35
40
|
|
36
41
|
def add_constraints(scope)
|
37
42
|
tables = construct_tables
|
@@ -53,7 +58,7 @@ module ActiveRecord
|
|
53
58
|
|
54
59
|
if reflection.source_macro == :belongs_to
|
55
60
|
if reflection.options[:polymorphic]
|
56
|
-
key = reflection.association_primary_key(klass)
|
61
|
+
key = reflection.association_primary_key(self.klass)
|
57
62
|
else
|
58
63
|
key = reflection.association_primary_key
|
59
64
|
end
|
@@ -64,21 +69,14 @@ module ActiveRecord
|
|
64
69
|
foreign_key = reflection.active_record_primary_key
|
65
70
|
end
|
66
71
|
|
67
|
-
conditions = self.conditions[i]
|
68
|
-
|
69
72
|
if reflection == chain.last
|
70
|
-
|
73
|
+
bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key]
|
74
|
+
scope = scope.where(table[key].eq(bind_val))
|
71
75
|
|
72
76
|
if reflection.type
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
conditions.each do |condition|
|
77
|
-
if options[:through] && condition.is_a?(Hash)
|
78
|
-
condition = disambiguate_condition(table, condition)
|
79
|
-
end
|
80
|
-
|
81
|
-
scope = scope.where(interpolate(condition))
|
77
|
+
value = owner.class.base_class.name
|
78
|
+
bind_val = bind scope, table.table_name, reflection.type.to_s, value
|
79
|
+
scope = scope.where(table[reflection.type].eq(bind_val))
|
82
80
|
end
|
83
81
|
else
|
84
82
|
constraint = table[key].eq(foreign_table[foreign_key])
|
@@ -89,10 +87,26 @@ module ActiveRecord
|
|
89
87
|
end
|
90
88
|
|
91
89
|
scope = scope.joins(join(foreign_table, constraint))
|
90
|
+
end
|
91
|
+
|
92
|
+
is_first_chain = i == 0
|
93
|
+
klass = is_first_chain ? self.klass : reflection.klass
|
92
94
|
|
93
|
-
|
94
|
-
|
95
|
+
# Exclude the scope of the association itself, because that
|
96
|
+
# was already merged in the #scope method.
|
97
|
+
scope_chain[i].each do |scope_chain_item|
|
98
|
+
item = eval_scope(klass, scope_chain_item)
|
99
|
+
|
100
|
+
if scope_chain_item == self.reflection.scope
|
101
|
+
scope.merge! item.except(:where, :includes)
|
102
|
+
end
|
103
|
+
|
104
|
+
if is_first_chain
|
105
|
+
scope.includes! item.includes_values
|
95
106
|
end
|
107
|
+
|
108
|
+
scope.where_values += item.where_values
|
109
|
+
scope.order_values |= item.order_values
|
96
110
|
end
|
97
111
|
end
|
98
112
|
|
@@ -114,19 +128,11 @@ module ActiveRecord
|
|
114
128
|
end
|
115
129
|
end
|
116
130
|
|
117
|
-
def
|
118
|
-
if
|
119
|
-
|
120
|
-
condition.map do |k, v|
|
121
|
-
if v.is_a?(Hash)
|
122
|
-
[k, v]
|
123
|
-
else
|
124
|
-
[table.table_alias || table.name, { k => v }]
|
125
|
-
end
|
126
|
-
end
|
127
|
-
]
|
131
|
+
def eval_scope(klass, scope)
|
132
|
+
if scope.is_a?(Relation)
|
133
|
+
scope
|
128
134
|
else
|
129
|
-
|
135
|
+
klass.unscoped.instance_exec(owner, &scope)
|
130
136
|
end
|
131
137
|
end
|
132
138
|
end
|
@@ -1,9 +1,14 @@
|
|
1
1
|
module ActiveRecord
|
2
|
-
# = Active Record Belongs To
|
2
|
+
# = Active Record Belongs To Association
|
3
3
|
module Associations
|
4
4
|
class BelongsToAssociation < SingularAssociation #:nodoc:
|
5
|
+
|
6
|
+
def handle_dependency
|
7
|
+
target.send(options[:dependent]) if load_target
|
8
|
+
end
|
9
|
+
|
5
10
|
def replace(record)
|
6
|
-
raise_on_type_mismatch(record) if record
|
11
|
+
raise_on_type_mismatch!(record) if record
|
7
12
|
|
8
13
|
update_counters(record)
|
9
14
|
replace_keys(record)
|
@@ -14,6 +19,11 @@ module ActiveRecord
|
|
14
19
|
self.target = record
|
15
20
|
end
|
16
21
|
|
22
|
+
def reset
|
23
|
+
super
|
24
|
+
@updated = false
|
25
|
+
end
|
26
|
+
|
17
27
|
def updated?
|
18
28
|
@updated
|
19
29
|
end
|
@@ -40,8 +50,11 @@ module ActiveRecord
|
|
40
50
|
|
41
51
|
# Checks whether record is different to the current target, without loading it
|
42
52
|
def different_target?(record)
|
43
|
-
record.nil?
|
44
|
-
|
53
|
+
if record.nil?
|
54
|
+
owner[reflection.foreign_key]
|
55
|
+
else
|
56
|
+
record.id != owner[reflection.foreign_key]
|
57
|
+
end
|
45
58
|
end
|
46
59
|
|
47
60
|
def replace_keys(record)
|
@@ -1,55 +1,108 @@
|
|
1
1
|
module ActiveRecord::Associations::Builder
|
2
2
|
class Association #:nodoc:
|
3
|
-
|
4
|
-
|
3
|
+
class << self
|
4
|
+
attr_accessor :valid_options
|
5
|
+
end
|
5
6
|
|
6
|
-
|
7
|
-
class_attribute :macro
|
7
|
+
self.valid_options = [:class_name, :foreign_key, :validate]
|
8
8
|
|
9
|
-
attr_reader :model, :name, :options, :reflection
|
9
|
+
attr_reader :model, :name, :scope, :options, :reflection
|
10
10
|
|
11
|
-
def self.build(
|
12
|
-
new(
|
11
|
+
def self.build(*args, &block)
|
12
|
+
new(*args, &block).build
|
13
13
|
end
|
14
14
|
|
15
|
-
def initialize(model, name, options)
|
16
|
-
|
15
|
+
def initialize(model, name, scope, options)
|
16
|
+
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
|
17
|
+
|
18
|
+
@model = model
|
19
|
+
@name = name
|
20
|
+
|
21
|
+
if scope.is_a?(Hash)
|
22
|
+
@scope = nil
|
23
|
+
@options = scope
|
24
|
+
else
|
25
|
+
@scope = scope
|
26
|
+
@options = options
|
27
|
+
end
|
28
|
+
|
29
|
+
if @scope && @scope.arity == 0
|
30
|
+
prev_scope = @scope
|
31
|
+
@scope = proc { instance_exec(&prev_scope) }
|
32
|
+
end
|
17
33
|
end
|
18
34
|
|
19
35
|
def mixin
|
20
36
|
@model.generated_feature_methods
|
21
37
|
end
|
22
38
|
|
39
|
+
include Module.new { def build; end }
|
40
|
+
|
23
41
|
def build
|
24
42
|
validate_options
|
25
|
-
reflection = model.create_reflection(self.class.macro, name, options, model)
|
26
43
|
define_accessors
|
27
|
-
|
44
|
+
configure_dependency if options[:dependent]
|
45
|
+
@reflection = model.create_reflection(macro, name, scope, options, model)
|
46
|
+
super # provides an extension point
|
47
|
+
@reflection
|
28
48
|
end
|
29
49
|
|
30
|
-
|
50
|
+
def macro
|
51
|
+
raise NotImplementedError
|
52
|
+
end
|
31
53
|
|
32
|
-
|
33
|
-
|
34
|
-
|
54
|
+
def valid_options
|
55
|
+
Association.valid_options
|
56
|
+
end
|
35
57
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
58
|
+
def validate_options
|
59
|
+
options.assert_valid_keys(valid_options)
|
60
|
+
end
|
61
|
+
|
62
|
+
def define_accessors
|
63
|
+
define_readers
|
64
|
+
define_writers
|
65
|
+
end
|
40
66
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
association(name).reader(*
|
67
|
+
def define_readers
|
68
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
69
|
+
def #{name}(*args)
|
70
|
+
association(:#{name}).reader(*args)
|
45
71
|
end
|
46
|
-
|
72
|
+
CODE
|
73
|
+
end
|
47
74
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
association(name).writer(value)
|
75
|
+
def define_writers
|
76
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
77
|
+
def #{name}=(value)
|
78
|
+
association(:#{name}).writer(value)
|
52
79
|
end
|
80
|
+
CODE
|
81
|
+
end
|
82
|
+
|
83
|
+
def configure_dependency
|
84
|
+
unless valid_dependent_options.include? options[:dependent]
|
85
|
+
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{options[:dependent]}"
|
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
|
+
)
|
53
93
|
end
|
94
|
+
|
95
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
96
|
+
def #{macro}_dependent_for_#{name}
|
97
|
+
association(:#{name}).handle_dependency
|
98
|
+
end
|
99
|
+
CODE
|
100
|
+
|
101
|
+
model.before_destroy "#{macro}_dependent_for_#{name}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def valid_dependent_options
|
105
|
+
raise NotImplementedError
|
106
|
+
end
|
54
107
|
end
|
55
108
|
end
|
@@ -1,10 +1,12 @@
|
|
1
|
-
require 'active_support/core_ext/object/inclusion'
|
2
|
-
|
3
1
|
module ActiveRecord::Associations::Builder
|
4
2
|
class BelongsTo < SingularAssociation #:nodoc:
|
5
|
-
|
3
|
+
def macro
|
4
|
+
:belongs_to
|
5
|
+
end
|
6
6
|
|
7
|
-
|
7
|
+
def valid_options
|
8
|
+
super + [:foreign_type, :polymorphic, :touch, :counter_cache]
|
9
|
+
end
|
8
10
|
|
9
11
|
def constructable?
|
10
12
|
!options[:polymorphic]
|
@@ -14,75 +16,90 @@ module ActiveRecord::Associations::Builder
|
|
14
16
|
reflection = super
|
15
17
|
add_counter_cache_callbacks(reflection) if options[:counter_cache]
|
16
18
|
add_touch_callbacks(reflection) if options[:touch]
|
17
|
-
configure_dependency
|
18
19
|
reflection
|
19
20
|
end
|
20
21
|
|
21
|
-
|
22
|
+
def add_counter_cache_callbacks(reflection)
|
23
|
+
cache_column = reflection.counter_cache_column
|
24
|
+
foreign_key = reflection.foreign_key
|
25
|
+
klass = reflection.class_name.safe_constantize
|
22
26
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
record = send(name)
|
30
|
-
record.class.increment_counter(cache_column, record.id) unless record.nil?
|
27
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
28
|
+
def belongs_to_counter_cache_after_create_for_#{name}
|
29
|
+
if record = #{name}
|
30
|
+
record.class.increment_counter(:#{cache_column}, record.id)
|
31
|
+
@_after_create_counter_called = true
|
32
|
+
end
|
31
33
|
end
|
32
|
-
model.after_create(method_name)
|
33
|
-
|
34
|
-
method_name = "belongs_to_counter_cache_before_destroy_for_#{name}"
|
35
|
-
mixin.redefine_method(method_name) do
|
36
|
-
record = send(name)
|
37
34
|
|
38
|
-
|
39
|
-
|
35
|
+
def belongs_to_counter_cache_before_destroy_for_#{name}
|
36
|
+
unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == #{foreign_key.to_sym.inspect}
|
37
|
+
record = #{name}
|
38
|
+
if record && !self.destroyed?
|
39
|
+
record.class.decrement_counter(:#{cache_column}, record.id)
|
40
|
+
end
|
40
41
|
end
|
41
42
|
end
|
42
|
-
model.before_destroy(method_name)
|
43
|
-
|
44
|
-
model.send(:module_eval,
|
45
|
-
"#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)", __FILE__, __LINE__
|
46
|
-
)
|
47
|
-
end
|
48
|
-
|
49
|
-
def add_touch_callbacks(reflection)
|
50
|
-
name = self.name
|
51
|
-
method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
|
52
|
-
touch = options[:touch]
|
53
43
|
|
54
|
-
|
55
|
-
|
44
|
+
def belongs_to_counter_cache_after_update_for_#{name}
|
45
|
+
if (@_after_create_counter_called ||= false)
|
46
|
+
@_after_create_counter_called = false
|
47
|
+
elsif self.#{foreign_key}_changed? && !new_record? && #{constructable?}
|
48
|
+
model = #{klass}
|
49
|
+
foreign_key_was = self.#{foreign_key}_was
|
50
|
+
foreign_key = self.#{foreign_key}
|
56
51
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
52
|
+
if foreign_key && model.respond_to?(:increment_counter)
|
53
|
+
model.increment_counter(:#{cache_column}, foreign_key)
|
54
|
+
end
|
55
|
+
if foreign_key_was && model.respond_to?(:decrement_counter)
|
56
|
+
model.decrement_counter(:#{cache_column}, foreign_key_was)
|
62
57
|
end
|
63
58
|
end
|
64
59
|
end
|
60
|
+
CODE
|
65
61
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
62
|
+
model.after_create "belongs_to_counter_cache_after_create_for_#{name}"
|
63
|
+
model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}"
|
64
|
+
model.after_update "belongs_to_counter_cache_after_update_for_#{name}"
|
65
|
+
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
|
66
|
+
end
|
70
67
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
68
|
+
def add_touch_callbacks(reflection)
|
69
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
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)
|
76
83
|
|
77
|
-
|
78
|
-
|
79
|
-
def #{method_name}
|
80
|
-
association = #{name}
|
81
|
-
association.#{options[:dependent]} if association
|
84
|
+
if old_record
|
85
|
+
old_record.touch #{options[:touch].inspect if options[:touch] != true}
|
82
86
|
end
|
83
|
-
|
84
|
-
|
87
|
+
end
|
88
|
+
|
89
|
+
record = #{name}
|
90
|
+
if record && record.persisted?
|
91
|
+
record.touch #{options[:touch].inspect if options[:touch] != true}
|
92
|
+
end
|
85
93
|
end
|
86
|
-
|
94
|
+
CODE
|
95
|
+
|
96
|
+
model.after_save "belongs_to_touch_after_save_or_destroy_for_#{name}"
|
97
|
+
model.after_touch "belongs_to_touch_after_save_or_destroy_for_#{name}"
|
98
|
+
model.after_destroy "belongs_to_touch_after_save_or_destroy_for_#{name}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def valid_dependent_options
|
102
|
+
[:destroy, :delete]
|
103
|
+
end
|
87
104
|
end
|
88
105
|
end
|
@@ -1,24 +1,24 @@
|
|
1
|
+
require 'active_record/associations'
|
2
|
+
|
1
3
|
module ActiveRecord::Associations::Builder
|
2
4
|
class CollectionAssociation < Association #:nodoc:
|
3
|
-
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
|
4
|
-
|
5
|
-
self.valid_options += [
|
6
|
-
:table_name, :order, :group, :having, :limit, :offset, :uniq, :finder_sql,
|
7
|
-
:counter_sql, :before_add, :after_add, :before_remove, :after_remove
|
8
|
-
]
|
9
5
|
|
10
|
-
|
6
|
+
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
|
11
7
|
|
12
|
-
def
|
13
|
-
|
8
|
+
def valid_options
|
9
|
+
super + [:table_name, :finder_sql, :counter_sql, :before_add,
|
10
|
+
:after_add, :before_remove, :after_remove, :extend]
|
14
11
|
end
|
15
12
|
|
16
|
-
|
17
|
-
|
13
|
+
attr_reader :block_extension, :extension_module
|
14
|
+
|
15
|
+
def initialize(*args, &extension)
|
16
|
+
super(*args)
|
18
17
|
@block_extension = extension
|
19
18
|
end
|
20
19
|
|
21
20
|
def build
|
21
|
+
show_deprecation_warnings
|
22
22
|
wrap_block_extension
|
23
23
|
reflection = super
|
24
24
|
CALLBACKS.each { |callback_name| define_callback(callback_name) }
|
@@ -29,47 +29,61 @@ module ActiveRecord::Associations::Builder
|
|
29
29
|
true
|
30
30
|
end
|
31
31
|
|
32
|
-
|
32
|
+
def show_deprecation_warnings
|
33
|
+
[:finder_sql, :counter_sql].each do |name|
|
34
|
+
if options.include? name
|
35
|
+
ActiveSupport::Deprecation.warn("The :#{name} association option is deprecated. Please find an alternative (such as using scopes).")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def wrap_block_extension
|
41
|
+
if block_extension
|
42
|
+
@extension_module = mod = Module.new(&block_extension)
|
43
|
+
silence_warnings do
|
44
|
+
model.parent.const_set(extension_module_name, mod)
|
45
|
+
end
|
33
46
|
|
34
|
-
|
35
|
-
options[:extend] = Array.wrap(options[:extend])
|
47
|
+
prev_scope = @scope
|
36
48
|
|
37
|
-
if
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
options[:extend].push("#{model.parent}::#{extension_module_name}".constantize)
|
49
|
+
if prev_scope
|
50
|
+
@scope = proc { |owner| instance_exec(owner, &prev_scope).extending(mod) }
|
51
|
+
else
|
52
|
+
@scope = proc { extending(mod) }
|
42
53
|
end
|
43
54
|
end
|
55
|
+
end
|
44
56
|
|
45
|
-
|
46
|
-
|
47
|
-
|
57
|
+
def extension_module_name
|
58
|
+
@extension_module_name ||= "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
|
59
|
+
end
|
48
60
|
|
49
|
-
|
50
|
-
|
61
|
+
def define_callback(callback_name)
|
62
|
+
full_callback_name = "#{callback_name}_for_#{name}"
|
51
63
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
64
|
+
# TODO : why do i need method_defined? I think its because of the inheritance chain
|
65
|
+
model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
|
66
|
+
model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
|
67
|
+
end
|
56
68
|
|
57
|
-
|
58
|
-
|
69
|
+
def define_readers
|
70
|
+
super
|
59
71
|
|
60
|
-
|
61
|
-
|
62
|
-
association(name).ids_reader
|
72
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
73
|
+
def #{name.to_s.singularize}_ids
|
74
|
+
association(:#{name}).ids_reader
|
63
75
|
end
|
64
|
-
|
76
|
+
CODE
|
77
|
+
end
|
65
78
|
|
66
|
-
|
67
|
-
|
79
|
+
def define_writers
|
80
|
+
super
|
68
81
|
|
69
|
-
|
70
|
-
|
71
|
-
association(name).ids_writer(ids)
|
82
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
83
|
+
def #{name.to_s.singularize}_ids=(ids)
|
84
|
+
association(:#{name}).ids_writer(ids)
|
72
85
|
end
|
73
|
-
|
86
|
+
CODE
|
87
|
+
end
|
74
88
|
end
|
75
89
|
end
|