How to do attr_accessor_with_default in ruby?

Some code that I had that used attr_accessor_with_default in a rails model is now giving me a deprecation warning, telling me to "Use Ruby instead!"

So, thinking that maybe there was a new bit in ruby 1.9.2 that made attr_accessor handle defaults, I googled it, but I don't see that. I did see a bunch of methods to override attr_accessor to handle defaults though.

Is that what they mean when they tell me to "Use Ruby?" Or am I supposed to write full getters/setters now? Or is there some new way I can't find?

Answers


attr_accessor :pancakes

def after_initialize
     return unless new_record?
     self.pancakes = 11
end

This ensures that the value is initialized to some default for new record only.


This apidock page suggests to just do it in the initialize method.

class Something
  attr_accessor :pancakes

  def initialize 
    @pancakes = true
    super
  end
end

Don't forget to call super especially when using ActiveRecord or similar.


Since you probably know your data quite well, it can be quite acceptable to assume nil is not a valid value.

This means you can do away with an after_initialize, as this will be executed for every object you create. As several people have pointed out, this is (potentially) disastrous for performance. Also, inlining the method as in the example is deprecated in Rails 3.1 anyway.

To 'use Ruby instead' I would take this approach:

attr_writer :pancakes

def pancakes
  return 12 if @pancakes.nil?
  @pancakes
end

So trim down the Ruby magic just a little bit and write your own getter. After all this does exactly what you are trying to accomplish, and it's nice and simple enough for anyone to wrap his/her head around.


This is an ooooold question, but the general problem still crops up - and I found myself here.

The other answers are varied and interesting, but I found problems with all of them when initializing arrays (especially as I wanted to be able to use them at a class level before initialize was called on the instance). I had success with:

attr_writer :pancakes

def pancakes
  @pancakes ||= []
end

If you use = instead of ||= you will find that the << operator fails for adding the first element to the array. (An anonymous array is created, a value is assigned to it, but it's never assigned back to @pancakes.)

For example:

obj.pancakes
#=> []
obj.pancakes << 'foo'
#=> ['foo']
obj.pancakes
#=> [] 
#???#!%$#@%FRAK!!!

As this is quite a subtle problem and could cause a few head scratches, I thought it was worth mentioning here.

This pattern will need to be altered for a bool, for example if you want to default to false:

attr_writer :pancakes

def pancakes
  @pancakes.nil? ? @pancakes = false : @pancakes
end

Although you could argue that the assignment isn't strictly necessary when dealing with a bool.


There's nothing magical in 1.9.2 for initializing instance variables that you set up with attr_accessor. But there is the after_initialize callback:

The after_initialize callback will be called whenever an Active Record object is instantiated, either by directly using new or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record initialize method.

So:

attr_accessor :pancakes
after_initialize :init

protected
def init
    @pancakes = 11
end

This is safer than something like this:

def pancakes
    @pancakes ||= 11
end

because nil or false might be perfectly valid values after initialization and assuming that they're not can cause some interesting bugs.


I'm wondering if just using Rails implementation would work for you:

http://apidock.com/rails/Module/attr_accessor_with_default

def attr_accessor_with_default(sym, default = nil, &block)
  raise 'Default value or block required' unless !default.nil? || block
  define_method(sym, block_given? ? block : Proc.new { default })
  module_eval(      def #{sym}=(value)                        # def age=(value)        class << self; attr_reader :#{sym} end  #   class << self; attr_reader :age end        @#{sym} = value                         #   @age = value      end                                       # end, __FILE__, __LINE__ + 1)
end

You can specify default values for instances of any class (not only ActiveRecords) after applying patch to Module:

class Zaloop

  attr_accessor var1: :default_value, var2: 2

  def initialize
    self.initialize_default_values
  end

end

puts Zaloop.new.var1 # :default_value

Patch for module:

Module.module_eval do

  alias _original_attr_accessor attr_accessor
  def attr_accessor(*args)
    attr_names = extract_default_values args
    _original_attr_accessor *attr_names
  end

  alias _original_attr_reader attr_reader
  def attr_reader(*args)
    attr_names = extract_default_values args
    _original_attr_reader *attr_names
  end

  def extract_default_values(args)
    @default_values ||= {}
    attr_names = []
    args.map do |arg|
      if arg.is_a? Hash
        arg.each do |key, value|
          define_default_initializer if @default_values.empty?
          @default_values[key] = value
          attr_names << key
        end
      else
        attr_names << arg
      end
    end
    attr_names
  end

  def define_default_initializer
    default_values = @default_values
    self.send :define_method, :initialize_default_values do
      default_values.each do |key, value|
        instance_variable_set("@#{key}".to_sym, value)
      end
    end
  end

  def initialize_default_values
    # Helper for autocomplete and syntax highlighters
  end

end

Need Your Help

Linux equivalent of the Mac OS X "open" command

linux macos command-line command-line-interface

I've found the "open" command in Mac OS X very handy in the command line. From "man open":

Rest vs. Soap. Has REST a better performance?

web-services performance rest soap

I read some questions already posted here regarding Soap and Rest