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
@@ -20,14 +20,13 @@ module ActiveRecord
|
|
20
20
|
# be cached. Usually caching only pays off for attributes with expensive conversion
|
21
21
|
# methods, like time related columns (e.g. +created_at+, +updated_at+).
|
22
22
|
def cache_attributes(*attribute_names)
|
23
|
-
attribute_names.
|
23
|
+
cached_attributes.merge attribute_names.map { |attr| attr.to_s }
|
24
24
|
end
|
25
25
|
|
26
26
|
# Returns the attributes which are cached. By default time related columns
|
27
27
|
# with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
|
28
28
|
def cached_attributes
|
29
|
-
@cached_attributes ||=
|
30
|
-
columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map{|col| col.name}.to_set
|
29
|
+
@cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
|
31
30
|
end
|
32
31
|
|
33
32
|
# Returns +true+ if the provided attribute is being cached.
|
@@ -37,7 +36,7 @@ module ActiveRecord
|
|
37
36
|
|
38
37
|
protected
|
39
38
|
def define_method_attribute(attr_name)
|
40
|
-
if
|
39
|
+
if serialized_attributes.include?(attr_name)
|
41
40
|
define_read_method_for_serialized_attribute(attr_name)
|
42
41
|
else
|
43
42
|
define_read_method(attr_name, attr_name, columns_hash[attr_name])
|
@@ -49,20 +48,25 @@ module ActiveRecord
|
|
49
48
|
end
|
50
49
|
|
51
50
|
private
|
51
|
+
def cacheable_column?(column)
|
52
|
+
serialized_attributes.include?(column.name) || attribute_types_cached_by_default.include?(column.type)
|
53
|
+
end
|
54
|
+
|
52
55
|
# Define read method for serialized attribute.
|
53
56
|
def define_read_method_for_serialized_attribute(attr_name)
|
54
|
-
|
57
|
+
access_code = "@attributes_cache['#{attr_name}'] ||= @attributes['#{attr_name}']"
|
58
|
+
generated_attribute_methods.module_eval("def _#{attr_name}; #{access_code}; end; alias #{attr_name} _#{attr_name}", __FILE__, __LINE__)
|
55
59
|
end
|
56
60
|
|
57
61
|
# Define an attribute reader method. Cope with nil column.
|
58
62
|
# method_name is the same as attr_name except when a non-standard primary key is used,
|
59
63
|
# we still define #id as an accessor for the key
|
60
64
|
def define_read_method(method_name, attr_name, column)
|
61
|
-
cast_code = column.type_cast_code('v')
|
62
|
-
access_code =
|
65
|
+
cast_code = column.type_cast_code('v')
|
66
|
+
access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}"
|
63
67
|
|
64
68
|
unless attr_name.to_s == self.primary_key.to_s
|
65
|
-
access_code
|
69
|
+
access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
|
66
70
|
end
|
67
71
|
|
68
72
|
if cache_attribute?(attr_name)
|
@@ -75,7 +79,7 @@ module ActiveRecord
|
|
75
79
|
#
|
76
80
|
# The second, slower, branch is necessary to support instances where the database
|
77
81
|
# returns columns with extra stuff in (like 'my_column(omg)').
|
78
|
-
if method_name =~
|
82
|
+
if method_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
|
79
83
|
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__
|
80
84
|
def _#{method_name}
|
81
85
|
#{access_code}
|
@@ -121,19 +125,15 @@ module ActiveRecord
|
|
121
125
|
|
122
126
|
# Returns true if the attribute is of a text column and marked for serialization.
|
123
127
|
def unserializable_attribute?(attr_name, column)
|
124
|
-
column.text? && self.class.serialized_attributes
|
128
|
+
column.text? && self.class.serialized_attributes.include?(attr_name)
|
125
129
|
end
|
126
130
|
|
127
131
|
# Returns the unserialized object of the attribute.
|
128
132
|
def unserialize_attribute(attr_name)
|
129
|
-
|
133
|
+
coder = self.class.serialized_attributes[attr_name]
|
134
|
+
unserialized_object = coder.load(@attributes[attr_name])
|
130
135
|
|
131
|
-
|
132
|
-
@attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
|
133
|
-
else
|
134
|
-
raise SerializationTypeMismatch,
|
135
|
-
"#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
|
136
|
-
end
|
136
|
+
@attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
|
137
137
|
end
|
138
138
|
|
139
139
|
private
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
require 'active_support/core_ext/object/inclusion'
|
3
|
+
|
1
4
|
module ActiveRecord
|
2
5
|
module AttributeMethods
|
3
6
|
module TimeZoneConversion
|
@@ -7,7 +10,7 @@ module ActiveRecord
|
|
7
10
|
cattr_accessor :time_zone_aware_attributes, :instance_writer => false
|
8
11
|
self.time_zone_aware_attributes = false
|
9
12
|
|
10
|
-
|
13
|
+
class_attribute :skip_time_zone_conversion_for_attributes, :instance_writer => false
|
11
14
|
self.skip_time_zone_conversion_for_attributes = []
|
12
15
|
end
|
13
16
|
|
@@ -19,9 +22,9 @@ module ActiveRecord
|
|
19
22
|
def define_method_attribute(attr_name)
|
20
23
|
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
|
21
24
|
method_body, line = <<-EOV, __LINE__ + 1
|
22
|
-
def _#{attr_name}
|
25
|
+
def _#{attr_name}
|
23
26
|
cached = @attributes_cache['#{attr_name}']
|
24
|
-
return cached if cached
|
27
|
+
return cached if cached
|
25
28
|
time = _read_attribute('#{attr_name}')
|
26
29
|
@attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
|
27
30
|
end
|
@@ -56,7 +59,7 @@ module ActiveRecord
|
|
56
59
|
|
57
60
|
private
|
58
61
|
def create_time_zone_conversion_attribute?(name, column)
|
59
|
-
time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp]
|
62
|
+
time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && column.type.in?([:datetime, :timestamp])
|
60
63
|
end
|
61
64
|
end
|
62
65
|
end
|
@@ -10,7 +10,7 @@ module ActiveRecord
|
|
10
10
|
module ClassMethods
|
11
11
|
protected
|
12
12
|
def define_method_attribute=(attr_name)
|
13
|
-
if attr_name =~
|
13
|
+
if attr_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
|
14
14
|
generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
|
15
15
|
else
|
16
16
|
generated_attribute_methods.send(:define_method, "#{attr_name}=") do |new_value|
|
@@ -32,6 +32,7 @@ module ActiveRecord
|
|
32
32
|
@attributes[attr_name] = value
|
33
33
|
end
|
34
34
|
end
|
35
|
+
alias_method :raw_write_attribute, :write_attribute
|
35
36
|
|
36
37
|
private
|
37
38
|
# Handle *= for method_missing.
|
@@ -4,7 +4,7 @@ module ActiveRecord
|
|
4
4
|
# = Active Record Autosave Association
|
5
5
|
#
|
6
6
|
# +AutosaveAssociation+ is a module that takes care of automatically saving
|
7
|
-
#
|
7
|
+
# associated records when their parent is saved. In addition to saving, it
|
8
8
|
# also destroys any associated records that were marked for destruction.
|
9
9
|
# (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
|
10
10
|
#
|
@@ -116,30 +116,29 @@ module ActiveRecord
|
|
116
116
|
module AutosaveAssociation
|
117
117
|
extend ActiveSupport::Concern
|
118
118
|
|
119
|
-
ASSOCIATION_TYPES = %w{
|
119
|
+
ASSOCIATION_TYPES = %w{ HasOne HasMany BelongsTo HasAndBelongsToMany }
|
120
|
+
|
121
|
+
module AssociationBuilderExtension #:nodoc:
|
122
|
+
def self.included(base)
|
123
|
+
base.valid_options << :autosave
|
124
|
+
end
|
125
|
+
|
126
|
+
def build
|
127
|
+
reflection = super
|
128
|
+
model.send(:add_autosave_association_callbacks, reflection)
|
129
|
+
reflection
|
130
|
+
end
|
131
|
+
end
|
120
132
|
|
121
133
|
included do
|
122
134
|
ASSOCIATION_TYPES.each do |type|
|
123
|
-
|
135
|
+
Associations::Builder.const_get(type).send(:include, AssociationBuilderExtension)
|
124
136
|
end
|
125
137
|
end
|
126
138
|
|
127
139
|
module ClassMethods
|
128
140
|
private
|
129
141
|
|
130
|
-
# def belongs_to(name, options = {})
|
131
|
-
# super
|
132
|
-
# add_autosave_association_callbacks(reflect_on_association(name))
|
133
|
-
# end
|
134
|
-
ASSOCIATION_TYPES.each do |type|
|
135
|
-
module_eval <<-CODE, __FILE__, __LINE__ + 1
|
136
|
-
def #{type}(name, options = {})
|
137
|
-
super
|
138
|
-
add_autosave_association_callbacks(reflect_on_association(name))
|
139
|
-
end
|
140
|
-
CODE
|
141
|
-
end
|
142
|
-
|
143
142
|
def define_non_cyclic_method(name, reflection, &block)
|
144
143
|
define_method(name) do |*args|
|
145
144
|
result = true; @_already_called ||= {}
|
@@ -177,14 +176,23 @@ module ActiveRecord
|
|
177
176
|
if collection
|
178
177
|
before_save :before_save_collection_association
|
179
178
|
|
180
|
-
|
179
|
+
define_non_cyclic_method(save_method, reflection) { save_collection_association(reflection) }
|
181
180
|
# Doesn't use after_save as that would save associations added in after_create/after_update twice
|
182
181
|
after_create save_method
|
183
182
|
after_update save_method
|
184
183
|
else
|
185
184
|
if reflection.macro == :has_one
|
186
185
|
define_method(save_method) { save_has_one_association(reflection) }
|
187
|
-
after_save
|
186
|
+
# Configures two callbacks instead of a single after_save so that
|
187
|
+
# the model may rely on their execution order relative to its
|
188
|
+
# own callbacks.
|
189
|
+
#
|
190
|
+
# For example, given that after_creates run before after_saves, if
|
191
|
+
# we configured instead an after_save there would be no way to fire
|
192
|
+
# a custom after_create callback after the child association gets
|
193
|
+
# created.
|
194
|
+
after_create save_method
|
195
|
+
after_update save_method
|
188
196
|
else
|
189
197
|
define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
|
190
198
|
before_save save_method
|
@@ -194,7 +202,7 @@ module ActiveRecord
|
|
194
202
|
|
195
203
|
if reflection.validate? && !method_defined?(validation_method)
|
196
204
|
method = (collection ? :validate_collection_association : :validate_single_association)
|
197
|
-
|
205
|
+
define_non_cyclic_method(validation_method, reflection) { send(method, reflection) }
|
198
206
|
validate validation_method
|
199
207
|
end
|
200
208
|
end
|
@@ -235,7 +243,7 @@ module ActiveRecord
|
|
235
243
|
# unless the parent is/was a new record itself.
|
236
244
|
def associated_records_to_validate_or_save(association, new_record, autosave)
|
237
245
|
if new_record
|
238
|
-
association
|
246
|
+
association && association.target
|
239
247
|
elsif autosave
|
240
248
|
association.target.find_all { |record| record.changed_for_autosave? }
|
241
249
|
else
|
@@ -255,9 +263,9 @@ module ActiveRecord
|
|
255
263
|
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
|
256
264
|
# turned on for the association.
|
257
265
|
def validate_single_association(reflection)
|
258
|
-
|
259
|
-
|
260
|
-
|
266
|
+
association = association_instance_get(reflection.name)
|
267
|
+
record = association && association.target
|
268
|
+
association_valid?(reflection, record) if record
|
261
269
|
end
|
262
270
|
|
263
271
|
# Validate the associated records if <tt>:validate</tt> or
|
@@ -274,12 +282,12 @@ module ActiveRecord
|
|
274
282
|
# Returns whether or not the association is valid and applies any errors to
|
275
283
|
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
|
276
284
|
# enabled records if they're marked_for_destruction? or destroyed.
|
277
|
-
def association_valid?(reflection,
|
278
|
-
return true if
|
285
|
+
def association_valid?(reflection, record)
|
286
|
+
return true if record.destroyed? || record.marked_for_destruction?
|
279
287
|
|
280
|
-
unless valid =
|
288
|
+
unless valid = record.valid?
|
281
289
|
if reflection.options[:autosave]
|
282
|
-
|
290
|
+
record.errors.each do |attribute, message|
|
283
291
|
attribute = "#{reflection.name}.#{attribute}"
|
284
292
|
errors[attribute] << message
|
285
293
|
errors[attribute].uniq!
|
@@ -311,27 +319,35 @@ module ActiveRecord
|
|
311
319
|
autosave = reflection.options[:autosave]
|
312
320
|
|
313
321
|
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
|
322
|
+
begin
|
314
323
|
records.each do |record|
|
315
324
|
next if record.destroyed?
|
316
325
|
|
326
|
+
saved = true
|
327
|
+
|
317
328
|
if autosave && record.marked_for_destruction?
|
318
|
-
association.destroy(record)
|
329
|
+
association.proxy.destroy(record)
|
319
330
|
elsif autosave != false && (@new_record_before_save || record.new_record?)
|
320
331
|
if autosave
|
321
|
-
saved = association.
|
332
|
+
saved = association.insert_record(record, false)
|
322
333
|
else
|
323
|
-
association.
|
334
|
+
association.insert_record(record)
|
324
335
|
end
|
325
336
|
elsif autosave
|
326
337
|
saved = record.save(:validate => false)
|
327
338
|
end
|
328
339
|
|
329
|
-
raise ActiveRecord::Rollback
|
340
|
+
raise ActiveRecord::Rollback unless saved
|
341
|
+
end
|
342
|
+
rescue
|
343
|
+
records.each {|x| IdentityMap.remove(x) } if IdentityMap.enabled?
|
344
|
+
raise
|
330
345
|
end
|
346
|
+
|
331
347
|
end
|
332
348
|
|
333
|
-
# reconstruct the
|
334
|
-
association.send(:
|
349
|
+
# reconstruct the scope now that we know the owner's id
|
350
|
+
association.send(:construct_scope) if association.respond_to?(:construct_scope)
|
335
351
|
end
|
336
352
|
end
|
337
353
|
|
@@ -344,16 +360,18 @@ module ActiveRecord
|
|
344
360
|
# This all happens inside a transaction, _if_ the Transactions module is included into
|
345
361
|
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
346
362
|
def save_has_one_association(reflection)
|
347
|
-
|
363
|
+
association = association_instance_get(reflection.name)
|
364
|
+
record = association && association.load_target
|
365
|
+
if record && !record.destroyed?
|
348
366
|
autosave = reflection.options[:autosave]
|
349
367
|
|
350
|
-
if autosave &&
|
351
|
-
|
368
|
+
if autosave && record.marked_for_destruction?
|
369
|
+
record.destroy
|
352
370
|
else
|
353
371
|
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
|
354
|
-
if autosave != false && (new_record? ||
|
355
|
-
|
356
|
-
saved =
|
372
|
+
if autosave != false && (new_record? || record.new_record? || record[reflection.foreign_key] != key || autosave)
|
373
|
+
record[reflection.foreign_key] = key
|
374
|
+
saved = record.save(:validate => !autosave)
|
357
375
|
raise ActiveRecord::Rollback if !saved && autosave
|
358
376
|
saved
|
359
377
|
end
|
@@ -365,17 +383,20 @@ module ActiveRecord
|
|
365
383
|
#
|
366
384
|
# In addition, it will destroy the association if it was marked for destruction.
|
367
385
|
def save_belongs_to_association(reflection)
|
368
|
-
|
386
|
+
association = association_instance_get(reflection.name)
|
387
|
+
record = association && association.load_target
|
388
|
+
if record && !record.destroyed?
|
369
389
|
autosave = reflection.options[:autosave]
|
370
390
|
|
371
|
-
if autosave &&
|
372
|
-
|
391
|
+
if autosave && record.marked_for_destruction?
|
392
|
+
record.destroy
|
373
393
|
elsif autosave != false
|
374
|
-
saved =
|
394
|
+
saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
|
375
395
|
|
376
396
|
if association.updated?
|
377
|
-
association_id =
|
378
|
-
self[reflection.
|
397
|
+
association_id = record.send(reflection.options[:primary_key] || :id)
|
398
|
+
self[reflection.foreign_key] = association_id
|
399
|
+
association.loaded!
|
379
400
|
end
|
380
401
|
|
381
402
|
saved if autosave
|
data/lib/active_record/base.rb
CHANGED
@@ -12,7 +12,7 @@ require 'active_support/time'
|
|
12
12
|
require 'active_support/core_ext/class/attribute'
|
13
13
|
require 'active_support/core_ext/class/attribute_accessors'
|
14
14
|
require 'active_support/core_ext/class/delegating_attributes'
|
15
|
-
require 'active_support/core_ext/class/
|
15
|
+
require 'active_support/core_ext/class/attribute'
|
16
16
|
require 'active_support/core_ext/array/extract_options'
|
17
17
|
require 'active_support/core_ext/hash/deep_merge'
|
18
18
|
require 'active_support/core_ext/hash/indifferent_access'
|
@@ -20,7 +20,6 @@ require 'active_support/core_ext/hash/slice'
|
|
20
20
|
require 'active_support/core_ext/string/behavior'
|
21
21
|
require 'active_support/core_ext/kernel/singleton_class'
|
22
22
|
require 'active_support/core_ext/module/delegation'
|
23
|
-
require 'active_support/core_ext/module/deprecation'
|
24
23
|
require 'active_support/core_ext/module/introspection'
|
25
24
|
require 'active_support/core_ext/object/duplicable'
|
26
25
|
require 'active_support/core_ext/object/blank'
|
@@ -84,7 +83,7 @@ module ActiveRecord #:nodoc:
|
|
84
83
|
#
|
85
84
|
# The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
|
86
85
|
# and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
|
87
|
-
# parameters come directly from an HTTP request. The <tt>authenticate_safely</tt>
|
86
|
+
# parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
|
88
87
|
# <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
|
89
88
|
# before inserting them in the query, which will ensure that an attacker can't escape the
|
90
89
|
# query and fake the login (or worse).
|
@@ -181,10 +180,7 @@ module ActiveRecord #:nodoc:
|
|
181
180
|
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
|
182
181
|
#
|
183
182
|
# Person.where(:user_name => user_name, :password => password).first
|
184
|
-
# Person.find_by_user_name_and_password #with dynamic finder
|
185
|
-
#
|
186
|
-
# Person.where(:user_name => user_name, :password => password, :gender => 'male').first
|
187
|
-
# Payment.find_by_user_name_and_password_and_gender
|
183
|
+
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
|
188
184
|
#
|
189
185
|
# It's even possible to call these dynamic finder methods on relations and named scopes.
|
190
186
|
#
|
@@ -210,7 +206,7 @@ module ActiveRecord #:nodoc:
|
|
210
206
|
#
|
211
207
|
# # No 'Winter' tag exists
|
212
208
|
# winter = Tag.find_or_initialize_by_name("Winter")
|
213
|
-
# winter.
|
209
|
+
# winter.persisted? # false
|
214
210
|
#
|
215
211
|
# To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of
|
216
212
|
# a list of parameters.
|
@@ -250,6 +246,17 @@ module ActiveRecord #:nodoc:
|
|
250
246
|
# user = User.create(:preferences => %w( one two three ))
|
251
247
|
# User.find(user.id).preferences # raises SerializationTypeMismatch
|
252
248
|
#
|
249
|
+
# When you specify a class option, the default value for that attribute will be a new
|
250
|
+
# instance of that class.
|
251
|
+
#
|
252
|
+
# class User < ActiveRecord::Base
|
253
|
+
# serialize :preferences, OpenStruct
|
254
|
+
# end
|
255
|
+
#
|
256
|
+
# user = User.new
|
257
|
+
# user.preferences.theme_color = "red"
|
258
|
+
#
|
259
|
+
#
|
253
260
|
# == Single table inheritance
|
254
261
|
#
|
255
262
|
# Active Record allows inheritance by storing the name of the class in a column that by
|
@@ -321,18 +328,6 @@ module ActiveRecord #:nodoc:
|
|
321
328
|
# a class and instance level by calling +logger+.
|
322
329
|
cattr_accessor :logger, :instance_writer => false
|
323
330
|
|
324
|
-
class << self
|
325
|
-
def reset_subclasses #:nodoc:
|
326
|
-
ActiveSupport::Deprecation.warn 'ActiveRecord::Base.reset_subclasses no longer does anything in Rails 3. It will be removed in the final release; please update your apps and plugins.', caller
|
327
|
-
end
|
328
|
-
|
329
|
-
def subclasses
|
330
|
-
descendants
|
331
|
-
end
|
332
|
-
|
333
|
-
deprecate :subclasses => :descendants
|
334
|
-
end
|
335
|
-
|
336
331
|
##
|
337
332
|
# :singleton-method:
|
338
333
|
# Contains the database configuration - as is typically stored in config/database.yml -
|
@@ -411,10 +406,10 @@ module ActiveRecord #:nodoc:
|
|
411
406
|
##
|
412
407
|
# :singleton-method:
|
413
408
|
# Specifies the format to use when dumping the database schema with Rails'
|
414
|
-
# Rakefile.
|
415
|
-
# specific) SQL statements.
|
409
|
+
# Rakefile. If :sql, the schema is dumped as (potentially database-
|
410
|
+
# specific) SQL statements. If :ruby, the schema is dumped as an
|
416
411
|
# ActiveRecord::Schema file which can be loaded into any database that
|
417
|
-
# supports migrations.
|
412
|
+
# supports migrations. Use :ruby if you want to have different database
|
418
413
|
# adapters for, e.g., your development and test environments.
|
419
414
|
cattr_accessor :schema_format , :instance_writer => false
|
420
415
|
@@schema_format = :ruby
|
@@ -426,38 +421,39 @@ module ActiveRecord #:nodoc:
|
|
426
421
|
@@timestamped_migrations = true
|
427
422
|
|
428
423
|
# Determine whether to store the full constant name including namespace when using STI
|
429
|
-
|
424
|
+
class_attribute :store_full_sti_class
|
430
425
|
self.store_full_sti_class = true
|
431
426
|
|
432
427
|
# Stores the default scope for the class
|
433
|
-
|
434
|
-
self.
|
428
|
+
class_attribute :default_scopes, :instance_writer => false
|
429
|
+
self.default_scopes = []
|
435
430
|
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
431
|
+
# Returns a hash of all the attributes that have been specified for serialization as
|
432
|
+
# keys and their class restriction as values.
|
433
|
+
class_attribute :serialized_attributes
|
434
|
+
self.serialized_attributes = {}
|
435
|
+
|
436
|
+
class_attribute :_attr_readonly, :instance_writer => false
|
437
|
+
self._attr_readonly = []
|
443
438
|
|
444
|
-
|
439
|
+
class << self # Class methods
|
440
|
+
delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
|
445
441
|
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
|
446
442
|
delegate :find_each, :find_in_batches, :to => :scoped
|
447
|
-
delegate :select, :group, :order, :
|
443
|
+
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
|
448
444
|
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
|
449
445
|
|
450
|
-
# Executes a custom SQL query against your database and returns all the results.
|
446
|
+
# Executes a custom SQL query against your database and returns all the results. The results will
|
451
447
|
# be returned as an array with columns requested encapsulated as attributes of the model you call
|
452
|
-
# this method from.
|
448
|
+
# this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
|
453
449
|
# a Product object with the attributes you specified in the SQL query.
|
454
450
|
#
|
455
451
|
# If you call a complicated SQL query which spans multiple tables the columns specified by the
|
456
452
|
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
|
457
453
|
# table.
|
458
454
|
#
|
459
|
-
# The +sql+ parameter is a full SQL query as a string.
|
460
|
-
# no database agnostic conversions performed.
|
455
|
+
# The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
|
456
|
+
# no database agnostic conversions performed. This should be a last resort because using, for example,
|
461
457
|
# MySQL specific terms will lock you to using that particular database engine or require you to
|
462
458
|
# change your call if you switch engines.
|
463
459
|
#
|
@@ -468,21 +464,30 @@ module ActiveRecord #:nodoc:
|
|
468
464
|
#
|
469
465
|
# # You can use the same string replacement techniques as you can with ActiveRecord#find
|
470
466
|
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
|
471
|
-
# > [#<Post:0x36bff9c @attributes={"
|
472
|
-
def find_by_sql(sql)
|
473
|
-
connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
|
467
|
+
# > [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
|
468
|
+
def find_by_sql(sql, binds = [])
|
469
|
+
connection.select_all(sanitize_sql(sql), "#{name} Load", binds).collect! { |record| instantiate(record) }
|
474
470
|
end
|
475
471
|
|
476
472
|
# Creates an object (or multiple objects) and saves it to the database, if validations pass.
|
477
473
|
# The resulting object is returned whether the object was saved successfully to the database or not.
|
478
474
|
#
|
479
|
-
# The +attributes+ parameter can be either be a Hash or an Array of Hashes.
|
475
|
+
# The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
|
480
476
|
# attributes on the objects that are to be created.
|
481
477
|
#
|
478
|
+
# +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
|
479
|
+
# in the +options+ parameter.
|
480
|
+
#
|
482
481
|
# ==== Examples
|
483
482
|
# # Create a single new object
|
484
483
|
# User.create(:first_name => 'Jamie')
|
485
484
|
#
|
485
|
+
# # Create a single new object using the :admin mass-assignment security scope
|
486
|
+
# User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
|
487
|
+
#
|
488
|
+
# # Create a single new object bypassing mass-assignment security
|
489
|
+
# User.create({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
|
490
|
+
#
|
486
491
|
# # Create an Array of new objects
|
487
492
|
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
|
488
493
|
#
|
@@ -495,11 +500,11 @@ module ActiveRecord #:nodoc:
|
|
495
500
|
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
|
496
501
|
# u.is_admin = false
|
497
502
|
# end
|
498
|
-
def create(attributes = nil, &block)
|
503
|
+
def create(attributes = nil, options = {}, &block)
|
499
504
|
if attributes.is_a?(Array)
|
500
|
-
attributes.collect { |attr| create(attr, &block) }
|
505
|
+
attributes.collect { |attr| create(attr, options, &block) }
|
501
506
|
else
|
502
|
-
object = new(attributes)
|
507
|
+
object = new(attributes, options)
|
503
508
|
yield(object) if block_given?
|
504
509
|
object.save
|
505
510
|
object
|
@@ -508,7 +513,7 @@ module ActiveRecord #:nodoc:
|
|
508
513
|
|
509
514
|
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
|
510
515
|
# The use of this method should be restricted to complicated SQL queries that can't be executed
|
511
|
-
# using the ActiveRecord::Calculations class methods.
|
516
|
+
# using the ActiveRecord::Calculations class methods. Look into those before using this.
|
512
517
|
#
|
513
518
|
# ==== Parameters
|
514
519
|
#
|
@@ -525,12 +530,12 @@ module ActiveRecord #:nodoc:
|
|
525
530
|
# Attributes listed as readonly will be used to create a new record but update operations will
|
526
531
|
# ignore these fields.
|
527
532
|
def attr_readonly(*attributes)
|
528
|
-
|
533
|
+
self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
|
529
534
|
end
|
530
535
|
|
531
536
|
# Returns an array of all the attributes that have been specified as readonly.
|
532
537
|
def readonly_attributes
|
533
|
-
|
538
|
+
self._attr_readonly
|
534
539
|
end
|
535
540
|
|
536
541
|
# If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
|
@@ -545,17 +550,19 @@ module ActiveRecord #:nodoc:
|
|
545
550
|
#
|
546
551
|
# ==== Example
|
547
552
|
# # Serialize a preferences attribute
|
548
|
-
# class User
|
553
|
+
# class User < ActiveRecord::Base
|
549
554
|
# serialize :preferences
|
550
555
|
# end
|
551
556
|
def serialize(attr_name, class_name = Object)
|
552
|
-
|
553
|
-
|
557
|
+
coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
|
558
|
+
class_name
|
559
|
+
else
|
560
|
+
Coders::YAMLColumn.new(class_name)
|
561
|
+
end
|
554
562
|
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {})
|
563
|
+
# merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
|
564
|
+
# has its own hash of own serialized attributes
|
565
|
+
self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
|
559
566
|
end
|
560
567
|
|
561
568
|
# Guesses the table name (in forced lower-case) based on the name of the class in the
|
@@ -583,7 +590,7 @@ module ActiveRecord #:nodoc:
|
|
583
590
|
# invoice/lineitem.rb Invoice::Lineitem lineitems
|
584
591
|
#
|
585
592
|
# Additionally, the class-level +table_name_prefix+ is prepended and the
|
586
|
-
# +table_name_suffix+ is appended.
|
593
|
+
# +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
|
587
594
|
# the table name guess for an Invoice class becomes "myapp_invoices".
|
588
595
|
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
|
589
596
|
#
|
@@ -617,7 +624,7 @@ module ActiveRecord #:nodoc:
|
|
617
624
|
@inheritance_column ||= "type"
|
618
625
|
end
|
619
626
|
|
620
|
-
# Lazy-set the sequence name to the connection's default.
|
627
|
+
# Lazy-set the sequence name to the connection's default. This method
|
621
628
|
# is only ever called once since set_sequence_name overrides it.
|
622
629
|
def sequence_name #:nodoc:
|
623
630
|
reset_sequence_name
|
@@ -629,7 +636,7 @@ module ActiveRecord #:nodoc:
|
|
629
636
|
default
|
630
637
|
end
|
631
638
|
|
632
|
-
# Sets the table name. If the value is nil or false
|
639
|
+
# Sets the table name. If the value is nil or false then the value returned by the given
|
633
640
|
# block is used.
|
634
641
|
#
|
635
642
|
# class Project < ActiveRecord::Base
|
@@ -638,6 +645,9 @@ module ActiveRecord #:nodoc:
|
|
638
645
|
def set_table_name(value = nil, &block)
|
639
646
|
@quoted_table_name = nil
|
640
647
|
define_attr_method :table_name, value, &block
|
648
|
+
|
649
|
+
@arel_table = Arel::Table.new(table_name, arel_engine)
|
650
|
+
@relation = Relation.new(self, arel_table)
|
641
651
|
end
|
642
652
|
alias :table_name= :set_table_name
|
643
653
|
|
@@ -681,16 +691,12 @@ module ActiveRecord #:nodoc:
|
|
681
691
|
|
682
692
|
# Returns an array of column objects for the table associated with this class.
|
683
693
|
def columns
|
684
|
-
|
685
|
-
@columns = connection.columns(table_name, "#{name} Columns")
|
686
|
-
@columns.each { |column| column.primary = column.name == primary_key }
|
687
|
-
end
|
688
|
-
@columns
|
694
|
+
connection_pool.columns[table_name]
|
689
695
|
end
|
690
696
|
|
691
697
|
# Returns a hash of column objects for the table associated with this class.
|
692
698
|
def columns_hash
|
693
|
-
|
699
|
+
connection_pool.columns_hash[table_name]
|
694
700
|
end
|
695
701
|
|
696
702
|
# Returns an array of column names as strings.
|
@@ -745,13 +751,16 @@ module ActiveRecord #:nodoc:
|
|
745
751
|
# end
|
746
752
|
# end
|
747
753
|
def reset_column_information
|
754
|
+
connection.clear_cache!
|
748
755
|
undefine_attribute_methods
|
749
|
-
|
750
|
-
|
756
|
+
connection_pool.clear_table_cache!(table_name) if table_exists?
|
757
|
+
|
758
|
+
@column_names = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
|
759
|
+
@arel_engine = @relation = nil
|
751
760
|
end
|
752
761
|
|
753
|
-
def
|
754
|
-
|
762
|
+
def clear_cache! # :nodoc:
|
763
|
+
connection_pool.clear_cache!
|
755
764
|
end
|
756
765
|
|
757
766
|
def attribute_method?(attribute)
|
@@ -762,15 +771,12 @@ module ActiveRecord #:nodoc:
|
|
762
771
|
def lookup_ancestors #:nodoc:
|
763
772
|
klass = self
|
764
773
|
classes = [klass]
|
774
|
+
return classes if klass == ActiveRecord::Base
|
775
|
+
|
765
776
|
while klass != klass.base_class
|
766
777
|
classes << klass = klass.superclass
|
767
778
|
end
|
768
779
|
classes
|
769
|
-
rescue
|
770
|
-
# OPTIMIZE this rescue is to fix this test: ./test/cases/reflection_test.rb:56:in `test_human_name_for_column'
|
771
|
-
# Apparently the method base_class causes some trouble.
|
772
|
-
# It now works for sure.
|
773
|
-
[self]
|
774
780
|
end
|
775
781
|
|
776
782
|
# Set the i18n scope to overwrite ActiveModel.
|
@@ -792,7 +798,7 @@ module ActiveRecord #:nodoc:
|
|
792
798
|
:true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
|
793
799
|
end
|
794
800
|
|
795
|
-
# Returns a string like 'Post
|
801
|
+
# Returns a string like 'Post(id:integer, title:string, body:text)'
|
796
802
|
def inspect
|
797
803
|
if self == Base
|
798
804
|
super
|
@@ -820,6 +826,10 @@ module ActiveRecord #:nodoc:
|
|
820
826
|
object.is_a?(self)
|
821
827
|
end
|
822
828
|
|
829
|
+
def symbolized_base_class
|
830
|
+
@symbolized_base_class ||= base_class.to_s.to_sym
|
831
|
+
end
|
832
|
+
|
823
833
|
# Returns the base AR subclass that this class descends from. If A
|
824
834
|
# extends AR::Base, A.base_class will return A. If B descends from A
|
825
835
|
# through some arbitrarily deep hierarchy, B.base_class will return A.
|
@@ -853,13 +863,13 @@ module ActiveRecord #:nodoc:
|
|
853
863
|
end
|
854
864
|
|
855
865
|
def arel_table
|
856
|
-
|
866
|
+
Arel::Table.new(table_name, arel_engine)
|
857
867
|
end
|
858
868
|
|
859
869
|
def arel_engine
|
860
870
|
@arel_engine ||= begin
|
861
871
|
if self == ActiveRecord::Base
|
862
|
-
|
872
|
+
ActiveRecord::Base
|
863
873
|
else
|
864
874
|
connection_handler.connection_pools[name] ? self : superclass.arel_engine
|
865
875
|
end
|
@@ -869,7 +879,9 @@ module ActiveRecord #:nodoc:
|
|
869
879
|
# Returns a scope for this class without taking into account the default_scope.
|
870
880
|
#
|
871
881
|
# class Post < ActiveRecord::Base
|
872
|
-
# default_scope
|
882
|
+
# def self.default_scope
|
883
|
+
# where :published => true
|
884
|
+
# end
|
873
885
|
# end
|
874
886
|
#
|
875
887
|
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
|
@@ -882,8 +894,8 @@ module ActiveRecord #:nodoc:
|
|
882
894
|
# limit(10) # Fires "SELECT * FROM posts LIMIT 10"
|
883
895
|
# }
|
884
896
|
#
|
885
|
-
# It is recommended to use block form of unscoped because chaining unscoped with <tt>
|
886
|
-
# does not work. Assuming that <tt>published</tt> is a <tt>
|
897
|
+
# It is recommended to use block form of unscoped because chaining unscoped with <tt>scope</tt>
|
898
|
+
# does not work. Assuming that <tt>published</tt> is a <tt>scope</tt> following two statements are same.
|
887
899
|
#
|
888
900
|
# Post.unscoped.published
|
889
901
|
# Post.published
|
@@ -891,29 +903,55 @@ module ActiveRecord #:nodoc:
|
|
891
903
|
block_given? ? relation.scoping { yield } : relation
|
892
904
|
end
|
893
905
|
|
894
|
-
def
|
895
|
-
|
896
|
-
|
906
|
+
def before_remove_const #:nodoc:
|
907
|
+
self.current_scope = nil
|
908
|
+
end
|
909
|
+
|
910
|
+
# Specifies how the record is loaded by +Marshal+.
|
911
|
+
#
|
912
|
+
# +_load+ sets an instance variable for each key in the hash it takes as input.
|
913
|
+
# Override this method if you require more complex marshalling.
|
914
|
+
def _load(data)
|
915
|
+
record = allocate
|
916
|
+
record.init_with(Marshal.load(data))
|
917
|
+
record
|
897
918
|
end
|
898
919
|
|
899
|
-
|
900
|
-
|
920
|
+
|
921
|
+
# Finder methods must instantiate through this method to work with the
|
922
|
+
# single-table inheritance model that makes it possible to create
|
923
|
+
# objects of different types from the same table.
|
924
|
+
def instantiate(record)
|
925
|
+
sti_class = find_sti_class(record[inheritance_column])
|
926
|
+
record_id = sti_class.primary_key && record[sti_class.primary_key]
|
927
|
+
|
928
|
+
if ActiveRecord::IdentityMap.enabled? && record_id
|
929
|
+
if (column = sti_class.columns_hash[sti_class.primary_key]) && column.number?
|
930
|
+
record_id = record_id.to_i
|
931
|
+
end
|
932
|
+
if instance = IdentityMap.get(sti_class, record_id)
|
933
|
+
instance.reinit_with('attributes' => record)
|
934
|
+
else
|
935
|
+
instance = sti_class.allocate.init_with('attributes' => record)
|
936
|
+
IdentityMap.add(instance)
|
937
|
+
end
|
938
|
+
else
|
939
|
+
instance = sti_class.allocate.init_with('attributes' => record)
|
940
|
+
end
|
941
|
+
|
942
|
+
instance
|
901
943
|
end
|
902
944
|
|
903
945
|
private
|
904
946
|
|
905
947
|
def relation #:nodoc:
|
906
948
|
@relation ||= Relation.new(self, arel_table)
|
907
|
-
finder_needs_type_condition? ? @relation.where(type_condition) : @relation
|
908
|
-
end
|
909
949
|
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
model.init_with('attributes' => record)
|
916
|
-
model
|
950
|
+
if finder_needs_type_condition?
|
951
|
+
@relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
|
952
|
+
else
|
953
|
+
@relation
|
954
|
+
end
|
917
955
|
end
|
918
956
|
|
919
957
|
def find_sti_class(type_name)
|
@@ -942,12 +980,11 @@ module ActiveRecord #:nodoc:
|
|
942
980
|
relation
|
943
981
|
end
|
944
982
|
|
945
|
-
def type_condition
|
946
|
-
sti_column =
|
947
|
-
|
948
|
-
descendants.each { |subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
|
983
|
+
def type_condition(table = arel_table)
|
984
|
+
sti_column = table[inheritance_column.to_sym]
|
985
|
+
sti_names = ([self] + descendants).map { |model| model.sti_name }
|
949
986
|
|
950
|
-
|
987
|
+
sti_column.in(sti_names)
|
951
988
|
end
|
952
989
|
|
953
990
|
# Guesses the table name, but does not decorate it with prefix and suffix information.
|
@@ -988,12 +1025,8 @@ module ActiveRecord #:nodoc:
|
|
988
1025
|
attribute_names = match.attribute_names
|
989
1026
|
super unless all_attributes_exists?(attribute_names)
|
990
1027
|
if match.finder?
|
991
|
-
options =
|
992
|
-
|
993
|
-
else
|
994
|
-
{}
|
995
|
-
end
|
996
|
-
relation = options.any? ? construct_finder_arel(options, current_scoped_methods) : scoped
|
1028
|
+
options = arguments.extract_options!
|
1029
|
+
relation = options.any? ? scoped(options) : scoped
|
997
1030
|
relation.send :find_by_attributes, match, attribute_names, *arguments
|
998
1031
|
elsif match.instantiator?
|
999
1032
|
scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
|
@@ -1003,14 +1036,11 @@ module ActiveRecord #:nodoc:
|
|
1003
1036
|
super unless all_attributes_exists?(attribute_names)
|
1004
1037
|
if match.scope?
|
1005
1038
|
self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
1006
|
-
def self.#{method_id}(*args)
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
#
|
1012
|
-
scoped(:conditions => attributes) # scoped(:conditions => attributes)
|
1013
|
-
end # end
|
1039
|
+
def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
|
1040
|
+
attributes = Hash[[:#{attribute_names.join(',:')}].zip(args)] # attributes = Hash[[:user_name, :password].zip(args)]
|
1041
|
+
#
|
1042
|
+
scoped(:conditions => attributes) # scoped(:conditions => attributes)
|
1043
|
+
end # end
|
1014
1044
|
METHOD
|
1015
1045
|
send(method_id, *arguments)
|
1016
1046
|
end
|
@@ -1019,31 +1049,22 @@ module ActiveRecord #:nodoc:
|
|
1019
1049
|
end
|
1020
1050
|
end
|
1021
1051
|
|
1022
|
-
def construct_attributes_from_arguments(attribute_names, arguments)
|
1023
|
-
attributes = {}
|
1024
|
-
attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
|
1025
|
-
attributes
|
1026
|
-
end
|
1027
|
-
|
1028
1052
|
# Similar in purpose to +expand_hash_conditions_for_aggregates+.
|
1029
1053
|
def expand_attribute_names_for_aggregates(attribute_names)
|
1030
|
-
|
1031
|
-
attribute_names.each do |attribute_name|
|
1054
|
+
attribute_names.map { |attribute_name|
|
1032
1055
|
unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil?
|
1033
|
-
aggregate_mapping(aggregation).
|
1034
|
-
|
1056
|
+
aggregate_mapping(aggregation).map do |field_attr, _|
|
1057
|
+
field_attr.to_sym
|
1035
1058
|
end
|
1036
1059
|
else
|
1037
|
-
|
1060
|
+
attribute_name.to_sym
|
1038
1061
|
end
|
1039
|
-
|
1040
|
-
expanded_attribute_names
|
1062
|
+
}.flatten
|
1041
1063
|
end
|
1042
1064
|
|
1043
1065
|
def all_attributes_exists?(attribute_names)
|
1044
|
-
expand_attribute_names_for_aggregates(attribute_names)
|
1045
|
-
|
1046
|
-
}
|
1066
|
+
(expand_attribute_names_for_aggregates(attribute_names) -
|
1067
|
+
column_methods_hash.keys).empty?
|
1047
1068
|
end
|
1048
1069
|
|
1049
1070
|
protected
|
@@ -1065,7 +1086,7 @@ module ActiveRecord #:nodoc:
|
|
1065
1086
|
# <tt>where</tt>, <tt>includes</tt>, and <tt>joins</tt> operations in <tt>Relation</tt>, which are merged.
|
1066
1087
|
#
|
1067
1088
|
# <tt>joins</tt> operations are uniqued so multiple scopes can join in the same table without table aliasing
|
1068
|
-
# problems.
|
1089
|
+
# problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
|
1069
1090
|
# array of strings format for your joins.
|
1070
1091
|
#
|
1071
1092
|
# class Article < ActiveRecord::Base
|
@@ -1094,43 +1115,47 @@ module ActiveRecord #:nodoc:
|
|
1094
1115
|
# end
|
1095
1116
|
#
|
1096
1117
|
# *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
|
1097
|
-
def with_scope(
|
1098
|
-
|
1118
|
+
def with_scope(scope = {}, action = :merge, &block)
|
1119
|
+
# If another Active Record class has been passed in, get its current scope
|
1120
|
+
scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope)
|
1121
|
+
|
1122
|
+
previous_scope = self.current_scope
|
1099
1123
|
|
1100
|
-
if
|
1124
|
+
if scope.is_a?(Hash)
|
1101
1125
|
# Dup first and second level of hash (method and params).
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1126
|
+
scope = scope.dup
|
1127
|
+
scope.each do |method, params|
|
1128
|
+
scope[method] = params.dup unless params == true
|
1105
1129
|
end
|
1106
1130
|
|
1107
|
-
|
1108
|
-
relation = construct_finder_arel(
|
1131
|
+
scope.assert_valid_keys([ :find, :create ])
|
1132
|
+
relation = construct_finder_arel(scope[:find] || {})
|
1133
|
+
relation.default_scoped = true unless action == :overwrite
|
1109
1134
|
|
1110
|
-
if
|
1135
|
+
if previous_scope && previous_scope.create_with_value && scope[:create]
|
1111
1136
|
scope_for_create = if action == :merge
|
1112
|
-
|
1137
|
+
previous_scope.create_with_value.merge(scope[:create])
|
1113
1138
|
else
|
1114
|
-
|
1139
|
+
scope[:create]
|
1115
1140
|
end
|
1116
1141
|
|
1117
1142
|
relation = relation.create_with(scope_for_create)
|
1118
1143
|
else
|
1119
|
-
scope_for_create =
|
1120
|
-
scope_for_create ||=
|
1144
|
+
scope_for_create = scope[:create]
|
1145
|
+
scope_for_create ||= previous_scope.create_with_value if previous_scope
|
1121
1146
|
relation = relation.create_with(scope_for_create) if scope_for_create
|
1122
1147
|
end
|
1123
1148
|
|
1124
|
-
|
1149
|
+
scope = relation
|
1125
1150
|
end
|
1126
1151
|
|
1127
|
-
|
1152
|
+
scope = previous_scope.merge(scope) if previous_scope && action == :merge
|
1128
1153
|
|
1129
|
-
self.
|
1154
|
+
self.current_scope = scope
|
1130
1155
|
begin
|
1131
1156
|
yield
|
1132
1157
|
ensure
|
1133
|
-
self.
|
1158
|
+
self.current_scope = previous_scope
|
1134
1159
|
end
|
1135
1160
|
end
|
1136
1161
|
|
@@ -1153,33 +1178,80 @@ MSG
|
|
1153
1178
|
with_scope(method_scoping, :overwrite, &block)
|
1154
1179
|
end
|
1155
1180
|
|
1156
|
-
|
1157
|
-
|
1181
|
+
def current_scope #:nodoc:
|
1182
|
+
Thread.current[:"#{self}_current_scope"]
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
def current_scope=(scope) #:nodoc:
|
1186
|
+
Thread.current[:"#{self}_current_scope"] = scope
|
1187
|
+
end
|
1188
|
+
|
1189
|
+
# Use this macro in your model to set a default scope for all operations on
|
1190
|
+
# the model.
|
1158
1191
|
#
|
1159
|
-
# class
|
1160
|
-
# default_scope
|
1192
|
+
# class Article < ActiveRecord::Base
|
1193
|
+
# default_scope where(:published => true)
|
1161
1194
|
# end
|
1162
1195
|
#
|
1163
|
-
#
|
1196
|
+
# Article.all # => SELECT * FROM articles WHERE published = true
|
1197
|
+
#
|
1198
|
+
# The <tt>default_scope</tt> is also applied while creating/building a record. It is not
|
1164
1199
|
# applied while updating a record.
|
1165
1200
|
#
|
1201
|
+
# Article.new.published # => true
|
1202
|
+
# Article.create.published # => true
|
1203
|
+
#
|
1204
|
+
# You can also use <tt>default_scope</tt> with a block, in order to have it lazily evaluated:
|
1205
|
+
#
|
1206
|
+
# class Article < ActiveRecord::Base
|
1207
|
+
# default_scope { where(:published_at => Time.now - 1.week) }
|
1208
|
+
# end
|
1209
|
+
#
|
1210
|
+
# (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
|
1211
|
+
# macro, and it will be called when building the default scope.)
|
1212
|
+
#
|
1213
|
+
# If you use multiple <tt>default_scope</tt> declarations in your model then they will
|
1214
|
+
# be merged together:
|
1215
|
+
#
|
1166
1216
|
# class Article < ActiveRecord::Base
|
1167
1217
|
# default_scope where(:published => true)
|
1218
|
+
# default_scope where(:rating => 'G')
|
1168
1219
|
# end
|
1169
1220
|
#
|
1170
|
-
# Article.
|
1171
|
-
#
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1221
|
+
# Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
|
1222
|
+
#
|
1223
|
+
# This is also the case with inheritance and module includes where the parent or module
|
1224
|
+
# defines a <tt>default_scope</tt> and the child or including class defines a second one.
|
1225
|
+
#
|
1226
|
+
# If you need to do more complex things with a default scope, you can alternatively
|
1227
|
+
# define it as a class method:
|
1228
|
+
#
|
1229
|
+
# class Article < ActiveRecord::Base
|
1230
|
+
# def self.default_scope
|
1231
|
+
# # Should return a scope, you can call 'super' here etc.
|
1232
|
+
# end
|
1233
|
+
# end
|
1234
|
+
def default_scope(scope = {})
|
1235
|
+
scope = Proc.new if block_given?
|
1236
|
+
self.default_scopes = default_scopes + [scope]
|
1179
1237
|
end
|
1180
1238
|
|
1181
|
-
def
|
1182
|
-
|
1239
|
+
def build_default_scope #:nodoc:
|
1240
|
+
if method(:default_scope).owner != Base.singleton_class
|
1241
|
+
# Use relation.scoping to ensure we ignore whatever the current value of
|
1242
|
+
# self.current_scope may be.
|
1243
|
+
relation.scoping { default_scope }
|
1244
|
+
elsif default_scopes.any?
|
1245
|
+
default_scopes.inject(relation) do |default_scope, scope|
|
1246
|
+
if scope.is_a?(Hash)
|
1247
|
+
default_scope.apply_finder_options(scope)
|
1248
|
+
elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
|
1249
|
+
default_scope.merge(scope.call)
|
1250
|
+
else
|
1251
|
+
default_scope.merge(scope)
|
1252
|
+
end
|
1253
|
+
end
|
1254
|
+
end
|
1183
1255
|
end
|
1184
1256
|
|
1185
1257
|
# Returns the class type of the record using the current module as a prefix. So descendants of
|
@@ -1301,9 +1373,11 @@ MSG
|
|
1301
1373
|
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
|
1302
1374
|
attrs = expand_hash_conditions_for_aggregates(attrs)
|
1303
1375
|
|
1304
|
-
table = Arel::Table.new(
|
1305
|
-
|
1306
|
-
|
1376
|
+
table = Arel::Table.new(table_name).alias(default_table_name)
|
1377
|
+
viz = Arel::Visitors.for(arel_engine)
|
1378
|
+
PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
|
1379
|
+
viz.accept b
|
1380
|
+
}.join(' AND ')
|
1307
1381
|
end
|
1308
1382
|
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
|
1309
1383
|
|
@@ -1316,12 +1390,12 @@ MSG
|
|
1316
1390
|
end.join(', ')
|
1317
1391
|
end
|
1318
1392
|
|
1319
|
-
# Accepts an array of conditions.
|
1393
|
+
# Accepts an array of conditions. The array has each value
|
1320
1394
|
# sanitized and interpolated into the SQL statement.
|
1321
1395
|
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
1322
1396
|
def sanitize_sql_array(ary)
|
1323
1397
|
statement, *values = ary
|
1324
|
-
if values.first.is_a?(Hash)
|
1398
|
+
if values.first.is_a?(Hash) && statement =~ /:\w+/
|
1325
1399
|
replace_named_bind_variables(statement, values.first)
|
1326
1400
|
elsif statement.include?('?')
|
1327
1401
|
replace_bind_variables(statement, values)
|
@@ -1400,8 +1474,23 @@ MSG
|
|
1400
1474
|
# attributes but not yet saved (pass a hash with key names matching the associated table column names).
|
1401
1475
|
# In both instances, valid attribute keys are determined by the column names of the associated table --
|
1402
1476
|
# hence you can't have attributes that aren't part of the table columns.
|
1403
|
-
|
1477
|
+
#
|
1478
|
+
# +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
|
1479
|
+
# in the +options+ parameter.
|
1480
|
+
#
|
1481
|
+
# ==== Examples
|
1482
|
+
# # Instantiates a single new object
|
1483
|
+
# User.new(:first_name => 'Jamie')
|
1484
|
+
#
|
1485
|
+
# # Instantiates a single new object using the :admin mass-assignment security scope
|
1486
|
+
# User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
|
1487
|
+
#
|
1488
|
+
# # Instantiates a single new object bypassing mass-assignment security
|
1489
|
+
# User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
|
1490
|
+
def initialize(attributes = nil, options = {})
|
1404
1491
|
@attributes = attributes_from_column_definition
|
1492
|
+
@association_cache = {}
|
1493
|
+
@aggregation_cache = {}
|
1405
1494
|
@attributes_cache = {}
|
1406
1495
|
@new_record = true
|
1407
1496
|
@readonly = false
|
@@ -1411,41 +1500,35 @@ MSG
|
|
1411
1500
|
@changed_attributes = {}
|
1412
1501
|
|
1413
1502
|
ensure_proper_type
|
1503
|
+
set_serialized_attributes
|
1414
1504
|
|
1415
1505
|
populate_with_current_scope_attributes
|
1416
|
-
|
1506
|
+
|
1507
|
+
assign_attributes(attributes, options) if attributes
|
1417
1508
|
|
1418
1509
|
result = yield self if block_given?
|
1419
|
-
|
1510
|
+
run_callbacks :initialize
|
1420
1511
|
result
|
1421
1512
|
end
|
1422
1513
|
|
1423
|
-
#
|
1424
|
-
#
|
1425
|
-
#
|
1426
|
-
|
1427
|
-
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1438
|
-
clear_aggregation_cache
|
1439
|
-
clear_association_cache
|
1440
|
-
@attributes_cache = {}
|
1441
|
-
@new_record = true
|
1442
|
-
ensure_proper_type
|
1443
|
-
|
1444
|
-
populate_with_current_scope_attributes
|
1514
|
+
# Populate +coder+ with attributes about this record that should be
|
1515
|
+
# serialized. The structure of +coder+ defined in this method is
|
1516
|
+
# guaranteed to match the structure of +coder+ passed to the +init_with+
|
1517
|
+
# method.
|
1518
|
+
#
|
1519
|
+
# Example:
|
1520
|
+
#
|
1521
|
+
# class Post < ActiveRecord::Base
|
1522
|
+
# end
|
1523
|
+
# coder = {}
|
1524
|
+
# Post.new.encode_with(coder)
|
1525
|
+
# coder # => { 'id' => nil, ... }
|
1526
|
+
def encode_with(coder)
|
1527
|
+
coder['attributes'] = attributes
|
1445
1528
|
end
|
1446
1529
|
|
1447
|
-
# Initialize an empty model object from +coder+.
|
1448
|
-
# the attributes necessary for initializing an empty model object.
|
1530
|
+
# Initialize an empty model object from +coder+. +coder+ must contain
|
1531
|
+
# the attributes necessary for initializing an empty model object. For
|
1449
1532
|
# example:
|
1450
1533
|
#
|
1451
1534
|
# class Post < ActiveRecord::Base
|
@@ -1456,10 +1539,28 @@ MSG
|
|
1456
1539
|
# post.title # => 'hello world'
|
1457
1540
|
def init_with(coder)
|
1458
1541
|
@attributes = coder['attributes']
|
1542
|
+
|
1543
|
+
set_serialized_attributes
|
1544
|
+
|
1459
1545
|
@attributes_cache, @previously_changed, @changed_attributes = {}, {}, {}
|
1460
|
-
@
|
1461
|
-
|
1462
|
-
|
1546
|
+
@association_cache = {}
|
1547
|
+
@aggregation_cache = {}
|
1548
|
+
@readonly = @destroyed = @marked_for_destruction = false
|
1549
|
+
@new_record = false
|
1550
|
+
run_callbacks :find
|
1551
|
+
run_callbacks :initialize
|
1552
|
+
|
1553
|
+
self
|
1554
|
+
end
|
1555
|
+
|
1556
|
+
# Specifies how the record is dumped by +Marshal+.
|
1557
|
+
#
|
1558
|
+
# +_dump+ emits a marshalled hash which has been passed to +encode_with+. Override this
|
1559
|
+
# method if you require more complex marshalling.
|
1560
|
+
def _dump(level)
|
1561
|
+
dump = {}
|
1562
|
+
encode_with(dump)
|
1563
|
+
Marshal.dump(dump)
|
1463
1564
|
end
|
1464
1565
|
|
1465
1566
|
# Returns a String, which Action Pack uses for constructing an URL to this
|
@@ -1501,8 +1602,7 @@ MSG
|
|
1501
1602
|
when new_record?
|
1502
1603
|
"#{self.class.model_name.cache_key}/new"
|
1503
1604
|
when timestamp = self[:updated_at]
|
1504
|
-
timestamp
|
1505
|
-
"#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
|
1605
|
+
"#{self.class.model_name.cache_key}/#{id}-#{timestamp.to_s(:number)}"
|
1506
1606
|
else
|
1507
1607
|
"#{self.class.model_name.cache_key}/#{id}"
|
1508
1608
|
end
|
@@ -1517,32 +1617,19 @@ MSG
|
|
1517
1617
|
@attributes.has_key?(attr_name.to_s)
|
1518
1618
|
end
|
1519
1619
|
|
1520
|
-
# Returns an array of names for the attributes available on this object
|
1620
|
+
# Returns an array of names for the attributes available on this object.
|
1521
1621
|
def attribute_names
|
1522
|
-
@attributes.keys
|
1523
|
-
end
|
1524
|
-
|
1525
|
-
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
1526
|
-
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
|
1527
|
-
# (Alias for the protected read_attribute method).
|
1528
|
-
def [](attr_name)
|
1529
|
-
read_attribute(attr_name)
|
1530
|
-
end
|
1531
|
-
|
1532
|
-
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
|
1533
|
-
# (Alias for the protected write_attribute method).
|
1534
|
-
def []=(attr_name, value)
|
1535
|
-
write_attribute(attr_name, value)
|
1622
|
+
@attributes.keys
|
1536
1623
|
end
|
1537
1624
|
|
1538
1625
|
# Allows you to set all the attributes at once by passing in a hash with keys
|
1539
1626
|
# matching the attribute names (which again matches the column names).
|
1540
1627
|
#
|
1541
|
-
# If
|
1542
|
-
#
|
1543
|
-
#
|
1544
|
-
#
|
1545
|
-
#
|
1628
|
+
# If any attributes are protected by either +attr_protected+ or
|
1629
|
+
# +attr_accessible+ then only settable attributes will be assigned.
|
1630
|
+
#
|
1631
|
+
# The +guard_protected_attributes+ argument is now deprecated, use
|
1632
|
+
# the +assign_attributes+ method if you want to bypass mass-assignment security.
|
1546
1633
|
#
|
1547
1634
|
# class User < ActiveRecord::Base
|
1548
1635
|
# attr_protected :is_admin
|
@@ -1552,21 +1639,67 @@ MSG
|
|
1552
1639
|
# user.attributes = { :username => 'Phusion', :is_admin => true }
|
1553
1640
|
# user.username # => "Phusion"
|
1554
1641
|
# user.is_admin? # => false
|
1642
|
+
def attributes=(new_attributes, guard_protected_attributes = nil)
|
1643
|
+
unless guard_protected_attributes.nil?
|
1644
|
+
message = "the use of 'guard_protected_attributes' will be removed from the next major release of rails, " +
|
1645
|
+
"if you want to bypass mass-assignment security then look into using assign_attributes"
|
1646
|
+
ActiveSupport::Deprecation.warn(message)
|
1647
|
+
end
|
1648
|
+
|
1649
|
+
return unless new_attributes.is_a?(Hash)
|
1650
|
+
|
1651
|
+
guard_protected_attributes ||= true
|
1652
|
+
if guard_protected_attributes
|
1653
|
+
assign_attributes(new_attributes)
|
1654
|
+
else
|
1655
|
+
assign_attributes(new_attributes, :without_protection => true)
|
1656
|
+
end
|
1657
|
+
end
|
1658
|
+
|
1659
|
+
# Allows you to set all the attributes for a particular mass-assignment
|
1660
|
+
# security scope by passing in a hash of attributes with keys matching
|
1661
|
+
# the attribute names (which again matches the column names) and the scope
|
1662
|
+
# name using the :as option.
|
1663
|
+
#
|
1664
|
+
# To bypass mass-assignment security you can use the :without_protection => true
|
1665
|
+
# option.
|
1666
|
+
#
|
1667
|
+
# class User < ActiveRecord::Base
|
1668
|
+
# attr_accessible :name
|
1669
|
+
# attr_accessible :name, :is_admin, :as => :admin
|
1670
|
+
# end
|
1671
|
+
#
|
1672
|
+
# user = User.new
|
1673
|
+
# user.assign_attributes({ :name => 'Josh', :is_admin => true })
|
1674
|
+
# user.name # => "Josh"
|
1675
|
+
# user.is_admin? # => false
|
1555
1676
|
#
|
1556
|
-
# user
|
1677
|
+
# user = User.new
|
1678
|
+
# user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
|
1679
|
+
# user.name # => "Josh"
|
1557
1680
|
# user.is_admin? # => true
|
1558
|
-
|
1559
|
-
|
1681
|
+
#
|
1682
|
+
# user = User.new
|
1683
|
+
# user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
|
1684
|
+
# user.name # => "Josh"
|
1685
|
+
# user.is_admin? # => true
|
1686
|
+
def assign_attributes(new_attributes, options = {})
|
1560
1687
|
attributes = new_attributes.stringify_keys
|
1688
|
+
scope = options[:as] || :default
|
1561
1689
|
|
1562
1690
|
multi_parameter_attributes = []
|
1563
|
-
|
1691
|
+
|
1692
|
+
unless options[:without_protection]
|
1693
|
+
attributes = sanitize_for_mass_assignment(attributes, scope)
|
1694
|
+
end
|
1564
1695
|
|
1565
1696
|
attributes.each do |k, v|
|
1566
1697
|
if k.include?("(")
|
1567
1698
|
multi_parameter_attributes << [ k, v ]
|
1699
|
+
elsif respond_to?("#{k}=")
|
1700
|
+
send("#{k}=", v)
|
1568
1701
|
else
|
1569
|
-
|
1702
|
+
raise(UnknownAttributeError, "unknown attribute: #{k}")
|
1570
1703
|
end
|
1571
1704
|
end
|
1572
1705
|
|
@@ -1575,9 +1708,7 @@ MSG
|
|
1575
1708
|
|
1576
1709
|
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
1577
1710
|
def attributes
|
1578
|
-
|
1579
|
-
attribute_names.each { |name| attrs[name] = read_attribute(name) }
|
1580
|
-
attrs
|
1711
|
+
Hash[@attributes.map { |name, _| [name, read_attribute(name)] }]
|
1581
1712
|
end
|
1582
1713
|
|
1583
1714
|
# Returns an <tt>#inspect</tt>-like string for the value of the
|
@@ -1608,8 +1739,7 @@ MSG
|
|
1608
1739
|
# Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
|
1609
1740
|
# nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
|
1610
1741
|
def attribute_present?(attribute)
|
1611
|
-
|
1612
|
-
!value.blank?
|
1742
|
+
!_read_attribute(attribute).blank?
|
1613
1743
|
end
|
1614
1744
|
|
1615
1745
|
# Returns the column object for the named attribute.
|
@@ -1628,13 +1758,14 @@ MSG
|
|
1628
1758
|
# models are still comparable.
|
1629
1759
|
def ==(comparison_object)
|
1630
1760
|
comparison_object.equal?(self) ||
|
1631
|
-
|
1632
|
-
|
1761
|
+
comparison_object.instance_of?(self.class) &&
|
1762
|
+
id.present? &&
|
1763
|
+
comparison_object.id == id
|
1633
1764
|
end
|
1634
1765
|
|
1635
1766
|
# Delegates to ==
|
1636
1767
|
def eql?(comparison_object)
|
1637
|
-
self ==
|
1768
|
+
self == comparison_object
|
1638
1769
|
end
|
1639
1770
|
|
1640
1771
|
# Delegates to id in order to allow two records of the same type and id to work with something like:
|
@@ -1653,11 +1784,42 @@ MSG
|
|
1653
1784
|
@attributes.frozen?
|
1654
1785
|
end
|
1655
1786
|
|
1656
|
-
#
|
1657
|
-
|
1658
|
-
|
1659
|
-
|
1660
|
-
|
1787
|
+
# Backport dup from 1.9 so that initialize_dup() gets called
|
1788
|
+
unless Object.respond_to?(:initialize_dup)
|
1789
|
+
def dup # :nodoc:
|
1790
|
+
copy = super
|
1791
|
+
copy.initialize_dup(self)
|
1792
|
+
copy
|
1793
|
+
end
|
1794
|
+
end
|
1795
|
+
|
1796
|
+
# Duped objects have no id assigned and are treated as new records. Note
|
1797
|
+
# that this is a "shallow" copy as it copies the object's attributes
|
1798
|
+
# only, not its associations. The extent of a "deep" copy is application
|
1799
|
+
# specific and is therefore left to the application to implement according
|
1800
|
+
# to its need.
|
1801
|
+
# The dup method does not preserve the timestamps (created|updated)_(at|on).
|
1802
|
+
def initialize_dup(other)
|
1803
|
+
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
|
1804
|
+
cloned_attributes.delete(self.class.primary_key)
|
1805
|
+
|
1806
|
+
@attributes = cloned_attributes
|
1807
|
+
|
1808
|
+
_run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
|
1809
|
+
|
1810
|
+
@changed_attributes = {}
|
1811
|
+
attributes_from_column_definition.each do |attr, orig_value|
|
1812
|
+
@changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr])
|
1813
|
+
end
|
1814
|
+
|
1815
|
+
@aggregation_cache = {}
|
1816
|
+
@association_cache = {}
|
1817
|
+
@attributes_cache = {}
|
1818
|
+
@new_record = true
|
1819
|
+
|
1820
|
+
ensure_proper_type
|
1821
|
+
populate_with_current_scope_attributes
|
1822
|
+
clear_timestamp_attributes
|
1661
1823
|
end
|
1662
1824
|
|
1663
1825
|
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
|
@@ -1674,7 +1836,7 @@ MSG
|
|
1674
1836
|
# Returns the contents of the record as a nicely formatted string.
|
1675
1837
|
def inspect
|
1676
1838
|
attributes_as_nice_string = self.class.column_names.collect { |name|
|
1677
|
-
if has_attribute?(name)
|
1839
|
+
if has_attribute?(name)
|
1678
1840
|
"#{name}: #{attribute_for_inspect(name)}"
|
1679
1841
|
end
|
1680
1842
|
}.compact.join(", ")
|
@@ -1698,6 +1860,13 @@ MSG
|
|
1698
1860
|
|
1699
1861
|
private
|
1700
1862
|
|
1863
|
+
def set_serialized_attributes
|
1864
|
+
(@attributes.keys & self.class.serialized_attributes.keys).each do |key|
|
1865
|
+
coder = self.class.serialized_attributes[key]
|
1866
|
+
@attributes[key] = coder.load @attributes[key]
|
1867
|
+
end
|
1868
|
+
end
|
1869
|
+
|
1701
1870
|
# Sets the attribute used for single table inheritance to this class name if this is not the
|
1702
1871
|
# ActiveRecord::Base descendant.
|
1703
1872
|
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
|
@@ -1719,17 +1888,25 @@ MSG
|
|
1719
1888
|
# Returns a copy of the attributes hash where all the values have been safely quoted for use in
|
1720
1889
|
# an Arel insert/update method.
|
1721
1890
|
def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
|
1722
|
-
attrs
|
1891
|
+
attrs = {}
|
1892
|
+
klass = self.class
|
1893
|
+
arel_table = klass.arel_table
|
1894
|
+
|
1723
1895
|
attribute_names.each do |name|
|
1724
1896
|
if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
|
1725
1897
|
|
1726
1898
|
if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
|
1727
|
-
value = read_attribute(name)
|
1728
1899
|
|
1729
|
-
if
|
1730
|
-
|
1731
|
-
|
1732
|
-
|
1900
|
+
value = if coder = klass.serialized_attributes[name]
|
1901
|
+
coder.dump @attributes[name]
|
1902
|
+
else
|
1903
|
+
# FIXME: we need @attributes to be used consistently.
|
1904
|
+
# If the values stored in @attributes were already type
|
1905
|
+
# casted, this code could be simplified
|
1906
|
+
read_attribute(name)
|
1907
|
+
end
|
1908
|
+
|
1909
|
+
attrs[arel_table[name]] = value
|
1733
1910
|
end
|
1734
1911
|
end
|
1735
1912
|
end
|
@@ -1741,26 +1918,6 @@ MSG
|
|
1741
1918
|
self.class.connection.quote(value, column)
|
1742
1919
|
end
|
1743
1920
|
|
1744
|
-
def interpolate_and_sanitize_sql(sql, record = nil, sanitize_klass = self.class)
|
1745
|
-
sanitized = sanitize_klass.send(:sanitize_sql, sql)
|
1746
|
-
interpolate_sanitized_sql(sanitized, record, sanitize_klass)
|
1747
|
-
end
|
1748
|
-
|
1749
|
-
def interpolate_sanitized_sql(sanitized, record = nil, sanitize_klass = self.class)
|
1750
|
-
if sanitized =~ /\#\{.*\}/
|
1751
|
-
ActiveSupport::Deprecation.warn(
|
1752
|
-
'String-based interpolation of association conditions is deprecated. Please use a ' \
|
1753
|
-
'proc instead. So, for example, has_many :older_friends, :conditions => \'age > #{age}\' ' \
|
1754
|
-
'should be changed to has_many :older_friends, :conditions => proc { "age > #{age}" }.'
|
1755
|
-
)
|
1756
|
-
instance_eval("%@#{sanitized.gsub('@', '\@')}@", __FILE__, __LINE__)
|
1757
|
-
elsif sanitized.respond_to?(:to_proc)
|
1758
|
-
sanitize_klass.send(:sanitize_sql, instance_exec(record, &sanitized))
|
1759
|
-
else
|
1760
|
-
sanitized
|
1761
|
-
end
|
1762
|
-
end
|
1763
|
-
|
1764
1921
|
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
1765
1922
|
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
1766
1923
|
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
@@ -1867,15 +2024,17 @@ MSG
|
|
1867
2024
|
end
|
1868
2025
|
end
|
1869
2026
|
|
1870
|
-
def
|
1871
|
-
|
1872
|
-
|
2027
|
+
def populate_with_current_scope_attributes
|
2028
|
+
self.class.scoped.scope_for_create.each do |att,value|
|
2029
|
+
respond_to?("#{att}=") && send("#{att}=", value)
|
2030
|
+
end
|
1873
2031
|
end
|
1874
2032
|
|
1875
|
-
|
1876
|
-
|
1877
|
-
|
1878
|
-
|
2033
|
+
# Clear attributes and changed_attributes
|
2034
|
+
def clear_timestamp_attributes
|
2035
|
+
all_timestamp_attributes_in_model.each do |attribute_name|
|
2036
|
+
self[attribute_name] = nil
|
2037
|
+
changed_attributes.delete(attribute_name)
|
1879
2038
|
end
|
1880
2039
|
end
|
1881
2040
|
end
|
@@ -1898,7 +2057,9 @@ MSG
|
|
1898
2057
|
include AttributeMethods::Dirty
|
1899
2058
|
include ActiveModel::MassAssignmentSecurity
|
1900
2059
|
include Callbacks, ActiveModel::Observing, Timestamp
|
1901
|
-
include Associations,
|
2060
|
+
include Associations, NamedScope
|
2061
|
+
include IdentityMap
|
2062
|
+
include ActiveModel::SecurePassword
|
1902
2063
|
|
1903
2064
|
# AutosaveAssociation needs to be included before Transactions, because we want
|
1904
2065
|
# #save_with_autosave_associations to be wrapped inside a transaction.
|
@@ -1906,6 +2067,17 @@ MSG
|
|
1906
2067
|
include Aggregations, Transactions, Reflection, Serialization
|
1907
2068
|
|
1908
2069
|
NilClass.add_whiner(self) if NilClass.respond_to?(:add_whiner)
|
2070
|
+
|
2071
|
+
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
2072
|
+
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
|
2073
|
+
# (Alias for the protected read_attribute method).
|
2074
|
+
alias [] read_attribute
|
2075
|
+
|
2076
|
+
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
|
2077
|
+
# (Alias for the protected write_attribute method).
|
2078
|
+
alias []= write_attribute
|
2079
|
+
|
2080
|
+
public :[], :[]=
|
1909
2081
|
end
|
1910
2082
|
end
|
1911
2083
|
|