Search code examples
ruby-on-railshelperinstance-variables

Rails instance variables not shared between helpers in ApplicationController and app/helpers


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.


Solution

  • 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.