Interesting Things
ActiveRecord’s #method_missing takes precedence over private methods, which means you cannot simply mark “private” database-derived attributes.
code:# File: app/models/rock_star.rb # # == Schema Information # Schema version: 1 # # Table name: rock_stars # # id :integer not null, primary key # real_name :string(255) # band_name :string(255) # personal_life :string(255) # class RockStar < ActiveRecord::Base def method_missing(method, *arguments, &block) puts "I see you've sent my #{method} back and my ActiveRecords and they're all scratched" super end private def personal_life=(arg) puts "Vanish in the air you'll never find me" attributes[:personal_life] = arg end endscript/console:
Loading development environment (Rails 2.0.2) >> sting = RockStar.new(:real_name => 'Gordon Sumner', :band_name => 'The Police') I see you've sent my real_name= back and my ActiveRecords and they're all scratched => #<RockStar id: nil, real_name: "Gordon Sumner", band_name: "The Police", personal_life: nil> >> sting.personal_life = "I'll be watching you" I see you've sent my personal_life= back and my ActiveRecords and they're all scratched => "I'll be watching you"Potential solutions:
- Convention… name “private” database attributes with leading underscores
Exception:
class RockStar < ActiveRecord::Base def personal_life=(arg) raise "Protest is futile, nothing seems to get through" end endHave another? Post a comment.
||= (”or equal”) blows up you have a public “writer”, but a private “reader”; makes sense, but still worth a mention.
code:class Model < ActiveRecord::Base def field_name=(arg) @field_name = arg end private def field_name @field_name end endscript/console:
Loading development environment (Rails 2.0.2) >> instance = Model.new => #<Model id: nil, field_name: nil> >> instance.field_name = 'lala' => "lala" >> instance.field_name ||= 'dodo' NoMethodError: private method `field_name' called for #<Model:0x17df6d0> from /Library/Ruby/Gems/1.8/gems/activerecord-2.0.2/lib/active_record/attribute_methods.rb:205:in `method_missing' from (irb):4ActiveRecord writers always return the passed in argument, even if you define some other return value. This also makes sense — necessary for chaining, etc., but what the heck…
code:class Model < ActiveRecord::Base def field_name=(arg) @field_name = arg return "custom return value" end endscript/console:
Loading development environment (Rails 2.0.2) >> instance = Model.new => #<Model id: nil, field_name: nil> >> instance.field_name = 'lala' => "lala"
The last example is due to Ruby, not AR.
class X; def x=(a); return :huh?; end; end
>> X.new.x = 5
=> 5
May 30, 2008 at 8:22 pm
for the private method issue you can do something like this:
class ActiveRecord::Base
alias_method :old_mm, :method_missing
def method_missing m, *a, &b
raise NoMethodError, “its private!” if self.class.private_method_defined? m
old_mm m, *a, &b
end
end
May 30, 2008 at 9:13 pm
Re: ||= (“or equal”) blows up you have a public “writer”, but a private “reader”
The example you show doesn’t work because the caller doesn’t have access to the callee’s private methods. However, the truly annoying thing about ||= for private accessors is that it doesn’t work even when called from within the class. For example:
class Thing
def public_method
value ||= 1 # sets a local, does not call value=
self.value ||= 1 # error: explicit sender
end
private
attr_accessor :value
end
June 29, 2008 at 8:29 am
Nice of my browser to strip out my carriage returns for me.
June 29, 2008 at 8:30 am
It’s not only ActiveRecord Writers that return the passed argument, it’s all Ruby methods that end in in equals sign and are called “the default way”.
If you use #send to call a method, you will get the correct return value. Weirdness :)
July 4, 2008 at 8:16 am