activerecord 3.1.12 → 3.2.0.rc1
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.md +6263 -103
- data/README.rdoc +2 -2
- data/examples/performance.rb +55 -31
- data/lib/active_record.rb +28 -2
- data/lib/active_record/aggregations.rb +2 -2
- data/lib/active_record/associations.rb +82 -69
- data/lib/active_record/associations/association.rb +2 -37
- data/lib/active_record/associations/association_scope.rb +3 -30
- data/lib/active_record/associations/builder/association.rb +6 -4
- data/lib/active_record/associations/builder/belongs_to.rb +3 -3
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +5 -6
- data/lib/active_record/associations/builder/singular_association.rb +3 -16
- data/lib/active_record/associations/collection_association.rb +55 -28
- data/lib/active_record/associations/collection_proxy.rb +1 -35
- data/lib/active_record/associations/has_many_association.rb +5 -1
- data/lib/active_record/associations/has_many_through_association.rb +11 -8
- data/lib/active_record/associations/join_dependency.rb +1 -1
- data/lib/active_record/associations/preloader/association.rb +3 -1
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods.rb +212 -32
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +3 -3
- data/lib/active_record/attribute_methods/primary_key.rb +62 -25
- data/lib/active_record/attribute_methods/read.rb +69 -80
- data/lib/active_record/attribute_methods/serialization.rb +89 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
- data/lib/active_record/attribute_methods/write.rb +27 -5
- data/lib/active_record/autosave_association.rb +23 -8
- data/lib/active_record/base.rb +223 -1712
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -13
- data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +79 -0
- data/lib/active_record/errors.rb +11 -1
- data/lib/active_record/explain.rb +83 -0
- data/lib/active_record/explain_subscriber.rb +21 -0
- data/lib/active_record/fixtures.rb +31 -76
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/identity_map.rb +1 -7
- data/lib/active_record/inheritance.rb +167 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locking/optimistic.rb +19 -11
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +3 -3
- data/lib/active_record/migration.rb +38 -29
- data/lib/active_record/migration/command_recorder.rb +7 -7
- data/lib/active_record/model_schema.rb +362 -0
- data/lib/active_record/nested_attributes.rb +3 -2
- data/lib/active_record/persistence.rb +51 -1
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +24 -28
- data/lib/active_record/railties/controller_runtime.rb +3 -1
- data/lib/active_record/railties/databases.rake +133 -77
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +7 -15
- data/lib/active_record/relation.rb +78 -35
- data/lib/active_record/relation/batches.rb +5 -2
- data/lib/active_record/relation/calculations.rb +27 -6
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +5 -4
- data/lib/active_record/relation/predicate_builder.rb +13 -16
- data/lib/active_record/relation/query_methods.rb +59 -4
- data/lib/active_record/result.rb +1 -1
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema_dumper.rb +5 -2
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/scoping/default.rb +140 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/serialization.rb +1 -43
- data/lib/active_record/serializers/xml_serializer.rb +2 -44
- data/lib/active_record/session_store.rb +11 -11
- data/lib/active_record/store.rb +50 -0
- data/lib/active_record/test_case.rb +11 -7
- data/lib/active_record/timestamp.rb +16 -3
- data/lib/active_record/transactions.rb +5 -5
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations.rb +1 -1
- data/lib/active_record/validations/associated.rb +5 -4
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/active_record/version.rb +3 -3
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
- metadata +48 -38
- checksums.yaml +0 -7
- data/lib/active_record/named_scope.rb +0 -200
@@ -16,21 +16,16 @@ module ActiveRecord
|
|
16
16
|
|
17
17
|
module ClassMethods
|
18
18
|
protected
|
19
|
-
#
|
20
|
-
# This enhanced read method automatically converts the UTC time stored in the database to the time
|
19
|
+
# The enhanced read method automatically converts the UTC time stored in the database to the time
|
21
20
|
# zone stored in Time.zone.
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
alias #{attr_name} _#{attr_name}
|
32
|
-
EOV
|
33
|
-
generated_attribute_methods.module_eval(method_body, __FILE__, line)
|
21
|
+
def attribute_cast_code(attr_name)
|
22
|
+
column = columns_hash[attr_name]
|
23
|
+
|
24
|
+
if create_time_zone_conversion_attribute?(attr_name, column)
|
25
|
+
typecast = "v = #{super}"
|
26
|
+
time_zone_conversion = "v.acts_like?(:time) ? v.in_time_zone : v"
|
27
|
+
|
28
|
+
"((#{typecast}) && (#{time_zone_conversion}))"
|
34
29
|
else
|
35
30
|
super
|
36
31
|
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 =~ ActiveModel::AttributeMethods::
|
13
|
+
if attr_name =~ ActiveModel::AttributeMethods::NAME_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|
|
@@ -24,12 +24,14 @@ module ActiveRecord
|
|
24
24
|
# for fixnum and float columns are turned into +nil+.
|
25
25
|
def write_attribute(attr_name, value)
|
26
26
|
attr_name = attr_name.to_s
|
27
|
-
attr_name = self.class.primary_key if attr_name == 'id'
|
27
|
+
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
|
28
28
|
@attributes_cache.delete(attr_name)
|
29
|
-
|
30
|
-
|
29
|
+
column = column_for_attribute(attr_name)
|
30
|
+
|
31
|
+
if column || @attributes.has_key?(attr_name)
|
32
|
+
@attributes[attr_name] = type_cast_attribute_for_write(column, value)
|
31
33
|
else
|
32
|
-
|
34
|
+
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
|
33
35
|
end
|
34
36
|
end
|
35
37
|
alias_method :raw_write_attribute, :write_attribute
|
@@ -39,6 +41,26 @@ module ActiveRecord
|
|
39
41
|
def attribute=(attribute_name, value)
|
40
42
|
write_attribute(attribute_name, value)
|
41
43
|
end
|
44
|
+
|
45
|
+
def type_cast_attribute_for_write(column, value)
|
46
|
+
if column && column.number?
|
47
|
+
convert_number_column_value(value)
|
48
|
+
else
|
49
|
+
value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def convert_number_column_value(value)
|
54
|
+
if value == false
|
55
|
+
0
|
56
|
+
elsif value == true
|
57
|
+
1
|
58
|
+
elsif value.is_a?(String) && value.blank?
|
59
|
+
nil
|
60
|
+
else
|
61
|
+
value
|
62
|
+
end
|
63
|
+
end
|
42
64
|
end
|
43
65
|
end
|
44
66
|
end
|
@@ -21,6 +21,21 @@ module ActiveRecord
|
|
21
21
|
# Note that <tt>:autosave => false</tt> is not same as not declaring <tt>:autosave</tt>.
|
22
22
|
# When the <tt>:autosave</tt> option is not present new associations are saved.
|
23
23
|
#
|
24
|
+
# == Validation
|
25
|
+
#
|
26
|
+
# Children records are validated unless <tt>:validate</tt> is +false+.
|
27
|
+
#
|
28
|
+
# == Callbacks
|
29
|
+
#
|
30
|
+
# Association with autosave option defines several callbacks on your
|
31
|
+
# model (before_save, after_create, after_update). Please note that
|
32
|
+
# callbacks are executed in the order they were defined in
|
33
|
+
# model. You should avoid modyfing the association content, before
|
34
|
+
# autosave callbacks are executed. Placing your callbacks after
|
35
|
+
# associations is usually a good practice.
|
36
|
+
#
|
37
|
+
# == Examples
|
38
|
+
#
|
24
39
|
# === One-to-one Example
|
25
40
|
#
|
26
41
|
# class Post
|
@@ -109,10 +124,7 @@ module ActiveRecord
|
|
109
124
|
# Now it _is_ removed from the database:
|
110
125
|
#
|
111
126
|
# Comment.find_by_id(id).nil? # => true
|
112
|
-
|
113
|
-
# === Validation
|
114
|
-
#
|
115
|
-
# Children records are validated unless <tt>:validate</tt> is +false+.
|
127
|
+
|
116
128
|
module AutosaveAssociation
|
117
129
|
extend ActiveSupport::Concern
|
118
130
|
|
@@ -161,7 +173,7 @@ module ActiveRecord
|
|
161
173
|
#
|
162
174
|
# For performance reasons, we don't check whether to validate at runtime.
|
163
175
|
# However the validation and callback methods are lazy and those methods
|
164
|
-
# get created when they are invoked for the very first time.
|
176
|
+
# get created when they are invoked for the very first time. However,
|
165
177
|
# this can change, for instance, when using nested attributes, which is
|
166
178
|
# called _after_ the association has been defined. Since we don't want
|
167
179
|
# the callbacks to get defined multiple times, there are guards that
|
@@ -264,7 +276,7 @@ module ActiveRecord
|
|
264
276
|
# turned on for the association.
|
265
277
|
def validate_single_association(reflection)
|
266
278
|
association = association_instance_get(reflection.name)
|
267
|
-
record = association && association.
|
279
|
+
record = association && association.reader
|
268
280
|
association_valid?(reflection, record) if record
|
269
281
|
end
|
270
282
|
|
@@ -331,7 +343,7 @@ module ActiveRecord
|
|
331
343
|
if autosave
|
332
344
|
saved = association.insert_record(record, false)
|
333
345
|
else
|
334
|
-
association.insert_record(record)
|
346
|
+
association.insert_record(record) unless reflection.nested?
|
335
347
|
end
|
336
348
|
elsif autosave
|
337
349
|
saved = record.save(:validate => false)
|
@@ -370,7 +382,10 @@ module ActiveRecord
|
|
370
382
|
else
|
371
383
|
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
|
372
384
|
if autosave != false && (new_record? || record.new_record? || record[reflection.foreign_key] != key || autosave)
|
373
|
-
|
385
|
+
unless reflection.through_reflection
|
386
|
+
record[reflection.foreign_key] = key
|
387
|
+
end
|
388
|
+
|
374
389
|
saved = record.save(:validate => !autosave)
|
375
390
|
raise ActiveRecord::Rollback if !saved && autosave
|
376
391
|
saved
|
data/lib/active_record/base.rb
CHANGED
@@ -27,6 +27,7 @@ require 'active_support/deprecation'
|
|
27
27
|
require 'arel'
|
28
28
|
require 'active_record/errors'
|
29
29
|
require 'active_record/log_subscriber'
|
30
|
+
require 'active_record/explain_subscriber'
|
30
31
|
|
31
32
|
module ActiveRecord #:nodoc:
|
32
33
|
# = Active Record
|
@@ -178,6 +179,10 @@ module ActiveRecord #:nodoc:
|
|
178
179
|
# And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do
|
179
180
|
# <tt>Person.find_all_by_last_name(last_name)</tt>.
|
180
181
|
#
|
182
|
+
# It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
|
183
|
+
# <tt>ActiveRecord::RecordNotFound</tt> error if they do not return any records,
|
184
|
+
# like <tt>Person.find_by_last_name!</tt>.
|
185
|
+
#
|
181
186
|
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
|
182
187
|
#
|
183
188
|
# Person.where(:user_name => user_name, :password => password).first
|
@@ -359,44 +364,6 @@ module ActiveRecord #:nodoc:
|
|
359
364
|
cattr_accessor :configurations, :instance_writer => false
|
360
365
|
@@configurations = {}
|
361
366
|
|
362
|
-
##
|
363
|
-
# :singleton-method:
|
364
|
-
# Accessor for the prefix type that will be prepended to every primary key column name.
|
365
|
-
# The options are :table_name and :table_name_with_underscore. If the first is specified,
|
366
|
-
# the Product class will look for "productid" instead of "id" as the primary column. If the
|
367
|
-
# latter is specified, the Product class will look for "product_id" instead of "id". Remember
|
368
|
-
# that this is a global setting for all Active Records.
|
369
|
-
cattr_accessor :primary_key_prefix_type, :instance_writer => false
|
370
|
-
@@primary_key_prefix_type = nil
|
371
|
-
|
372
|
-
##
|
373
|
-
# :singleton-method:
|
374
|
-
# Accessor for the name of the prefix string to prepend to every table name. So if set
|
375
|
-
# to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
|
376
|
-
# etc. This is a convenient way of creating a namespace for tables in a shared database.
|
377
|
-
# By default, the prefix is the empty string.
|
378
|
-
#
|
379
|
-
# If you are organising your models within modules you can add a prefix to the models within
|
380
|
-
# a namespace by defining a singleton method in the parent module called table_name_prefix which
|
381
|
-
# returns your chosen prefix.
|
382
|
-
class_attribute :table_name_prefix, :instance_writer => false
|
383
|
-
self.table_name_prefix = ""
|
384
|
-
|
385
|
-
##
|
386
|
-
# :singleton-method:
|
387
|
-
# Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
|
388
|
-
# "people_basecamp"). By default, the suffix is the empty string.
|
389
|
-
class_attribute :table_name_suffix, :instance_writer => false
|
390
|
-
self.table_name_suffix = ""
|
391
|
-
|
392
|
-
##
|
393
|
-
# :singleton-method:
|
394
|
-
# Indicates whether table names should be the pluralized versions of the corresponding class names.
|
395
|
-
# If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
|
396
|
-
# See table_name for the full rules on table/class naming. This is true, by default.
|
397
|
-
class_attribute :pluralize_table_names, :instance_writer => false
|
398
|
-
self.pluralize_table_names = true
|
399
|
-
|
400
367
|
##
|
401
368
|
# :singleton-method:
|
402
369
|
# Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling
|
@@ -421,1517 +388,276 @@ module ActiveRecord #:nodoc:
|
|
421
388
|
cattr_accessor :timestamped_migrations , :instance_writer => false
|
422
389
|
@@timestamped_migrations = true
|
423
390
|
|
424
|
-
# Determine whether to store the full constant name including namespace when using STI
|
425
|
-
class_attribute :store_full_sti_class
|
426
|
-
self.store_full_sti_class = true
|
427
|
-
|
428
|
-
# Stores the default scope for the class
|
429
|
-
class_attribute :default_scopes, :instance_writer => false
|
430
|
-
self.default_scopes = []
|
431
|
-
|
432
|
-
# Returns a hash of all the attributes that have been specified for serialization as
|
433
|
-
# keys and their class restriction as values.
|
434
|
-
class_attribute :serialized_attributes
|
435
|
-
self.serialized_attributes = {}
|
436
|
-
|
437
|
-
class_attribute :_attr_readonly, :instance_writer => false
|
438
|
-
self._attr_readonly = []
|
439
|
-
|
440
391
|
class << self # Class methods
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
392
|
+
def inherited(child_class) #:nodoc:
|
393
|
+
# force attribute methods to be higher in inheritance hierarchy than other generated methods
|
394
|
+
child_class.generated_attribute_methods
|
395
|
+
child_class.generated_feature_methods
|
396
|
+
super
|
397
|
+
end
|
446
398
|
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
|
454
|
-
# table.
|
455
|
-
#
|
456
|
-
# The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
|
457
|
-
# no database agnostic conversions performed. This should be a last resort because using, for example,
|
458
|
-
# MySQL specific terms will lock you to using that particular database engine or require you to
|
459
|
-
# change your call if you switch engines.
|
460
|
-
#
|
461
|
-
# ==== Examples
|
462
|
-
# # A simple SQL query spanning multiple tables
|
463
|
-
# Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
|
464
|
-
# > [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
|
465
|
-
#
|
466
|
-
# # You can use the same string replacement techniques as you can with ActiveRecord#find
|
467
|
-
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
|
468
|
-
# > [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
|
469
|
-
def find_by_sql(sql, binds = [])
|
470
|
-
connection.select_all(sanitize_sql(sql), "#{name} Load", binds).collect! { |record| instantiate(record) }
|
399
|
+
def generated_feature_methods
|
400
|
+
@generated_feature_methods ||= begin
|
401
|
+
mod = const_set(:GeneratedFeatureMethods, Module.new)
|
402
|
+
include mod
|
403
|
+
mod
|
404
|
+
end
|
471
405
|
end
|
472
406
|
|
473
|
-
#
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
# ==== Examples
|
483
|
-
# # Create a single new object
|
484
|
-
# User.create(:first_name => 'Jamie')
|
485
|
-
#
|
486
|
-
# # Create a single new object using the :admin mass-assignment security role
|
487
|
-
# User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
|
488
|
-
#
|
489
|
-
# # Create a single new object bypassing mass-assignment security
|
490
|
-
# User.create({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
|
491
|
-
#
|
492
|
-
# # Create an Array of new objects
|
493
|
-
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
|
494
|
-
#
|
495
|
-
# # Create a single object and pass it into a block to set other attributes.
|
496
|
-
# User.create(:first_name => 'Jamie') do |u|
|
497
|
-
# u.is_admin = false
|
498
|
-
# end
|
499
|
-
#
|
500
|
-
# # Creating an Array of new objects using a block, where the block is executed for each object:
|
501
|
-
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
|
502
|
-
# u.is_admin = false
|
503
|
-
# end
|
504
|
-
def create(attributes = nil, options = {}, &block)
|
505
|
-
if attributes.is_a?(Array)
|
506
|
-
attributes.collect { |attr| create(attr, options, &block) }
|
407
|
+
# Returns a string like 'Post(id:integer, title:string, body:text)'
|
408
|
+
def inspect
|
409
|
+
if self == Base
|
410
|
+
super
|
411
|
+
elsif abstract_class?
|
412
|
+
"#{super}(abstract)"
|
413
|
+
elsif table_exists?
|
414
|
+
attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
|
415
|
+
"#{super}(#{attr_list})"
|
507
416
|
else
|
508
|
-
|
509
|
-
yield(object) if block_given?
|
510
|
-
object.save
|
511
|
-
object
|
417
|
+
"#{super}(Table doesn't exist)"
|
512
418
|
end
|
513
419
|
end
|
514
420
|
|
515
|
-
#
|
516
|
-
|
517
|
-
|
518
|
-
#
|
519
|
-
# ==== Parameters
|
520
|
-
#
|
521
|
-
# * +sql+ - An SQL statement which should return a count query from the database, see the example below.
|
522
|
-
#
|
523
|
-
# ==== Examples
|
524
|
-
#
|
525
|
-
# Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
|
526
|
-
def count_by_sql(sql)
|
527
|
-
sql = sanitize_conditions(sql)
|
528
|
-
connection.select_value(sql, "#{name} Count").to_i
|
421
|
+
# Overwrite the default class equality method to provide support for association proxies.
|
422
|
+
def ===(object)
|
423
|
+
object.is_a?(self)
|
529
424
|
end
|
530
425
|
|
531
|
-
|
532
|
-
|
533
|
-
def attr_readonly(*attributes)
|
534
|
-
self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
|
426
|
+
def arel_table
|
427
|
+
@arel_table ||= Arel::Table.new(table_name, arel_engine)
|
535
428
|
end
|
536
429
|
|
537
|
-
|
538
|
-
|
539
|
-
|
430
|
+
def arel_engine
|
431
|
+
@arel_engine ||= begin
|
432
|
+
if self == ActiveRecord::Base
|
433
|
+
ActiveRecord::Base
|
434
|
+
else
|
435
|
+
connection_handler.connection_pools[name] ? self : superclass.arel_engine
|
436
|
+
end
|
437
|
+
end
|
540
438
|
end
|
541
439
|
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
#
|
547
|
-
# ==== Parameters
|
548
|
-
#
|
549
|
-
# * +attr_name+ - The field name that should be serialized.
|
550
|
-
# * +class_name+ - Optional, class name that the object type should be equal to.
|
551
|
-
#
|
552
|
-
# ==== Example
|
553
|
-
# # Serialize a preferences attribute
|
554
|
-
# class User < ActiveRecord::Base
|
555
|
-
# serialize :preferences
|
556
|
-
# end
|
557
|
-
def serialize(attr_name, class_name = Object)
|
558
|
-
coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
|
559
|
-
class_name
|
560
|
-
else
|
561
|
-
Coders::YAMLColumn.new(class_name)
|
562
|
-
end
|
440
|
+
private
|
441
|
+
|
442
|
+
def relation #:nodoc:
|
443
|
+
@relation ||= Relation.new(self, arel_table)
|
563
444
|
|
564
|
-
|
565
|
-
|
566
|
-
|
445
|
+
if finder_needs_type_condition?
|
446
|
+
@relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
|
447
|
+
else
|
448
|
+
@relation
|
449
|
+
end
|
567
450
|
end
|
451
|
+
end
|
568
452
|
|
569
|
-
|
570
|
-
#
|
571
|
-
#
|
572
|
-
#
|
573
|
-
#
|
574
|
-
# English inflections. You can add new inflections in config/initializers/inflections.rb.
|
453
|
+
public
|
454
|
+
# New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
|
455
|
+
# attributes but not yet saved (pass a hash with key names matching the associated table column names).
|
456
|
+
# In both instances, valid attribute keys are determined by the column names of the associated table --
|
457
|
+
# hence you can't have attributes that aren't part of the table columns.
|
575
458
|
#
|
576
|
-
#
|
577
|
-
# the
|
459
|
+
# +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
|
460
|
+
# in the +options+ parameter.
|
578
461
|
#
|
579
462
|
# ==== Examples
|
463
|
+
# # Instantiates a single new object
|
464
|
+
# User.new(:first_name => 'Jamie')
|
580
465
|
#
|
581
|
-
#
|
582
|
-
#
|
583
|
-
#
|
584
|
-
# file class table_name
|
585
|
-
# invoice.rb Invoice invoices
|
586
|
-
#
|
587
|
-
# class Invoice < ActiveRecord::Base
|
588
|
-
# class Lineitem < ActiveRecord::Base
|
589
|
-
# end
|
590
|
-
# end
|
591
|
-
#
|
592
|
-
# file class table_name
|
593
|
-
# invoice.rb Invoice::Lineitem invoice_lineitems
|
594
|
-
#
|
595
|
-
# module Invoice
|
596
|
-
# class Lineitem < ActiveRecord::Base
|
597
|
-
# end
|
598
|
-
# end
|
599
|
-
#
|
600
|
-
# file class table_name
|
601
|
-
# invoice/lineitem.rb Invoice::Lineitem lineitems
|
602
|
-
#
|
603
|
-
# Additionally, the class-level +table_name_prefix+ is prepended and the
|
604
|
-
# +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
|
605
|
-
# the table name guess for an Invoice class becomes "myapp_invoices".
|
606
|
-
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
|
607
|
-
#
|
608
|
-
# You can also overwrite this class method to allow for unguessable
|
609
|
-
# links, such as a Mouse class with a link to a "mice" table. Example:
|
466
|
+
# # Instantiates a single new object using the :admin mass-assignment security role
|
467
|
+
# User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
|
610
468
|
#
|
611
|
-
#
|
612
|
-
#
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
@
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
return if abstract_class?
|
626
|
-
|
627
|
-
self.table_name = compute_table_name
|
628
|
-
end
|
469
|
+
# # Instantiates a single new object bypassing mass-assignment security
|
470
|
+
# User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
|
471
|
+
def initialize(attributes = nil, options = {})
|
472
|
+
@attributes = attributes_from_column_definition
|
473
|
+
@association_cache = {}
|
474
|
+
@aggregation_cache = {}
|
475
|
+
@attributes_cache = {}
|
476
|
+
@new_record = true
|
477
|
+
@readonly = false
|
478
|
+
@destroyed = false
|
479
|
+
@marked_for_destruction = false
|
480
|
+
@previously_changed = {}
|
481
|
+
@changed_attributes = {}
|
482
|
+
@relation = nil
|
629
483
|
|
630
|
-
|
631
|
-
|
632
|
-
end
|
484
|
+
ensure_proper_type
|
485
|
+
set_serialized_attributes
|
633
486
|
|
634
|
-
|
635
|
-
# <tt>set_inheritance_column</tt> to set a different value.
|
636
|
-
def inheritance_column
|
637
|
-
@inheritance_column ||= "type"
|
638
|
-
end
|
487
|
+
populate_with_current_scope_attributes
|
639
488
|
|
640
|
-
|
641
|
-
# is only ever called once since set_sequence_name overrides it.
|
642
|
-
def sequence_name #:nodoc:
|
643
|
-
reset_sequence_name
|
644
|
-
end
|
489
|
+
assign_attributes(attributes, options) if attributes
|
645
490
|
|
646
|
-
|
647
|
-
|
648
|
-
set_sequence_name(default)
|
649
|
-
default
|
491
|
+
yield self if block_given?
|
492
|
+
run_callbacks :initialize
|
650
493
|
end
|
651
494
|
|
652
|
-
#
|
653
|
-
#
|
495
|
+
# Initialize an empty model object from +coder+. +coder+ must contain
|
496
|
+
# the attributes necessary for initializing an empty model object. For
|
497
|
+
# example:
|
654
498
|
#
|
655
|
-
# class
|
656
|
-
# set_table_name "project"
|
499
|
+
# class Post < ActiveRecord::Base
|
657
500
|
# end
|
658
|
-
def set_table_name(value = nil, &block)
|
659
|
-
@quoted_table_name = nil
|
660
|
-
define_attr_method :table_name, value, &block
|
661
|
-
@arel_table = nil
|
662
|
-
|
663
|
-
@arel_table = Arel::Table.new(table_name, arel_engine)
|
664
|
-
@relation = Relation.new(self, arel_table)
|
665
|
-
end
|
666
|
-
alias :table_name= :set_table_name
|
667
|
-
|
668
|
-
# Sets the name of the inheritance column to use to the given value,
|
669
|
-
# or (if the value # is nil or false) to the value returned by the
|
670
|
-
# given block.
|
671
501
|
#
|
672
|
-
#
|
673
|
-
#
|
674
|
-
#
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
define_attr_method :inheritance_column, value, &block
|
679
|
-
end
|
680
|
-
alias :inheritance_column= :set_inheritance_column
|
502
|
+
# post = Post.allocate
|
503
|
+
# post.init_with('attributes' => { 'title' => 'hello world' })
|
504
|
+
# post.title # => 'hello world'
|
505
|
+
def init_with(coder)
|
506
|
+
@attributes = coder['attributes']
|
507
|
+
@relation = nil
|
681
508
|
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
# will discover the sequence corresponding to your primary key for you.
|
692
|
-
#
|
693
|
-
# class Project < ActiveRecord::Base
|
694
|
-
# set_sequence_name "projectseq" # default would have been "project_seq"
|
695
|
-
# end
|
696
|
-
def set_sequence_name(value = nil, &block)
|
697
|
-
define_attr_method :sequence_name, value, &block
|
698
|
-
end
|
699
|
-
alias :sequence_name= :set_sequence_name
|
509
|
+
set_serialized_attributes
|
510
|
+
|
511
|
+
@attributes_cache, @previously_changed, @changed_attributes = {}, {}, {}
|
512
|
+
@association_cache = {}
|
513
|
+
@aggregation_cache = {}
|
514
|
+
@readonly = @destroyed = @marked_for_destruction = false
|
515
|
+
@new_record = false
|
516
|
+
run_callbacks :find
|
517
|
+
run_callbacks :initialize
|
700
518
|
|
701
|
-
|
702
|
-
def table_exists?
|
703
|
-
connection.table_exists?(table_name)
|
519
|
+
self
|
704
520
|
end
|
705
521
|
|
706
|
-
#
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
522
|
+
# Duped objects have no id assigned and are treated as new records. Note
|
523
|
+
# that this is a "shallow" copy as it copies the object's attributes
|
524
|
+
# only, not its associations. The extent of a "deep" copy is application
|
525
|
+
# specific and is therefore left to the application to implement according
|
526
|
+
# to its need.
|
527
|
+
# The dup method does not preserve the timestamps (created|updated)_(at|on).
|
528
|
+
def initialize_dup(other)
|
529
|
+
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
|
530
|
+
cloned_attributes.delete(self.class.primary_key)
|
711
531
|
|
712
|
-
|
713
|
-
end
|
532
|
+
@attributes = cloned_attributes
|
714
533
|
|
715
|
-
|
716
|
-
def columns_hash
|
717
|
-
connection_pool.columns_hash[table_name]
|
718
|
-
end
|
534
|
+
_run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
|
719
535
|
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
end
|
536
|
+
@changed_attributes = {}
|
537
|
+
attributes_from_column_definition.each do |attr, orig_value|
|
538
|
+
@changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr])
|
539
|
+
end
|
725
540
|
|
726
|
-
|
727
|
-
|
728
|
-
@
|
729
|
-
|
541
|
+
@aggregation_cache = {}
|
542
|
+
@association_cache = {}
|
543
|
+
@attributes_cache = {}
|
544
|
+
@new_record = true
|
730
545
|
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
@content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
|
546
|
+
ensure_proper_type
|
547
|
+
populate_with_current_scope_attributes
|
548
|
+
super
|
735
549
|
end
|
736
550
|
|
737
|
-
#
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
methods[attr.to_sym] = attr_name
|
744
|
-
methods["#{attr}=".to_sym] = attr_name
|
745
|
-
methods["#{attr}?".to_sym] = attr_name
|
746
|
-
methods["#{attr}_before_type_cast".to_sym] = attr_name
|
747
|
-
methods
|
551
|
+
# Backport dup from 1.9 so that initialize_dup() gets called
|
552
|
+
unless Object.respond_to?(:initialize_dup)
|
553
|
+
def dup # :nodoc:
|
554
|
+
copy = super
|
555
|
+
copy.initialize_dup(self)
|
556
|
+
copy
|
748
557
|
end
|
749
558
|
end
|
750
559
|
|
751
|
-
#
|
752
|
-
#
|
753
|
-
#
|
754
|
-
#
|
755
|
-
# when just after creating a table you want to populate it with some default
|
756
|
-
# values, eg:
|
560
|
+
# Populate +coder+ with attributes about this record that should be
|
561
|
+
# serialized. The structure of +coder+ defined in this method is
|
562
|
+
# guaranteed to match the structure of +coder+ passed to the +init_with+
|
563
|
+
# method.
|
757
564
|
#
|
758
|
-
#
|
759
|
-
# def up
|
760
|
-
# create_table :job_levels do |t|
|
761
|
-
# t.integer :id
|
762
|
-
# t.string :name
|
565
|
+
# Example:
|
763
566
|
#
|
764
|
-
#
|
765
|
-
#
|
567
|
+
# class Post < ActiveRecord::Base
|
568
|
+
# end
|
569
|
+
# coder = {}
|
570
|
+
# Post.new.encode_with(coder)
|
571
|
+
# coder # => { 'id' => nil, ... }
|
572
|
+
def encode_with(coder)
|
573
|
+
coder['attributes'] = attributes
|
574
|
+
end
|
575
|
+
|
576
|
+
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
|
577
|
+
# is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
|
766
578
|
#
|
767
|
-
#
|
768
|
-
#
|
769
|
-
#
|
770
|
-
# end
|
771
|
-
# end
|
579
|
+
# Note that new records are different from any other record by definition, unless the
|
580
|
+
# other record is the receiver itself. Besides, if you fetch existing records with
|
581
|
+
# +select+ and leave the ID out, you're on your own, this predicate will return false.
|
772
582
|
#
|
773
|
-
#
|
774
|
-
#
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
connection_pool.clear_table_cache!(table_name) if table_exists?
|
781
|
-
|
782
|
-
@column_names = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
|
783
|
-
@arel_engine = @relation = nil
|
583
|
+
# Note also that destroying a record preserves its ID in the model instance, so deleted
|
584
|
+
# models are still comparable.
|
585
|
+
def ==(comparison_object)
|
586
|
+
super ||
|
587
|
+
comparison_object.instance_of?(self.class) &&
|
588
|
+
id.present? &&
|
589
|
+
comparison_object.id == id
|
784
590
|
end
|
591
|
+
alias :eql? :==
|
785
592
|
|
786
|
-
|
787
|
-
|
593
|
+
# Delegates to id in order to allow two records of the same type and id to work with something like:
|
594
|
+
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
|
595
|
+
def hash
|
596
|
+
id.hash
|
788
597
|
end
|
789
598
|
|
790
|
-
|
791
|
-
|
599
|
+
# Freeze the attributes hash such that associations are still accessible, even on destroyed records.
|
600
|
+
def freeze
|
601
|
+
@attributes.freeze; self
|
792
602
|
end
|
793
603
|
|
794
|
-
# Returns
|
795
|
-
|
796
|
-
|
797
|
-
def attribute_names
|
798
|
-
@attribute_names ||= if !abstract_class? && table_exists?
|
799
|
-
column_names
|
800
|
-
else
|
801
|
-
[]
|
802
|
-
end
|
604
|
+
# Returns +true+ if the attributes hash has been frozen.
|
605
|
+
def frozen?
|
606
|
+
@attributes.frozen?
|
803
607
|
end
|
804
608
|
|
805
|
-
#
|
806
|
-
def
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
while klass != klass.base_class
|
812
|
-
classes << klass = klass.superclass
|
609
|
+
# Allows sort on objects
|
610
|
+
def <=>(other_object)
|
611
|
+
if other_object.is_a?(self.class)
|
612
|
+
self.to_key <=> other_object.to_key
|
613
|
+
else
|
614
|
+
nil
|
813
615
|
end
|
814
|
-
classes
|
815
616
|
end
|
816
617
|
|
817
|
-
#
|
818
|
-
|
819
|
-
|
618
|
+
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
|
619
|
+
# attributes will be marked as read only since they cannot be saved.
|
620
|
+
def readonly?
|
621
|
+
@readonly
|
622
|
+
end
|
623
|
+
|
624
|
+
# Marks this record as read only.
|
625
|
+
def readonly!
|
626
|
+
@readonly = true
|
820
627
|
end
|
821
628
|
|
822
|
-
#
|
823
|
-
def
|
824
|
-
if
|
825
|
-
|
629
|
+
# Returns the contents of the record as a nicely formatted string.
|
630
|
+
def inspect
|
631
|
+
inspection = if @attributes
|
632
|
+
self.class.column_names.collect { |name|
|
633
|
+
if has_attribute?(name)
|
634
|
+
"#{name}: #{attribute_for_inspect(name)}"
|
635
|
+
end
|
636
|
+
}.compact.join(", ")
|
637
|
+
else
|
638
|
+
"not initialized"
|
639
|
+
end
|
640
|
+
"#<#{self.class} #{inspection}>"
|
641
|
+
end
|
642
|
+
|
643
|
+
# Hackery to accomodate Syck. Remove for 4.0.
|
644
|
+
def to_yaml(opts = {}) #:nodoc:
|
645
|
+
if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck?
|
646
|
+
super
|
826
647
|
else
|
827
|
-
|
648
|
+
coder = {}
|
649
|
+
encode_with(coder)
|
650
|
+
YAML.quick_emit(self, opts) do |out|
|
651
|
+
out.map(taguri, to_yaml_style) do |map|
|
652
|
+
coder.each { |k, v| map.add(k, v) }
|
653
|
+
end
|
654
|
+
end
|
828
655
|
end
|
829
656
|
end
|
830
657
|
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
end
|
835
|
-
|
836
|
-
# Returns a string like 'Post(id:integer, title:string, body:text)'
|
837
|
-
def inspect
|
838
|
-
if self == Base
|
839
|
-
super
|
840
|
-
elsif abstract_class?
|
841
|
-
"#{super}(abstract)"
|
842
|
-
elsif table_exists?
|
843
|
-
attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
|
844
|
-
"#{super}(#{attr_list})"
|
845
|
-
else
|
846
|
-
"#{super}(Table doesn't exist)"
|
847
|
-
end
|
848
|
-
end
|
849
|
-
|
850
|
-
def quote_value(value, column = nil) #:nodoc:
|
851
|
-
connection.quote(value,column)
|
852
|
-
end
|
853
|
-
|
854
|
-
# Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
|
855
|
-
def sanitize(object) #:nodoc:
|
856
|
-
connection.quote(object)
|
857
|
-
end
|
858
|
-
|
859
|
-
# Overwrite the default class equality method to provide support for association proxies.
|
860
|
-
def ===(object)
|
861
|
-
object.is_a?(self)
|
862
|
-
end
|
863
|
-
|
864
|
-
def symbolized_base_class
|
865
|
-
@symbolized_base_class ||= base_class.to_s.to_sym
|
866
|
-
end
|
867
|
-
|
868
|
-
def symbolized_sti_name
|
869
|
-
@symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
|
870
|
-
end
|
871
|
-
|
872
|
-
# Returns the base AR subclass that this class descends from. If A
|
873
|
-
# extends AR::Base, A.base_class will return A. If B descends from A
|
874
|
-
# through some arbitrarily deep hierarchy, B.base_class will return A.
|
875
|
-
#
|
876
|
-
# If B < A and C < B and if A is an abstract_class then both B.base_class
|
877
|
-
# and C.base_class would return B as the answer since A is an abstract_class.
|
878
|
-
def base_class
|
879
|
-
class_of_active_record_descendant(self)
|
880
|
-
end
|
881
|
-
|
882
|
-
# Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
|
883
|
-
attr_accessor :abstract_class
|
884
|
-
|
885
|
-
# Returns whether this class is an abstract class or not.
|
886
|
-
def abstract_class?
|
887
|
-
defined?(@abstract_class) && @abstract_class == true
|
888
|
-
end
|
889
|
-
|
890
|
-
def respond_to?(method_id, include_private = false)
|
891
|
-
if match = DynamicFinderMatch.match(method_id)
|
892
|
-
return true if all_attributes_exists?(match.attribute_names)
|
893
|
-
elsif match = DynamicScopeMatch.match(method_id)
|
894
|
-
return true if all_attributes_exists?(match.attribute_names)
|
895
|
-
end
|
896
|
-
|
897
|
-
super
|
898
|
-
end
|
899
|
-
|
900
|
-
def sti_name
|
901
|
-
store_full_sti_class ? name : name.demodulize
|
902
|
-
end
|
903
|
-
|
904
|
-
def arel_table
|
905
|
-
@arel_table ||= Arel::Table.new(table_name, arel_engine)
|
906
|
-
end
|
907
|
-
|
908
|
-
def arel_engine
|
909
|
-
@arel_engine ||= begin
|
910
|
-
if self == ActiveRecord::Base
|
911
|
-
ActiveRecord::Base
|
912
|
-
else
|
913
|
-
connection_handler.connection_pools[name] ? self : superclass.arel_engine
|
914
|
-
end
|
915
|
-
end
|
916
|
-
end
|
917
|
-
|
918
|
-
# Returns a scope for this class without taking into account the default_scope.
|
919
|
-
#
|
920
|
-
# class Post < ActiveRecord::Base
|
921
|
-
# def self.default_scope
|
922
|
-
# where :published => true
|
923
|
-
# end
|
924
|
-
# end
|
925
|
-
#
|
926
|
-
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
|
927
|
-
# Post.unscoped.all # Fires "SELECT * FROM posts"
|
928
|
-
#
|
929
|
-
# This method also accepts a block meaning that all queries inside the block will
|
930
|
-
# not use the default_scope:
|
931
|
-
#
|
932
|
-
# Post.unscoped {
|
933
|
-
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
|
934
|
-
# }
|
935
|
-
#
|
936
|
-
# It is recommended to use block form of unscoped because chaining unscoped with <tt>scope</tt>
|
937
|
-
# does not work. Assuming that <tt>published</tt> is a <tt>scope</tt> following two statements are same.
|
938
|
-
#
|
939
|
-
# Post.unscoped.published
|
940
|
-
# Post.published
|
941
|
-
def unscoped #:nodoc:
|
942
|
-
block_given? ? relation.scoping { yield } : relation
|
943
|
-
end
|
944
|
-
|
945
|
-
def before_remove_const #:nodoc:
|
946
|
-
self.current_scope = nil
|
947
|
-
end
|
948
|
-
|
949
|
-
# Finder methods must instantiate through this method to work with the
|
950
|
-
# single-table inheritance model that makes it possible to create
|
951
|
-
# objects of different types from the same table.
|
952
|
-
def instantiate(record)
|
953
|
-
sti_class = find_sti_class(record[inheritance_column])
|
954
|
-
record_id = sti_class.primary_key && record[sti_class.primary_key]
|
955
|
-
|
956
|
-
if ActiveRecord::IdentityMap.enabled? && record_id
|
957
|
-
instance = use_identity_map(sti_class, record_id, record)
|
958
|
-
else
|
959
|
-
instance = sti_class.allocate.init_with('attributes' => record)
|
960
|
-
end
|
961
|
-
|
962
|
-
instance
|
963
|
-
end
|
964
|
-
|
965
|
-
private
|
966
|
-
|
967
|
-
def use_identity_map(sti_class, record_id, record)
|
968
|
-
if (column = sti_class.columns_hash[sti_class.primary_key]) && column.number?
|
969
|
-
record_id = record_id.to_i
|
970
|
-
end
|
971
|
-
|
972
|
-
if instance = IdentityMap.get(sti_class, record_id)
|
973
|
-
instance.reinit_with('attributes' => record)
|
974
|
-
else
|
975
|
-
instance = sti_class.allocate.init_with('attributes' => record)
|
976
|
-
IdentityMap.add(instance)
|
977
|
-
end
|
978
|
-
|
979
|
-
instance
|
980
|
-
end
|
981
|
-
|
982
|
-
def relation #:nodoc:
|
983
|
-
@relation ||= Relation.new(self, arel_table)
|
984
|
-
|
985
|
-
if finder_needs_type_condition?
|
986
|
-
@relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
|
987
|
-
else
|
988
|
-
@relation
|
989
|
-
end
|
990
|
-
end
|
991
|
-
|
992
|
-
def find_sti_class(type_name)
|
993
|
-
if type_name.blank? || !columns_hash.include?(inheritance_column)
|
994
|
-
self
|
995
|
-
else
|
996
|
-
begin
|
997
|
-
if store_full_sti_class
|
998
|
-
ActiveSupport::Dependencies.constantize(type_name)
|
999
|
-
else
|
1000
|
-
compute_type(type_name)
|
1001
|
-
end
|
1002
|
-
rescue NameError
|
1003
|
-
raise SubclassNotFound,
|
1004
|
-
"The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
|
1005
|
-
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
|
1006
|
-
"Please rename this column if you didn't intend it to be used for storing the inheritance class " +
|
1007
|
-
"or overwrite #{name}.inheritance_column to use another column for that information."
|
1008
|
-
end
|
1009
|
-
end
|
1010
|
-
end
|
1011
|
-
|
1012
|
-
def construct_finder_arel(options = {}, scope = nil)
|
1013
|
-
relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options) : options
|
1014
|
-
relation = scope.merge(relation) if scope
|
1015
|
-
relation
|
1016
|
-
end
|
1017
|
-
|
1018
|
-
def type_condition(table = arel_table)
|
1019
|
-
sti_column = table[inheritance_column.to_sym]
|
1020
|
-
sti_names = ([self] + descendants).map { |model| model.sti_name }
|
1021
|
-
|
1022
|
-
sti_column.in(sti_names)
|
1023
|
-
end
|
1024
|
-
|
1025
|
-
# Guesses the table name, but does not decorate it with prefix and suffix information.
|
1026
|
-
def undecorated_table_name(class_name = base_class.name)
|
1027
|
-
table_name = class_name.to_s.demodulize.underscore
|
1028
|
-
table_name = table_name.pluralize if pluralize_table_names
|
1029
|
-
table_name
|
1030
|
-
end
|
1031
|
-
|
1032
|
-
# Computes and returns a table name according to default conventions.
|
1033
|
-
def compute_table_name
|
1034
|
-
base = base_class
|
1035
|
-
if self == base
|
1036
|
-
# Nested classes are prefixed with singular parent table name.
|
1037
|
-
if parent < ActiveRecord::Base && !parent.abstract_class?
|
1038
|
-
contained = parent.table_name
|
1039
|
-
contained = contained.singularize if parent.pluralize_table_names
|
1040
|
-
contained += '_'
|
1041
|
-
end
|
1042
|
-
"#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{table_name_suffix}"
|
1043
|
-
else
|
1044
|
-
# STI subclasses always use their superclass' table.
|
1045
|
-
base.table_name
|
1046
|
-
end
|
1047
|
-
end
|
1048
|
-
|
1049
|
-
# Enables dynamic finders like <tt>User.find_by_user_name(user_name)</tt> and
|
1050
|
-
# <tt>User.scoped_by_user_name(user_name). Refer to Dynamic attribute-based finders
|
1051
|
-
# section at the top of this file for more detailed information.
|
1052
|
-
#
|
1053
|
-
# It's even possible to use all the additional parameters to +find+. For example, the
|
1054
|
-
# full interface for +find_all_by_amount+ is actually <tt>find_all_by_amount(amount, options)</tt>.
|
1055
|
-
#
|
1056
|
-
# Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it
|
1057
|
-
# is first invoked, so that future attempts to use it do not run through method_missing.
|
1058
|
-
def method_missing(method_id, *arguments, &block)
|
1059
|
-
if match = DynamicFinderMatch.match(method_id)
|
1060
|
-
attribute_names = match.attribute_names
|
1061
|
-
super unless all_attributes_exists?(attribute_names)
|
1062
|
-
if !arguments.first.is_a?(Hash) && arguments.size < attribute_names.size
|
1063
|
-
ActiveSupport::Deprecation.warn(<<-eowarn)
|
1064
|
-
Calling dynamic finder with less number of arguments than the number of attributes in the method name is deprecated and will raise an ArgumentError in the next version of Rails. Please pass `nil' explicitly to the arguments that are left out.
|
1065
|
-
eowarn
|
1066
|
-
end
|
1067
|
-
if match.finder?
|
1068
|
-
options = if arguments.length > attribute_names.size
|
1069
|
-
arguments.extract_options!
|
1070
|
-
else
|
1071
|
-
{}
|
1072
|
-
end
|
1073
|
-
relation = options.any? ? scoped(options) : scoped
|
1074
|
-
relation.send :find_by_attributes, match, attribute_names, *arguments
|
1075
|
-
elsif match.instantiator?
|
1076
|
-
scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
|
1077
|
-
end
|
1078
|
-
elsif match = DynamicScopeMatch.match(method_id)
|
1079
|
-
attribute_names = match.attribute_names
|
1080
|
-
super unless all_attributes_exists?(attribute_names)
|
1081
|
-
if arguments.size < attribute_names.size
|
1082
|
-
ActiveSupport::Deprecation.warn(
|
1083
|
-
"Calling dynamic scope with less number of arguments than the number of attributes in " \
|
1084
|
-
"the method name is deprecated and will raise an ArgumentError in the next version of Rails. " \
|
1085
|
-
"Please pass `nil' explicitly to the arguments that are left out."
|
1086
|
-
)
|
1087
|
-
end
|
1088
|
-
if match.scope?
|
1089
|
-
self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
1090
|
-
def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
|
1091
|
-
attributes = Hash[[:#{attribute_names.join(',:')}].zip(args)] # attributes = Hash[[:user_name, :password].zip(args)]
|
1092
|
-
#
|
1093
|
-
scoped(:conditions => attributes) # scoped(:conditions => attributes)
|
1094
|
-
end # end
|
1095
|
-
METHOD
|
1096
|
-
send(method_id, *arguments)
|
1097
|
-
end
|
1098
|
-
else
|
1099
|
-
super
|
1100
|
-
end
|
1101
|
-
end
|
1102
|
-
|
1103
|
-
# Similar in purpose to +expand_hash_conditions_for_aggregates+.
|
1104
|
-
def expand_attribute_names_for_aggregates(attribute_names)
|
1105
|
-
attribute_names.map { |attribute_name|
|
1106
|
-
unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil?
|
1107
|
-
aggregate_mapping(aggregation).map do |field_attr, _|
|
1108
|
-
field_attr.to_sym
|
1109
|
-
end
|
1110
|
-
else
|
1111
|
-
attribute_name.to_sym
|
1112
|
-
end
|
1113
|
-
}.flatten
|
1114
|
-
end
|
1115
|
-
|
1116
|
-
def all_attributes_exists?(attribute_names)
|
1117
|
-
(expand_attribute_names_for_aggregates(attribute_names) -
|
1118
|
-
column_methods_hash.keys).empty?
|
1119
|
-
end
|
1120
|
-
|
1121
|
-
protected
|
1122
|
-
# with_scope lets you apply options to inner block incrementally. It takes a hash and the keys must be
|
1123
|
-
# <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while
|
1124
|
-
# <tt>:create</tt> parameters are an attributes hash.
|
1125
|
-
#
|
1126
|
-
# class Article < ActiveRecord::Base
|
1127
|
-
# def self.create_with_scope
|
1128
|
-
# with_scope(:find => where(:blog_id => 1), :create => { :blog_id => 1 }) do
|
1129
|
-
# find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
|
1130
|
-
# a = create(1)
|
1131
|
-
# a.blog_id # => 1
|
1132
|
-
# end
|
1133
|
-
# end
|
1134
|
-
# end
|
1135
|
-
#
|
1136
|
-
# In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
|
1137
|
-
# <tt>where</tt>, <tt>includes</tt>, and <tt>joins</tt> operations in <tt>Relation</tt>, which are merged.
|
1138
|
-
#
|
1139
|
-
# <tt>joins</tt> operations are uniqued so multiple scopes can join in the same table without table aliasing
|
1140
|
-
# problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
|
1141
|
-
# array of strings format for your joins.
|
1142
|
-
#
|
1143
|
-
# class Article < ActiveRecord::Base
|
1144
|
-
# def self.find_with_scope
|
1145
|
-
# with_scope(:find => where(:blog_id => 1).limit(1), :create => { :blog_id => 1 }) do
|
1146
|
-
# with_scope(:find => limit(10)) do
|
1147
|
-
# all # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
|
1148
|
-
# end
|
1149
|
-
# with_scope(:find => where(:author_id => 3)) do
|
1150
|
-
# all # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
|
1151
|
-
# end
|
1152
|
-
# end
|
1153
|
-
# end
|
1154
|
-
# end
|
1155
|
-
#
|
1156
|
-
# You can ignore any previous scopings by using the <tt>with_exclusive_scope</tt> method.
|
1157
|
-
#
|
1158
|
-
# class Article < ActiveRecord::Base
|
1159
|
-
# def self.find_with_exclusive_scope
|
1160
|
-
# with_scope(:find => where(:blog_id => 1).limit(1)) do
|
1161
|
-
# with_exclusive_scope(:find => limit(10)) do
|
1162
|
-
# all # => SELECT * from articles LIMIT 10
|
1163
|
-
# end
|
1164
|
-
# end
|
1165
|
-
# end
|
1166
|
-
# end
|
1167
|
-
#
|
1168
|
-
# *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
|
1169
|
-
def with_scope(scope = {}, action = :merge, &block)
|
1170
|
-
# If another Active Record class has been passed in, get its current scope
|
1171
|
-
scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope)
|
1172
|
-
|
1173
|
-
previous_scope = self.current_scope
|
1174
|
-
|
1175
|
-
if scope.is_a?(Hash)
|
1176
|
-
# Dup first and second level of hash (method and params).
|
1177
|
-
scope = scope.dup
|
1178
|
-
scope.each do |method, params|
|
1179
|
-
scope[method] = params.dup unless params == true
|
1180
|
-
end
|
1181
|
-
|
1182
|
-
scope.assert_valid_keys([ :find, :create ])
|
1183
|
-
relation = construct_finder_arel(scope[:find] || {})
|
1184
|
-
relation.default_scoped = true unless action == :overwrite
|
1185
|
-
|
1186
|
-
if previous_scope && previous_scope.create_with_value && scope[:create]
|
1187
|
-
scope_for_create = if action == :merge
|
1188
|
-
previous_scope.create_with_value.merge(scope[:create])
|
1189
|
-
else
|
1190
|
-
scope[:create]
|
1191
|
-
end
|
1192
|
-
|
1193
|
-
relation = relation.create_with(scope_for_create)
|
1194
|
-
else
|
1195
|
-
scope_for_create = scope[:create]
|
1196
|
-
scope_for_create ||= previous_scope.create_with_value if previous_scope
|
1197
|
-
relation = relation.create_with(scope_for_create) if scope_for_create
|
1198
|
-
end
|
1199
|
-
|
1200
|
-
scope = relation
|
1201
|
-
end
|
1202
|
-
|
1203
|
-
scope = previous_scope.merge(scope) if previous_scope && action == :merge
|
1204
|
-
|
1205
|
-
self.current_scope = scope
|
1206
|
-
begin
|
1207
|
-
yield
|
1208
|
-
ensure
|
1209
|
-
self.current_scope = previous_scope
|
1210
|
-
end
|
1211
|
-
end
|
1212
|
-
|
1213
|
-
# Works like with_scope, but discards any nested properties.
|
1214
|
-
def with_exclusive_scope(method_scoping = {}, &block)
|
1215
|
-
if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) }
|
1216
|
-
raise ArgumentError, <<-MSG
|
1217
|
-
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:
|
1218
|
-
|
1219
|
-
User.unscoped.where(:active => true)
|
1220
|
-
|
1221
|
-
Or call unscoped with a block:
|
1222
|
-
|
1223
|
-
User.unscoped do
|
1224
|
-
User.where(:active => true).all
|
1225
|
-
end
|
1226
|
-
|
1227
|
-
MSG
|
1228
|
-
end
|
1229
|
-
with_scope(method_scoping, :overwrite, &block)
|
1230
|
-
end
|
1231
|
-
|
1232
|
-
def current_scope #:nodoc:
|
1233
|
-
Thread.current["#{self}_current_scope"]
|
1234
|
-
end
|
1235
|
-
|
1236
|
-
def current_scope=(scope) #:nodoc:
|
1237
|
-
Thread.current["#{self}_current_scope"] = scope
|
1238
|
-
end
|
1239
|
-
|
1240
|
-
# Use this macro in your model to set a default scope for all operations on
|
1241
|
-
# the model.
|
1242
|
-
#
|
1243
|
-
# class Article < ActiveRecord::Base
|
1244
|
-
# default_scope where(:published => true)
|
1245
|
-
# end
|
1246
|
-
#
|
1247
|
-
# Article.all # => SELECT * FROM articles WHERE published = true
|
1248
|
-
#
|
1249
|
-
# The <tt>default_scope</tt> is also applied while creating/building a record. It is not
|
1250
|
-
# applied while updating a record.
|
1251
|
-
#
|
1252
|
-
# Article.new.published # => true
|
1253
|
-
# Article.create.published # => true
|
1254
|
-
#
|
1255
|
-
# You can also use <tt>default_scope</tt> with a block, in order to have it lazily evaluated:
|
1256
|
-
#
|
1257
|
-
# class Article < ActiveRecord::Base
|
1258
|
-
# default_scope { where(:published_at => Time.now - 1.week) }
|
1259
|
-
# end
|
1260
|
-
#
|
1261
|
-
# (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
|
1262
|
-
# macro, and it will be called when building the default scope.)
|
1263
|
-
#
|
1264
|
-
# If you use multiple <tt>default_scope</tt> declarations in your model then they will
|
1265
|
-
# be merged together:
|
1266
|
-
#
|
1267
|
-
# class Article < ActiveRecord::Base
|
1268
|
-
# default_scope where(:published => true)
|
1269
|
-
# default_scope where(:rating => 'G')
|
1270
|
-
# end
|
1271
|
-
#
|
1272
|
-
# Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
|
1273
|
-
#
|
1274
|
-
# This is also the case with inheritance and module includes where the parent or module
|
1275
|
-
# defines a <tt>default_scope</tt> and the child or including class defines a second one.
|
1276
|
-
#
|
1277
|
-
# If you need to do more complex things with a default scope, you can alternatively
|
1278
|
-
# define it as a class method:
|
1279
|
-
#
|
1280
|
-
# class Article < ActiveRecord::Base
|
1281
|
-
# def self.default_scope
|
1282
|
-
# # Should return a scope, you can call 'super' here etc.
|
1283
|
-
# end
|
1284
|
-
# end
|
1285
|
-
def default_scope(scope = {})
|
1286
|
-
scope = Proc.new if block_given?
|
1287
|
-
self.default_scopes = default_scopes + [scope]
|
1288
|
-
end
|
1289
|
-
|
1290
|
-
def build_default_scope #:nodoc:
|
1291
|
-
if method(:default_scope).owner != Base.singleton_class
|
1292
|
-
evaluate_default_scope { default_scope }
|
1293
|
-
elsif default_scopes.any?
|
1294
|
-
evaluate_default_scope do
|
1295
|
-
default_scopes.inject(relation) do |default_scope, scope|
|
1296
|
-
if scope.is_a?(Hash)
|
1297
|
-
default_scope.apply_finder_options(scope)
|
1298
|
-
elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
|
1299
|
-
default_scope.merge(scope.call)
|
1300
|
-
else
|
1301
|
-
default_scope.merge(scope)
|
1302
|
-
end
|
1303
|
-
end
|
1304
|
-
end
|
1305
|
-
end
|
1306
|
-
end
|
1307
|
-
|
1308
|
-
def ignore_default_scope? #:nodoc:
|
1309
|
-
Thread.current["#{self}_ignore_default_scope"]
|
1310
|
-
end
|
1311
|
-
|
1312
|
-
def ignore_default_scope=(ignore) #:nodoc:
|
1313
|
-
Thread.current["#{self}_ignore_default_scope"] = ignore
|
1314
|
-
end
|
1315
|
-
|
1316
|
-
# The ignore_default_scope flag is used to prevent an infinite recursion situation where
|
1317
|
-
# a default scope references a scope which has a default scope which references a scope...
|
1318
|
-
def evaluate_default_scope
|
1319
|
-
return if ignore_default_scope?
|
1320
|
-
|
1321
|
-
begin
|
1322
|
-
self.ignore_default_scope = true
|
1323
|
-
yield
|
1324
|
-
ensure
|
1325
|
-
self.ignore_default_scope = false
|
1326
|
-
end
|
1327
|
-
end
|
1328
|
-
|
1329
|
-
# Returns the class type of the record using the current module as a prefix. So descendants of
|
1330
|
-
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
|
1331
|
-
def compute_type(type_name)
|
1332
|
-
if type_name.match(/^::/)
|
1333
|
-
# If the type is prefixed with a scope operator then we assume that
|
1334
|
-
# the type_name is an absolute reference.
|
1335
|
-
ActiveSupport::Dependencies.constantize(type_name)
|
1336
|
-
else
|
1337
|
-
# Build a list of candidates to search for
|
1338
|
-
candidates = []
|
1339
|
-
name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
|
1340
|
-
candidates << type_name
|
1341
|
-
|
1342
|
-
candidates.each do |candidate|
|
1343
|
-
begin
|
1344
|
-
constant = ActiveSupport::Dependencies.constantize(candidate)
|
1345
|
-
return constant if candidate == constant.to_s
|
1346
|
-
rescue NameError => e
|
1347
|
-
# We don't want to swallow NoMethodError < NameError errors
|
1348
|
-
raise e unless e.instance_of?(NameError)
|
1349
|
-
end
|
1350
|
-
end
|
1351
|
-
|
1352
|
-
raise NameError, "uninitialized constant #{candidates.first}"
|
1353
|
-
end
|
1354
|
-
end
|
1355
|
-
|
1356
|
-
# Returns the class descending directly from ActiveRecord::Base or an
|
1357
|
-
# abstract class, if any, in the inheritance hierarchy.
|
1358
|
-
def class_of_active_record_descendant(klass)
|
1359
|
-
if klass.superclass == Base || klass.superclass.abstract_class?
|
1360
|
-
klass
|
1361
|
-
elsif klass.superclass.nil?
|
1362
|
-
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
|
1363
|
-
else
|
1364
|
-
class_of_active_record_descendant(klass.superclass)
|
1365
|
-
end
|
1366
|
-
end
|
1367
|
-
|
1368
|
-
# Accepts an array, hash, or string of SQL conditions and sanitizes
|
1369
|
-
# them into a valid SQL fragment for a WHERE clause.
|
1370
|
-
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
1371
|
-
# { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
|
1372
|
-
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
|
1373
|
-
def sanitize_sql_for_conditions(condition, table_name = self.table_name)
|
1374
|
-
return nil if condition.blank?
|
1375
|
-
|
1376
|
-
case condition
|
1377
|
-
when Array; sanitize_sql_array(condition)
|
1378
|
-
when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
|
1379
|
-
else condition
|
1380
|
-
end
|
1381
|
-
end
|
1382
|
-
alias_method :sanitize_sql, :sanitize_sql_for_conditions
|
1383
|
-
|
1384
|
-
# Accepts an array, hash, or string of SQL conditions and sanitizes
|
1385
|
-
# them into a valid SQL fragment for a SET clause.
|
1386
|
-
# { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
|
1387
|
-
def sanitize_sql_for_assignment(assignments)
|
1388
|
-
case assignments
|
1389
|
-
when Array; sanitize_sql_array(assignments)
|
1390
|
-
when Hash; sanitize_sql_hash_for_assignment(assignments)
|
1391
|
-
else assignments
|
1392
|
-
end
|
1393
|
-
end
|
1394
|
-
|
1395
|
-
def aggregate_mapping(reflection)
|
1396
|
-
mapping = reflection.options[:mapping] || [reflection.name, reflection.name]
|
1397
|
-
mapping.first.is_a?(Array) ? mapping : [mapping]
|
1398
|
-
end
|
1399
|
-
|
1400
|
-
# Accepts a hash of SQL conditions and replaces those attributes
|
1401
|
-
# that correspond to a +composed_of+ relationship with their expanded
|
1402
|
-
# aggregate attribute values.
|
1403
|
-
# Given:
|
1404
|
-
# class Person < ActiveRecord::Base
|
1405
|
-
# composed_of :address, :class_name => "Address",
|
1406
|
-
# :mapping => [%w(address_street street), %w(address_city city)]
|
1407
|
-
# end
|
1408
|
-
# Then:
|
1409
|
-
# { :address => Address.new("813 abc st.", "chicago") }
|
1410
|
-
# # => { :address_street => "813 abc st.", :address_city => "chicago" }
|
1411
|
-
def expand_hash_conditions_for_aggregates(attrs)
|
1412
|
-
expanded_attrs = {}
|
1413
|
-
attrs.each do |attr, value|
|
1414
|
-
unless (aggregation = reflect_on_aggregation(attr.to_sym)).nil?
|
1415
|
-
mapping = aggregate_mapping(aggregation)
|
1416
|
-
mapping.each do |field_attr, aggregate_attr|
|
1417
|
-
if mapping.size == 1 && !value.respond_to?(aggregate_attr)
|
1418
|
-
expanded_attrs[field_attr] = value
|
1419
|
-
else
|
1420
|
-
expanded_attrs[field_attr] = value.send(aggregate_attr)
|
1421
|
-
end
|
1422
|
-
end
|
1423
|
-
else
|
1424
|
-
expanded_attrs[attr] = value
|
1425
|
-
end
|
1426
|
-
end
|
1427
|
-
expanded_attrs
|
1428
|
-
end
|
1429
|
-
|
1430
|
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
|
1431
|
-
# { :name => "foo'bar", :group_id => 4 }
|
1432
|
-
# # => "name='foo''bar' and group_id= 4"
|
1433
|
-
# { :status => nil, :group_id => [1,2,3] }
|
1434
|
-
# # => "status IS NULL and group_id IN (1,2,3)"
|
1435
|
-
# { :age => 13..18 }
|
1436
|
-
# # => "age BETWEEN 13 AND 18"
|
1437
|
-
# { 'other_records.id' => 7 }
|
1438
|
-
# # => "`other_records`.`id` = 7"
|
1439
|
-
# { :other_records => { :id => 7 } }
|
1440
|
-
# # => "`other_records`.`id` = 7"
|
1441
|
-
# And for value objects on a composed_of relationship:
|
1442
|
-
# { :address => Address.new("123 abc st.", "chicago") }
|
1443
|
-
# # => "address_street='123 abc st.' and address_city='chicago'"
|
1444
|
-
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
|
1445
|
-
attrs = expand_hash_conditions_for_aggregates(attrs)
|
1446
|
-
|
1447
|
-
table = Arel::Table.new(table_name).alias(default_table_name)
|
1448
|
-
PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
|
1449
|
-
connection.visitor.accept b
|
1450
|
-
}.join(' AND ')
|
1451
|
-
end
|
1452
|
-
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
|
1453
|
-
|
1454
|
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
|
1455
|
-
# { :status => nil, :group_id => 1 }
|
1456
|
-
# # => "status = NULL , group_id = 1"
|
1457
|
-
def sanitize_sql_hash_for_assignment(attrs)
|
1458
|
-
attrs.map do |attr, value|
|
1459
|
-
"#{connection.quote_column_name(attr)} = #{quote_bound_value(value)}"
|
1460
|
-
end.join(', ')
|
1461
|
-
end
|
1462
|
-
|
1463
|
-
# Accepts an array of conditions. The array has each value
|
1464
|
-
# sanitized and interpolated into the SQL statement.
|
1465
|
-
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
1466
|
-
def sanitize_sql_array(ary)
|
1467
|
-
statement, *values = ary
|
1468
|
-
if values.first.is_a?(Hash) && statement =~ /:\w+/
|
1469
|
-
replace_named_bind_variables(statement, values.first)
|
1470
|
-
elsif statement.include?('?')
|
1471
|
-
replace_bind_variables(statement, values)
|
1472
|
-
elsif statement.blank?
|
1473
|
-
statement
|
1474
|
-
else
|
1475
|
-
statement % values.collect { |value| connection.quote_string(value.to_s) }
|
1476
|
-
end
|
1477
|
-
end
|
1478
|
-
|
1479
|
-
alias_method :sanitize_conditions, :sanitize_sql
|
1480
|
-
|
1481
|
-
def replace_bind_variables(statement, values) #:nodoc:
|
1482
|
-
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
|
1483
|
-
bound = values.dup
|
1484
|
-
c = connection
|
1485
|
-
statement.gsub('?') { quote_bound_value(bound.shift, c) }
|
1486
|
-
end
|
1487
|
-
|
1488
|
-
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
|
1489
|
-
statement.gsub(/(:?):([a-zA-Z]\w*)/) do
|
1490
|
-
if $1 == ':' # skip postgresql casts
|
1491
|
-
$& # return the whole match
|
1492
|
-
elsif bind_vars.include?(match = $2.to_sym)
|
1493
|
-
quote_bound_value(bind_vars[match])
|
1494
|
-
else
|
1495
|
-
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
|
1496
|
-
end
|
1497
|
-
end
|
1498
|
-
end
|
1499
|
-
|
1500
|
-
def expand_range_bind_variables(bind_vars) #:nodoc:
|
1501
|
-
expanded = []
|
1502
|
-
|
1503
|
-
bind_vars.each do |var|
|
1504
|
-
next if var.is_a?(Hash)
|
1505
|
-
|
1506
|
-
if var.is_a?(Range)
|
1507
|
-
expanded << var.first
|
1508
|
-
expanded << var.last
|
1509
|
-
else
|
1510
|
-
expanded << var
|
1511
|
-
end
|
1512
|
-
end
|
1513
|
-
|
1514
|
-
expanded
|
1515
|
-
end
|
1516
|
-
|
1517
|
-
def quote_bound_value(value, c = connection) #:nodoc:
|
1518
|
-
if value.respond_to?(:map) && !value.acts_like?(:string)
|
1519
|
-
if value.respond_to?(:empty?) && value.empty?
|
1520
|
-
c.quote(nil)
|
1521
|
-
else
|
1522
|
-
value.map { |v| c.quote(v) }.join(',')
|
1523
|
-
end
|
1524
|
-
else
|
1525
|
-
c.quote(value)
|
1526
|
-
end
|
1527
|
-
end
|
1528
|
-
|
1529
|
-
def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
|
1530
|
-
unless expected == provided
|
1531
|
-
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
|
1532
|
-
end
|
1533
|
-
end
|
1534
|
-
|
1535
|
-
def encode_quoted_value(value) #:nodoc:
|
1536
|
-
quoted_value = connection.quote(value)
|
1537
|
-
quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'") # (for ruby mode) "
|
1538
|
-
quoted_value
|
1539
|
-
end
|
1540
|
-
end
|
1541
|
-
|
1542
|
-
public
|
1543
|
-
# New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
|
1544
|
-
# attributes but not yet saved (pass a hash with key names matching the associated table column names).
|
1545
|
-
# In both instances, valid attribute keys are determined by the column names of the associated table --
|
1546
|
-
# hence you can't have attributes that aren't part of the table columns.
|
1547
|
-
#
|
1548
|
-
# +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
|
1549
|
-
# in the +options+ parameter.
|
1550
|
-
#
|
1551
|
-
# ==== Examples
|
1552
|
-
# # Instantiates a single new object
|
1553
|
-
# User.new(:first_name => 'Jamie')
|
1554
|
-
#
|
1555
|
-
# # Instantiates a single new object using the :admin mass-assignment security role
|
1556
|
-
# User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
|
1557
|
-
#
|
1558
|
-
# # Instantiates a single new object bypassing mass-assignment security
|
1559
|
-
# User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
|
1560
|
-
def initialize(attributes = nil, options = {})
|
1561
|
-
@attributes = attributes_from_column_definition
|
1562
|
-
@association_cache = {}
|
1563
|
-
@aggregation_cache = {}
|
1564
|
-
@attributes_cache = {}
|
1565
|
-
@new_record = true
|
1566
|
-
@readonly = false
|
1567
|
-
@destroyed = false
|
1568
|
-
@marked_for_destruction = false
|
1569
|
-
@previously_changed = {}
|
1570
|
-
@changed_attributes = {}
|
1571
|
-
@relation = nil
|
1572
|
-
|
1573
|
-
ensure_proper_type
|
1574
|
-
set_serialized_attributes
|
1575
|
-
|
1576
|
-
populate_with_current_scope_attributes
|
1577
|
-
|
1578
|
-
assign_attributes(attributes, options) if attributes
|
1579
|
-
|
1580
|
-
yield self if block_given?
|
1581
|
-
run_callbacks :initialize
|
1582
|
-
end
|
1583
|
-
|
1584
|
-
# Populate +coder+ with attributes about this record that should be
|
1585
|
-
# serialized. The structure of +coder+ defined in this method is
|
1586
|
-
# guaranteed to match the structure of +coder+ passed to the +init_with+
|
1587
|
-
# method.
|
1588
|
-
#
|
1589
|
-
# Example:
|
1590
|
-
#
|
1591
|
-
# class Post < ActiveRecord::Base
|
1592
|
-
# end
|
1593
|
-
# coder = {}
|
1594
|
-
# Post.new.encode_with(coder)
|
1595
|
-
# coder # => { 'id' => nil, ... }
|
1596
|
-
def encode_with(coder)
|
1597
|
-
coder['attributes'] = attributes
|
1598
|
-
end
|
1599
|
-
|
1600
|
-
# Initialize an empty model object from +coder+. +coder+ must contain
|
1601
|
-
# the attributes necessary for initializing an empty model object. For
|
1602
|
-
# example:
|
1603
|
-
#
|
1604
|
-
# class Post < ActiveRecord::Base
|
1605
|
-
# end
|
1606
|
-
#
|
1607
|
-
# post = Post.allocate
|
1608
|
-
# post.init_with('attributes' => { 'title' => 'hello world' })
|
1609
|
-
# post.title # => 'hello world'
|
1610
|
-
def init_with(coder)
|
1611
|
-
@attributes = coder['attributes']
|
1612
|
-
@relation = nil
|
1613
|
-
|
1614
|
-
set_serialized_attributes
|
1615
|
-
|
1616
|
-
@attributes_cache, @previously_changed, @changed_attributes = {}, {}, {}
|
1617
|
-
@association_cache = {}
|
1618
|
-
@aggregation_cache = {}
|
1619
|
-
@readonly = @destroyed = @marked_for_destruction = false
|
1620
|
-
@new_record = false
|
1621
|
-
run_callbacks :find
|
1622
|
-
run_callbacks :initialize
|
1623
|
-
|
1624
|
-
self
|
1625
|
-
end
|
1626
|
-
|
1627
|
-
# Returns a String, which Action Pack uses for constructing an URL to this
|
1628
|
-
# object. The default implementation returns this record's id as a String,
|
1629
|
-
# or nil if this record's unsaved.
|
1630
|
-
#
|
1631
|
-
# For example, suppose that you have a User model, and that you have a
|
1632
|
-
# <tt>resources :users</tt> route. Normally, +user_path+ will
|
1633
|
-
# construct a path with the user object's 'id' in it:
|
1634
|
-
#
|
1635
|
-
# user = User.find_by_name('Phusion')
|
1636
|
-
# user_path(user) # => "/users/1"
|
1637
|
-
#
|
1638
|
-
# You can override +to_param+ in your model to make +user_path+ construct
|
1639
|
-
# a path using the user's name instead of the user's id:
|
1640
|
-
#
|
1641
|
-
# class User < ActiveRecord::Base
|
1642
|
-
# def to_param # overridden
|
1643
|
-
# name
|
1644
|
-
# end
|
1645
|
-
# end
|
1646
|
-
#
|
1647
|
-
# user = User.find_by_name('Phusion')
|
1648
|
-
# user_path(user) # => "/users/Phusion"
|
1649
|
-
def to_param
|
1650
|
-
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
|
1651
|
-
id && id.to_s # Be sure to stringify the id for routes
|
1652
|
-
end
|
1653
|
-
|
1654
|
-
# Returns a cache key that can be used to identify this record.
|
1655
|
-
#
|
1656
|
-
# ==== Examples
|
1657
|
-
#
|
1658
|
-
# Product.new.cache_key # => "products/new"
|
1659
|
-
# Product.find(5).cache_key # => "products/5" (updated_at not available)
|
1660
|
-
# Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
|
1661
|
-
def cache_key
|
1662
|
-
case
|
1663
|
-
when new_record?
|
1664
|
-
"#{self.class.model_name.cache_key}/new"
|
1665
|
-
when timestamp = self[:updated_at]
|
1666
|
-
timestamp = timestamp.utc.to_s(:number)
|
1667
|
-
"#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
|
1668
|
-
else
|
1669
|
-
"#{self.class.model_name.cache_key}/#{id}"
|
1670
|
-
end
|
1671
|
-
end
|
1672
|
-
|
1673
|
-
def quoted_id #:nodoc:
|
1674
|
-
quote_value(id, column_for_attribute(self.class.primary_key))
|
1675
|
-
end
|
1676
|
-
|
1677
|
-
# Returns true if the given attribute is in the attributes hash
|
1678
|
-
def has_attribute?(attr_name)
|
1679
|
-
@attributes.has_key?(attr_name.to_s)
|
1680
|
-
end
|
1681
|
-
|
1682
|
-
# Returns an array of names for the attributes available on this object.
|
1683
|
-
def attribute_names
|
1684
|
-
@attributes.keys
|
1685
|
-
end
|
1686
|
-
|
1687
|
-
# Allows you to set all the attributes at once by passing in a hash with keys
|
1688
|
-
# matching the attribute names (which again matches the column names).
|
1689
|
-
#
|
1690
|
-
# If any attributes are protected by either +attr_protected+ or
|
1691
|
-
# +attr_accessible+ then only settable attributes will be assigned.
|
1692
|
-
#
|
1693
|
-
# The +guard_protected_attributes+ argument is now deprecated, use
|
1694
|
-
# the +assign_attributes+ method if you want to bypass mass-assignment security.
|
1695
|
-
#
|
1696
|
-
# class User < ActiveRecord::Base
|
1697
|
-
# attr_protected :is_admin
|
1698
|
-
# end
|
1699
|
-
#
|
1700
|
-
# user = User.new
|
1701
|
-
# user.attributes = { :username => 'Phusion', :is_admin => true }
|
1702
|
-
# user.username # => "Phusion"
|
1703
|
-
# user.is_admin? # => false
|
1704
|
-
def attributes=(new_attributes, guard_protected_attributes = nil)
|
1705
|
-
unless guard_protected_attributes.nil?
|
1706
|
-
message = "the use of 'guard_protected_attributes' will be removed from the next minor release of rails, " +
|
1707
|
-
"if you want to bypass mass-assignment security then look into using assign_attributes"
|
1708
|
-
ActiveSupport::Deprecation.warn(message)
|
1709
|
-
end
|
1710
|
-
|
1711
|
-
return unless new_attributes.is_a?(Hash)
|
1712
|
-
|
1713
|
-
if guard_protected_attributes == false
|
1714
|
-
assign_attributes(new_attributes, :without_protection => true)
|
1715
|
-
else
|
1716
|
-
assign_attributes(new_attributes)
|
1717
|
-
end
|
1718
|
-
end
|
1719
|
-
|
1720
|
-
# Allows you to set all the attributes for a particular mass-assignment
|
1721
|
-
# security role by passing in a hash of attributes with keys matching
|
1722
|
-
# the attribute names (which again matches the column names) and the role
|
1723
|
-
# name using the :as option.
|
1724
|
-
#
|
1725
|
-
# To bypass mass-assignment security you can use the :without_protection => true
|
1726
|
-
# option.
|
1727
|
-
#
|
1728
|
-
# class User < ActiveRecord::Base
|
1729
|
-
# attr_accessible :name
|
1730
|
-
# attr_accessible :name, :is_admin, :as => :admin
|
1731
|
-
# end
|
1732
|
-
#
|
1733
|
-
# user = User.new
|
1734
|
-
# user.assign_attributes({ :name => 'Josh', :is_admin => true })
|
1735
|
-
# user.name # => "Josh"
|
1736
|
-
# user.is_admin? # => false
|
1737
|
-
#
|
1738
|
-
# user = User.new
|
1739
|
-
# user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
|
1740
|
-
# user.name # => "Josh"
|
1741
|
-
# user.is_admin? # => true
|
1742
|
-
#
|
1743
|
-
# user = User.new
|
1744
|
-
# user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
|
1745
|
-
# user.name # => "Josh"
|
1746
|
-
# user.is_admin? # => true
|
1747
|
-
def assign_attributes(new_attributes, options = {})
|
1748
|
-
return unless new_attributes
|
1749
|
-
|
1750
|
-
attributes = new_attributes.stringify_keys
|
1751
|
-
multi_parameter_attributes = []
|
1752
|
-
@mass_assignment_options = options
|
1753
|
-
|
1754
|
-
unless options[:without_protection]
|
1755
|
-
attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role)
|
1756
|
-
end
|
1757
|
-
|
1758
|
-
attributes.each do |k, v|
|
1759
|
-
if k.include?("(")
|
1760
|
-
multi_parameter_attributes << [ k, v ]
|
1761
|
-
elsif respond_to?("#{k}=")
|
1762
|
-
send("#{k}=", v)
|
1763
|
-
else
|
1764
|
-
raise(UnknownAttributeError, "unknown attribute: #{k}")
|
1765
|
-
end
|
1766
|
-
end
|
1767
|
-
|
1768
|
-
@mass_assignment_options = nil
|
1769
|
-
assign_multiparameter_attributes(multi_parameter_attributes)
|
1770
|
-
end
|
1771
|
-
|
1772
|
-
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
1773
|
-
def attributes
|
1774
|
-
Hash[@attributes.map { |name, _| [name, read_attribute(name)] }]
|
1775
|
-
end
|
1776
|
-
|
1777
|
-
# Returns an <tt>#inspect</tt>-like string for the value of the
|
1778
|
-
# attribute +attr_name+. String attributes are truncated upto 50
|
1779
|
-
# characters, and Date and Time attributes are returned in the
|
1780
|
-
# <tt>:db</tt> format. Other attributes return the value of
|
1781
|
-
# <tt>#inspect</tt> without modification.
|
1782
|
-
#
|
1783
|
-
# person = Person.create!(:name => "David Heinemeier Hansson " * 3)
|
1784
|
-
#
|
1785
|
-
# person.attribute_for_inspect(:name)
|
1786
|
-
# # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
|
1787
|
-
#
|
1788
|
-
# person.attribute_for_inspect(:created_at)
|
1789
|
-
# # => '"2009-01-12 04:48:57"'
|
1790
|
-
def attribute_for_inspect(attr_name)
|
1791
|
-
value = read_attribute(attr_name)
|
1792
|
-
|
1793
|
-
if value.is_a?(String) && value.length > 50
|
1794
|
-
"#{value[0..50]}...".inspect
|
1795
|
-
elsif value.is_a?(Date) || value.is_a?(Time)
|
1796
|
-
%("#{value.to_s(:db)}")
|
1797
|
-
else
|
1798
|
-
value.inspect
|
1799
|
-
end
|
1800
|
-
end
|
1801
|
-
|
1802
|
-
# Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
|
1803
|
-
# nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
|
1804
|
-
def attribute_present?(attribute)
|
1805
|
-
!_read_attribute(attribute).blank?
|
1806
|
-
end
|
1807
|
-
|
1808
|
-
# Returns the column object for the named attribute.
|
1809
|
-
def column_for_attribute(name)
|
1810
|
-
self.class.columns_hash[name.to_s]
|
1811
|
-
end
|
1812
|
-
|
1813
|
-
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
|
1814
|
-
# is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
|
1815
|
-
#
|
1816
|
-
# Note that new records are different from any other record by definition, unless the
|
1817
|
-
# other record is the receiver itself. Besides, if you fetch existing records with
|
1818
|
-
# +select+ and leave the ID out, you're on your own, this predicate will return false.
|
1819
|
-
#
|
1820
|
-
# Note also that destroying a record preserves its ID in the model instance, so deleted
|
1821
|
-
# models are still comparable.
|
1822
|
-
def ==(comparison_object)
|
1823
|
-
super ||
|
1824
|
-
comparison_object.instance_of?(self.class) &&
|
1825
|
-
id.present? &&
|
1826
|
-
comparison_object.id == id
|
1827
|
-
end
|
1828
|
-
alias :eql? :==
|
1829
|
-
|
1830
|
-
# Delegates to id in order to allow two records of the same type and id to work with something like:
|
1831
|
-
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
|
1832
|
-
def hash
|
1833
|
-
id.hash
|
1834
|
-
end
|
1835
|
-
|
1836
|
-
# Freeze the attributes hash such that associations are still accessible, even on destroyed records.
|
1837
|
-
def freeze
|
1838
|
-
@attributes.freeze; self
|
1839
|
-
end
|
1840
|
-
|
1841
|
-
# Returns +true+ if the attributes hash has been frozen.
|
1842
|
-
def frozen?
|
1843
|
-
@attributes.frozen?
|
1844
|
-
end
|
1845
|
-
|
1846
|
-
# Allows sort on objects
|
1847
|
-
def <=>(other_object)
|
1848
|
-
if other_object.is_a?(self.class)
|
1849
|
-
self.to_key <=> other_object.to_key
|
1850
|
-
else
|
1851
|
-
nil
|
1852
|
-
end
|
1853
|
-
end
|
1854
|
-
|
1855
|
-
# Backport dup from 1.9 so that initialize_dup() gets called
|
1856
|
-
unless Object.respond_to?(:initialize_dup)
|
1857
|
-
def dup # :nodoc:
|
1858
|
-
copy = super
|
1859
|
-
copy.initialize_dup(self)
|
1860
|
-
copy
|
1861
|
-
end
|
1862
|
-
end
|
1863
|
-
|
1864
|
-
# Duped objects have no id assigned and are treated as new records. Note
|
1865
|
-
# that this is a "shallow" copy as it copies the object's attributes
|
1866
|
-
# only, not its associations. The extent of a "deep" copy is application
|
1867
|
-
# specific and is therefore left to the application to implement according
|
1868
|
-
# to its need.
|
1869
|
-
# The dup method does not preserve the timestamps (created|updated)_(at|on).
|
1870
|
-
def initialize_dup(other)
|
1871
|
-
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
|
1872
|
-
cloned_attributes.delete(self.class.primary_key)
|
1873
|
-
|
1874
|
-
@attributes = cloned_attributes
|
1875
|
-
|
1876
|
-
_run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
|
1877
|
-
|
1878
|
-
@changed_attributes = {}
|
1879
|
-
attributes_from_column_definition.each do |attr, orig_value|
|
1880
|
-
@changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr])
|
1881
|
-
end
|
1882
|
-
|
1883
|
-
@aggregation_cache = {}
|
1884
|
-
@association_cache = {}
|
1885
|
-
@attributes_cache = {}
|
1886
|
-
@new_record = true
|
1887
|
-
|
1888
|
-
ensure_proper_type
|
1889
|
-
populate_with_current_scope_attributes
|
1890
|
-
clear_timestamp_attributes
|
1891
|
-
end
|
1892
|
-
|
1893
|
-
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
|
1894
|
-
# attributes will be marked as read only since they cannot be saved.
|
1895
|
-
def readonly?
|
1896
|
-
@readonly
|
1897
|
-
end
|
1898
|
-
|
1899
|
-
# Marks this record as read only.
|
1900
|
-
def readonly!
|
1901
|
-
@readonly = true
|
1902
|
-
end
|
1903
|
-
|
1904
|
-
# Returns the contents of the record as a nicely formatted string.
|
1905
|
-
def inspect
|
1906
|
-
attributes_as_nice_string = self.class.column_names.collect { |name|
|
1907
|
-
if has_attribute?(name)
|
1908
|
-
"#{name}: #{attribute_for_inspect(name)}"
|
1909
|
-
end
|
1910
|
-
}.compact.join(", ")
|
1911
|
-
"#<#{self.class} #{attributes_as_nice_string}>"
|
1912
|
-
end
|
1913
|
-
|
1914
|
-
protected
|
1915
|
-
def clone_attributes(reader_method = :read_attribute, attributes = {})
|
1916
|
-
attribute_names.each do |name|
|
1917
|
-
attributes[name] = clone_attribute_value(reader_method, name)
|
1918
|
-
end
|
1919
|
-
attributes
|
1920
|
-
end
|
1921
|
-
|
1922
|
-
def clone_attribute_value(reader_method, attribute_name)
|
1923
|
-
value = send(reader_method, attribute_name)
|
1924
|
-
value.duplicable? ? value.clone : value
|
1925
|
-
rescue TypeError, NoMethodError
|
1926
|
-
value
|
1927
|
-
end
|
1928
|
-
|
1929
|
-
def mass_assignment_options
|
1930
|
-
@mass_assignment_options ||= {}
|
1931
|
-
end
|
1932
|
-
|
1933
|
-
def mass_assignment_role
|
1934
|
-
mass_assignment_options[:as] || :default
|
658
|
+
# Hackery to accomodate Syck. Remove for 4.0.
|
659
|
+
def yaml_initialize(tag, coder) #:nodoc:
|
660
|
+
init_with(coder)
|
1935
661
|
end
|
1936
662
|
|
1937
663
|
private
|
@@ -1948,254 +674,39 @@ MSG
|
|
1948
674
|
nil
|
1949
675
|
end
|
1950
676
|
|
1951
|
-
def set_serialized_attributes
|
1952
|
-
sattrs = self.class.serialized_attributes
|
1953
|
-
|
1954
|
-
sattrs.each do |key, coder|
|
1955
|
-
@attributes[key] = coder.load @attributes[key] if @attributes.key?(key)
|
1956
|
-
end
|
1957
|
-
end
|
1958
|
-
|
1959
|
-
# Sets the attribute used for single table inheritance to this class name if this is not the
|
1960
|
-
# ActiveRecord::Base descendant.
|
1961
|
-
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
|
1962
|
-
# do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
|
1963
|
-
# No such attribute would be set for objects of the Message class in that example.
|
1964
|
-
def ensure_proper_type
|
1965
|
-
klass = self.class
|
1966
|
-
if klass.finder_needs_type_condition?
|
1967
|
-
write_attribute(klass.inheritance_column, klass.sti_name)
|
1968
|
-
end
|
1969
|
-
end
|
1970
|
-
|
1971
|
-
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
|
1972
|
-
def self.attributes_protected_by_default
|
1973
|
-
default = [ primary_key, inheritance_column ]
|
1974
|
-
default << 'id' unless primary_key.eql? 'id'
|
1975
|
-
default
|
1976
|
-
end
|
1977
|
-
|
1978
|
-
# Returns a copy of the attributes hash where all the values have been safely quoted for use in
|
1979
|
-
# an Arel insert/update method.
|
1980
|
-
def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
|
1981
|
-
attrs = {}
|
1982
|
-
klass = self.class
|
1983
|
-
arel_table = klass.arel_table
|
1984
|
-
|
1985
|
-
attribute_names.each do |name|
|
1986
|
-
if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
|
1987
|
-
|
1988
|
-
if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
|
1989
|
-
|
1990
|
-
value = if coder = klass.serialized_attributes[name]
|
1991
|
-
coder.dump @attributes[name]
|
1992
|
-
else
|
1993
|
-
# FIXME: we need @attributes to be used consistently.
|
1994
|
-
# If the values stored in @attributes were already type
|
1995
|
-
# casted, this code could be simplified
|
1996
|
-
read_attribute(name)
|
1997
|
-
end
|
1998
|
-
|
1999
|
-
attrs[arel_table[name]] = value
|
2000
|
-
end
|
2001
|
-
end
|
2002
|
-
end
|
2003
|
-
attrs
|
2004
|
-
end
|
2005
|
-
|
2006
|
-
# Quote strings appropriately for SQL statements.
|
2007
|
-
def quote_value(value, column = nil)
|
2008
|
-
self.class.connection.quote(value, column)
|
2009
|
-
end
|
2010
|
-
|
2011
|
-
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
2012
|
-
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
2013
|
-
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
2014
|
-
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
2015
|
-
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
|
2016
|
-
# f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the
|
2017
|
-
# attribute will be set to nil.
|
2018
|
-
def assign_multiparameter_attributes(pairs)
|
2019
|
-
execute_callstack_for_multiparameter_attributes(
|
2020
|
-
extract_callstack_for_multiparameter_attributes(pairs)
|
2021
|
-
)
|
2022
|
-
end
|
2023
|
-
|
2024
|
-
def instantiate_time_object(name, values)
|
2025
|
-
if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
|
2026
|
-
Time.zone.local(*values)
|
2027
|
-
else
|
2028
|
-
Time.time_with_datetime_fallback(@@default_timezone, *values)
|
2029
|
-
end
|
2030
|
-
end
|
2031
|
-
|
2032
|
-
def execute_callstack_for_multiparameter_attributes(callstack)
|
2033
|
-
errors = []
|
2034
|
-
callstack.each do |name, values_with_empty_parameters|
|
2035
|
-
begin
|
2036
|
-
send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
|
2037
|
-
rescue => ex
|
2038
|
-
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
|
2039
|
-
end
|
2040
|
-
end
|
2041
|
-
unless errors.empty?
|
2042
|
-
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
|
2043
|
-
end
|
2044
|
-
end
|
2045
|
-
|
2046
|
-
def read_value_from_parameter(name, values_hash_from_param)
|
2047
|
-
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
|
2048
|
-
if values_hash_from_param.values.all?{|v|v.nil?}
|
2049
|
-
nil
|
2050
|
-
elsif klass == Time
|
2051
|
-
read_time_parameter_value(name, values_hash_from_param)
|
2052
|
-
elsif klass == Date
|
2053
|
-
read_date_parameter_value(name, values_hash_from_param)
|
2054
|
-
else
|
2055
|
-
read_other_parameter_value(klass, name, values_hash_from_param)
|
2056
|
-
end
|
2057
|
-
end
|
2058
|
-
|
2059
|
-
def read_time_parameter_value(name, values_hash_from_param)
|
2060
|
-
# If Date bits were not provided, error
|
2061
|
-
raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
|
2062
|
-
max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
|
2063
|
-
set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
|
2064
|
-
# If Date bits were provided but blank, then default to 1
|
2065
|
-
# If Time bits are not there, then default to 0
|
2066
|
-
[1,1,1,0,0,0].each_with_index{|v,i| set_values[i] = set_values[i].blank? ? v : set_values[i]}
|
2067
|
-
instantiate_time_object(name, set_values)
|
2068
|
-
end
|
2069
|
-
|
2070
|
-
def read_date_parameter_value(name, values_hash_from_param)
|
2071
|
-
set_values = (1..3).collect{|position| values_hash_from_param[position].blank? ? 1 : values_hash_from_param[position]}
|
2072
|
-
begin
|
2073
|
-
Date.new(*set_values)
|
2074
|
-
rescue ArgumentError # if Date.new raises an exception on an invalid date
|
2075
|
-
instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
|
2076
|
-
end
|
2077
|
-
end
|
2078
|
-
|
2079
|
-
def read_other_parameter_value(klass, name, values_hash_from_param)
|
2080
|
-
max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
|
2081
|
-
values = (1..max_position).collect do |position|
|
2082
|
-
raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
|
2083
|
-
values_hash_from_param[position]
|
2084
|
-
end
|
2085
|
-
klass.new(*values)
|
2086
|
-
end
|
2087
|
-
|
2088
|
-
def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
|
2089
|
-
[values_hash_from_param.keys.max,upper_cap].min
|
2090
|
-
end
|
2091
|
-
|
2092
|
-
def extract_callstack_for_multiparameter_attributes(pairs)
|
2093
|
-
attributes = { }
|
2094
|
-
|
2095
|
-
pairs.each do |pair|
|
2096
|
-
multiparameter_name, value = pair
|
2097
|
-
attribute_name = multiparameter_name.split("(").first
|
2098
|
-
attributes[attribute_name] = {} unless attributes.include?(attribute_name)
|
2099
|
-
|
2100
|
-
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
2101
|
-
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
2102
|
-
end
|
2103
|
-
|
2104
|
-
attributes
|
2105
|
-
end
|
2106
|
-
|
2107
|
-
def type_cast_attribute_value(multiparameter_name, value)
|
2108
|
-
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
|
2109
|
-
end
|
2110
|
-
|
2111
|
-
def find_parameter_position(multiparameter_name)
|
2112
|
-
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
2113
|
-
end
|
2114
|
-
|
2115
|
-
# Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
|
2116
|
-
def comma_pair_list(hash)
|
2117
|
-
hash.map { |k,v| "#{k} = #{v}" }.join(", ")
|
2118
|
-
end
|
2119
|
-
|
2120
|
-
def quote_columns(quoter, hash)
|
2121
|
-
Hash[hash.map { |name, value| [quoter.quote_column_name(name), value] }]
|
2122
|
-
end
|
2123
|
-
|
2124
|
-
def quoted_comma_pair_list(quoter, hash)
|
2125
|
-
comma_pair_list(quote_columns(quoter, hash))
|
2126
|
-
end
|
2127
|
-
|
2128
|
-
def convert_number_column_value(value)
|
2129
|
-
if value == false
|
2130
|
-
0
|
2131
|
-
elsif value == true
|
2132
|
-
1
|
2133
|
-
elsif value.is_a?(String) && value.blank?
|
2134
|
-
nil
|
2135
|
-
else
|
2136
|
-
value
|
2137
|
-
end
|
2138
|
-
end
|
2139
|
-
|
2140
|
-
def populate_with_current_scope_attributes
|
2141
|
-
return unless self.class.scope_attributes?
|
2142
|
-
|
2143
|
-
self.class.scope_attributes.each do |att,value|
|
2144
|
-
send("#{att}=", value) if respond_to?("#{att}=")
|
2145
|
-
end
|
2146
|
-
end
|
2147
|
-
|
2148
|
-
# Clear attributes and changed_attributes
|
2149
|
-
def clear_timestamp_attributes
|
2150
|
-
all_timestamp_attributes_in_model.each do |attribute_name|
|
2151
|
-
self[attribute_name] = nil
|
2152
|
-
changed_attributes.delete(attribute_name)
|
2153
|
-
end
|
2154
|
-
end
|
2155
|
-
end
|
2156
|
-
|
2157
|
-
Base.class_eval do
|
2158
677
|
include ActiveRecord::Persistence
|
2159
678
|
extend ActiveModel::Naming
|
2160
679
|
extend QueryCache::ClassMethods
|
2161
680
|
extend ActiveSupport::Benchmarkable
|
2162
681
|
extend ActiveSupport::DescendantsTracker
|
2163
682
|
|
683
|
+
extend Querying
|
684
|
+
include ReadonlyAttributes
|
685
|
+
include ModelSchema
|
686
|
+
extend Translation
|
687
|
+
include Inheritance
|
688
|
+
include Scoping
|
689
|
+
extend DynamicMatchers
|
690
|
+
include Sanitization
|
691
|
+
include Integration
|
692
|
+
include AttributeAssignment
|
2164
693
|
include ActiveModel::Conversion
|
2165
694
|
include Validations
|
2166
695
|
extend CounterCache
|
2167
696
|
include Locking::Optimistic, Locking::Pessimistic
|
2168
697
|
include AttributeMethods
|
2169
|
-
include AttributeMethods::Read, AttributeMethods::Write, AttributeMethods::BeforeTypeCast, AttributeMethods::Query
|
2170
|
-
include AttributeMethods::PrimaryKey
|
2171
|
-
include AttributeMethods::TimeZoneConversion
|
2172
|
-
include AttributeMethods::Dirty
|
2173
|
-
include ActiveModel::MassAssignmentSecurity
|
2174
698
|
include Callbacks, ActiveModel::Observing, Timestamp
|
2175
|
-
include Associations
|
699
|
+
include Associations
|
2176
700
|
include IdentityMap
|
2177
701
|
include ActiveModel::SecurePassword
|
702
|
+
extend Explain
|
2178
703
|
|
2179
704
|
# AutosaveAssociation needs to be included before Transactions, because we want
|
2180
705
|
# #save_with_autosave_associations to be wrapped inside a transaction.
|
2181
706
|
include AutosaveAssociation, NestedAttributes
|
2182
|
-
include Aggregations, Transactions, Reflection, Serialization
|
2183
|
-
|
2184
|
-
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
2185
|
-
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
|
2186
|
-
# (Alias for the protected read_attribute method).
|
2187
|
-
def [](attr_name)
|
2188
|
-
read_attribute(attr_name)
|
2189
|
-
end
|
2190
|
-
|
2191
|
-
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
|
2192
|
-
# (Alias for the protected write_attribute method).
|
2193
|
-
def []=(attr_name, value)
|
2194
|
-
write_attribute(attr_name, value)
|
2195
|
-
end
|
707
|
+
include Aggregations, Transactions, Reflection, Serialization, Store
|
2196
708
|
end
|
2197
709
|
end
|
2198
710
|
|
2199
|
-
|
2200
|
-
require 'active_record/connection_adapters/abstract_adapter'
|
711
|
+
require 'active_record/connection_adapters/abstract/connection_specification'
|
2201
712
|
ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
|