Is it necessary to set instance variable in resource controller `#create` method? - ruby-on-rails

Having resource Foobar with the following controller:
class FoobarController < ApplicationController
def new
#foobar = Foobar.new(baz: params[:baz])
#foobar.build_data
end
def create
#foobar = Foobar.new(foobar_params)
respond_with(#foobar)
end
# ...
end
Is it necessary to set instance variable #foobar in #create method? Could not I just write
def create
Foobar.new(foobar_params).tap &method(:respond_with)
end
?

It depends on what content types you respond with. The docs describe exactly what happens when you call respond_with. In your case, in the create action, respond_with is the same as the following, assuming you did not specify any other format than html in a respond_to call in your controller:
respond_to do |format|
if #foobar.save
flash[:notice] = 'Foobar was successfully created.'
format.html { redirect_to(#foobar) }
else
format.html { render action: "new" }
end
end
The only case where the #foobar instance variable would be necessary is, if there is a validation error and your new.html template includes #foobar. If the foobar_params are always valid, then respond_with will always respond with a redirect to the show action, so the instance variable is unnecessary.

Related

Send a variable from one action to another in ruby on rails

Code in controller is
if params["type"] == "user"
respond_to do |format|
format.html { redirect_to home_user_path, notice: "abc" }
end
If I send variable with notice then work fine but I want to send with my own key like
format.html { redirect_to home_user_path, search: "abc" }
It doesn't recieve there
You must remember that you're not "sending" a variable to another action; you're invoking another action, and populating it with a variable (data):
1. Instance Variable
You can set an instance variable, which will then be available in the next action:
def your_action
#search = "abc"
respond_to do |format|
format.html { redirect_to home_user_path } #-> #search will be available in the other view
end
end
2. Sessions
You're currently trying to use sessions to populate the data:
def your_action
redirect_to home_user_path, search: "abc"
end
#app/views/controller/your_action.html.erb
<%= flash[:search] %>
3. URL
Finally, you can set the value in the URL through your routes:
def your_action
redirect_to home_user_path(search: "abc") #-> you'll probably need to set the user object too, judging from the route name
end
This should populate your route with some GET request params: url.com/action?param=value
The underpin of all of this, as mentioned, is that you're not sending a variable, you'll be initializing it in your current controller action, and then having the next action call it.
This is a problem with Ruby's optional braces: sometimes it's hard to see which method call something is being treated as an argument to.
Try this instead:
format.html { redirect_to home_user_path(search: "abc") }
this will redirect to home_user_path and set params[:search] to "abc".

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.

What does respond_to do when called without a block?

I understand how respond_to works when it's called with something like this:
def index
#users = User.all
respond_to do |format|
format.html
format.json { render json: #users }
end
end
But I've seen some apps which pass respond_to a list of symbols, outside of the controller methods, e.g.:
class UsersController < ApplicationController
respond_to :html, :json
def index
# blah blah bah
end
end
What does this do? I've been playing around with it in one of my controllers and I can't figure out what difference it makes.
For a given controller action, #respond_with generates an appropriate response based on the mime-type requested by the client.
If the method is called with just a resource, as in this example -
class PeopleController < ApplicationController
respond_to :html, :xml, :json
def index
#people = Person.all
respond_with #people
end
end
then the mime-type of the response is typically selected based on the request's Accept header and the set of available formats declared by previous calls to the controller's class method respond_to. Alternatively the mime-type can be selected by explicitly setting request.format in the controller.
If an acceptable format is not identified, the application returns a '406 - not acceptable' status. Otherwise, the default response is to render a template named after the current action and the selected format, e.g. index.html.erb. If no template is available, the behavior depends on the selected format:
for an html response - if the request method is get, an exception is raised but for other requests such as post the response depends on whether the resource has any validation errors (i.e. assuming that an attempt has been made to save the resource, e.g. by a create action) -
If there are no errors, i.e. the resource was saved successfully, the response redirect's to the resource i.e. its show action.
If there are validation errors, the response renders a default action, which is :new for a post request or :edit for patch or put.
Thus an example like this -
respond_to :html, :xml
def create
#user = User.new(params[:user])
flash[:notice] = 'User was successfully created.' if #user.save
respond_with(#user)
end
is equivalent, in the absence of create.html.erb, to -
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
flash[:notice] = 'User was successfully created.'
format.html { redirect_to(#user) }
format.xml { render xml: #user }
else
format.html { render action: "new" }
format.xml { render xml: #user }
end
end
end
for a javascript request - if the template isn't found, an exception is raised.
for other requests - i.e. data formats such as xml, json, csv etc, if the resource passed to respond_with responds to to_, the method attempts to render the resource in the requested format directly, e.g. for an xml request, the response is equivalent to calling render xml: resource.

Rails redirect_to in controller to correct page

I have several models with has_many :attachments.
I'm trying to redirect back to the Note view after the Note is created.
This is the attachments controller code I'm trying. The #note tells me that this attachment is related to that Note.
# GET /attachments/new
# GET /attachments/new.json
def new
#attachment = Attachment.new
#comment = params[:comment_id]
#note = params[:note_id]
respond_to do |format|
format.html # new.html.erb
format.json { render json: #attachment }
end
end
# POST /attachments
# POST /attachments.json
def create
#attachment = Attachment.new(params[:attachment])
respond_to do |format|
if #attachment.save
if #note != nil
format.html { redirect_to note_path(#note), notice: 'Attachment was successfully created.' }
else
format.html { redirect_to attachments_path, notice: 'Attachment was successfully created.' }
end
But, #note is nil by the time the create code happens.
Thanks for the help!
As a rule, you probably won't see "new" and "create" blocks executed in the same context. That's a bit of a mouthful, so lets be a bit more specific: the variables you declare in "new" won't still exist when "create" is called. So, any variables you want to use in "create" must be declared there as well.
One thing you can do (depending on the code) is share a block between different controller methods that initialized these variables for you. For example:
before_filter :initialize_vars, only: [:new, :create]
...
def initialize_vars
#note = params[:note_id]
end
The "before_filter" will execute the "initialize_vars" method before any new request is sent to the "new" or "create" methods.
More generally, this relates to a pretty important Rails concept (and server-side web engineering in general) - that there is very little "state" within the server. The server takes a request, processes it, and forgets about it. Everything that's needs to be remembered must be stored in the server, or somehow communicated by the request the user sends.

How do I deal with an authorization hiccup because of bad controller naming?

I seem to have an authorization hiccup in my Ruby on Rails app. I have been using the following method in my application controller and it has been working beautifully.
def require_owner
obj = instance_variable_get("##{controller_name.singularize.camelize.underscore}") # LineItem becomes #line_item
return true if current_user_is_owner?(obj)
render_error_message("You must be the #{controller_name.singularize.camelize} owner to access this page", root_url)
return false
end
I then filter in the specific controllers by:
before_filter :require_owner, :only => [:destroy, :update, :edit]
I recently created a new controller which has a bit of a different naming convention that seems to be causing a problem. Normally my controllers read messages_controller or posts_controller. In this specific case I named the resource box_wod which generated box_wods_controller.
This is the only controller that seems to be having a problem with this filter so I bet I can tell it is in the naming of it and therefore the application_controller method is not recognizing the owner of the record.
I am not getting an error message but the application is not letting me edit, update or destroy a record because I am not the BoxWod owner. My routes are correct as are my associations and the correct information is getting passed to the box_wod table.
Is there a way to rewrite the application_controller method to recognize the additional underscore in the box_wod resource? Or is this even my problem?
UPDATE:
Here are the three methods in the BoxWodsController:
def edit
#workout_count = Workout.count
#box_wod = BoxWod.find(params[:id])
end
def update
#box_wod = BoxWod.find(params[:id])
respond_to do |format|
if #box_wod.update_attributes(params[:box_wod])
flash[:notice] = 'BoxWod was successfully updated.'
format.html { redirect_to(#box_wod) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #box_wod.errors, :status => :unprocessable_entity }
end
end
end
def destroy
#box_wod = BoxWod.find(params[:id])
#box_wod.destroy
respond_to do |format|
format.html { redirect_to(box_wods_url) }
format.js
end
end
In situations like this, I like to create a controller method that I can override when necessary. For example:
# application_controller.rb
class ApplicationController
def require_owner
obj = instance_variable_get("##{resource_instance_variable_name}")
# Do your authorization stuff
end
private
def resource_instance_variable_name
controller_name.singularize.camelize.underscore
end
end
# box_wods_controller.rb
class BoxWodsController
private
def resource_instance_variable_name
'box_wod' # Or whatever your instance variable is called
end
end
Lastly, please post your BoxWodsController code so we can better diagnose the problem.
It would seem that the #box_wod instance variable is not created until the require_owner method is invoked so current_user_is_owner? is checking a nil value, resulting in it always returning false. Perhaps you need another before_filter to populate the instance variable before require_owner is invoked.

Resources