Corey InnisCorey Innis
Standup 05/30/2008
edit Posted by Corey Innis on Friday May 30, 2008 at 06:25PM

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
    end
    

    script/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
      end
      
    • Have 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
    end
    

    script/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):4
    
  • ActiveRecord 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
    end
    

    script/console:

    Loading development environment (Rails 2.0.2)
    >> instance = Model.new
    => #<Model id: nil, field_name: nil>
    >> instance.field_name = 'lala'
    => "lala"
    

Comments

  1. coderrr coderrr on May 30, 2008 at 08:22PM

    The last example is due to Ruby, not AR.

    class X; def x=(a); return :huh?; end; end

    X.new.x = 5 => 5

  2. coderrr coderrr on May 30, 2008 at 09:13PM

    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
    
  3. Adam Adam on June 29, 2008 at 08:29AM

    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

  4. Adam Adam on June 29, 2008 at 08:30AM

    Nice of my browser to strip out my carriage returns for me.

  5. Arthur Arthur on July 04, 2008 at 08:16AM

    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 :)