Persistent Instance Variables in Ruby on Rails

I just recently built a rake task in my project where I wanted to loop through a list of Users, queue up some data for each one, then loop back through them again to process the data that had been queued. I didn’t need the data to live for very long, but I needed it for 2 separate requests.

The problem with this is that in Ruby on Rails, instance variables are not persistent across requests. This means their scope is limited to one usage of an object and the next time you call for that object, the variable is gone.

Unfortunately, the only solution I could imagine for my problem without persistent instance variables was to create a massive join table to connect my generated data between Users. Not only would this have been a lot of work, it would have been a big waste considering the data would have only lived for about a minute as the rake task was executed.

I wanted to improve/increase the scope of instance variables, and so my solution was to take advantage of Class Variables.

At the bottom of my User class, I now have the following:

  protected
    def late_employees
      @@late_employees ||= {}
      @@late_employees[self] ||= {}
      @@late_employees[self]
    end
    
    def late_team_members
      @@late_team_members ||= {}
      @@late_team_members[self] ||= {}
      @@late_team_members[self]
    end
    
    def clear_late_emails
      @@late_employees[self] = {}
      @@late_team_members[self] = {}
    end

As you can see, each element of the Class Variable hashes are referenced by the current instance. So when I call u.late_employees, it’s always the same across requests until either I call u.clear_late_emails or the server dies.

To use them, just treat them like any normal instance method/variable. Here is an example of how you can do that:

def employee_late(user, days)
  if user.manager == self
    late_team_members[user] = days
  else
    late_employees[user] = days
  end
end

def has_late_employees?
  !(late_employees.empty? and late_team_members.empty?)
end

def send_late_employee_emails
  if has_late_employees?
    EvaluationNotifier.deliver_late_employee_notification(self)
  end
  
  length = late_employees.length + late_team_members.length
  clear_late_emails
  
  length
end

Voila, instance variables that are persistent across requests!

9 comments ↓

#1 hvm on 04.04.08 at 11:03 pm

Thank you, however, How do you use the variable ?, I meant how do you store data into and get data out of those variables? could you give an example of that?

#2 Glenn Ford on 04.05.08 at 9:51 am

Certainly! I’ll append it to the blog itself so that it’s easier to follow. Thank you for the question.

#3 Gwyn Morfey on 04.09.08 at 4:57 am

What do your deployments look like? Does this work if Mongrel#1 handles the first request, and Mongrel#2 handles the second one?

#4 Glenn Ford on 04.09.08 at 11:12 am

Gwyn, great question! I tested locally starting up two environments to be sure and found that they do not share instance variables across servers. I’m pretty sure that instance/class variables are stored in memory, meaning they will only be available to the local environment.

The catch is that grabbing the same model on each environment will give you two separate instances, and thus two separate sets of instance variables.

If you need data to extend to everywhere, including across your separate Mongrel’s, you’ll need to store it in the db.

#5 Deepak on 08.27.08 at 4:45 am

Its good

#6 Deepak on 08.27.08 at 4:46 am

How i take data from static variable which i put in model,can we do caching statically

#7 Sam on 10.07.08 at 2:07 am

Hi, I am also facing the same problem in one of my projects, but with only one variable and I am not using a list. How about then.. how can persist the values..

#8 Glenn Ford on 10.07.08 at 9:22 am

@Sam: If it’s only one variable, just set it in a class method:

def self.project_name=(new_name)
   @@project_name = new_name
end
def self.project_name
   @@project_name
end

This should be accessible from anywhere by calling ClassName.project_name

Does that help?

#9 Riccardo on 10.22.08 at 3:15 am

Hi,

i’m trying to use class variable but i’m not able to do that.. can anyone explain me how can i set and retrieve the value of the variable?

Thanks!