Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing `attr_accessor`-like method with history

class Class
  def attr_accessor_with_history(attr_name)
    attr_name = attr_name.to_s
    attr_reader attr_name
    attr_reader attr_name + "_history"
    class_eval %Q{
      def #{attr_name}=(new_value)
        @#{attr_name}_history = [nil] if @#{attr_name}_history.nil?
        @#{attr_name}_history << @#{attr_name} = new_value
      end
    }
  end
end

class Example
  attr_accessor_with_history :foo
  attr_accessor_with_history :bar
end

There is Class.attr_accessor_with_history method that provides the same functionality as attr_accessor but also tracks every value the attribute has ever had.

> a = Example.new; a.foo = 2; a.foo = "test"; a.foo_history
=> [nil, 2, "test"]

But,

> a = Example.new; a.foo_history
=> nil

and it should be [nil.

How can I define single initialize method for Example class where each …_history value will be initialize as [nil]?

like image 833
ДМИТРИЙ МАЛИКОВ Avatar asked Feb 03 '26 12:02

ДМИТРИЙ МАЛИКОВ


1 Answers

I think, your best bet is to define a custom reader for history (along with your custom writer).

class Class
  def attr_accessor_with_history(attr_name)
    attr_name = attr_name.to_s
    attr_reader attr_name
    class_eval %Q{
      def #{attr_name}_history
        @#{attr_name}_history || [nil] # give default value if not assigned
      end

      def #{attr_name}=(new_value)
        @#{attr_name}_history ||= [nil] # shortcut, compare to your line
        @#{attr_name}_history << @#{attr_name} = new_value
      end
    }
  end
end

class Example
  attr_accessor_with_history :foo
  attr_accessor_with_history :bar
end

a = Example.new; a.foo = 2; a.foo = "test"; 
a.foo_history # => [nil, 2, "test"]

a = Example.new
a.foo_history # => [nil]

Edit:

Here's a slightly more verbose snippet, but it doesn't use class_eval (which is frowned upon, when used without necessity).

class Class
  def attr_accessor_with_history(attr_name)
    attr_name = attr_name.to_s
    attr_reader attr_name

    define_method "#{attr_name}_history" do
      instance_variable_get("@#{attr_name}_history") || [nil]
    end

    define_method "#{attr_name}=" do |new_value|
      v = instance_variable_get("@#{attr_name}_history")
      v ||= [nil]
      v << new_value

      instance_variable_set("@#{attr_name}_history", v)
      instance_variable_set("@#{attr_name}", new_value)
    end
  end
end
like image 66
Sergio Tulentsev Avatar answered Feb 05 '26 08:02

Sergio Tulentsev