Rails: I cant pass a validation error in a redirec

2019-03-13 07:09发布

问题:

So this is a simple project where there are products and you can bid for them. Think eBay.

I built the project as follows:

$ rails new routetest
$ rails g scaffold product productname reserveprice:integer
$ rails g scaffold bid bidprice:integer product_id

On the 'show' view for each product I include

  • the product details (as generated by the scaffold)
  • a list of bids so far
  • a form to sumbit new bids using the form_for helper

<p id="notice"><%= notice %></p>
<h2>Normal Products\Show Part</h2>
<p>
  <strong>Productname:</strong>
  <%= @product.productname %>
</p>
<p>
  <strong>Reserveprice:</strong>
  <%= @product.reserveprice %>
</p>
<%= link_to 'Back', products_path %>
<br /><br />

<h2>List of bids</h2>
<table>
  <thead>
    <tr>
      <th>Product ID</th>
      <th>Bid Price</th>
    </tr>
  </thead>
  <tbody>
    <% @bids.each do |bid| %>
      <tr>
        <td><%= bid.product_id %></td>
        <td><%= bid.bidprice %></td>
      </tr>
    <% end %>       
  </tbody>
</table>
<br /><br />

<h2>Form For Bids</h2>
<h4>Make a Bid</h4>
<%= form_for(@bid) do |b| %>
  <div class="field">
    <%= b.number_field :bidprice %>
  </div>
  <div>
    <%= b.hidden_field :product_id, :value => (params[:id]) %>
  </div>
  <div class="actions">
    <%= b.submit %>
  </div>
<% end %>

On clicking 'submit' the bids controller#create action is called.

  def create
    @bid = Bid.new(bid_params)
      if @bid.save
        redirect_to "/products/#{@bid.product_id}", notice: 'Bid was successfully created.' 
      else
        render action: 'new' 
      end
  end

So a sucessful bid returns the user to the product show view and adds the record to the bids section of the page.

I have a validates_presence_of validation in the bids model. So if the user leaves the bids field empty the validation fails and the bids controller crate action renders the bids new view. Including the error messages. The bids new view includes the scaffold generated code "<% if @bid.errors.any? %> etc"

What I actually want to do instead of rendering the new action is to throw the user back to the products show page as it does when a successful bid is made only to show the error messages on the products show page. Basically this gives the user the sense of staying on the products show page instead of throwing them to the bids new page.

The problem I have is how do I get the error message to pass back to the products show page? If I edit the bids controller create action code above and replace the "render action: 'new'" line with

redirect_to "/products/#{@bid.product_id}"

then the user is returned to the page but the error message isn't passed. I think this is because the error message is stored as part of the rendering of the new action (of the bids controller)

So my question is how do I achieve what I want to do? To return to the products show view and show the error messages that come from the failed validation. Thank you.

PS - I kow I need to add the scaffold generated code "<% if @bid.errors.any? %> ..." code to the products show view but I haven't done so in the code above because it will cause a NilMethod error because the error isnt created for the method to exist on it.

回答1:

You want something like this

redirect_to product_path(@bid.product_id), :flash => { :error => @bid.errors.full_messages.join(', ') }

This will redirect to the product page but pass the errors as a flash message.



回答2:

One quick option would be to render the show product view from within the bids#create action:

if @bid.save
  redirect_to "/products/#{@bid.product_id}", notice: 'Bid was successfully created.' 
else
  @product = Product.find(params[:bid][:product_id])
  render template: 'products/show'

This will display what you want but the URL will still show as the create bid path which might be confusing. And it might be a little hard to read since we have to bounce between two controllers to accomplish this one task.

A better option might be to move the bid creation action into your products controller. To do this you could...

Add a new route for /products/:id/create_bid in routes.rb:

resources :products do
  member do
    post 'create_bid'
  end
end

Use this route in your bid creation form:

<h4>Make a Bid</h4>
<%= form_for(@bid, url: create_bid_product_path(@product)) do |b| %>

Define this action in your products controller:

def create_bid
  @bid = Bid.new(bid_params)
  if @bid.save
    redirect_to product_url(params[:id]), notice: 'Bid was successfully created.' 
  else
    @product = Product.find(params[:id])
    render action: 'show' 
  end
end

And copy over the bid_params method from the bids controller into the products controller.