aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason Stirk <jason@reinteractive.net>2013-01-03 22:26:53 +1100
committerJason Stirk <jason@reinteractive.net>2013-01-04 10:34:08 +1100
commit8d98c83bbceb905cd65c487cfa714d2fca92501c (patch)
tree6db9a93e3835f1fc2c82fb1cd29319108573f204
parenta664686e47fb8fb2ffa3e512bdbd380face4e577 (diff)
downloadrails-8d98c83bbceb905cd65c487cfa714d2fca92501c.tar.gz
rails-8d98c83bbceb905cd65c487cfa714d2fca92501c.tar.bz2
rails-8d98c83bbceb905cd65c487cfa714d2fca92501c.zip
Fix undefined method `to_i' introduced since 3.2.8
This commit fixes a bug introduced in 96a13fc7 which breaks behaviour of integer fields. In 3.2.8, setting the value of an integer field to a non-integer (eg. Array, Hash, etc.) would default to 1 (true) : # 3.2.8 p = Post.new p.category_id = [ 1, 2 ] p.category_id # => 1 p.category_id = { 3 => 4 } p.category_id # => 1 In 3.2.9 and above, this will raise a NoMethodError : # 3.2.9 p = Post.new p.category_id = [ 1, 2 ] NoMethodError: undefined method `to_i' for [1, 2]:Array Whilst at first blush this appear to be sensible, it combines in bad ways with scoping. For example, it is common to use scopes to control access to data : @collection = Posts.where(:category_id => [ 1, 2 ]) @new_post = @collection.new In 3.2.8, this would work as expected, creating a new Post object (albeit with @new_post.category_id = 1). However, in 3.2.9 this will cause the NoMethodError to be raised as above. It is difficult to avoid triggering this error without descoping before calling .new, breaking any apps running on 3.2.8 that rely on this behaviour. This patch deviates from 3.2.8 in that it does not retain the somewhat spurious behaviour of setting the attribute to 1. Instead, it explicitly sets these invalid values to nil : p = Post.new p.category_id = [ 1, 2 ] p.category_id # => nil This also fixes the situation where a scope using an array will "pollute" any newly instantiated records. @new_post = @collection.new @new_post.category_id # => nil Finally, 3.2.8 exhibited a behaviour where setting an object to an integer field caused it to be coerced to "1". This has not been retained, as it is spurious and surprising in the same way that setting Arrays and Heshes was : c = Category.find(6) p = Post.new # 3.2.8 p.category_id = c p.category_id # => 1 # This patch p.category_id = c p.category_id # => nil This commit includes explicit test cases that expose the original issue with calling new on a scope that uses an Array. As this is a common situation, an explicit test case is the best way to prevent regressions in the future. It also updates and separates existing tests to be explicit about the situation that is being tested (eg. AR objects vs. other objects vs. non-integers)
-rw-r--r--activerecord/CHANGELOG.md7
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb6
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb12
-rw-r--r--activerecord/test/cases/column_test.rb20
4 files changed, 37 insertions, 8 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index db8fa303be..49473dc92a 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,6 +1,11 @@
## Rails 4.0.0 (unreleased) ##
-* Rename `update_attributes` to `update`, keep `update_attributes` as an alias for `update` method.
+* Fix undefined method `to_i` when calling `new` on a scope that uses an Array.
+ Fixes #8718, #8734.
+
+ *Jason Stirk*
+
+* Rename `update_attributes` to `update`, keep `update_attributes` as an alias for `update` method.
This is a soft-deprecation for `update_attributes`, although it will still work without any
deprecation message in 4.0 is recommended to start using `update` since `update_attributes` will be
deprecated and removed in future versions of Rails.
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index fd36a5b075..51d3acaff8 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -206,7 +206,11 @@ module ActiveRecord
when TrueClass, FalseClass
value ? 1 : 0
else
- value.to_i
+ if value.respond_to?(:to_i)
+ value.to_i
+ else
+ nil
+ end
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 97a3d57fe0..3a6da0e59f 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -63,6 +63,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal apple.id, citibank.firm_id
end
+ def test_id_assignment
+ apple = Firm.create("name" => "Apple")
+ citibank = Account.create("credit_limit" => 10)
+ citibank.firm_id = apple
+ assert_nil citibank.firm_id
+ end
+
def test_natural_assignment_with_primary_key
apple = Firm.create("name" => "Apple")
citibank = Client.create("name" => "Primary key client")
@@ -567,6 +574,11 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal new_firm.name, "Apple"
end
+ def test_attributes_are_set_without_error_when_initialized_from_belongs_to_association_with_array_in_where_clause
+ new_account = Account.where(:credit_limit => [ 50, 60 ]).new
+ assert_nil new_account.credit_limit
+ end
+
def test_reassigning_the_parent_id_updates_the_object
client = companies(:second_client)
diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb
index 2124c256fa..dc1b30261c 100644
--- a/activerecord/test/cases/column_test.rb
+++ b/activerecord/test/cases/column_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'models/company'
module ActiveRecord
module ConnectionAdapters
@@ -40,13 +41,20 @@ module ActiveRecord
def test_type_cast_non_integer_to_integer
column = Column.new("field", nil, "integer")
- assert_raises(NoMethodError) do
- column.type_cast([])
- end
+ assert_nil column.type_cast([1,2])
+ assert_nil column.type_cast({1 => 2})
+ assert_nil column.type_cast((1..2))
+ end
- assert_raises(NoMethodError) do
- column.type_cast(Object.new)
- end
+ def test_type_cast_activerecord_to_integer
+ column = Column.new("field", nil, "integer")
+ firm = Firm.create(:name => 'Apple')
+ assert_nil column.type_cast(firm)
+ end
+
+ def test_type_cast_object_without_to_i_to_integer
+ column = Column.new("field", nil, "integer")
+ assert_nil column.type_cast(Object.new)
end
def test_type_cast_time