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
@@ -5,7 +5,7 @@ module ActiveRecord
|
|
5
5
|
attr_reader :join_table
|
6
6
|
|
7
7
|
def initialize(owner, reflection)
|
8
|
-
@join_table = Arel::Table.new(reflection.
|
8
|
+
@join_table = Arel::Table.new(reflection.join_table)
|
9
9
|
super
|
10
10
|
end
|
11
11
|
|
@@ -26,32 +26,34 @@ module ActiveRecord
|
|
26
26
|
join_table[reflection.association_foreign_key] => record.id
|
27
27
|
)
|
28
28
|
|
29
|
-
owner.connection.insert stmt
|
29
|
+
owner.class.connection.insert stmt
|
30
30
|
end
|
31
31
|
|
32
32
|
record
|
33
33
|
end
|
34
34
|
|
35
|
-
# ActiveRecord::Relation#delete_all needs to support joins before we can use a
|
36
|
-
# SQL-only implementation.
|
37
|
-
alias delete_all_on_destroy delete_all
|
38
|
-
|
39
35
|
private
|
40
36
|
|
41
37
|
def count_records
|
42
|
-
load_target.size
|
38
|
+
load_target.reject { |r| r.new_record? }.size
|
43
39
|
end
|
44
40
|
|
45
41
|
def delete_records(records, method)
|
46
42
|
if sql = options[:delete_sql]
|
47
43
|
records = load_target if records == :all
|
48
|
-
records.each { |record| owner.connection.delete(interpolate(sql, record)) }
|
44
|
+
records.each { |record| owner.class.connection.delete(interpolate(sql, record)) }
|
49
45
|
else
|
50
|
-
relation
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
46
|
+
relation = join_table
|
47
|
+
condition = relation[reflection.foreign_key].eq(owner.id)
|
48
|
+
|
49
|
+
unless records == :all
|
50
|
+
condition = condition.and(
|
51
|
+
relation[reflection.association_foreign_key]
|
52
|
+
.in(records.map { |x| x.id }.compact)
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
owner.class.connection.delete(relation.where(condition).compile_delete)
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
@@ -7,6 +7,29 @@ module ActiveRecord
|
|
7
7
|
# is provided by its child HasManyThroughAssociation.
|
8
8
|
class HasManyAssociation < CollectionAssociation #:nodoc:
|
9
9
|
|
10
|
+
def handle_dependency
|
11
|
+
case options[:dependent]
|
12
|
+
when :restrict, :restrict_with_exception
|
13
|
+
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
|
14
|
+
|
15
|
+
when :restrict_with_error
|
16
|
+
unless empty?
|
17
|
+
record = klass.human_attribute_name(reflection.name).downcase
|
18
|
+
owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
else
|
23
|
+
if options[:dependent] == :destroy
|
24
|
+
# No point in executing the counter update since we're going to destroy the parent anyway
|
25
|
+
load_target.each { |t| t.destroyed_by_association = reflection }
|
26
|
+
destroy_all
|
27
|
+
else
|
28
|
+
delete_all
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
10
33
|
def insert_record(record, validate = true, raise = false)
|
11
34
|
set_owner_attributes(record)
|
12
35
|
set_inverse_instance(record)
|
@@ -39,7 +62,7 @@ module ActiveRecord
|
|
39
62
|
elsif options[:counter_sql] || options[:finder_sql]
|
40
63
|
reflection.klass.count_by_sql(custom_counter_sql)
|
41
64
|
else
|
42
|
-
|
65
|
+
scope.count
|
43
66
|
end
|
44
67
|
|
45
68
|
# If there's nothing in the database and @target has no new records
|
@@ -47,18 +70,18 @@ module ActiveRecord
|
|
47
70
|
# documented side-effect of the method that may avoid an extra SELECT.
|
48
71
|
@target ||= [] and loaded! if count == 0
|
49
72
|
|
50
|
-
[
|
73
|
+
[association_scope.limit_value, count].compact.min
|
51
74
|
end
|
52
75
|
|
53
|
-
def has_cached_counter?(reflection =
|
76
|
+
def has_cached_counter?(reflection = reflection())
|
54
77
|
owner.attribute_present?(cached_counter_attribute_name(reflection))
|
55
78
|
end
|
56
79
|
|
57
|
-
def cached_counter_attribute_name(reflection =
|
58
|
-
"#{reflection.name}_count"
|
80
|
+
def cached_counter_attribute_name(reflection = reflection())
|
81
|
+
options[:counter_cache] || "#{reflection.name}_count"
|
59
82
|
end
|
60
83
|
|
61
|
-
def update_counter(difference, reflection =
|
84
|
+
def update_counter(difference, reflection = reflection())
|
62
85
|
if has_cached_counter?(reflection)
|
63
86
|
counter = cached_counter_attribute_name(reflection)
|
64
87
|
owner.class.update_counters(owner.id, counter => difference)
|
@@ -77,7 +100,7 @@ module ActiveRecord
|
|
77
100
|
# it will be decremented twice.
|
78
101
|
#
|
79
102
|
# Hence this method.
|
80
|
-
def inverse_updates_counter_cache?(reflection =
|
103
|
+
def inverse_updates_counter_cache?(reflection = reflection())
|
81
104
|
counter_name = cached_counter_attribute_name(reflection)
|
82
105
|
reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
|
83
106
|
inverse_reflection.counter_cache_column == counter_name
|
@@ -90,7 +113,11 @@ module ActiveRecord
|
|
90
113
|
records.each { |r| r.destroy }
|
91
114
|
update_counter(-records.length) unless inverse_updates_counter_cache?
|
92
115
|
else
|
93
|
-
|
116
|
+
if records == :all
|
117
|
+
scope = self.scope
|
118
|
+
else
|
119
|
+
scope = self.scope.where(reflection.klass.primary_key => records)
|
120
|
+
end
|
94
121
|
|
95
122
|
if method == :delete_all
|
96
123
|
update_counter(-scope.delete_all)
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'active_support/core_ext/object/blank'
|
2
1
|
|
3
2
|
module ActiveRecord
|
4
3
|
# = Active Record Has Many Through Association
|
@@ -30,7 +29,7 @@ module ActiveRecord
|
|
30
29
|
def concat(*records)
|
31
30
|
unless owner.new_record?
|
32
31
|
records.flatten.each do |record|
|
33
|
-
raise_on_type_mismatch(record)
|
32
|
+
raise_on_type_mismatch!(record)
|
34
33
|
record.save! if record.new_record?
|
35
34
|
end
|
36
35
|
end
|
@@ -38,6 +37,20 @@ module ActiveRecord
|
|
38
37
|
super
|
39
38
|
end
|
40
39
|
|
40
|
+
def concat_records(records)
|
41
|
+
ensure_not_nested
|
42
|
+
|
43
|
+
records = super
|
44
|
+
|
45
|
+
if owner.new_record? && records
|
46
|
+
records.flatten.each do |record|
|
47
|
+
build_through_record(record)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
records
|
52
|
+
end
|
53
|
+
|
41
54
|
def insert_record(record, validate = true, raise = false)
|
42
55
|
ensure_not_nested
|
43
56
|
|
@@ -54,10 +67,6 @@ module ActiveRecord
|
|
54
67
|
record
|
55
68
|
end
|
56
69
|
|
57
|
-
# ActiveRecord::Relation#delete_all needs to support joins before we can use a
|
58
|
-
# SQL-only implementation.
|
59
|
-
alias delete_all_on_destroy delete_all
|
60
|
-
|
61
70
|
private
|
62
71
|
|
63
72
|
def through_association
|
@@ -75,22 +84,32 @@ module ActiveRecord
|
|
75
84
|
@through_records[record.object_id] ||= begin
|
76
85
|
ensure_mutable
|
77
86
|
|
78
|
-
through_record = through_association.build
|
87
|
+
through_record = through_association.build(*options_for_through_record)
|
79
88
|
through_record.send("#{source_reflection.name}=", record)
|
80
89
|
through_record
|
81
90
|
end
|
82
91
|
end
|
83
92
|
|
93
|
+
def options_for_through_record
|
94
|
+
[through_scope_attributes]
|
95
|
+
end
|
96
|
+
|
97
|
+
def through_scope_attributes
|
98
|
+
scope.where_values_hash(through_association.reflection.name.to_s).
|
99
|
+
except!(through_association.reflection.foreign_key,
|
100
|
+
through_association.reflection.klass.inheritance_column.to_sym)
|
101
|
+
end
|
102
|
+
|
84
103
|
def save_through_record(record)
|
85
104
|
build_through_record(record).save!
|
86
105
|
ensure
|
87
106
|
@through_records.delete(record.object_id)
|
88
107
|
end
|
89
108
|
|
90
|
-
def build_record(attributes
|
109
|
+
def build_record(attributes)
|
91
110
|
ensure_not_nested
|
92
111
|
|
93
|
-
record = super(attributes
|
112
|
+
record = super(attributes)
|
94
113
|
|
95
114
|
inverse = source_reflection.inverse_of
|
96
115
|
if inverse
|
@@ -105,11 +124,7 @@ module ActiveRecord
|
|
105
124
|
end
|
106
125
|
|
107
126
|
def target_reflection_has_associated_record?
|
108
|
-
|
109
|
-
false
|
110
|
-
else
|
111
|
-
true
|
112
|
-
end
|
127
|
+
!(through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?)
|
113
128
|
end
|
114
129
|
|
115
130
|
def update_through_counter?(method)
|
@@ -126,7 +141,12 @@ module ActiveRecord
|
|
126
141
|
def delete_records(records, method)
|
127
142
|
ensure_not_nested
|
128
143
|
|
129
|
-
|
144
|
+
# This is unoptimised; it will load all the target records
|
145
|
+
# even when we just want to delete everything.
|
146
|
+
records = load_target if records == :all
|
147
|
+
|
148
|
+
scope = through_association.scope
|
149
|
+
scope.where! construct_join_attributes(*records)
|
130
150
|
|
131
151
|
case method
|
132
152
|
when :destroy
|
@@ -139,7 +159,7 @@ module ActiveRecord
|
|
139
159
|
|
140
160
|
delete_through_records(records)
|
141
161
|
|
142
|
-
if source_reflection.options[:counter_cache]
|
162
|
+
if source_reflection.options[:counter_cache] && method != :destroy
|
143
163
|
counter = source_reflection.counter_cache_column
|
144
164
|
klass.decrement_counter counter, records.map(&:id)
|
145
165
|
end
|
@@ -175,7 +195,7 @@ module ActiveRecord
|
|
175
195
|
|
176
196
|
def find_target
|
177
197
|
return [] unless target_reflection_has_associated_record?
|
178
|
-
|
198
|
+
scope.to_a
|
179
199
|
end
|
180
200
|
|
181
201
|
# NOTE - not sure that we can actually cope with inverses here
|
@@ -1,24 +1,44 @@
|
|
1
|
-
require 'active_support/core_ext/object/inclusion'
|
2
1
|
|
3
2
|
module ActiveRecord
|
4
3
|
# = Active Record Belongs To Has One Association
|
5
4
|
module Associations
|
6
5
|
class HasOneAssociation < SingularAssociation #:nodoc:
|
6
|
+
|
7
|
+
def handle_dependency
|
8
|
+
case options[:dependent]
|
9
|
+
when :restrict, :restrict_with_exception
|
10
|
+
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target
|
11
|
+
|
12
|
+
when :restrict_with_error
|
13
|
+
if load_target
|
14
|
+
record = klass.human_attribute_name(reflection.name).downcase
|
15
|
+
owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
else
|
20
|
+
delete
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
7
24
|
def replace(record, save = true)
|
8
|
-
raise_on_type_mismatch(record) if record
|
25
|
+
raise_on_type_mismatch!(record) if record
|
9
26
|
load_target
|
10
27
|
|
11
|
-
|
12
|
-
|
13
|
-
|
28
|
+
return self.target if !(target || record)
|
29
|
+
|
30
|
+
assigning_another_record = target != record
|
31
|
+
if assigning_another_record || record.changed?
|
32
|
+
save &&= owner.persisted?
|
33
|
+
|
14
34
|
transaction_if(save) do
|
15
|
-
remove_target!(options[:dependent]) if target && !target.destroyed?
|
16
|
-
|
35
|
+
remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
|
36
|
+
|
17
37
|
if record
|
18
38
|
set_owner_attributes(record)
|
19
39
|
set_inverse_instance(record)
|
20
|
-
|
21
|
-
if
|
40
|
+
|
41
|
+
if save && !record.save
|
22
42
|
nullify_owner_attributes(record)
|
23
43
|
set_owner_attributes(target) if target
|
24
44
|
raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
|
@@ -38,7 +58,7 @@ module ActiveRecord
|
|
38
58
|
when :destroy
|
39
59
|
target.destroy
|
40
60
|
when :nullify
|
41
|
-
target.
|
61
|
+
target.update_columns(reflection.foreign_key => nil)
|
42
62
|
end
|
43
63
|
end
|
44
64
|
end
|
@@ -54,16 +74,19 @@ module ActiveRecord
|
|
54
74
|
end
|
55
75
|
|
56
76
|
def remove_target!(method)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
77
|
+
case method
|
78
|
+
when :delete
|
79
|
+
target.delete
|
80
|
+
when :destroy
|
81
|
+
target.destroy
|
82
|
+
else
|
83
|
+
nullify_owner_attributes(target)
|
61
84
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
85
|
+
if target.persisted? && owner.persisted? && !target.save
|
86
|
+
set_owner_attributes(target)
|
87
|
+
raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
|
88
|
+
"The record failed to save after its foreign key was set to nil."
|
89
|
+
end
|
67
90
|
end
|
68
91
|
end
|
69
92
|
|
@@ -57,17 +57,17 @@ module ActiveRecord
|
|
57
57
|
other_join_dependency.join_parts.detect do |join_part|
|
58
58
|
case parent
|
59
59
|
when JoinBase
|
60
|
-
parent.
|
60
|
+
parent.base_klass == join_part.base_klass
|
61
61
|
else
|
62
62
|
parent == join_part
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
-
def join_to(
|
67
|
+
def join_to(manager)
|
68
68
|
tables = @tables.dup
|
69
69
|
foreign_table = parent_table
|
70
|
-
foreign_klass = parent.
|
70
|
+
foreign_klass = parent.base_klass
|
71
71
|
|
72
72
|
# The chain starts with the target table, but we want to end with it here (makes
|
73
73
|
# more sense in this context), so we reverse
|
@@ -80,7 +80,7 @@ module ActiveRecord
|
|
80
80
|
foreign_key = reflection.foreign_key
|
81
81
|
when :has_and_belongs_to_many
|
82
82
|
# Join the join table first...
|
83
|
-
|
83
|
+
manager.from(join(
|
84
84
|
table,
|
85
85
|
table[reflection.foreign_key].
|
86
86
|
eq(foreign_table[reflection.active_record_primary_key])
|
@@ -97,22 +97,49 @@ module ActiveRecord
|
|
97
97
|
|
98
98
|
constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
|
99
99
|
|
100
|
-
|
101
|
-
conditions << { reflection.type => foreign_klass.base_class.name } if reflection.type
|
100
|
+
scope_chain_items = scope_chain[i]
|
102
101
|
|
103
|
-
|
104
|
-
|
102
|
+
if reflection.type
|
103
|
+
scope_chain_items += [
|
104
|
+
ActiveRecord::Relation.new(reflection.klass, table)
|
105
|
+
.where(reflection.type => foreign_klass.base_class.name)
|
106
|
+
]
|
105
107
|
end
|
106
108
|
|
107
|
-
|
109
|
+
scope_chain_items += [reflection.klass.send(:build_default_scope, ActiveRecord::Relation.new(reflection.klass, table))].compact
|
110
|
+
|
111
|
+
scope_chain_items.each do |item|
|
112
|
+
unless item.is_a?(Relation)
|
113
|
+
item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item)
|
114
|
+
end
|
115
|
+
|
116
|
+
constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty?
|
117
|
+
end
|
118
|
+
|
119
|
+
manager.from(join(table, constraint))
|
108
120
|
|
109
121
|
# The current table in this iteration becomes the foreign table in the next
|
110
122
|
foreign_table, foreign_klass = table, reflection.klass
|
111
123
|
end
|
112
124
|
|
113
|
-
|
125
|
+
manager
|
114
126
|
end
|
115
127
|
|
128
|
+
# Builds equality condition.
|
129
|
+
#
|
130
|
+
# Example:
|
131
|
+
#
|
132
|
+
# class Physician < ActiveRecord::Base
|
133
|
+
# has_many :appointments
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# If I execute `Physician.joins(:appointments).to_a` then
|
137
|
+
# reflection #=> #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
|
138
|
+
# table #=> #<Arel::Table @name="appointments" ...>
|
139
|
+
# key #=> physician_id
|
140
|
+
# foreign_table #=> #<Arel::Table @name="physicians" ...>
|
141
|
+
# foreign_key #=> id
|
142
|
+
#
|
116
143
|
def build_constraint(reflection, table, key, foreign_table, foreign_key)
|
117
144
|
constraint = table[key].eq(foreign_table[foreign_key])
|
118
145
|
|
@@ -139,18 +166,8 @@ module ActiveRecord
|
|
139
166
|
table.table_alias || table.name
|
140
167
|
end
|
141
168
|
|
142
|
-
def
|
143
|
-
@
|
144
|
-
end
|
145
|
-
|
146
|
-
private
|
147
|
-
|
148
|
-
def interpolate(conditions)
|
149
|
-
if conditions.respond_to?(:to_proc)
|
150
|
-
instance_eval(&conditions)
|
151
|
-
else
|
152
|
-
conditions
|
153
|
-
end
|
169
|
+
def scope_chain
|
170
|
+
@scope_chain ||= reflection.scope_chain.reverse
|
154
171
|
end
|
155
172
|
|
156
173
|
end
|
@@ -4,7 +4,7 @@ module ActiveRecord
|
|
4
4
|
class JoinBase < JoinPart # :nodoc:
|
5
5
|
def ==(other)
|
6
6
|
other.class == self.class &&
|
7
|
-
other.
|
7
|
+
other.base_klass == base_klass
|
8
8
|
end
|
9
9
|
|
10
10
|
def aliased_prefix
|
@@ -16,7 +16,7 @@ module ActiveRecord
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def aliased_table_name
|
19
|
-
|
19
|
+
base_klass.table_name
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
3
|
class JoinDependency # :nodoc:
|
4
|
-
# A JoinPart represents a part of a JoinDependency. It is
|
4
|
+
# A JoinPart represents a part of a JoinDependency. It is inherited
|
5
5
|
# by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
|
6
6
|
# everything else is being joined onto. A JoinAssociation represents an association which
|
7
7
|
# is joining to the base. A JoinAssociation may result in more than one actual join
|
@@ -11,12 +11,12 @@ module ActiveRecord
|
|
11
11
|
# The Active Record class which this join part is associated 'about'; for a JoinBase
|
12
12
|
# this is the actual base model, for a JoinAssociation this is the target model of the
|
13
13
|
# association.
|
14
|
-
attr_reader :
|
14
|
+
attr_reader :base_klass
|
15
15
|
|
16
|
-
delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :
|
16
|
+
delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :base_klass
|
17
17
|
|
18
|
-
def initialize(
|
19
|
-
@
|
18
|
+
def initialize(base_klass)
|
19
|
+
@base_klass = base_klass
|
20
20
|
@cached_record = {}
|
21
21
|
@column_names_with_alias = nil
|
22
22
|
end
|
@@ -54,7 +54,7 @@ module ActiveRecord
|
|
54
54
|
unless @column_names_with_alias
|
55
55
|
@column_names_with_alias = []
|
56
56
|
|
57
|
-
([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
|
57
|
+
([primary_key] + (column_names - [primary_key])).compact.each_with_index do |column_name, i|
|
58
58
|
@column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
|
59
59
|
end
|
60
60
|
end
|
@@ -62,7 +62,20 @@ module ActiveRecord
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def extract_record(row)
|
65
|
-
|
65
|
+
# This code is performance critical as it is called per row.
|
66
|
+
# see: https://github.com/rails/rails/pull/12185
|
67
|
+
hash = {}
|
68
|
+
|
69
|
+
index = 0
|
70
|
+
length = column_names_with_alias.length
|
71
|
+
|
72
|
+
while index < length
|
73
|
+
column_name, alias_name = column_names_with_alias[index]
|
74
|
+
hash[column_name] = row[alias_name]
|
75
|
+
index += 1
|
76
|
+
end
|
77
|
+
|
78
|
+
hash
|
66
79
|
end
|
67
80
|
|
68
81
|
def record_id(row)
|
@@ -70,7 +83,7 @@ module ActiveRecord
|
|
70
83
|
end
|
71
84
|
|
72
85
|
def instantiate(row)
|
73
|
-
@cached_record[record_id(row)] ||=
|
86
|
+
@cached_record[record_id(row)] ||= base_klass.instantiate(extract_record(row))
|
74
87
|
end
|
75
88
|
end
|
76
89
|
end
|
@@ -5,10 +5,31 @@ module ActiveRecord
|
|
5
5
|
autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
|
6
6
|
autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
|
7
7
|
|
8
|
-
attr_reader :join_parts, :reflections, :alias_tracker, :
|
9
|
-
|
8
|
+
attr_reader :join_parts, :reflections, :alias_tracker, :base_klass
|
9
|
+
|
10
|
+
# base is the base class on which operation is taking place.
|
11
|
+
# associations is the list of associations which are joined using hash, symbol or array.
|
12
|
+
# joins is the list of all string join commnads and arel nodes.
|
13
|
+
#
|
14
|
+
# Example :
|
15
|
+
#
|
16
|
+
# class Physician < ActiveRecord::Base
|
17
|
+
# has_many :appointments
|
18
|
+
# has_many :patients, through: :appointments
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# If I execute `@physician.patients.to_a` then
|
22
|
+
# base #=> Physician
|
23
|
+
# associations #=> []
|
24
|
+
# joins #=> [#<Arel::Nodes::InnerJoin: ...]
|
25
|
+
#
|
26
|
+
# However if I execute `Physician.joins(:appointments).to_a` then
|
27
|
+
# base #=> Physician
|
28
|
+
# associations #=> [:appointments]
|
29
|
+
# joins #=> []
|
30
|
+
#
|
10
31
|
def initialize(base, associations, joins)
|
11
|
-
@
|
32
|
+
@base_klass = base
|
12
33
|
@table_joins = joins
|
13
34
|
@join_parts = [JoinBase.new(base)]
|
14
35
|
@associations = {}
|
@@ -54,10 +75,12 @@ module ActiveRecord
|
|
54
75
|
parent
|
55
76
|
}.uniq
|
56
77
|
|
57
|
-
remove_duplicate_results!(
|
78
|
+
remove_duplicate_results!(base_klass, records, @associations)
|
58
79
|
records
|
59
80
|
end
|
60
81
|
|
82
|
+
protected
|
83
|
+
|
61
84
|
def remove_duplicate_results!(base, records, associations)
|
62
85
|
case associations
|
63
86
|
when Symbol, String
|
@@ -68,7 +91,7 @@ module ActiveRecord
|
|
68
91
|
remove_duplicate_results!(base, records, association)
|
69
92
|
end
|
70
93
|
when Hash
|
71
|
-
associations.
|
94
|
+
associations.each_key do |name|
|
72
95
|
reflection = base.reflections[name]
|
73
96
|
remove_uniq_by_reflection(reflection, records)
|
74
97
|
|
@@ -88,8 +111,6 @@ module ActiveRecord
|
|
88
111
|
end
|
89
112
|
end
|
90
113
|
|
91
|
-
protected
|
92
|
-
|
93
114
|
def cache_joined_association(association)
|
94
115
|
associations = []
|
95
116
|
parent = association.parent
|
@@ -108,8 +129,8 @@ module ActiveRecord
|
|
108
129
|
parent ||= join_parts.last
|
109
130
|
case associations
|
110
131
|
when Symbol, String
|
111
|
-
reflection = parent.reflections[associations.
|
112
|
-
raise ConfigurationError, "Association named '#{ associations }' was not found on #{parent.
|
132
|
+
reflection = parent.reflections[associations.intern] or
|
133
|
+
raise ConfigurationError, "Association named '#{ associations }' was not found on #{ parent.base_klass.name }; perhaps you misspelled it?"
|
113
134
|
unless join_association = find_join_association(reflection, parent)
|
114
135
|
@reflections << reflection
|
115
136
|
join_association = build_join_association(reflection, parent)
|
@@ -19,7 +19,7 @@ module ActiveRecord
|
|
19
19
|
|
20
20
|
if reflection.source_macro == :has_and_belongs_to_many
|
21
21
|
tables << alias_tracker.aliased_table_for(
|
22
|
-
(reflection.source_reflection || reflection).
|
22
|
+
(reflection.source_reflection || reflection).join_table,
|
23
23
|
table_alias_for(reflection, true)
|
24
24
|
)
|
25
25
|
end
|
@@ -40,16 +40,6 @@ module ActiveRecord
|
|
40
40
|
def join(table, constraint)
|
41
41
|
table.create_join(table, table.create_on(constraint), join_type)
|
42
42
|
end
|
43
|
-
|
44
|
-
def sanitize(conditions, table)
|
45
|
-
conditions = conditions.map do |condition|
|
46
|
-
condition = active_record.send(:sanitize_sql, interpolate(condition), table.table_alias || table.name)
|
47
|
-
condition = Arel.sql(condition) unless condition.is_a?(Arel::Node)
|
48
|
-
condition
|
49
|
-
end
|
50
|
-
|
51
|
-
conditions.length == 1 ? conditions.first : Arel::Nodes::And.new(conditions)
|
52
|
-
end
|
53
43
|
end
|
54
44
|
end
|
55
45
|
end
|