activerecord 4.0.4 → 4.1.16
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 +1632 -1797
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/examples/performance.rb +30 -18
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/association_relation.rb +4 -0
- data/lib/active_record/associations/alias_tracker.rb +49 -29
- data/lib/active_record/associations/association.rb +9 -17
- data/lib/active_record/associations/association_scope.rb +59 -49
- data/lib/active_record/associations/belongs_to_association.rb +34 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +6 -1
- data/lib/active_record/associations/builder/association.rb +84 -54
- data/lib/active_record/associations/builder/belongs_to.rb +90 -58
- data/lib/active_record/associations/builder/collection_association.rb +47 -45
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +119 -25
- data/lib/active_record/associations/builder/has_many.rb +3 -3
- 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 +121 -111
- data/lib/active_record/associations/collection_proxy.rb +73 -18
- data/lib/active_record/associations/has_many_association.rb +14 -11
- data/lib/active_record/associations/has_many_through_association.rb +33 -6
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +46 -104
- 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_dependency.rb +208 -168
- data/lib/active_record/associations/preloader/association.rb +69 -27
- 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/preloader.rb +63 -49
- data/lib/active_record/associations/singular_association.rb +6 -5
- data/lib/active_record/associations/through_association.rb +30 -9
- data/lib/active_record/associations.rb +116 -42
- data/lib/active_record/attribute_assignment.rb +6 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
- data/lib/active_record/attribute_methods/dirty.rb +35 -26
- data/lib/active_record/attribute_methods/primary_key.rb +8 -1
- data/lib/active_record/attribute_methods/read.rb +56 -29
- data/lib/active_record/attribute_methods/serialization.rb +44 -12
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +13 -1
- data/lib/active_record/attribute_methods/write.rb +59 -26
- data/lib/active_record/attribute_methods.rb +82 -43
- data/lib/active_record/autosave_association.rb +209 -194
- data/lib/active_record/base.rb +6 -2
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +5 -10
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -24
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -13
- data/lib/active_record/connection_adapters/abstract/quoting.rb +6 -3
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +90 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +45 -70
- data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -96
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +74 -66
- data/lib/active_record/connection_adapters/column.rb +1 -35
- data/lib/active_record/connection_adapters/connection_specification.rb +231 -43
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -5
- data/lib/active_record/connection_adapters/mysql_adapter.rb +24 -17
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +22 -15
- data/lib/active_record/connection_adapters/postgresql/cast.rb +12 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -44
- data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -14
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +37 -12
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +20 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +98 -52
- data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -60
- data/lib/active_record/connection_handling.rb +39 -5
- data/lib/active_record/core.rb +38 -54
- data/lib/active_record/counter_cache.rb +9 -10
- data/lib/active_record/dynamic_matchers.rb +6 -2
- data/lib/active_record/enum.rb +199 -0
- data/lib/active_record/errors.rb +22 -5
- data/lib/active_record/fixture_set/file.rb +2 -1
- data/lib/active_record/fixtures.rb +173 -76
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +23 -9
- data/lib/active_record/integration.rb +54 -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 +6 -13
- data/lib/active_record/migration/command_recorder.rb +8 -2
- data/lib/active_record/migration.rb +91 -56
- data/lib/active_record/model_schema.rb +7 -14
- data/lib/active_record/nested_attributes.rb +25 -13
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +26 -6
- data/lib/active_record/persistence.rb +23 -29
- data/lib/active_record/querying.rb +15 -12
- data/lib/active_record/railtie.rb +12 -61
- data/lib/active_record/railties/databases.rake +37 -56
- data/lib/active_record/readonly_attributes.rb +0 -6
- data/lib/active_record/reflection.rb +230 -79
- data/lib/active_record/relation/batches.rb +74 -24
- data/lib/active_record/relation/calculations.rb +52 -48
- data/lib/active_record/relation/delegation.rb +54 -39
- data/lib/active_record/relation/finder_methods.rb +210 -67
- data/lib/active_record/relation/merger.rb +15 -12
- data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder.rb +81 -40
- data/lib/active_record/relation/query_methods.rb +185 -108
- data/lib/active_record/relation/spawn_methods.rb +8 -5
- data/lib/active_record/relation.rb +79 -84
- data/lib/active_record/result.rb +45 -6
- data/lib/active_record/runtime_registry.rb +5 -0
- data/lib/active_record/sanitization.rb +4 -4
- data/lib/active_record/schema_dumper.rb +18 -6
- data/lib/active_record/schema_migration.rb +31 -18
- data/lib/active_record/scoping/default.rb +5 -18
- data/lib/active_record/scoping/named.rb +14 -29
- data/lib/active_record/scoping.rb +5 -0
- data/lib/active_record/store.rb +67 -18
- data/lib/active_record/tasks/database_tasks.rb +66 -26
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -10
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +6 -6
- data/lib/active_record/transactions.rb +10 -12
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +19 -9
- data/lib/active_record/version.rb +4 -7
- data/lib/active_record.rb +5 -7
- data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
- data/lib/rails/generators/active_record.rb +2 -8
- metadata +18 -30
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
- data/lib/active_record/associations/join_helper.rb +0 -45
- 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 -96
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -175,7 +175,7 @@ by relying on a number of conventions that make it easy for Active Record to inf
|
|
175
175
|
complex relations and structures from a minimal amount of explicit direction.
|
176
176
|
|
177
177
|
Convention over Configuration:
|
178
|
-
* No XML
|
178
|
+
* No XML files!
|
179
179
|
* Lots of reflection and run-time extension
|
180
180
|
* Magic is not inherently a bad word
|
181
181
|
|
@@ -192,7 +192,7 @@ The latest version of Active Record can be installed with RubyGems:
|
|
192
192
|
|
193
193
|
Source code can be downloaded as part of the Rails project on GitHub:
|
194
194
|
|
195
|
-
* https://github.com/rails/rails/tree/4-
|
195
|
+
* https://github.com/rails/rails/tree/4-1-stable/activerecord
|
196
196
|
|
197
197
|
|
198
198
|
== License
|
data/examples/performance.rb
CHANGED
@@ -5,12 +5,12 @@ require 'benchmark/ips'
|
|
5
5
|
TIME = (ENV['BENCHMARK_TIME'] || 20).to_i
|
6
6
|
RECORDS = (ENV['BENCHMARK_RECORDS'] || TIME*1000).to_i
|
7
7
|
|
8
|
-
conn = { :
|
8
|
+
conn = { adapter: 'sqlite3', database: ':memory:' }
|
9
9
|
|
10
10
|
ActiveRecord::Base.establish_connection(conn)
|
11
11
|
|
12
12
|
class User < ActiveRecord::Base
|
13
|
-
connection.create_table :users, :
|
13
|
+
connection.create_table :users, force: true do |t|
|
14
14
|
t.string :name, :email
|
15
15
|
t.timestamps
|
16
16
|
end
|
@@ -19,7 +19,7 @@ class User < ActiveRecord::Base
|
|
19
19
|
end
|
20
20
|
|
21
21
|
class Exhibit < ActiveRecord::Base
|
22
|
-
connection.create_table :exhibits, :
|
22
|
+
connection.create_table :exhibits, force: true do |t|
|
23
23
|
t.belongs_to :user
|
24
24
|
t.string :name
|
25
25
|
t.text :notes
|
@@ -43,6 +43,8 @@ class Exhibit < ActiveRecord::Base
|
|
43
43
|
def self.feel(exhibits) exhibits.each { |e| e.feel } end
|
44
44
|
end
|
45
45
|
|
46
|
+
def progress_bar(int); print "." if (int%100).zero? ; end
|
47
|
+
|
46
48
|
puts 'Generating data...'
|
47
49
|
|
48
50
|
module ActiveRecord
|
@@ -75,30 +77,32 @@ notes = ActiveRecord::Faker::LOREM.join ' '
|
|
75
77
|
today = Date.today
|
76
78
|
|
77
79
|
puts "Inserting #{RECORDS} users and exhibits..."
|
78
|
-
RECORDS.times do
|
80
|
+
RECORDS.times do |record|
|
79
81
|
user = User.create(
|
80
|
-
:
|
81
|
-
:
|
82
|
-
:
|
82
|
+
created_at: today,
|
83
|
+
name: ActiveRecord::Faker.name,
|
84
|
+
email: ActiveRecord::Faker.email
|
83
85
|
)
|
84
86
|
|
85
87
|
Exhibit.create(
|
86
|
-
:
|
87
|
-
:
|
88
|
-
:
|
89
|
-
:
|
88
|
+
created_at: today,
|
89
|
+
name: ActiveRecord::Faker.name,
|
90
|
+
user: user,
|
91
|
+
notes: notes
|
90
92
|
)
|
93
|
+
progress_bar(record)
|
91
94
|
end
|
95
|
+
puts "Done!\n"
|
92
96
|
|
93
97
|
Benchmark.ips(TIME) do |x|
|
94
98
|
ar_obj = Exhibit.find(1)
|
95
|
-
attrs = { :
|
96
|
-
attrs_first = { :
|
97
|
-
attrs_second = { :
|
99
|
+
attrs = { name: 'sam' }
|
100
|
+
attrs_first = { name: 'sam' }
|
101
|
+
attrs_second = { name: 'tom' }
|
98
102
|
exhibit = {
|
99
|
-
:
|
100
|
-
:
|
101
|
-
:
|
103
|
+
name: ActiveRecord::Faker.name,
|
104
|
+
notes: notes,
|
105
|
+
created_at: Date.today
|
102
106
|
}
|
103
107
|
|
104
108
|
x.report("Model#id") do
|
@@ -117,10 +121,18 @@ Benchmark.ips(TIME) do |x|
|
|
117
121
|
Exhibit.first.look
|
118
122
|
end
|
119
123
|
|
124
|
+
x.report 'Model.take' do
|
125
|
+
Exhibit.take
|
126
|
+
end
|
127
|
+
|
120
128
|
x.report("Model.all limit(100)") do
|
121
129
|
Exhibit.look Exhibit.limit(100)
|
122
130
|
end
|
123
131
|
|
132
|
+
x.report("Model.all take(100)") do
|
133
|
+
Exhibit.look Exhibit.take(100)
|
134
|
+
end
|
135
|
+
|
124
136
|
x.report "Model.all limit(100) with relationship" do
|
125
137
|
Exhibit.feel Exhibit.limit(100).includes(:user)
|
126
138
|
end
|
@@ -167,6 +179,6 @@ Benchmark.ips(TIME) do |x|
|
|
167
179
|
end
|
168
180
|
|
169
181
|
x.report "AR.execute(query)" do
|
170
|
-
ActiveRecord::Base.connection.execute("
|
182
|
+
ActiveRecord::Base.connection.execute("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}")
|
171
183
|
end
|
172
184
|
end
|
data/examples/simple.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
|
1
|
+
require File.expand_path('../../../load_paths', __FILE__)
|
2
2
|
require 'active_record'
|
3
3
|
|
4
4
|
class Person < ActiveRecord::Base
|
5
|
-
establish_connection :
|
6
|
-
connection.create_table table_name, :
|
5
|
+
establish_connection adapter: 'sqlite3', database: 'foobar.db'
|
6
|
+
connection.create_table table_name, force: true do |t|
|
7
7
|
t.string :name
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
bob = Person.create!(:
|
11
|
+
bob = Person.create!(name: 'bob')
|
12
12
|
puts Person.all.inspect
|
13
13
|
bob.destroy
|
14
14
|
puts Person.all.inspect
|
@@ -223,7 +223,8 @@ module ActiveRecord
|
|
223
223
|
reader_method(name, class_name, mapping, allow_nil, constructor)
|
224
224
|
writer_method(name, class_name, mapping, allow_nil, converter)
|
225
225
|
|
226
|
-
|
226
|
+
reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self)
|
227
|
+
Reflection.add_aggregate_reflection self, part_id, reflection
|
227
228
|
end
|
228
229
|
|
229
230
|
private
|
@@ -5,16 +5,58 @@ module ActiveRecord
|
|
5
5
|
# Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
|
6
6
|
# ActiveRecord::Associations::ThroughAssociationScope
|
7
7
|
class AliasTracker # :nodoc:
|
8
|
-
attr_reader :aliases, :
|
8
|
+
attr_reader :aliases, :connection
|
9
|
+
|
10
|
+
def self.empty(connection)
|
11
|
+
new connection, Hash.new(0)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.create(connection, table_joins)
|
15
|
+
if table_joins.empty?
|
16
|
+
empty connection
|
17
|
+
else
|
18
|
+
aliases = Hash.new { |h,k|
|
19
|
+
h[k] = initial_count_for(connection, k, table_joins)
|
20
|
+
}
|
21
|
+
new connection, aliases
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.initial_count_for(connection, name, table_joins)
|
26
|
+
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
|
27
|
+
quoted_name = connection.quote_table_name(name).downcase
|
28
|
+
|
29
|
+
counts = table_joins.map do |join|
|
30
|
+
if join.is_a?(Arel::Nodes::StringJoin)
|
31
|
+
# Table names + table aliases
|
32
|
+
join.left.downcase.scan(
|
33
|
+
/join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
|
34
|
+
).size
|
35
|
+
elsif join.respond_to? :left
|
36
|
+
join.left.table_name == name ? 1 : 0
|
37
|
+
else
|
38
|
+
# this branch is reached by two tests:
|
39
|
+
#
|
40
|
+
# activerecord/test/cases/associations/cascaded_eager_loading_test.rb:37
|
41
|
+
# with :posts
|
42
|
+
#
|
43
|
+
# activerecord/test/cases/associations/eager_test.rb:1133
|
44
|
+
# with :comments
|
45
|
+
#
|
46
|
+
0
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
counts.sum
|
51
|
+
end
|
9
52
|
|
10
53
|
# table_joins is an array of arel joins which might conflict with the aliases we assign here
|
11
|
-
def initialize(connection
|
12
|
-
@aliases
|
13
|
-
@
|
14
|
-
@connection = connection
|
54
|
+
def initialize(connection, aliases)
|
55
|
+
@aliases = aliases
|
56
|
+
@connection = connection
|
15
57
|
end
|
16
58
|
|
17
|
-
def aliased_table_for(table_name, aliased_name
|
59
|
+
def aliased_table_for(table_name, aliased_name)
|
18
60
|
table_alias = aliased_name_for(table_name, aliased_name)
|
19
61
|
|
20
62
|
if table_alias == table_name
|
@@ -24,9 +66,7 @@ module ActiveRecord
|
|
24
66
|
end
|
25
67
|
end
|
26
68
|
|
27
|
-
def aliased_name_for(table_name, aliased_name
|
28
|
-
aliased_name ||= table_name
|
29
|
-
|
69
|
+
def aliased_name_for(table_name, aliased_name)
|
30
70
|
if aliases[table_name].zero?
|
31
71
|
# If it's zero, we can have our table_name
|
32
72
|
aliases[table_name] = 1
|
@@ -48,26 +88,6 @@ module ActiveRecord
|
|
48
88
|
|
49
89
|
private
|
50
90
|
|
51
|
-
def initial_count_for(name)
|
52
|
-
return 0 if Arel::Table === table_joins
|
53
|
-
|
54
|
-
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
|
55
|
-
quoted_name = connection.quote_table_name(name).downcase
|
56
|
-
|
57
|
-
counts = table_joins.map do |join|
|
58
|
-
if join.is_a?(Arel::Nodes::StringJoin)
|
59
|
-
# Table names + table aliases
|
60
|
-
join.left.downcase.scan(
|
61
|
-
/join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
|
62
|
-
).size
|
63
|
-
else
|
64
|
-
join.left.table_name == name ? 1 : 0
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
counts.sum
|
69
|
-
end
|
70
|
-
|
71
91
|
def truncate(name)
|
72
92
|
name.slice(0, connection.table_alias_length - 2)
|
73
93
|
end
|
@@ -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
|
@@ -100,7 +94,7 @@ module ActiveRecord
|
|
100
94
|
# actually gets built.
|
101
95
|
def association_scope
|
102
96
|
if klass
|
103
|
-
@association_scope ||= AssociationScope.
|
97
|
+
@association_scope ||= AssociationScope.scope(self, klass.connection)
|
104
98
|
end
|
105
99
|
end
|
106
100
|
|
@@ -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.
|
@@ -169,7 +160,7 @@ module ActiveRecord
|
|
169
160
|
def marshal_load(data)
|
170
161
|
reflection_name, ivars = data
|
171
162
|
ivars.each { |name, val| instance_variable_set(name, val) }
|
172
|
-
@reflection = @owner.class.
|
163
|
+
@reflection = @owner.class._reflect_on_association(reflection_name)
|
173
164
|
end
|
174
165
|
|
175
166
|
def initialize_attributes(record) #:nodoc:
|
@@ -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
|
@@ -1,64 +1,77 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
3
|
class AssociationScope #:nodoc:
|
4
|
-
|
4
|
+
INSTANCE = new
|
5
5
|
|
6
|
-
|
6
|
+
def self.scope(association, connection)
|
7
|
+
INSTANCE.scope association, connection
|
8
|
+
end
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
+
def scope(association, connection)
|
11
|
+
klass = association.klass
|
12
|
+
reflection = association.reflection
|
13
|
+
scope = klass.unscoped
|
14
|
+
owner = association.owner
|
15
|
+
alias_tracker = AliasTracker.empty connection
|
10
16
|
|
11
|
-
|
12
|
-
|
13
|
-
@alias_tracker = AliasTracker.new klass.connection
|
17
|
+
scope.extending! Array(reflection.options[:extend])
|
18
|
+
add_constraints(scope, owner, klass, reflection, alias_tracker)
|
14
19
|
end
|
15
20
|
|
16
|
-
def
|
17
|
-
|
18
|
-
scope.extending! Array(options[:extend])
|
19
|
-
add_constraints(scope)
|
21
|
+
def join_type
|
22
|
+
Arel::Nodes::InnerJoin
|
20
23
|
end
|
21
24
|
|
22
25
|
private
|
23
26
|
|
24
|
-
def
|
27
|
+
def construct_tables(chain, klass, refl, alias_tracker)
|
28
|
+
chain.map do |reflection|
|
29
|
+
alias_tracker.aliased_table_for(
|
30
|
+
table_name_for(reflection, klass, refl),
|
31
|
+
table_alias_for(reflection, refl, reflection != refl)
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def table_alias_for(reflection, refl, join = false)
|
37
|
+
name = "#{reflection.plural_name}_#{alias_suffix(refl)}"
|
38
|
+
name << "_join" if join
|
39
|
+
name
|
40
|
+
end
|
41
|
+
|
42
|
+
def join(table, constraint)
|
43
|
+
table.create_join(table, table.create_on(constraint), join_type)
|
44
|
+
end
|
45
|
+
|
46
|
+
def column_for(table_name, column_name, alias_tracker)
|
25
47
|
columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
|
26
48
|
columns[column_name]
|
27
49
|
end
|
28
50
|
|
29
|
-
def bind_value(scope, column, value)
|
51
|
+
def bind_value(scope, column, value, alias_tracker)
|
30
52
|
substitute = alias_tracker.connection.substitute_at(
|
31
53
|
column, scope.bind_values.length)
|
32
54
|
scope.bind_values += [[column, value]]
|
33
55
|
substitute
|
34
56
|
end
|
35
57
|
|
36
|
-
def bind(scope, table_name, column_name, value)
|
37
|
-
column = column_for table_name, column_name
|
38
|
-
bind_value scope, column, value
|
58
|
+
def bind(scope, table_name, column_name, value, tracker)
|
59
|
+
column = column_for table_name, column_name, tracker
|
60
|
+
bind_value scope, column, value, tracker
|
39
61
|
end
|
40
62
|
|
41
|
-
def add_constraints(scope)
|
42
|
-
|
63
|
+
def add_constraints(scope, owner, assoc_klass, refl, tracker)
|
64
|
+
chain = refl.chain
|
65
|
+
scope_chain = refl.scope_chain
|
66
|
+
|
67
|
+
tables = construct_tables(chain, assoc_klass, refl, tracker)
|
43
68
|
|
44
69
|
chain.each_with_index do |reflection, i|
|
45
70
|
table, foreign_table = tables.shift, tables.first
|
46
71
|
|
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
72
|
if reflection.source_macro == :belongs_to
|
60
73
|
if reflection.options[:polymorphic]
|
61
|
-
key = reflection.association_primary_key(
|
74
|
+
key = reflection.association_primary_key(assoc_klass)
|
62
75
|
else
|
63
76
|
key = reflection.association_primary_key
|
64
77
|
end
|
@@ -70,35 +83,36 @@ module ActiveRecord
|
|
70
83
|
end
|
71
84
|
|
72
85
|
if reflection == chain.last
|
73
|
-
bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key]
|
86
|
+
bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
|
74
87
|
scope = scope.where(table[key].eq(bind_val))
|
75
88
|
|
76
89
|
if reflection.type
|
77
90
|
value = owner.class.base_class.name
|
78
|
-
bind_val = bind scope, table.table_name, reflection.type.to_s, value
|
91
|
+
bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
|
79
92
|
scope = scope.where(table[reflection.type].eq(bind_val))
|
80
93
|
end
|
81
94
|
else
|
82
95
|
constraint = table[key].eq(foreign_table[foreign_key])
|
83
96
|
|
84
97
|
if reflection.type
|
85
|
-
|
86
|
-
|
98
|
+
value = chain[i + 1].klass.base_class.name
|
99
|
+
bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
|
100
|
+
scope = scope.where(table[reflection.type].eq(bind_val))
|
87
101
|
end
|
88
102
|
|
89
103
|
scope = scope.joins(join(foreign_table, constraint))
|
90
104
|
end
|
91
105
|
|
92
106
|
is_first_chain = i == 0
|
93
|
-
klass = is_first_chain ?
|
107
|
+
klass = is_first_chain ? assoc_klass : reflection.klass
|
94
108
|
|
95
109
|
# Exclude the scope of the association itself, because that
|
96
110
|
# was already merged in the #scope method.
|
97
111
|
scope_chain[i].each do |scope_chain_item|
|
98
|
-
item = eval_scope(klass, scope_chain_item)
|
112
|
+
item = eval_scope(klass, scope_chain_item, owner)
|
99
113
|
|
100
|
-
if scope_chain_item ==
|
101
|
-
scope.merge! item.except(:where, :includes)
|
114
|
+
if scope_chain_item == refl.scope
|
115
|
+
scope.merge! item.except(:where, :includes, :bind)
|
102
116
|
end
|
103
117
|
|
104
118
|
if is_first_chain
|
@@ -113,12 +127,12 @@ module ActiveRecord
|
|
113
127
|
scope
|
114
128
|
end
|
115
129
|
|
116
|
-
def alias_suffix
|
117
|
-
|
130
|
+
def alias_suffix(refl)
|
131
|
+
refl.name
|
118
132
|
end
|
119
133
|
|
120
|
-
def table_name_for(reflection)
|
121
|
-
if reflection ==
|
134
|
+
def table_name_for(reflection, klass, refl)
|
135
|
+
if reflection == refl
|
122
136
|
# If this is a polymorphic belongs_to, we want to get the klass from the
|
123
137
|
# association because it depends on the polymorphic_type attribute of
|
124
138
|
# the owner
|
@@ -128,12 +142,8 @@ module ActiveRecord
|
|
128
142
|
end
|
129
143
|
end
|
130
144
|
|
131
|
-
def eval_scope(klass, scope)
|
132
|
-
|
133
|
-
scope
|
134
|
-
else
|
135
|
-
klass.unscoped.instance_exec(owner, &scope)
|
136
|
-
end
|
145
|
+
def eval_scope(klass, scope, owner)
|
146
|
+
klass.unscoped.instance_exec(owner, &scope)
|
137
147
|
end
|
138
148
|
end
|
139
149
|
end
|
@@ -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?
|
@@ -11,7 +11,12 @@ module ActiveRecord
|
|
11
11
|
|
12
12
|
def replace_keys(record)
|
13
13
|
super
|
14
|
-
owner[reflection.foreign_type] = record
|
14
|
+
owner[reflection.foreign_type] = record.class.base_class.name
|
15
|
+
end
|
16
|
+
|
17
|
+
def remove_keys
|
18
|
+
super
|
19
|
+
owner[reflection.foreign_type] = nil
|
15
20
|
end
|
16
21
|
|
17
22
|
def different_target?(record)
|