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.
Files changed (106) hide show
  1. data/CHANGELOG.md +6294 -97
  2. data/README.rdoc +2 -2
  3. data/examples/performance.rb +55 -31
  4. data/lib/active_record/aggregations.rb +2 -2
  5. data/lib/active_record/associations/association.rb +2 -42
  6. data/lib/active_record/associations/association_scope.rb +3 -30
  7. data/lib/active_record/associations/builder/association.rb +6 -4
  8. data/lib/active_record/associations/builder/belongs_to.rb +3 -3
  9. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  10. data/lib/active_record/associations/builder/has_many.rb +4 -4
  11. data/lib/active_record/associations/builder/has_one.rb +5 -6
  12. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  13. data/lib/active_record/associations/collection_association.rb +55 -28
  14. data/lib/active_record/associations/collection_proxy.rb +1 -35
  15. data/lib/active_record/associations/has_many_association.rb +5 -1
  16. data/lib/active_record/associations/has_many_through_association.rb +11 -8
  17. data/lib/active_record/associations/join_dependency.rb +1 -1
  18. data/lib/active_record/associations/preloader/association.rb +3 -1
  19. data/lib/active_record/associations.rb +82 -69
  20. data/lib/active_record/attribute_assignment.rb +221 -0
  21. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +3 -3
  23. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  24. data/lib/active_record/attribute_methods/read.rb +72 -83
  25. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
  27. data/lib/active_record/attribute_methods/write.rb +27 -5
  28. data/lib/active_record/attribute_methods.rb +209 -30
  29. data/lib/active_record/autosave_association.rb +23 -8
  30. data/lib/active_record/base.rb +217 -1709
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
  33. data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
  34. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  35. data/lib/active_record/connection_adapters/abstract/quoting.rb +9 -12
  36. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
  37. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +43 -22
  38. data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
  39. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  40. data/lib/active_record/connection_adapters/column.rb +2 -2
  41. data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
  42. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
  43. data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
  44. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  45. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  46. data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
  47. data/lib/active_record/counter_cache.rb +4 -3
  48. data/lib/active_record/dynamic_matchers.rb +79 -0
  49. data/lib/active_record/errors.rb +11 -1
  50. data/lib/active_record/explain.rb +83 -0
  51. data/lib/active_record/explain_subscriber.rb +21 -0
  52. data/lib/active_record/fixtures/file.rb +65 -0
  53. data/lib/active_record/fixtures.rb +31 -76
  54. data/lib/active_record/identity_map.rb +4 -11
  55. data/lib/active_record/inheritance.rb +167 -0
  56. data/lib/active_record/integration.rb +49 -0
  57. data/lib/active_record/locking/optimistic.rb +30 -25
  58. data/lib/active_record/locking/pessimistic.rb +23 -1
  59. data/lib/active_record/log_subscriber.rb +3 -3
  60. data/lib/active_record/migration/command_recorder.rb +8 -8
  61. data/lib/active_record/migration.rb +47 -30
  62. data/lib/active_record/model_schema.rb +366 -0
  63. data/lib/active_record/nested_attributes.rb +3 -2
  64. data/lib/active_record/persistence.rb +51 -9
  65. data/lib/active_record/querying.rb +58 -0
  66. data/lib/active_record/railtie.rb +24 -28
  67. data/lib/active_record/railties/controller_runtime.rb +3 -1
  68. data/lib/active_record/railties/databases.rake +134 -77
  69. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  70. data/lib/active_record/readonly_attributes.rb +26 -0
  71. data/lib/active_record/reflection.rb +7 -15
  72. data/lib/active_record/relation/batches.rb +5 -2
  73. data/lib/active_record/relation/calculations.rb +27 -6
  74. data/lib/active_record/relation/delegation.rb +49 -0
  75. data/lib/active_record/relation/finder_methods.rb +6 -5
  76. data/lib/active_record/relation/predicate_builder.rb +12 -19
  77. data/lib/active_record/relation/query_methods.rb +76 -10
  78. data/lib/active_record/relation/spawn_methods.rb +11 -2
  79. data/lib/active_record/relation.rb +77 -34
  80. data/lib/active_record/result.rb +1 -1
  81. data/lib/active_record/sanitization.rb +194 -0
  82. data/lib/active_record/schema_dumper.rb +5 -2
  83. data/lib/active_record/scoping/default.rb +142 -0
  84. data/lib/active_record/scoping/named.rb +202 -0
  85. data/lib/active_record/scoping.rb +152 -0
  86. data/lib/active_record/serialization.rb +1 -43
  87. data/lib/active_record/serializers/xml_serializer.rb +2 -44
  88. data/lib/active_record/session_store.rb +15 -15
  89. data/lib/active_record/store.rb +50 -0
  90. data/lib/active_record/test_case.rb +11 -7
  91. data/lib/active_record/timestamp.rb +16 -3
  92. data/lib/active_record/transactions.rb +5 -5
  93. data/lib/active_record/translation.rb +22 -0
  94. data/lib/active_record/validations/associated.rb +5 -4
  95. data/lib/active_record/validations/uniqueness.rb +4 -4
  96. data/lib/active_record/validations.rb +1 -1
  97. data/lib/active_record/version.rb +2 -2
  98. data/lib/active_record.rb +28 -2
  99. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  100. data/lib/rails/generators/active_record/migration/templates/migration.rb +9 -3
  101. data/lib/rails/generators/active_record/model/model_generator.rb +5 -1
  102. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  103. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  104. metadata +50 -40
  105. checksums.yaml +0 -7
  106. 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 Rubygems:
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.com
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
 
@@ -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 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit. Integer consequat tincidunt felis. Etiam non erat dolor. Vivamus imperdiet nibh sit amet diam eleifend id posuere diam malesuada. Mauris at accumsan sem. Donec id lorem neque. Fusce erat lorem, ornare eu congue vitae, malesuada quis neque. Maecenas vel urna a velit pretium fermentum. Donec tortor enim, tempor venenatis egestas a, tempor sed ipsum. Ut arcu justo, faucibus non imperdiet ac, interdum at diam. Pellentesque ipsum enim, venenatis ut iaculis vitae, 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 tincidunt vel interdum sem. Curabitur eget erat arcu. Praesent eget eros leo. Nam magna enim, sollicitudin vehicula scelerisque in, vulputate ut libero. Praesent varius tincidunt commodo".split
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 "Inserting #{RECORDS} users and exhibits..."
63
- RECORDS.times do
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
- Benchmark.ips(TIME) do |x|
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
- e = Exhibit.new(attrs_first)
123
- e.attributes = attrs_second
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
- User.find(1)
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
- Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first
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 <=> other_money.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+. Setting the value object to +nil+ has the effect of writing +nil+ to all
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.original_build_association_called = false
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
- if select = select_value
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 = disambiguate_condition(table, 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
- model.redefine_method(method_name) do
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
- model.redefine_method(method_name) do
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
- model.redefine_method(method_name) do
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
- model.redefine_method("#{name.to_s.singularize}_ids") do
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
- model.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
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
- model.send(:define_method, dependency_method_name) do
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
- model.send(:define_method, dependency_method_name) do
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
- model.send(:define_method, dependency_method_name) do
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
- model.send(:define_method, dependency_method_name) do
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
- model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
48
- def #{dependency_method_name}
49
- association(#{name.to_sym.inspect}).delete
50
- end
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
- model.redefine_method(dependency_method_name) do
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
- model.redefine_method("build_#{name}") do |*params, &block|
19
+ mixin.redefine_method("build_#{name}") do |*params, &block|
33
20
  association(name).build(*params, &block)
34
21
  end
35
22
 
36
- model.redefine_method("create_#{name}") do |*params, &block|
23
+ mixin.redefine_method("create_#{name}") do |*params, &block|
37
24
  association(name).create(*params, &block)
38
25
  end
39
26
 
40
- model.redefine_method("create_#{name}!") do |*params, &block|
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
- scoped.select(column).except(:includes).map! do |record|
54
- record.send(reflection.association_primary_key)
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. Returns +self+ so method calls may be chained.
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
- transaction do
125
- records.flatten.each do |record|
126
- raise_on_type_mismatch(record)
127
- add_to_target(record) do |r|
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 owner.new_record? || (loaded? && !options[:uniq])
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
- transaction do
310
- delete(target - other_array)
311
-
312
- unless concat(other_array - target)
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
- transaction do
457
- records.each { |record| callback(:before_remove, record) }
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
- delete_records(existing_records, method) if existing_records.any?
460
- records.each { |record| target.delete(record) }
464
+ delete_records(existing_records, method) if existing_records.any?
465
+ records.each { |record| target.delete(record) }
461
466
 
462
- records.each { |record| callback(:after_remove, record) }
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