Render view from another controller with params - ruby-on-rails

I have code in my products_controller.rb
def create
#product = Product.new(product_params)
brand = Brand.find(params[:brand_id])
#product.brand = brand
if #product.save
raise "OK"
else
#brand = Brand.find(params[:brand_id])
render 'brands/show'
end
end
After failure of save method, i need to redirect to brands show.html.erb view, which find brand by id and display information about it.
Questions:
Is it right way of doing that
If user manually passed server_id parameter, how can prevent it from executing (SQL injection)? Or how should i check and display error?
How can i display errors in brands/show views outside form ( because i'm storing form in <a data-form="..."> property, so user can't see form until he clicks on link. Even more, i use form_for([#brand,#brand.send(:products).klass.new]))

use redirect_to brands_path(#brand)

Related

Rails: Stay on the same page after post action fails

I'm working on an order system, I passed some information about a new order from controller (orders#confirm) to a confirmation page using instance variable (such as #detail). On the confirmation page, users are supposed to confirm the information and submit a form to create the new order (orders#create). If the post action fails, I want it to stay on the same confirmation page and preserve all the information on the page for the user:
def create
#order = Order.new(order_params)
if verify_recaptcha(model: #order) && #order.save
redirect_to items_url
else
render :confirm
end
end
The code is not working because all the variables that I passed from orders#confirm to the confirmation page are lost. I know I can recreate them, but is there any better ways to preserve those information? Thank you very much!
Within your approach, you have to rebuild the #detail object in the current action create in order to make your confirm.html.erb view to be rendered properly.
It is possible. However, I think there is a better way that you can let the user confirms the order by AJAX ( which is dead simple with Rails ) so the user can stay on the page if the confirmation failed.
In your confirm.html.erb, suppose you have a form to let user confirm, just change it to remote: true
<%= form_for #order, remote: true, format: :js do |form| %>
<%# blah blah %>
<% end %>
Modify your controller
def create
#order = Order.new(order_params)
if verify_recaptcha(model: #order) && #order.save
# Redirect when success
render js: "window.location = '#{your_desired_path}'"
else
# Display error to user.
render "ajax_create_error", status: :bad_request
end
end
Now you can create a file name ajax_create_error.js.erb to display error to the user
# app/views/your_controller_name/ajax_create_error.js.erb
alert("Cannot create order");

Rails controller redirect to form in another controller then back to saved model

I need to do something kind of weird in my Rails app. Once a user creates a Product instance through the create action, I need it to save and then redirect them to a Braintree payment method form if they haven't already added one to their account, and only then redirect them to the show page for the product.
Here's the product create action:
def create
#product = Product.new(product_params)
#product.set_user!(current_user)
if #product.save
if !current_user.braintree_customer_id?
redirect_to "/customer/new"
else
redirect_to view_item_path(#product.id)
end
else
flash.now[:alert] = "Woops, looks like something went wrong."
format.html {render :action => "new"}
end
end
The confirm method for the Braintree customer controller is this:
def confirm
#result = Braintree::TransparentRedirect.confirm(request.query_string)
if #result.success?
current_user.braintree_customer_id = #result.customer.id
current_user.customer_added = true
current_user.first_name = #result.customer.first_name
current_user.last_name = #result.customer.last_name
current_user.save!
redirect_to ## not sure what to put here
elsif current_user.has_payment_info?
current_user.with_braintree_data!
_set_customer_edit_tr_data
render :action => "edit"
else
_set_customer_new_tr_data
render :action => "new"
end
end
Is what I want to do possible?
You can store product id in a session variable before redirecting to braintree form, a then after complete confirmation just read this id from session and redirect to product show action.
if !current_user.braintree_customer_id?
session[:stored_product_id] = #product.id
redirect_to "/customer/new"
else
redirect_to view_item_path(#product.id)
end
Keep in mind that user can open product view page just by entering valid url address if he knows product id, so you should also handle this kind of situation. You can put before_filter in product show action to check if user has brain tree setup. If you go this way, you don't need to have condition in create action. You can always redirect to product show page and before_filter will check if user needs to update braintree data.

How does rails populate forms?

Let's say I create a scaffold:
rails g scaffold Cat name:string age:integer
and I add a presence validation on the Cat model's age attribute:
validates :age, presence: true
When I attempt to create a cat via the form, and put in the cat's name but purposely leave out the cat's age the controller bounces me back to the form but that cat's name is still present in the name field!
How is this happening?
I would have thought the
#cat = Cat.new
would replace all of the invalid cat's attributes. Maybe if it were #cat ||= Cat.new I could understand that more.
Also, how can I make this behaviour happen in a more complex rails app? I have a simple forum where topics has_many replies. I create my new replies via a form in my topic show view:
topic#show:
#reply = Reply.new
topic/show.html.erb:
<%= form_for [#toplic, #reply] do |f| %>
<%= f.text_field :name placeholder: 'Create a new name...' %><br>
<%= f.text_area :description, placeholder: 'Create a new description...', rows: 5 %><br>
<%= f.submit 'Create Discussion' %>
<% end %>
While everything works perfectly, when I purposely leave out a reply's name, though I am redirected back to the form and an error flash shows, my form is completely empty. All of the attributes have vanished? Why is this?
The key to understanding how this works is to realize that in the case of a form failure, the controller action is not rerun, but rather the template is rendered using the existing state from the action.
In a typical Rails scaffold, your create action will look like this
def create
#cat = Cat.new(cat_params) # instance variable is initialized with the form values
if #cat.save
redirect_to #cat, notice: 'Success!'
else
# in the case of form failure, we will re-render the 'new' template
# this will NOT rerun the entire 'new' action, thus the #cat variable
# will still maintain the values from the form that we gave it above
render 'new'
# note the difference if we had instead done a redirect_to; this would
# cause the CatsController#new action to be re-run which would reinitialize
# the #cat variable according to the code within the 'new' action
# redirect_to new_cat_url
end
end
For your more complex example, you'll want to follow the same procedure, making sure you just re-render the form and don't redirect to another action (which will cause the state to be lost).
# TopicsController
def show
#topic = Topic.find(params[:id])
#reply = Reply.new
end
# RepliesController
def create
#reply = Reply.new(reply_params) # init the var with the form values
if #reply.save
redirect_to #topic, notice: 'Success!'
else
# this is the key - we need to re-render the template of the previous action
# in this case, it would be the TopicsController#show template
render 'topics/show'
# Remember - if we instead do a redirect_to #topic, then we will lose the form
# values which are currently set in the #reply variable.
end
end
In short, make sure you recognize when you are redirecting to a new action versus just re-rendering a template.
One important GOTCHA to be aware of when re-rendering a template is that you must make sure that all the instance variables which exist for the controller action are available when you render the template.
For example,
# TopicsController
def show
#topic = Topic.find(params[:id])
#reply = Reply.new
#foo = Foo.new
end
# RepliesController
before_action :set_topic
def create
#reply = Reply.new(reply_params)
if #reply.save
# ...
else
# we need to remember to set up a #foo variable here otherwise it will be undefined
# when used within the 'show' template
#foo = Foo.new
render 'topics/show'
end
protected
def set_topic
#topic = Topic.find(params[:topic_id])
end
OK, so, you go go /cat/new. Rails' route for this URL runs the method CatsController#new, which renders the new.html.erb template. You put in your data, then hit submit. The action for this form is to POST to /cats, which runs the CatsController#create method. This method does this following:
#cat = Cat.new(cat_params)
It then tries to save the Cat. If it succeeds, it redirects you to the Cat's URL. If not, it re-renders the new.html.erb template. That's where the name comes from — the CatsController#update method creates its Cat from the values you put into the original form.
For a typical scaffold create action:
# POST /products
# POST /products.json
def create
#product = Product.new(params[:product])
respond_to do |format|
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
format.json { render json: #product, status: :created, location: #product }
else
format.html { render action: "new" } #will re-submit
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
The magic happens in the render method! which will submit the previous POST request (won't go back to the new action while a redirect will do), this way the submited values are still there. (check this SO question for more details)
That's said, this behavior relies on following the convention, however sometimes you need to give it a hand specially with some inputs (e.g selects, checkboxes, radio) might require extra setup using selected or value options

Get actual object Rails

I have two forms in my new view, one is for the product and the other is for fotos.
When I upload the fotos with a select.file field, these are created by Ajax call by a file create.js.erb then when I fill the others fields to the product I've another button to create it. So I have two forms and one way to create each one.
The problem is the ID, the solution that I've found was to create an object before the user enter to the new view, so I have this code:
Product's controller:
def new
#product = current_user.products.create
end
It creates an object nil, now I can create my Foto to that object, like this:
Painting's controller:
def create
#product = Product.last
#painting = #product.paintings.create(params[:painting])
end
The problem is the line "#product = Product.last", I know that it isn't the right solution, because when I try the edit action, and when I try to create new objects it goes to the last product and not to the actual edit product.
How can I find that current product at my new action???
Thanks a lot.
Building a new object (really showing the new form, since #new is a GET request and should not make destructive changes)
def new
#product = current_user.products.build
end
Creating a new object
def create
#product = current_user.products.build(params[:product])
if #product.save
redirect_to #product
else
render :new
end
end
Showing the Edit form for an object
def edit
#product = current_user.products.find(params[:id])
end
Updating an existing product
def update
#product = current_user.products.find(params[:id])
if #product.update_attributes(params[:product])
redirect_to #product
else
render :edit
end
end
You'll notice that the GET requests (new and edit) make no chagnes to the database.
The two destructive requests (PUT and POST) to (update/create) make changes to the database.
What you are doing in general is awkward and probably not the best way to use the new action of a controller.
To answer your question you would need to pass in the ID of the product in your parameters.
Depending how you are submitting your paintings form you need to add the parameter in either the body of the request or the url. that way you would be able to do something like
Painting's controller
def create
#product = Product.find(params[:product_id]
#painting = #product.paintings.create(params[:painting])
end
If you add a code snippet of your views/forms I'll probably be able to help you better.

Showing Post + Poster's Info on the "Index" Page

I want to show a post along with the poster's info on my rails app. Right now I'm able to show the post and user association on the "show" page (the page for a single post), but when I want to show it on the "index" page (where all of my posts are), I get this error: undefined method `username' for nil:NilClass
I added #post.user = current_user to my post_controller under "show" (That allowed me to show the poster's info. But i dont know what to add to the "def index".
This is what I have there:
def index
#posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #posts }
end
end
This is what I have under "show"
def show
#post = Post.find(params[:id])
#post.user = current_user
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #post }
end
end
I want to know what to add to my def index to be able to display the poster's info. Thanks in advance.
Your set up is wrong. The user needs to be added to the post when it is created and updated.
As you have it now, all posts will always belong to the user that is currently viewing the post because you are assigning the current_user to the post in the show action (and would be doing the same in the index view if you continued with this approach) meaning that when post 1 is viewed by user 1 it would show as belonging to user 1. When post 1 is shown by user 2 it would belong to user 2.
The solution is to assign the current user to the post in the update and create actions of the posts controller exactly as you have it now in the show action but before the post gets physically updated or created.
Then remove the #post.user = current_user from the show action.
This may mean you are left with legacy data in your app that doesn't have a user assigned. But that's o.k., just edit and save each post and it will have you attached automatically.
Then in the views/posts/index_html.erb just add the user details that you want to see in new table row columns before the show/edit links. This is assuming you have a standard scaffolded index.html.erb file. If you don't have a standard index view then put it wherever you want it but then if you had a customised index view you probably wouldn't be asking this question in the first place so forget about that and you'd know where it goes
Update
I did explain how to show the user in the views in my response above but possibly I need to be a little clearer so I'll show you the code.
To show the user name in the show view use
<%= #post.user.name unless #product.user.blank? %>
To show the user in the index action for the post use
<% #posts.each do |post| %> <!-- This is already in your index action. -->
<%=post.user.name unless post.user.blank?%><!-- This is the code you need -->
<!-- You might want to add a new th in the header for the css table then you can add a <td></td> tags round the above line so your table all matches up nicely for display purposes-->
<%end%> <!-- This is already in your index action -->
Hope that helps

Resources