activerecord 3.0.20 → 3.1.0.beta1
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 +220 -91
- data/README.rdoc +3 -3
- data/examples/performance.rb +88 -109
- data/lib/active_record.rb +6 -2
- data/lib/active_record/aggregations.rb +22 -45
- data/lib/active_record/associations.rb +264 -991
- data/lib/active_record/associations/alias_tracker.rb +85 -0
- data/lib/active_record/associations/association.rb +231 -0
- data/lib/active_record/associations/association_scope.rb +120 -0
- data/lib/active_record/associations/belongs_to_association.rb +40 -60
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +15 -63
- data/lib/active_record/associations/builder/association.rb +53 -0
- data/lib/active_record/associations/builder/belongs_to.rb +85 -0
- data/lib/active_record/associations/builder/collection_association.rb +75 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +63 -0
- data/lib/active_record/associations/builder/has_many.rb +65 -0
- data/lib/active_record/associations/builder/has_one.rb +63 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +524 -0
- data/lib/active_record/associations/collection_proxy.rb +125 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +27 -118
- data/lib/active_record/associations/has_many_association.rb +50 -79
- data/lib/active_record/associations/has_many_through_association.rb +98 -67
- data/lib/active_record/associations/has_one_association.rb +45 -115
- data/lib/active_record/associations/has_one_through_association.rb +21 -25
- data/lib/active_record/associations/join_dependency.rb +215 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +150 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_helper.rb +56 -0
- data/lib/active_record/associations/preloader.rb +177 -0
- data/lib/active_record/associations/preloader/association.rb +126 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +67 -0
- data/lib/active_record/associations/singular_association.rb +55 -0
- data/lib/active_record/associations/through_association.rb +80 -0
- data/lib/active_record/attribute_methods.rb +19 -5
- data/lib/active_record/attribute_methods/before_type_cast.rb +9 -8
- data/lib/active_record/attribute_methods/dirty.rb +8 -2
- data/lib/active_record/attribute_methods/primary_key.rb +33 -13
- data/lib/active_record/attribute_methods/read.rb +17 -17
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -4
- data/lib/active_record/attribute_methods/write.rb +2 -1
- data/lib/active_record/autosave_association.rb +66 -45
- data/lib/active_record/base.rb +445 -273
- data/lib/active_record/callbacks.rb +24 -33
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +106 -13
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +16 -2
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -12
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +16 -16
- data/lib/active_record/connection_adapters/abstract/quoting.rb +61 -22
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -273
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -42
- data/lib/active_record/connection_adapters/abstract_adapter.rb +44 -25
- data/lib/active_record/connection_adapters/column.rb +268 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +686 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +331 -88
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +295 -267
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -7
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +108 -26
- data/lib/active_record/counter_cache.rb +7 -4
- data/lib/active_record/fixtures.rb +174 -192
- data/lib/active_record/identity_map.rb +131 -0
- data/lib/active_record/locking/optimistic.rb +20 -14
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +24 -4
- data/lib/active_record/migration.rb +265 -144
- data/lib/active_record/migration/command_recorder.rb +103 -0
- data/lib/active_record/named_scope.rb +68 -25
- data/lib/active_record/nested_attributes.rb +58 -15
- data/lib/active_record/observer.rb +3 -7
- data/lib/active_record/persistence.rb +58 -38
- data/lib/active_record/query_cache.rb +25 -3
- data/lib/active_record/railtie.rb +21 -12
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/databases.rake +147 -116
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/reflection.rb +176 -44
- data/lib/active_record/relation.rb +125 -49
- data/lib/active_record/relation/batches.rb +7 -5
- data/lib/active_record/relation/calculations.rb +50 -18
- data/lib/active_record/relation/finder_methods.rb +47 -26
- data/lib/active_record/relation/predicate_builder.rb +24 -21
- data/lib/active_record/relation/query_methods.rb +117 -101
- data/lib/active_record/relation/spawn_methods.rb +27 -20
- data/lib/active_record/result.rb +34 -0
- data/lib/active_record/schema.rb +5 -6
- data/lib/active_record/schema_dumper.rb +11 -13
- data/lib/active_record/serialization.rb +2 -2
- data/lib/active_record/serializers/xml_serializer.rb +10 -10
- data/lib/active_record/session_store.rb +8 -2
- data/lib/active_record/test_case.rb +9 -20
- data/lib/active_record/timestamp.rb +21 -9
- data/lib/active_record/transactions.rb +16 -15
- data/lib/active_record/validations.rb +21 -22
- data/lib/active_record/validations/associated.rb +3 -1
- data/lib/active_record/validations/uniqueness.rb +48 -58
- data/lib/active_record/version.rb +3 -3
- data/lib/rails/generators/active_record.rb +6 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +10 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +2 -1
- data/lib/rails/generators/active_record/model/templates/migration.rb +6 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +2 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +2 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +2 -1
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +2 -2
- metadata +106 -77
- checksums.yaml +0 -7
- data/lib/active_record/association_preload.rb +0 -431
- data/lib/active_record/associations/association_collection.rb +0 -572
- data/lib/active_record/associations/association_proxy.rb +0 -304
- data/lib/active_record/associations/through_association_scope.rb +0 -160
@@ -0,0 +1,103 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Migration
|
3
|
+
# ActiveRecord::Migration::CommandRecorder records commands done during
|
4
|
+
# a migration and knows how to reverse those commands. The CommandRecorder
|
5
|
+
# knows how to invert the following commands:
|
6
|
+
#
|
7
|
+
# * add_column
|
8
|
+
# * add_index
|
9
|
+
# * add_timestamp
|
10
|
+
# * create_table
|
11
|
+
# * remove_timestamps
|
12
|
+
# * rename_column
|
13
|
+
# * rename_index
|
14
|
+
# * rename_table
|
15
|
+
class CommandRecorder
|
16
|
+
attr_accessor :commands, :delegate
|
17
|
+
|
18
|
+
def initialize(delegate = nil)
|
19
|
+
@commands = []
|
20
|
+
@delegate = delegate
|
21
|
+
end
|
22
|
+
|
23
|
+
# record +command+. +command+ should be a method name and arguments.
|
24
|
+
# For example:
|
25
|
+
#
|
26
|
+
# recorder.record(:method_name, [:arg1, arg2])
|
27
|
+
def record(*command)
|
28
|
+
@commands << command
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns a list that represents commands that are the inverse of the
|
32
|
+
# commands stored in +commands+. For example:
|
33
|
+
#
|
34
|
+
# recorder.record(:rename_table, [:old, :new])
|
35
|
+
# recorder.inverse # => [:rename_table, [:new, :old]]
|
36
|
+
#
|
37
|
+
# This method will raise an IrreversibleMigration exception if it cannot
|
38
|
+
# invert the +commands+.
|
39
|
+
def inverse
|
40
|
+
@commands.reverse.map { |name, args|
|
41
|
+
method = :"invert_#{name}"
|
42
|
+
raise IrreversibleMigration unless respond_to?(method, true)
|
43
|
+
send(method, args)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def respond_to?(*args) # :nodoc:
|
48
|
+
super || delegate.respond_to?(*args)
|
49
|
+
end
|
50
|
+
|
51
|
+
[:create_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method|
|
52
|
+
class_eval <<-EOV, __FILE__, __LINE__ + 1
|
53
|
+
def #{method}(*args)
|
54
|
+
record(:"#{method}", args)
|
55
|
+
end
|
56
|
+
EOV
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def invert_create_table(args)
|
62
|
+
[:drop_table, args]
|
63
|
+
end
|
64
|
+
|
65
|
+
def invert_rename_table(args)
|
66
|
+
[:rename_table, args.reverse]
|
67
|
+
end
|
68
|
+
|
69
|
+
def invert_add_column(args)
|
70
|
+
[:remove_column, args.first(2)]
|
71
|
+
end
|
72
|
+
|
73
|
+
def invert_rename_index(args)
|
74
|
+
[:rename_index, args.reverse]
|
75
|
+
end
|
76
|
+
|
77
|
+
def invert_rename_column(args)
|
78
|
+
[:rename_column, [args.first] + args.last(2).reverse]
|
79
|
+
end
|
80
|
+
|
81
|
+
def invert_add_index(args)
|
82
|
+
table, columns, _ = *args
|
83
|
+
[:remove_index, [table, {:column => columns}]]
|
84
|
+
end
|
85
|
+
|
86
|
+
def invert_remove_timestamps(args)
|
87
|
+
[:add_timestamps, args]
|
88
|
+
end
|
89
|
+
|
90
|
+
def invert_add_timestamps(args)
|
91
|
+
[:remove_timestamps, args]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Forwards any missing method call to the \target.
|
95
|
+
def method_missing(method, *args, &block)
|
96
|
+
@delegate.send(method, *args, &block)
|
97
|
+
rescue NoMethodError => e
|
98
|
+
raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@delegate}")
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -2,6 +2,7 @@ require 'active_support/core_ext/array'
|
|
2
2
|
require 'active_support/core_ext/hash/except'
|
3
3
|
require 'active_support/core_ext/kernel/singleton_class'
|
4
4
|
require 'active_support/core_ext/object/blank'
|
5
|
+
require 'active_support/core_ext/class/attribute'
|
5
6
|
|
6
7
|
module ActiveRecord
|
7
8
|
# = Active Record Named \Scopes
|
@@ -29,14 +30,16 @@ module ActiveRecord
|
|
29
30
|
if options
|
30
31
|
scoped.apply_finder_options(options)
|
31
32
|
else
|
32
|
-
|
33
|
+
if current_scope
|
34
|
+
current_scope.clone
|
35
|
+
else
|
36
|
+
scope = relation.clone
|
37
|
+
scope.default_scoped = true
|
38
|
+
scope
|
39
|
+
end
|
33
40
|
end
|
34
41
|
end
|
35
42
|
|
36
|
-
def scopes
|
37
|
-
read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
|
38
|
-
end
|
39
|
-
|
40
43
|
# Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query,
|
41
44
|
# such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>.
|
42
45
|
#
|
@@ -48,6 +51,14 @@ module ActiveRecord
|
|
48
51
|
# The above calls to <tt>scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
|
49
52
|
# in effect, represents the query <tt>Shirt.where(:color => 'red')</tt>.
|
50
53
|
#
|
54
|
+
# Note that this is simply 'syntactic sugar' for defining an actual class method:
|
55
|
+
#
|
56
|
+
# class Shirt < ActiveRecord::Base
|
57
|
+
# def self.red
|
58
|
+
# where(:color => 'red')
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
#
|
51
62
|
# Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it
|
52
63
|
# resembles the association object constructed by a <tt>has_many</tt> declaration. For instance,
|
53
64
|
# you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>, <tt>Shirt.red.where(:size => 'small')</tt>.
|
@@ -74,11 +85,31 @@ module ActiveRecord
|
|
74
85
|
# Named \scopes can also be procedural:
|
75
86
|
#
|
76
87
|
# class Shirt < ActiveRecord::Base
|
77
|
-
# scope :colored, lambda {|color| where(:color => color) }
|
88
|
+
# scope :colored, lambda { |color| where(:color => color) }
|
78
89
|
# end
|
79
90
|
#
|
80
91
|
# In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
|
81
92
|
#
|
93
|
+
# On Ruby 1.9 you can use the 'stabby lambda' syntax:
|
94
|
+
#
|
95
|
+
# scope :colored, ->(color) { where(:color => color) }
|
96
|
+
#
|
97
|
+
# Note that scopes defined with \scope will be evaluated when they are defined, rather than
|
98
|
+
# when they are used. For example, the following would be incorrect:
|
99
|
+
#
|
100
|
+
# class Post < ActiveRecord::Base
|
101
|
+
# scope :recent, where('published_at >= ?', Time.now - 1.week)
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# The example above would be 'frozen' to the <tt>Time.now</tt> value when the <tt>Post</tt>
|
105
|
+
# class was defined, and so the resultant SQL query would always be the same. The correct
|
106
|
+
# way to do this would be via a lambda, which will re-evaluate the scope each time
|
107
|
+
# it is called:
|
108
|
+
#
|
109
|
+
# class Post < ActiveRecord::Base
|
110
|
+
# scope :recent, lambda { where('published_at >= ?', Time.now - 1.week) }
|
111
|
+
# end
|
112
|
+
#
|
82
113
|
# Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
|
83
114
|
#
|
84
115
|
# class Shirt < ActiveRecord::Base
|
@@ -97,38 +128,50 @@ module ActiveRecord
|
|
97
128
|
#
|
98
129
|
# Article.published.new.published # => true
|
99
130
|
# Article.published.create.published # => true
|
100
|
-
|
131
|
+
#
|
132
|
+
# Class methods on your model are automatically available
|
133
|
+
# on scopes. Assuming the following setup:
|
134
|
+
#
|
135
|
+
# class Article < ActiveRecord::Base
|
136
|
+
# scope :published, where(:published => true)
|
137
|
+
# scope :featured, where(:featured => true)
|
138
|
+
#
|
139
|
+
# def self.latest_article
|
140
|
+
# order('published_at desc').first
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
# def self.titles
|
144
|
+
# map(&:title)
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
# end
|
148
|
+
#
|
149
|
+
# We are able to call the methods like this:
|
150
|
+
#
|
151
|
+
# Article.published.featured.latest_article
|
152
|
+
# Article.featured.titles
|
153
|
+
|
154
|
+
def scope(name, scope_options = {})
|
101
155
|
name = name.to_sym
|
102
156
|
valid_scope_name?(name)
|
157
|
+
extension = Module.new(&Proc.new) if block_given?
|
103
158
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
options = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options
|
159
|
+
scope_proc = lambda do |*args|
|
160
|
+
options = scope_options.respond_to?(:call) ? scope_options.call(*args) : scope_options
|
161
|
+
options = scoped.apply_finder_options(options) if options.is_a?(Hash)
|
108
162
|
|
109
|
-
relation =
|
110
|
-
scoped.apply_finder_options(options)
|
111
|
-
elsif options
|
112
|
-
scoped.merge(options)
|
113
|
-
else
|
114
|
-
scoped
|
115
|
-
end
|
163
|
+
relation = scoped.merge(options)
|
116
164
|
|
117
165
|
extension ? relation.extending(extension) : relation
|
118
166
|
end
|
119
167
|
|
120
|
-
singleton_class.send(:redefine_method, name, &
|
121
|
-
end
|
122
|
-
|
123
|
-
def named_scope(*args, &block)
|
124
|
-
ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead", caller)
|
125
|
-
scope(*args, &block)
|
168
|
+
singleton_class.send(:redefine_method, name, &scope_proc)
|
126
169
|
end
|
127
170
|
|
128
171
|
protected
|
129
172
|
|
130
173
|
def valid_scope_name?(name)
|
131
|
-
if
|
174
|
+
if respond_to?(name, true)
|
132
175
|
logger.warn "Creating scope :#{name}. " \
|
133
176
|
"Overwriting existing method #{self.name}.#{name}."
|
134
177
|
end
|
@@ -2,6 +2,7 @@ require 'active_support/core_ext/hash/except'
|
|
2
2
|
require 'active_support/core_ext/object/try'
|
3
3
|
require 'active_support/core_ext/object/blank'
|
4
4
|
require 'active_support/core_ext/hash/indifferent_access'
|
5
|
+
require 'active_support/core_ext/class/attribute'
|
5
6
|
|
6
7
|
module ActiveRecord
|
7
8
|
module NestedAttributes #:nodoc:
|
@@ -11,7 +12,7 @@ module ActiveRecord
|
|
11
12
|
extend ActiveSupport::Concern
|
12
13
|
|
13
14
|
included do
|
14
|
-
|
15
|
+
class_attribute :nested_attributes_options, :instance_writer => false
|
15
16
|
self.nested_attributes_options = {}
|
16
17
|
end
|
17
18
|
|
@@ -190,6 +191,34 @@ module ActiveRecord
|
|
190
191
|
# destruction, are saved and destroyed automatically and atomically when
|
191
192
|
# the parent model is saved. This happens inside the transaction initiated
|
192
193
|
# by the parents save method. See ActiveRecord::AutosaveAssociation.
|
194
|
+
#
|
195
|
+
# === Using with attr_accessible
|
196
|
+
#
|
197
|
+
# The use of <tt>attr_accessible</tt> can interfere with nested attributes
|
198
|
+
# if you're not careful. For example, if the <tt>Member</tt> model above
|
199
|
+
# was using <tt>attr_accessible</tt> like this:
|
200
|
+
#
|
201
|
+
# attr_accessible :name
|
202
|
+
#
|
203
|
+
# You would need to modify it to look like this:
|
204
|
+
#
|
205
|
+
# attr_accessible :name, :posts_attributes
|
206
|
+
#
|
207
|
+
# === Validating the presence of a parent model
|
208
|
+
#
|
209
|
+
# If you want to validate that a child record is associated with a parent
|
210
|
+
# record, you can use <tt>validates_presence_of</tt> and
|
211
|
+
# <tt>inverse_of</tt> as this example illustrates:
|
212
|
+
#
|
213
|
+
# class Member < ActiveRecord::Base
|
214
|
+
# has_many :posts, :inverse_of => :member
|
215
|
+
# accepts_nested_attributes_for :posts
|
216
|
+
# end
|
217
|
+
#
|
218
|
+
# class Post < ActiveRecord::Base
|
219
|
+
# belongs_to :member, :inverse_of => :posts
|
220
|
+
# validates_presence_of :member
|
221
|
+
# end
|
193
222
|
module ClassMethods
|
194
223
|
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |_, value| value.blank? } }
|
195
224
|
|
@@ -240,7 +269,11 @@ module ActiveRecord
|
|
240
269
|
if reflection = reflect_on_association(association_name)
|
241
270
|
reflection.options[:autosave] = true
|
242
271
|
add_autosave_association_callbacks(reflection)
|
272
|
+
|
273
|
+
nested_attributes_options = self.nested_attributes_options.dup
|
243
274
|
nested_attributes_options[association_name.to_sym] = options
|
275
|
+
self.nested_attributes_options = nested_attributes_options
|
276
|
+
|
244
277
|
type = (reflection.collection? ? :collection : :one_to_one)
|
245
278
|
|
246
279
|
# def pirate_attributes=(attributes)
|
@@ -287,15 +320,14 @@ module ActiveRecord
|
|
287
320
|
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
|
288
321
|
# then the existing record will be marked for destruction.
|
289
322
|
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
|
290
|
-
options = nested_attributes_options[association_name]
|
323
|
+
options = self.nested_attributes_options[association_name]
|
291
324
|
attributes = attributes.with_indifferent_access
|
292
|
-
check_existing_record = (options[:update_only] || !attributes['id'].blank?)
|
293
325
|
|
294
|
-
if
|
326
|
+
if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) &&
|
295
327
|
(options[:update_only] || record.id.to_s == attributes['id'].to_s)
|
296
328
|
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
|
297
329
|
|
298
|
-
elsif
|
330
|
+
elsif attributes['id'].present?
|
299
331
|
raise_nested_attributes_record_not_found(association_name, attributes['id'])
|
300
332
|
|
301
333
|
elsif !reject_new_record?(association_name, attributes)
|
@@ -336,7 +368,7 @@ module ActiveRecord
|
|
336
368
|
# { :id => '2', :_destroy => true }
|
337
369
|
# ])
|
338
370
|
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
|
339
|
-
options = nested_attributes_options[association_name]
|
371
|
+
options = self.nested_attributes_options[association_name]
|
340
372
|
|
341
373
|
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
342
374
|
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
|
@@ -355,13 +387,13 @@ module ActiveRecord
|
|
355
387
|
end
|
356
388
|
end
|
357
389
|
|
358
|
-
association =
|
390
|
+
association = association(association_name)
|
359
391
|
|
360
392
|
existing_records = if association.loaded?
|
361
|
-
association.
|
393
|
+
association.target
|
362
394
|
else
|
363
395
|
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
|
364
|
-
attribute_ids.
|
396
|
+
attribute_ids.empty? ? [] : association.scoped.where(association.klass.primary_key => attribute_ids)
|
365
397
|
end
|
366
398
|
|
367
399
|
attributes_collection.each do |attributes|
|
@@ -371,11 +403,23 @@ module ActiveRecord
|
|
371
403
|
unless reject_new_record?(association_name, attributes)
|
372
404
|
association.build(attributes.except(*UNASSIGNABLE_KEYS))
|
373
405
|
end
|
374
|
-
|
375
406
|
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
|
376
|
-
|
377
|
-
|
407
|
+
unless association.loaded? || call_reject_if(association_name, attributes)
|
408
|
+
# Make sure we are operating on the actual object which is in the association's
|
409
|
+
# proxy_target array (either by finding it, or adding it if not found)
|
410
|
+
target_record = association.target.detect { |record| record == existing_record }
|
411
|
+
|
412
|
+
if target_record
|
413
|
+
existing_record = target_record
|
414
|
+
else
|
415
|
+
association.add_to_target(existing_record)
|
416
|
+
end
|
378
417
|
|
418
|
+
end
|
419
|
+
|
420
|
+
if !call_reject_if(association_name, attributes)
|
421
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
422
|
+
end
|
379
423
|
else
|
380
424
|
raise_nested_attributes_record_not_found(association_name, attributes['id'])
|
381
425
|
end
|
@@ -403,7 +447,7 @@ module ActiveRecord
|
|
403
447
|
|
404
448
|
def call_reject_if(association_name, attributes)
|
405
449
|
return false if has_destroy_flag?(attributes)
|
406
|
-
case callback = nested_attributes_options[association_name][:reject_if]
|
450
|
+
case callback = self.nested_attributes_options[association_name][:reject_if]
|
407
451
|
when Symbol
|
408
452
|
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
|
409
453
|
when Proc
|
@@ -412,8 +456,7 @@ module ActiveRecord
|
|
412
456
|
end
|
413
457
|
|
414
458
|
def raise_nested_attributes_record_not_found(association_name, record_id)
|
415
|
-
|
416
|
-
raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
|
459
|
+
raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
|
417
460
|
end
|
418
461
|
end
|
419
462
|
end
|
@@ -90,15 +90,11 @@ module ActiveRecord
|
|
90
90
|
#
|
91
91
|
class Observer < ActiveModel::Observer
|
92
92
|
|
93
|
-
def initialize
|
94
|
-
super
|
95
|
-
observed_descendants.each { |klass| add_observer!(klass) }
|
96
|
-
end
|
97
|
-
|
98
93
|
protected
|
99
94
|
|
100
|
-
def
|
101
|
-
|
95
|
+
def observed_classes
|
96
|
+
klasses = super
|
97
|
+
klasses + klasses.map { |klass| klass.descendants }.flatten
|
102
98
|
end
|
103
99
|
|
104
100
|
def add_observer!(klass)
|
@@ -18,9 +18,6 @@ module ActiveRecord
|
|
18
18
|
!(new_record? || destroyed?)
|
19
19
|
end
|
20
20
|
|
21
|
-
# :call-seq:
|
22
|
-
# save(options)
|
23
|
-
#
|
24
21
|
# Saves the model.
|
25
22
|
#
|
26
23
|
# If the model is new a record gets created in the database, otherwise
|
@@ -36,11 +33,7 @@ module ActiveRecord
|
|
36
33
|
# +save+ returns +false+. See ActiveRecord::Callbacks for further
|
37
34
|
# details.
|
38
35
|
def save(*)
|
39
|
-
|
40
|
-
create_or_update
|
41
|
-
rescue ActiveRecord::RecordInvalid
|
42
|
-
false
|
43
|
-
end
|
36
|
+
create_or_update
|
44
37
|
end
|
45
38
|
|
46
39
|
# Saves the model.
|
@@ -71,7 +64,10 @@ module ActiveRecord
|
|
71
64
|
# callbacks, Observer methods, or any <tt>:dependent</tt> association
|
72
65
|
# options, use <tt>#destroy</tt>.
|
73
66
|
def delete
|
74
|
-
|
67
|
+
if persisted?
|
68
|
+
self.class.delete(id)
|
69
|
+
IdentityMap.remove(self) if IdentityMap.enabled?
|
70
|
+
end
|
75
71
|
@destroyed = true
|
76
72
|
freeze
|
77
73
|
end
|
@@ -79,10 +75,17 @@ module ActiveRecord
|
|
79
75
|
# Deletes the record in the database and freezes this instance to reflect
|
80
76
|
# that no changes should be made (since they can't be persisted).
|
81
77
|
def destroy
|
82
|
-
destroy_associations
|
83
|
-
|
84
78
|
if persisted?
|
85
|
-
|
79
|
+
IdentityMap.remove(self) if IdentityMap.enabled?
|
80
|
+
pk = self.class.primary_key
|
81
|
+
column = self.class.columns_hash[pk]
|
82
|
+
substitute = connection.substitute_at(column, 0)
|
83
|
+
|
84
|
+
relation = self.class.unscoped.where(
|
85
|
+
self.class.arel_table[pk].eq(substitute))
|
86
|
+
|
87
|
+
relation.bind_values = [[column, id]]
|
88
|
+
relation.delete_all
|
86
89
|
end
|
87
90
|
|
88
91
|
@destroyed = true
|
@@ -105,6 +108,7 @@ module ActiveRecord
|
|
105
108
|
became.instance_variable_set("@attributes_cache", @attributes_cache)
|
106
109
|
became.instance_variable_set("@new_record", new_record?)
|
107
110
|
became.instance_variable_set("@destroyed", destroyed?)
|
111
|
+
became.type = klass.name unless self.class.descends_from_active_record?
|
108
112
|
became
|
109
113
|
end
|
110
114
|
|
@@ -123,25 +127,44 @@ module ActiveRecord
|
|
123
127
|
save(:validate => false)
|
124
128
|
end
|
125
129
|
|
130
|
+
# Updates a single attribute of an object, without calling save.
|
131
|
+
#
|
132
|
+
# * Validation is skipped.
|
133
|
+
# * Callbacks are skipped.
|
134
|
+
# * updated_at/updated_on column is not updated if that column is available.
|
135
|
+
#
|
136
|
+
def update_column(name, value)
|
137
|
+
name = name.to_s
|
138
|
+
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
|
139
|
+
raise ActiveRecordError, "can not update on a new record object" unless persisted?
|
140
|
+
raw_write_attribute(name, value)
|
141
|
+
self.class.update_all({ name => value }, self.class.primary_key => id) == 1
|
142
|
+
end
|
143
|
+
|
126
144
|
# Updates the attributes of the model from the passed-in hash and saves the
|
127
145
|
# record, all wrapped in a transaction. If the object is invalid, the saving
|
128
146
|
# will fail and false will be returned.
|
129
|
-
|
147
|
+
#
|
148
|
+
# When updating model attributes, mass-assignment security protection is respected.
|
149
|
+
# If no +:as+ option is supplied then the +:default+ scope will be used.
|
150
|
+
# If you want to bypass the protection given by +attr_protected+ and
|
151
|
+
# +attr_accessible+ then you can do so using the +:without_protection+ option.
|
152
|
+
def update_attributes(attributes, options = {})
|
130
153
|
# The following transaction covers any possible database side-effects of the
|
131
154
|
# attributes assignment. For example, setting the IDs of a child collection.
|
132
155
|
with_transaction_returning_status do
|
133
|
-
self.attributes
|
156
|
+
self.assign_attributes(attributes, options)
|
134
157
|
save
|
135
158
|
end
|
136
159
|
end
|
137
160
|
|
138
161
|
# Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
|
139
162
|
# of +save+, so an exception is raised if the record is invalid.
|
140
|
-
def update_attributes!(attributes)
|
163
|
+
def update_attributes!(attributes, options = {})
|
141
164
|
# The following transaction covers any possible database side-effects of the
|
142
165
|
# attributes assignment. For example, setting the IDs of a child collection.
|
143
166
|
with_transaction_returning_status do
|
144
|
-
self.attributes
|
167
|
+
self.assign_attributes(attributes, options)
|
145
168
|
save!
|
146
169
|
end
|
147
170
|
end
|
@@ -204,7 +227,12 @@ module ActiveRecord
|
|
204
227
|
def reload(options = nil)
|
205
228
|
clear_aggregation_cache
|
206
229
|
clear_association_cache
|
207
|
-
|
230
|
+
|
231
|
+
IdentityMap.without do
|
232
|
+
fresh_object = self.class.unscoped { self.class.find(self.id, options) }
|
233
|
+
@attributes.update(fresh_object.instance_variable_get('@attributes'))
|
234
|
+
end
|
235
|
+
|
208
236
|
@attributes_cache = {}
|
209
237
|
self
|
210
238
|
end
|
@@ -232,6 +260,7 @@ module ActiveRecord
|
|
232
260
|
def touch(name = nil)
|
233
261
|
attributes = timestamp_attributes_for_update_in_model
|
234
262
|
attributes << name if name
|
263
|
+
|
235
264
|
unless attributes.empty?
|
236
265
|
current_time = current_time_from_proper_timezone
|
237
266
|
changes = {}
|
@@ -240,18 +269,15 @@ module ActiveRecord
|
|
240
269
|
changes[column.to_s] = write_attribute(column.to_s, current_time)
|
241
270
|
end
|
242
271
|
|
272
|
+
changes[self.class.locking_column] = increment_lock if locking_enabled?
|
273
|
+
|
243
274
|
@changed_attributes.except!(*changes.keys)
|
244
275
|
primary_key = self.class.primary_key
|
245
|
-
self.class.
|
276
|
+
self.class.update_all(changes, { primary_key => self[primary_key] }) == 1
|
246
277
|
end
|
247
278
|
end
|
248
279
|
|
249
280
|
private
|
250
|
-
|
251
|
-
# A hook to be overriden by association modules.
|
252
|
-
def destroy_associations
|
253
|
-
end
|
254
|
-
|
255
281
|
def create_or_update
|
256
282
|
raise ReadOnlyRecord if readonly?
|
257
283
|
result = new_record? ? create : update
|
@@ -263,26 +289,21 @@ module ActiveRecord
|
|
263
289
|
def update(attribute_names = @attributes.keys)
|
264
290
|
attributes_with_values = arel_attributes_values(false, false, attribute_names)
|
265
291
|
return 0 if attributes_with_values.empty?
|
266
|
-
self.class
|
292
|
+
klass = self.class
|
293
|
+
stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
|
294
|
+
klass.connection.update stmt.to_sql
|
267
295
|
end
|
268
296
|
|
269
297
|
# Creates a record with values matching those of the instance attributes
|
270
298
|
# and returns its id.
|
271
299
|
def create
|
272
|
-
|
273
|
-
self.id = connection.next_sequence_value(self.class.sequence_name)
|
274
|
-
end
|
300
|
+
attributes_values = arel_attributes_values(!id.nil?)
|
275
301
|
|
276
|
-
|
277
|
-
|
278
|
-
new_id = if attributes_values.empty?
|
279
|
-
self.class.unscoped.insert connection.empty_insert_statement_value
|
280
|
-
else
|
281
|
-
self.class.unscoped.insert attributes_values
|
282
|
-
end
|
302
|
+
new_id = self.class.unscoped.insert attributes_values
|
283
303
|
|
284
304
|
self.id ||= new_id
|
285
305
|
|
306
|
+
IdentityMap.add(self) if IdentityMap.enabled?
|
286
307
|
@new_record = false
|
287
308
|
id
|
288
309
|
end
|
@@ -292,10 +313,9 @@ module ActiveRecord
|
|
292
313
|
# that a new instance, or one populated from a passed-in Hash, still has all the attributes
|
293
314
|
# that instances loaded from the database would.
|
294
315
|
def attributes_from_column_definition
|
295
|
-
self.class.columns.
|
296
|
-
|
297
|
-
|
298
|
-
end
|
316
|
+
Hash[self.class.columns.map do |column|
|
317
|
+
[column.name, column.default]
|
318
|
+
end]
|
299
319
|
end
|
300
320
|
end
|
301
321
|
end
|