activerecord 3.0.0.beta4 → 3.0.0.rc
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.
- data/CHANGELOG +267 -254
- data/README.rdoc +222 -0
- data/examples/performance.rb +9 -9
- data/lib/active_record/aggregations.rb +3 -4
- data/lib/active_record/association_preload.rb +15 -10
- data/lib/active_record/associations.rb +54 -37
- data/lib/active_record/associations/association_collection.rb +43 -17
- data/lib/active_record/associations/association_proxy.rb +2 -0
- data/lib/active_record/associations/belongs_to_association.rb +1 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +22 -7
- data/lib/active_record/associations/has_many_association.rb +6 -1
- data/lib/active_record/associations/has_many_through_association.rb +1 -0
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/has_one_through_association.rb +1 -0
- data/lib/active_record/associations/through_association_scope.rb +3 -2
- data/lib/active_record/attribute_methods.rb +1 -0
- data/lib/active_record/autosave_association.rb +4 -6
- data/lib/active_record/base.rb +106 -240
- data/lib/active_record/callbacks.rb +4 -25
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +22 -29
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +2 -8
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +10 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +56 -7
- data/lib/active_record/connection_adapters/abstract_adapter.rb +10 -18
- data/lib/active_record/connection_adapters/mysql_adapter.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +65 -69
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +19 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +20 -46
- data/lib/active_record/counter_cache.rb +14 -4
- data/lib/active_record/dynamic_finder_match.rb +9 -0
- data/lib/active_record/dynamic_scope_match.rb +7 -0
- data/lib/active_record/errors.rb +3 -0
- data/lib/active_record/fixtures.rb +5 -6
- data/lib/active_record/locale/en.yml +1 -1
- data/lib/active_record/locking/optimistic.rb +1 -0
- data/lib/active_record/log_subscriber.rb +48 -0
- data/lib/active_record/migration.rb +64 -37
- data/lib/active_record/named_scope.rb +33 -19
- data/lib/active_record/nested_attributes.rb +17 -13
- data/lib/active_record/observer.rb +13 -6
- data/lib/active_record/persistence.rb +55 -22
- data/lib/active_record/query_cache.rb +1 -0
- data/lib/active_record/railtie.rb +14 -8
- data/lib/active_record/railties/controller_runtime.rb +2 -2
- data/lib/active_record/railties/databases.rake +63 -33
- data/lib/active_record/reflection.rb +46 -28
- data/lib/active_record/relation.rb +38 -24
- data/lib/active_record/relation/finder_methods.rb +5 -5
- data/lib/active_record/relation/predicate_builder.rb +2 -4
- data/lib/active_record/relation/query_methods.rb +134 -115
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/schema.rb +2 -0
- data/lib/active_record/schema_dumper.rb +15 -12
- data/lib/active_record/serialization.rb +2 -0
- data/lib/active_record/session_store.rb +93 -79
- data/lib/active_record/test_case.rb +3 -0
- data/lib/active_record/timestamp.rb +49 -29
- data/lib/active_record/transactions.rb +5 -2
- data/lib/active_record/validations.rb +5 -2
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +1 -1
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -6
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- metadata +27 -14
- data/README +0 -351
- data/lib/active_record/railties/log_subscriber.rb +0 -32
@@ -1,4 +1,9 @@
|
|
1
1
|
module ActiveRecord
|
2
|
+
|
3
|
+
# = Active Record Dynamic Finder Match
|
4
|
+
#
|
5
|
+
# Provides dynamic attribute-based finders such as <tt>find_by_country</tt>
|
6
|
+
# if, for example, the <tt>Person</tt> has an attribute with that name.
|
2
7
|
class DynamicFinderMatch
|
3
8
|
def self.match(method)
|
4
9
|
df_match = self.new(method)
|
@@ -37,6 +42,10 @@ module ActiveRecord
|
|
37
42
|
@finder == :first && !@instantiator.nil?
|
38
43
|
end
|
39
44
|
|
45
|
+
def creator?
|
46
|
+
@finder == :first && @instantiator == :create
|
47
|
+
end
|
48
|
+
|
40
49
|
def bang?
|
41
50
|
@bang
|
42
51
|
end
|
@@ -1,4 +1,11 @@
|
|
1
1
|
module ActiveRecord
|
2
|
+
|
3
|
+
# = Active Record Dynamic Scope Match
|
4
|
+
#
|
5
|
+
# Provides dynamic attribute-based scopes such as <tt>scoped_by_price(4.99)</tt>
|
6
|
+
# if, for example, the <tt>Product</tt> has an attribute with that name. You can
|
7
|
+
# chain more <tt>scoped_by_* </tt> methods after the other. It acts like a named
|
8
|
+
# scope except that it's dynamic.
|
2
9
|
class DynamicScopeMatch
|
3
10
|
def self.match(method)
|
4
11
|
ds_match = self.new(method)
|
data/lib/active_record/errors.rb
CHANGED
@@ -664,14 +664,13 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
|
|
664
664
|
end
|
665
665
|
|
666
666
|
def has_primary_key_column?
|
667
|
-
@has_primary_key_column ||=
|
668
|
-
model_class.columns.
|
667
|
+
@has_primary_key_column ||= primary_key_name &&
|
668
|
+
model_class.columns.any? { |c| c.name == primary_key_name }
|
669
669
|
end
|
670
670
|
|
671
671
|
def timestamp_column_names
|
672
|
-
@timestamp_column_names ||=
|
673
|
-
|
674
|
-
end
|
672
|
+
@timestamp_column_names ||=
|
673
|
+
%w(created_at created_on updated_at updated_on) & column_names
|
675
674
|
end
|
676
675
|
|
677
676
|
def inheritance_column_name
|
@@ -872,7 +871,7 @@ module ActiveRecord
|
|
872
871
|
table_names.each do |table_name|
|
873
872
|
table_name = table_name.to_s.tr('./', '_')
|
874
873
|
|
875
|
-
|
874
|
+
redefine_method(table_name) do |*fixtures|
|
876
875
|
force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
|
877
876
|
|
878
877
|
@fixture_cache[table_name] ||= {}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
|
+
def self.runtime=(value)
|
4
|
+
Thread.current["active_record_sql_runtime"] = value
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.runtime
|
8
|
+
Thread.current["active_record_sql_runtime"] ||= 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.reset_runtime
|
12
|
+
rt, self.runtime = runtime, 0
|
13
|
+
rt
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
super
|
18
|
+
@odd_or_even = false
|
19
|
+
end
|
20
|
+
|
21
|
+
def sql(event)
|
22
|
+
self.class.runtime += event.duration
|
23
|
+
return unless logger.debug?
|
24
|
+
|
25
|
+
name = '%s (%.1fms)' % [event.payload[:name], event.duration]
|
26
|
+
sql = event.payload[:sql].squeeze(' ')
|
27
|
+
|
28
|
+
if odd?
|
29
|
+
name = color(name, CYAN, true)
|
30
|
+
sql = color(sql, nil, true)
|
31
|
+
else
|
32
|
+
name = color(name, MAGENTA, true)
|
33
|
+
end
|
34
|
+
|
35
|
+
debug " #{name} #{sql}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def odd?
|
39
|
+
@odd_or_even = !@odd_or_even
|
40
|
+
end
|
41
|
+
|
42
|
+
def logger
|
43
|
+
ActiveRecord::Base.logger
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
ActiveRecord::LogSubscriber.attach_to :active_record
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_support/core_ext/kernel/singleton_class'
|
2
|
+
require 'active_support/core_ext/module/aliasing'
|
2
3
|
|
3
4
|
module ActiveRecord
|
4
5
|
# Exception that can be raised to stop migrations from going backwards.
|
@@ -29,11 +30,15 @@ module ActiveRecord
|
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
33
|
+
# = Active Record Migrations
|
34
|
+
#
|
35
|
+
# Migrations can manage the evolution of a schema used by several physical
|
36
|
+
# databases. It's a solution to the common problem of adding a field to make
|
37
|
+
# a new feature work in your local database, but being unsure of how to
|
38
|
+
# push that change to other developers and to the production server. With
|
39
|
+
# migrations, you can describe the transformations in self-contained classes
|
40
|
+
# that can be checked into version control systems and executed against
|
41
|
+
# another database that might be one, two, or five versions behind.
|
37
42
|
#
|
38
43
|
# Example of a simple migration:
|
39
44
|
#
|
@@ -47,10 +52,13 @@ module ActiveRecord
|
|
47
52
|
# end
|
48
53
|
# end
|
49
54
|
#
|
50
|
-
# This migration will add a boolean flag to the accounts table and remove it
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
55
|
+
# This migration will add a boolean flag to the accounts table and remove it
|
56
|
+
# if you're backing out of the migration. It shows how all migrations have
|
57
|
+
# two class methods +up+ and +down+ that describes the transformations
|
58
|
+
# required to implement or remove the migration. These methods can consist
|
59
|
+
# of both the migration specific methods like add_column and remove_column,
|
60
|
+
# but may also contain regular Ruby code for generating data needed for the
|
61
|
+
# transformations.
|
54
62
|
#
|
55
63
|
# Example of a more complex migration that also needs to initialize data:
|
56
64
|
#
|
@@ -64,7 +72,9 @@ module ActiveRecord
|
|
64
72
|
# t.integer :position
|
65
73
|
# end
|
66
74
|
#
|
67
|
-
# SystemSetting.create
|
75
|
+
# SystemSetting.create :name => "notice",
|
76
|
+
# :label => "Use notice?",
|
77
|
+
# :value => 1
|
68
78
|
# end
|
69
79
|
#
|
70
80
|
# def self.down
|
@@ -72,35 +82,49 @@ module ActiveRecord
|
|
72
82
|
# end
|
73
83
|
# end
|
74
84
|
#
|
75
|
-
# This migration first adds the system_settings table, then creates the very
|
76
|
-
#
|
77
|
-
#
|
85
|
+
# This migration first adds the system_settings table, then creates the very
|
86
|
+
# first row in it using the Active Record model that relies on the table. It
|
87
|
+
# also uses the more advanced create_table syntax where you can specify a
|
88
|
+
# complete table schema in one block call.
|
78
89
|
#
|
79
90
|
# == Available transformations
|
80
91
|
#
|
81
|
-
# * <tt>create_table(name, options)</tt> Creates a table called +name+ and
|
82
|
-
#
|
83
|
-
#
|
92
|
+
# * <tt>create_table(name, options)</tt> Creates a table called +name+ and
|
93
|
+
# makes the table object available to a block that can then add columns to it,
|
94
|
+
# following the same format as add_column. See example above. The options hash
|
95
|
+
# is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create
|
96
|
+
# table definition.
|
84
97
|
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
|
85
|
-
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
|
86
|
-
#
|
98
|
+
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
|
99
|
+
# to +new_name+.
|
100
|
+
# * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column
|
101
|
+
# to the table called +table_name+
|
87
102
|
# named +column_name+ specified to be one of the following types:
|
88
|
-
# <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>,
|
89
|
-
# <tt>:
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
# * <tt>
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
103
|
+
# <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>,
|
104
|
+
# <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
|
105
|
+
# <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. A default value can be
|
106
|
+
# specified by passing an +options+ hash like <tt>{ :default => 11 }</tt>.
|
107
|
+
# Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g.
|
108
|
+
# <tt>{ :limit => 50, :null => false }</tt>) -- see
|
109
|
+
# ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
|
110
|
+
# * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames
|
111
|
+
# a column but keeps the type and content.
|
112
|
+
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
|
113
|
+
# the column to a different type using the same parameters as add_column.
|
114
|
+
# * <tt>remove_column(table_name, column_name)</tt>: Removes the column named
|
115
|
+
# +column_name+ from the table called +table_name+.
|
116
|
+
# * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
|
117
|
+
# with the name of the column. Other options include
|
118
|
+
# <tt>:name</tt> and <tt>:unique</tt> (e.g.
|
119
|
+
# <tt>{ :name => "users_name_index", :unique => true }</tt>).
|
120
|
+
# * <tt>remove_index(table_name, index_name)</tt>: Removes the index specified
|
121
|
+
# by +index_name+.
|
99
122
|
#
|
100
123
|
# == Irreversible transformations
|
101
124
|
#
|
102
|
-
# Some transformations are destructive in a manner that cannot be reversed.
|
103
|
-
# an <tt>ActiveRecord::IrreversibleMigration</tt>
|
125
|
+
# Some transformations are destructive in a manner that cannot be reversed.
|
126
|
+
# Migrations of that kind should raise an <tt>ActiveRecord::IrreversibleMigration</tt>
|
127
|
+
# exception in their +down+ method.
|
104
128
|
#
|
105
129
|
# == Running migrations from within Rails
|
106
130
|
#
|
@@ -110,13 +134,15 @@ module ActiveRecord
|
|
110
134
|
# rails generate migration MyNewMigration
|
111
135
|
#
|
112
136
|
# where MyNewMigration is the name of your migration. The generator will
|
113
|
-
# create an empty migration file <tt>timestamp_my_new_migration.rb</tt>
|
114
|
-
# directory where <tt>timestamp</tt> is the
|
137
|
+
# create an empty migration file <tt>timestamp_my_new_migration.rb</tt>
|
138
|
+
# in the <tt>db/migrate/</tt> directory where <tt>timestamp</tt> is the
|
139
|
+
# UTC formatted date and time that the migration was generated.
|
115
140
|
#
|
116
141
|
# You may then edit the <tt>self.up</tt> and <tt>self.down</tt> methods of
|
117
142
|
# MyNewMigration.
|
118
143
|
#
|
119
144
|
# There is a special syntactic shortcut to generate migrations that add fields to a table.
|
145
|
+
#
|
120
146
|
# rails generate migration add_fieldname_to_tablename fieldname:string
|
121
147
|
#
|
122
148
|
# This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
|
@@ -191,9 +217,10 @@ module ActiveRecord
|
|
191
217
|
#
|
192
218
|
# == Using a model after changing its table
|
193
219
|
#
|
194
|
-
# Sometimes you'll want to add a column in a migration and populate it
|
195
|
-
#
|
196
|
-
#
|
220
|
+
# Sometimes you'll want to add a column in a migration and populate it
|
221
|
+
# immediately after. In that case, you'll need to make a call to
|
222
|
+
# <tt>Base#reset_column_information</tt> in order to ensure that the model has the
|
223
|
+
# latest column data from after the new column was added. Example:
|
197
224
|
#
|
198
225
|
# class AddPeopleSalary < ActiveRecord::Migration
|
199
226
|
# def self.up
|
@@ -257,7 +284,7 @@ module ActiveRecord
|
|
257
284
|
#
|
258
285
|
# config.active_record.timestamped_migrations = false
|
259
286
|
#
|
260
|
-
# In
|
287
|
+
# In application.rb.
|
261
288
|
#
|
262
289
|
class Migration
|
263
290
|
@@verbose = true
|
@@ -4,11 +4,12 @@ require 'active_support/core_ext/kernel/singleton_class'
|
|
4
4
|
require 'active_support/core_ext/object/blank'
|
5
5
|
|
6
6
|
module ActiveRecord
|
7
|
+
# = Active Record Named \Scopes
|
7
8
|
module NamedScope
|
8
9
|
extend ActiveSupport::Concern
|
9
10
|
|
10
11
|
module ClassMethods
|
11
|
-
# Returns an anonymous scope.
|
12
|
+
# Returns an anonymous \scope.
|
12
13
|
#
|
13
14
|
# posts = Post.scoped
|
14
15
|
# posts.size # Fires "select count(*) from posts" and returns the count
|
@@ -18,16 +19,17 @@ module ActiveRecord
|
|
18
19
|
# fruits = fruits.where(:colour => 'red') if options[:red_only]
|
19
20
|
# fruits = fruits.limit(10) if limited?
|
20
21
|
#
|
21
|
-
# Anonymous \scopes tend to be useful when procedurally generating complex
|
22
|
-
# intermediate values (scopes) around as first-class
|
22
|
+
# Anonymous \scopes tend to be useful when procedurally generating complex
|
23
|
+
# queries, where passing intermediate values (\scopes) around as first-class
|
24
|
+
# objects is convenient.
|
23
25
|
#
|
24
|
-
# You can define a scope that applies to all finders using
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
# You can define a \scope that applies to all finders using
|
27
|
+
# ActiveRecord::Base.default_scope.
|
28
|
+
def scoped(options = nil)
|
29
|
+
if options
|
30
|
+
scoped.apply_finder_options(options)
|
29
31
|
else
|
30
|
-
current_scoped_methods ?
|
32
|
+
current_scoped_methods ? relation.merge(current_scoped_methods) : relation.clone
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
@@ -35,7 +37,7 @@ module ActiveRecord
|
|
35
37
|
read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
|
36
38
|
end
|
37
39
|
|
38
|
-
# Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
|
40
|
+
# Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query,
|
39
41
|
# such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>.
|
40
42
|
#
|
41
43
|
# class Shirt < ActiveRecord::Base
|
@@ -85,28 +87,40 @@ module ActiveRecord
|
|
85
87
|
# end
|
86
88
|
def scope(name, scope_options = {}, &block)
|
87
89
|
name = name.to_sym
|
90
|
+
valid_scope_name?(name)
|
88
91
|
|
89
|
-
|
90
|
-
logger.warn "Creating scope :#{name}. " \
|
91
|
-
"Overwriting existing method #{self.name}.#{name}."
|
92
|
-
end
|
92
|
+
extension = Module.new(&block) if block_given?
|
93
93
|
|
94
94
|
scopes[name] = lambda do |*args|
|
95
95
|
options = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options
|
96
96
|
|
97
|
-
relation =
|
98
|
-
|
99
|
-
|
97
|
+
relation = if options.is_a?(Hash)
|
98
|
+
scoped.apply_finder_options(options)
|
99
|
+
elsif options
|
100
|
+
scoped.merge(options)
|
101
|
+
else
|
102
|
+
scoped
|
103
|
+
end
|
104
|
+
|
105
|
+
extension ? relation.extending(extension) : relation
|
100
106
|
end
|
101
107
|
|
102
|
-
singleton_class.send
|
108
|
+
singleton_class.send(:redefine_method, name, &scopes[name])
|
103
109
|
end
|
104
110
|
|
105
111
|
def named_scope(*args, &block)
|
106
112
|
ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead", caller)
|
107
113
|
scope(*args, &block)
|
108
114
|
end
|
109
|
-
end
|
110
115
|
|
116
|
+
protected
|
117
|
+
|
118
|
+
def valid_scope_name?(name)
|
119
|
+
if !scopes[name] && respond_to?(name, true)
|
120
|
+
logger.warn "Creating scope :#{name}. " \
|
121
|
+
"Overwriting existing method #{self.name}.#{name}."
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
111
125
|
end
|
112
126
|
end
|
@@ -15,7 +15,7 @@ module ActiveRecord
|
|
15
15
|
self.nested_attributes_options = {}
|
16
16
|
end
|
17
17
|
|
18
|
-
#
|
18
|
+
# = Active Record Nested Attributes
|
19
19
|
#
|
20
20
|
# Nested attributes allow you to save attributes on associated records
|
21
21
|
# through the parent. By default nested attribute updating is turned off,
|
@@ -25,6 +25,7 @@ module ActiveRecord
|
|
25
25
|
#
|
26
26
|
# The attribute writer is named after the association, which means that
|
27
27
|
# in the following example, two new methods are added to your model:
|
28
|
+
#
|
28
29
|
# <tt>author_attributes=(attributes)</tt> and
|
29
30
|
# <tt>pages_attributes=(attributes)</tt>.
|
30
31
|
#
|
@@ -132,7 +133,7 @@ module ActiveRecord
|
|
132
133
|
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
|
133
134
|
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
|
134
135
|
#
|
135
|
-
#
|
136
|
+
# Alternatively, :reject_if also accepts a symbol for using methods:
|
136
137
|
#
|
137
138
|
# class Member < ActiveRecord::Base
|
138
139
|
# has_many :posts
|
@@ -144,7 +145,7 @@ module ActiveRecord
|
|
144
145
|
# accepts_nested_attributes_for :posts, :reject_if => :reject_posts
|
145
146
|
#
|
146
147
|
# def reject_posts(attributed)
|
147
|
-
# attributed['title].blank?
|
148
|
+
# attributed['title'].blank?
|
148
149
|
# end
|
149
150
|
# end
|
150
151
|
#
|
@@ -212,7 +213,7 @@ module ActiveRecord
|
|
212
213
|
# that will reject a record where all the attributes are blank.
|
213
214
|
# [:limit]
|
214
215
|
# Allows you to specify the maximum number of the associated records that
|
215
|
-
# can be
|
216
|
+
# can be processed with the nested attributes. If the size of the
|
216
217
|
# nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
|
217
218
|
# exception is raised. If omitted, any number associations can be processed.
|
218
219
|
# Note that the :limit option is only applicable to one-to-many associations.
|
@@ -278,7 +279,7 @@ module ActiveRecord
|
|
278
279
|
# Assigns the given attributes to the association.
|
279
280
|
#
|
280
281
|
# If update_only is false and the given attributes include an <tt>:id</tt>
|
281
|
-
# that matches the existing record
|
282
|
+
# that matches the existing record's id, then the existing record will be
|
282
283
|
# modified. If update_only is true, a new record is only created when no
|
283
284
|
# object exists. Otherwise a new record will be built.
|
284
285
|
#
|
@@ -295,7 +296,9 @@ module ActiveRecord
|
|
295
296
|
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy])
|
296
297
|
|
297
298
|
elsif attributes['id']
|
298
|
-
|
299
|
+
existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
|
300
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
301
|
+
self.send(association_name.to_s+'=', existing_record)
|
299
302
|
|
300
303
|
elsif !reject_new_record?(association_name, attributes)
|
301
304
|
method = "build_#{association_name}"
|
@@ -365,11 +368,16 @@ module ActiveRecord
|
|
365
368
|
unless reject_new_record?(association_name, attributes)
|
366
369
|
association.build(attributes.except(*UNASSIGNABLE_KEYS))
|
367
370
|
end
|
371
|
+
|
372
|
+
elsif existing_records.count == 0 #Existing record but not yet associated
|
373
|
+
existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
|
374
|
+
association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
|
375
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
376
|
+
|
368
377
|
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
|
369
378
|
association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
|
370
379
|
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
371
|
-
|
372
|
-
raise_nested_attributes_record_not_found(association_name, attributes['id'])
|
380
|
+
|
373
381
|
end
|
374
382
|
end
|
375
383
|
end
|
@@ -389,7 +397,7 @@ module ActiveRecord
|
|
389
397
|
ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
|
390
398
|
end
|
391
399
|
|
392
|
-
# Determines if a new record should be
|
400
|
+
# Determines if a new record should be built by checking for
|
393
401
|
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
|
394
402
|
# association and evaluates to +true+.
|
395
403
|
def reject_new_record?(association_name, attributes)
|
@@ -405,9 +413,5 @@ module ActiveRecord
|
|
405
413
|
end
|
406
414
|
end
|
407
415
|
|
408
|
-
def raise_nested_attributes_record_not_found(association_name, record_id)
|
409
|
-
reflection = self.class.reflect_on_association(association_name)
|
410
|
-
raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
|
411
|
-
end
|
412
416
|
end
|
413
417
|
end
|