可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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 :
When a Rails controller action renders a view, the instance
variables can be accessed over there.
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