4

In my application I have a products_controller that makes use of instance variables. My understanding of instance variables in Ruby is that you can use them within different methods of the same class. So why is it that we use the same instance variable in multiple methods of rails apps? Below we have an instance variable @product set twice, is the @product variable in the new action not overwritten when we use it in the create action?

I am just a little confused as to the scope of these variables within methods of the same class.

  def new
    @product = Product.new
  end

  def create
    @product = Product.new(product_params)

    respond_to do |format|
      if @product.save
        format.html { redirect_to @product, notice: 'Product was successfully created.' }
        format.json { render :show, status: :created, location: @product }
      else
        format.html { render :new }
        format.json { render json: @product.errors, status: :unprocessable_entity }
      end
    end
  end

5 Answers 5

12

Instance variables are in the instance class scope. In Ruby on Rails, because the way how that API was built, instance variables are also available in the views.

You need to be aware of that new and create methods are commonly used in different ProductsController instances.

First request: GET http://localhost:3000/product/new

When you ask for the new action (I suppose that is a form), Rails API implementation at a given point creates an instance of ProductsController and sends the new message to that instance (calls the new method). Then, the instance variable @product is created and available in any method, or in any view that the action renders. At a given point, Rails replies with a web page and the class instance, and all its instance variables, are destroyed (won't be available anymore).

Second request: POST http://localhost:3000/product/create

When you submit the form for database persistence, again a new controller instance is created, and the create method is called. Because is a new instance, the @product doesn't have any value.

Note, however, that there is a difference between rendering a view (like its happening in the new action) and a redirect (like you do in the create action if @product.save is true). When you render, you remain in the same controller instance, with you redirect, new server request happens, so the previous controller instance is destroyed and a new controller instance is created.

The before action

before_action is called before you actually start executing the action code. In Rails perspective, an action is not a Ruby method. The class method is the definition of that action:

From Rails guides:

A controller is a Ruby class which inherits from ApplicationController and has methods just like any other class. When your application receives a request, the routing will determine which controller and action to run, then Rails creates an instance of that controller and runs the method with the same name as the action.

The action acts as an entry point determined by the routes. If you call create inside new, it won't trigger that before_action again.

Sign up to request clarification or add additional context in comments.

Comments

3

No, it doesn't overwrite it. An instance variable (@variable_name) is accessible within all methods of a single instance object of a class.

Now imagine, there's a client request to the "new product route". Rails creates an instance object of your products_controller and invokes only the new action of that instance. That defines @product = Product.new, renders your new.html.erb template and that's it. After that, the controller instance will be forgotten.

Next, your client clicks the "create product button" of your "new product form". Another request arrives the server. Rails creates another instance of your products_controller and calls the create action. The new action isn't invoked. And so, you have a new product instance (@product = Product.new(product_params)) with the attributes sent by the form.

Of course, you could call the create method from your new action ...

# only an example
def new
  @product = Product.new
  create
end

... or the other way round. This would overwrite the @product variable. But why should you do that?

Comments

0

An instance variable is accessible in any instance method, for that instance of the class. Multiple instances of a class will each have their own copy of a given instance variable.

You mentioned the new and show methods -- did you mean the new and create methods?

Note that the new method shown is an instance method, and not the class method that you are accustomed to seeing used to instantiate objects. I think the point of the two methods is that only one of the two will be used to created a given instance, so there is no issue of collision.

But yes, if you called one of the methods and then called the other, the second would overwrite the value assigned by the first.

3 Comments

Yes, I did mean create, I updated my question. And okay, so we are able to use the same instance variable more than once because the scope in which we are calling the instance methods is different for our objects, so they will not run into each other. I'm now trying to think of an example where the variable could collide, so I can better understand it
When you say "we are able to use the same instance variable...", you mean that you're dealing with several instances, and since each instance has its own instance variable they don't collide with each other, right? If you wanted a value that all instances of a class shared, you would use a class variable rather than an instance variable.
Regarding "I'm now trying to think of an example where the variable could collide, so I can better understand it", could you explain? Do you mean an example of when it would be problematic that an instance variable could be overwritten? Since it's intended to be scoped to an object, its ability to be overwritten elsewhere is an intended effect.
0

In a rails controller instance variables are accessible from :

  1. When a Rails controller action renders a view, the instance variables can be accessed over there.

  2. It is also available within instance methods of that controller. (Also the superclass instance methods)

So here @product in new and create both refer to different instance variables.

So in your case taking example of the new action, the variable @product is accessible in your new form because it's an instance variable.

2 Comments

That is not true. An instance variable's scope includes any instance methods for that instance.
@KeithBennett yes that's right. I'll update my answer.
0

No, you are not able to set one instance variable to all until, you are not setting it with before_action method.

For example below controller, You are on index page and after going to create new product page you will get an error in a form. Such as not defined...

It means didnt set a variable to new method that we called in index method.

def index
   @products = Product.all

   #adding this
   @product = Product.new
end

def new
   # leaving empty
end

As the same thing will happens in create method. if we are not defining, it will return an error.

To set an instance variable once, You have to make it like this below. But its not right way to make it, its really messy and not suggestible.

class ProductsController < ApplicationController
before_action :set_new_product_variable, only: [:new, :create]

def new
end

def create
   @product.title        = params[:product][:title]
   @product.price        = params[:product][:price]
   @product.description  = params[:product][:description]
   @product.image        = params[:product][:image]
   @product.blabla       = params[:product][:blabla]

   #look above its really messy and it gets bigger. Below example much more efficient, it covers everything in just one line of code.

   @product = Product.new(product_params)

   redirect_to @product if @product.save
end

private 

  def set_new_product_variable
      @product = Product.new
  end
end

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.