danieljacky
danieljacky

Reputation: 187

Ruby on Rails Controller Instance Variable not shared

My 'new' action generates the cart object @cart out of a session. When I call the 'update' action via AJAX the @cart object doesn't exist. Why is it not shared across the controller?

cart_controller.rb

def new
  @cart = Cart.new(session[:cart])
end

def update
  logger.debug @cart.present? # false
end

Upvotes: 3

Views: 4870

Answers (5)

John Baker
John Baker

Reputation: 2398

I would suggest using before_action to create the instance of @cart, in the case the @cart instance variable will be visible to new and update actions.

before_action :get_cart, only: [:new, :update]

private
def get_cart
  @cart = Cart.new(session[:cart])
end 

If you don't want to use action callbacks, another alternative is calling get_cart method directly to new and update actions. Since, get_cart returns the instance @cart. As reference to this you can see this link

Upvotes: 0

Tarek N. Elsamni
Tarek N. Elsamni

Reputation: 1837

TL;DR: controller instance variables are not shared across different HTTP requests as each request create a new instance of the controller.

Conceptually what you are expecting should have been correct! You are defining an instance variable and you should have access to it everywhere across the class.

The problem is that on every HTTP request, a new instance of the class is being created.

So when you hit the new action an instance of the controller will be initiated, new method will be called and @cart will be created and assigned. Something like:

# HTTP request /new
controller = MyController.new # an object of your controller is created
controller.new # the requested action is called and @cart is assigned

But when you make a new HTTP request to update a new instance of the controller will be initiated, update method will be called and it has no @cart!

# HTTP request /update
controller1 = MyController.new # an object of your controller is created
controller1.new # the requested action is called and @cart is not assigned 😱

As you can see controller and controller1 are two different objects initiated from MyController as this took place in two different HTTP requests (different contexts).

To fix your issue you need to create @cart for each action when it's needed something like:

def new
  cart
end

def update
  logger.debug cart.present?
end

private

def cart
  @cart ||= Cart.new(session[:cart])
end

Upvotes: 0

Roman Kiselenko
Roman Kiselenko

Reputation: 44380

Instance variables (starting with @) are not shared between requests (or across controller actions). You can denote a method in order to get cart. Here is an example:

def new
  cart
end

def update
  logger.debug cart.present?
end

private

def cart
  @cart ||= Cart.new(session[:cart])
end

Upvotes: 1

Pavan
Pavan

Reputation: 33552

The instance variables can't be shared across the controller. They are available to the actions where they are defined. So you can't use @cart in update action since you didn't define it.

def new
  @cart = Cart.new(session[:cart])
end

def update
  @cart = Cart.new(session[:cart])
  logger.debug @cart.present?
end

To DRY the code, use before_action to set the cart

before_action :set_cart, only: [:new, :update]

def new
end

def update
  logger.debug @cart.present?
end

private
def set_cart
  @cart = Cart.new(session[:cart])
end

Upvotes: 0

teckden
teckden

Reputation: 337

@cart is an instance variable and it is not persisted between requests. And session is accessible between requests.

Basically if you have set some data into session then you can use that data between requests. As it was mentioned you can setup a before_filter and preset @cart instance variable before executing the update action.

class MyController < ApplicationController
  before_action :instantiate_cart, only: [:update] #is the list of actions you want to affect with this `before_action` method
  ...
  private

  def instantiate_cart
    @cart = Cart.new(session[:cart])
  end
end

Upvotes: 6

Related Questions