activerecord 3.1.11 → 3.2.0
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.
- data/CHANGELOG.md +6294 -97
- data/README.rdoc +2 -2
- data/examples/performance.rb +55 -31
- data/lib/active_record/aggregations.rb +2 -2
- data/lib/active_record/associations/association.rb +2 -42
- data/lib/active_record/associations/association_scope.rb +3 -30
- data/lib/active_record/associations/builder/association.rb +6 -4
- data/lib/active_record/associations/builder/belongs_to.rb +3 -3
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +5 -6
- data/lib/active_record/associations/builder/singular_association.rb +3 -16
- data/lib/active_record/associations/collection_association.rb +55 -28
- data/lib/active_record/associations/collection_proxy.rb +1 -35
- data/lib/active_record/associations/has_many_association.rb +5 -1
- data/lib/active_record/associations/has_many_through_association.rb +11 -8
- data/lib/active_record/associations/join_dependency.rb +1 -1
- data/lib/active_record/associations/preloader/association.rb +3 -1
- data/lib/active_record/associations.rb +82 -69
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +3 -3
- data/lib/active_record/attribute_methods/primary_key.rb +62 -25
- data/lib/active_record/attribute_methods/read.rb +72 -83
- data/lib/active_record/attribute_methods/serialization.rb +93 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
- data/lib/active_record/attribute_methods/write.rb +27 -5
- data/lib/active_record/attribute_methods.rb +209 -30
- data/lib/active_record/autosave_association.rb +23 -8
- data/lib/active_record/base.rb +217 -1709
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +9 -12
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +43 -22
- data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
- data/lib/active_record/connection_adapters/column.rb +2 -2
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
- data/lib/active_record/counter_cache.rb +4 -3
- data/lib/active_record/dynamic_matchers.rb +79 -0
- data/lib/active_record/errors.rb +11 -1
- data/lib/active_record/explain.rb +83 -0
- data/lib/active_record/explain_subscriber.rb +21 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/fixtures.rb +31 -76
- data/lib/active_record/identity_map.rb +4 -11
- data/lib/active_record/inheritance.rb +167 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locking/optimistic.rb +30 -25
- data/lib/active_record/locking/pessimistic.rb +23 -1
- data/lib/active_record/log_subscriber.rb +3 -3
- data/lib/active_record/migration/command_recorder.rb +8 -8
- data/lib/active_record/migration.rb +47 -30
- data/lib/active_record/model_schema.rb +366 -0
- data/lib/active_record/nested_attributes.rb +3 -2
- data/lib/active_record/persistence.rb +51 -9
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +24 -28
- data/lib/active_record/railties/controller_runtime.rb +3 -1
- data/lib/active_record/railties/databases.rake +134 -77
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +7 -15
- data/lib/active_record/relation/batches.rb +5 -2
- data/lib/active_record/relation/calculations.rb +27 -6
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +6 -5
- data/lib/active_record/relation/predicate_builder.rb +12 -19
- data/lib/active_record/relation/query_methods.rb +76 -10
- data/lib/active_record/relation/spawn_methods.rb +11 -2
- data/lib/active_record/relation.rb +77 -34
- data/lib/active_record/result.rb +1 -1
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema_dumper.rb +5 -2
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/serialization.rb +1 -43
- data/lib/active_record/serializers/xml_serializer.rb +2 -44
- data/lib/active_record/session_store.rb +15 -15
- data/lib/active_record/store.rb +50 -0
- data/lib/active_record/test_case.rb +11 -7
- data/lib/active_record/timestamp.rb +16 -3
- data/lib/active_record/transactions.rb +5 -5
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +5 -4
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/active_record/validations.rb +1 -1
- data/lib/active_record/version.rb +2 -2
- data/lib/active_record.rb +28 -2
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +9 -3
- data/lib/rails/generators/active_record/model/model_generator.rb +5 -1
- data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
- metadata +50 -40
- checksums.yaml +0 -7
- data/lib/active_record/named_scope.rb +0 -200
data/README.rdoc
CHANGED
|
@@ -197,7 +197,7 @@ Admit the Database:
|
|
|
197
197
|
|
|
198
198
|
== Download and installation
|
|
199
199
|
|
|
200
|
-
The latest version of Active Record can be installed with
|
|
200
|
+
The latest version of Active Record can be installed with RubyGems:
|
|
201
201
|
|
|
202
202
|
% [sudo] gem install activerecord
|
|
203
203
|
|
|
@@ -215,7 +215,7 @@ Active Record is released under the MIT license.
|
|
|
215
215
|
|
|
216
216
|
API documentation is at
|
|
217
217
|
|
|
218
|
-
* http://api.rubyonrails.
|
|
218
|
+
* http://api.rubyonrails.org
|
|
219
219
|
|
|
220
220
|
Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
|
|
221
221
|
|
data/examples/performance.rb
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
+
TIMES = (ENV['N'] || 10000).to_i
|
|
2
|
+
|
|
1
3
|
require File.expand_path('../../../load_paths', __FILE__)
|
|
2
4
|
require "active_record"
|
|
3
|
-
require 'benchmark/ips'
|
|
4
|
-
|
|
5
|
-
TIME = (ENV['BENCHMARK_TIME'] || 20).to_i
|
|
6
|
-
RECORDS = (ENV['BENCHMARK_RECORDS'] || TIME*1000).to_i
|
|
7
5
|
|
|
8
6
|
conn = { :adapter => 'sqlite3', :database => ':memory:' }
|
|
9
7
|
|
|
@@ -31,6 +29,14 @@ class Exhibit < ActiveRecord::Base
|
|
|
31
29
|
def look; attributes end
|
|
32
30
|
def feel; look; user.name end
|
|
33
31
|
|
|
32
|
+
def self.with_name
|
|
33
|
+
where("name IS NOT NULL")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.with_notes
|
|
37
|
+
where("notes IS NOT NULL")
|
|
38
|
+
end
|
|
39
|
+
|
|
34
40
|
def self.look(exhibits) exhibits.each { |e| e.look } end
|
|
35
41
|
def self.feel(exhibits) exhibits.each { |e| e.feel } end
|
|
36
42
|
end
|
|
@@ -39,7 +45,14 @@ puts 'Generating data...'
|
|
|
39
45
|
|
|
40
46
|
module ActiveRecord
|
|
41
47
|
class Faker
|
|
42
|
-
LOREM =
|
|
48
|
+
LOREM = %Q{Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit.
|
|
49
|
+
Integer consequat tincidunt felis. Etiam non erat dolor. Vivamus imperdiet nibh sit amet diam eleifend id posuere diam malesuada. Mauris at accumsan sem.
|
|
50
|
+
Donec id lorem neque. Fusce erat lorem, ornare eu congue vitae, malesuada quis neque. Maecenas vel urna a velit pretium fermentum. Donec tortor enim,
|
|
51
|
+
tempor venenatis egestas a, tempor sed ipsum. Ut arcu justo, faucibus non imperdiet ac, interdum at diam. Pellentesque ipsum enim, venenatis ut iaculis vitae,
|
|
52
|
+
varius vitae sem. Sed rutrum quam ac elit euismod bibendum. Donec ultricies ultricies magna, at lacinia libero mollis aliquam. Sed ac arcu in tortor elementum
|
|
53
|
+
tincidunt vel interdum sem. Curabitur eget erat arcu. Praesent eget eros leo. Nam magna enim, sollicitudin vehicula scelerisque in, vulputate ut libero.
|
|
54
|
+
Praesent varius tincidunt commodo}.split
|
|
55
|
+
|
|
43
56
|
def self.name
|
|
44
57
|
LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join ' '
|
|
45
58
|
end
|
|
@@ -59,8 +72,8 @@ end
|
|
|
59
72
|
notes = ActiveRecord::Faker::LOREM.join ' '
|
|
60
73
|
today = Date.today
|
|
61
74
|
|
|
62
|
-
puts
|
|
63
|
-
|
|
75
|
+
puts 'Inserting 10,000 users and exhibits...'
|
|
76
|
+
10_000.times do
|
|
64
77
|
user = User.create(
|
|
65
78
|
:created_at => today,
|
|
66
79
|
:name => ActiveRecord::Faker.name,
|
|
@@ -75,7 +88,9 @@ RECORDS.times do
|
|
|
75
88
|
)
|
|
76
89
|
end
|
|
77
90
|
|
|
78
|
-
|
|
91
|
+
require 'benchmark'
|
|
92
|
+
|
|
93
|
+
Benchmark.bm(46) do |x|
|
|
79
94
|
ar_obj = Exhibit.find(1)
|
|
80
95
|
attrs = { :name => 'sam' }
|
|
81
96
|
attrs_first = { :name => 'sam' }
|
|
@@ -86,68 +101,77 @@ Benchmark.ips(TIME) do |x|
|
|
|
86
101
|
:created_at => Date.today
|
|
87
102
|
}
|
|
88
103
|
|
|
89
|
-
x.report("Model#id") do
|
|
90
|
-
ar_obj.id
|
|
104
|
+
x.report("Model#id (x#{(TIMES * 100).ceil})") do
|
|
105
|
+
(TIMES * 100).ceil.times { ar_obj.id }
|
|
91
106
|
end
|
|
92
107
|
|
|
93
108
|
x.report 'Model.new (instantiation)' do
|
|
94
|
-
Exhibit.new
|
|
109
|
+
TIMES.times { Exhibit.new }
|
|
95
110
|
end
|
|
96
111
|
|
|
97
112
|
x.report 'Model.new (setting attributes)' do
|
|
98
|
-
Exhibit.new(attrs)
|
|
113
|
+
TIMES.times { Exhibit.new(attrs) }
|
|
99
114
|
end
|
|
100
115
|
|
|
101
116
|
x.report 'Model.first' do
|
|
102
|
-
Exhibit.first.look
|
|
117
|
+
TIMES.times { Exhibit.first.look }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
x.report 'Model.named_scope' do
|
|
121
|
+
TIMES.times { Exhibit.limit(10).with_name.with_notes }
|
|
103
122
|
end
|
|
104
123
|
|
|
105
|
-
x.report("Model.all limit(100)") do
|
|
106
|
-
Exhibit.look Exhibit.limit(100)
|
|
124
|
+
x.report("Model.all limit(100) (x#{(TIMES / 10).ceil})") do
|
|
125
|
+
(TIMES / 10).ceil.times { Exhibit.look Exhibit.limit(100) }
|
|
107
126
|
end
|
|
108
127
|
|
|
109
|
-
x.report "Model.all limit(100) with relationship" do
|
|
110
|
-
Exhibit.feel Exhibit.limit(100).includes(:user)
|
|
128
|
+
x.report "Model.all limit(100) with relationship (x#{(TIMES / 10).ceil})" do
|
|
129
|
+
(TIMES / 10).ceil.times { Exhibit.feel Exhibit.limit(100).includes(:user) }
|
|
111
130
|
end
|
|
112
131
|
|
|
113
|
-
x.report "Model.all limit(10,000)" do
|
|
114
|
-
Exhibit.look Exhibit.limit(10000)
|
|
132
|
+
x.report "Model.all limit(10,000) x(#{(TIMES / 1000).ceil})" do
|
|
133
|
+
(TIMES / 1000).ceil.times { Exhibit.look Exhibit.limit(10000) }
|
|
115
134
|
end
|
|
116
135
|
|
|
117
136
|
x.report 'Model.create' do
|
|
118
|
-
Exhibit.create(exhibit)
|
|
137
|
+
TIMES.times { Exhibit.create(exhibit) }
|
|
119
138
|
end
|
|
120
139
|
|
|
121
140
|
x.report 'Resource#attributes=' do
|
|
122
|
-
|
|
123
|
-
|
|
141
|
+
TIMES.times {
|
|
142
|
+
exhibit = Exhibit.new(attrs_first)
|
|
143
|
+
exhibit.attributes = attrs_second
|
|
144
|
+
}
|
|
124
145
|
end
|
|
125
146
|
|
|
126
147
|
x.report 'Resource#update' do
|
|
127
|
-
Exhibit.first.update_attributes(:name => 'bob')
|
|
148
|
+
TIMES.times { Exhibit.first.update_attributes(:name => 'bob') }
|
|
128
149
|
end
|
|
129
150
|
|
|
130
151
|
x.report 'Resource#destroy' do
|
|
131
|
-
Exhibit.first.destroy
|
|
152
|
+
TIMES.times { Exhibit.first.destroy }
|
|
132
153
|
end
|
|
133
154
|
|
|
134
155
|
x.report 'Model.transaction' do
|
|
135
|
-
Exhibit.transaction { Exhibit.new }
|
|
156
|
+
TIMES.times { Exhibit.transaction { Exhibit.new } }
|
|
136
157
|
end
|
|
137
158
|
|
|
138
159
|
x.report 'Model.find(id)' do
|
|
139
|
-
|
|
160
|
+
id = Exhibit.first.id
|
|
161
|
+
TIMES.times { Exhibit.find(id) }
|
|
140
162
|
end
|
|
141
163
|
|
|
142
164
|
x.report 'Model.find_by_sql' do
|
|
143
|
-
|
|
165
|
+
TIMES.times {
|
|
166
|
+
Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first
|
|
167
|
+
}
|
|
144
168
|
end
|
|
145
169
|
|
|
146
|
-
x.report "Model.log" do
|
|
147
|
-
Exhibit.connection.send(:log, "hello", "world") {}
|
|
170
|
+
x.report "Model.log x(#{TIMES * 10})" do
|
|
171
|
+
(TIMES * 10).times { Exhibit.connection.send(:log, "hello", "world") {} }
|
|
148
172
|
end
|
|
149
173
|
|
|
150
|
-
x.report "AR.execute(query)" do
|
|
151
|
-
ActiveRecord::Base.connection.execute("Select * from exhibits where id = #{(rand * 1000 + 1).to_i}")
|
|
174
|
+
x.report "AR.execute(query) (#{TIMES / 2})" do
|
|
175
|
+
(TIMES / 2).times { ActiveRecord::Base.connection.execute("Select * from exhibits where id = #{(rand * 1000 + 1).to_i}") }
|
|
152
176
|
end
|
|
153
177
|
end
|
|
@@ -46,7 +46,7 @@ module ActiveRecord
|
|
|
46
46
|
#
|
|
47
47
|
# def <=>(other_money)
|
|
48
48
|
# if currency == other_money.currency
|
|
49
|
-
# amount <=>
|
|
49
|
+
# amount <=> amount
|
|
50
50
|
# else
|
|
51
51
|
# amount <=> other_money.exchange_to(currency).amount
|
|
52
52
|
# end
|
|
@@ -176,7 +176,7 @@ module ActiveRecord
|
|
|
176
176
|
# order in which mappings are defined determines the order in which attributes are sent to the
|
|
177
177
|
# value class constructor.
|
|
178
178
|
# * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
|
|
179
|
-
# attributes are +nil+.
|
|
179
|
+
# attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
|
|
180
180
|
# mapped attributes.
|
|
181
181
|
# This defaults to +false+.
|
|
182
182
|
# * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
require 'active_support/core_ext/array/wrap'
|
|
2
2
|
require 'active_support/core_ext/object/inclusion'
|
|
3
|
-
require 'active_support/deprecation'
|
|
4
3
|
|
|
5
4
|
module ActiveRecord
|
|
6
5
|
module Associations
|
|
@@ -231,48 +230,9 @@ module ActiveRecord
|
|
|
231
230
|
end
|
|
232
231
|
|
|
233
232
|
def build_record(attributes, options)
|
|
234
|
-
reflection.
|
|
235
|
-
|
|
236
|
-
record = reflection.build_association(attributes, options) do |r|
|
|
237
|
-
r.assign_attributes(
|
|
238
|
-
create_scope.except(*r.changed),
|
|
239
|
-
:without_protection => true
|
|
240
|
-
)
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
if !reflection.original_build_association_called &&
|
|
244
|
-
(record.changed & create_scope.keys) != create_scope.keys
|
|
245
|
-
# We have detected that there is an overridden AssociationReflection#build_association
|
|
246
|
-
# method, but it looks like it has not passed through the block above. So try again and
|
|
247
|
-
# show a noisy deprecation warning.
|
|
248
|
-
|
|
249
|
-
record.assign_attributes(
|
|
250
|
-
create_scope.except(*record.changed),
|
|
251
|
-
:without_protection => true
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
method = reflection.method(:build_association)
|
|
255
|
-
if RUBY_VERSION >= '1.9.2'
|
|
256
|
-
source = method.source_location
|
|
257
|
-
debug_info = "It looks like the method is defined in #{source[0]} at line #{source[1]}."
|
|
258
|
-
else
|
|
259
|
-
debug_info = "This might help you find the method: #{method}. If you run this on Ruby 1.9.2 we can tell you exactly where the method is."
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
ActiveSupport::Deprecation.warn <<-WARN
|
|
263
|
-
It looks like ActiveRecord::Reflection::AssociationReflection#build_association has been redefined, either by you or by a plugin or library that you are using. The signature of this method has changed.
|
|
264
|
-
|
|
265
|
-
Before: def build_association(*options)
|
|
266
|
-
After: def build_association(*options, &block)
|
|
267
|
-
|
|
268
|
-
The block argument now needs to be passed through to ActiveRecord::Base#new when this method is overridden, or else your associations will not function correctly in Rails 3.2.
|
|
269
|
-
|
|
270
|
-
#{debug_info}
|
|
271
|
-
|
|
272
|
-
WARN
|
|
233
|
+
reflection.build_association(attributes, options) do |record|
|
|
234
|
+
record.assign_attributes(create_scope.except(*record.changed), :without_protection => true)
|
|
273
235
|
end
|
|
274
|
-
|
|
275
|
-
record
|
|
276
236
|
end
|
|
277
237
|
end
|
|
278
238
|
end
|
|
@@ -20,31 +20,19 @@ module ActiveRecord
|
|
|
20
20
|
# It's okay to just apply all these like this. The options will only be present if the
|
|
21
21
|
# association supports that option; this is enforced by the association builder.
|
|
22
22
|
scope = scope.apply_finder_options(options.slice(
|
|
23
|
-
:readonly, :include, :order, :limit, :joins, :group, :having, :offset))
|
|
23
|
+
:readonly, :include, :order, :limit, :joins, :group, :having, :offset, :select))
|
|
24
24
|
|
|
25
25
|
if options[:through] && !options[:include]
|
|
26
26
|
scope = scope.includes(source_options[:include])
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
scope = scope.select(select)
|
|
31
|
-
end
|
|
29
|
+
scope = scope.uniq if options[:uniq]
|
|
32
30
|
|
|
33
31
|
add_constraints(scope)
|
|
34
32
|
end
|
|
35
33
|
|
|
36
34
|
private
|
|
37
35
|
|
|
38
|
-
def select_value
|
|
39
|
-
select_value = options[:select]
|
|
40
|
-
|
|
41
|
-
if reflection.collection?
|
|
42
|
-
select_value ||= options[:uniq] && "DISTINCT #{reflection.quoted_table_name}.*"
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
select_value
|
|
46
|
-
end
|
|
47
|
-
|
|
48
36
|
def add_constraints(scope)
|
|
49
37
|
tables = construct_tables
|
|
50
38
|
|
|
@@ -87,7 +75,7 @@ module ActiveRecord
|
|
|
87
75
|
|
|
88
76
|
conditions.each do |condition|
|
|
89
77
|
if options[:through] && condition.is_a?(Hash)
|
|
90
|
-
condition =
|
|
78
|
+
condition = { table.name => condition }
|
|
91
79
|
end
|
|
92
80
|
|
|
93
81
|
scope = scope.where(interpolate(condition))
|
|
@@ -126,21 +114,6 @@ module ActiveRecord
|
|
|
126
114
|
end
|
|
127
115
|
end
|
|
128
116
|
|
|
129
|
-
def disambiguate_condition(table, condition)
|
|
130
|
-
if condition.is_a?(Hash)
|
|
131
|
-
Hash[
|
|
132
|
-
condition.map do |k, v|
|
|
133
|
-
if v.is_a?(Hash)
|
|
134
|
-
[k, v]
|
|
135
|
-
else
|
|
136
|
-
[table.table_alias || table.name, { k => v }]
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
]
|
|
140
|
-
else
|
|
141
|
-
condition
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
117
|
end
|
|
145
118
|
end
|
|
146
119
|
end
|
|
@@ -16,6 +16,10 @@ module ActiveRecord::Associations::Builder
|
|
|
16
16
|
@model, @name, @options = model, name, options
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
def mixin
|
|
20
|
+
@model.generated_feature_methods
|
|
21
|
+
end
|
|
22
|
+
|
|
19
23
|
def build
|
|
20
24
|
validate_options
|
|
21
25
|
reflection = model.create_reflection(self.class.macro, name, options, model)
|
|
@@ -36,16 +40,14 @@ module ActiveRecord::Associations::Builder
|
|
|
36
40
|
|
|
37
41
|
def define_readers
|
|
38
42
|
name = self.name
|
|
39
|
-
|
|
40
|
-
model.redefine_method(name) do |*params|
|
|
43
|
+
mixin.redefine_method(name) do |*params|
|
|
41
44
|
association(name).reader(*params)
|
|
42
45
|
end
|
|
43
46
|
end
|
|
44
47
|
|
|
45
48
|
def define_writers
|
|
46
49
|
name = self.name
|
|
47
|
-
|
|
48
|
-
model.redefine_method("#{name}=") do |value|
|
|
50
|
+
mixin.redefine_method("#{name}=") do |value|
|
|
49
51
|
association(name).writer(value)
|
|
50
52
|
end
|
|
51
53
|
end
|
|
@@ -25,14 +25,14 @@ module ActiveRecord::Associations::Builder
|
|
|
25
25
|
name = self.name
|
|
26
26
|
|
|
27
27
|
method_name = "belongs_to_counter_cache_after_create_for_#{name}"
|
|
28
|
-
|
|
28
|
+
mixin.redefine_method(method_name) do
|
|
29
29
|
record = send(name)
|
|
30
30
|
record.class.increment_counter(cache_column, record.id) unless record.nil?
|
|
31
31
|
end
|
|
32
32
|
model.after_create(method_name)
|
|
33
33
|
|
|
34
34
|
method_name = "belongs_to_counter_cache_before_destroy_for_#{name}"
|
|
35
|
-
|
|
35
|
+
mixin.redefine_method(method_name) do
|
|
36
36
|
record = send(name)
|
|
37
37
|
record.class.decrement_counter(cache_column, record.id) unless record.nil?
|
|
38
38
|
end
|
|
@@ -48,7 +48,7 @@ module ActiveRecord::Associations::Builder
|
|
|
48
48
|
method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
|
|
49
49
|
touch = options[:touch]
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
mixin.redefine_method(method_name) do
|
|
52
52
|
record = send(name)
|
|
53
53
|
|
|
54
54
|
unless record.nil?
|
|
@@ -58,7 +58,7 @@ module ActiveRecord::Associations::Builder
|
|
|
58
58
|
super
|
|
59
59
|
|
|
60
60
|
name = self.name
|
|
61
|
-
|
|
61
|
+
mixin.redefine_method("#{name.to_s.singularize}_ids") do
|
|
62
62
|
association(name).ids_reader
|
|
63
63
|
end
|
|
64
64
|
end
|
|
@@ -67,7 +67,7 @@ module ActiveRecord::Associations::Builder
|
|
|
67
67
|
super
|
|
68
68
|
|
|
69
69
|
name = self.name
|
|
70
|
-
|
|
70
|
+
mixin.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
|
|
71
71
|
association(name).ids_writer(ids)
|
|
72
72
|
end
|
|
73
73
|
end
|
|
@@ -28,7 +28,7 @@ module ActiveRecord::Associations::Builder
|
|
|
28
28
|
|
|
29
29
|
def define_destroy_dependency_method
|
|
30
30
|
name = self.name
|
|
31
|
-
|
|
31
|
+
mixin.redefine_method(dependency_method_name) do
|
|
32
32
|
send(name).each do |o|
|
|
33
33
|
# No point in executing the counter update since we're going to destroy the parent anyway
|
|
34
34
|
counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym
|
|
@@ -45,21 +45,21 @@ module ActiveRecord::Associations::Builder
|
|
|
45
45
|
|
|
46
46
|
def define_delete_all_dependency_method
|
|
47
47
|
name = self.name
|
|
48
|
-
|
|
48
|
+
mixin.redefine_method(dependency_method_name) do
|
|
49
49
|
association(name).delete_all_on_destroy
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def define_nullify_dependency_method
|
|
54
54
|
name = self.name
|
|
55
|
-
|
|
55
|
+
mixin.redefine_method(dependency_method_name) do
|
|
56
56
|
send(name).delete_all
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def define_restrict_dependency_method
|
|
61
61
|
name = self.name
|
|
62
|
-
|
|
62
|
+
mixin.redefine_method(dependency_method_name) do
|
|
63
63
|
raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).empty?
|
|
64
64
|
end
|
|
65
65
|
end
|
|
@@ -44,18 +44,17 @@ module ActiveRecord::Associations::Builder
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def define_destroy_dependency_method
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
eoruby
|
|
47
|
+
name = self.name
|
|
48
|
+
mixin.redefine_method(dependency_method_name) do
|
|
49
|
+
association(name).delete
|
|
50
|
+
end
|
|
52
51
|
end
|
|
53
52
|
alias :define_delete_dependency_method :define_destroy_dependency_method
|
|
54
53
|
alias :define_nullify_dependency_method :define_destroy_dependency_method
|
|
55
54
|
|
|
56
55
|
def define_restrict_dependency_method
|
|
57
56
|
name = self.name
|
|
58
|
-
|
|
57
|
+
mixin.redefine_method(dependency_method_name) do
|
|
59
58
|
raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).nil?
|
|
60
59
|
end
|
|
61
60
|
end
|
|
@@ -13,31 +13,18 @@ module ActiveRecord::Associations::Builder
|
|
|
13
13
|
|
|
14
14
|
private
|
|
15
15
|
|
|
16
|
-
def define_readers
|
|
17
|
-
super
|
|
18
|
-
name = self.name
|
|
19
|
-
|
|
20
|
-
model.redefine_method("#{name}_loaded?") do
|
|
21
|
-
ActiveSupport::Deprecation.warn(
|
|
22
|
-
"Calling obj.#{name}_loaded? is deprecated. Please use " \
|
|
23
|
-
"obj.association(:#{name}).loaded? instead."
|
|
24
|
-
)
|
|
25
|
-
association(name).loaded?
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
16
|
def define_constructors
|
|
30
17
|
name = self.name
|
|
31
18
|
|
|
32
|
-
|
|
19
|
+
mixin.redefine_method("build_#{name}") do |*params, &block|
|
|
33
20
|
association(name).build(*params, &block)
|
|
34
21
|
end
|
|
35
22
|
|
|
36
|
-
|
|
23
|
+
mixin.redefine_method("create_#{name}") do |*params, &block|
|
|
37
24
|
association(name).create(*params, &block)
|
|
38
25
|
end
|
|
39
26
|
|
|
40
|
-
|
|
27
|
+
mixin.redefine_method("create_#{name}!") do |*params, &block|
|
|
41
28
|
association(name).create!(*params, &block)
|
|
42
29
|
end
|
|
43
30
|
end
|
|
@@ -49,10 +49,18 @@ module ActiveRecord
|
|
|
49
49
|
end
|
|
50
50
|
else
|
|
51
51
|
column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
|
|
52
|
+
relation = scoped
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
including = (relation.eager_load_values + relation.includes_values).uniq
|
|
55
|
+
|
|
56
|
+
if including.any?
|
|
57
|
+
join_dependency = ActiveRecord::Associations::JoinDependency.new(reflection.klass, including, [])
|
|
58
|
+
relation = join_dependency.join_associations.inject(relation) do |r, association|
|
|
59
|
+
association.join_relation(r)
|
|
60
|
+
end
|
|
55
61
|
end
|
|
62
|
+
|
|
63
|
+
relation.uniq.pluck(column)
|
|
56
64
|
end
|
|
57
65
|
end
|
|
58
66
|
|
|
@@ -115,22 +123,16 @@ module ActiveRecord
|
|
|
115
123
|
create_record(attributes, options, true, &block)
|
|
116
124
|
end
|
|
117
125
|
|
|
118
|
-
# Add +records+ to this association.
|
|
126
|
+
# Add +records+ to this association. Returns +self+ so method calls may be chained.
|
|
119
127
|
# Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
|
|
120
128
|
def concat(*records)
|
|
121
|
-
result = true
|
|
122
129
|
load_target if owner.new_record?
|
|
123
130
|
|
|
124
|
-
|
|
125
|
-
records
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
result &&= insert_record(record) unless owner.new_record?
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
+
if owner.new_record?
|
|
132
|
+
concat_records(records)
|
|
133
|
+
else
|
|
134
|
+
transaction { concat_records(records) }
|
|
131
135
|
end
|
|
132
|
-
|
|
133
|
-
result && records
|
|
134
136
|
end
|
|
135
137
|
|
|
136
138
|
# Starts a transaction in the association class's database connection.
|
|
@@ -248,7 +250,7 @@ module ActiveRecord
|
|
|
248
250
|
# This method is abstract in the sense that it relies on
|
|
249
251
|
# +count_records+, which is a method descendants have to provide.
|
|
250
252
|
def size
|
|
251
|
-
if
|
|
253
|
+
if !find_target? || (loaded? && !options[:uniq])
|
|
252
254
|
target.size
|
|
253
255
|
elsif !loaded? && options[:group]
|
|
254
256
|
load_target.size
|
|
@@ -306,14 +308,10 @@ module ActiveRecord
|
|
|
306
308
|
other_array.each { |val| raise_on_type_mismatch(val) }
|
|
307
309
|
original_target = load_target.dup
|
|
308
310
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
@target = original_target
|
|
314
|
-
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
|
|
315
|
-
"new records could not be saved."
|
|
316
|
-
end
|
|
311
|
+
if owner.new_record?
|
|
312
|
+
replace_records(other_array, original_target)
|
|
313
|
+
else
|
|
314
|
+
transaction { replace_records(other_array, original_target) }
|
|
317
315
|
end
|
|
318
316
|
end
|
|
319
317
|
|
|
@@ -453,14 +451,20 @@ module ActiveRecord
|
|
|
453
451
|
records.each { |record| raise_on_type_mismatch(record) }
|
|
454
452
|
existing_records = records.reject { |r| r.new_record? }
|
|
455
453
|
|
|
456
|
-
|
|
457
|
-
|
|
454
|
+
if existing_records.empty?
|
|
455
|
+
remove_records(existing_records, records, method)
|
|
456
|
+
else
|
|
457
|
+
transaction { remove_records(existing_records, records, method) }
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def remove_records(existing_records, records, method)
|
|
462
|
+
records.each { |record| callback(:before_remove, record) }
|
|
458
463
|
|
|
459
|
-
|
|
460
|
-
|
|
464
|
+
delete_records(existing_records, method) if existing_records.any?
|
|
465
|
+
records.each { |record| target.delete(record) }
|
|
461
466
|
|
|
462
|
-
|
|
463
|
-
end
|
|
467
|
+
records.each { |record| callback(:after_remove, record) }
|
|
464
468
|
end
|
|
465
469
|
|
|
466
470
|
# Delete the given records from the association, using one of the methods :destroy,
|
|
@@ -469,6 +473,29 @@ module ActiveRecord
|
|
|
469
473
|
raise NotImplementedError
|
|
470
474
|
end
|
|
471
475
|
|
|
476
|
+
def replace_records(new_target, original_target)
|
|
477
|
+
delete(target - new_target)
|
|
478
|
+
|
|
479
|
+
unless concat(new_target - target)
|
|
480
|
+
@target = original_target
|
|
481
|
+
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
|
|
482
|
+
"new records could not be saved."
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def concat_records(records)
|
|
487
|
+
result = true
|
|
488
|
+
|
|
489
|
+
records.flatten.each do |record|
|
|
490
|
+
raise_on_type_mismatch(record)
|
|
491
|
+
add_to_target(record) do |r|
|
|
492
|
+
result &&= insert_record(record) unless owner.new_record?
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
result && records
|
|
497
|
+
end
|
|
498
|
+
|
|
472
499
|
def callback(method, record)
|
|
473
500
|
callbacks_for(method).each do |callback|
|
|
474
501
|
case callback
|