views:

376

answers:

4

In my Review model, I have the following:

class Review < ActiveRecord::Base
  belongs_to :vendor
  belongs_to :user
  has_many :votes  

  validates_presence_of :summary
end

I submit a new entry as follows in the URL:

vendors/9/reviews/new

The new.html.erb contains a form as follows:

<%= error_messages_for 'review' %>

<h1>New review for <%= link_to @vendor.name, @vendor%></h1>

<% form_for(@review, :url =>vendor_reviews_path(@vendor.id)) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :summary %><br />
    <%= f.text_area :summary, :rows=>'3', :class=>'input_summary' %>

    <%= f.hidden_field :vendor_id, :value => @vendor.id %>
  </p>
  <p>
    <%= f.submit 'Submit Review' %>
  </p>
<% end %>

When I leave the field for :summary blank, I get an error, not a validation message:

You have a nil object when you didn't expect it! The error occurred while evaluating nil.name

Extracted source (around line #3):

1: <%= error_messages_for 'review' %>
2: 
3: <h1>New review for <%= link_to @vendor.name, @vendor%></h1>

I don't understand what is happening, it works if :summary is populated

  def new
    @review = Review.new
    @vendor = Vendor.find(params[:vendor_id])
    @review = @vendor.reviews.build

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @review }
    end
  end

def create
    @review = Review.new(params[:review])

    #@vendor = Vendor.find(params[:vendor_id]) #instantiate the vendor from the URL id -- NOT WOKRING
    #@review = @vendor.reviews.build #build a review with vendor_id -- NOT working
    @review = @current_user.reviews.build params[:review]#build a review with the current_user id

    respond_to do |format|
      if @review.save
        flash[:notice] = 'Review was successfully created.'
        format.html { redirect_to review_path(@review) }
        format.xml  { render :xml => @review, :status => :created, :location => @review }
      else
        format.html { redirect_to new_review_path(@review) }
        format.xml  { render :xml => @review.errors, :status => :unprocessable_entity }
      end
    end
  end

My guess is that when it fails it is going to redirect_to new_review_path(@review) and so doesn't know the vendor it. How can I redirect to vendor/:vendor_id/reviews/new instead?

A: 

What code do you have in the new and create actions in your ReviewsController?

I suspect that your new Review is failing validation because the summary field is blank and then when the form is redisplayed on validation failure, the @vendor instance variable is nil.

You need to make sure that @vendor is assigned a value for both code paths.

John Topley
that might be it, although I'm trying to have vendor/:vendor_id/reviews be the path...I've added how my new actions
Angela
I think the URL it is failing over to is wrong...not sure how to set it so that is goes to vendor/:vendor_id/review/new to display the form upon validation failure?
Angela
okay, I am not assigning a value for vendor, but as you can see, I am trying to use restful so that the :vendor_id is in the path...?
Angela
A: 

You probably don't have @vendor member variable set - but to fix this, it would be more correct to use not @vendor directly, but through your @review variable instance.

If you are creating new review, you already have @review member variable created, and you simply are populating fields in it - so, you need to set the vendor for @review (unless it's optional)... it would be more correct to use @review.vendor.name instead.

(If vendor is optional, then you obviously must catch all vendor.nil? cases.)

Valters Vingolds
hmmm...vendor is not optional...you mean to use @review.vendor in the form?
Angela
you meant to set @review.vendor within the controller for both new and create? as @review.vendor = @vendor?
Angela
I think the URL it is failing over to is wrong...not sure how to set it so that is goes to vendor/:vendor_id/review/new to display the form upon validation failure?
Angela
A: 

I think you need to render :action => 'new' instead of your redirect_to new_review_path(@review). This will keep your error_messages on the @review object. By redirecting you are losing the old object and creating a new one.

As others has said, you also need to make sure you re-populate the @vender variable in your create method before rendering the view.

PS. I like to use the ardes resources_controller plugin for bog standard controller actions like these, makes life a lot easier for me and it handles nested resources really well.

tsdbrown
Some some reason my link doesn't work correctly? The underscore is being converted once I save the answer, see below:http://github.com/econsultancy/resources_controller/tree/master
tsdbrown
but how do I keep the restful URL? To get to the View/New it is using vendor/:vendor_id/review/new...so shouldn't I still have the vendor ID in the URL already?
Angela
Sorry but I'm a little confused as to what you mean? Have you tried the `format.html { render :action => "new" }`. code? The resulting URL will still contain /vendors/vendor_id. By rendering you are not actually calling the method 'new' but just rendering the template, so you will still need to set the @vender variable in your create action. Give it a try and post back the result.
tsdbrown
A: 

Did you ever figure out what was going on? I am also having a similar problem. I am starting to suspect that it has something to do with nested resources.

I have a model called Page and model called Post. Each page has_many posts and each post belongs_to a page. I am validating presence of title and I get a 422 Validation error page with a message:

The change you wanted was rejected.

Maybe you tried to change something you didn't have access to.

It doesn't redirect to the pages/page_id/posts/new page with nice error feedback like it's supposed to.

I am doing something kind of fancy with my urls. Instead of the plain, old boring pages/1 url, I've trained RoR to use a page slug (like WordPress) like this pages/page-slug. This is working well, and when title is present the post saves just fine, so I'm pretty sure it's not because of that. Anyway, here's my code:

  def new
    @post = @page.posts.new

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @post }
    end
  end

  # GET /posts/1/edit
  def edit
    @post = Post.find(params[:id])
  end

  # POST /posts
  # POST /posts.xml
  def create
    @page = Page.find(params[:post][:page_id])
    @post = @page.posts.create!(params[:post])
    current_page_id = @page.id
    if Post.maximum(:position, :conditions => ['page_id = ?', current_page_id])
      last_position = Post.maximum(:position, :conditions => ['page_id = ?', current_page_id]) + 1
    else
      last_position = 0
    end
    @post.position = last_position

    respond_to do |format|
      if @post.save
        flash[:notice] = 'Post was successfully created.'
        format.html { redirect_to(@page) }
        format.xml  { render :xml => @post, :status => :created, :location => @post }
      else
        flash[:error] = 'Post was not created. Please correct the errors below.'
        format.html { render :action => "new" }
        format.xml  { render :xml => @post.errors, :status => :unprocessable_entity }
      end
    end
  end

I'm using a before_filter to catch the page_id and fetch the Page from the database:

  def get_page_by_slug
    if params[:page_id].to_i == 0
      slug = params[:page_id]
      @page = Page.find(:first, :conditions => "page_slug = '#{slug}'")
    else
      @page = Page.find(params[:page_id])
    end
  end

routes.rb

  map.resources :pages
  map.resources :posts

  map.connect ':pages/:page_slug', :controller => 'pages', :action => 'show'
  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'
  map.resources :pages, :has_many => :posts
  map.root :controller => "pages"

  map.resource  :session

Okay, there's my addition to this problem. It's a little different than what you're experiencing but also similar.

Rodney Blevins