activerecord 3.1.12 → 3.2.22.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +804 -338
- data/README.rdoc +3 -3
- data/examples/performance.rb +20 -1
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +3 -6
- data/lib/active_record/associations/association.rb +13 -45
- data/lib/active_record/associations/association_scope.rb +3 -15
- data/lib/active_record/associations/belongs_to_association.rb +1 -1
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +2 -1
- data/lib/active_record/associations/builder/association.rb +6 -4
- data/lib/active_record/associations/builder/belongs_to.rb +7 -4
- 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 +65 -32
- data/lib/active_record/associations/collection_proxy.rb +8 -41
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
- data/lib/active_record/associations/has_many_association.rb +11 -7
- data/lib/active_record/associations/has_many_through_association.rb +19 -9
- data/lib/active_record/associations/has_one_association.rb +23 -13
- data/lib/active_record/associations/join_dependency/join_association.rb +6 -1
- data/lib/active_record/associations/join_dependency.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +3 -3
- data/lib/active_record/associations/preloader.rb +14 -10
- data/lib/active_record/associations/through_association.rb +8 -4
- data/lib/active_record/associations.rb +92 -76
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +21 -11
- data/lib/active_record/attribute_methods/primary_key.rb +62 -25
- data/lib/active_record/attribute_methods/read.rb +73 -83
- data/lib/active_record/attribute_methods/serialization.rb +120 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attribute_methods/write.rb +32 -6
- data/lib/active_record/attribute_methods.rb +231 -30
- data/lib/active_record/autosave_association.rb +44 -26
- data/lib/active_record/base.rb +227 -1708
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +7 -34
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +39 -28
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
- data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +676 -0
- data/lib/active_record/connection_adapters/column.rb +37 -11
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +133 -581
- data/lib/active_record/connection_adapters/mysql_adapter.rb +136 -693
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +209 -97
- data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +62 -35
- data/lib/active_record/counter_cache.rb +9 -4
- data/lib/active_record/dynamic_finder_match.rb +12 -0
- data/lib/active_record/dynamic_matchers.rb +84 -0
- data/lib/active_record/errors.rb +11 -1
- data/lib/active_record/explain.rb +86 -0
- data/lib/active_record/explain_subscriber.rb +25 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/fixtures.rb +57 -86
- data/lib/active_record/identity_map.rb +3 -4
- data/lib/active_record/inheritance.rb +174 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locking/optimistic.rb +33 -26
- data/lib/active_record/locking/pessimistic.rb +23 -1
- data/lib/active_record/log_subscriber.rb +8 -4
- data/lib/active_record/migration/command_recorder.rb +8 -8
- data/lib/active_record/migration.rb +68 -35
- data/lib/active_record/model_schema.rb +368 -0
- data/lib/active_record/nested_attributes.rb +60 -24
- data/lib/active_record/persistence.rb +57 -11
- data/lib/active_record/query_cache.rb +6 -6
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +37 -29
- data/lib/active_record/railties/controller_runtime.rb +3 -1
- data/lib/active_record/railties/databases.rake +213 -117
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +7 -15
- data/lib/active_record/relation/batches.rb +7 -4
- data/lib/active_record/relation/calculations.rb +55 -16
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +16 -11
- data/lib/active_record/relation/predicate_builder.rb +8 -6
- data/lib/active_record/relation/query_methods.rb +75 -9
- data/lib/active_record/relation/spawn_methods.rb +48 -7
- data/lib/active_record/relation.rb +78 -32
- data/lib/active_record/result.rb +10 -4
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema_dumper.rb +12 -5
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +200 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/serialization.rb +1 -43
- data/lib/active_record/serializers/xml_serializer.rb +4 -45
- data/lib/active_record/session_store.rb +18 -16
- data/lib/active_record/store.rb +52 -0
- data/lib/active_record/test_case.rb +11 -7
- data/lib/active_record/timestamp.rb +17 -3
- data/lib/active_record/transactions.rb +27 -6
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +5 -4
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record/validations.rb +1 -1
- data/lib/active_record/version.rb +3 -3
- data/lib/active_record.rb +38 -3
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
- data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
- metadata +49 -28
- data/lib/active_record/named_scope.rb +0 -200
data/lib/active_record/base.rb
CHANGED
@@ -5,6 +5,7 @@ end
|
|
5
5
|
|
6
6
|
require 'yaml'
|
7
7
|
require 'set'
|
8
|
+
require 'thread'
|
8
9
|
require 'active_support/benchmarkable'
|
9
10
|
require 'active_support/dependencies'
|
10
11
|
require 'active_support/descendants_tracker'
|
@@ -27,6 +28,7 @@ require 'active_support/deprecation'
|
|
27
28
|
require 'arel'
|
28
29
|
require 'active_record/errors'
|
29
30
|
require 'active_record/log_subscriber'
|
31
|
+
require 'active_record/explain_subscriber'
|
30
32
|
|
31
33
|
module ActiveRecord #:nodoc:
|
32
34
|
# = Active Record
|
@@ -178,6 +180,10 @@ module ActiveRecord #:nodoc:
|
|
178
180
|
# And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do
|
179
181
|
# <tt>Person.find_all_by_last_name(last_name)</tt>.
|
180
182
|
#
|
183
|
+
# It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
|
184
|
+
# <tt>ActiveRecord::RecordNotFound</tt> error if they do not return any records,
|
185
|
+
# like <tt>Person.find_by_last_name!</tt>.
|
186
|
+
#
|
181
187
|
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
|
182
188
|
#
|
183
189
|
# Person.where(:user_name => user_name, :password => password).first
|
@@ -202,6 +208,9 @@ module ActiveRecord #:nodoc:
|
|
202
208
|
# # Now 'Bob' exist and is an 'admin'
|
203
209
|
# User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
|
204
210
|
#
|
211
|
+
# Adding an exclamation point (!) on to the end of <tt>find_or_create_by_</tt> will
|
212
|
+
# raise an <tt>ActiveRecord::RecordInvalid</tt> error if the new record is invalid.
|
213
|
+
#
|
205
214
|
# Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without
|
206
215
|
# saving it first. Protected attributes won't be set unless they are given in a block.
|
207
216
|
#
|
@@ -359,44 +368,6 @@ module ActiveRecord #:nodoc:
|
|
359
368
|
cattr_accessor :configurations, :instance_writer => false
|
360
369
|
@@configurations = {}
|
361
370
|
|
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
371
|
##
|
401
372
|
# :singleton-method:
|
402
373
|
# Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling
|
@@ -421,1520 +392,283 @@ module ActiveRecord #:nodoc:
|
|
421
392
|
cattr_accessor :timestamped_migrations , :instance_writer => false
|
422
393
|
@@timestamped_migrations = true
|
423
394
|
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
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 = {}
|
395
|
+
class << self # Class methods
|
396
|
+
def inherited(child_class) #:nodoc:
|
397
|
+
child_class.initialize_generated_modules
|
398
|
+
super
|
399
|
+
end
|
436
400
|
|
437
|
-
|
438
|
-
|
401
|
+
def initialize_generated_modules #:nodoc:
|
402
|
+
@attribute_methods_mutex = Mutex.new
|
439
403
|
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
|
445
|
-
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
|
404
|
+
# force attribute methods to be higher in inheritance hierarchy than other generated methods
|
405
|
+
generated_attribute_methods
|
406
|
+
generated_feature_methods
|
407
|
+
end
|
446
408
|
|
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) }
|
409
|
+
def generated_feature_methods
|
410
|
+
@generated_feature_methods ||= begin
|
411
|
+
mod = const_set(:GeneratedFeatureMethods, Module.new)
|
412
|
+
include mod
|
413
|
+
mod
|
414
|
+
end
|
471
415
|
end
|
472
416
|
|
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) }
|
417
|
+
# Returns a string like 'Post(id:integer, title:string, body:text)'
|
418
|
+
def inspect
|
419
|
+
if self == Base
|
420
|
+
super
|
421
|
+
elsif abstract_class?
|
422
|
+
"#{super}(abstract)"
|
423
|
+
elsif table_exists?
|
424
|
+
attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
|
425
|
+
"#{super}(#{attr_list})"
|
507
426
|
else
|
508
|
-
|
509
|
-
yield(object) if block_given?
|
510
|
-
object.save
|
511
|
-
object
|
427
|
+
"#{super}(Table doesn't exist)"
|
512
428
|
end
|
513
429
|
end
|
514
430
|
|
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
|
431
|
+
# Overwrite the default class equality method to provide support for association proxies.
|
432
|
+
def ===(object)
|
433
|
+
object.is_a?(self)
|
529
434
|
end
|
530
435
|
|
531
|
-
|
532
|
-
|
533
|
-
def attr_readonly(*attributes)
|
534
|
-
self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
|
436
|
+
def arel_table
|
437
|
+
@arel_table ||= Arel::Table.new(table_name, arel_engine)
|
535
438
|
end
|
536
439
|
|
537
|
-
|
538
|
-
|
539
|
-
|
440
|
+
def arel_engine
|
441
|
+
@arel_engine ||= begin
|
442
|
+
if self == ActiveRecord::Base
|
443
|
+
ActiveRecord::Base
|
444
|
+
else
|
445
|
+
connection_handler.retrieve_connection_pool(self) ? self : superclass.arel_engine
|
446
|
+
end
|
447
|
+
end
|
540
448
|
end
|
541
449
|
|
542
|
-
|
543
|
-
# then specify the name of that attribute using this method and it will be handled automatically.
|
544
|
-
# The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
|
545
|
-
# class on retrieval or SerializationTypeMismatch will be raised.
|
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
|
450
|
+
private
|
563
451
|
|
564
|
-
|
565
|
-
|
566
|
-
|
452
|
+
def relation #:nodoc:
|
453
|
+
relation = Relation.new(self, arel_table)
|
454
|
+
|
455
|
+
if finder_needs_type_condition?
|
456
|
+
relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
|
457
|
+
else
|
458
|
+
relation
|
459
|
+
end
|
567
460
|
end
|
461
|
+
end
|
568
462
|
|
569
|
-
|
570
|
-
#
|
571
|
-
#
|
572
|
-
#
|
573
|
-
#
|
574
|
-
# English inflections. You can add new inflections in config/initializers/inflections.rb.
|
463
|
+
public
|
464
|
+
# New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
|
465
|
+
# attributes but not yet saved (pass a hash with key names matching the associated table column names).
|
466
|
+
# In both instances, valid attribute keys are determined by the column names of the associated table --
|
467
|
+
# hence you can't have attributes that aren't part of the table columns.
|
575
468
|
#
|
576
|
-
#
|
577
|
-
# the
|
469
|
+
# +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
|
470
|
+
# in the +options+ parameter.
|
578
471
|
#
|
579
472
|
# ==== Examples
|
473
|
+
# # Instantiates a single new object
|
474
|
+
# User.new(:first_name => 'Jamie')
|
580
475
|
#
|
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:
|
476
|
+
# # Instantiates a single new object using the :admin mass-assignment security role
|
477
|
+
# User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
|
610
478
|
#
|
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
|
479
|
+
# # Instantiates a single new object bypassing mass-assignment security
|
480
|
+
# User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
|
481
|
+
def initialize(attributes = nil, options = {})
|
482
|
+
defaults = Hash[self.class.column_defaults.map { |k, v| [k, v.duplicable? ? v.dup : v] }]
|
483
|
+
@attributes = self.class.initialize_attributes(defaults)
|
484
|
+
@association_cache = {}
|
485
|
+
@aggregation_cache = {}
|
486
|
+
@attributes_cache = {}
|
487
|
+
@new_record = true
|
488
|
+
@readonly = false
|
489
|
+
@destroyed = false
|
490
|
+
@marked_for_destruction = false
|
491
|
+
@previously_changed = {}
|
492
|
+
@changed_attributes = {}
|
629
493
|
|
630
|
-
|
631
|
-
(parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
|
632
|
-
end
|
494
|
+
ensure_proper_type
|
633
495
|
|
634
|
-
|
635
|
-
# <tt>set_inheritance_column</tt> to set a different value.
|
636
|
-
def inheritance_column
|
637
|
-
@inheritance_column ||= "type"
|
638
|
-
end
|
496
|
+
populate_with_current_scope_attributes
|
639
497
|
|
640
|
-
|
641
|
-
# is only ever called once since set_sequence_name overrides it.
|
642
|
-
def sequence_name #:nodoc:
|
643
|
-
reset_sequence_name
|
644
|
-
end
|
498
|
+
assign_attributes(attributes, options) if attributes
|
645
499
|
|
646
|
-
|
647
|
-
|
648
|
-
set_sequence_name(default)
|
649
|
-
default
|
500
|
+
yield self if block_given?
|
501
|
+
run_callbacks :initialize
|
650
502
|
end
|
651
503
|
|
652
|
-
#
|
653
|
-
#
|
504
|
+
# Initialize an empty model object from +coder+. +coder+ must contain
|
505
|
+
# the attributes necessary for initializing an empty model object. For
|
506
|
+
# example:
|
654
507
|
#
|
655
|
-
# class
|
656
|
-
# set_table_name "project"
|
508
|
+
# class Post < ActiveRecord::Base
|
657
509
|
# 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
510
|
#
|
672
|
-
#
|
673
|
-
#
|
674
|
-
#
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
define_attr_method :inheritance_column, value, &block
|
679
|
-
end
|
680
|
-
alias :inheritance_column= :set_inheritance_column
|
511
|
+
# post = Post.allocate
|
512
|
+
# post.init_with('attributes' => { 'title' => 'hello world' })
|
513
|
+
# post.title # => 'hello world'
|
514
|
+
def init_with(coder)
|
515
|
+
@attributes = self.class.initialize_attributes(coder['attributes'])
|
516
|
+
@relation = nil
|
681
517
|
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
#
|
690
|
-
# If a sequence name is not explicitly set when using PostgreSQL, it
|
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
|
518
|
+
@attributes_cache, @previously_changed, @changed_attributes = {}, {}, {}
|
519
|
+
@association_cache = {}
|
520
|
+
@aggregation_cache = {}
|
521
|
+
@readonly = @destroyed = @marked_for_destruction = false
|
522
|
+
@new_record = false
|
523
|
+
run_callbacks :find
|
524
|
+
run_callbacks :initialize
|
700
525
|
|
701
|
-
|
702
|
-
def table_exists?
|
703
|
-
connection.table_exists?(table_name)
|
526
|
+
self
|
704
527
|
end
|
705
528
|
|
706
|
-
#
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
529
|
+
# Duped objects have no id assigned and are treated as new records. Note
|
530
|
+
# that this is a "shallow" copy as it copies the object's attributes
|
531
|
+
# only, not its associations. The extent of a "deep" copy is application
|
532
|
+
# specific and is therefore left to the application to implement according
|
533
|
+
# to its need.
|
534
|
+
# The dup method does not preserve the timestamps (created|updated)_(at|on).
|
535
|
+
def initialize_dup(other)
|
536
|
+
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
|
537
|
+
self.class.initialize_attributes(cloned_attributes, :serialized => false)
|
711
538
|
|
712
|
-
|
713
|
-
end
|
539
|
+
cloned_attributes.delete(self.class.primary_key)
|
714
540
|
|
715
|
-
|
716
|
-
def columns_hash
|
717
|
-
connection_pool.columns_hash[table_name]
|
718
|
-
end
|
541
|
+
@attributes = cloned_attributes
|
719
542
|
|
720
|
-
|
721
|
-
# default values when instantiating the AR object for this table.
|
722
|
-
def column_defaults
|
723
|
-
connection_pool.column_defaults[table_name]
|
724
|
-
end
|
543
|
+
_run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
|
725
544
|
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
545
|
+
@changed_attributes = {}
|
546
|
+
self.class.column_defaults.each do |attr, orig_value|
|
547
|
+
@changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
|
548
|
+
end
|
549
|
+
|
550
|
+
@aggregation_cache = {}
|
551
|
+
@association_cache = {}
|
552
|
+
@attributes_cache = {}
|
553
|
+
@new_record = true
|
730
554
|
|
731
|
-
|
732
|
-
|
733
|
-
def content_columns
|
734
|
-
@content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
|
555
|
+
ensure_proper_type
|
556
|
+
super
|
735
557
|
end
|
736
558
|
|
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
|
559
|
+
# Backport dup from 1.9 so that initialize_dup() gets called
|
560
|
+
unless Object.respond_to?(:initialize_dup, true)
|
561
|
+
def dup # :nodoc:
|
562
|
+
copy = super
|
563
|
+
copy.initialize_dup(self)
|
564
|
+
copy
|
748
565
|
end
|
749
566
|
end
|
750
567
|
|
751
|
-
#
|
752
|
-
#
|
753
|
-
#
|
754
|
-
#
|
755
|
-
# when just after creating a table you want to populate it with some default
|
756
|
-
# values, eg:
|
568
|
+
# Populate +coder+ with attributes about this record that should be
|
569
|
+
# serialized. The structure of +coder+ defined in this method is
|
570
|
+
# guaranteed to match the structure of +coder+ passed to the +init_with+
|
571
|
+
# method.
|
757
572
|
#
|
758
|
-
#
|
759
|
-
# def up
|
760
|
-
# create_table :job_levels do |t|
|
761
|
-
# t.integer :id
|
762
|
-
# t.string :name
|
573
|
+
# Example:
|
763
574
|
#
|
764
|
-
#
|
765
|
-
#
|
575
|
+
# class Post < ActiveRecord::Base
|
576
|
+
# end
|
577
|
+
# coder = {}
|
578
|
+
# Post.new.encode_with(coder)
|
579
|
+
# coder # => { 'id' => nil, ... }
|
580
|
+
def encode_with(coder)
|
581
|
+
coder['attributes'] = attributes
|
582
|
+
end
|
583
|
+
|
584
|
+
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
|
585
|
+
# is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
|
766
586
|
#
|
767
|
-
#
|
768
|
-
#
|
769
|
-
#
|
770
|
-
# end
|
771
|
-
# end
|
587
|
+
# Note that new records are different from any other record by definition, unless the
|
588
|
+
# other record is the receiver itself. Besides, if you fetch existing records with
|
589
|
+
# +select+ and leave the ID out, you're on your own, this predicate will return false.
|
772
590
|
#
|
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
|
591
|
+
# Note also that destroying a record preserves its ID in the model instance, so deleted
|
592
|
+
# models are still comparable.
|
593
|
+
def ==(comparison_object)
|
594
|
+
super ||
|
595
|
+
comparison_object.instance_of?(self.class) &&
|
596
|
+
id.present? &&
|
597
|
+
comparison_object.id == id
|
784
598
|
end
|
599
|
+
alias :eql? :==
|
785
600
|
|
786
|
-
|
787
|
-
|
601
|
+
# Delegates to id in order to allow two records of the same type and id to work with something like:
|
602
|
+
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
|
603
|
+
def hash
|
604
|
+
id.hash
|
788
605
|
end
|
789
606
|
|
790
|
-
|
791
|
-
|
607
|
+
# Freeze the attributes hash such that associations are still accessible, even on destroyed records.
|
608
|
+
def freeze
|
609
|
+
@attributes.freeze; self
|
792
610
|
end
|
793
611
|
|
794
|
-
# Returns
|
795
|
-
|
796
|
-
|
797
|
-
def attribute_names
|
798
|
-
@attribute_names ||= if !abstract_class? && table_exists?
|
799
|
-
column_names
|
800
|
-
else
|
801
|
-
[]
|
802
|
-
end
|
612
|
+
# Returns +true+ if the attributes hash has been frozen.
|
613
|
+
def frozen?
|
614
|
+
@attributes.frozen?
|
803
615
|
end
|
804
616
|
|
805
|
-
#
|
806
|
-
def
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
while klass != klass.base_class
|
812
|
-
classes << klass = klass.superclass
|
617
|
+
# Allows sort on objects
|
618
|
+
def <=>(other_object)
|
619
|
+
if other_object.is_a?(self.class)
|
620
|
+
self.to_key <=> other_object.to_key
|
621
|
+
else
|
622
|
+
nil
|
813
623
|
end
|
814
|
-
classes
|
815
|
-
end
|
816
|
-
|
817
|
-
# Set the i18n scope to overwrite ActiveModel.
|
818
|
-
def i18n_scope #:nodoc:
|
819
|
-
:activerecord
|
820
624
|
end
|
821
625
|
|
822
|
-
#
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
else
|
827
|
-
superclass == Base || !columns_hash.include?(inheritance_column)
|
828
|
-
end
|
626
|
+
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
|
627
|
+
# attributes will be marked as read only since they cannot be saved.
|
628
|
+
def readonly?
|
629
|
+
@readonly
|
829
630
|
end
|
830
631
|
|
831
|
-
|
832
|
-
|
833
|
-
|
632
|
+
# Marks this record as read only.
|
633
|
+
def readonly!
|
634
|
+
@readonly = true
|
834
635
|
end
|
835
636
|
|
836
|
-
# Returns a
|
637
|
+
# Returns the contents of the record as a nicely formatted string.
|
837
638
|
def inspect
|
838
|
-
|
639
|
+
inspection = if @attributes
|
640
|
+
self.class.column_names.collect { |name|
|
641
|
+
if has_attribute?(name)
|
642
|
+
"#{name}: #{attribute_for_inspect(name)}"
|
643
|
+
end
|
644
|
+
}.compact.join(", ")
|
645
|
+
else
|
646
|
+
"not initialized"
|
647
|
+
end
|
648
|
+
"#<#{self.class} #{inspection}>"
|
649
|
+
end
|
650
|
+
|
651
|
+
# Hackery to accomodate Syck. Remove for 4.0.
|
652
|
+
def to_yaml(opts = {}) #:nodoc:
|
653
|
+
if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck?
|
839
654
|
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
655
|
else
|
846
|
-
|
656
|
+
coder = {}
|
657
|
+
encode_with(coder)
|
658
|
+
YAML.quick_emit(self, opts) do |out|
|
659
|
+
out.map(taguri, to_yaml_style) do |map|
|
660
|
+
coder.each { |k, v| map.add(k, v) }
|
661
|
+
end
|
662
|
+
end
|
847
663
|
end
|
848
664
|
end
|
849
665
|
|
850
|
-
|
851
|
-
|
666
|
+
# Hackery to accomodate Syck. Remove for 4.0.
|
667
|
+
def yaml_initialize(tag, coder) #:nodoc:
|
668
|
+
init_with(coder)
|
852
669
|
end
|
853
670
|
|
854
|
-
|
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
|
1935
|
-
end
|
1936
|
-
|
1937
|
-
private
|
671
|
+
private
|
1938
672
|
|
1939
673
|
# Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements
|
1940
674
|
# of the array, and then rescues from the possible NoMethodError. If those elements are
|
@@ -1948,254 +682,39 @@ MSG
|
|
1948
682
|
nil
|
1949
683
|
end
|
1950
684
|
|
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
685
|
include ActiveRecord::Persistence
|
2159
686
|
extend ActiveModel::Naming
|
2160
687
|
extend QueryCache::ClassMethods
|
2161
688
|
extend ActiveSupport::Benchmarkable
|
2162
689
|
extend ActiveSupport::DescendantsTracker
|
2163
690
|
|
691
|
+
extend Querying
|
692
|
+
include ReadonlyAttributes
|
693
|
+
include ModelSchema
|
694
|
+
extend Translation
|
695
|
+
include Inheritance
|
696
|
+
include Scoping
|
697
|
+
extend DynamicMatchers
|
698
|
+
include Sanitization
|
699
|
+
include AttributeAssignment
|
2164
700
|
include ActiveModel::Conversion
|
701
|
+
include Integration
|
2165
702
|
include Validations
|
2166
703
|
extend CounterCache
|
2167
704
|
include Locking::Optimistic, Locking::Pessimistic
|
2168
705
|
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
706
|
include Callbacks, ActiveModel::Observing, Timestamp
|
2175
|
-
include Associations
|
707
|
+
include Associations
|
2176
708
|
include IdentityMap
|
2177
709
|
include ActiveModel::SecurePassword
|
710
|
+
extend Explain
|
2178
711
|
|
2179
712
|
# AutosaveAssociation needs to be included before Transactions, because we want
|
2180
713
|
# #save_with_autosave_associations to be wrapped inside a transaction.
|
2181
714
|
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
|
715
|
+
include Aggregations, Transactions, Reflection, Serialization, Store
|
2196
716
|
end
|
2197
717
|
end
|
2198
718
|
|
2199
|
-
|
2200
|
-
require 'active_record/connection_adapters/abstract_adapter'
|
719
|
+
require 'active_record/connection_adapters/abstract/connection_specification'
|
2201
720
|
ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
|