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.

Files changed (99) hide show
  1. data/CHANGELOG.md +6263 -103
  2. data/README.rdoc +2 -2
  3. data/examples/performance.rb +55 -31
  4. data/lib/active_record.rb +28 -2
  5. data/lib/active_record/aggregations.rb +2 -2
  6. data/lib/active_record/associations.rb +82 -69
  7. data/lib/active_record/associations/association.rb +2 -37
  8. data/lib/active_record/associations/association_scope.rb +3 -30
  9. data/lib/active_record/associations/builder/association.rb +6 -4
  10. data/lib/active_record/associations/builder/belongs_to.rb +3 -3
  11. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +4 -4
  13. data/lib/active_record/associations/builder/has_one.rb +5 -6
  14. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  15. data/lib/active_record/associations/collection_association.rb +55 -28
  16. data/lib/active_record/associations/collection_proxy.rb +1 -35
  17. data/lib/active_record/associations/has_many_association.rb +5 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +11 -8
  19. data/lib/active_record/associations/join_dependency.rb +1 -1
  20. data/lib/active_record/associations/preloader/association.rb +3 -1
  21. data/lib/active_record/attribute_assignment.rb +221 -0
  22. data/lib/active_record/attribute_methods.rb +212 -32
  23. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  24. data/lib/active_record/attribute_methods/dirty.rb +3 -3
  25. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  26. data/lib/active_record/attribute_methods/read.rb +69 -80
  27. data/lib/active_record/attribute_methods/serialization.rb +89 -0
  28. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
  29. data/lib/active_record/attribute_methods/write.rb +27 -5
  30. data/lib/active_record/autosave_association.rb +23 -8
  31. data/lib/active_record/base.rb +223 -1712
  32. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
  33. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -13
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
  40. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  41. data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
  42. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
  43. data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
  44. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  45. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  46. data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
  47. data/lib/active_record/counter_cache.rb +1 -1
  48. data/lib/active_record/dynamic_matchers.rb +79 -0
  49. data/lib/active_record/errors.rb +11 -1
  50. data/lib/active_record/explain.rb +83 -0
  51. data/lib/active_record/explain_subscriber.rb +21 -0
  52. data/lib/active_record/fixtures.rb +31 -76
  53. data/lib/active_record/fixtures/file.rb +65 -0
  54. data/lib/active_record/identity_map.rb +1 -7
  55. data/lib/active_record/inheritance.rb +167 -0
  56. data/lib/active_record/integration.rb +49 -0
  57. data/lib/active_record/locking/optimistic.rb +19 -11
  58. data/lib/active_record/locking/pessimistic.rb +1 -1
  59. data/lib/active_record/log_subscriber.rb +3 -3
  60. data/lib/active_record/migration.rb +38 -29
  61. data/lib/active_record/migration/command_recorder.rb +7 -7
  62. data/lib/active_record/model_schema.rb +362 -0
  63. data/lib/active_record/nested_attributes.rb +3 -2
  64. data/lib/active_record/persistence.rb +51 -1
  65. data/lib/active_record/querying.rb +58 -0
  66. data/lib/active_record/railtie.rb +24 -28
  67. data/lib/active_record/railties/controller_runtime.rb +3 -1
  68. data/lib/active_record/railties/databases.rake +133 -77
  69. data/lib/active_record/readonly_attributes.rb +26 -0
  70. data/lib/active_record/reflection.rb +7 -15
  71. data/lib/active_record/relation.rb +78 -35
  72. data/lib/active_record/relation/batches.rb +5 -2
  73. data/lib/active_record/relation/calculations.rb +27 -6
  74. data/lib/active_record/relation/delegation.rb +49 -0
  75. data/lib/active_record/relation/finder_methods.rb +5 -4
  76. data/lib/active_record/relation/predicate_builder.rb +13 -16
  77. data/lib/active_record/relation/query_methods.rb +59 -4
  78. data/lib/active_record/result.rb +1 -1
  79. data/lib/active_record/sanitization.rb +194 -0
  80. data/lib/active_record/schema_dumper.rb +5 -2
  81. data/lib/active_record/scoping.rb +152 -0
  82. data/lib/active_record/scoping/default.rb +140 -0
  83. data/lib/active_record/scoping/named.rb +202 -0
  84. data/lib/active_record/serialization.rb +1 -43
  85. data/lib/active_record/serializers/xml_serializer.rb +2 -44
  86. data/lib/active_record/session_store.rb +11 -11
  87. data/lib/active_record/store.rb +50 -0
  88. data/lib/active_record/test_case.rb +11 -7
  89. data/lib/active_record/timestamp.rb +16 -3
  90. data/lib/active_record/transactions.rb +5 -5
  91. data/lib/active_record/translation.rb +22 -0
  92. data/lib/active_record/validations.rb +1 -1
  93. data/lib/active_record/validations/associated.rb +5 -4
  94. data/lib/active_record/validations/uniqueness.rb +4 -4
  95. data/lib/active_record/version.rb +3 -3
  96. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  97. metadata +48 -38
  98. checksums.yaml +0 -7
  99. 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
- # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
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 define_method_attribute(attr_name)
23
- if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
24
- method_body, line = <<-EOV, __LINE__ + 1
25
- def _#{attr_name}
26
- cached = @attributes_cache['#{attr_name}']
27
- return cached if cached
28
- time = _read_attribute('#{attr_name}')
29
- @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
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::COMPILABLE_REGEXP
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
- if (column = column_for_attribute(attr_name)) && column.number?
30
- @attributes[attr_name] = convert_number_column_value(value)
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
- @attributes[attr_name] = value
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. However,
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.target
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
- record[reflection.foreign_key] = key
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
@@ -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
- delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
442
- delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
443
- delegate :find_each, :find_in_batches, :to => :scoped
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
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
- # Executes a custom SQL query against your database and returns all the results. The results will
448
- # be returned as an array with columns requested encapsulated as attributes of the model you call
449
- # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
450
- # a Product object with the attributes you specified in the SQL query.
451
- #
452
- # If you call a complicated SQL query which spans multiple tables the columns specified by the
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
- # Creates an object (or multiple objects) and saves it to the database, if validations pass.
474
- # The resulting object is returned whether the object was saved successfully to the database or not.
475
- #
476
- # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
477
- # attributes on the objects that are to be created.
478
- #
479
- # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
480
- # in the +options+ parameter.
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
- object = new(attributes, options)
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
- # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
516
- # The use of this method should be restricted to complicated SQL queries that can't be executed
517
- # using the ActiveRecord::Calculations class methods. Look into those before using this.
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
- # Attributes listed as readonly will be used to create a new record but update operations will
532
- # ignore these fields.
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
- # Returns an array of all the attributes that have been specified as readonly.
538
- def readonly_attributes
539
- self._attr_readonly
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
- # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
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
440
+ private
441
+
442
+ def relation #:nodoc:
443
+ @relation ||= Relation.new(self, arel_table)
563
444
 
564
- # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
565
- # has its own hash of own serialized attributes
566
- self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
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
- # Guesses the table name (in forced lower-case) based on the name of the class in the
570
- # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
571
- # looks like: Reply < Message < ActiveRecord::Base, then Message is used
572
- # to guess the table name even when called on Reply. The rules used to do the guess
573
- # are handled by the Inflector class in Active Support, which knows almost all common
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
- # Nested classes are given table names prefixed by the singular form of
577
- # the parent's table name. Enclosing modules are not considered.
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
- # class Invoice < ActiveRecord::Base
582
- # end
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
- # class Mouse < ActiveRecord::Base
612
- # set_table_name "mice"
613
- # end
614
- def table_name
615
- reset_table_name
616
- end
617
-
618
- # Returns a quoted version of the table name, used to construct SQL statements.
619
- def quoted_table_name
620
- @quoted_table_name ||= connection.quote_table_name(table_name)
621
- end
622
-
623
- # Computes the table name, (re)sets it internally, and returns it.
624
- def reset_table_name #:nodoc:
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
- def full_table_name_prefix #:nodoc:
631
- (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
632
- end
484
+ ensure_proper_type
485
+ set_serialized_attributes
633
486
 
634
- # Defines the column name for use with single table inheritance. Use
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
- # Lazy-set the sequence name to the connection's default. This method
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
- def reset_sequence_name #:nodoc:
647
- default = connection.default_sequence_name(table_name, primary_key)
648
- set_sequence_name(default)
649
- default
491
+ yield self if block_given?
492
+ run_callbacks :initialize
650
493
  end
651
494
 
652
- # Sets the table name. If the value is nil or false then the value returned by the given
653
- # block is used.
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 Project < ActiveRecord::Base
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
- # class Project < ActiveRecord::Base
673
- # set_inheritance_column do
674
- # original_inheritance_column + "_id"
675
- # end
676
- # end
677
- def set_inheritance_column(value = nil, &block)
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
- # Sets the name of the sequence to use when generating ids to the given
683
- # value, or (if the value is nil or false) to the value returned by the
684
- # given block. This is required for Oracle and is useful for any
685
- # database which relies on sequences for primary key generation.
686
- #
687
- # If a sequence name is not explicitly set when using Oracle or Firebird,
688
- # it will default to the commonly used pattern of: #{table_name}_seq
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
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
- # Indicates whether the table associated with this class exists
702
- def table_exists?
703
- connection.table_exists?(table_name)
519
+ self
704
520
  end
705
521
 
706
- # Returns an array of column objects for the table associated with this class.
707
- def columns
708
- if defined?(@primary_key)
709
- connection_pool.primary_keys[table_name] ||= primary_key
710
- end
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
- connection_pool.columns[table_name]
713
- end
532
+ @attributes = cloned_attributes
714
533
 
715
- # Returns a hash of column objects for the table associated with this class.
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
- # Returns a hash where the keys are column names and the values are
721
- # default values when instantiating the AR object for this table.
722
- def column_defaults
723
- connection_pool.column_defaults[table_name]
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
- # Returns an array of column names as strings.
727
- def column_names
728
- @column_names ||= columns.map { |column| column.name }
729
- end
541
+ @aggregation_cache = {}
542
+ @association_cache = {}
543
+ @attributes_cache = {}
544
+ @new_record = true
730
545
 
731
- # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
732
- # and columns used for single table inheritance have been removed.
733
- def content_columns
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
- # Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
738
- # and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
739
- # is available.
740
- def column_methods_hash #:nodoc:
741
- @dynamic_methods_hash ||= column_names.inject(Hash.new(false)) do |methods, attr|
742
- attr_name = attr.to_s
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
- # Resets all the cached information about columns, which will cause them
752
- # to be reloaded on the next request.
753
- #
754
- # The most common usage pattern for this method is probably in a migration,
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
- # class CreateJobLevels < ActiveRecord::Migration
759
- # def up
760
- # create_table :job_levels do |t|
761
- # t.integer :id
762
- # t.string :name
565
+ # Example:
763
566
  #
764
- # t.timestamps
765
- # end
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
- # JobLevel.reset_column_information
768
- # %w{assistant executive manager director}.each do |type|
769
- # JobLevel.create(:name => type)
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
- # def down
774
- # drop_table :job_levels
775
- # end
776
- # end
777
- def reset_column_information
778
- connection.clear_cache!
779
- undefine_attribute_methods
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
- def clear_cache! # :nodoc:
787
- connection_pool.clear_cache!
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
- def attribute_method?(attribute)
791
- super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
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 an array of column names as strings if it's not
795
- # an abstract class and table exists.
796
- # Otherwise it returns an empty array.
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
- # Set the lookup ancestors for ActiveModel.
806
- def lookup_ancestors #:nodoc:
807
- klass = self
808
- classes = [klass]
809
- return classes if klass == ActiveRecord::Base
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
- # Set the i18n scope to overwrite ActiveModel.
818
- def i18n_scope #:nodoc:
819
- :activerecord
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
- # True if this isn't a concrete subclass needing a STI type condition.
823
- def descends_from_active_record?
824
- if superclass.abstract_class?
825
- superclass.descends_from_active_record?
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
- superclass == Base || !columns_hash.include?(inheritance_column)
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
- def finder_needs_type_condition? #:nodoc:
832
- # This is like this because benchmarking justifies the strange :false stuff
833
- :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
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, NamedScope
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
- # TODO: Remove this and make it work with LAZY flag
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)