Alright here's a weird one that would have been easy to solve if I thought more about how Ruby works (and how it's not like Java:)).
So here was the issue, we were looping through a set of keys on a hash. Now, you should know that the keys on this hash were strings. So we had something like this
my_hash = {"color" => "blue", "size" => "medium" }
Now, on top of that we had a model that we'll call Person that had a "name" attribute and looked something like this:
class Person < ActiveRecord::Base
before_validation upcase_name
private
def upcase_name
name.upcase!
end
end
Ok, for those who don't know, the bang version of upcase, upcase!, uppercases a string in place - it modifies the actual string itself.
So here is the setup, we were looping through keys of the hash:
my_hash.keys.each do |name|
...
end
Now, first thing to know is that you CAN'T modify a key in a hash. Anytime you try to modify the keys of a hash, you get an error. So something like this would produce an error:
my_hash.keys.each do |name|
name.upcase! # produces frozen string error
end
That all makes sense.
What we were surprised to see was that the following code also produces a similar error:
my_hash.keys.each do |name|
Person.create(name: name) # produces frozen string error
end
What's going on here? Well it turns out that the "before_validation" callback tries to upcase the name. But unlike languages like Java, the string that is passed in is the exact same object that exists in the "my_hash.keys" array. (I'm purposely avoiding by-value and by-reference wording here, because I feel it can lead to confusion)
So the hash holds a frozen lock on that string, and then when that string gets passed into the Person constructor, and the callback tries to modify it - you get the frozen string error.
How do you fix it? Change your callback:
class Person < ActiveRecord::Base
before_validation upcase_name
private
def upcase_name
name = name.upcase
end
end
This version doesn't modify the original string but rather produces a new string.
Special thanks to d3chapma for helping me figure this one out. Check out his Gist here for more info.
Hope this helps out some peeps!