activerecord 1.14.4 → 1.15.0

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 (159) hide show
  1. data/CHANGELOG +400 -1
  2. data/README +2 -2
  3. data/RUNNING_UNIT_TESTS +21 -3
  4. data/Rakefile +55 -10
  5. data/lib/active_record.rb +10 -4
  6. data/lib/active_record/acts/list.rb +15 -4
  7. data/lib/active_record/acts/nested_set.rb +11 -12
  8. data/lib/active_record/acts/tree.rb +13 -14
  9. data/lib/active_record/aggregations.rb +46 -22
  10. data/lib/active_record/associations.rb +213 -162
  11. data/lib/active_record/associations/association_collection.rb +45 -15
  12. data/lib/active_record/associations/association_proxy.rb +32 -13
  13. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
  14. data/lib/active_record/associations/has_many_association.rb +37 -17
  15. data/lib/active_record/associations/has_many_through_association.rb +120 -30
  16. data/lib/active_record/associations/has_one_association.rb +1 -1
  17. data/lib/active_record/attribute_methods.rb +75 -0
  18. data/lib/active_record/base.rb +282 -203
  19. data/lib/active_record/calculations.rb +95 -54
  20. data/lib/active_record/callbacks.rb +13 -24
  21. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
  24. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
  25. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
  27. data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
  28. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
  29. data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
  30. data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
  31. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
  32. data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
  33. data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
  34. data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
  35. data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
  36. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
  37. data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
  38. data/lib/active_record/deprecated_associations.rb +24 -10
  39. data/lib/active_record/deprecated_finders.rb +4 -1
  40. data/lib/active_record/fixtures.rb +37 -23
  41. data/lib/active_record/locking/optimistic.rb +106 -0
  42. data/lib/active_record/locking/pessimistic.rb +77 -0
  43. data/lib/active_record/migration.rb +8 -5
  44. data/lib/active_record/observer.rb +73 -34
  45. data/lib/active_record/reflection.rb +21 -7
  46. data/lib/active_record/schema_dumper.rb +33 -5
  47. data/lib/active_record/timestamp.rb +23 -34
  48. data/lib/active_record/transactions.rb +37 -30
  49. data/lib/active_record/validations.rb +46 -30
  50. data/lib/active_record/vendor/mysql.rb +20 -5
  51. data/lib/active_record/version.rb +2 -2
  52. data/lib/active_record/wrappings.rb +1 -2
  53. data/lib/active_record/xml_serialization.rb +308 -0
  54. data/test/aaa_create_tables_test.rb +5 -1
  55. data/test/abstract_unit.rb +18 -8
  56. data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
  57. data/test/adapter_test.rb +9 -7
  58. data/test/adapter_test_sqlserver.rb +81 -0
  59. data/test/aggregations_test.rb +29 -0
  60. data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
  61. data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
  62. data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
  63. data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
  64. data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
  65. data/test/associations_test.rb +339 -45
  66. data/test/attribute_methods_test.rb +49 -0
  67. data/test/base_test.rb +321 -67
  68. data/test/calculations_test.rb +48 -10
  69. data/test/callbacks_test.rb +13 -0
  70. data/test/connection_test_firebird.rb +8 -0
  71. data/test/connections/native_db2/connection.rb +18 -17
  72. data/test/connections/native_firebird/connection.rb +19 -17
  73. data/test/connections/native_frontbase/connection.rb +27 -0
  74. data/test/connections/native_mysql/connection.rb +18 -15
  75. data/test/connections/native_openbase/connection.rb +14 -15
  76. data/test/connections/native_oracle/connection.rb +16 -12
  77. data/test/connections/native_postgresql/connection.rb +16 -17
  78. data/test/connections/native_sqlite/connection.rb +3 -6
  79. data/test/connections/native_sqlite3/connection.rb +3 -6
  80. data/test/connections/native_sqlserver/connection.rb +16 -17
  81. data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
  82. data/test/connections/native_sybase/connection.rb +16 -17
  83. data/test/datatype_test_postgresql.rb +52 -0
  84. data/test/defaults_test.rb +52 -10
  85. data/test/deprecated_associations_test.rb +151 -107
  86. data/test/deprecated_finder_test.rb +83 -66
  87. data/test/empty_date_time_test.rb +25 -0
  88. data/test/finder_test.rb +118 -11
  89. data/test/fixtures/accounts.yml +6 -1
  90. data/test/fixtures/author.rb +27 -4
  91. data/test/fixtures/categorizations.yml +8 -2
  92. data/test/fixtures/category.rb +1 -2
  93. data/test/fixtures/comments.yml +0 -6
  94. data/test/fixtures/companies.yml +6 -1
  95. data/test/fixtures/company.rb +23 -1
  96. data/test/fixtures/company_in_module.rb +8 -10
  97. data/test/fixtures/customer.rb +2 -2
  98. data/test/fixtures/customers.yml +9 -0
  99. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  100. data/test/fixtures/db_definitions/db2.sql +9 -0
  101. data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
  102. data/test/fixtures/db_definitions/firebird.sql +13 -1
  103. data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
  104. data/test/fixtures/db_definitions/frontbase.sql +262 -0
  105. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  107. data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
  108. data/test/fixtures/db_definitions/mysql.sql +23 -14
  109. data/test/fixtures/db_definitions/openbase.sql +13 -1
  110. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  111. data/test/fixtures/db_definitions/oracle.sql +29 -2
  112. data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
  113. data/test/fixtures/db_definitions/postgresql.sql +13 -3
  114. data/test/fixtures/db_definitions/schema.rb +29 -1
  115. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  116. data/test/fixtures/db_definitions/sqlite.sql +12 -3
  117. data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
  118. data/test/fixtures/db_definitions/sqlserver.sql +35 -0
  119. data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
  120. data/test/fixtures/db_definitions/sybase.sql +13 -4
  121. data/test/fixtures/developer.rb +12 -0
  122. data/test/fixtures/edge.rb +5 -0
  123. data/test/fixtures/edges.yml +6 -0
  124. data/test/fixtures/funny_jokes.yml +3 -7
  125. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  126. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  127. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  128. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  129. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  130. data/test/fixtures/mixin.rb +15 -0
  131. data/test/fixtures/mixins.yml +38 -0
  132. data/test/fixtures/post.rb +3 -2
  133. data/test/fixtures/project.rb +3 -1
  134. data/test/fixtures/topic.rb +6 -1
  135. data/test/fixtures/topics.yml +4 -4
  136. data/test/fixtures/vertex.rb +9 -0
  137. data/test/fixtures/vertices.yml +4 -0
  138. data/test/fixtures_test.rb +45 -0
  139. data/test/inheritance_test.rb +67 -6
  140. data/test/lifecycle_test.rb +40 -19
  141. data/test/locking_test.rb +170 -26
  142. data/test/method_scoping_test.rb +2 -2
  143. data/test/migration_test.rb +387 -110
  144. data/test/migration_test_firebird.rb +124 -0
  145. data/test/mixin_nested_set_test.rb +14 -2
  146. data/test/mixin_test.rb +56 -18
  147. data/test/modules_test.rb +8 -2
  148. data/test/multiple_db_test.rb +2 -2
  149. data/test/pk_test.rb +1 -0
  150. data/test/reflection_test.rb +8 -2
  151. data/test/schema_authorization_test_postgresql.rb +75 -0
  152. data/test/schema_dumper_test.rb +40 -4
  153. data/test/table_name_test_sqlserver.rb +23 -0
  154. data/test/threaded_connections_test.rb +19 -16
  155. data/test/transactions_test.rb +86 -72
  156. data/test/validations_test.rb +126 -56
  157. data/test/xml_serialization_test.rb +125 -0
  158. metadata +45 -11
  159. data/lib/active_record/locking.rb +0 -79
@@ -69,7 +69,7 @@ module ActiveRecord
69
69
  when @reflection.options[:as]
70
70
  @finder_sql =
71
71
  "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
72
- "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote @owner.class.base_class.name.to_s}"
72
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
73
73
  else
74
74
  @finder_sql = "#{@reflection.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
75
75
  end
@@ -0,0 +1,75 @@
1
+ module ActiveRecord
2
+ module AttributeMethods #:nodoc:
3
+ DEFAULT_SUFFIXES = %w(= ? _before_type_cast)
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ base.attribute_method_suffix *DEFAULT_SUFFIXES
8
+ end
9
+
10
+ # Declare and check for suffixed attribute methods.
11
+ module ClassMethods
12
+ # Declare a method available for all attributes with the given suffix.
13
+ # Uses method_missing and respond_to? to rewrite the method
14
+ # #{attr}#{suffix}(*args, &block)
15
+ # to
16
+ # attribute#{suffix}(#{attr}, *args, &block)
17
+ #
18
+ # An attribute#{suffix} instance method must exist and accept at least
19
+ # the attr argument.
20
+ #
21
+ # For example:
22
+ # class Person < ActiveRecord::Base
23
+ # attribute_method_suffix '_changed?'
24
+ #
25
+ # private
26
+ # def attribute_changed?(attr)
27
+ # ...
28
+ # end
29
+ # end
30
+ #
31
+ # person = Person.find(1)
32
+ # person.name_changed? # => false
33
+ # person.name = 'Hubert'
34
+ # person.name_changed? # => true
35
+ def attribute_method_suffix(*suffixes)
36
+ attribute_method_suffixes.concat suffixes
37
+ rebuild_attribute_method_regexp
38
+ end
39
+
40
+ # Returns MatchData if method_name is an attribute method.
41
+ def match_attribute_method?(method_name)
42
+ rebuild_attribute_method_regexp unless defined?(@@attribute_method_regexp) && @@attribute_method_regexp
43
+ @@attribute_method_regexp.match(method_name)
44
+ end
45
+
46
+ private
47
+ # Suffixes a, ?, c become regexp /(a|\?|c)$/
48
+ def rebuild_attribute_method_regexp
49
+ suffixes = attribute_method_suffixes.map { |s| Regexp.escape(s) }
50
+ @@attribute_method_regexp = /(#{suffixes.join('|')})$/.freeze
51
+ end
52
+
53
+ # Default to =, ?, _before_type_cast
54
+ def attribute_method_suffixes
55
+ @@attribute_method_suffixes ||= []
56
+ end
57
+ end
58
+
59
+ private
60
+ # Handle *? for method_missing.
61
+ def attribute?(attribute_name)
62
+ query_attribute(attribute_name)
63
+ end
64
+
65
+ # Handle *= for method_missing.
66
+ def attribute=(attribute_name, value)
67
+ write_attribute(attribute_name, value)
68
+ end
69
+
70
+ # Handle *_before_type_cast for method_missing.
71
+ def attribute_before_type_cast(attribute_name)
72
+ read_attribute_before_type_cast(attribute_name)
73
+ end
74
+ end
75
+ end
@@ -1,3 +1,4 @@
1
+ require 'base64'
1
2
  require 'yaml'
2
3
  require 'set'
3
4
  require 'active_record/deprecated_finders'
@@ -80,11 +81,12 @@ module ActiveRecord #:nodoc:
80
81
  #
81
82
  # == Conditions
82
83
  #
83
- # Conditions can either be specified as a string or an array representing the WHERE-part of an SQL statement.
84
+ # Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement.
84
85
  # The array form is to be used when the condition input is tainted and requires sanitization. The string form can
85
- # be used for statements that don't involve tainted data. Examples:
86
+ # be used for statements that don't involve tainted data. The hash form works much like the array form, except
87
+ # only equality is possible. Examples:
86
88
  #
87
- # User < ActiveRecord::Base
89
+ # class User < ActiveRecord::Base
88
90
  # def self.authenticate_unsafely(user_name, password)
89
91
  # find(:first, :conditions => "user_name = '#{user_name}' AND password = '#{password}'")
90
92
  # end
@@ -92,12 +94,16 @@ module ActiveRecord #:nodoc:
92
94
  # def self.authenticate_safely(user_name, password)
93
95
  # find(:first, :conditions => [ "user_name = ? AND password = ?", user_name, password ])
94
96
  # end
97
+ #
98
+ # def self.authenticate_safely_simply(user_name, password)
99
+ # find(:first, :conditions => { :user_name => user_name, :password => password })
100
+ # end
95
101
  # end
96
102
  #
97
103
  # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
98
- # attacks if the <tt>user_name</tt> and +password+ parameters come directly from a HTTP request. The <tt>authenticate_safely</tt> method,
99
- # on the other hand, will sanitize the <tt>user_name</tt> and +password+ before inserting them in the query, which will ensure that
100
- # an attacker can't escape the query and fake the login (or worse).
104
+ # attacks if the <tt>user_name</tt> and +password+ parameters come directly from a HTTP request. The <tt>authenticate_safely</tt> and
105
+ # <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+ before inserting them in the query,
106
+ # which will ensure that an attacker can't escape the query and fake the login (or worse).
101
107
  #
102
108
  # When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
103
109
  # question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
@@ -108,6 +114,13 @@ module ActiveRecord #:nodoc:
108
114
  # { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
109
115
  # ])
110
116
  #
117
+ # Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
118
+ # operator. For instance:
119
+ #
120
+ # Student.find(:all, :conditions => { :first_name => "Harvey", :status => 1 })
121
+ # Student.find(:all, :conditions => params[:student])
122
+ #
123
+ #
111
124
  # == Overwriting default accessors
112
125
  #
113
126
  # All column values are automatically available through basic accessors on the Active Record object, but some times you
@@ -166,6 +179,12 @@ module ActiveRecord #:nodoc:
166
179
  # # Now the 'Summer' tag does exist
167
180
  # Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
168
181
  #
182
+ # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Example:
183
+ #
184
+ # # No 'Winter' tag exists
185
+ # winter = Tag.find_or_initialize_by_name("Winter")
186
+ # winter.new_record? # true
187
+ #
169
188
  # == Saving arrays, hashes, and other non-mappable objects in text columns
170
189
  #
171
190
  # Active Record can serialize any object in text columns using YAML. To do so, you must specify this with a call to the class method +serialize+.
@@ -245,7 +264,7 @@ module ActiveRecord #:nodoc:
245
264
  # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
246
265
  cattr_accessor :logger
247
266
 
248
- include Reloadable::Subclasses
267
+ include Reloadable::Deprecated
249
268
 
250
269
  def self.inherited(child) #:nodoc:
251
270
  @@subclasses[self] ||= []
@@ -256,7 +275,7 @@ module ActiveRecord #:nodoc:
256
275
  def self.reset_subclasses #:nodoc:
257
276
  nonreloadables = []
258
277
  subclasses.each do |klass|
259
- unless klass.reloadable?
278
+ unless Dependencies.autoloaded? klass
260
279
  nonreloadables << klass
261
280
  next
262
281
  end
@@ -351,7 +370,11 @@ module ActiveRecord #:nodoc:
351
370
  # to already defined associations. See eager loading under Associations.
352
371
  # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
353
372
  # include the joined columns.
373
+ # * <tt>:from</tt>: By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
374
+ # of a database view).
354
375
  # * <tt>:readonly</tt>: Mark the returned records read-only so they cannot be saved or updated.
376
+ # * <tt>:lock</tt>: An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
377
+ # :lock => true gives connection's default exclusive lock, usually "FOR UPDATE".
355
378
  #
356
379
  # Examples for find by id:
357
380
  # Person.find(1) # returns the object for ID = 1
@@ -371,6 +394,17 @@ module ActiveRecord #:nodoc:
371
394
  # Person.find(:all, :offset => 10, :limit => 10)
372
395
  # Person.find(:all, :include => [ :account, :friends ])
373
396
  # Person.find(:all, :group => "category")
397
+ #
398
+ # Example for find with a lock. Imagine two concurrent transactions:
399
+ # each will read person.visits == 2, add 1 to it, and save, resulting
400
+ # in two saves of person.visits = 3. By locking the row, the second
401
+ # transaction has to wait until the first is finished; we get the
402
+ # expected person.visits == 4.
403
+ # Person.transaction do
404
+ # person = Person.find(1, :lock => true)
405
+ # person.visits += 1
406
+ # person.save!
407
+ # end
374
408
  def find(*args)
375
409
  options = extract_options_from_args!(args)
376
410
  validate_find_options(options)
@@ -391,10 +425,16 @@ module ActiveRecord #:nodoc:
391
425
  end
392
426
 
393
427
  # Returns true if the given +id+ represents the primary key of a record in the database, false otherwise.
428
+ # You can also pass a set of SQL conditions.
394
429
  # Example:
395
430
  # Person.exists?(5)
396
- def exists?(id)
397
- !find(:first, :conditions => ["#{primary_key} = ?", id]).nil? rescue false
431
+ # Person.exists?('5')
432
+ # Person.exists?(:name => "David")
433
+ # Person.exists?(['name LIKE ?', "%#{query}%"])
434
+ def exists?(id_or_conditions)
435
+ !find(:first, :conditions => expand_id_conditions(id_or_conditions)).nil?
436
+ rescue ActiveRecord::ActiveRecordError
437
+ false
398
438
  end
399
439
 
400
440
  # Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
@@ -483,12 +523,12 @@ module ActiveRecord #:nodoc:
483
523
  # for looping over a collection where each element require a number of aggregate values. Like the DiscussionBoard
484
524
  # that needs to list both the number of posts and comments.
485
525
  def increment_counter(counter_name, id)
486
- update_all "#{counter_name} = #{counter_name} + 1", "#{primary_key} = #{quote(id)}"
526
+ update_all "#{counter_name} = #{counter_name} + 1", "#{primary_key} = #{quote_value(id)}"
487
527
  end
488
528
 
489
529
  # Works like increment_counter, but decrements instead.
490
530
  def decrement_counter(counter_name, id)
491
- update_all "#{counter_name} = #{counter_name} - 1", "#{primary_key} = #{quote(id)}"
531
+ update_all "#{counter_name} = #{counter_name} - 1", "#{primary_key} = #{quote_value(id)}"
492
532
  end
493
533
 
494
534
 
@@ -548,21 +588,44 @@ module ActiveRecord #:nodoc:
548
588
  # to guess the table name from even when called on Reply. The rules used to do the guess are handled by the Inflector class
549
589
  # in Active Support, which knows almost all common English inflections (report a bug if your inflection isn't covered).
550
590
  #
551
- # Additionally, the class-level table_name_prefix is prepended to the table_name and the table_name_suffix is appended.
552
- # So if you have "myapp_" as a prefix, the table name guess for an Account class becomes "myapp_accounts".
591
+ # Nested classes are given table names prefixed by the singular form of
592
+ # the parent's table name. Example:
593
+ # file class table_name
594
+ # invoice.rb Invoice invoices
595
+ # invoice/lineitem.rb Invoice::Lineitem invoice_lineitems
596
+ #
597
+ # Additionally, the class-level table_name_prefix is prepended and the
598
+ # table_name_suffix is appended. So if you have "myapp_" as a prefix,
599
+ # the table name guess for an Invoice class becomes "myapp_invoices".
600
+ # Invoice::Lineitem becomes "myapp_invoice_lineitems".
553
601
  #
554
- # You can also overwrite this class method to allow for unguessable links, such as a Mouse class with a link to a
555
- # "mice" table. Example:
602
+ # You can also overwrite this class method to allow for unguessable
603
+ # links, such as a Mouse class with a link to a "mice" table. Example:
556
604
  #
557
605
  # class Mouse < ActiveRecord::Base
558
- # set_table_name "mice"
606
+ # set_table_name "mice"
559
607
  # end
560
608
  def table_name
561
609
  reset_table_name
562
610
  end
563
611
 
564
612
  def reset_table_name #:nodoc:
565
- name = "#{table_name_prefix}#{undecorated_table_name(base_class.name)}#{table_name_suffix}"
613
+ base = base_class
614
+
615
+ name =
616
+ # STI subclasses always use their superclass' table.
617
+ unless self == base
618
+ base.table_name
619
+ else
620
+ # Nested classes are prefixed with singular parent table name.
621
+ if parent < ActiveRecord::Base && !parent.abstract_class?
622
+ contained = parent.table_name
623
+ contained = contained.singularize if parent.pluralize_table_names
624
+ contained << '_'
625
+ end
626
+ name = "#{table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
627
+ end
628
+
566
629
  set_table_name(name)
567
630
  name
568
631
  end
@@ -585,9 +648,10 @@ module ActiveRecord #:nodoc:
585
648
  key
586
649
  end
587
650
 
588
- # Defines the column name for use with single table inheritance -- can be overridden in subclasses.
651
+ # Defines the column name for use with single table inheritance
652
+ # -- can be set in subclasses like so: self.inheritance_column = "type_id"
589
653
  def inheritance_column
590
- "type"
654
+ @inheritance_column ||= "type".freeze
591
655
  end
592
656
 
593
657
  # Lazy-set the sequence name to the connection's default. This method
@@ -699,7 +763,7 @@ module ActiveRecord #:nodoc:
699
763
  @columns
700
764
  end
701
765
 
702
- # Returns an array of column objects for the table associated with this class.
766
+ # Returns a hash of column objects for the table associated with this class.
703
767
  def columns_hash
704
768
  @columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash }
705
769
  end
@@ -737,7 +801,7 @@ module ActiveRecord #:nodoc:
737
801
  # Resets all the cached information about columns, which will cause them to be reloaded on the next request.
738
802
  def reset_column_information
739
803
  read_methods.each { |name| undef_method(name) }
740
- @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = nil
804
+ @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = @inheritance_column = nil
741
805
  end
742
806
 
743
807
  def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
@@ -755,9 +819,15 @@ module ActiveRecord #:nodoc:
755
819
  superclass == Base || !columns_hash.include?(inheritance_column)
756
820
  end
757
821
 
758
- def quote(object) #:nodoc:
759
- connection.quote(object)
822
+
823
+ def quote_value(value, column = nil) #:nodoc:
824
+ connection.quote(value,column)
825
+ end
826
+
827
+ def quote(value, column = nil) #:nodoc:
828
+ connection.quote(value, column)
760
829
  end
830
+ deprecate :quote => :quote_value
761
831
 
762
832
  # Used to sanitize objects before they're used in an SELECT SQL-statement. Delegates to <tt>connection.quote</tt>.
763
833
  def sanitize(object) #:nodoc:
@@ -837,7 +907,7 @@ module ActiveRecord #:nodoc:
837
907
  method_scoping.assert_valid_keys([ :find, :create ])
838
908
 
839
909
  if f = method_scoping[:find]
840
- f.assert_valid_keys([ :conditions, :joins, :select, :include, :from, :offset, :limit, :readonly ])
910
+ f.assert_valid_keys([ :conditions, :joins, :select, :include, :from, :offset, :limit, :order, :readonly, :lock ])
841
911
  f[:readonly] = true if !f[:joins].blank? && !f.has_key?(:readonly)
842
912
  end
843
913
 
@@ -917,7 +987,7 @@ module ActiveRecord #:nodoc:
917
987
  options.update(:limit => 1) unless options[:include]
918
988
  find_every(options).first
919
989
  end
920
-
990
+
921
991
  def find_every(options)
922
992
  records = scoped?(:find, :include) || options[:include] ?
923
993
  find_with_associations(options) :
@@ -927,11 +997,11 @@ module ActiveRecord #:nodoc:
927
997
 
928
998
  records
929
999
  end
930
-
1000
+
931
1001
  def find_from_ids(ids, options)
932
- expects_array = ids.first.kind_of?(Array)
1002
+ expects_array = ids.first.kind_of?(Array)
933
1003
  return ids.first if expects_array && ids.first.empty?
934
-
1004
+
935
1005
  ids = ids.flatten.compact.uniq
936
1006
 
937
1007
  case ids.size
@@ -947,9 +1017,12 @@ module ActiveRecord #:nodoc:
947
1017
 
948
1018
  def find_one(id, options)
949
1019
  conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
950
- options.update :conditions => "#{table_name}.#{primary_key} = #{sanitize(id)}#{conditions}"
1020
+ options.update :conditions => "#{table_name}.#{primary_key} = #{quote_value(id,columns_hash[primary_key])}#{conditions}"
951
1021
 
952
- if result = find_initial(options)
1022
+ # Use find_every(options).first since the primary key condition
1023
+ # already ensures we have a single record. Using find_initial adds
1024
+ # a superfluous :limit => 1.
1025
+ if result = find_every(options).first
953
1026
  result
954
1027
  else
955
1028
  raise RecordNotFound, "Couldn't find #{name} with ID=#{id}#{conditions}"
@@ -958,7 +1031,7 @@ module ActiveRecord #:nodoc:
958
1031
 
959
1032
  def find_some(ids, options)
960
1033
  conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
961
- ids_list = ids.map { |id| sanitize(id) }.join(',')
1034
+ ids_list = ids.map { |id| quote_value(id,columns_hash[primary_key]) }.join(',')
962
1035
  options.update :conditions => "#{table_name}.#{primary_key} IN (#{ids_list})#{conditions}"
963
1036
 
964
1037
  result = find_every(options)
@@ -970,23 +1043,32 @@ module ActiveRecord #:nodoc:
970
1043
  end
971
1044
  end
972
1045
 
973
- # Finder methods must instantiate through this method to work with the single-table inheritance model
974
- # that makes it possible to create objects of different types from the same table.
1046
+ # Finder methods must instantiate through this method to work with the
1047
+ # single-table inheritance model that makes it possible to create
1048
+ # objects of different types from the same table.
975
1049
  def instantiate(record)
976
- object =
1050
+ object =
977
1051
  if subclass_name = record[inheritance_column]
1052
+ # No type given.
978
1053
  if subclass_name.empty?
979
1054
  allocate
1055
+
980
1056
  else
981
- require_association_class(subclass_name)
982
- begin
983
- compute_type(subclass_name).allocate
984
- rescue NameError
985
- raise SubclassNotFound,
986
- "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
987
- "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
988
- "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
989
- "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
1057
+ # Ignore type if no column is present since it was probably
1058
+ # pulled in from a sloppy join.
1059
+ unless columns_hash.include?(inheritance_column)
1060
+ allocate
1061
+
1062
+ else
1063
+ begin
1064
+ compute_type(subclass_name).allocate
1065
+ rescue NameError
1066
+ raise SubclassNotFound,
1067
+ "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
1068
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
1069
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
1070
+ "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
1071
+ end
990
1072
  end
991
1073
  end
992
1074
  else
@@ -1012,19 +1094,20 @@ module ActiveRecord #:nodoc:
1012
1094
  add_conditions!(sql, options[:conditions], scope)
1013
1095
 
1014
1096
  sql << " GROUP BY #{options[:group]} " if options[:group]
1015
- sql << " ORDER BY #{options[:order]} " if options[:order]
1016
1097
 
1098
+ add_order!(sql, options[:order], scope)
1017
1099
  add_limit!(sql, options, scope)
1100
+ add_lock!(sql, options, scope)
1018
1101
 
1019
1102
  sql
1020
1103
  end
1021
1104
 
1022
1105
  # Merges includes so that the result is a valid +include+
1023
1106
  def merge_includes(first, second)
1024
- safe_to_array(first) + safe_to_array(second)
1107
+ (safe_to_array(first) + safe_to_array(second)).uniq
1025
1108
  end
1026
1109
 
1027
- # Object#to_a is deprecated, though it does have the desired behaviour
1110
+ # Object#to_a is deprecated, though it does have the desired behavior
1028
1111
  def safe_to_array(o)
1029
1112
  case o
1030
1113
  when NilClass
@@ -1036,16 +1119,32 @@ module ActiveRecord #:nodoc:
1036
1119
  end
1037
1120
  end
1038
1121
 
1122
+ def add_order!(sql, order, scope = :auto)
1123
+ scope = scope(:find) if :auto == scope
1124
+ scoped_order = scope[:order] if scope
1125
+ if order
1126
+ sql << " ORDER BY #{order}"
1127
+ sql << ", #{scoped_order}" if scoped_order
1128
+ else
1129
+ sql << " ORDER BY #{scoped_order}" if scoped_order
1130
+ end
1131
+ end
1132
+
1039
1133
  # The optional scope argument is for the current :find scope.
1040
1134
  def add_limit!(sql, options, scope = :auto)
1041
1135
  scope = scope(:find) if :auto == scope
1042
- if scope
1043
- options[:limit] ||= scope[:limit]
1044
- options[:offset] ||= scope[:offset]
1045
- end
1136
+ options = options.reverse_merge(:limit => scope[:limit], :offset => scope[:offset]) if scope
1046
1137
  connection.add_limit_offset!(sql, options)
1047
1138
  end
1048
1139
 
1140
+ # The optional scope argument is for the current :find scope.
1141
+ # The :lock option has precedence over a scoped :lock.
1142
+ def add_lock!(sql, options, scope = :auto)
1143
+ scope = scope(:find) if :auto == scope
1144
+ options = options.reverse_merge(:lock => scope[:lock]) if scope
1145
+ connection.add_lock!(sql, options)
1146
+ end
1147
+
1049
1148
  # The optional scope argument is for the current :find scope.
1050
1149
  def add_joins!(sql, options, scope = :auto)
1051
1150
  scope = scope(:find) if :auto == scope
@@ -1060,7 +1159,7 @@ module ActiveRecord #:nodoc:
1060
1159
  segments = []
1061
1160
  segments << sanitize_sql(scope[:conditions]) if scope && scope[:conditions]
1062
1161
  segments << sanitize_sql(conditions) unless conditions.nil?
1063
- segments << type_condition unless descends_from_active_record?
1162
+ segments << type_condition unless descends_from_active_record?
1064
1163
  segments.compact!
1065
1164
  sql << "WHERE (#{segments.join(") AND (")}) " unless segments.empty?
1066
1165
  end
@@ -1088,43 +1187,48 @@ module ActiveRecord #:nodoc:
1088
1187
  # It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
1089
1188
  # is actually find_all_by_amount(amount, options).
1090
1189
  def method_missing(method_id, *arguments)
1091
- if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s)
1190
+ if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
1092
1191
  finder, deprecated_finder = determine_finder(match), determine_deprecated_finder(match)
1093
1192
 
1094
1193
  attribute_names = extract_attribute_names_from_match(match)
1095
1194
  super unless all_attributes_exists?(attribute_names)
1096
1195
 
1097
- conditions = construct_conditions_from_arguments(attribute_names, arguments)
1196
+ attributes = construct_attributes_from_arguments(attribute_names, arguments)
1098
1197
 
1099
1198
  case extra_options = arguments[attribute_names.size]
1100
1199
  when nil
1101
- options = { :conditions => conditions }
1200
+ options = { :conditions => attributes }
1102
1201
  set_readonly_option!(options)
1103
- send(finder, options)
1202
+ ActiveSupport::Deprecation.silence { send(finder, options) }
1104
1203
 
1105
1204
  when Hash
1106
- finder_options = extra_options.merge(:conditions => conditions)
1205
+ finder_options = extra_options.merge(:conditions => attributes)
1107
1206
  validate_find_options(finder_options)
1108
1207
  set_readonly_option!(finder_options)
1109
1208
 
1110
1209
  if extra_options[:conditions]
1111
1210
  with_scope(:find => { :conditions => extra_options[:conditions] }) do
1112
- send(finder, finder_options)
1211
+ ActiveSupport::Deprecation.silence { send(finder, finder_options) }
1113
1212
  end
1114
1213
  else
1115
- send(finder, finder_options)
1214
+ ActiveSupport::Deprecation.silence { send(finder, finder_options) }
1116
1215
  end
1117
1216
 
1118
1217
  else
1119
- send(deprecated_finder, conditions, *arguments[attribute_names.length..-1]) # deprecated API
1218
+ ActiveSupport::Deprecation.silence do
1219
+ send(deprecated_finder, sanitize_sql(attributes), *arguments[attribute_names.length..-1])
1220
+ end
1120
1221
  end
1121
- elsif match = /find_or_create_by_([_a-zA-Z]\w*)/.match(method_id.to_s)
1222
+ elsif match = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)
1223
+ instantiator = determine_instantiator(match)
1122
1224
  attribute_names = extract_attribute_names_from_match(match)
1123
1225
  super unless all_attributes_exists?(attribute_names)
1124
1226
 
1125
- options = { :conditions => construct_conditions_from_arguments(attribute_names, arguments) }
1227
+ attributes = construct_attributes_from_arguments(attribute_names, arguments)
1228
+ options = { :conditions => attributes }
1126
1229
  set_readonly_option!(options)
1127
- find_initial(options) || create(construct_attributes_from_arguments(attribute_names, arguments))
1230
+
1231
+ find_initial(options) || send(instantiator, attributes)
1128
1232
  else
1129
1233
  super
1130
1234
  end
@@ -1138,16 +1242,14 @@ module ActiveRecord #:nodoc:
1138
1242
  match.captures.first == 'all_by' ? :find_all : :find_first
1139
1243
  end
1140
1244
 
1245
+ def determine_instantiator(match)
1246
+ match.captures.first == 'initialize' ? :new : :create
1247
+ end
1248
+
1141
1249
  def extract_attribute_names_from_match(match)
1142
1250
  match.captures.last.split('_and_')
1143
1251
  end
1144
1252
 
1145
- def construct_conditions_from_arguments(attribute_names, arguments)
1146
- conditions = []
1147
- attribute_names.each_with_index { |name, idx| conditions << "#{table_name}.#{connection.quote_column_name(name)} #{attribute_condition(arguments[idx])} " }
1148
- [ conditions.join(" AND "), *arguments[0...attribute_names.length] ]
1149
- end
1150
-
1151
1253
  def construct_attributes_from_arguments(attribute_names, arguments)
1152
1254
  attributes = {}
1153
1255
  attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
@@ -1166,6 +1268,15 @@ module ActiveRecord #:nodoc:
1166
1268
  end
1167
1269
  end
1168
1270
 
1271
+ # Interpret Array and Hash as conditions and anything else as an id.
1272
+ def expand_id_conditions(id_or_conditions)
1273
+ case id_or_conditions
1274
+ when Array, Hash then id_or_conditions
1275
+ else sanitize_sql(primary_key => id_or_conditions)
1276
+ end
1277
+ end
1278
+
1279
+
1169
1280
  # Defines an "attribute" method (like #inheritance_column or
1170
1281
  # #table_name). A new (class) method will be created with the
1171
1282
  # given name. If a value is specified, the new method will
@@ -1241,9 +1352,9 @@ module ActiveRecord #:nodoc:
1241
1352
  def compute_type(type_name)
1242
1353
  modularized_name = type_name_with_module(type_name)
1243
1354
  begin
1244
- instance_eval(modularized_name)
1245
- rescue NameError => e
1246
- instance_eval(type_name)
1355
+ class_eval(modularized_name, __FILE__, __LINE__)
1356
+ rescue NameError
1357
+ class_eval(type_name, __FILE__, __LINE__)
1247
1358
  end
1248
1359
  end
1249
1360
 
@@ -1263,12 +1374,36 @@ module ActiveRecord #:nodoc:
1263
1374
  klass.base_class.name
1264
1375
  end
1265
1376
 
1266
- # Accepts an array or string. The string is returned untouched, but the array has each value
1267
- # sanitized and interpolated into the sql statement.
1377
+ # Accepts an array, hash, or string of sql conditions and sanitizes
1378
+ # them into a valid SQL fragment.
1268
1379
  # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
1269
- def sanitize_sql(ary)
1270
- return ary unless ary.is_a?(Array)
1380
+ # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
1381
+ # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
1382
+ def sanitize_sql(condition)
1383
+ case condition
1384
+ when Array; sanitize_sql_array(condition)
1385
+ when Hash; sanitize_sql_hash(condition)
1386
+ else condition
1387
+ end
1388
+ end
1271
1389
 
1390
+ # Sanitizes a hash of attribute/value pairs into SQL conditions.
1391
+ # { :name => "foo'bar", :group_id => 4 }
1392
+ # # => "name='foo''bar' and group_id= 4"
1393
+ # { :status => nil, :group_id => [1,2,3] }
1394
+ # # => "status IS NULL and group_id IN (1,2,3)"
1395
+ def sanitize_sql_hash(attrs)
1396
+ conditions = attrs.map do |attr, value|
1397
+ "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
1398
+ end.join(' AND ')
1399
+
1400
+ replace_bind_variables(conditions, attrs.values)
1401
+ end
1402
+
1403
+ # Accepts an array of conditions. The array has each value
1404
+ # sanitized and interpolated into the sql statement.
1405
+ # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
1406
+ def sanitize_sql_array(ary)
1272
1407
  statement, *values = ary
1273
1408
  if values.first.is_a?(Hash) and statement =~ /:\w+/
1274
1409
  replace_named_bind_variables(statement, values.first)
@@ -1299,8 +1434,12 @@ module ActiveRecord #:nodoc:
1299
1434
  end
1300
1435
 
1301
1436
  def quote_bound_value(value) #:nodoc:
1302
- if (value.respond_to?(:map) && !value.is_a?(String))
1303
- value.map { |v| connection.quote(v) }.join(',')
1437
+ if value.respond_to?(:map) && !value.is_a?(String)
1438
+ if value.respond_to?(:empty?) && value.empty?
1439
+ connection.quote(nil)
1440
+ else
1441
+ value.map { |v| connection.quote(v) }.join(',')
1442
+ end
1304
1443
  else
1305
1444
  connection.quote(value)
1306
1445
  end
@@ -1317,12 +1456,12 @@ module ActiveRecord #:nodoc:
1317
1456
  end
1318
1457
 
1319
1458
  VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
1320
- :order, :select, :readonly, :group, :from ]
1321
-
1459
+ :order, :select, :readonly, :group, :from, :lock ]
1460
+
1322
1461
  def validate_find_options(options) #:nodoc:
1323
1462
  options.assert_valid_keys(VALID_FIND_OPTIONS)
1324
1463
  end
1325
-
1464
+
1326
1465
  def set_readonly_option!(options) #:nodoc:
1327
1466
  # Inherit :readonly from finder scope if set. Otherwise,
1328
1467
  # if :joins is not blank then :readonly defaults to true.
@@ -1365,14 +1504,17 @@ module ActiveRecord #:nodoc:
1365
1504
  end
1366
1505
 
1367
1506
  # Enables Active Record objects to be used as URL parameters in Action Pack automatically.
1368
- alias_method :to_param, :id
1507
+ def to_param
1508
+ # We can't use alias_method here, because method 'id' optimizes itself on the fly.
1509
+ (id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
1510
+ end
1369
1511
 
1370
1512
  def id_before_type_cast #:nodoc:
1371
1513
  read_attribute_before_type_cast(self.class.primary_key)
1372
1514
  end
1373
1515
 
1374
1516
  def quoted_id #:nodoc:
1375
- quote(id, column_for_attribute(self.class.primary_key))
1517
+ quote_value(id, column_for_attribute(self.class.primary_key))
1376
1518
  end
1377
1519
 
1378
1520
  # Sets the primary ID.
@@ -1388,14 +1530,13 @@ module ActiveRecord #:nodoc:
1388
1530
  # * No record exists: Creates a new record with values matching those of the object attributes.
1389
1531
  # * A record does exist: Updates the record with values matching those of the object attributes.
1390
1532
  def save
1391
- raise ReadOnlyRecord if readonly?
1392
1533
  create_or_update
1393
1534
  end
1394
1535
 
1395
1536
  # Attempts to save the record, but instead of just returning false if it couldn't happen, it raises a
1396
1537
  # RecordNotSaved exception
1397
1538
  def save!
1398
- save || raise(RecordNotSaved)
1539
+ create_or_update || raise(RecordNotSaved)
1399
1540
  end
1400
1541
 
1401
1542
  # Deletes the record in the database and freezes this instance to reflect that no changes should
@@ -1438,6 +1579,12 @@ module ActiveRecord #:nodoc:
1438
1579
  self.attributes = attributes
1439
1580
  save
1440
1581
  end
1582
+
1583
+ # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
1584
+ def update_attributes!(attributes)
1585
+ self.attributes = attributes
1586
+ save!
1587
+ end
1441
1588
 
1442
1589
  # Initializes the +attribute+ to zero if nil and adds one. Only makes sense for number-based attributes. Returns self.
1443
1590
  def increment(attribute)
@@ -1475,10 +1622,13 @@ module ActiveRecord #:nodoc:
1475
1622
  end
1476
1623
 
1477
1624
  # Reloads the attributes of this object from the database.
1478
- def reload
1625
+ # The optional options argument is passed to find when reloading so you
1626
+ # may do e.g. record.reload(:lock => true) to reload the same record with
1627
+ # an exclusive row lock.
1628
+ def reload(options = nil)
1479
1629
  clear_aggregation_cache
1480
1630
  clear_association_cache
1481
- @attributes.update(self.class.find(self.id).instance_variable_get('@attributes'))
1631
+ @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
1482
1632
  self
1483
1633
  end
1484
1634
 
@@ -1588,13 +1738,13 @@ module ActiveRecord #:nodoc:
1588
1738
  # person.respond_to?("name?") which will all return true.
1589
1739
  def respond_to?(method, include_priv = false)
1590
1740
  if @attributes.nil?
1591
- return super
1741
+ return super
1592
1742
  elsif attr_name = self.class.column_methods_hash[method.to_sym]
1593
1743
  return true if @attributes.include?(attr_name) || attr_name == self.class.primary_key
1594
1744
  return false if self.class.read_methods.include?(attr_name)
1595
1745
  elsif @attributes.include?(method_name = method.to_s)
1596
1746
  return true
1597
- elsif md = /(=|\?|_before_type_cast)$/.match(method_name)
1747
+ elsif md = self.class.match_attribute_method?(method.to_s)
1598
1748
  return true if @attributes.include?(md.pre_match)
1599
1749
  end
1600
1750
  # super must be called at the end of the method, because the inherited respond_to?
@@ -1620,117 +1770,27 @@ module ActiveRecord #:nodoc:
1620
1770
  @readonly = true
1621
1771
  end
1622
1772
 
1623
- # Builds an XML document to represent the model. Some configuration is
1624
- # availble through +options+, however more complicated cases should use
1625
- # Builder.
1626
- #
1627
- # By default the generated XML document will include the processing
1628
- # instruction and all object's attributes. For example:
1629
- #
1630
- # <?xml version="1.0" encoding="UTF-8"?>
1631
- # <topic>
1632
- # <title>The First Topic</title>
1633
- # <author-name>David</author-name>
1634
- # <id type="integer">1</id>
1635
- # <approved type="boolean">false</approved>
1636
- # <replies-count type="integer">0</replies-count>
1637
- # <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time>
1638
- # <written-on type="datetime">2003-07-16T09:28:00+1200</written-on>
1639
- # <content>Have a nice day</content>
1640
- # <author-email-address>david@loudthinking.com</author-email-address>
1641
- # <parent-id></parent-id>
1642
- # <last-read type="date">2004-04-15</last-read>
1643
- # </topic>
1644
- #
1645
- # This behaviour can be controlled with :only, :except, and :skip_instruct
1646
- # for instance:
1647
- #
1648
- # topic.to_xml(:skip_instruct => true, :except => [ :id, bonus_time, :written_on, replies_count ])
1649
- #
1650
- # <topic>
1651
- # <title>The First Topic</title>
1652
- # <author-name>David</author-name>
1653
- # <approved type="boolean">false</approved>
1654
- # <content>Have a nice day</content>
1655
- # <author-email-address>david@loudthinking.com</author-email-address>
1656
- # <parent-id></parent-id>
1657
- # <last-read type="date">2004-04-15</last-read>
1658
- # </topic>
1659
- #
1660
- # To include first level associations use :include
1661
- #
1662
- # firm.to_xml :include => [ :account, :clients ]
1663
- #
1664
- # <?xml version="1.0" encoding="UTF-8"?>
1665
- # <firm>
1666
- # <id type="integer">1</id>
1667
- # <rating type="integer">1</rating>
1668
- # <name>37signals</name>
1669
- # <clients>
1670
- # <client>
1671
- # <rating type="integer">1</rating>
1672
- # <name>Summit</name>
1673
- # </client>
1674
- # <client>
1675
- # <rating type="integer">1</rating>
1676
- # <name>Microsoft</name>
1677
- # </client>
1678
- # </clients>
1679
- # <account>
1680
- # <id type="integer">1</id>
1681
- # <credit-limit type="integer">50</credit-limit>
1682
- # </account>
1683
- # </firm>
1684
- def to_xml(options = {})
1685
- options[:root] ||= self.class.to_s.underscore
1686
- options[:except] = Array(options[:except]) << self.class.inheritance_column unless options[:only] # skip type column
1687
- root_only_or_except = { :only => options[:only], :except => options[:except] }
1688
-
1689
- attributes_for_xml = attributes(root_only_or_except)
1690
-
1691
- if include_associations = options.delete(:include)
1692
- include_has_options = include_associations.is_a?(Hash)
1693
-
1694
- for association in include_has_options ? include_associations.keys : Array(include_associations)
1695
- association_options = include_has_options ? include_associations[association] : root_only_or_except
1696
-
1697
- case self.class.reflect_on_association(association).macro
1698
- when :has_many, :has_and_belongs_to_many
1699
- records = send(association).to_a
1700
- unless records.empty?
1701
- attributes_for_xml[association] = records.collect do |record|
1702
- record.attributes(association_options)
1703
- end
1704
- end
1705
- when :has_one, :belongs_to
1706
- if record = send(association)
1707
- attributes_for_xml[association] = record.attributes(association_options)
1708
- end
1709
- end
1710
- end
1711
- end
1712
-
1713
- attributes_for_xml.to_xml(options)
1714
- end
1715
1773
 
1716
1774
  private
1717
1775
  def create_or_update
1718
- if new_record? then create else update end
1776
+ raise ReadOnlyRecord if readonly?
1777
+ result = new_record? ? create : update
1778
+ result != false
1719
1779
  end
1720
1780
 
1721
1781
  # Updates the associated record with values matching those of the instance attributes.
1782
+ # Returns the number of affected rows.
1722
1783
  def update
1723
1784
  connection.update(
1724
1785
  "UPDATE #{self.class.table_name} " +
1725
1786
  "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +
1726
- "WHERE #{self.class.primary_key} = #{quote(id)}",
1787
+ "WHERE #{self.class.primary_key} = #{quote_value(id)}",
1727
1788
  "#{self.class.name} Update"
1728
1789
  )
1729
-
1730
- return true
1731
1790
  end
1732
1791
 
1733
- # Creates a new record with values matching those of the instance attributes.
1792
+ # Creates a record with values matching those of the instance attributes
1793
+ # and returns its id.
1734
1794
  def create
1735
1795
  if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
1736
1796
  self.id = connection.next_sequence_value(self.class.sequence_name)
@@ -1745,8 +1805,7 @@ module ActiveRecord #:nodoc:
1745
1805
  )
1746
1806
 
1747
1807
  @new_record = false
1748
-
1749
- return true
1808
+ id
1750
1809
  end
1751
1810
 
1752
1811
  # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendent.
@@ -1759,6 +1818,7 @@ module ActiveRecord #:nodoc:
1759
1818
  end
1760
1819
  end
1761
1820
 
1821
+
1762
1822
  # Allows access to the object attributes, which are held in the @attributes hash, as were
1763
1823
  # they first-class methods. So a Person class with a name attribute can use Person#name and
1764
1824
  # Person#name= and never directly use the attributes hash -- except for multiple assigns with
@@ -1771,20 +1831,16 @@ module ActiveRecord #:nodoc:
1771
1831
  method_name = method_id.to_s
1772
1832
  if @attributes.include?(method_name) or
1773
1833
  (md = /\?$/.match(method_name) and
1774
- @attributes.include?(method_name = md.pre_match))
1834
+ @attributes.include?(query_method_name = md.pre_match) and
1835
+ method_name = query_method_name)
1775
1836
  define_read_methods if self.class.read_methods.empty? && self.class.generate_read_methods
1776
1837
  md ? query_attribute(method_name) : read_attribute(method_name)
1777
1838
  elsif self.class.primary_key.to_s == method_name
1778
1839
  id
1779
- elsif md = /(=|_before_type_cast)$/.match(method_name)
1840
+ elsif md = self.class.match_attribute_method?(method_name)
1780
1841
  attribute_name, method_type = md.pre_match, md.to_s
1781
1842
  if @attributes.include?(attribute_name)
1782
- case method_type
1783
- when '='
1784
- write_attribute(attribute_name, args.first)
1785
- when '_before_type_cast'
1786
- read_attribute_before_type_cast(attribute_name)
1787
- end
1843
+ __send__("attribute#{method_type}", attribute_name, *args, &block)
1788
1844
  else
1789
1845
  super
1790
1846
  end
@@ -1821,9 +1877,16 @@ module ActiveRecord #:nodoc:
1821
1877
  # ActiveRecord::Base.generate_read_methods is set to true.
1822
1878
  def define_read_methods
1823
1879
  self.class.columns_hash.each do |name, column|
1824
- unless self.class.serialized_attributes[name]
1825
- define_read_method(name.to_sym, name, column) unless respond_to_without_attributes?(name)
1826
- define_question_method(name) unless respond_to_without_attributes?("#{name}?")
1880
+ unless respond_to_without_attributes?(name)
1881
+ if self.class.serialized_attributes[name]
1882
+ define_read_method_for_serialized_attribute(name)
1883
+ else
1884
+ define_read_method(name.to_sym, name, column)
1885
+ end
1886
+ end
1887
+
1888
+ unless respond_to_without_attributes?("#{name}?")
1889
+ define_question_method(name)
1827
1890
  end
1828
1891
  end
1829
1892
  end
@@ -1841,6 +1904,15 @@ module ActiveRecord #:nodoc:
1841
1904
  evaluate_read_method attr_name, "def #{symbol}; #{access_code}; end"
1842
1905
  end
1843
1906
 
1907
+ # Define read method for serialized attribute.
1908
+ def define_read_method_for_serialized_attribute(attr_name)
1909
+ unless attr_name.to_s == self.class.primary_key.to_s
1910
+ self.class.read_methods << attr_name
1911
+ end
1912
+
1913
+ evaluate_read_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end"
1914
+ end
1915
+
1844
1916
  # Define an attribute ? method.
1845
1917
  def define_question_method(attr_name)
1846
1918
  unless attr_name.to_s == self.class.primary_key.to_s
@@ -1857,7 +1929,7 @@ module ActiveRecord #:nodoc:
1857
1929
  rescue SyntaxError => err
1858
1930
  self.class.read_methods.delete(attr_name)
1859
1931
  if logger
1860
- logger.warn "Exception occured during reader method compilation."
1932
+ logger.warn "Exception occurred during reader method compilation."
1861
1933
  logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
1862
1934
  logger.warn "#{err.message}"
1863
1935
  end
@@ -1944,17 +2016,24 @@ module ActiveRecord #:nodoc:
1944
2016
  def attributes_with_quotes(include_primary_key = true)
1945
2017
  attributes.inject({}) do |quoted, (name, value)|
1946
2018
  if column = column_for_attribute(name)
1947
- quoted[name] = quote(value, column) unless !include_primary_key && column.primary
2019
+ quoted[name] = quote_value(value, column) unless !include_primary_key && column.primary
1948
2020
  end
1949
2021
  quoted
1950
2022
  end
1951
2023
  end
1952
2024
 
1953
2025
  # Quote strings appropriately for SQL statements.
1954
- def quote(value, column = nil)
2026
+ def quote_value(value, column = nil)
1955
2027
  self.class.connection.quote(value, column)
1956
2028
  end
1957
2029
 
2030
+ # Deprecated, use quote_value
2031
+ def quote(value, column = nil)
2032
+ self.class.connection.quote(value, column)
2033
+ end
2034
+ deprecate :quote => :quote_value
2035
+
2036
+
1958
2037
  # Interpolate custom sql string in instance context.
1959
2038
  # Optional record argument is meant for custom insert_sql.
1960
2039
  def interpolate_sql(sql, record = nil)
@@ -1993,7 +2072,7 @@ module ActiveRecord #:nodoc:
1993
2072
  send(name + "=", nil)
1994
2073
  else
1995
2074
  begin
1996
- send(name + "=", Time == klass ? klass.local(*values) : klass.new(*values))
2075
+ send(name + "=", Time == klass ? (@@default_timezone == :utc ? klass.utc(*values) : klass.local(*values)) : klass.new(*values))
1997
2076
  rescue => ex
1998
2077
  errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
1999
2078
  end