Understanding Rails Instance Variables

2020-07-24 04:49发布

问题:

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

回答1:

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.



回答2:

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?



回答3:

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.



回答4:

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.



回答5:

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