0

I've built a simple app that has businesses, and those businesses have many reviews. I'm using a partial _form to create/update reviews. The create action works fine, but the update action creates a new review instead of updating the review like its supposed to. Here's the relevant code (irrelevant code removed for brevity):

Code from routes.rb:

resources :businesses do
  resources :reviews
end

Code from models/business.rb:

class Business < ActiveRecord::Base
  has_many :reviews, dependent: :destroy
  validates_associated :reviews
end

Code from models/review.rb:

class Review < ActiveRecord::Base
  belongs_to :business
  belongs_to :user
end

Code from controllers/reviews_controller.rb:

class ReviewsController < ApplicationController
  load_and_authorize_resource

  before_filter :load_business

  def new
  end

  def index
  end

  def create
    @review = @business.reviews.create(review_params)
    @review.user_id = current_user.id
    @review.reviewer = current_user.first_name + ' ' + current_user.last_name
    if @review.save
        redirect_to business_path(@business)
        flash[:notice] = 'Review posted!'
    else
        redirect_to business_path(@business)
        flash[:danger] = 'Your review has an error. Please double check!'
    end
  end

  def edit
    @review = @business.reviews.find(params[:id])
  end

  def update
    @review = @business.reviews.find(params[:id])
    if @review.update(params[review_params])
        redirect_to businesses_path(@business)
        flash[:notice] = 'Review updated!'
    else
        render :action => 'edit'
    end
  end

  def destroy
    @review = @business.reviews.find(params[:id])
    @review.destroy
    redirect_to business_path(@business)
    flash[:notice] = "Review deleted."
  end

  private

  def load_business
    @business = Business.find(params[:business_id])
  end

  def review_params
    params.require(:review).permit(:review, :rating)
  end
end

Code for `reviews/_form.html.erb:

<%= simple_form_for([@business, @business.reviews.build]) do |f| %>
  <%= f.error_notification %>
  <p>
    <%= f.input :review %>
  </p>
  <p>
    <%= f.input :rating, collection: 1..10 %>
  </p>
  <p>
    <%= f.submit :class => 'btn btn-primary' %>
  </p>
<% end %>

The reviews are rendered in the business#show view as a partial. Code for `reviews/_review.html.erb:

<% if !review.user_id.nil? %>
  <div class="well">
    <p>
      <strong>Reviewer:</strong>
      <%= review.reviewer %>
    </p>
    <p>
      <strong>Review:</strong>
      <%= review.review %>
    </p>
    <p>
      <strong>Rating:</strong>
      <%= review.rating %>
    </p>
    <span class = "timestamp">
      posted <%= time_ago_in_words(review.created_at) %> ago.
    </span>
    <% if can? :update, review %>
      <span class = "timestamp">
        <%= link_to "Edit", [:edit, review.business, review], class: 'btn btn-small' %>|
        <%= link_to "Delete", [review.business, review], method: :delete,
                   data: { confirm: 'Are you sure?' } %>
      </span>
    <% end %>
  </div>
<% end %>

Code for reviews/edit.html.erb:

<h1>Edit Review</h1>

<%= render 'form', review: @review %>

<button type="button" class="btn btn-default"><%= link_to 'Back', businesses_path %></button>

The weird thing is, when I click on the "Edit" link in _review.html.erb, the URL that's generated appears to be correct, for example I get http://localhost:3000/businesses/10/reviews/82/edit. However, the _form.html.erb is empty where I would expect it to be populated with the current data for review #82. Furthermore, when I click the "Create Review" button, it creates a new review and doesn't edit review #82.

Here's the server logs after I click "Edit" for an existing review:

Started GET "/businesses/3/reviews/81/edit" for 127.0.0.1 at 2014-08-28 19:18:58 -0500
Processing by ReviewsController#edit as HTML
  Parameters: {"business_id"=>"3", "id"=>"81"}
  User Load (0.8ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = 8  ORDER BY "users"."id" ASC LIMIT 1
  Review Load (6.9ms)  SELECT  "reviews".* FROM "reviews"  WHERE "reviews"."id" = $1 LIMIT 1  [["id", 81]]
  Business Load (0.5ms)  SELECT  "businesses".* FROM "businesses"  WHERE "businesses"."id" = $1 LIMIT 1  [["id", 3]]
  Review Load (27.4ms)  SELECT  "reviews".* FROM "reviews"  WHERE "reviews"."business_id" = $1 AND "reviews"."id" = $2 LIMIT 1  [["business_id", 3], ["id", 81]]
  Rendered reviews/_form.html.erb (107.0ms)
  Rendered reviews/edit.html.erb within layouts/application (143.4ms)
Completed 200 OK in 967ms (Views: 669.2ms | ActiveRecord: 35.6ms)


Started POST "/businesses/3/reviews" for 127.0.0.1 at 2014-08-28 19:19:23 -0500
Processing by ReviewsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"<redacted>", "review"=>{"review"=>"Edit review #81.", "rating"=>"1"}, "commit"=>"Create Review", "business_id"=>"3"}
  User Load (0.8ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = 8  ORDER BY "users"."id" ASC LIMIT 1
  Business Load (0.6ms)  SELECT  "businesses".* FROM "businesses"  WHERE "businesses"."id" = $1 LIMIT 1  [["id", 3]]
    (28.9ms)  BEGIN
  SQL (132.9ms)  INSERT INTO "reviews" ("business_id", "created_at", "rating", "review", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["business_id", 3], ["created_at", "2014-08-29 00:19:23.470750"], ["rating", 1], ["review", "Edit review #81."], ["updated_at", "2014-08-29 00:19:23.470750"]]
    (39.7ms)  COMMIT
    (0.3ms)  BEGIN
  SQL (0.9ms)  UPDATE "reviews" SET "reviewer" = $1, "updated_at" = $2, "user_id" = $3 WHERE "reviews"."id" = 83  [["reviewer", "<redacted>"], ["updated_at", "2014-08-29 00:19:23.692880"], ["user_id", 8]]
    (17.5ms)  COMMIT
Redirected to http://localhost:3000/businesses/3
Completed 302 Found in 553ms (ActiveRecord: 221.6ms)


Started GET "/businesses/3" for 127.0.0.1 at 2014-08-28 19:19:23 -0500
Processing by BusinessesController#show as HTML
  Parameters: {"id"=>"3"}
  User Load (0.7ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = 8  ORDER BY "users"."id" ASC LIMIT 1
  Business Load (0.5ms)  SELECT  "businesses".* FROM "businesses"  WHERE "businesses"."id" = $1 LIMIT 1  [["id", 3]]
    (0.5ms)  SELECT AVG("reviews"."rating") AS avg_id FROM "reviews"  WHERE "reviews"."business_id" = $1  [["business_id", 3]]
  Rendered reviews/_form.html.erb (5.7ms)
  Review Load (0.6ms)  SELECT "reviews".* FROM "reviews"  WHERE "reviews"."business_id" = $1  ORDER BY "reviews"."id" DESC  [["business_id", 3]]
  Rendered reviews/_review.html.erb (58.1ms)
  Rendered businesses/show.html.erb within layouts/application (173.5ms)
  Rendered users/_avatar.html.haml (13.1ms)
  Rendered layouts/_navigation.html.haml (30.9ms)
  Rendered layouts/_messages.html.haml (1.5ms)
  Rendered layouts/_footer.html.haml (16.5ms)
  Rendered layouts/_analytics.html.haml (0.6ms)
  Rendered layouts/_javascripts.html.haml (0.5ms)
Completed 200 OK in 421ms (Views: 391.5ms | ActiveRecord: 2.3ms)

It's clear that the second action in the server log is "Started POST..." which isn't correct. While I'm at it, I may as well provide the relevant routes from rake routes:

business_reviews     GET      /businesses/:business_id/reviews(.:format)          reviews#index
                     POST     /businesses/:business_id/reviews(.:format)          reviews#create
new_business_review  GET      /businesses/:business_id/reviews/new(.:format)      reviews#new
edit_business_review GET      /businesses/:business_id/reviews/:id/edit(.:format) reviews#edit
business_review      GET      /businesses/:business_id/reviews/:id(.:format)      reviews#show
                     PATCH    /businesses/:business_id/reviews/:id(.:format)      reviews#update
                     PUT      /businesses/:business_id/reviews/:id(.:format)      reviews#update
                     DELETE   /businesses/:business_id/reviews/:id(.:format)      reviews#destroy

I've already tried implementing every answer I could find on here, but to no avail. The closest one I've found to being almost my exact problem was this one, but it didn't help either: Rails: Use same partial for creating and editing nested items

Many thanks.

Community
  • 1
  • 1
K.C. Barrett
  • 152
  • 1
  • 9
  • Try adding `:id` to the `review_params` `params.require(:review).permit(:id,:review, :rating)` – Pavan Aug 29 '14 at 05:42
  • The only time I use review_params is during CREATE. Doesn't the id just get assigned when it's added to the database? Does it really need to be permitted? – K.C. Barrett Aug 29 '14 at 15:02

1 Answers1

0

According to your output in console, you've got a sql insert stmt before update stmt. Your code in the form cause it. @business.reviews.build will create new review object even when edit.

Update your form_for to:

<%= simple_form_for([@business, @review]) do |f| %>

Instead of @business.reviews.build. You should create objects in controller instead of view.

So you may need to new review objects in your new action too.

  def new
     @review = @business.reviews.build
  end

I have some more suggestions for you to simplify your code that could make things easier.

Use review id to find review, instead of through business.
Such as code in edit and update, you could:

  def edit
    @review = Reviews.find(params[:id])
  end

  def update
    @review = Reviews.find(params[:id])
    ...
  end
Jaugar Chang
  • 3,176
  • 2
  • 13
  • 23
  • When I make your first 2 changes and click on a business from business#index, I receive this error: 'NoMethodError at /businesses/3: undefined method `model_name' for NilClass:Class.' I reversed your changes so I could select a business, and remade your changes once on business#show. I'm able to click 'Edit' on a review and it takes me to the edit page with the form correctly populated. Clicking "Update" on the form throws this error: 'ArgumentError at /businesses/3/reviews/84: When assigning attributes, you must pass a hash as an argument.' – K.C. Barrett Aug 29 '14 at 15:47
  • 1.I miss that part, you should do `@business.reviews.build` on business#show when there is no review. 2. `ArgumentError` of update may cause by the code of `@review.update(params[review_params])`. I think it should be change to `@review.update(review_params)` – Jaugar Chang Aug 29 '14 at 16:25
  • That fix allows the editing of reviews, assuming I use the correct version of the form. If I use the `[@business, @business.reviews.build]` version, clicking 'Edit' creates a new review. If I change it to the `[@business, @review]` version and click 'Edit', the form is pre-populated and updates properly. But, I can't even get to business#show if I use this version. I get the first error I mentioned previously. That's why I'm changing it once I've landed on business#show. It's like a need two versions of the form. What am I missing? Isn't that why we use partials in the first place? – K.C. Barrett Aug 29 '14 at 17:14
  • I think you should follow the MVC principle(http://stackoverflow.com/a/1947268/3630826). There must be some other code in your view manuplated models like `@business.reviews.build`. Because you didn't post your business#show scripts, I couldn't 100% sure about that. If can follow the MVC principle, you don't need two version for form, you just to take care of your controller code. – Jaugar Chang Aug 29 '14 at 23:07