I have an instance variable that I set in a controller.
#cost=[1,2,3]
Eventually a view gets called that calls a partial that in turn calls a helper in app/helpers.
In the debugger I can see that #cost is still [1,2,3] when I enter that helper method.
Inside the helper method, I set #cost to [4,5,6] and then call a helper method set_cost that is defined in application_controller.rb.
class ApplicationController < ActionController::Base
helper_method :set_cost
def set_cost
#cost=[7,8,9]
end
end
When I enter set_cost, I can see in the debugger that #cost has reverted to [1,2,3].
When I return back to the helper method in app/helpers, I can see in the debugger that #cost is no longer [7,8,9], but has reverted to [4,5,6].
If I call set_cost directly from the controller, #cost is [7,8,9] when control returns back to the controller.
Is there a way that I can access and manipulate the instance variable across the controller, view, partial, helper method in app/helpers, and helper method in application_controller.rb such that the reference / values stay consistent? It's as though it's changing the scope and making local versions of the variable.
I realize there may be design and practical benefits to passing #cost to set_cost (my actual function is more complex than what I showed), updating it, and then returning it to the original function for assignment there, but even if that accomplishes what I want, I'd like to understand the Rails design better. I thought an instance variable was akin to a global variable, but apparently not.
Thank you for your help.
An instance variable is not a global variable - it is tied to a particular instance (hence the name).
Rails allows you to access controller instance variables from a view. This works by copying the controller instance variables: the view_assigns methods in AbstractController::Rendering creates a hash of all the instance variables. Later on, the view object uses that hash to recreate the instance variables. When you say that it's as if there are local copies of the variable being made, that's pretty much exactly what is happening.
The values are shared, i.e. initially in both the view and the controller #cost.object_id will be the same. If your helper were to do something like #cost.replace(...) then you would see that change everywhere, however when you reassign #cost that assignment does not affect the controller (and vice-versa - the copy of controller instance variables to the view is a once-off operation).
"Normal" helpers end up being instance methods of the view context, however ones created by helper_method are controller instance methods - rails basically defines a view helper that looks like
def set_cost
controller.set_cost
end
You can probably dodge these issues by mutating #cost rather than reassigning it, but I find the idea that a view helper might mutate state like this surprising.
Related
I'm trying to understand how the controller class and the .html.erb view files in Rails are connected, and how the view accesses data in the controller methods. For example, I have the following controller class:
class SomeController < ApplicationController
def show
# defining some data to access in the view
x = 1
#y = 2
end
end
If in the corresponding .html.erb view file, I try to access #y, this works fine
<p> <%= #y %> </p>
However, if I try to access x, it gives an error
<p> <%= x %> </p>
undefined local variable or method 'x'
Conceptually, why #y is accessible in the .html.erb view, but x is not.
PS: I should add that I know variables with # indicate instance variables in Ruby, so (I think) #y would be an instance variable of the instance of SomeController. However, I'm unclear how this affects what .html.erb view file has access to from SomeController.
In RoR if you declare a variable in your controller as an instance variable #y it becomes available to your view.
On the other hand x is the local variable and is only accessible within it's scope.
This is a matter of scope (scope defines where in a program a variable is accessible). Ruby has four types of variable scope, local, global, instance and class.
In your case:
x is a local variable, they are local to the code(method, loop etc.) in which they are declared.
#y is an instance variable, they are visible anywhere in the instance of the class in which it has been defined.
You can access x if you explicitly provide it as local:
def show
render locals: {
x: 1
}
end
The recommended way to use variables in views is to use instance variables. That's because if you try to use a local variable and you didn’t pass a value in, you would get an error. But with instance variables, you would get a nil.
For more information on how to use local variables in a view, you can check the link.
As per the description and the answers mentioned above it is clear that instance variables are accessible in the views.
Answering the below mentioned point
Conceptually, why #y is accessible in the .html.erb view, but x is
not.
While rendering the views, instance variables and their values are taken from the controller and passed to the view initializer which assigns them to the view instance.
This is done using these ruby methods:
instance_variables - gets names of instance variables
instance_variable_get(variable_name) - gets value of an instance variable
instance_variable_set(variable_name, variable_value) - sets value of an instance variable
[https://github.com/rails/rails/blob/0c5552a3dd28e35cce64462765cc41c5355db0f1/actionpack/lib/abstract_controller/rendering.rb#L138-L145][1]
In the above link the method named
"view_assigns" -> collects the controller instance variable
"view_context passes" -> them to the views
"assign(new_assigns)" -> setting them in the view
As per your example, #y in the controller is not the same #y in the view, however at the very high level it seems like they're the same. Rails does a lot of magic under the hood, As you already know (if not), Rails believes in Convention over configuration, at this point Rails will look for all the instance variables in the controller and will copy them over to the views, even let's say you don't want them, it'll be there. Rails is exposing all the instance variables to the respective view.
So far, this has been my understanding, if I have missed something, please add more details.
About variables:
foo = 'bar' # this means that you declare a local variable, not accessible from view
#foo = 'bar' # means you declare an instance_variable, this will be available on view
Also you can define helper_method on controller that execute and returns some data, like helpers.
Within a model I have the following method:
def some_method
some_obj.new(view_c: view_context).create_some_links
end
An exception is thrown with the following message:
undefined local variable or method `view_context'
I am fully aware that it is not good practice to call view-related methods from your model, but nonetheless: Is it possible to access view_context from the model so that I can pass it along to a Plain Old Ruby Object (PORO) which creates some links?
Update: From the code I have above, one suggestion might be to simply create and call the PORO directly in the view. However: pretend that the code requires it to go through the model in order to create the right PORO.
view_context doc
Unless you pass the view_context to the method it is impossible:
model layer has nothing to do with view layer.
model itself can not possibly know anything about view context.
model has no access to view context.
Here's how you'd pass a view to the method (while being in the view):
#model_instance.some_method(self) # self is the view itself
Now, slight method change does the trick:
def some_method(view_context)
some_obj.new(view_c: view_context).create_some_links
end
I understand and appreciate that by putting # in front of a variable name in a Controller that it becomes available in whatever View is loaded. This is wonderfully useful, but I would like to understand the magic. How does it happen, and can it be stopped?
I am trying to DRY my CRUDdy resource controllers using inheritance, placing most of the logic in ApplicationController. The superclass should refer to the abstract variables #resource (for a single resource), #resources (for a collection of resources), and #parent_resource (for the parent resource when #resource is nested), but ideally the view would get more concrete names, for example;#customer, #customers, and #sales_territory respectively. Can this be done without sending duplicates of all objects (once in the abstract name, and once in the concrete name) to the view?
As I write this, the possibilities that come to mind are;
protected instance variables...does Ruby have such a thing, and
if so does the Controller magic hand them to the View?
setting the generic named variables to nil before render/redirect
using a protected empty method defined in the subclass to instead of
abstract named instance variables
What is the right choice in how to implement this?
What I'm assuming is happening here, is that there's a bunch of controllers in your app that literally just do the same thing and so you are wanting to make use of inheritance to DRY it up.
That being said, I'm not entirely sure the ApplicationController is the right place to dump all of this functionality as in the future if you had new controllers, they would also inherit all of this functionality without necessarily needing it.
I would do something like this:
Assuming you have controllers like this:
lions_controller.rb
tigers_controller.rb
hippos_controller.rb
and they pretty much have similar functionality... I would create a "Base" controller and then setup inheritance on child controllers. I would then also make an action that sets the "logical" defaults of child controllers, something like this.
AnimalsController.rb
class AnimalsController < ApplicationController
class_attribute :resource_class, :parent_resource_class
protected
def self.set_resource_attributes(options={})
self.resource_class = options[:resource_class]
self.parent_resource_class = options[:parent_resource_class]
end
end
LionsController.rb
class LionsController < AnimalsController
#call methods in AnimalsController here, start with setting the resource name
set_resource_attributes :resource_class => Lion, :parent_resource_class => Animal
end
and so on and so forth... the other thing that may be useful is to make use of the methods "instance_variable_set" so that you can set instance variable names in the view that actually make sense... You can make use of the class variables you just set to do this... so for example, lets re-open the AnimalsController.rb class:
class AnimalsController < ApplicationController
def show
instance_variable_set("##{self.resource_class.name.underscore}".to_sym, self.resource_class.find(params[:id]))
#... all the regular show stuff
end
end
This way, when you go to the lions#show path, what you will get in your view is access to a variable named #lion which will be set and contain an instance of the Lion class found through ActiveRecord.
Of course this pseudo code I threw in here can be cleaned up and DRY'd a bit more, but hopefully you get where I'm going with it. Hopefully this helps.
How and instance variable #variable defined in the controller's action can be called from its views ?
like
class UsersControllers
def index
#whythiscolavery=User.all
end
end
Now in /views/users/index.html.haml why variable #whythiscolavery is directly accessible in views defined in controllers action?
This query failed me in an important interview..
#whythiscolavery.each do
user.name
end
Quoting Obie Fernandez from The Rails 3 way:
The way Rails implements controller-to-view data handoffs is through
instance variables. Typically, a controller action initializes one or
more instance variables. Those instance variables can then be used by
the view.
There’s a bit of irony (and possible confusion for
newcomers) in the choice of instance variables to share data between
controllers and views. The main reason that instance variables exist
is so that objects (whether Controller objects, String objects, and so
on) can hold on to data that they don’t share with other objects. When
your controller action is executed, everything is happening in the
context of a controller object—an instance of, say, DemoController or
EventController. Context includes the fact that every instance
variable in the code belongs to the controller instance.
When the view template is rendered,
the context is that of a different object, an instance of
ActionView::Base. That instance has its own instance variables, and
does not have access to those of the controller object.
So instance
variables, on the face of it, are about the worst choice for a way for
two objects to share data. However, it’s possible to make it happen—or
make it appear to happen. What Rails does is to loop through the
controller object’s variables and, for each one, create an instance
variable for the view object, with the same name and containing the
same data.
It’s kind of labor-intensive, for the framework: It’s like
copying over a grocery list by hand. But the end result is that things
are easier for you, the programmer. If you’re a Ruby purist, you might
wince a little bit at the thought of instance variables serving to
connect objects, rather than separate them. On the other hand, being a
Ruby purist should also include understanding the fact that you can do
lots of different things in Ruby—such as copying instance variables in
a loop. So there’s nothing really un-Ruby-like about it. And it does
provide a seamless connection, from the programmer’s perspective,
between a controller and the template it’s rendering.
Views are basically methods of a controller, so they can access instance variables just like a regular plain old ruby class can. If you had a class
class Donut
def show
#delicious = "very very delicious"
end
private
def render_show
"donuts are #{#delicious}"
end
end
Donut.new.show
Erb (and haml, and js and all other renders) are handled in the same way.
Because in Ruby On Rails MVC architecture and "convention over configuration", instance variables are crossing models/views/controllers and helpers.
methods in a controller are public actions (unless they are protected). Instance variables assigned in that action are available in that view.
The haml syntax for this would be:
-#whythiscolavery.each do |user|
= user.name
The erb syntax would be
<%= #whythiscolavery.each do |user| %>
<%= user.name %>
<% end %>
if you're asking how to access the variable from the view, niiru's answer is good with the exception of an extra '=' sign in the erb syntax example. it should be:
<% #whythiscolavery.each do |user| %>
<%= user.name %>
<% end %>
if you're asking why it is possible that the variable is available in the view, it's because the controller makes them available to the view (they're copied to the view). this design shortcoming (maintaining state) prompted the creation of the gem decent_exposure: https://github.com/voxdolo/decent_exposure
I have the following code in my controller.
def index
#customer = Customer.find(params[:customer_id])
#current_orders = #customer.current_orders
#pending_orders = #customer.pending_orders
#previous_orders = #customer.previous_orders
#recurring_orders = #customer.recurring_orders
end
As you see, all the instance variables(#current_orders,#pending_orders,etc) can be obtain from #customer.
What is the best way to write this code?
Should I need to create these instance variable in controller or I just only used these through #customer variable in views.
If you reference the customer methods directly in the view you are tying the view directly to your model i.e. if for some reason you needed to change the name of the previous_orders method on customer you'd have to go through all your views and change it there, whereas with the instance variables you've used you'd only have to change it in your controllers. Using instance variables also makes your views more re-usable.
However, if you find yourself using loads of instance variables in your controllers it may be worth investigating adding another layer of abstraction in there to tidy things up.
In my opinion the example you've given is fine.