Peter Marklund's Home |
Ruby Gotcha: Default Values for the options Argument Hash
Like in Java, method arguments in Ruby are passed by reference rather than by copy. This means it's possible for the method to modify the arguments that it receives. This can happen unintentionally and be a very unpleasant surprise for the caller. A good example of this is the typical options = {} at the end of the method argument list. If you set a default value in that hash then that is a potential issue for the caller when the options hash is reused in subsequent calls (i.e. in a loop). See below for an example:
[:to_table] ||= from_column.to_s[/^(.+)_id$/, 1].pluralize
execute ["alter table ",
"add constraint ",
"foreign key ( )",
"references (id)",
""].join(" ")
end
from_columns.each do |from_column|
foreign_key(from_table, from_column, options)
end
end
options
In the first invocation of foreign_key options[:to_table] will be set (if it isn't set already) to the destination table of the first column. The options[:to_table] value will be retained throughout the loop causing all foreign keys to point to the same table. The fix is to make to_table a local variable or to do add an "options = options.dup" line at the beginning of the method.
Lesson learned - avoid modifying the options hash and any other arguments that the caller doesn't expect will be modified.