dantswain
dantswain

Reputation: 5467

Rails instance variable for record

Is there any built-in way to attach an instance variable to a record? For example, suppose I have a User class with a foo attr_accessor:

class User < ActiveRecord::Base
  ...
  attr_accessor :foo
end

If I do

u = User.first
u.foo = "bar"
u.foo # -> "bar"
User.find(1).foo # => nil

This is the correct behavior and I understand why: the instance variable exists for the instance (in memory) and not for the record. What I want is something like a record variable, so that in the above example u.foo and User.find(1).foo return the same value within the same instance of my application. I don't want persistence: it's not appropriate for foo to be a column in the table, it's just appropriate for foo to return the same value for the same record during the life cycle of e.g., a controller action, console session, etc. Nor do I want a class variable via cattr_accessor, because there's no reason that User.find(1).foo should be the same as User.find(2).foo.

The best bet I can come up with is to fake it with a class variable array and instance methods to get/set the appropriate element of the array:

class User < ActiveRecord::Base

  cattr_accessor :all_the_foos
  self.all_the_foos = Array.new

  def foo
    self.all_the_foos[id]
  end

  def foo= new_foo
    self.all_the_foos[id] = new_foo
  end

end

This ALMOST works, but it doesn't work with un-saved records, e.g. User.new.foo = "bar" fails.

Upvotes: 2

Views: 1335

Answers (2)

Harish Shetty
Harish Shetty

Reputation: 64363

You can use Thread.current to set variables that are active within the context of a controller action invocation. Your current implementation doesn't guarantee context reset across calls.

class User < ActiveRecord::Base

  after_create    :set_thread_var

  def foo
    new_record? ? @foo : Thread.current["User-foo-#{id}"]
  end

  def foo=(val)
    new_record? ? (@foo = val) : (Thread.current["User-foo-#{id}"] = val)
  end

private

  def set_thread_var
    Thread.current["User-foo-#{id}"] = @foo if defined?(@foo)
  end

end

Upvotes: 2

Jordan Running
Jordan Running

Reputation: 106027

I can't think of a better idea than your class variable solution. To solve your "new" problem I'd use after_initialize.

class User < ActiveRecord::Base
  after_initialize :init_foos

  cattr_accessor :all_the_foos

  def foo
    self.all_the_foos[id]
  end

  def foo= new_foo
    self.all_the_foos[id] = new_foo
  end

  private
  def init_foos
    @@all_the_foos ||= []
  end
end

Upvotes: 1

Related Questions