Understanding Rails Instance Variables - ruby-on-rails

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

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.

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?

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.

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.

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

Related

Default serializer render options in Rails controller

I am using Active Model Serializers in a rails project and have a user object that needs to be passed in from the controller to the serializer like this:
# Note the 'user:' option that will be accessible inside
# the serializer with #options[:user]
def show
render json: #some_object, user: current_user
end
def index
render json: SomeObject.all, user: current_user
end
This is good enough for what I am trying to do, but it is not very DRY and results in render statements that are littered with options. When those options change, I need to go back and manually remove/modify them across all actions.
My question is: Is there a way to set a list of default options for a render call at the controller level, instead of manually putting the options in every single controller action?
This can be accomplished by adding this method to your controller:
def default_serializer_options
{user: current_user}
end
You can then access it from within the serializer via options[:user]

Retaining form values in Ruby on Rails

I am working on an ROR application and i would like retain form values after the form reloads on some error from the server side so that the user doesn't have to go through filling the form again.
This is standard functionality, which is enabled with use of an #instance variable. This allows you to send data to a Ruby class (controller) and process the returned data in a single instance
You'd do it like this:
#app/controllers/posts_controller.rb
def new
#post = Post.new #-> notice instance variable
end
def create
#post = Post.new(post_params)
#post.save
end
private
def post_params
params.require(:post).permit(:title, :body)
end
This enables you to maintain the instance of your newly created ActiveRecord object. It means each time you can't submit your form, you'll receive errors with the previously inputted data

Rails controller methods

I have a controller action that is calling a model method to render some data. The controller looks something like this
class WeekController < ApplicationController
def index
#week = Week.first
end
def render
Week.render
redirect_to root_url
end
end
Currently my Week.render method has a bug in it and is causing errors. Which is fine, however when I am calling my index action the page fails because of the error in my render action.
Is it normal for rails to call actions other than the one being called or is there something weird going on?
It is ok to call other actions as long as you don't call redirect_to or render multiple times in a response.
By the way render is a reserved word, and you should not use it for a custom method.

Rails: call another controller action from a controller

I need to call the create action in controller A, from controller B.
The reason is that I need to redirect differently when I'm calling from controller B.
Can it be done in Rails?
To use one controller from another, do this:
def action_that_calls_one_from_another_controller
controller_you_want = ControllerYouWant.new
controller_you_want.request = request
controller_you_want.response = response
controller_you_want.action_you_want
end
You can use a redirect to that action :
redirect_to your_controller_action_url
More on : Rails Guide
To just render the new action :
redirect_to your_controller_action_url and return
The logic you present is not MVC, then not Rails, compatible.
A controller renders a view or redirect
A method executes code
From these considerations, I advise you to create methods in your controller and call them from your action.
Example:
def index
get_variable
end
private
def get_variable
#var = Var.all
end
That said you can do exactly the same through different controllers and summon a method from controller A while you are in controller B.
Vocabulary is extremely important that's why I insist much.
You can use url_for to get the URL for a controller and action and then use redirect_to to go to that URL.
redirect_to url_for(:controller => :controller_name, :action => :action_name)
This is bad practice to call another controller action.
You should
duplicate this action in your controller B, or
wrap it as a model method, that will be shared to all controllers, or
you can extend this action in controller A.
My opinion:
First approach is not DRY but it is still better than calling for another action.
Second approach is good and flexible.
Third approach is what I used to do often. So I'll show little example.
def create
#my_obj = MyModel.new(params[:my_model])
if #my_obj.save
redirect_to params[:redirect_to] || some_default_path
end
end
So you can send to this action redirect_to param, which can be any path you want.
Perhaps the logic could be extracted into a helper? helpers are available to all classes and don't transfer control. You could check within it, perhaps for controller name, to see how it was called.
Composition to the rescue!
Given the reason, rather than invoking actions across controllers one should design controllers to seperate shared and custom parts of the code. This will help to avoid both - code duplication and breaking MVC pattern.
Although that can be done in a number of ways, using concerns (composition) is a good practice.
# controllers/a_controller.rb
class AController < ApplicationController
include Createable
private def redirect_url
'one/url'
end
end
# controllers/b_controller.rb
class BController < ApplicationController
include Createable
private def redirect_url
'another/url'
end
end
# controllers/concerns/createable.rb
module Createable
def create
do_usefull_things
redirect_to redirect_url
end
end
Hope that helps.
You can call another action inside a action as follows:
redirect_to action: 'action_name'
class MyController < ApplicationController
def action1
redirect_to action: 'action2'
end
def action2
end
end
Separate these functions from controllers and put them into model file. Then include the model file in your controller.

Rendering different controller actions in Rails when using resource-oriented controllers

Say I'm making a Q&A site like StackOverflow. I have two resources: Question and Answer. I'm using default Rails RESTful resource routes, so each resource has its own controller and methods for creating it.
In the /questions/show view, I want to allow the user to submit an answer for the particular question. The form will POST to /answers, which will get routed as a request to the AnswersController with a call to the create method.
If the answer was created, I can simply redirect back to the original question. However, I'm running into trouble dealing with validation failures on the answer object. I need to render the /question/show view and show the validation errors for the answer object. It's not clear to me how to best do this.
Here are example snippets of what the two controllers might look like.
class AnswersController < ApplicationController
def create
#answer = Answer.new(params[:answer])
if #answer.save
redirect_to #answer.question
else
# What should go here??
end
end
end
class QuestionsController < ApplicationController
def show
#question = Question.find(params[:id])
#answer = Answer.new(:question_id => #question.id)
end
end
What should go in the else clause of the AnswersController's create method? A redirect seems wrong, since the error is really caused by the same request. Calling something like render :template => 'questions/show' seems wrong too, since I have to initialize any instance variables that the template depends on.
This style of having separate actions for calling GET to view the form for creating an object and calling POST to actually create the object seems to work well within a single controller.
How can it be done across controllers?
Try this on for size. It redirects, but passes back the dodgy answer object full of errors.
class AnswersController < ApplicationController
def create
#answer = Answer.new(params[:answer])
# stash the dodgy answer if it failed to save
session[:answer] = #answer unless #answer.save
redirect_to #answer.question
end
end
class QuestionsController < ApplicationController
def show
#question = Question.find(params[:id])
# if we have one stashed in the session - grab it from there
# because it probably contains errors
#answer = session[:answer] || Answer.new(:question_id => #question.id)
end
end
Some details need adding (eg clearing it from the session when done) etc

Resources