Ruby Forum Ruby on Rails > Help with RoR partial views

Posted by CrazyMenConnected (Guest)
on 19.08.2008 23:17
(Received via mailing list)
Hi, and thank you for reading this.

First of all let me explain that i am a developer learning Ruby and
RubyOnRails for future projects, so i am basically a noob that is in
need of some help. :-)

I created a little RoR project (im using netbeans 6.1 as IDE, but that
should make no difference), that is a very simple product management
web app.
I created it using scaffolding, and CRUD works just fine when called
from the views that the scaffold created. I have some other
controller, the "main" controller with a "welcome" view. This
controller is not tied to any model since it only exists has the
welcome page for the site.

My problem is that i wanted to call the "new" view from "welcome" view
using render :partial=>"products/new", but when ever i call it from
there it gives me an error.

I would like that this partial view would work just like any other
view.
How can i do this?

sample code is bellow

products.rb
[code=]class Product < ActiveRecord::Base
  validates_uniqueness_of :name
end
[/code]

products_controller.rb
[code=]class ProductsController < ApplicationController
  # GET /products
  # GET /products.xml
  def index
    @products = Product.find(:all)

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

  # GET /products/1
  # GET /products/1.xml
  def show
    @product = Product.find(params[:id])

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

  # GET /products/new
  # GET /products/new.xml
  def new
    @product = Product.new

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

  # GET /products/1/edit
  def edit
    @product = Product.find(params[:id])
  end

  # POST /products
  # POST /products.xml
  def create
    @product = Product.new(params[:product])

    respond_to do |format|
      if @product.save
        flash[:notice] = 'Product was successfully created.'
        format.html { redirect_to(@product) }
        format.xml  { render :xml => @product, :status
=> :created, :location => @product }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @product.errors, :status
=> :unprocessable_entity }
      end
    end
  end

  # PUT /products/1
  # PUT /products/1.xml
  def update
    @product = Product.find(params[:id])

    respond_to do |format|
      if @product.update_attributes(params[:product])
        flash[:notice] = 'Product was successfully updated.'
        format.html { redirect_to(@product) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @product.errors, :status
=> :unprocessable_entity }
      end
    end
  end

  # DELETE /products/1
  # DELETE /products/1.xml
  def destroy
    @product = Product.find(params[:id])
    @product.destroy

    respond_to do |format|
      format.html { redirect_to(products_url) }
      format.xml  { head :ok }
    end
  end
end
[/code]

new.html.erb
[code=]<h1>New product</h1>

<% form_for(@product) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.label :price %><br />
    <%= f.text_field :price %>
  </p>
  <p>
    <%= f.label :type %><br />
    <%= f.text_field :type %>
  </p>
  <p>
    <%= f.submit "Create" %>
  </p>
<% end %>

<%= link_to 'Back', products_path %>
[/code]

show.html.erb
[code=]<p>
  <b>Name:</b>
  <%=h @product.name %>
</p>

<p>
  <b>Price:</b>
  <%=h @product.price %>
</p>

<p>
  <b>Type:</b>
  <%=h @product.type %>
</p>


<%= link_to 'Edit', edit_product_path(@product) %> |
<%= link_to 'Back', products_path %>
[/code]

index.html.erb
[code=]<h1>Listing products</h1>

<table>
  <tr>
    <th>Name</th>
    <th>Price</th>
    <th>Type</th>
  </tr>

<% for product in @products %>
  <tr>
    <td><%=h product.name %></td>
    <td><%=h product.price %></td>
    <td><%=h product.type %></td>
    <td><%= link_to 'Show', product %></td>
    <td><%= link_to 'Edit', edit_product_path(product) %></td>
    <td><%= link_to 'Destroy', product, :confirm => 'Are you
sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>

<br />

<%= link_to 'New product', new_product_path %>
[/code]

edit.html.erb
[code=]<h1>Editing product</h1>

<% form_for(@product) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.label :price %><br />
    <%= f.text_field :price %>
  </p>
  <p>
    <%= f.label :type %><br />
    <%= f.text_field :type %>
  </p>
  <p>
    <%= f.submit "Update" %>
  </p>
<% end %>

<%= link_to 'Show', @product %> |
<%= link_to 'Back', products_path %>
[/code]

the partial view _new.html.erb (its a copy of new.html.erb)
[code=]<h1>New product</h1>

<% form_for(@product) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.label :price %><br />
    <%= f.text_field :price %>
  </p>
  <p>
    <%= f.label :type %><br />
    <%= f.text_field :type %>
  </p>
  <p>
    <%= f.submit "Create" %>
  </p>
<% end %>

<%= link_to 'Back', products_path %>
[/code]

main_controller.rb
[code=]class MainController < ApplicationController
  def welcome
  end
end[/code]
welcome.html.erb
[code=]<h1>Main#welcome</h1>
<p>Find me in app/views/main/welcome.html.erb</p>

<%=  render :partial=>"products/new"  %>
[/code]

So my question is, shouldn't the partial view that is being called
from the welcome view, work just like it was being called from
products/new view?

I am aware that this is probably not the best of examples, but imagine
that i have a welcome page, and on that same view i would like to
allow users to search and create new products. And show all the error
messages on the welcome page.
How is it done?

Thank you once more for your time!
CrazyMenConnected
Posted by Roy Pardee (rpardee)
on 19.08.2008 23:42
(Received via mailing list)
Heh--"it gives me an error" covers a lot of ground. ;-)

I'm guessing you get a complaint about there not being a @product 
variable when you hit that form_for call in the partial?  If that's it, 
changing your main_controller.rb's welcome action like so should be a 
fix:

  def welcome
    @product = Product.new
  end

If my guess is wrong, write back w/the error message & stack trace.

Cheers,

-Roy
Posted by Robert Walker (robert4723)
on 20.08.2008 00:00
> the partial view _new.html.erb (its a copy of new.html.erb)
Although this isn't technically wrong, it also isn't DRY. What you would 
actually want to do is decide what part of the new.html.erb page you 
want to share in multiple places. Then, if it were me. I would create a 
separate folder in the project to contain partials that will be shared 
between different views. Maybe something like app/views/shared. In this 
case you probably only want to share the actual form. This way any other 
details of the page can be formatted independently (such as headings, 
layouts, etc.).

And as mentioned previously you'll still need to make sure you load all 
the resources the page needs from the appropriate controller action.

So new.html.erb would end up looking something like this:

#--------------------------
new.html.erb
[code=]<h1>New product</h1>

<%= render :partial => "shared/new_product_form" %>
#--------------------------
Note: check that path, I wrote that off the top of my head. It may not 
be exactly correct, but should be a relative path to the location of the 
partial, wherever that might be.

And of course your welcome page would render that same partial.

Take note that the first line of the partial: <% form_for(@product) do 
|f| %> accesses @product so the controller action that got you to the 
welcome page is responsible to loading that resource.

Roy Pardee wrote:
> Heh--"it gives me an error" covers a lot of ground. ;-)
> 
> I'm guessing you get a complaint about there not being a @product 
> variable when you hit that form_for call in the partial?  If that's it, 
> changing your main_controller.rb's welcome action like so should be a 
> fix:
> 
>   def welcome
>     @product = Product.new
>   end
> 
> If my guess is wrong, write back w/the error message & stack trace.
> 
> Cheers,
> 
> -Roy
Posted by CrazyMenConnected (Guest)
on 20.08.2008 00:48
(Received via mailing list)
I and thank you for your replies.

>>Heh--"it gives me an error" covers a lot of ground. ;-)

Yes your right, and i am sorry for that, my message was a little
ambiguous.

I have changed the main_controller.rb  and i've added the instance
variable has you said. It now looks like this

main_controller.rb
[code]
class MainController < ApplicationController
  def welcome
    @product = Product.new
  end
end
[/code]

There is no error now, the partial is rendered. But now i have another
question.
When i press the "Create" button on the form, all of the output is
redirected back to the products view.
I want the error messages to the displayed on the welcome view if a
user tried to enter the same product twice.
Is it possible to do that? How?
Posted by CrazyMenConnected (Guest)
on 20.08.2008 00:53
(Received via mailing list)
Hello, and thank you for your reply.

You are right when you say the this code is not DRY. I am aware if
lacks a bit of DRYness, but this is just a simple and fast example to
demonstrate my question.

Has said above i changed the main_controller and added the instance
variable on the welcome proc with @product = Product.new.

I can render the partial fine now, but one question still remains.
How do i redirect that partial output to the welcome view?

Because when i hit the "Create" button on the for the welcome view is
not rendered any more, it changes to the products views.
I would like to stay in the welcome view and that and output from the
product partial would still be rendered on the welcome view.

How is this done?

Thanks for your help
CrazyMenConnected
Posted by Roy Pardee (rpardee)
on 20.08.2008 18:29
(Received via mailing list)
Your partial uses a simple form_for(@product) call.  By default that 
will do a POST to the products/create action, which if it's successful, 
does a redirect to products/show/<<id for the new product>> (see the 
create method in your products_controller.rb file).

My advice is to just play around w/rails for a bit & get used to its 
conventions before you try to bend it to your will. ;-)

HTH,

-Roy