Instance variable vs local variable in the Create method - ruby-on-rails

As far as I understand we use namely an instance variable in the method new
def new
#article = Article.new
end
because this variable is used in new.html.erb too. (Please correct me if I am wrong).
But why do we use an instance variable in the create method? Where else is it used outside the create method? Can't we just use a local variable article instead of the instance variable #article?
def create
article = Article.new(article_params)
if article.save
flash[:success] = "Article created successfully!"
redirect_to articles_url
else
render 'new'
end
end
private
def article_params
params.require(:article).permit(:title, :body)
end

This means that you don't understand render :new.
This line means that the view for the new method will be rendered, but the method itself will NOT be invoked. That's why you need the variable to be instance variable, because if the validations fails, you will need it to pass it to form.
Take a look at the guides for more information
PS render :new is the same as render 'new'

You can't, because #article variable is also used in new.html.erb file, rendered when article isn't saved successfully.
So you'll get (as far as I remember) error undefined method 'model_name' for nil:NilClass.

Related

Why can't I refer to my #quiz variable in 'update' action?

My QuizzesController#index action looks like this:
def index
#user = current_user
#quiz = Quiz.create(user_id: current_user.id)
end
My view draws the quiz form fine. It goes to the results/index view as intended. BUT the various attributes of the quiz are NOT updated on the Quiz instance which is pulled from the database, in the QuizzesContoller#update action:
def update
#results = Quiz.where(user_id: current_user.id).last
redirect_to results_path
end
('update' is called in this case because the Quiz instance already exists, having been created in the 'index' action).
So, I tried changing the 'update' action to:
def update
#quiz.save
#results = Quiz.where(user_id: current_user.id).last
redirect_to results_path
end
But this triggers the error:
undefined method 'save' for nil:NilClass
Why is that? Shouldn't my QuizzesController have access to the #quiz variable as set up in the 'index' action? Can anyone explain what the problem is there?
Others have answered this question, so I thought I would explain why the answer is what it is. In Ruby, variables that begin with the # symbol are instance variables. This means that they are created when a new instance of their parent object is instantiated and are unique to that instance of the object.
Rails based web apps, for the most part, are stateless, meaning that state is not persisted between http requests. In layman terms, the app treats each and every request independent of all other requests. Due to this, the controllers are instanced classes. Every request instantiates a new instance of the controller class.
EDIT:
More I look at your code, you aren't following proper conventions
class QuizzesController < ApplicationController
# GET index: for displaying a list of quizzes
def index
#quizzes = Quiz.where(user_id: current_user.id)
end
# GET show: for getting a single quiz record
def show
#quiz = Quiz.find(params[:id])
end
# GET new: for initializing a new quiz record
def new
#quiz = Quiz.new
end
# POST create: for saving a new quiz record
def create
#quiz = current_user.quizzes.create(quiz_params)
if #quiz.errors
render :new
else
redirect_to #quiz #or whereever
end
end
# GET edit: for initializing existing quiz for update
def edit
#quiz = Quiz.find(params[:id)
end
# PUT/PATCH update: for updating an existing quiz record
def update
#quiz = Quiz.find(params[:id])
if #quiz.update(quiz_params)
redirect_to #quiz # or whereever
else
render :edit
end
# DELETE destroy: for deleting a quiz record
def destroy
Quiz.find(params[:id]).destroy
redirect_to :index # or whereever
end
end
You have not #quiz variable in your update action. Actions in the controller does not have access to variables in other actions.
The QuizzesController instance is not persisted between requests. The real reason instance variables are used in controllers is to pass that variable to the view.
A normal update action would look something like:
def update
#quiz = current_user.quiz # I'm assuming a user has one quiz?
#quiz.update(quiz_params) # Where quiz params takes the posted parameters from your update form
if #quiz.errors.any?
render :edit
else
redirect_to results_path
end
The key is you need to reassign #quiz with each request

is there any difference in using instance variables in a Rails controller other than visibility in the view

I tend to mix local variables and instance variables in Rails controllers when I don't need to use them in a view. Obviously, if I'm using in the view, I use instance variables. Is there any difference between using them in that scenario (other than class-level visibiity)?
For example:
def destroy
#micropost.find(params[:id])
#micropost.destroy
redirect_to root_url
end
or
def destroy
micropost.find(params[:id])
micropost.destroy
redirect_to root_url
end
an example of using instance variables for class level visibility would be here: https://github.com/mhartl/sample_app/blob/master/app/controllers/microposts_controller.rb ?
I think these lines of code is what your question about. Of course you don't have to instantiate that variable with # since you are not actually going to show it on your view(since it is being destroyed). The purpose of these lines of code if to first check wether #micropost exists, if it does not then redirect_to root_path else it will go to destroy method destroy the micropost and then redirect_to root_path.
Now, to answer your question, yes, there is a huge difference between #micropost and micropost. #micropost will be accessible in other methods of your controller while micropost will not(since its scope will be limited to the method you instantiate it in).
However, if you're concern about not having a # variable then you can change the code shown here to this:
class MicropostsController < ApplicationController
before_filter :signed_in_user
def create
#micropost = current_user.microposts.build(params[:micropost])
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to root_path
else
#feed_items = []
render 'static_pages/home'
end
end
def destroy
if micropost = current_user.microposts.find_by_id(params[:id])
micropost.destroy
end
redirect_to root_path
end
end
Beside, scope difference, you can access instance variable in another method of same controller, as
def method1
#var = "some value"
puts #var
end
def method2
puts #var
end
now, depending on the sequence u call these method, #var can have different values

Instantiate instance variable in helper method from controller

I have a helper which instantiates a model and renders a form. This form should be available to any view in the application
# support_form_helper
def support_form
#support_stats = SupportForm::Stat.find(get_stats_id)
#enquiry = SupportForm::Enquiry.new(stats_id: #support_stats.id)
render partial: 'support_form/enquiries/form'
end
And its rendered in the view:
# some_view.html.erb
<%= support_form %>
This is fine until I want to submit the form and validate it in the controller.
# enquiries_controller.rb
def create
#enquiry = SupportForm::Enquiry.new(params[:support_form_enquiry])
topic = #enquiry.topic
#stat = SupportForm::Stat.find(#enquiry.stats_id)
#stat.stats[topic] = #stat.stats[topic].to_i.next
respond_to do |format|
if #enquiry.valid? && #stat.save
format.html { redirect_to(root_path) }
else
format.html { redirect_to(:back) }
end
end
end
This is where I can't render the previous view with the errors attached to the invalid object. The helper gets invoked again and initializes a new #enquiries object, without the errors obviously.
How can I render the form in many views across an application and still return to the view with the object together with errors when it is invalid?
I found an answer which answers my question but its a bad idea:
Render the action that initiated update
def create
#enquiry = SupportForm::Enquiry.new(params[:support_form_enquiry])
topic = #enquiry.topic
#stat = SupportForm::Stat.find(#enquiry.stats_id)
#stat.stats[topic] = #stat.stats[topic].to_i.next
if #enquiry.valid? && #stat.save
redirect_to(root_path)
else
render Rails.application.routes.recognize_path(request.referer).values.join("/")
end
end
The problem is that there will likely be instance variables in the view that submitted the form and I would have to be able to instantiate all the instance variable in the application then.....not possible.
Currently I'm considering putting the errors in the flash hash... not something I want to do. With the original object returned i can repopulate the fields with the users input.
When you use redirect_to, rails will kick off a whole new controller & view sequence. Use
render "path/to/template/from/view/folder"`
instead.
A typical create action using this pattern would look like (for a 'post' object in this case):
def create
#post = Post.new(params[:post])
#created = #post.save
respond_to do |format|
if #created
flash[:notice] = 'Post was successfully created.'
format.html { redirect_to post_path(#post) }
format.js
else
format.html { render :action => :new }
format.js
end
end
end
Notice how if it's successfully created we do a full redirect to the "show" page for the post, but if it's not successful we just do a render.
You should probably modify your support_form helper so that it only creates a new #enquiry if it hasn't been created already:
def support_form
#support_stats = SupportForm::Stat.find(get_stats_id)
#enquiry ||= SupportForm::Enquiry.new(stats_id: #support_stats.id)
render partial: 'support_form/enquiries/form'
end
||= is shorthand for "equals itself or". If it hasn't been defined (or is nil or false) then it will fail the first part of the or and pass through to the second, where the object is created.
In your form partial, also, you should make sure you're using form_for, which will submit to the create or update action depending on whether the object has been saved already.

controller action contains more than one model method call?

I have a simple update action in a Rails 4 controller:
#more stuff here
def update
if #user.update(user_params)
flash[:notice] = "User #{#user.username} updated"
redirect_to users_path
else
render 'edit'
end
end
private
def set_user
#user = User.find(params[:id])
end
However, RubyMine is warning about #user.update and #user.username:
This inspection warns if a controller action contains more than one model method call, after the initial .find or .new. It’s recommended that you implement all business logic inside the model class, and use a single method to access it.
I don't see more than one model method call here. Can some one explain what is going on?
EDIT - I have something similar in the create action without warns, so I believe there is something to do with user_params...
def create
if #user.save
flash[:notice] = "User #{#user.username} created"
redirect_to users_path
else
render 'new'
end
end
Assuming username is a method in model where you merge user first_name and last_name.
I guess #user.update(user_params) and #user.username are your both method calls. One that saves the model, another that sets the user full name in flash notice.
It's just a warning from rubymine that just recommends you some actions to do, not necessary to follow them.

Ruby on Rails when create method fails, render loses local variables

Hey guys I have a simple create method with some validations and whenever the create method fails due to validation errors it re-renders the 'new' action.
The problem is in my new action/view I have a local variable that is established in the action and passed to a partial to render some related information to what the user is creating.
Now when my create action fails and I try to re-render the 'new' action I'm getting the always awesome
undefined method `cover' for nil:NilClass
error.
What is the best way to handle re-establishing my action's local variables on a render instead of redirecting to the action again and the user losing the data they input?
For some clarification. Here is some sample code:
#controller.rb
def new
#summary = User.find(params[:user_id])
#page = Page.new
end
def create
#page = Page.new(params[:page])
if #page.save
redirect_to #page
else
render :action => 'new'
end
end
in my new.html.erb file i have something like this
<%= #summary.cover %>
#page form etc...
When you create the object and attempt to save it, the object still holds the values and the validation errors, so pass it on into the render. Usually it is named the same in your create method as it is in your new method, so the template just works.
if #my_object.save
flash[:notice] = "Successfully created."
redirect_to ....
else
render :action => 'new' #assuming new.html.erb uses #my_object
end

Resources