Re: Creating Two Models in One Form
This is definitely a great tutorial !
I've a quick question.
Imagine you want each tasks of your project to be attached to this project. I think you will add
validates_presence_of :project_id within the task model.
However if you do that when there is a validation error you get two nasty messages not very user friendly :
Tasks is invalid
Project can't be blank
On a computer point of view these messages make total sense, however form the user side...
Is there someone knows how to hide validation messages which concerns database relations ?Thanks
Cyrille
Try
validates_associated :tasks
Re: Creating Two Models in One Form
Does build work with a belongs_to and has_one relationship? In my model a user has_one :provider and a provider belongs_to :user.
When I try to follow the tutorial I substitute the singular for the plural to reflect the has_one instead of has_many e.g.
@provider = @user.provider.build(params[:provider])
instead of
@provider = @user.providers.build(params[:provider])
but I get an error message noting that "you have a nil object when you didn't expect it" "the error occured while evaluating nil.build"
Any suggestions?
Re: Creating Two Models in One Form
You need to do this:
@provider = @user.build_provider(params[:provider])
Re: Creating Two Models in One Form
I am surprised no one has mentioned this. The view can also be coded using fields_for. This has the advantage of being much DRYer/cleaner for longer forms.
# in the projects/new.rhtml
<h1>New Project</h1><%= error_messages_for :project %>
<%= error_messages_for :task %><%= form_for :project, :url => { :action => 'create'} %>
<p>
Project Name:
<%= f.text_field :name %>
</p>
<%= fields_for :tasks do |detail| do %>
<p>
First Task:
<%= f.text_field :name %>
</p>
<%= end %>
<p>
<%= submit_tag 'Create' %>
</p>
<%= end %>
Re: Creating Two Models in One Form
Good point. I use fields_for in the second tutorial, but it can be used here as well.
Re: Creating Two Models in One Form
awesome. but in the controller i just did this.
<code>
def create
@tasks = Task.new(params[:task])
@tasks.project_id = params[:project]
@tasks.save
end
</code>
I think this makes more sense to me, being a noob . remember if you are using drop down menues
<code><%= collection_select("project", "id" , Project.find(:all), "id", "title") %></code>
then in your controller u can just add
<code>
@tasks.project_id = params[:project][:id]
</code>
i donno if its useful or not, just that usually when you are choosing categories, its common to use drop down menus especially if its really long.
Re: Creating Two Models in One Form
Hi, I was trying out your example as given in this post. After creating couple of new project i found that the task name in the tasks field is set to null. My code is identical to your code. Any ideas what can be wrong.
If you want me to copy paste my code here for debuggin purpose let me know.
Cheers
Hari
Re: Creating Two Models in One Form
Thanks to this tutorial, I have conquored a problem I've been having for a week or so. I am faced with another problem. I am working with 3 models and one controller. I have figured out how to display in my list method one of the values associated with my main model. however I can't get the values from the 2nd model displayed. In the list i can get the category displayed but i can't get anything from the menuItemSize table displayed but the name of the model. here is my code in snippets..Thank you..
MODELS
=========================
class MenuItem < ActiveRecord::Base
belongs_to :category
has_many :menu_item_sizesvalidates_associated :menu_item_sizes
validates_presence_of :description, :name
endclass MenuItemSize < ActiveRecord::Base
belongs_to :menu_item
validates_presence_of :price, :size
validates_numericality_of :price, :only_integer => true
endclass Category < ActiveRecord::Base
has_many :menu_items
validates_presence_of :categoryend
CONTROLLER..
==============================
def create
@menu_item = MenuItem.new(params[:menu_item])
@menu_item_size = @menu_item.menu_item_sizes.build(params[:menu_item_size])
if @menu_item.save
flash[:notice] = 'a new menu item was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
endend
def list
@menu_item_pages, @menu_items = paginate :menu_items, :per_page => 10
end
LIST.rhtml
====================================
<h1>Listing menu_items</h1><table border='1' >
<tr>
<th>menu Item</th>
<th>Description</th>
<th>Category</th>
<th>size</th>
<th>price</th>
</tr>
<% for menu_item in @menu_items %>
<tr>
<td><%=h(menu_item.name)%></td>
<td><%=h(menu_item.description)%></td>
<td><%=h(menu_item.category.category)%></td>
<td><%=h(menu_item.menu_item_sizes.price)%></td>
<td><%= link_to 'Show', :action => 'show', :id => menu_item %></td>
<td><%= link_to 'Edit', :action => 'edit', :id => menu_item %></td>
<td><%= link_to 'Destroy', { :action => 'destroy', :id => menu_item }, :confirm => 'Are you sure?', :method => :post %></td>
</tr>
<% end %>
</table><%= link_to 'Previous page', { :page => @menu_item_pages.current.previous } if @menu_item_pages.current.previous %>
<%= link_to 'Next page', { :page => @menu_item_pages.current.next } if @menu_item_pages.current.next %><br />
<%= link_to 'New menu_item', :action => 'new' %>
Re: Creating Two Models in One Form
any takers....
-B
Re: Creating Two Models in One Form
The problem is you are calling "prices" on an array of menu item sizes. A given menu item can have many sizes, so Rails doesn't know what to do when you call price on many:
<td><%=h(menu_item.menu_item_sizes.price)%></td>
You can either just call it on the first one:
<td><%=h(menu_item.menu_item_sizes.first.price)%></td>
Or loop through them and display multiple prices:
<% for menu_item_size in menu_item.menu_item_sizes %>
<%= h(menu_item_size.price) %>
<% end %>
Re: Creating Two Models in One Form
def create
....
@task = @project.tasks.build(params[:task])
.....
end
The @project instance variable is set normally, but what's up with how the task is created? We use a fancy method to help us out here. The @project.tasks.build method creates a task (just like Task.new) and adds it to the @project at the same time. Pretty cool huh?
I've tried to modify / adapt this code to the "cookbook" tutorial.
In the category_controller.rb file I added a 'new_mult' (multiple) method :
def new_mult
@category = Category.new
@recipe = Recipe.new
end
I also added a "create_mult" method :
def create_mult
@category = Category.new(params[:category])
@recipe = @category.recipes.build(params[:recipe])
if @category.save
redirect_to :action => 'index'
else
render :action => 'new_mult'
end
end
I don't get any errors, but only the category is being added. (when I call localhost://cook/category/new_mult) from the browser.
I suspect the "@recipe = @category.recipes.build(params[:recipe])" is not
working.
Is the "<variable>.<table_name>.build(....." method built in? .. or do I need
to define the "...build.." method somewhere?
Mike
Last edited by mstram (2007-05-15 18:28:53)
Re: Creating Two Models in One Form
How is recipe related to category? And are you certain Recipe is passing validation? Lastly, what version of Rails are you using?
Re: Creating Two Models in One Form
Found the problem, there was both a 'start_form_tag' and a 'form_tag' both had :action => 'create', I changed one of them to 'create_mult' but not the other ... I don't know why there are two clauses in that file, I removed the second one, and it's working.
Would still like to find out about the 'build' thing .. where is that documented ?
Mike
Re: Creating Two Models in One Form
That's documented in the has_many method.
Re: Creating Two Models in One Form
Ok, thanks !
Mike
Re: Creating Two Models in One Form
Hi,
I am confused about the lack of transactions here. The example in Pragmatic Rails uses transactions for the save part. Is that redundant or just another way of doing this?
Thanks.
Re: Creating Two Models in One Form
It just so happens Rails handles this properly without a transaction. When the project is saved, it automatically saves the task along with it. If any are invalid it won't save either one.
However, if I redid this tutorial (along with the others on this forum) I would probably use a transaction. First of all because the behavior of saving multiple models in ActiveRecord is not very consistant. For example, while editing the models, saving the parent model (project) will not automatically save the children models. The behavior also varies depending upon the association (has_one vs. has_many). With a transaction it's easy to just save all of the models, and if it comes to a validation error it rolls back the changes.
Re: Creating Two Models in One Form
Thank-you for this tutorial.
I'm currently editing two models in one form where the models have a has_one/belongs_to relationship (Account has_one AgentDetail. There is validation on both models, however validation errors are not appearing for AgentDetail in the form. Validation is working though, since an account is saved without an associated AgentDetail when AgentDetail fails validation.
My code:
#Accounts Controller
#########
def create
@account = Account.new(params[:account])
@agent_detail = @account.build_agent_detail(params[:agent_detail])
if @account.save
redirect_to some_path
else
render :action => 'new'
end
end
class AgentDetail < ActiveRecord::Base
belongs_to :account
validates_presence_of :agency_name
endclass Account < ActiveRecord::Base
has_one :agent_detail
validates_presence_of :login, :email
end
<%= error_messages_for :account, :agent_detail %>
<% form_for :account, :url => accounts_path do |f| -%>
<legend>Account Details</legend>
<p><label for="login">Login</label><br/>
<%= f.text_field :login %></p>....
<% fields_for :agent_detail do |d| %>....
<legend>Agent Details</legend>
<p><label for="agency_name">Agency name</label><br/>
<%= d.text_field :agency_name %></p>....
Can somebody suggest why validation errors for the agency detail model are not showing up?
Many thanks
Re: Creating Two Models in One Form
when editing a model it works a little differently. The child (agency detail) doesn't automatically get saved/validated so you need to do that manually. You can do this with a transaction.
def create
@account = Account.new(params[:account])
@agent_detail = @account.build_agent_detail(params[:agent_detail])
do Account.transaction
@account.save!
@agent_detail.save!
end
redirect_to some_path
rescue ActiveRecord::RecordInvalid
render :action => 'new'
end