Controller Instance Variables

Values of instance variables set within an action of a controller are available in the template associated with that action. I was thinking (hoping?) that the values of instance variables set within a controller but outside of any action would be universally available to all the templates associated with that controller. Apparently that is not the case. Two questions: (1) Why is that? (2) Is there some way (other than using a global variable) that I can set a variable in a controller and have its value available to all templates associated with that controller?

Thanks for any input.

        ... doug

Values of instance variables set within an action of a controller are available in the template associated with that action. I was thinking (hoping?) that the values of instance variables set within a controller but outside of any action would be universally available to all the templates associated with that controller. Apparently that is not the case. Two questions: (1) Why is that? (2) Is there some way (other than using a global variable) that I can set a variable in a controller and have its value available to all templates associated with that controller?

it might clear things up if you pastied what you've been trying.

Fred

Doug Jolley wrote:

Values of instance variables set within an action of a controller are available in the template associated with that action. I was thinking (hoping?) that the values of instance variables set within a controller but outside of any action would be universally available to all the templates associated with that controller. Apparently that is not the case. Two questions: (1) Why is that?

Perhaps a more basic question is: why is the template able to access the private variables in the controller at all? In other words, if you define a ruby class like this:

class A   def initialize       @var = 10   end end

then all the other methods in class A can access @var, but an object of class B cannot access @var. I think a template is over in class B somewhere.

(2) Is there some way (other than using a global variable) that I can set a variable in a controller and have its value available to all templates associated with that controller?

Sessions?

Rails does some magic to make that happen. Here is a blog post that explains the basics of how this happens:

http://www.neeraj.name/blog/articles/719-how-controller-and-view-shar

Thanks. The blog post explains the magic that Rails uses to make the instance variables contained in an action visible in the view associated with that action. I just need to figure out how I can set a value in a controller and make that value available to all of the views in that controller.

Thanks again.

          ... doug

I am having difficulty imagining why you would want an instance variable of a controller that is not associated with an action, what else do controllers do? Are you sure it is not an instance variable of a model that you should be using? Colin

Is this something that you can do with a before_filter?

I am having difficulty imagining why you would want an instance variable of a controller that is not associated with an action, what else do controllers do?

The thing is that this particular instance variable is associated with *ALL* of the actions of the controller. I want to be DRY and only specify the value of the instance variable once and have that value available in all of the views associated with each of the actions of that controller.

Are you sure it is not an instance variable of a model that you should be using?

I don't think so. Models extend from ActiveRecord::Base. Therefore in my nieve view of the Rails world, models are only used with databases. I don't have a database involved. I just want to pass a common value from the controller to the views and I don't want to have to redundantly specify that value in each of the actions.

Thanks for the input.

        ... doug

Is this something that you can do with a before_filter?

I think you may be onto something. Conceptually, it sounds like your suggestion should work. I'm just not sure that I know how to go about implementing it. I'll play around with the concept and post if I am successful. In the meantime, if anyone has any specific input on precisely how to do this, I'd sure love to hear it.

Thanks for the input.

         ... doug

In that case would an @variable in before_filter, rather than an instance variable do? Colin

In that case would an @variable in before_filter, rather than an instance variable do?

I'm sorry. I'm not following. I thought an @variable was an instance variable.

As I see it, in order to implement your suggestion, I need to do something like:

before_filter do |controller|   The_meat_goes_here end

I don't know what to put where I have, "The_meat_goes_here".

       ... doug

> In that case would an @variable in before_filter, rather than an instance > variable do?

I'm sorry. I'm not following. I thought an @variable was an instance variable.

it is.

As I see it, in order to implement your suggestion, I need to do something like:

before_filter do |controller| The_meat_goes_here end

you don't want to use that form of before filter (you'd be setting instance variables on the wrong object). you want something like

before_filter :setup_stuff ... protected def setup_stuff ... end

As for what goes inside setup_stuff, that's up to you. If what you want to do is set instance variables you can do it in there.

Fred

Freddy Andersen wrote:

Is this something that you can do with a before_filter?

I looked up filters, and the description I read says a filter applies to only one action. That would mean the op would have to write the same before filter for every action. Or is there a way to refer to the same filter?

Colin Law wrote:

I am having difficulty imagining why you would want an instance variable of a controller that is not associated with an action...

That does not summarize what the op wants to do--quite the opposite. The op wants one instance variable to be associated with *every* action. For instance, in a ruby class you can do this:

class A   def initialize(val)      @var1 = val   end

  ... end

Then @var1 will be accessible inside any method in class A. Also, if you write:

class A   def meth(val2)      @var2 = val2   end

and then you call meth(), then @var2 will be created and it too will be accessible inside any method in class A. If rails worked the same way as ruby, then any instance variable defined in one action would be accessible inside any another action--which would also mean that all the views associated with all the actions could also access the instance variable.

Instance variables are really just global variables within a scope: the class scope. Like global variables, instance variables can be accessed anywhere inside their scope. Rails seems to be saying, "No global variables". But in this case, that rule is causing the op to "get wet" by having to write something like:

@var1 = 10

in every action. If the op wants to change that constant at some later date, then it means changing it in every action (which really isn't that bad--search and replace in one file), but that isn't DRY.

Are you sure it is not an instance variable of a model that you should be using?

I don't think so. Models extend from ActiveRecord::Base. Therefore in my nieve view of the Rails world, models are only used with databases.

Not true, not true. I'm reading AWDWR(3rd), which is very highly regarded in the rails world, and in their first non trivial example they create a model that is not hooked up to a database. They just navigate to the models directory, open up a blank file, and define a class which does not inherit from anything. Voila, a new model.

In my naive view, a model is any class that has methods that do something. The way I look at it is, the controller is very lazy--instead of calculating or doing stuff itself, it would rather call methods in other classes so that they have to do the work. A model that is hooked up to a database provides easy database access for the controller. But other models have methods that do other things, and when the controller needs to get those other things done, it calls the methods in those models.

they I don't have a database involved. I just want to pass a common value from the controller to the views and I don't want to have to redundantly specify that value in each of the actions.

Creating a model that is not hooked up to a database sounds like something that might work. It could be as simple as:

class Constants   attr_reader :val

  def initialize     @const = 10   end end

Then in an action, you could write:

c = Constants.new val = c.const

Of course, then you have to write that in every action--but if you ever want to change the value of the constant, you only have to change it in one place: in the Constants class.

7stud -- wrote:

Doug Jolley wrote:

Values of instance variables set within an action of a controller are available in the template associated with that action. I was thinking (hoping?) that the values of instance variables set within a controller but outside of any action would be universally available to all the templates associated with that controller. Apparently that is not the case. Two questions: (1) Why is that?

Perhaps a more basic question is: why is the template able to access the private variables in the controller at all?

Well, I fooled around and answered my own question. This is a toy example that I came up with (the output, variable names, and the comments make it self explanatory):

class A   def initialize     @var1 = 10     @var2 = 20   end

  def meth     @greeting = "hello"   end end

a = A.new p A.new.instance_variables

--output:-- ["@var1", "@var2"]

a.meth #>creates another instance variable(see definition of meth) names = a.instance_variables p names

--output:-- ["@var1", "@greeting", "@var2"]

names.each do |name|   print "#{name} = "   val = a.instance_variable_get(name)   print val   puts end puts

--output:-- @var1 = 10 @greeting = hello @var2 = 20

class B end

b = B.new #>Now attempt to insert the instance variables from obj a into object b:

names.each do |var_name| #>names contains a's instance var names from above

  var_val = a.instance_variable_get(var_name)   b.instance_variable_set(var_name, var_val)   #>Now if you inspect b:   #> p b   #>the output will be:   #> #<B:0x2420c @var1=10>   #>If you look closely, you can see @var1 in there   #>along with its value. However, @var1 ends up being a   #>private variable and it is inaccessible:   #> puts b.var1   #>results in an error message. So...

  B.class_eval("attr_reader :#{var_name[1..-1]}")   #>I want to insert the statement:   #> attr_reader :var1   #>into class B. The variable var_name is assigned a   #>string like "@var1", so var_name[1..-1] is equal to   #>"var1". Remember arrays and strings are indexed   #>starting at 0, also an index of -1 is the last character   #>in the string, so some_string[1..-1] returns the substring   #>starting at position 1 and ending at the last character--   #>in other words some_string[1..-1] is a copy of some_string   #>with the first character chopped off. So if var_name is   #>equal to "@var1" the string:   #> "attr_reader :#{var_name[1..-1]}"   #>is converted to:   #> "attr_reader :var1"   #>which is the string I am after. After that string is   #>eval'ed in class B, I should have access to the   #>private variable @var1. end

puts b.var1, b.var2, b.greeting #>Will it work?

--output:-- 10 20 hello

Great success!

Robert Walker wrote:

Rails does some magic to make that happen. Here is a blog post that explains the basics of how this happens:

http://www.neeraj.name/blog/articles/719-how-controller-and-view-share-instance-variables

In the linked article, there is an extra step: a's var names and values are stored in a hash. That is easy to incorporate:

a_names_vals = {} #<---added ****

names.each do |name|   print "#{name} = "   name_symbol = name.to_s   val = a.instance_variable_get(name_symbol)   print val   puts

  a_names_vals[name_symbol] = val #<---added *** end puts

p a_names_vals #<---added ***

--output:-- {"@var1"=>10, "@var2"=>20, "@greeting"=>"hello"}

class B end

b = B.new

#>Then this simplifies down to:

a_names_vals.each do |var_name, var_val|   b.instance_variable_set(var_name, var_val)   B.class_eval("attr_reader :#{var_name[1..-1]}") end

puts b.var1, b.var2, b.greeting

--output:-- 10 20 hello

7stud -- wrote:

Robert Walker wrote:

Rails does some magic to make that happen. Here is a blog post that explains the basics of how this happens:

http://www.neeraj.name/blog/articles/719-how-controller-and-view-share-instance-variables

In the linked article, there is an extra step: a's var names and values are stored in a hash. That is easy to incorporate:

a_names_vals = {} #<---added ****

names.each do |name|   print "#{name} = "   name_symbol = name.to_s   val = a.instance_variable_get(name_symbol)   print val   puts

  a_names_vals[name_symbol] = val #<---added *** end puts

p a_names_vals #<---added ***

--output:-- {"@var1"=>10, "@var2"=>20, "@greeting"=>"hello"}

class B end

b = B.new

#>Then this simplifies down to:

a_names_vals.each do |var_name, var_val|   b.instance_variable_set(var_name, var_val)   B.class_eval("attr_reader :#{var_name[1..-1]}") end

puts b.var1, b.var2, b.greeting

--output:-- 10 20 hello

Darn, I forgot to incorporate some changes I made before posting that second part (namely astring.to_s doesn't do anything):

class A   def initialize     @var1 = 10     @var2 = 20   end

  def meth     @greeting = "hello"   end end

a = A.new a.meth

a_names_vals = {} names = a.instance_variables

names.each do |name|   val = a.instance_variable_get(name)   a_names_vals[name] = val end

p a_names_vals

--output:-- {"@var1"=>10, "@var2"=>20, "@greeting"=>"hello"}

class B end b = B.new

a_names_vals.each do |var_name, var_val|   b.instance_variable_set(var_name, var_val)   B.class_eval("attr_reader :#{var_name[1..-1]}") end

puts b.var1, b.var2, b.greeting

--output:-- 10 20 hello

Freddy Andersen wrote: > Is this something that you can do with a before_filter?

I looked up filters, and the description I read says a filter applies to only one action. That would mean the op would have to write the same before filter for every action. Or is there a way to refer to the same filter?

That's not true. By default a filter applies to all actions. You can change that with the :only and :except options.

Then @var1 will be accessible inside any method in class A. Also, if you write:

class A def meth(val2) @var2 = val2 end

and then you call meth(), then @var2 will be created and it too will be accessible inside any method in class A. If rails worked the same way as ruby, then any instance variable defined in one action would be accessible inside any another action--which would also mean that all the views associated with all the actions could also access the instance variable.

rails is ruby. different web requests are handled by new instances of controllers though, so if you set a controller instance variable it won't be there on the next request.

At a basic level, before_filter :meth is pretty much the same as just calling meth at the top of every action, without the repetitiveness.

Of course, then you have to write that in every action--but if you ever want to change the value of the constant, you only have to change it in one place: in the Constants class.

You could also create an actual constant, eg

class Person   MINIMUM_AGE = 18 end

and then use Person::MINIMUM_AGE

Fred

class ProductsController < ApplicationController # GET /links # GET /links.xml

def initialize @val = 10 super end

and that seems to work as well. All the views for the actions in the ProductsController gain access to the @val instance variable. Is overriding the super class's initialize method bad form in rails?

you could do that. i would usually prefer before_filters (at least in part because you only have one initialize method so you'd have to chuck everything in there, whereas you can have separate, appropriately named before filters that setup related collections of variables.

Fred

So to sum it up is is as simple as this:

class MyController < ApplicationController

before_filter :setup_stuff … protected def setup_stuff @var1 = … @var2 = … end

end

then @var1 and @var2 will be available in views for all actions of MyController

Colin

So, I see that we have a working solution to this problem. THAT'S GREAT NEWS! Thanks a batch for all of the contributions.

I would, however, like to see if the solution couldn't be advanced one step further. The methodology that we have working so far is to define a method as a filter somewhere and then pass the name of that method as a symbol to before_filter. Obviously, this requires a separate definition of the method. My question is this: Can we somehow do this whole thing in a single step by passing the before_filter a block rather than a symbol? before_filter is supposed to accept a block. So, conceptually, my understanding is that we would need some code that looks something like this:

before_filter do |controller|   some_code_here end

Can anyone please tell me what code I would need to supply as a replacement for some_code_here in order to get this thing to work using a block.

Thanks.

          ... doug

A key difference with using the block form is that (because blocks are closures) self will be the class, not the controller instance (which is why you get passed the controller as a parameter). For example you can't do

before_filter do |controller|   @val = true end

because that will set the instance variable in the wrong object. you could use controller.instance_variable_set but that's just disgusting. so you're left with

before_filter do |controller|   controller.some_method end ... def some_method

end

at which point you might as well do before_filter :some_method. Plus I like giving things names when appropriate.

Fred

because that will set the instance variable in the wrong object. you could use controller.instance_variable_set but that's just disgusting.

Well now, hold on there a minute, Fred. :slight_smile: I kind of like it. In fact, it's exactly what I asked for. I don't find it all that disgusting. Anyway, I now know how to do it both ways and I can decide which way I like the better. Thanks to all for the input in this topic.

          ... doug