activerecord 2.1.2 → 2.2.2

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 (110) hide show
  1. data/CHANGELOG +32 -6
  2. data/README +0 -0
  3. data/Rakefile +4 -5
  4. data/lib/active_record.rb +11 -10
  5. data/lib/active_record/aggregations.rb +110 -38
  6. data/lib/active_record/association_preload.rb +104 -15
  7. data/lib/active_record/associations.rb +427 -212
  8. data/lib/active_record/associations/association_collection.rb +101 -16
  9. data/lib/active_record/associations/association_proxy.rb +65 -13
  10. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -0
  12. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +13 -3
  13. data/lib/active_record/associations/has_many_association.rb +28 -28
  14. data/lib/active_record/associations/has_many_through_association.rb +21 -19
  15. data/lib/active_record/associations/has_one_association.rb +24 -7
  16. data/lib/active_record/associations/has_one_through_association.rb +3 -4
  17. data/lib/active_record/attribute_methods.rb +13 -5
  18. data/lib/active_record/base.rb +435 -212
  19. data/lib/active_record/calculations.rb +12 -5
  20. data/lib/active_record/callbacks.rb +28 -9
  21. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +355 -0
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +42 -215
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -5
  24. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
  25. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +48 -7
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +10 -4
  27. data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -26
  28. data/lib/active_record/connection_adapters/mysql_adapter.rb +71 -45
  29. data/lib/active_record/connection_adapters/postgresql_adapter.rb +155 -84
  30. data/lib/active_record/dirty.rb +25 -7
  31. data/lib/active_record/dynamic_finder_match.rb +41 -0
  32. data/lib/active_record/fixtures.rb +10 -9
  33. data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
  34. data/lib/active_record/locale/en.yml +54 -0
  35. data/lib/active_record/migration.rb +47 -10
  36. data/lib/active_record/named_scope.rb +29 -16
  37. data/lib/active_record/reflection.rb +118 -54
  38. data/lib/active_record/schema_dumper.rb +13 -7
  39. data/lib/active_record/test_case.rb +18 -5
  40. data/lib/active_record/transactions.rb +89 -34
  41. data/lib/active_record/validations.rb +270 -180
  42. data/lib/active_record/version.rb +1 -1
  43. data/test/cases/active_schema_test_mysql.rb +5 -0
  44. data/test/cases/adapter_test.rb +6 -0
  45. data/test/cases/aggregations_test.rb +39 -0
  46. data/test/cases/associations/belongs_to_associations_test.rb +10 -0
  47. data/test/cases/associations/eager_load_nested_include_test.rb +30 -12
  48. data/test/cases/associations/eager_test.rb +54 -5
  49. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +77 -10
  50. data/test/cases/associations/has_many_associations_test.rb +74 -7
  51. data/test/cases/associations/has_many_through_associations_test.rb +50 -3
  52. data/test/cases/associations/has_one_associations_test.rb +17 -0
  53. data/test/cases/associations/has_one_through_associations_test.rb +49 -1
  54. data/test/cases/associations_test.rb +0 -0
  55. data/test/cases/attribute_methods_test.rb +59 -4
  56. data/test/cases/base_test.rb +93 -21
  57. data/test/cases/binary_test.rb +1 -5
  58. data/test/cases/calculations_test.rb +5 -0
  59. data/test/cases/callbacks_observers_test.rb +38 -0
  60. data/test/cases/connection_test_mysql.rb +1 -1
  61. data/test/cases/defaults_test.rb +32 -1
  62. data/test/cases/deprecated_finder_test.rb +0 -0
  63. data/test/cases/dirty_test.rb +13 -0
  64. data/test/cases/finder_test.rb +162 -12
  65. data/test/cases/fixtures_test.rb +32 -3
  66. data/test/cases/helper.rb +15 -0
  67. data/test/cases/i18n_test.rb +41 -0
  68. data/test/cases/inheritance_test.rb +2 -2
  69. data/test/cases/lifecycle_test.rb +0 -0
  70. data/test/cases/locking_test.rb +4 -9
  71. data/test/cases/method_scoping_test.rb +109 -2
  72. data/test/cases/migration_test.rb +43 -8
  73. data/test/cases/multiple_db_test.rb +25 -0
  74. data/test/cases/named_scope_test.rb +74 -0
  75. data/test/cases/pooled_connections_test.rb +103 -0
  76. data/test/cases/readonly_test.rb +0 -0
  77. data/test/cases/reflection_test.rb +11 -3
  78. data/test/cases/reload_models_test.rb +20 -0
  79. data/test/cases/sanitize_test.rb +25 -0
  80. data/test/cases/schema_authorization_test_postgresql.rb +2 -2
  81. data/test/cases/transactions_test.rb +62 -12
  82. data/test/cases/unconnected_test.rb +0 -0
  83. data/test/cases/validations_i18n_test.rb +921 -0
  84. data/test/cases/validations_test.rb +44 -33
  85. data/test/connections/native_mysql/connection.rb +1 -3
  86. data/test/fixtures/companies.yml +1 -0
  87. data/test/fixtures/customers.yml +10 -1
  88. data/test/fixtures/fixture_database.sqlite3 +0 -0
  89. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  90. data/test/fixtures/organizations.yml +5 -0
  91. data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
  92. data/test/models/author.rb +3 -0
  93. data/test/models/category.rb +3 -0
  94. data/test/models/club.rb +6 -0
  95. data/test/models/company.rb +25 -1
  96. data/test/models/customer.rb +19 -1
  97. data/test/models/member.rb +2 -0
  98. data/test/models/member_detail.rb +4 -0
  99. data/test/models/organization.rb +4 -0
  100. data/test/models/parrot.rb +1 -0
  101. data/test/models/post.rb +3 -0
  102. data/test/models/reply.rb +0 -0
  103. data/test/models/topic.rb +3 -0
  104. data/test/schema/schema.rb +12 -1
  105. metadata +22 -10
  106. data/lib/active_record/vendor/mysql.rb +0 -1214
  107. data/test/cases/adapter_test_sqlserver.rb +0 -95
  108. data/test/cases/table_name_test_sqlserver.rb +0 -23
  109. data/test/cases/threaded_connections_test.rb +0 -48
  110. data/test/schema/sqlserver_specific_schema.rb +0 -5
@@ -37,7 +37,7 @@ module ActiveRecord
37
37
  attributes = columns.inject({}) do |attrs, column|
38
38
  case column.name.to_s
39
39
  when @reflection.primary_key_name.to_s
40
- attrs[column.name] = @owner.quoted_id
40
+ attrs[column.name] = owner_quoted_id
41
41
  when @reflection.association_foreign_key.to_s
42
42
  attrs[column.name] = record.quoted_id
43
43
  else
@@ -64,7 +64,7 @@ module ActiveRecord
64
64
  records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
65
65
  else
66
66
  ids = quoted_record_ids(records)
67
- sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
67
+ sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
68
68
  @owner.connection.delete(sql)
69
69
  end
70
70
  end
@@ -73,11 +73,21 @@ module ActiveRecord
73
73
  if @reflection.options[:finder_sql]
74
74
  @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
75
75
  else
76
- @finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
76
+ @finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
77
77
  @finder_sql << " AND (#{conditions})" if conditions
78
78
  end
79
79
 
80
80
  @join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
81
+
82
+ if @reflection.options[:counter_sql]
83
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
84
+ elsif @reflection.options[:finder_sql]
85
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
86
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
87
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
88
+ else
89
+ @counter_sql = @finder_sql
90
+ end
81
91
  end
82
92
 
83
93
  def construct_scope
@@ -1,33 +1,32 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
+ # This is the proxy that handles a has many association.
4
+ #
5
+ # If the association has a <tt>:through</tt> option further specialization
6
+ # is provided by its child HasManyThroughAssociation.
3
7
  class HasManyAssociation < AssociationCollection #:nodoc:
4
- # Count the number of associated records. All arguments are optional.
5
- def count(*args)
6
- if @reflection.options[:counter_sql]
7
- @reflection.klass.count_by_sql(@counter_sql)
8
- elsif @reflection.options[:finder_sql]
9
- @reflection.klass.count_by_sql(@finder_sql)
10
- else
11
- column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
12
- options[:conditions] = options[:conditions].blank? ?
13
- @finder_sql :
14
- @finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
15
- options[:include] ||= @reflection.options[:include]
16
-
17
- value = @reflection.klass.count(column_name, options)
18
-
19
- limit = @reflection.options[:limit]
20
- offset = @reflection.options[:offset]
21
-
22
- if limit || offset
23
- [ [value - offset.to_i, 0].max, limit.to_i ].min
8
+ protected
9
+ def owner_quoted_id
10
+ if @reflection.options[:primary_key]
11
+ quote_value(@owner.send(@reflection.options[:primary_key]))
24
12
  else
25
- value
13
+ @owner.quoted_id
26
14
  end
27
15
  end
28
- end
29
16
 
30
- protected
17
+ # Returns the number of records in this collection.
18
+ #
19
+ # If the association has a counter cache it gets that value. Otherwise
20
+ # it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
21
+ # there's one. Some configuration options like :group make it impossible
22
+ # to do a SQL count, in those cases the array count will be used.
23
+ #
24
+ # That does not depend on whether the collection has already been loaded
25
+ # or not. The +size+ method is the one that takes the loaded flag into
26
+ # account and delegates to +count_records+ if needed.
27
+ #
28
+ # If the collection is empty the target is set to an empty array and
29
+ # the loaded flag is set to true as well.
31
30
  def count_records
32
31
  count = if has_cached_counter?
33
32
  @owner.send(:read_attribute, cached_counter_attribute_name)
@@ -62,17 +61,18 @@ module ActiveRecord
62
61
  record.save
63
62
  end
64
63
 
64
+ # Deletes the records according to the <tt>:dependent</tt> option.
65
65
  def delete_records(records)
66
66
  case @reflection.options[:dependent]
67
67
  when :destroy
68
- records.each(&:destroy)
68
+ records.each { |r| r.destroy }
69
69
  when :delete_all
70
- @reflection.klass.delete(records.map(&:id))
70
+ @reflection.klass.delete(records.map { |record| record.id })
71
71
  else
72
72
  ids = quoted_record_ids(records)
73
73
  @reflection.klass.update_all(
74
74
  "#{@reflection.primary_key_name} = NULL",
75
- "#{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
75
+ "#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
76
76
  )
77
77
  end
78
78
  end
@@ -88,12 +88,12 @@ module ActiveRecord
88
88
 
89
89
  when @reflection.options[:as]
90
90
  @finder_sql =
91
- "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
91
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
92
92
  "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
93
93
  @finder_sql << " AND (#{conditions})" if conditions
94
94
 
95
95
  else
96
- @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
96
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
97
97
  @finder_sql << " AND (#{conditions})" if conditions
98
98
  end
99
99
 
@@ -9,15 +9,15 @@ module ActiveRecord
9
9
  alias_method :new, :build
10
10
 
11
11
  def create!(attrs = nil)
12
- @reflection.klass.transaction do
13
- self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create! } : @reflection.klass.create!)
12
+ transaction do
13
+ self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association! } : @reflection.create_association!)
14
14
  object
15
15
  end
16
16
  end
17
17
 
18
18
  def create(attrs = nil)
19
- @reflection.klass.transaction do
20
- self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create } : @reflection.klass.create)
19
+ transaction do
20
+ self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association)
21
21
  object
22
22
  end
23
23
  end
@@ -31,17 +31,15 @@ module ActiveRecord
31
31
  return count
32
32
  end
33
33
 
34
- def count(*args)
35
- column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
36
- if @reflection.options[:uniq]
37
- # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL statement.
38
- column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
39
- options.merge!(:distinct => true)
40
- end
41
- @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
42
- end
43
-
44
34
  protected
35
+ def target_reflection_has_associated_record?
36
+ if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank?
37
+ false
38
+ else
39
+ true
40
+ end
41
+ end
42
+
45
43
  def construct_find_options!(options)
46
44
  options[:select] = construct_select(options[:select])
47
45
  options[:from] ||= construct_from
@@ -57,8 +55,9 @@ module ActiveRecord
57
55
  return false unless record.save
58
56
  end
59
57
  end
60
- klass = @reflection.through_reflection.klass
61
- @owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { klass.create! }
58
+ through_reflection = @reflection.through_reflection
59
+ klass = through_reflection.klass
60
+ @owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { through_reflection.create_association! }
62
61
  end
63
62
 
64
63
  # TODO - add dependent option support
@@ -70,6 +69,7 @@ module ActiveRecord
70
69
  end
71
70
 
72
71
  def find_target
72
+ return [] unless target_reflection_has_associated_record?
73
73
  @reflection.klass.find(:all,
74
74
  :select => construct_select,
75
75
  :conditions => construct_conditions,
@@ -107,12 +107,14 @@ module ActiveRecord
107
107
  # Associate attributes pointing to owner, quoted.
108
108
  def construct_quoted_owner_attributes(reflection)
109
109
  if as = reflection.options[:as]
110
- { "#{as}_id" => @owner.quoted_id,
110
+ { "#{as}_id" => owner_quoted_id,
111
111
  "#{as}_type" => reflection.klass.quote_value(
112
112
  @owner.class.base_class.name.to_s,
113
113
  reflection.klass.columns_hash["#{as}_type"]) }
114
+ elsif reflection.macro == :belongs_to
115
+ { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
114
116
  else
115
- { reflection.primary_key_name => @owner.quoted_id }
117
+ { reflection.primary_key_name => owner_quoted_id }
116
118
  end
117
119
  end
118
120
 
@@ -183,7 +185,7 @@ module ActiveRecord
183
185
  when @reflection.options[:finder_sql]
184
186
  @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
185
187
 
186
- @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
188
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
187
189
  @finder_sql << " AND (#{conditions})" if conditions
188
190
  else
189
191
  @finder_sql = construct_conditions
@@ -7,15 +7,21 @@ module ActiveRecord
7
7
  end
8
8
 
9
9
  def create(attrs = {}, replace_existing = true)
10
- new_record(replace_existing) { |klass| klass.create(attrs) }
10
+ new_record(replace_existing) do |reflection|
11
+ reflection.create_association(attrs)
12
+ end
11
13
  end
12
14
 
13
15
  def create!(attrs = {}, replace_existing = true)
14
- new_record(replace_existing) { |klass| klass.create!(attrs) }
16
+ new_record(replace_existing) do |reflection|
17
+ reflection.create_association!(attrs)
18
+ end
15
19
  end
16
20
 
17
21
  def build(attrs = {}, replace_existing = true)
18
- new_record(replace_existing) { |klass| klass.new(attrs) }
22
+ new_record(replace_existing) do |reflection|
23
+ reflection.build_association(attrs)
24
+ end
19
25
  end
20
26
 
21
27
  def replace(obj, dont_save = false)
@@ -47,7 +53,16 @@ module ActiveRecord
47
53
  return (obj.nil? ? nil : self)
48
54
  end
49
55
  end
50
-
56
+
57
+ protected
58
+ def owner_quoted_id
59
+ if @reflection.options[:primary_key]
60
+ @owner.class.quote_value(@owner.send(@reflection.options[:primary_key]))
61
+ else
62
+ @owner.quoted_id
63
+ end
64
+ end
65
+
51
66
  private
52
67
  def find_target
53
68
  @reflection.klass.find(:first,
@@ -63,10 +78,10 @@ module ActiveRecord
63
78
  case
64
79
  when @reflection.options[:as]
65
80
  @finder_sql =
66
- "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
81
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
67
82
  "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
68
83
  else
69
- @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
84
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
70
85
  end
71
86
  @finder_sql << " AND (#{conditions})" if conditions
72
87
  end
@@ -82,7 +97,9 @@ module ActiveRecord
82
97
  # instance. Otherwise, if the target has not previously been loaded
83
98
  # elsewhere, the instance we create will get orphaned.
84
99
  load_target if replace_existing
85
- record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { yield @reflection.klass }
100
+ record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) do
101
+ yield @reflection
102
+ end
86
103
 
87
104
  if replace_existing
88
105
  replace(record, true)
@@ -8,11 +8,10 @@ module ActiveRecord
8
8
  current_object = @owner.send(@reflection.through_reflection.name)
9
9
 
10
10
  if current_object
11
- klass.destroy(current_object)
12
- @owner.clear_association_cache
11
+ current_object.update_attributes(construct_join_attributes(new_value))
12
+ else
13
+ @owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value)))
13
14
  end
14
-
15
- @owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value)))
16
15
  end
17
16
 
18
17
  private
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  base.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
11
11
  base.cattr_accessor :time_zone_aware_attributes, :instance_writer => false
12
12
  base.time_zone_aware_attributes = false
13
- base.cattr_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
13
+ base.class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
14
14
  base.skip_time_zone_conversion_for_attributes = []
15
15
  end
16
16
 
@@ -214,7 +214,7 @@ module ActiveRecord
214
214
  if logger
215
215
  logger.warn "Exception occurred during reader method compilation."
216
216
  logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
217
- logger.warn "#{err.message}"
217
+ logger.warn err.message
218
218
  end
219
219
  end
220
220
  end
@@ -232,6 +232,10 @@ module ActiveRecord
232
232
  def method_missing(method_id, *args, &block)
233
233
  method_name = method_id.to_s
234
234
 
235
+ if self.class.private_method_defined?(method_name)
236
+ raise NoMethodError.new("Attempt to call private method", method_name, args)
237
+ end
238
+
235
239
  # If we haven't generated any methods yet, generate them, then
236
240
  # see if we've created the method we're looking for.
237
241
  if !self.class.generated_methods?
@@ -330,14 +334,18 @@ module ActiveRecord
330
334
  end
331
335
  end
332
336
 
333
- # A Person object with a name attribute can ask <tt>person.respond_to?("name")</tt>,
334
- # <tt>person.respond_to?("name=")</tt>, and <tt>person.respond_to?("name?")</tt>
337
+ # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
338
+ # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
335
339
  # which will all return +true+.
336
340
  alias :respond_to_without_attributes? :respond_to?
337
- def respond_to?(method, include_priv = false)
341
+ def respond_to?(method, include_private_methods = false)
338
342
  method_name = method.to_s
339
343
  if super
340
344
  return true
345
+ elsif !include_private_methods && super(method, true)
346
+ # If we're here than we haven't found among non-private methods
347
+ # but found among all methods. Which means that given method is private.
348
+ return false
341
349
  elsif !self.class.generated_methods?
342
350
  self.class.define_attribute_methods
343
351
  if self.class.generated_methods.include?(method_name)
@@ -6,7 +6,7 @@ module ActiveRecord #:nodoc:
6
6
  class ActiveRecordError < StandardError
7
7
  end
8
8
 
9
- # Raised when the single-table inheritance mechanism failes to locate the subclass
9
+ # Raised when the single-table inheritance mechanism fails to locate the subclass
10
10
  # (for example due to improper usage of column that +inheritance_column+ points to).
11
11
  class SubclassNotFound < ActiveRecordError #:nodoc:
12
12
  end
@@ -83,8 +83,33 @@ module ActiveRecord #:nodoc:
83
83
  class ReadOnlyRecord < ActiveRecordError
84
84
  end
85
85
 
86
- # Used by Active Record transaction mechanism to distinguish rollback from other exceptional situations.
87
- # You can use it to roll your transaction back explicitly in the block passed to +transaction+ method.
86
+ # ActiveRecord::Transactions::ClassMethods.transaction uses this exception
87
+ # to distinguish a deliberate rollback from other exceptional situations.
88
+ # Normally, raising an exception will cause the +transaction+ method to rollback
89
+ # the database transaction *and* pass on the exception. But if you raise an
90
+ # ActiveRecord::Rollback exception, then the database transaction will be rolled back,
91
+ # without passing on the exception.
92
+ #
93
+ # For example, you could do this in your controller to rollback a transaction:
94
+ #
95
+ # class BooksController < ActionController::Base
96
+ # def create
97
+ # Book.transaction do
98
+ # book = Book.new(params[:book])
99
+ # book.save!
100
+ # if today_is_friday?
101
+ # # The system must fail on Friday so that our support department
102
+ # # won't be out of job. We silently rollback this transaction
103
+ # # without telling the user.
104
+ # raise ActiveRecord::Rollback, "Call tech support!"
105
+ # end
106
+ # end
107
+ # # ActiveRecord::Rollback is the only exception that won't be passed on
108
+ # # by ActiveRecord::Base.transaction, so this line will still be reached
109
+ # # even on Friday.
110
+ # redirect_to root_url
111
+ # end
112
+ # end
88
113
  class Rollback < ActiveRecordError
89
114
  end
90
115
 
@@ -97,7 +122,11 @@ module ActiveRecord #:nodoc:
97
122
  class MissingAttributeError < NoMethodError
98
123
  end
99
124
 
100
- # Raised when an error occured while doing a mass assignment to an attribute through the
125
+ # Raised when unknown attributes are supplied via mass assignment.
126
+ class UnknownAttributeError < NoMethodError
127
+ end
128
+
129
+ # Raised when an error occurred while doing a mass assignment to an attribute through the
101
130
  # <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the
102
131
  # offending attribute.
103
132
  class AttributeAssignmentError < ActiveRecordError
@@ -245,7 +274,7 @@ module ActiveRecord #:nodoc:
245
274
  # == Dynamic attribute-based finders
246
275
  #
247
276
  # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
248
- # appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like <tt>Person.find_by_user_name</tt>,
277
+ # appending the name of an attribute to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt>, so you get finders like <tt>Person.find_by_user_name</tt>,
249
278
  # <tt>Person.find_all_by_last_name</tt>, and <tt>Payment.find_by_transaction_id</tt>. So instead of writing
250
279
  # <tt>Person.find(:first, :conditions => ["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
251
280
  # And instead of writing <tt>Person.find(:all, :conditions => ["last_name = ?", last_name])</tt>, you just do <tt>Person.find_all_by_last_name(last_name)</tt>.
@@ -258,6 +287,7 @@ module ActiveRecord #:nodoc:
258
287
  # It's even possible to use all the additional parameters to find. For example, the full interface for <tt>Payment.find_all_by_amount</tt>
259
288
  # is actually <tt>Payment.find_all_by_amount(amount, options)</tt>. And the full interface to <tt>Person.find_by_user_name</tt> is
260
289
  # actually <tt>Person.find_by_user_name(user_name, options)</tt>. So you could call <tt>Payment.find_all_by_amount(50, :order => "created_on")</tt>.
290
+ # Also you may call <tt>Payment.find_last_by_amount(amount, options)</tt> returning the last record matching that amount and options.
261
291
  #
262
292
  # The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with
263
293
  # <tt>find_or_create_by_</tt> and will return the object if it already exists and otherwise creates it, then returns it. Protected attributes won't be set unless they are given in a block. For example:
@@ -271,7 +301,7 @@ module ActiveRecord #:nodoc:
271
301
  # # Now 'Bob' exist and is an 'admin'
272
302
  # User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
273
303
  #
274
- # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Protected attributes won't be setted unless they are given in a block. For example:
304
+ # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Protected attributes won't be set unless they are given in a block. For example:
275
305
  #
276
306
  # # No 'Winter' tag exists
277
307
  # winter = Tag.find_or_initialize_by_name("Winter")
@@ -385,6 +415,31 @@ module ActiveRecord #:nodoc:
385
415
 
386
416
  @@subclasses = {}
387
417
 
418
+ # Contains the database configuration - as is typically stored in config/database.yml -
419
+ # as a Hash.
420
+ #
421
+ # For example, the following database.yml...
422
+ #
423
+ # development:
424
+ # adapter: sqlite3
425
+ # database: db/development.sqlite3
426
+ #
427
+ # production:
428
+ # adapter: sqlite3
429
+ # database: db/production.sqlite3
430
+ #
431
+ # ...would result in ActiveRecord::Base.configurations to look like this:
432
+ #
433
+ # {
434
+ # 'development' => {
435
+ # 'adapter' => 'sqlite3',
436
+ # 'database' => 'db/development.sqlite3'
437
+ # },
438
+ # 'production' => {
439
+ # 'adapter' => 'sqlite3',
440
+ # 'database' => 'db/production.sqlite3'
441
+ # }
442
+ # }
388
443
  cattr_accessor :configurations, :instance_writer => false
389
444
  @@configurations = {}
390
445
 
@@ -423,13 +478,6 @@ module ActiveRecord #:nodoc:
423
478
  cattr_accessor :default_timezone, :instance_writer => false
424
479
  @@default_timezone = :local
425
480
 
426
- # Determines whether to use a connection for each thread, or a single shared connection for all threads.
427
- # Defaults to false. If you're writing a threaded application, set to true
428
- # and periodically call verify_active_connections! to clear out connections
429
- # assigned to stale threads.
430
- cattr_accessor :allow_concurrency, :instance_writer => false
431
- @@allow_concurrency = false
432
-
433
481
  # Specifies the format to use when dumping the database schema with Rails'
434
482
  # Rakefile. If :sql, the schema is dumped as (potentially database-
435
483
  # specific) SQL statements. If :ruby, the schema is dumped as an
@@ -464,9 +512,9 @@ module ActiveRecord #:nodoc:
464
512
  #
465
513
  # All approaches accept an options hash as their last parameter.
466
514
  #
467
- # ==== Attributes
515
+ # ==== Parameters
468
516
  #
469
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or <tt>[ "user_name = ?", username ]</tt>. See conditions in the intro.
517
+ # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>, or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
470
518
  # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
471
519
  # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
472
520
  # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
@@ -478,7 +526,7 @@ module ActiveRecord #:nodoc:
478
526
  # * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
479
527
  # to already defined associations. See eager loading under Associations.
480
528
  # * <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
481
- # include the joined columns.
529
+ # include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
482
530
  # * <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
483
531
  # of a database view).
484
532
  # * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
@@ -503,6 +551,7 @@ module ActiveRecord #:nodoc:
503
551
  # # find first
504
552
  # Person.find(:first) # returns the first object fetched by SELECT * FROM people
505
553
  # Person.find(:first, :conditions => [ "user_name = ?", user_name])
554
+ # Person.find(:first, :conditions => [ "user_name = :u", { :u => user_name }])
506
555
  # Person.find(:first, :order => "created_on DESC", :offset => 5)
507
556
  #
508
557
  # # find last
@@ -562,8 +611,8 @@ module ActiveRecord #:nodoc:
562
611
 
563
612
  # Executes a custom SQL query against your database and returns all the results. The results will
564
613
  # be returned as an array with columns requested encapsulated as attributes of the model you call
565
- # this method from. If you call +Product.find_by_sql+ then the results will be returned in a Product
566
- # object with the attributes you specified in the SQL query.
614
+ # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
615
+ # a Product object with the attributes you specified in the SQL query.
567
616
  #
568
617
  # If you call a complicated SQL query which spans multiple tables the columns specified by the
569
618
  # SELECT will be attributes of the model, whether or not they are columns of the corresponding
@@ -572,7 +621,7 @@ module ActiveRecord #:nodoc:
572
621
  # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
573
622
  # no database agnostic conversions performed. This should be a last resort because using, for example,
574
623
  # MySQL specific terms will lock you to using that particular database engine or require you to
575
- # change your call if you switch engines
624
+ # change your call if you switch engines.
576
625
  #
577
626
  # ==== Examples
578
627
  # # A simple SQL query spanning multiple tables
@@ -649,7 +698,7 @@ module ActiveRecord #:nodoc:
649
698
  # Updates an object (or multiple objects) and saves it to the database, if validations pass.
650
699
  # The resulting object is returned whether the object was saved successfully to the database or not.
651
700
  #
652
- # ==== Attributes
701
+ # ==== Parameters
653
702
  #
654
703
  # * +id+ - This should be the id or an array of ids to be updated.
655
704
  # * +attributes+ - This should be a Hash of attributes to be set on the object, or an array of Hashes.
@@ -677,9 +726,10 @@ module ActiveRecord #:nodoc:
677
726
  # is executed on the database which means that no callbacks are fired off running this. This is an efficient method
678
727
  # of deleting records that don't need cleaning up after or other actions to be taken.
679
728
  #
680
- # Objects are _not_ instantiated with this method.
729
+ # Objects are _not_ instantiated with this method, and so +:dependent+ rules
730
+ # defined on associations are not honered.
681
731
  #
682
- # ==== Attributes
732
+ # ==== Parameters
683
733
  #
684
734
  # * +id+ - Can be either an Integer or an Array of Integers.
685
735
  #
@@ -702,7 +752,7 @@ module ActiveRecord #:nodoc:
702
752
  # This essentially finds the object (or multiple objects) with the given id, creates a new object
703
753
  # from the attributes, and then calls destroy on it.
704
754
  #
705
- # ==== Attributes
755
+ # ==== Parameters
706
756
  #
707
757
  # * +id+ - Can be either an Integer or an Array of Integers.
708
758
  #
@@ -723,14 +773,15 @@ module ActiveRecord #:nodoc:
723
773
  end
724
774
 
725
775
  # Updates all records with details given if they match a set of conditions supplied, limits and order can
726
- # also be supplied.
776
+ # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
777
+ # database. It does not instantiate the involved models and it does not trigger Active Record callbacks.
727
778
  #
728
- # ==== Attributes
779
+ # ==== Parameters
729
780
  #
730
- # * +updates+ - A String of column and value pairs that will be set on any records that match conditions.
731
- # * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
732
- # See conditions in the intro for more info.
733
- # * +options+ - Additional options are <tt>:limit</tt> and/or <tt>:order</tt>, see the examples for usage.
781
+ # * +updates+ - A string of column and value pairs that will be set on any records that match conditions.
782
+ # What goes into the SET clause.
783
+ # * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro for more info.
784
+ # * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
734
785
  #
735
786
  # ==== Examples
736
787
  #
@@ -745,46 +796,66 @@ module ActiveRecord #:nodoc:
745
796
  # :order => 'created_at', :limit => 5 )
746
797
  def update_all(updates, conditions = nil, options = {})
747
798
  sql = "UPDATE #{quoted_table_name} SET #{sanitize_sql_for_assignment(updates)} "
799
+
748
800
  scope = scope(:find)
749
- add_conditions!(sql, conditions, scope)
750
- add_order!(sql, options[:order], nil)
751
- add_limit!(sql, options, nil)
801
+
802
+ select_sql = ""
803
+ add_conditions!(select_sql, conditions, scope)
804
+
805
+ if options.has_key?(:limit) || (scope && scope[:limit])
806
+ # Only take order from scope if limit is also provided by scope, this
807
+ # is useful for updating a has_many association with a limit.
808
+ add_order!(select_sql, options[:order], scope)
809
+
810
+ add_limit!(select_sql, options, scope)
811
+ sql.concat(connection.limited_update_conditions(select_sql, quoted_table_name, connection.quote_column_name(primary_key)))
812
+ else
813
+ add_order!(select_sql, options[:order], nil)
814
+ sql.concat(select_sql)
815
+ end
816
+
752
817
  connection.update(sql, "#{name} Update")
753
818
  end
754
819
 
755
- # Destroys the records matching +conditions+ by instantiating each record and calling the destroy method.
756
- # This means at least 2*N database queries to destroy N records, so avoid destroy_all if you are deleting
820
+ # Destroys the records matching +conditions+ by instantiating each record and calling their +destroy+ method.
821
+ # This means at least 2*N database queries to destroy N records, so avoid +destroy_all+ if you are deleting
757
822
  # many records. If you want to simply delete records without worrying about dependent associations or
758
823
  # callbacks, use the much faster +delete_all+ method instead.
759
824
  #
760
- # ==== Attributes
825
+ # ==== Parameters
761
826
  #
762
827
  # * +conditions+ - Conditions are specified the same way as with +find+ method.
763
828
  #
764
829
  # ==== Example
765
830
  #
766
- # Person.destroy_all "last_login < '2004-04-04'"
831
+ # Person.destroy_all("last_login < '2004-04-04'")
767
832
  #
768
833
  # This loads and destroys each person one by one, including its dependent associations and before_ and
769
834
  # after_destroy callbacks.
835
+ #
836
+ # +conditions+ can be anything that +find+ also accepts:
837
+ #
838
+ # Person.destroy_all(:last_login => 6.hours.ago)
770
839
  def destroy_all(conditions = nil)
771
840
  find(:all, :conditions => conditions).each { |object| object.destroy }
772
841
  end
773
842
 
774
843
  # Deletes the records matching +conditions+ without instantiating the records first, and hence not
775
- # calling the destroy method and invoking callbacks. This is a single SQL query, much more efficient
776
- # than destroy_all.
844
+ # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
845
+ # goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
846
+ # though, in particular <tt>:dependent</tt> rules defined on associations are not honored.
777
847
  #
778
- # ==== Attributes
848
+ # ==== Parameters
779
849
  #
780
850
  # * +conditions+ - Conditions are specified the same way as with +find+ method.
781
851
  #
782
852
  # ==== Example
783
853
  #
784
- # Post.delete_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
854
+ # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
855
+ # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
785
856
  #
786
- # This deletes the affected posts all at once with a single DELETE query. If you need to destroy dependent
787
- # associations or call your before_ or after_destroy callbacks, use the +destroy_all+ method instead.
857
+ # Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent
858
+ # associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
788
859
  def delete_all(conditions = nil)
789
860
  sql = "DELETE FROM #{quoted_table_name} "
790
861
  add_conditions!(sql, conditions, scope(:find))
@@ -795,7 +866,7 @@ module ActiveRecord #:nodoc:
795
866
  # The use of this method should be restricted to complicated SQL queries that can't be executed
796
867
  # using the ActiveRecord::Calculations class methods. Look into those before using this.
797
868
  #
798
- # ==== Attributes
869
+ # ==== Parameters
799
870
  #
800
871
  # * +sql+ - An SQL statement which should return a count query from the database, see the example below.
801
872
  #
@@ -813,7 +884,7 @@ module ActiveRecord #:nodoc:
813
884
  # with the given ID, altering the given hash of counters by the amount
814
885
  # given by the corresponding value:
815
886
  #
816
- # ==== Attributes
887
+ # ==== Parameters
817
888
  #
818
889
  # * +id+ - The id of the object you wish to update a counter on.
819
890
  # * +counters+ - An Array of Hashes containing the names of the fields
@@ -843,7 +914,7 @@ module ActiveRecord #:nodoc:
843
914
  # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
844
915
  # shown it would have to run an SQL query to find how many posts and comments there are.
845
916
  #
846
- # ==== Attributes
917
+ # ==== Parameters
847
918
  #
848
919
  # * +counter_name+ - The name of the field that should be incremented.
849
920
  # * +id+ - The id of the object that should be incremented.
@@ -860,7 +931,7 @@ module ActiveRecord #:nodoc:
860
931
  #
861
932
  # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
862
933
  #
863
- # ==== Attributes
934
+ # ==== Parameters
864
935
  #
865
936
  # * +counter_name+ - The name of the field that should be decremented.
866
937
  # * +id+ - The id of the object that should be decremented.
@@ -899,12 +970,12 @@ module ActiveRecord #:nodoc:
899
970
  # To start from an all-closed default and enable attributes as needed,
900
971
  # have a look at +attr_accessible+.
901
972
  def attr_protected(*attributes)
902
- write_inheritable_attribute("attr_protected", Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
973
+ write_inheritable_attribute(:attr_protected, Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
903
974
  end
904
975
 
905
976
  # Returns an array of all the attributes that have been protected from mass-assignment.
906
977
  def protected_attributes # :nodoc:
907
- read_inheritable_attribute("attr_protected")
978
+ read_inheritable_attribute(:attr_protected)
908
979
  end
909
980
 
910
981
  # Specifies a white list of model attributes that can be set via
@@ -932,22 +1003,22 @@ module ActiveRecord #:nodoc:
932
1003
  # customer.credit_rating = "Average"
933
1004
  # customer.credit_rating # => "Average"
934
1005
  def attr_accessible(*attributes)
935
- write_inheritable_attribute("attr_accessible", Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
1006
+ write_inheritable_attribute(:attr_accessible, Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
936
1007
  end
937
1008
 
938
1009
  # Returns an array of all the attributes that have been made accessible to mass-assignment.
939
1010
  def accessible_attributes # :nodoc:
940
- read_inheritable_attribute("attr_accessible")
1011
+ read_inheritable_attribute(:attr_accessible)
941
1012
  end
942
1013
 
943
1014
  # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
944
1015
  def attr_readonly(*attributes)
945
- write_inheritable_attribute("attr_readonly", Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
1016
+ write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
946
1017
  end
947
1018
 
948
1019
  # Returns an array of all the attributes that have been specified as readonly.
949
1020
  def readonly_attributes
950
- read_inheritable_attribute("attr_readonly")
1021
+ read_inheritable_attribute(:attr_readonly)
951
1022
  end
952
1023
 
953
1024
  # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
@@ -955,7 +1026,7 @@ module ActiveRecord #:nodoc:
955
1026
  # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
956
1027
  # class on retrieval or SerializationTypeMismatch will be raised.
957
1028
  #
958
- # ==== Attributes
1029
+ # ==== Parameters
959
1030
  #
960
1031
  # * +attr_name+ - The field name that should be serialized.
961
1032
  # * +class_name+ - Optional, class name that the object type should be equal to.
@@ -971,7 +1042,7 @@ module ActiveRecord #:nodoc:
971
1042
 
972
1043
  # Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
973
1044
  def serialized_attributes
974
- read_inheritable_attribute("attr_serialized") or write_inheritable_attribute("attr_serialized", {})
1045
+ read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {})
975
1046
  end
976
1047
 
977
1048
 
@@ -1182,7 +1253,32 @@ module ActiveRecord #:nodoc:
1182
1253
  end
1183
1254
  end
1184
1255
 
1185
- # Resets all the cached information about columns, which will cause them to be reloaded on the next request.
1256
+ # Resets all the cached information about columns, which will cause them
1257
+ # to be reloaded on the next request.
1258
+ #
1259
+ # The most common usage pattern for this method is probably in a migration,
1260
+ # when just after creating a table you want to populate it with some default
1261
+ # values, eg:
1262
+ #
1263
+ # class CreateJobLevels < ActiveRecord::Migration
1264
+ # def self.up
1265
+ # create_table :job_levels do |t|
1266
+ # t.integer :id
1267
+ # t.string :name
1268
+ #
1269
+ # t.timestamps
1270
+ # end
1271
+ #
1272
+ # JobLevel.reset_column_information
1273
+ # %w{assistant executive manager director}.each do |type|
1274
+ # JobLevel.create(:name => type)
1275
+ # end
1276
+ # end
1277
+ #
1278
+ # def self.down
1279
+ # drop_table :job_levels
1280
+ # end
1281
+ # end
1186
1282
  def reset_column_information
1187
1283
  generated_methods.each { |name| undef_method(name) }
1188
1284
  @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @generated_methods = @inheritance_column = nil
@@ -1192,11 +1288,46 @@ module ActiveRecord #:nodoc:
1192
1288
  subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
1193
1289
  end
1194
1290
 
1291
+ def self_and_descendents_from_active_record#nodoc:
1292
+ klass = self
1293
+ classes = [klass]
1294
+ while klass != klass.base_class
1295
+ classes << klass = klass.superclass
1296
+ end
1297
+ classes
1298
+ rescue
1299
+ # OPTIMIZE this rescue is to fix this test: ./test/cases/reflection_test.rb:56:in `test_human_name_for_column'
1300
+ # Appearantly the method base_class causes some trouble.
1301
+ # It now works for sure.
1302
+ [self]
1303
+ end
1304
+
1195
1305
  # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
1196
1306
  # Person.human_attribute_name("first_name") # => "First name"
1197
- # Deprecated in favor of just calling "first_name".humanize
1198
- def human_attribute_name(attribute_key_name) #:nodoc:
1199
- attribute_key_name.humanize
1307
+ # This used to be depricated in favor of humanize, but is now preferred, because it automatically uses the I18n
1308
+ # module now.
1309
+ # Specify +options+ with additional translating options.
1310
+ def human_attribute_name(attribute_key_name, options = {})
1311
+ defaults = self_and_descendents_from_active_record.map do |klass|
1312
+ :"#{klass.name.underscore}.#{attribute_key_name}"
1313
+ end
1314
+ defaults << options[:default] if options[:default]
1315
+ defaults.flatten!
1316
+ defaults << attribute_key_name.humanize
1317
+ options[:count] ||= 1
1318
+ I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes]))
1319
+ end
1320
+
1321
+ # Transform the modelname into a more humane format, using I18n.
1322
+ # Defaults to the basic humanize method.
1323
+ # Default scope of the translation is activerecord.models
1324
+ # Specify +options+ with additional translating options.
1325
+ def human_name(options = {})
1326
+ defaults = self_and_descendents_from_active_record.map do |klass|
1327
+ :"#{klass.name.underscore}"
1328
+ end
1329
+ defaults << self.name.humanize
1330
+ I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options))
1200
1331
  end
1201
1332
 
1202
1333
  # True if this isn't a concrete subclass needing a STI type condition.
@@ -1254,7 +1385,7 @@ module ActiveRecord #:nodoc:
1254
1385
  if logger && logger.level <= log_level
1255
1386
  result = nil
1256
1387
  seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
1257
- logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
1388
+ logger.add(log_level, "#{title} (#{'%.1f' % (seconds * 1000)}ms)")
1258
1389
  result
1259
1390
  else
1260
1391
  yield
@@ -1291,8 +1422,8 @@ module ActiveRecord #:nodoc:
1291
1422
  end
1292
1423
 
1293
1424
  def respond_to?(method_id, include_private = false)
1294
- if match = matches_dynamic_finder?(method_id) || matches_dynamic_finder_with_initialize_or_create?(method_id)
1295
- return true if all_attributes_exists?(extract_attribute_names_from_match(match))
1425
+ if match = DynamicFinderMatch.match(method_id)
1426
+ return true if all_attributes_exists?(match.attribute_names)
1296
1427
  end
1297
1428
  super
1298
1429
  end
@@ -1301,6 +1432,20 @@ module ActiveRecord #:nodoc:
1301
1432
  store_full_sti_class ? name : name.demodulize
1302
1433
  end
1303
1434
 
1435
+ # Merges conditions so that the result is a valid +condition+
1436
+ def merge_conditions(*conditions)
1437
+ segments = []
1438
+
1439
+ conditions.each do |condition|
1440
+ unless condition.blank?
1441
+ sql = sanitize_sql(condition)
1442
+ segments << sql unless sql.blank?
1443
+ end
1444
+ end
1445
+
1446
+ "(#{segments.join(') AND (')})" unless segments.empty?
1447
+ end
1448
+
1304
1449
  private
1305
1450
  def find_initial(options)
1306
1451
  options.update(:limit => 1)
@@ -1467,12 +1612,20 @@ module ActiveRecord #:nodoc:
1467
1612
  end
1468
1613
  end
1469
1614
 
1615
+ def default_select(qualified)
1616
+ if qualified
1617
+ quoted_table_name + '.*'
1618
+ else
1619
+ '*'
1620
+ end
1621
+ end
1622
+
1470
1623
  def construct_finder_sql(options)
1471
1624
  scope = scope(:find)
1472
- sql = "SELECT #{options[:select] || (scope && scope[:select]) || ((options[:joins] || (scope && scope[:joins])) && quoted_table_name + '.*') || '*'} "
1625
+ sql = "SELECT #{options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))} "
1473
1626
  sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
1474
1627
 
1475
- add_joins!(sql, options, scope)
1628
+ add_joins!(sql, options[:joins], scope)
1476
1629
  add_conditions!(sql, options[:conditions], scope)
1477
1630
 
1478
1631
  add_group!(sql, options[:group], scope)
@@ -1488,18 +1641,20 @@ module ActiveRecord #:nodoc:
1488
1641
  (safe_to_array(first) + safe_to_array(second)).uniq
1489
1642
  end
1490
1643
 
1491
- # Merges conditions so that the result is a valid +condition+
1492
- def merge_conditions(*conditions)
1493
- segments = []
1494
-
1495
- conditions.each do |condition|
1496
- unless condition.blank?
1497
- sql = sanitize_sql(condition)
1498
- segments << sql unless sql.blank?
1644
+ def merge_joins(*joins)
1645
+ if joins.any?{|j| j.is_a?(String) || array_of_strings?(j) }
1646
+ joins = joins.collect do |join|
1647
+ join = [join] if join.is_a?(String)
1648
+ unless array_of_strings?(join)
1649
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil)
1650
+ join = join_dependency.join_associations.collect { |assoc| assoc.association_join }
1651
+ end
1652
+ join
1499
1653
  end
1654
+ joins.flatten.uniq
1655
+ else
1656
+ joins.collect{|j| safe_to_array(j)}.flatten.uniq
1500
1657
  end
1501
-
1502
- "(#{segments.join(') AND (')})" unless segments.empty?
1503
1658
  end
1504
1659
 
1505
1660
  # Object#to_a is deprecated, though it does have the desired behavior
@@ -1514,6 +1669,10 @@ module ActiveRecord #:nodoc:
1514
1669
  end
1515
1670
  end
1516
1671
 
1672
+ def array_of_strings?(o)
1673
+ o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
1674
+ end
1675
+
1517
1676
  def add_order!(sql, order, scope = :auto)
1518
1677
  scope = scope(:find) if :auto == scope
1519
1678
  scoped_order = scope[:order] if scope
@@ -1557,16 +1716,19 @@ module ActiveRecord #:nodoc:
1557
1716
  end
1558
1717
 
1559
1718
  # The optional scope argument is for the current <tt>:find</tt> scope.
1560
- def add_joins!(sql, options, scope = :auto)
1719
+ def add_joins!(sql, joins, scope = :auto)
1561
1720
  scope = scope(:find) if :auto == scope
1562
- [(scope && scope[:joins]), options[:joins]].each do |join|
1563
- case join
1564
- when Symbol, Hash, Array
1565
- join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil)
1566
- sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
1721
+ merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
1722
+ case merged_joins
1723
+ when Symbol, Hash, Array
1724
+ if array_of_strings?(merged_joins)
1725
+ sql << merged_joins.join(' ') + " "
1567
1726
  else
1568
- sql << " #{join} "
1727
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
1728
+ sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
1569
1729
  end
1730
+ when String
1731
+ sql << " #{merged_joins} "
1570
1732
  end
1571
1733
  end
1572
1734
 
@@ -1610,89 +1772,68 @@ module ActiveRecord #:nodoc:
1610
1772
  #
1611
1773
  # Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
1612
1774
  # attempts to use it do not run through method_missing.
1613
- def method_missing(method_id, *arguments)
1614
- if match = matches_dynamic_finder?(method_id)
1615
- finder = determine_finder(match)
1616
-
1617
- attribute_names = extract_attribute_names_from_match(match)
1775
+ def method_missing(method_id, *arguments, &block)
1776
+ if match = DynamicFinderMatch.match(method_id)
1777
+ attribute_names = match.attribute_names
1618
1778
  super unless all_attributes_exists?(attribute_names)
1619
-
1620
- self.class_eval %{
1621
- def self.#{method_id}(*args)
1622
- options = args.extract_options!
1623
- attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
1624
- finder_options = { :conditions => attributes }
1625
- validate_find_options(options)
1626
- set_readonly_option!(options)
1627
-
1628
- if options[:conditions]
1629
- with_scope(:find => finder_options) do
1630
- ActiveSupport::Deprecation.silence { send(:#{finder}, options) }
1779
+ if match.finder?
1780
+ finder = match.finder
1781
+ bang = match.bang?
1782
+ self.class_eval %{
1783
+ def self.#{method_id}(*args)
1784
+ options = args.extract_options!
1785
+ attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
1786
+ finder_options = { :conditions => attributes }
1787
+ validate_find_options(options)
1788
+ set_readonly_option!(options)
1789
+
1790
+ #{'result = ' if bang}if options[:conditions]
1791
+ with_scope(:find => finder_options) do
1792
+ find(:#{finder}, options)
1793
+ end
1794
+ else
1795
+ find(:#{finder}, options.merge(finder_options))
1631
1796
  end
1632
- else
1633
- ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) }
1634
- end
1635
- end
1636
- }, __FILE__, __LINE__
1637
- send(method_id, *arguments)
1638
- elsif match = matches_dynamic_finder_with_initialize_or_create?(method_id)
1639
- instantiator = determine_instantiator(match)
1640
- attribute_names = extract_attribute_names_from_match(match)
1641
- super unless all_attributes_exists?(attribute_names)
1642
-
1643
- self.class_eval %{
1644
- def self.#{method_id}(*args)
1645
- guard_protected_attributes = false
1646
-
1647
- if args[0].is_a?(Hash)
1648
- guard_protected_attributes = true
1649
- attributes = args[0].with_indifferent_access
1650
- find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
1651
- else
1652
- find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
1797
+ #{'result || raise(RecordNotFound)' if bang}
1653
1798
  end
1799
+ }, __FILE__, __LINE__
1800
+ send(method_id, *arguments)
1801
+ elsif match.instantiator?
1802
+ instantiator = match.instantiator
1803
+ self.class_eval %{
1804
+ def self.#{method_id}(*args)
1805
+ guard_protected_attributes = false
1806
+
1807
+ if args[0].is_a?(Hash)
1808
+ guard_protected_attributes = true
1809
+ attributes = args[0].with_indifferent_access
1810
+ find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
1811
+ else
1812
+ find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
1813
+ end
1654
1814
 
1655
- options = { :conditions => find_attributes }
1656
- set_readonly_option!(options)
1815
+ options = { :conditions => find_attributes }
1816
+ set_readonly_option!(options)
1657
1817
 
1658
- record = find_initial(options)
1818
+ record = find(:first, options)
1659
1819
 
1660
- if record.nil?
1661
- record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
1662
- #{'yield(record) if block_given?'}
1663
- #{'record.save' if instantiator == :create}
1664
- record
1665
- else
1666
- record
1820
+ if record.nil?
1821
+ record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
1822
+ #{'yield(record) if block_given?'}
1823
+ #{'record.save' if instantiator == :create}
1824
+ record
1825
+ else
1826
+ record
1827
+ end
1667
1828
  end
1668
- end
1669
- }, __FILE__, __LINE__
1670
- send(method_id, *arguments)
1829
+ }, __FILE__, __LINE__
1830
+ send(method_id, *arguments, &block)
1831
+ end
1671
1832
  else
1672
1833
  super
1673
1834
  end
1674
1835
  end
1675
1836
 
1676
- def matches_dynamic_finder?(method_id)
1677
- /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
1678
- end
1679
-
1680
- def matches_dynamic_finder_with_initialize_or_create?(method_id)
1681
- /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)
1682
- end
1683
-
1684
- def determine_finder(match)
1685
- match.captures.first == 'all_by' ? :find_every : :find_initial
1686
- end
1687
-
1688
- def determine_instantiator(match)
1689
- match.captures.first == 'initialize' ? :new : :create
1690
- end
1691
-
1692
- def extract_attribute_names_from_match(match)
1693
- match.captures.last.split('_and_')
1694
- end
1695
-
1696
1837
  def construct_attributes_from_arguments(attribute_names, arguments)
1697
1838
  attributes = {}
1698
1839
  attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
@@ -1809,6 +1950,9 @@ module ActiveRecord #:nodoc:
1809
1950
  # end
1810
1951
  # end
1811
1952
  # end
1953
+ #
1954
+ # *Note*: the +:find+ scope also has effect on update and deletion methods,
1955
+ # like +update_all+ and +delete_all+.
1812
1956
  def with_scope(method_scoping = {}, action = :merge, &block)
1813
1957
  method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
1814
1958
 
@@ -1837,6 +1981,8 @@ module ActiveRecord #:nodoc:
1837
1981
  hash[method][key] = merge_conditions(params[key], hash[method][key])
1838
1982
  elsif key == :include && merge
1839
1983
  hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
1984
+ elsif key == :joins && merge
1985
+ hash[method][key] = merge_joins(params[key], hash[method][key])
1840
1986
  else
1841
1987
  hash[method][key] = hash[method][key] || params[key]
1842
1988
  end
@@ -1884,20 +2030,8 @@ module ActiveRecord #:nodoc:
1884
2030
  end
1885
2031
  end
1886
2032
 
1887
- def thread_safe_scoped_methods #:nodoc:
1888
- scoped_methods = (Thread.current[:scoped_methods] ||= {})
1889
- scoped_methods[self] ||= []
1890
- end
1891
-
1892
- def single_threaded_scoped_methods #:nodoc:
1893
- @scoped_methods ||= []
1894
- end
1895
-
1896
- # pick up the correct scoped_methods version from @@allow_concurrency
1897
- if @@allow_concurrency
1898
- alias_method :scoped_methods, :thread_safe_scoped_methods
1899
- else
1900
- alias_method :scoped_methods, :single_threaded_scoped_methods
2033
+ def scoped_methods #:nodoc:
2034
+ Thread.current[:"#{self}_scoped_methods"] ||= []
1901
2035
  end
1902
2036
 
1903
2037
  def current_scoped_methods #:nodoc:
@@ -1908,10 +2042,12 @@ module ActiveRecord #:nodoc:
1908
2042
  # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
1909
2043
  def compute_type(type_name)
1910
2044
  modularized_name = type_name_with_module(type_name)
1911
- begin
1912
- class_eval(modularized_name, __FILE__, __LINE__)
1913
- rescue NameError
1914
- class_eval(type_name, __FILE__, __LINE__)
2045
+ silence_warnings do
2046
+ begin
2047
+ class_eval(modularized_name, __FILE__, __LINE__)
2048
+ rescue NameError
2049
+ class_eval(type_name, __FILE__, __LINE__)
2050
+ end
1915
2051
  end
1916
2052
  end
1917
2053
 
@@ -2002,24 +2138,28 @@ module ActiveRecord #:nodoc:
2002
2138
  # # => "age BETWEEN 13 AND 18"
2003
2139
  # { 'other_records.id' => 7 }
2004
2140
  # # => "`other_records`.`id` = 7"
2141
+ # { :other_records => { :id => 7 } }
2142
+ # # => "`other_records`.`id` = 7"
2005
2143
  # And for value objects on a composed_of relationship:
2006
2144
  # { :address => Address.new("123 abc st.", "chicago") }
2007
2145
  # # => "address_street='123 abc st.' and address_city='chicago'"
2008
- def sanitize_sql_hash_for_conditions(attrs)
2146
+ def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name)
2009
2147
  attrs = expand_hash_conditions_for_aggregates(attrs)
2010
2148
 
2011
2149
  conditions = attrs.map do |attr, value|
2012
- attr = attr.to_s
2150
+ unless value.is_a?(Hash)
2151
+ attr = attr.to_s
2152
+
2153
+ # Extract table name from qualified attribute names.
2154
+ if attr.include?('.')
2155
+ table_name, attr = attr.split('.', 2)
2156
+ table_name = connection.quote_table_name(table_name)
2157
+ end
2013
2158
 
2014
- # Extract table name from qualified attribute names.
2015
- if attr.include?('.')
2016
- table_name, attr = attr.split('.', 2)
2017
- table_name = connection.quote_table_name(table_name)
2159
+ "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
2018
2160
  else
2019
- table_name = quoted_table_name
2161
+ sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
2020
2162
  end
2021
-
2022
- "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
2023
2163
  end.join(' AND ')
2024
2164
 
2025
2165
  replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
@@ -2073,6 +2213,8 @@ module ActiveRecord #:nodoc:
2073
2213
  expanded = []
2074
2214
 
2075
2215
  bind_vars.each do |var|
2216
+ next if var.is_a?(Hash)
2217
+
2076
2218
  if var.is_a?(Range)
2077
2219
  expanded << var.first
2078
2220
  expanded << var.last
@@ -2085,7 +2227,7 @@ module ActiveRecord #:nodoc:
2085
2227
  end
2086
2228
 
2087
2229
  def quote_bound_value(value) #:nodoc:
2088
- if value.respond_to?(:map) && !value.is_a?(String) && !value.is_a?(ActiveSupport::Multibyte::Chars)
2230
+ if value.respond_to?(:map) && !value.acts_like?(:string)
2089
2231
  if value.respond_to?(:empty?) && value.empty?
2090
2232
  connection.quote(nil)
2091
2233
  else
@@ -2157,7 +2299,28 @@ module ActiveRecord #:nodoc:
2157
2299
 
2158
2300
  end
2159
2301
 
2160
- # Enables Active Record objects to be used as URL parameters in Action Pack automatically.
2302
+ # Returns a String, which Action Pack uses for constructing an URL to this
2303
+ # object. The default implementation returns this record's id as a String,
2304
+ # or nil if this record's unsaved.
2305
+ #
2306
+ # For example, suppose that you have a Users model, and that you have a
2307
+ # <tt>map.resources :users</tt> route. Normally, +users_path+ will
2308
+ # construct an URI with the user object's 'id' in it:
2309
+ #
2310
+ # user = User.find_by_name('Phusion')
2311
+ # user_path(path) # => "/users/1"
2312
+ #
2313
+ # You can override +to_param+ in your model to make +users_path+ construct
2314
+ # an URI using the user's name instead of the user's id:
2315
+ #
2316
+ # class User < ActiveRecord::Base
2317
+ # def to_param # overridden
2318
+ # name
2319
+ # end
2320
+ # end
2321
+ #
2322
+ # user = User.find_by_name('Phusion')
2323
+ # user_path(path) # => "/users/Phusion"
2161
2324
  def to_param
2162
2325
  # We can't use alias_method here, because method 'id' optimizes itself on the fly.
2163
2326
  (id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
@@ -2173,11 +2336,11 @@ module ActiveRecord #:nodoc:
2173
2336
  def cache_key
2174
2337
  case
2175
2338
  when new_record?
2176
- "#{self.class.name.tableize}/new"
2177
- when self[:updated_at]
2178
- "#{self.class.name.tableize}/#{id}-#{updated_at.to_s(:number)}"
2339
+ "#{self.class.model_name.cache_key}/new"
2340
+ when timestamp = self[:updated_at]
2341
+ "#{self.class.model_name.cache_key}/#{id}-#{timestamp.to_s(:number)}"
2179
2342
  else
2180
- "#{self.class.name.tableize}/#{id}"
2343
+ "#{self.class.model_name.cache_key}/#{id}"
2181
2344
  end
2182
2345
  end
2183
2346
 
@@ -2199,32 +2362,66 @@ module ActiveRecord #:nodoc:
2199
2362
  defined?(@new_record) && @new_record
2200
2363
  end
2201
2364
 
2202
- # * No record exists: Creates a new record with values matching those of the object attributes.
2203
- # * A record does exist: Updates the record with values matching those of the object attributes.
2365
+ # :call-seq:
2366
+ # save(perform_validation = true)
2367
+ #
2368
+ # Saves the model.
2204
2369
  #
2205
- # Note: If your model specifies any validations then the method declaration dynamically
2206
- # changes to:
2207
- # save(perform_validation=true)
2208
- # Calling save(false) saves the model without running validations.
2209
- # See ActiveRecord::Validations for more information.
2370
+ # If the model is new a record gets created in the database, otherwise
2371
+ # the existing record gets updated.
2372
+ #
2373
+ # If +perform_validation+ is true validations run. If any of them fail
2374
+ # the action is cancelled and +save+ returns +false+. If the flag is
2375
+ # false validations are bypassed altogether. See
2376
+ # ActiveRecord::Validations for more information.
2377
+ #
2378
+ # There's a series of callbacks associated with +save+. If any of the
2379
+ # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
2380
+ # +save+ returns +false+. See ActiveRecord::Callbacks for further
2381
+ # details.
2210
2382
  def save
2211
2383
  create_or_update
2212
2384
  end
2213
2385
 
2214
- # Attempts to save the record, but instead of just returning false if it couldn't happen, it raises a
2215
- # RecordNotSaved exception
2386
+ # Saves the model.
2387
+ #
2388
+ # If the model is new a record gets created in the database, otherwise
2389
+ # the existing record gets updated.
2390
+ #
2391
+ # With <tt>save!</tt> validations always run. If any of them fail
2392
+ # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
2393
+ # for more information.
2394
+ #
2395
+ # There's a series of callbacks associated with <tt>save!</tt>. If any of
2396
+ # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
2397
+ # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
2398
+ # ActiveRecord::Callbacks for further details.
2216
2399
  def save!
2217
2400
  create_or_update || raise(RecordNotSaved)
2218
2401
  end
2219
2402
 
2403
+ # Deletes the record in the database and freezes this instance to reflect that no changes should
2404
+ # be made (since they can't be persisted).
2405
+ #
2406
+ # Unlike #destroy, this method doesn't run any +before_delete+ and +after_delete+
2407
+ # callbacks, nor will it enforce any association +:dependent+ rules.
2408
+ #
2409
+ # In addition to deleting this record, any defined +before_delete+ and +after_delete+
2410
+ # callbacks are run, and +:dependent+ rules defined on associations are run.
2411
+ def delete
2412
+ self.class.delete(id) unless new_record?
2413
+ freeze
2414
+ end
2415
+
2220
2416
  # Deletes the record in the database and freezes this instance to reflect that no changes should
2221
2417
  # be made (since they can't be persisted).
2222
2418
  def destroy
2223
2419
  unless new_record?
2224
- connection.delete <<-end_sql, "#{self.class.name} Destroy"
2225
- DELETE FROM #{self.class.quoted_table_name}
2226
- WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}
2227
- end_sql
2420
+ connection.delete(
2421
+ "DELETE FROM #{self.class.quoted_table_name} " +
2422
+ "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}",
2423
+ "#{self.class.name} Destroy"
2424
+ )
2228
2425
  end
2229
2426
 
2230
2427
  freeze
@@ -2258,12 +2455,12 @@ module ActiveRecord #:nodoc:
2258
2455
  end
2259
2456
  end
2260
2457
 
2261
- # Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records.
2262
- # Note: This method is overwritten by the Validation module that'll make sure that updates made with this method
2263
- # aren't subjected to validation checks. Hence, attributes can be updated even if the full object isn't valid.
2458
+ # Updates a single attribute and saves the record without going through the normal validation procedure.
2459
+ # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
2460
+ # in Base is replaced with this when the validations module is mixed in, which it is by default.
2264
2461
  def update_attribute(name, value)
2265
2462
  send(name.to_s + '=', value)
2266
- save
2463
+ save(false)
2267
2464
  end
2268
2465
 
2269
2466
  # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
@@ -2356,10 +2553,25 @@ module ActiveRecord #:nodoc:
2356
2553
  end
2357
2554
 
2358
2555
  # Allows you to set all the attributes at once by passing in a hash with keys
2359
- # matching the attribute names (which again matches the column names). Sensitive attributes can be protected
2360
- # from this form of mass-assignment by using the +attr_protected+ macro. Or you can alternatively
2361
- # specify which attributes *can* be accessed with the +attr_accessible+ macro. Then all the
2556
+ # matching the attribute names (which again matches the column names).
2557
+ #
2558
+ # If +guard_protected_attributes+ is true (the default), then sensitive
2559
+ # attributes can be protected from this form of mass-assignment by using
2560
+ # the +attr_protected+ macro. Or you can alternatively specify which
2561
+ # attributes *can* be accessed with the +attr_accessible+ macro. Then all the
2362
2562
  # attributes not included in that won't be allowed to be mass-assigned.
2563
+ #
2564
+ # class User < ActiveRecord::Base
2565
+ # attr_protected :is_admin
2566
+ # end
2567
+ #
2568
+ # user = User.new
2569
+ # user.attributes = { :username => 'Phusion', :is_admin => true }
2570
+ # user.username # => "Phusion"
2571
+ # user.is_admin? # => false
2572
+ #
2573
+ # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
2574
+ # user.is_admin? # => true
2363
2575
  def attributes=(new_attributes, guard_protected_attributes = true)
2364
2576
  return if new_attributes.nil?
2365
2577
  attributes = new_attributes.dup
@@ -2369,7 +2581,11 @@ module ActiveRecord #:nodoc:
2369
2581
  attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes
2370
2582
 
2371
2583
  attributes.each do |k, v|
2372
- k.include?("(") ? multi_parameter_attributes << [ k, v ] : send(k + "=", v)
2584
+ if k.include?("(")
2585
+ multi_parameter_attributes << [ k, v ]
2586
+ else
2587
+ respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
2588
+ end
2373
2589
  end
2374
2590
 
2375
2591
  assign_multiparameter_attributes(multi_parameter_attributes)
@@ -2532,11 +2748,14 @@ module ActiveRecord #:nodoc:
2532
2748
  end
2533
2749
 
2534
2750
  def convert_number_column_value(value)
2535
- case value
2536
- when FalseClass; 0
2537
- when TrueClass; 1
2538
- when ''; nil
2539
- else value
2751
+ if value == false
2752
+ 0
2753
+ elsif value == true
2754
+ 1
2755
+ elsif value.is_a?(String) && value.blank?
2756
+ nil
2757
+ else
2758
+ value
2540
2759
  end
2541
2760
  end
2542
2761
 
@@ -2555,7 +2774,7 @@ module ActiveRecord #:nodoc:
2555
2774
  removed_attributes = attributes.keys - safe_attributes.keys
2556
2775
 
2557
2776
  if removed_attributes.any?
2558
- logger.debug "WARNING: Can't mass-assign these protected attributes: #{removed_attributes.join(', ')}"
2777
+ log_protected_attribute_removal(removed_attributes)
2559
2778
  end
2560
2779
 
2561
2780
  safe_attributes
@@ -2570,6 +2789,10 @@ module ActiveRecord #:nodoc:
2570
2789
  end
2571
2790
  end
2572
2791
 
2792
+ def log_protected_attribute_removal(*attributes)
2793
+ logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}"
2794
+ end
2795
+
2573
2796
  # The primary key and inheritance column can never be set by mass-assignment for security reasons.
2574
2797
  def attributes_protected_by_default
2575
2798
  default = [ self.class.primary_key, self.class.inheritance_column ]
@@ -2723,7 +2946,7 @@ module ActiveRecord #:nodoc:
2723
2946
  end
2724
2947
 
2725
2948
  def object_from_yaml(string)
2726
- return string unless string.is_a?(String)
2949
+ return string unless string.is_a?(String) && string =~ /^---/
2727
2950
  YAML::load(string) rescue string
2728
2951
  end
2729
2952