activerecord 3.0.0.beta4 → 3.0.0.rc
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +267 -254
- data/README.rdoc +222 -0
- data/examples/performance.rb +9 -9
- data/lib/active_record/aggregations.rb +3 -4
- data/lib/active_record/association_preload.rb +15 -10
- data/lib/active_record/associations.rb +54 -37
- data/lib/active_record/associations/association_collection.rb +43 -17
- data/lib/active_record/associations/association_proxy.rb +2 -0
- data/lib/active_record/associations/belongs_to_association.rb +1 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +22 -7
- data/lib/active_record/associations/has_many_association.rb +6 -1
- data/lib/active_record/associations/has_many_through_association.rb +1 -0
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/has_one_through_association.rb +1 -0
- data/lib/active_record/associations/through_association_scope.rb +3 -2
- data/lib/active_record/attribute_methods.rb +1 -0
- data/lib/active_record/autosave_association.rb +4 -6
- data/lib/active_record/base.rb +106 -240
- data/lib/active_record/callbacks.rb +4 -25
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +22 -29
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +2 -8
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +10 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +56 -7
- data/lib/active_record/connection_adapters/abstract_adapter.rb +10 -18
- data/lib/active_record/connection_adapters/mysql_adapter.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +65 -69
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +19 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +20 -46
- data/lib/active_record/counter_cache.rb +14 -4
- data/lib/active_record/dynamic_finder_match.rb +9 -0
- data/lib/active_record/dynamic_scope_match.rb +7 -0
- data/lib/active_record/errors.rb +3 -0
- data/lib/active_record/fixtures.rb +5 -6
- data/lib/active_record/locale/en.yml +1 -1
- data/lib/active_record/locking/optimistic.rb +1 -0
- data/lib/active_record/log_subscriber.rb +48 -0
- data/lib/active_record/migration.rb +64 -37
- data/lib/active_record/named_scope.rb +33 -19
- data/lib/active_record/nested_attributes.rb +17 -13
- data/lib/active_record/observer.rb +13 -6
- data/lib/active_record/persistence.rb +55 -22
- data/lib/active_record/query_cache.rb +1 -0
- data/lib/active_record/railtie.rb +14 -8
- data/lib/active_record/railties/controller_runtime.rb +2 -2
- data/lib/active_record/railties/databases.rake +63 -33
- data/lib/active_record/reflection.rb +46 -28
- data/lib/active_record/relation.rb +38 -24
- data/lib/active_record/relation/finder_methods.rb +5 -5
- data/lib/active_record/relation/predicate_builder.rb +2 -4
- data/lib/active_record/relation/query_methods.rb +134 -115
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/schema.rb +2 -0
- data/lib/active_record/schema_dumper.rb +15 -12
- data/lib/active_record/serialization.rb +2 -0
- data/lib/active_record/session_store.rb +93 -79
- data/lib/active_record/test_case.rb +3 -0
- data/lib/active_record/timestamp.rb +49 -29
- data/lib/active_record/transactions.rb +5 -2
- data/lib/active_record/validations.rb +5 -2
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +1 -1
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -6
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- metadata +27 -14
- data/README +0 -351
- data/lib/active_record/railties/log_subscriber.rb +0 -32
@@ -3,6 +3,8 @@ require 'active_support/core_ext/array/wrap'
|
|
3
3
|
|
4
4
|
module ActiveRecord
|
5
5
|
module Associations
|
6
|
+
# = Active Record Association Collection
|
7
|
+
#
|
6
8
|
# AssociationCollection is an abstract class that provides common stuff to
|
7
9
|
# ease the implementation of association proxies that represent
|
8
10
|
# collections. See the class hierarchy in AssociationProxy.
|
@@ -24,10 +26,10 @@ module ActiveRecord
|
|
24
26
|
|
25
27
|
delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
|
26
28
|
|
27
|
-
def select(select = nil
|
29
|
+
def select(select = nil)
|
28
30
|
if block_given?
|
29
31
|
load_target
|
30
|
-
@target.select
|
32
|
+
@target.select.each { |e| yield e }
|
31
33
|
else
|
32
34
|
scoped.select(select)
|
33
35
|
end
|
@@ -121,7 +123,7 @@ module ActiveRecord
|
|
121
123
|
end
|
122
124
|
end
|
123
125
|
|
124
|
-
# Add +records+ to this association. Returns +self+ so method calls may be chained.
|
126
|
+
# Add +records+ to this association. Returns +self+ so method calls may be chained.
|
125
127
|
# Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
|
126
128
|
def <<(*records)
|
127
129
|
result = true
|
@@ -166,7 +168,7 @@ module ActiveRecord
|
|
166
168
|
reset_target!
|
167
169
|
reset_named_scopes_cache!
|
168
170
|
end
|
169
|
-
|
171
|
+
|
170
172
|
# Calculate sum using SQL, not Enumerable
|
171
173
|
def sum(*args)
|
172
174
|
if block_given?
|
@@ -181,10 +183,13 @@ module ActiveRecord
|
|
181
183
|
# descendant's +construct_sql+ method will have set :counter_sql automatically.
|
182
184
|
# Otherwise, construct options and pass them with scope to the target class's +count+.
|
183
185
|
def count(column_name = nil, options = {})
|
184
|
-
if
|
186
|
+
column_name, options = nil, column_name if column_name.is_a?(Hash)
|
187
|
+
|
188
|
+
if @reflection.options[:counter_sql] && !options.blank?
|
189
|
+
raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
|
190
|
+
elsif @reflection.options[:counter_sql]
|
185
191
|
@reflection.klass.count_by_sql(@counter_sql)
|
186
192
|
else
|
187
|
-
column_name, options = nil, column_name if column_name.is_a?(Hash)
|
188
193
|
|
189
194
|
if @reflection.options[:uniq]
|
190
195
|
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
|
@@ -213,9 +218,9 @@ module ActiveRecord
|
|
213
218
|
# are actually removed from the database, that depends precisely on
|
214
219
|
# +delete_records+. They are in any case removed from the collection.
|
215
220
|
def delete(*records)
|
216
|
-
remove_records(records) do |
|
221
|
+
remove_records(records) do |_records, old_records|
|
217
222
|
delete_records(old_records) if old_records.any?
|
218
|
-
|
223
|
+
_records.each { |record| @target.delete(record) }
|
219
224
|
end
|
220
225
|
end
|
221
226
|
|
@@ -226,7 +231,7 @@ module ActiveRecord
|
|
226
231
|
# ignoring the +:dependent+ option.
|
227
232
|
def destroy(*records)
|
228
233
|
records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)}
|
229
|
-
remove_records(records) do |
|
234
|
+
remove_records(records) do |_records, old_records|
|
230
235
|
old_records.each { |record| record.destroy }
|
231
236
|
end
|
232
237
|
|
@@ -239,7 +244,7 @@ module ActiveRecord
|
|
239
244
|
|
240
245
|
if @reflection.options[:dependent] && @reflection.options[:dependent] == :destroy
|
241
246
|
destroy_all
|
242
|
-
else
|
247
|
+
else
|
243
248
|
delete_all
|
244
249
|
end
|
245
250
|
|
@@ -251,9 +256,10 @@ module ActiveRecord
|
|
251
256
|
# See destroy for more info.
|
252
257
|
def destroy_all
|
253
258
|
load_target
|
254
|
-
destroy(@target)
|
255
|
-
|
256
|
-
|
259
|
+
destroy(@target).tap do
|
260
|
+
reset_target!
|
261
|
+
reset_named_scopes_cache!
|
262
|
+
end
|
257
263
|
end
|
258
264
|
|
259
265
|
def create(attrs = {})
|
@@ -388,7 +394,17 @@ module ActiveRecord
|
|
388
394
|
begin
|
389
395
|
if !loaded?
|
390
396
|
if @target.is_a?(Array) && @target.any?
|
391
|
-
@target = find_target
|
397
|
+
@target = find_target.map do |f|
|
398
|
+
i = @target.index(f)
|
399
|
+
if i
|
400
|
+
@target.delete_at(i).tap do |t|
|
401
|
+
keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
|
402
|
+
t.attributes = f.attributes.except(*keys)
|
403
|
+
end
|
404
|
+
else
|
405
|
+
f
|
406
|
+
end
|
407
|
+
end + @target
|
392
408
|
else
|
393
409
|
@target = find_target
|
394
410
|
end
|
@@ -403,6 +419,12 @@ module ActiveRecord
|
|
403
419
|
end
|
404
420
|
|
405
421
|
def method_missing(method, *args)
|
422
|
+
match = DynamicFinderMatch.match(method)
|
423
|
+
if match && match.creator?
|
424
|
+
attributes = match.attribute_names
|
425
|
+
return send(:"find_by_#{attributes.join('and')}", *args) || create(Hash[attributes.zip(args)])
|
426
|
+
end
|
427
|
+
|
406
428
|
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
407
429
|
if block_given?
|
408
430
|
super { |*block_args| yield(*block_args) }
|
@@ -456,7 +478,11 @@ module ActiveRecord
|
|
456
478
|
callback(:before_add, record)
|
457
479
|
yield(record) if block_given?
|
458
480
|
@target ||= [] unless loaded?
|
459
|
-
|
481
|
+
if index = @target.index(record)
|
482
|
+
@target[index] = record
|
483
|
+
else
|
484
|
+
@target << record
|
485
|
+
end
|
460
486
|
callback(:after_add, record)
|
461
487
|
set_inverse_instance(record, @owner)
|
462
488
|
record
|
@@ -514,8 +540,8 @@ module ActiveRecord
|
|
514
540
|
def callbacks_for(callback_name)
|
515
541
|
full_callback_name = "#{callback_name}_for_#{@reflection.name}"
|
516
542
|
@owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
|
517
|
-
end
|
518
|
-
|
543
|
+
end
|
544
|
+
|
519
545
|
def ensure_owner_is_not_new
|
520
546
|
if @owner.new_record?
|
521
547
|
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module ActiveRecord
|
2
|
+
# = Active Record Has And Belongs To Many Association
|
2
3
|
module Associations
|
3
4
|
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
|
4
5
|
def create(attributes = {})
|
@@ -44,17 +45,23 @@ module ActiveRecord
|
|
44
45
|
if @reflection.options[:insert_sql]
|
45
46
|
@owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
|
46
47
|
else
|
47
|
-
relation
|
48
|
+
relation = Arel::Table.new(@reflection.options[:join_table])
|
49
|
+
timestamps = record_timestamp_columns(record)
|
50
|
+
timezone = record.send(:current_time_from_proper_timezone) if timestamps.any?
|
51
|
+
|
48
52
|
attributes = columns.inject({}) do |attrs, column|
|
49
|
-
|
53
|
+
name = column.name
|
54
|
+
case name.to_s
|
50
55
|
when @reflection.primary_key_name.to_s
|
51
|
-
attrs[relation[
|
56
|
+
attrs[relation[name]] = @owner.id
|
52
57
|
when @reflection.association_foreign_key.to_s
|
53
|
-
attrs[relation[
|
58
|
+
attrs[relation[name]] = record.id
|
59
|
+
when *timestamps
|
60
|
+
attrs[relation[name]] = timezone
|
54
61
|
else
|
55
|
-
if record.has_attribute?(
|
56
|
-
value = @owner.send(:quote_value, record[
|
57
|
-
attrs[relation[
|
62
|
+
if record.has_attribute?(name)
|
63
|
+
value = @owner.send(:quote_value, record[name], column)
|
64
|
+
attrs[relation[name]] = value unless value.nil?
|
58
65
|
end
|
59
66
|
end
|
60
67
|
attrs
|
@@ -116,6 +123,14 @@ module ActiveRecord
|
|
116
123
|
build_record(attributes, &block)
|
117
124
|
end
|
118
125
|
end
|
126
|
+
|
127
|
+
def record_timestamp_columns(record)
|
128
|
+
if record.record_timestamps
|
129
|
+
record.send(:all_timestamp_attributes).map(&:to_s)
|
130
|
+
else
|
131
|
+
[]
|
132
|
+
end
|
133
|
+
end
|
119
134
|
end
|
120
135
|
end
|
121
136
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module ActiveRecord
|
2
|
+
# = Active Record Has Many Association
|
2
3
|
module Associations
|
3
4
|
# This is the proxy that handles a has many association.
|
4
5
|
#
|
@@ -109,7 +110,11 @@ module ActiveRecord
|
|
109
110
|
create_scoping = {}
|
110
111
|
set_belongs_to_association_for(create_scoping)
|
111
112
|
{
|
112
|
-
:find => { :conditions => @finder_sql,
|
113
|
+
:find => { :conditions => @finder_sql,
|
114
|
+
:readonly => false,
|
115
|
+
:order => @reflection.options[:order],
|
116
|
+
:limit => @reflection.options[:limit],
|
117
|
+
:include => @reflection.options[:include]},
|
113
118
|
:create => create_scoping
|
114
119
|
}
|
115
120
|
end
|
@@ -2,6 +2,7 @@ require "active_record/associations/through_association_scope"
|
|
2
2
|
require 'active_support/core_ext/object/blank'
|
3
3
|
|
4
4
|
module ActiveRecord
|
5
|
+
# = Active Record Has Many Through Association
|
5
6
|
module Associations
|
6
7
|
class HasManyThroughAssociation < HasManyAssociation #:nodoc:
|
7
8
|
include ThroughAssociationScope
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module ActiveRecord
|
2
|
+
# = Active Record Through Association Scope
|
2
3
|
module Associations
|
3
4
|
module ThroughAssociationScope
|
4
5
|
|
@@ -34,7 +35,7 @@ module ActiveRecord
|
|
34
35
|
@owner.class.base_class.name.to_s,
|
35
36
|
reflection.klass.columns_hash["#{as}_type"]) }
|
36
37
|
elsif reflection.macro == :belongs_to
|
37
|
-
{ reflection.klass.primary_key => @owner[reflection.primary_key_name] }
|
38
|
+
{ reflection.klass.primary_key => @owner.class.quote_value(@owner[reflection.primary_key_name]) }
|
38
39
|
else
|
39
40
|
{ reflection.primary_key_name => owner_quoted_id }
|
40
41
|
end
|
@@ -91,7 +92,7 @@ module ActiveRecord
|
|
91
92
|
|
92
93
|
# Construct attributes for :through pointing to owner and associate.
|
93
94
|
def construct_join_attributes(associate)
|
94
|
-
# TODO:
|
95
|
+
# TODO: revisit this to allow it for deletion, supposing dependent option is supported
|
95
96
|
raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
|
96
97
|
|
97
98
|
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'active_support/core_ext/array/wrap'
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
|
+
# = Active Record Autosave Association
|
5
|
+
#
|
4
6
|
# AutosaveAssociation is a module that takes care of automatically saving
|
5
7
|
# your associations when the parent is saved. In addition to saving, it
|
6
8
|
# also destroys any associations that were marked for destruction.
|
@@ -145,12 +147,12 @@ module ActiveRecord
|
|
145
147
|
# add_autosave_association_callbacks(reflect_on_association(name))
|
146
148
|
# end
|
147
149
|
ASSOCIATION_TYPES.each do |type|
|
148
|
-
module_eval
|
150
|
+
module_eval <<-CODE, __FILE__, __LINE__ + 1
|
149
151
|
def #{type}(name, options = {})
|
150
152
|
super
|
151
153
|
add_autosave_association_callbacks(reflect_on_association(name))
|
152
154
|
end
|
153
|
-
|
155
|
+
CODE
|
154
156
|
end
|
155
157
|
|
156
158
|
# Adds a validate and save callback for the association as specified by
|
@@ -375,10 +377,6 @@ module ActiveRecord
|
|
375
377
|
if association.updated?
|
376
378
|
association_id = association.send(reflection.options[:primary_key] || :id)
|
377
379
|
self[reflection.primary_key_name] = association_id
|
378
|
-
# TODO: Removing this code doesn't seem to matter…
|
379
|
-
if reflection.options[:polymorphic]
|
380
|
-
self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
|
381
|
-
end
|
382
380
|
end
|
383
381
|
|
384
382
|
saved if autosave
|
data/lib/active_record/base.rb
CHANGED
@@ -2,6 +2,7 @@ require 'yaml'
|
|
2
2
|
require 'set'
|
3
3
|
require 'active_support/benchmarkable'
|
4
4
|
require 'active_support/dependencies'
|
5
|
+
require 'active_support/descendants_tracker'
|
5
6
|
require 'active_support/time'
|
6
7
|
require 'active_support/core_ext/class/attribute'
|
7
8
|
require 'active_support/core_ext/class/attribute_accessors'
|
@@ -14,12 +15,17 @@ require 'active_support/core_ext/hash/slice'
|
|
14
15
|
require 'active_support/core_ext/string/behavior'
|
15
16
|
require 'active_support/core_ext/kernel/singleton_class'
|
16
17
|
require 'active_support/core_ext/module/delegation'
|
18
|
+
require 'active_support/core_ext/module/deprecation'
|
19
|
+
require 'active_support/core_ext/module/introspection'
|
17
20
|
require 'active_support/core_ext/object/duplicable'
|
18
21
|
require 'active_support/core_ext/object/blank'
|
19
22
|
require 'arel'
|
20
23
|
require 'active_record/errors'
|
24
|
+
require 'active_record/log_subscriber'
|
21
25
|
|
22
26
|
module ActiveRecord #:nodoc:
|
27
|
+
# = Active Record
|
28
|
+
#
|
23
29
|
# Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
|
24
30
|
# which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
|
25
31
|
# is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
|
@@ -273,27 +279,17 @@ module ActiveRecord #:nodoc:
|
|
273
279
|
# on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
|
274
280
|
cattr_accessor :logger, :instance_writer => false
|
275
281
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
end
|
282
|
+
class << self
|
283
|
+
def reset_subclasses #:nodoc:
|
284
|
+
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
|
285
|
+
end
|
281
286
|
|
282
|
-
|
283
|
-
|
284
|
-
subclasses.each do |klass|
|
285
|
-
unless ActiveSupport::Dependencies.autoloaded? klass
|
286
|
-
nonreloadables << klass
|
287
|
-
next
|
288
|
-
end
|
289
|
-
klass.instance_variables.each { |var| klass.send(:remove_instance_variable, var) }
|
290
|
-
klass.instance_methods(false).each { |m| klass.send :undef_method, m }
|
287
|
+
def subclasses
|
288
|
+
descendants
|
291
289
|
end
|
292
|
-
@@subclasses = {}
|
293
|
-
nonreloadables.each { |klass| (@@subclasses[klass.superclass] ||= []) << klass }
|
294
|
-
end
|
295
290
|
|
296
|
-
|
291
|
+
deprecate :subclasses => :descendants
|
292
|
+
end
|
297
293
|
|
298
294
|
##
|
299
295
|
# :singleton-method:
|
@@ -402,7 +398,7 @@ module ActiveRecord #:nodoc:
|
|
402
398
|
|
403
399
|
delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
|
404
400
|
delegate :find_each, :find_in_batches, :to => :scoped
|
405
|
-
delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
|
401
|
+
delegate :select, :group, :order, :reorder, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
|
406
402
|
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
|
407
403
|
|
408
404
|
# Executes a custom SQL query against your database and returns all the results. The results will
|
@@ -480,112 +476,16 @@ module ActiveRecord #:nodoc:
|
|
480
476
|
connection.select_value(sql, "#{name} Count").to_i
|
481
477
|
end
|
482
478
|
|
483
|
-
# Attributes
|
484
|
-
|
485
|
-
|
486
|
-
# <tt>attributes=(attributes)</tt>.
|
487
|
-
#
|
488
|
-
# Mass-assignment to these attributes will simply be ignored, to assign
|
489
|
-
# to them you can use direct writer methods. This is meant to protect
|
490
|
-
# sensitive attributes from being overwritten by malicious users
|
491
|
-
# tampering with URLs or forms.
|
492
|
-
#
|
493
|
-
# class Customer < ActiveRecord::Base
|
494
|
-
# attr_protected :credit_rating
|
495
|
-
# end
|
496
|
-
#
|
497
|
-
# customer = Customer.new("name" => David, "credit_rating" => "Excellent")
|
498
|
-
# customer.credit_rating # => nil
|
499
|
-
# customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
|
500
|
-
# customer.credit_rating # => nil
|
501
|
-
#
|
502
|
-
# customer.credit_rating = "Average"
|
503
|
-
# customer.credit_rating # => "Average"
|
504
|
-
#
|
505
|
-
# To start from an all-closed default and enable attributes as needed,
|
506
|
-
# have a look at +attr_accessible+.
|
507
|
-
#
|
508
|
-
# If the access logic of your application is richer you can use <tt>Hash#except</tt>
|
509
|
-
# or <tt>Hash#slice</tt> to sanitize the hash of parameters before they are
|
510
|
-
# passed to Active Record.
|
511
|
-
#
|
512
|
-
# For example, it could be the case that the list of protected attributes
|
513
|
-
# for a given model depends on the role of the user:
|
514
|
-
#
|
515
|
-
# # Assumes plan_id is not protected because it depends on the role.
|
516
|
-
# params[:account] = params[:account].except(:plan_id) unless admin?
|
517
|
-
# @account.update_attributes(params[:account])
|
518
|
-
#
|
519
|
-
# Note that +attr_protected+ is still applied to the received hash. Thus,
|
520
|
-
# with this technique you can at most _extend_ the list of protected
|
521
|
-
# attributes for a particular mass-assignment call.
|
522
|
-
def attr_protected(*attributes)
|
523
|
-
write_inheritable_attribute(:attr_protected, Set.new(attributes.map {|a| a.to_s}) + (protected_attributes || []))
|
524
|
-
end
|
525
|
-
|
526
|
-
# Returns an array of all the attributes that have been protected from mass-assignment.
|
527
|
-
def protected_attributes # :nodoc:
|
528
|
-
read_inheritable_attribute(:attr_protected)
|
479
|
+
# Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
|
480
|
+
def attr_readonly(*attributes)
|
481
|
+
write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
|
529
482
|
end
|
530
483
|
|
531
|
-
#
|
532
|
-
|
533
|
-
|
534
|
-
# <tt>attributes=(attributes)</tt>
|
535
|
-
#
|
536
|
-
# This is the opposite of the +attr_protected+ macro: Mass-assignment
|
537
|
-
# will only set attributes in this list, to assign to the rest of
|
538
|
-
# attributes you can use direct writer methods. This is meant to protect
|
539
|
-
# sensitive attributes from being overwritten by malicious users
|
540
|
-
# tampering with URLs or forms. If you'd rather start from an all-open
|
541
|
-
# default and restrict attributes as needed, have a look at
|
542
|
-
# +attr_protected+.
|
543
|
-
#
|
544
|
-
# class Customer < ActiveRecord::Base
|
545
|
-
# attr_accessible :name, :nickname
|
546
|
-
# end
|
547
|
-
#
|
548
|
-
# customer = Customer.new(:name => "David", :nickname => "Dave", :credit_rating => "Excellent")
|
549
|
-
# customer.credit_rating # => nil
|
550
|
-
# customer.attributes = { :name => "Jolly fellow", :credit_rating => "Superb" }
|
551
|
-
# customer.credit_rating # => nil
|
552
|
-
#
|
553
|
-
# customer.credit_rating = "Average"
|
554
|
-
# customer.credit_rating # => "Average"
|
555
|
-
#
|
556
|
-
# If the access logic of your application is richer you can use <tt>Hash#except</tt>
|
557
|
-
# or <tt>Hash#slice</tt> to sanitize the hash of parameters before they are
|
558
|
-
# passed to Active Record.
|
559
|
-
#
|
560
|
-
# For example, it could be the case that the list of accessible attributes
|
561
|
-
# for a given model depends on the role of the user:
|
562
|
-
#
|
563
|
-
# # Assumes plan_id is accessible because it depends on the role.
|
564
|
-
# params[:account] = params[:account].except(:plan_id) unless admin?
|
565
|
-
# @account.update_attributes(params[:account])
|
566
|
-
#
|
567
|
-
# Note that +attr_accessible+ is still applied to the received hash. Thus,
|
568
|
-
# with this technique you can at most _narrow_ the list of accessible
|
569
|
-
# attributes for a particular mass-assignment call.
|
570
|
-
def attr_accessible(*attributes)
|
571
|
-
write_inheritable_attribute(:attr_accessible, Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
|
484
|
+
# Returns an array of all the attributes that have been specified as readonly.
|
485
|
+
def readonly_attributes
|
486
|
+
read_inheritable_attribute(:attr_readonly) || []
|
572
487
|
end
|
573
488
|
|
574
|
-
# Returns an array of all the attributes that have been made accessible to mass-assignment.
|
575
|
-
def accessible_attributes # :nodoc:
|
576
|
-
read_inheritable_attribute(:attr_accessible)
|
577
|
-
end
|
578
|
-
|
579
|
-
# Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
|
580
|
-
def attr_readonly(*attributes)
|
581
|
-
write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
|
582
|
-
end
|
583
|
-
|
584
|
-
# Returns an array of all the attributes that have been specified as readonly.
|
585
|
-
def readonly_attributes
|
586
|
-
read_inheritable_attribute(:attr_readonly) || []
|
587
|
-
end
|
588
|
-
|
589
489
|
# If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
|
590
490
|
# then specify the name of that attribute using this method and it will be handled automatically.
|
591
491
|
# The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
|
@@ -647,29 +547,14 @@ module ActiveRecord #:nodoc:
|
|
647
547
|
reset_table_name
|
648
548
|
end
|
649
549
|
|
550
|
+
# Returns a quoted version of the table name, used to construct SQL statements.
|
650
551
|
def quoted_table_name
|
651
552
|
@quoted_table_name ||= connection.quote_table_name(table_name)
|
652
553
|
end
|
653
554
|
|
555
|
+
# Computes the table name, (re)sets it internally, and returns it.
|
654
556
|
def reset_table_name #:nodoc:
|
655
|
-
|
656
|
-
|
657
|
-
name =
|
658
|
-
# STI subclasses always use their superclass' table.
|
659
|
-
unless self == base
|
660
|
-
base.table_name
|
661
|
-
else
|
662
|
-
# Nested classes are prefixed with singular parent table name.
|
663
|
-
if parent < ActiveRecord::Base && !parent.abstract_class?
|
664
|
-
contained = parent.table_name
|
665
|
-
contained = contained.singularize if parent.pluralize_table_names
|
666
|
-
contained << '_'
|
667
|
-
end
|
668
|
-
name = "#{full_table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
|
669
|
-
end
|
670
|
-
|
671
|
-
set_table_name(name)
|
672
|
-
name
|
557
|
+
self.table_name = compute_table_name
|
673
558
|
end
|
674
559
|
|
675
560
|
def full_table_name_prefix #:nodoc:
|
@@ -739,14 +624,6 @@ module ActiveRecord #:nodoc:
|
|
739
624
|
end
|
740
625
|
alias :sequence_name= :set_sequence_name
|
741
626
|
|
742
|
-
# Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
|
743
|
-
def class_name(table_name = table_name) # :nodoc:
|
744
|
-
# remove any prefix and/or suffix from the table name
|
745
|
-
class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
|
746
|
-
class_name = class_name.singularize if pluralize_table_names
|
747
|
-
class_name
|
748
|
-
end
|
749
|
-
|
750
627
|
# Indicates whether the table associated with this class exists
|
751
628
|
def table_exists?
|
752
629
|
connection.table_exists?(table_name)
|
@@ -820,11 +697,11 @@ module ActiveRecord #:nodoc:
|
|
820
697
|
def reset_column_information
|
821
698
|
undefine_attribute_methods
|
822
699
|
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
|
823
|
-
@arel_engine = @
|
700
|
+
@arel_engine = @relation = @arel_table = nil
|
824
701
|
end
|
825
702
|
|
826
703
|
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
|
827
|
-
|
704
|
+
descendants.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
|
828
705
|
end
|
829
706
|
|
830
707
|
def attribute_method?(attribute)
|
@@ -896,6 +773,9 @@ module ActiveRecord #:nodoc:
|
|
896
773
|
# Returns the base AR subclass that this class descends from. If A
|
897
774
|
# extends AR::Base, A.base_class will return A. If B descends from A
|
898
775
|
# through some arbitrarily deep hierarchy, B.base_class will return A.
|
776
|
+
#
|
777
|
+
# If B < A and C < B and if A is an abstract_class then both B.base_class
|
778
|
+
# and C.base_class would return B as the answer since A is an abstract_class.
|
899
779
|
def base_class
|
900
780
|
class_of_active_record_descendant(self)
|
901
781
|
end
|
@@ -903,8 +783,7 @@ module ActiveRecord #:nodoc:
|
|
903
783
|
# Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
|
904
784
|
attr_accessor :abstract_class
|
905
785
|
|
906
|
-
# Returns whether this class is
|
907
|
-
# B descends from A, then B.base_class will return B.
|
786
|
+
# Returns whether this class is an abstract class or not.
|
908
787
|
def abstract_class?
|
909
788
|
defined?(@abstract_class) && @abstract_class == true
|
910
789
|
end
|
@@ -923,11 +802,6 @@ module ActiveRecord #:nodoc:
|
|
923
802
|
store_full_sti_class ? name : name.demodulize
|
924
803
|
end
|
925
804
|
|
926
|
-
def unscoped
|
927
|
-
@unscoped ||= Relation.new(self, arel_table)
|
928
|
-
finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
|
929
|
-
end
|
930
|
-
|
931
805
|
def arel_table
|
932
806
|
@arel_table ||= Arel::Table.new(table_name, :engine => arel_engine)
|
933
807
|
end
|
@@ -942,15 +816,46 @@ module ActiveRecord #:nodoc:
|
|
942
816
|
end
|
943
817
|
end
|
944
818
|
|
819
|
+
# Returns a scope for this class without taking into account the default_scope.
|
820
|
+
#
|
821
|
+
# class Post < ActiveRecord::Base
|
822
|
+
# default_scope :published => true
|
823
|
+
# end
|
824
|
+
#
|
825
|
+
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
|
826
|
+
# Post.unscoped.all # Fires "SELECT * FROM posts"
|
827
|
+
#
|
828
|
+
# This method also accepts a block meaning that all queries inside the block will
|
829
|
+
# not use the default_scope:
|
830
|
+
#
|
831
|
+
# Post.unscoped {
|
832
|
+
# limit(10) # Fires "SELECT * FROM posts LIMIT 10"
|
833
|
+
# }
|
834
|
+
#
|
835
|
+
def unscoped
|
836
|
+
block_given? ? relation.scoping { yield } : relation
|
837
|
+
end
|
838
|
+
|
839
|
+
def scoped_methods #:nodoc:
|
840
|
+
key = :"#{self}_scoped_methods"
|
841
|
+
Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
|
842
|
+
end
|
843
|
+
|
945
844
|
private
|
845
|
+
|
846
|
+
def relation #:nodoc:
|
847
|
+
@relation ||= Relation.new(self, arel_table)
|
848
|
+
finder_needs_type_condition? ? @relation.where(type_condition) : @relation
|
849
|
+
end
|
850
|
+
|
946
851
|
# Finder methods must instantiate through this method to work with the
|
947
852
|
# single-table inheritance model that makes it possible to create
|
948
853
|
# objects of different types from the same table.
|
949
854
|
def instantiate(record)
|
950
855
|
object = find_sti_class(record[inheritance_column]).allocate
|
951
856
|
|
952
|
-
object.instance_variable_set(
|
953
|
-
object.instance_variable_set(
|
857
|
+
object.instance_variable_set(:@attributes, record)
|
858
|
+
object.instance_variable_set(:@attributes_cache, {})
|
954
859
|
object.instance_variable_set(:@new_record, false)
|
955
860
|
object.instance_variable_set(:@readonly, false)
|
956
861
|
object.instance_variable_set(:@destroyed, false)
|
@@ -989,7 +894,7 @@ module ActiveRecord #:nodoc:
|
|
989
894
|
def type_condition
|
990
895
|
sti_column = arel_table[inheritance_column]
|
991
896
|
condition = sti_column.eq(sti_name)
|
992
|
-
|
897
|
+
descendants.each { |subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
|
993
898
|
|
994
899
|
condition
|
995
900
|
end
|
@@ -1001,6 +906,23 @@ module ActiveRecord #:nodoc:
|
|
1001
906
|
table_name
|
1002
907
|
end
|
1003
908
|
|
909
|
+
# Computes and returns a table name according to default conventions.
|
910
|
+
def compute_table_name
|
911
|
+
base = base_class
|
912
|
+
if self == base
|
913
|
+
# Nested classes are prefixed with singular parent table name.
|
914
|
+
if parent < ActiveRecord::Base && !parent.abstract_class?
|
915
|
+
contained = parent.table_name
|
916
|
+
contained = contained.singularize if parent.pluralize_table_names
|
917
|
+
contained << '_'
|
918
|
+
end
|
919
|
+
"#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{table_name_suffix}"
|
920
|
+
else
|
921
|
+
# STI subclasses always use their superclass' table.
|
922
|
+
base.table_name
|
923
|
+
end
|
924
|
+
end
|
925
|
+
|
1004
926
|
# Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt>
|
1005
927
|
# that are turned into <tt>where(:user_name => user_name).first</tt> and <tt>where(:user_name => user_name, :password => :password).first</tt>
|
1006
928
|
# respectively. Also works for <tt>all</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>where(:amount => 50).all</tt>.
|
@@ -1068,19 +990,6 @@ module ActiveRecord #:nodoc:
|
|
1068
990
|
attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) }
|
1069
991
|
end
|
1070
992
|
|
1071
|
-
def attribute_condition(quoted_column_name, argument)
|
1072
|
-
case argument
|
1073
|
-
when nil then "#{quoted_column_name} IS ?"
|
1074
|
-
when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope then "#{quoted_column_name} IN (?)"
|
1075
|
-
when Range then if argument.exclude_end?
|
1076
|
-
"#{quoted_column_name} >= ? AND #{quoted_column_name} < ?"
|
1077
|
-
else
|
1078
|
-
"#{quoted_column_name} BETWEEN ? AND ?"
|
1079
|
-
end
|
1080
|
-
else "#{quoted_column_name} = ?"
|
1081
|
-
end
|
1082
|
-
end
|
1083
|
-
|
1084
993
|
protected
|
1085
994
|
# Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
|
1086
995
|
# method_name may be <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while
|
@@ -1121,7 +1030,7 @@ module ActiveRecord #:nodoc:
|
|
1121
1030
|
# class Article < ActiveRecord::Base
|
1122
1031
|
# def self.find_with_exclusive_scope
|
1123
1032
|
# with_scope(:find => where(:blog_id => 1).limit(1)) do
|
1124
|
-
# with_exclusive_scope(:find => limit(10))
|
1033
|
+
# with_exclusive_scope(:find => limit(10)) do
|
1125
1034
|
# all # => SELECT * from articles LIMIT 10
|
1126
1035
|
# end
|
1127
1036
|
# end
|
@@ -1172,16 +1081,22 @@ module ActiveRecord #:nodoc:
|
|
1172
1081
|
|
1173
1082
|
# Works like with_scope, but discards any nested properties.
|
1174
1083
|
def with_exclusive_scope(method_scoping = {}, &block)
|
1175
|
-
|
1176
|
-
|
1084
|
+
if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) }
|
1085
|
+
raise ArgumentError, <<-MSG
|
1086
|
+
New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope:
|
1177
1087
|
|
1178
|
-
|
1179
|
-
def subclasses
|
1180
|
-
@@subclasses[self] ||= []
|
1181
|
-
@@subclasses[self] + @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
|
1182
|
-
end
|
1088
|
+
User.unscoped.where(:active => true)
|
1183
1089
|
|
1184
|
-
|
1090
|
+
Or call unscoped with a block:
|
1091
|
+
|
1092
|
+
User.unscoped do
|
1093
|
+
User.where(:active => true).all
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
MSG
|
1097
|
+
end
|
1098
|
+
with_scope(method_scoping, :overwrite, &block)
|
1099
|
+
end
|
1185
1100
|
|
1186
1101
|
# Sets the default options for the model. The format of the
|
1187
1102
|
# <tt>options</tt> argument is the same as in find.
|
@@ -1193,11 +1108,6 @@ module ActiveRecord #:nodoc:
|
|
1193
1108
|
self.default_scoping << construct_finder_arel(options, default_scoping.pop)
|
1194
1109
|
end
|
1195
1110
|
|
1196
|
-
def scoped_methods #:nodoc:
|
1197
|
-
key = :"#{self}_scoped_methods"
|
1198
|
-
Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
|
1199
|
-
end
|
1200
|
-
|
1201
1111
|
def current_scoped_methods #:nodoc:
|
1202
1112
|
scoped_methods.last
|
1203
1113
|
end
|
@@ -1242,11 +1152,6 @@ module ActiveRecord #:nodoc:
|
|
1242
1152
|
end
|
1243
1153
|
end
|
1244
1154
|
|
1245
|
-
# Returns the name of the class descending directly from Active Record in the inheritance hierarchy.
|
1246
|
-
def class_name_of_active_record_descendant(klass) #:nodoc:
|
1247
|
-
klass.base_class.name
|
1248
|
-
end
|
1249
|
-
|
1250
1155
|
# Accepts an array, hash, or string of SQL conditions and sanitizes
|
1251
1156
|
# them into a valid SQL fragment for a WHERE clause.
|
1252
1157
|
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
@@ -1350,6 +1255,8 @@ module ActiveRecord #:nodoc:
|
|
1350
1255
|
replace_named_bind_variables(statement, values.first)
|
1351
1256
|
elsif statement.include?('?')
|
1352
1257
|
replace_bind_variables(statement, values)
|
1258
|
+
elsif statement.blank?
|
1259
|
+
statement
|
1353
1260
|
else
|
1354
1261
|
statement % values.collect { |value| connection.quote_string(value.to_s) }
|
1355
1262
|
end
|
@@ -1450,14 +1357,6 @@ module ActiveRecord #:nodoc:
|
|
1450
1357
|
# as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
|
1451
1358
|
# application specific and is therefore left to the application to implement according to its need.
|
1452
1359
|
def initialize_copy(other)
|
1453
|
-
# Think the assertion which fails if the after_initialize callback goes at the end of the method is wrong. The
|
1454
|
-
# deleted clone method called new which therefore called the after_initialize callback. It then went on to copy
|
1455
|
-
# over the attributes. But if it's copying the attributes afterwards then it hasn't finished initializing right?
|
1456
|
-
# For example in the test suite the topic model's after_initialize method sets the author_email_address to
|
1457
|
-
# test@test.com. I would have thought this would mean that all cloned models would have an author email address
|
1458
|
-
# of test@test.com. However the test_clone test method seems to test that this is not the case. As a result the
|
1459
|
-
# after_initialize callback has to be run *before* the copying of the atrributes rather than afterwards in order
|
1460
|
-
# for all tests to pass. This makes no sense to me.
|
1461
1360
|
callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
|
1462
1361
|
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
|
1463
1362
|
cloned_attributes.delete(self.class.primary_key)
|
@@ -1470,6 +1369,7 @@ module ActiveRecord #:nodoc:
|
|
1470
1369
|
end
|
1471
1370
|
|
1472
1371
|
clear_aggregation_cache
|
1372
|
+
clear_association_cache
|
1473
1373
|
@attributes_cache = {}
|
1474
1374
|
@new_record = true
|
1475
1375
|
ensure_proper_type
|
@@ -1573,11 +1473,11 @@ module ActiveRecord #:nodoc:
|
|
1573
1473
|
# user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
|
1574
1474
|
# user.is_admin? # => true
|
1575
1475
|
def attributes=(new_attributes, guard_protected_attributes = true)
|
1576
|
-
return
|
1476
|
+
return unless new_attributes.is_a?(Hash)
|
1577
1477
|
attributes = new_attributes.stringify_keys
|
1578
1478
|
|
1579
1479
|
multi_parameter_attributes = []
|
1580
|
-
attributes =
|
1480
|
+
attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes
|
1581
1481
|
|
1582
1482
|
attributes.each do |k, v|
|
1583
1483
|
if k.include?("(")
|
@@ -1717,46 +1617,10 @@ module ActiveRecord #:nodoc:
|
|
1717
1617
|
end
|
1718
1618
|
end
|
1719
1619
|
|
1720
|
-
def remove_attributes_protected_from_mass_assignment(attributes)
|
1721
|
-
safe_attributes =
|
1722
|
-
if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
|
1723
|
-
attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
|
1724
|
-
elsif self.class.protected_attributes.nil?
|
1725
|
-
attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
|
1726
|
-
elsif self.class.accessible_attributes.nil?
|
1727
|
-
attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
|
1728
|
-
else
|
1729
|
-
raise "Declare either attr_protected or attr_accessible for #{self.class}, but not both."
|
1730
|
-
end
|
1731
|
-
|
1732
|
-
removed_attributes = attributes.keys - safe_attributes.keys
|
1733
|
-
|
1734
|
-
if removed_attributes.any?
|
1735
|
-
log_protected_attribute_removal(removed_attributes)
|
1736
|
-
end
|
1737
|
-
|
1738
|
-
safe_attributes
|
1739
|
-
end
|
1740
|
-
|
1741
|
-
# Removes attributes which have been marked as readonly.
|
1742
|
-
def remove_readonly_attributes(attributes)
|
1743
|
-
unless self.class.readonly_attributes.nil?
|
1744
|
-
attributes.delete_if { |key, value| self.class.readonly_attributes.include?(key.gsub(/\(.+/,"")) }
|
1745
|
-
else
|
1746
|
-
attributes
|
1747
|
-
end
|
1748
|
-
end
|
1749
|
-
|
1750
|
-
def log_protected_attribute_removal(*attributes)
|
1751
|
-
if logger
|
1752
|
-
logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}"
|
1753
|
-
end
|
1754
|
-
end
|
1755
|
-
|
1756
1620
|
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
|
1757
|
-
def attributes_protected_by_default
|
1758
|
-
default = [
|
1759
|
-
default << 'id' unless
|
1621
|
+
def self.attributes_protected_by_default
|
1622
|
+
default = [ primary_key, inheritance_column ]
|
1623
|
+
default << 'id' unless primary_key.eql? 'id'
|
1760
1624
|
default
|
1761
1625
|
end
|
1762
1626
|
|
@@ -1910,6 +1774,7 @@ module ActiveRecord #:nodoc:
|
|
1910
1774
|
extend ActiveModel::Naming
|
1911
1775
|
extend QueryCache::ClassMethods
|
1912
1776
|
extend ActiveSupport::Benchmarkable
|
1777
|
+
extend ActiveSupport::DescendantsTracker
|
1913
1778
|
|
1914
1779
|
include ActiveModel::Conversion
|
1915
1780
|
include Validations
|
@@ -1920,6 +1785,7 @@ module ActiveRecord #:nodoc:
|
|
1920
1785
|
include AttributeMethods::PrimaryKey
|
1921
1786
|
include AttributeMethods::TimeZoneConversion
|
1922
1787
|
include AttributeMethods::Dirty
|
1788
|
+
include ActiveModel::MassAssignmentSecurity
|
1923
1789
|
include Callbacks, ActiveModel::Observing, Timestamp
|
1924
1790
|
include Associations, AssociationPreload, NamedScope
|
1925
1791
|
|