activerecord 3.2.22.4 → 4.0.13
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 +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
|