Paul Barry
2006-May-31 02:20 UTC
[Rails] How do you create a controller & view to create a list of objects?
I''m trying to figure out how to design a view and controller to work
with a
simple collection. I have a Foo that has many Bars, so here''s what I
did:
$ ruby script/generate model Foo
$ ruby script/generate model Bar
(Uncomment t.column, :name: :string in foo and bar migrations)
(Edit Foo.rb and Bar.rb, add has_many :bars to Foo, belongs_to :foo to Bar)
$ rake db:migrate
$ ruby script/generate scaffold Foo
At this point you can create/edit foos, but no bars. So what I want to be
able to do it create 5 bars at the same time I create a foo. So the form
would look like this:
Name: [_______________________]
Bar #1 Name: [_______________________]
Bar #2 Name: [_______________________]
Bar #3 Name: [_______________________]
Bar #4 Name: [_______________________]
Bar #5 Name: [_______________________]
So, trying to follow the example from
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html, I
added this to app/views/foos/_form.rhtml:
<% 5.times do |i| %>
<p><label for="bar_<%= i %>_name"/><br/>
<%= text_field ''bar[]'', ''name''
%></p>
<% end %>
But I get this error:
You have a nil object when you didn''t expect it!
You might have expected an instance of ActiveRecord::Base.
The error occured while evaluating nil.id_before_type_cast
Extracted source (around line #9):
6:
7: <% 5.times do |i| %>
8: <p><label for="bar_<%= i %>_name"/><br/>
9: <%= text_field ''bar[]'', ''name''
%></p>
10: <% end %>
11:
12: <!--[eoform:foo]-->
Hmmm... so I guess I need a bar array that is initialized with Bar objects?
So I add this to the new method in the FooController:
@bars = []
5.times do |i|
@bars.push Bar.new
end
But then I get this error:
undefined method `id_before_type_cast'' for #<Array:0x3925ee8>
Extracted source (around line #9):
6:
7: <% 5.times do |i| %>
8: <p><label for="bar_<%= i %>_name"/><br/>
9: <%= text_field ''bars[]'', ''name''
%></p>
10: <% end %>
11:
12: <!--[eoform:foo]-->
So I''m stuck, how are you supposed to do this?
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
http://wrath.rubyonrails.org/pipermail/rails/attachments/20060531/dee00d05/attachment.html
Paul Barry
2006-Jun-01 01:02 UTC
[Rails] Re: How do you create a controller & view to create a list of objects?
Thanks to some help from people in #rubyonrails, I''ve made some
progress on
this. Handling an object graph is definately a weakness of Ruby on Rails
when compared to other web frameworks, such as Webwork. First of all, for
any non-Java Railers, Webwork is a Java MVC framework. When working with an
object graph, you use Object Graph Navigation Language (OGNL) to specify the
properties in the forms. For example, lets say you want to have a form that
has a User object that has a property that is a Collection of Addresses.
You first define you model objects:
public class User {
private List<Address> addresses = new ArrayList<Address>();
}
public class Address {
private String street;
private String city;
//etc.
}
Yes, you need to make getters and setters, there is no attr for Java :)
Then, create an Action, which is Webwork terminology for a Controller. Then
you define the class properties for the Action (again, can''t just do it
on
the fly, this is a staticly typed language we''re dealing with)
public class UsersAction {
private User user = new User();
}
So, there is a little more work here setting this up, because you''re
dealing
with Java instead of Ruby, but here''s where Java/Webwork wins out over
Ruby
on Rails. In your view, you might have this:
<input type="text" name="user.address[2].city"/>
Then when you submit your form, Webwork''s type conversion takes care of
things just like you''d want. The city property of the 3rd element
(zero
based index) of the user''s addresses is set to that value of these
field.
As far as I can tell, you can''t do this with Ruby on Rails. The
problem
lies in the fact that RoR converts the params into a Hash of Hashes. So
sticking with the example above, you''d like to have a form like this:
<%= text_field ''user[address]'', ''name'',
"index" => i %>
But that doesn''t work. The only way around it seems to be to create an
addresses Array in the controller, then putting it back into the user on
submit. Then, in the controller, you''d have to do this:
def new
@user = User.new
@addresses = []
5.times {|i| @addresses.push Address.new}
end
And that assumes you know exactly how many Addresses that you will have.
Any way, then when you submit it, you need to put it back together:
def create
@user = User.new(params[:user])
@user.addresses = params[:user].values.collect {|a| Address.new(a)}
end
I''ve got this simple example to work, put I''m having problems
getting it
work with a more complex example. So, somebody prove me wrong. Tell me
there''s a better way to handle an object graph with Ruby On Rails.
On 5/30/06, Paul Barry <mail@paulbarry.com> wrote:>
> I''m trying to figure out how to design a view and controller to
work with
> a simple collection. I have a Foo that has many Bars, so here''s
what I did:
>
> $ ruby script/generate model Foo
> $ ruby script/generate model Bar
> (Uncomment t.column, :name: :string in foo and bar migrations)
> (Edit Foo.rb and Bar.rb, add has_many :bars to Foo, belongs_to :foo to
> Bar)
> $ rake db:migrate
> $ ruby script/generate scaffold Foo
>
> At this point you can create/edit foos, but no bars. So what I want to be
> able to do it create 5 bars at the same time I create a foo. So the form
> would look like this:
>
> Name: [_______________________]
> Bar #1 Name: [_______________________]
> Bar #2 Name: [_______________________]
> Bar #3 Name: [_______________________]
> Bar #4 Name: [_______________________]
> Bar #5 Name: [_______________________]
>
> So, trying to follow the example from
> http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html, I
> added this to app/views/foos/_form.rhtml:
>
> <% 5.times do |i| %>
> <p><label for="bar_<%= i %>_name"/><br/>
> <%= text_field ''bar[]'', ''name''
%></p>
> <% end %>
>
> But I get this error:
>
> You have a nil object when you didn''t expect it!
> You might have expected an instance of ActiveRecord::Base.
> The error occured while evaluating nil.id_before_type_cast
>
> Extracted source (around line #9):
>
> 6:
> 7: <% 5.times do |i| %>
> 8: <p><label for="bar_<%= i
%>_name"/><br/>
> 9: <%= text_field ''bar[]'', ''name''
%></p>
> 10: <% end %>
> 11:
> 12: <!--[eoform:foo]-->
>
> Hmmm... so I guess I need a bar array that is initialized with Bar
> objects? So I add this to the new method in the FooController:
>
> @bars = []
> 5.times do |i|
> @bars.push Bar.new
> end
>
> But then I get this error:
>
> undefined method `id_before_type_cast'' for
#<Array:0x3925ee8>
>
> Extracted source (around line #9):
>
> 6:
> 7: <% 5.times do |i| %>
> 8: <p><label for="bar_<%= i
%>_name"/><br/>
> 9: <%= text_field ''bars[]'', ''name''
%></p>
> 10: <% end %>
> 11:
> 12: <!--[eoform:foo]-->
>
> So I''m stuck, how are you supposed to do this?
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
http://wrath.rubyonrails.org/pipermail/rails/attachments/20060601/ececc37a/attachment-0001.html
Paul Barry
2006-Jun-01 13:51 UTC
[Rails] Re: How do you create a controller & view to create a list of objects?
I''m still having no luck with trying to create an object with n child
objects through a form. I feel like I''m close though, hoping someone
can
help me figure it out. What basically happens is that when my form is
submitted to the controller, I have a Foo object and it has a bars
property. Everything is getting populated, the bars property has 5 Bar
objects, but each Bar object''s foo_id is nil. That makes sense,
because the
Foo itself hasn''t been created yet, so there is no id. My hope would
be
that AR would save the Foo to the DB first, then populate the foo_id of each
Bar, then save each Bar. But instead I get 5 "Bars is Invalid"
errors, one
for each Bar in foo.bars. Has anybody been able to create an object with
child objects using RoR? If so, what am I doing wrong? Here''s my
code:
#Pretty standard model classes
class Foo < ActiveRecord::Base
has_many :bars
end
class Bar < ActiveRecord::Base
belongs_to :foo
validates_presence_of :foo_id
end
#Controller with just the relevant methods shown here, new and create
#I am doing something a little funky here
#The form submits to /foos/new instead of /foos/create
#The new method calls create if the request is a post
#This allows me to easily show the form again if there is a problem
#without explicitly rendering the template or calling the new method
class FoosController < ApplicationController
...
def new
if request.post?
create
else
@foo = Foo.new
@bars = []
5.times {|i| @bars.push Bar.new} #There''s probably a cleaner way
to do
this
end
end
def create
@foo = Foo.new(params[:foo])
@foo.bars = params[:bar].values.collect {|bar| Bar.new(bar)}
if @foo.save
flash[:notice] = ''Foo was successfully created.''
redirect_to :action => ''list''
end
@bars = @foo.bars
end
...
end
#The form partial for foo _form.rhtml:
<p><label for="foo_name">Name</label><br/>
<%= text_field ''foo'', ''name''
%></p>
<% @bars.each_index do |i| %>
<p><label for="bar_<%= i %>_name">Bar #<%=i%>:
</label><br/>
<%= text_field ''bar'', ''name'',
"index" => i %></p>
<% end %>
On 5/30/06, Paul Barry <mail@paulbarry.com> wrote:>
> I''m trying to figure out how to design a view and controller to
work with
> a simple collection. I have a Foo that has many Bars, so here''s
what I did:
>
> $ ruby script/generate model Foo
> $ ruby script/generate model Bar
> (Uncomment t.column, :name: :string in foo and bar migrations)
> (Edit Foo.rb and Bar.rb, add has_many :bars to Foo, belongs_to :foo to
> Bar)
> $ rake db:migrate
> $ ruby script/generate scaffold Foo
>
> At this point you can create/edit foos, but no bars. So what I want to be
> able to do it create 5 bars at the same time I create a foo. So the form
> would look like this:
>
> Name: [_______________________]
> Bar #1 Name: [_______________________]
> Bar #2 Name: [_______________________]
> Bar #3 Name: [_______________________]
> Bar #4 Name: [_______________________]
> Bar #5 Name: [_______________________]
>
> So, trying to follow the example from
> http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html, I
> added this to app/views/foos/_form.rhtml:
>
> <% 5.times do |i| %>
> <p><label for="bar_<%= i %>_name"/><br/>
> <%= text_field ''bar[]'', ''name''
%></p>
> <% end %>
>
> But I get this error:
>
> You have a nil object when you didn''t expect it!
> You might have expected an instance of ActiveRecord::Base.
> The error occured while evaluating nil.id_before_type_cast
>
> Extracted source (around line #9):
>
> 6:
> 7: <% 5.times do |i| %>
> 8: <p><label for="bar_<%= i
%>_name"/><br/>
> 9: <%= text_field ''bar[]'', ''name''
%></p>
> 10: <% end %>
> 11:
> 12: <!--[eoform:foo]-->
>
> Hmmm... so I guess I need a bar array that is initialized with Bar
> objects? So I add this to the new method in the FooController:
>
> @bars = []
> 5.times do |i|
> @bars.push Bar.new
> end
>
> But then I get this error:
>
> undefined method `id_before_type_cast'' for
#<Array:0x3925ee8>
>
> Extracted source (around line #9):
>
> 6:
> 7: <% 5.times do |i| %>
> 8: <p><label for="bar_<%= i
%>_name"/><br/>
> 9: <%= text_field ''bars[]'', ''name''
%></p>
> 10: <% end %>
> 11:
> 12: <!--[eoform:foo]-->
>
> So I''m stuck, how are you supposed to do this?
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
http://wrath.rubyonrails.org/pipermail/rails/attachments/20060601/3b741cd9/attachment-0001.html
Paul Barry
2006-Jun-01 15:19 UTC
[Rails] Re: How do you create a controller & view to create a list of objects?
Alright, I''ve boiled this problem down to a specific AR thing, using script/console instead of the forms and controllers: C:\ruby\workspace\eval>ruby script/console Loading development environment.>> foo = Foo.new=> #<Foo:0x3e3c1e0 @attributes={"name"=>nil}, @new_record=true>>> foo.bars = []=> []>> foo.bars << Bar.new=> [#<Bar:0x3e27330 @attributes={"name"=>nil, "foo_id"=>nil}, @new_record=true>]>> foo.save!ActiveRecord::RecordInvalid: Validation failed: Bars is invalid from c:/ruby/ruby- 1.8.4_16/lib/ruby/gems/1.8/gems/activerecord-1.14.2/lib/active_record/validations.rb:736:in`save!'' from (irb):4>>So I guess AR doesn''t know how to save a new record that has child records? Is this the only way to do it: C:\ruby\workspace\eval>ruby script/console Loading development environment.>> foo = Foo.new=> #<Foo:0x3e3c1e0 @attributes={"name"=>nil}, @new_record=true>>> foo.save=> true>> foo.bars << Bar.new=> [#<Bar:0x3e250b0 @errors=#<ActiveRecord::Errors:0x3e22878 @errors={}, @base=#<Bar:0x3e250b0 ...>>, @attributes={"name"=>nil, "foo_id"=>4, "id"=>11}, @new_record=false>]>> foo.save=> true>>If that''s the case, I guess I should probably create a foo.save_with_barsmethod to encapsulate this. Is this how other people are handling saving a record that has_many other records? On 6/1/06, Paul Barry <mail@paulbarry.com> wrote:> > I''m still having no luck with trying to create an object with n child > objects through a form. I feel like I''m close though, hoping someone can > help me figure it out. What basically happens is that when my form is > submitted to the controller, I have a Foo object and it has a bars > property. Everything is getting populated, the bars property has 5 Bar > objects, but each Bar object''s foo_id is nil. That makes sense, because the > Foo itself hasn''t been created yet, so there is no id. My hope would be > that AR would save the Foo to the DB first, then populate the foo_id of each > Bar, then save each Bar. But instead I get 5 "Bars is Invalid" errors, one > for each Bar in foo.bars. Has anybody been able to create an object with > child objects using RoR? If so, what am I doing wrong? Here''s my code: > > #Pretty standard model classes > class Foo < ActiveRecord::Base > has_many :bars > end > > class Bar < ActiveRecord::Base > belongs_to :foo > validates_presence_of :foo_id > end > > #Controller with just the relevant methods shown here, new and create > #I am doing something a little funky here > #The form submits to /foos/new instead of /foos/create > #The new method calls create if the request is a post > #This allows me to easily show the form again if there is a problem > #without explicitly rendering the template or calling the new method > class FoosController < ApplicationController > > ... > > def new > if request.post? > create > else > @foo = Foo.new > @bars = [] > 5.times {|i| @bars.push Bar.new} #There''s probably a cleaner way to > do this > end > end > > def create > @foo = Foo.new(params[:foo]) > @foo.bars = params[:bar].values.collect {|bar| Bar.new(bar)} > if @foo.save > flash[:notice] = ''Foo was successfully created.'' > redirect_to :action => ''list'' > end > @bars = @foo.bars > end > > ... > > end > > > > #The form partial for foo _form.rhtml: > <p><label for="foo_name">Name</label><br/> > <%= text_field ''foo'', ''name'' %></p> > > <% @bars.each_index do |i| %> > <p><label for="bar_<%= i %>_name">Bar #<%=i%>: </label><br/> > <%= text_field ''bar'', ''name'', "index" => i %></p> > <% end %> > > > > > On 5/30/06, Paul Barry <mail@paulbarry.com> wrote: > > > I''m trying to figure out how to design a view and controller to work > > with a simple collection. I have a Foo that has many Bars, so here''s what I > > did: > > > > $ ruby script/generate model Foo > > $ ruby script/generate model Bar > > (Uncomment t.column, :name: :string in foo and bar migrations) > > (Edit Foo.rb and Bar.rb, add has_many :bars to Foo, belongs_to :foo to > > Bar) > > $ rake db:migrate > > $ ruby script/generate scaffold Foo > > > > At this point you can create/edit foos, but no bars. So what I want to > > be able to do it create 5 bars at the same time I create a foo. So the form > > would look like this: > > > > Name: [_______________________] > > Bar #1 Name: [_______________________] > > Bar #2 Name: [_______________________] > > Bar #3 Name: [_______________________] > > Bar #4 Name: [_______________________] > > Bar #5 Name: [_______________________] > > > > So, trying to follow the example from http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html > > , I added this to app/views/foos/_form.rhtml: > > > > <% 5.times do |i| %> > > <p><label for="bar_<%= i %>_name"/><br/> > > <%= text_field ''bar[]'', ''name'' %></p> > > <% end %> > > > > But I get this error: > > > > You have a nil object when you didn''t expect it! > > You might have expected an instance of ActiveRecord::Base. > > The error occured while evaluating nil.id_before_type_cast > > > > Extracted source (around line #9): > > > > 6: > > 7: <% 5.times do |i| %> > > 8: <p><label for="bar_<%= i %>_name"/><br/> > > 9: <%= text_field ''bar[]'', ''name'' %></p> > > 10: <% end %> > > 11: > > 12: <!--[eoform:foo]--> > > > > Hmmm... so I guess I need a bar array that is initialized with Bar > > objects? So I add this to the new method in the FooController: > > > > @bars = [] > > 5.times do |i| > > @bars.push Bar.new > > end > > > > But then I get this error: > > > > undefined method `id_before_type_cast'' for #<Array:0x3925ee8> > > > > Extracted source (around line #9): > > > > 6: > > 7: <% 5.times do |i| %> > > 8: <p><label for="bar_<%= i %>_name"/><br/> > > 9: <%= text_field ''bars[]'', ''name'' %></p> > > 10: <% end %> > > 11: > > 12: <!--[eoform:foo]--> > > > > So I''m stuck, how are you supposed to do this? > > > > > > >-------------- next part -------------- An HTML attachment was scrubbed... URL: http://wrath.rubyonrails.org/pipermail/rails/attachments/20060601/5274c3bc/attachment.html
Stephen Bartholomew
2006-Jun-01 15:35 UTC
[Rails] Re: How do you create a controller & view to create a list o
Could you do:
foo = Foo.new
foo.save
if foo.errors.empty?
params[:bars].each do |bar|
foo.bars.create(bar)
end
end
I think the trouble with any of these methods is error handling.
Thinking about it, you would have to check that all the bars were valid
first. Could get kinda complicated.
When i''ve *had* to do this, i''ve created the parent then the
children as
above. For error checking i check the creation of each child and make a
custom hash of errors. That''s as hacky as hell though and was more of
a
necessity than a well thought out solution :0)
Steve
Paul Barry wrote:> Alright, I''ve boiled this problem down to a specific AR thing,
using
> script/console instead of the forms and controllers:
>
> C:\ruby\workspace\eval>ruby script/console
> Loading development environment.
>>> foo = Foo.new
> => #<Foo:0x3e3c1e0 @attributes={"name"=>nil},
@new_record=true>
>>> foo.bars = []
> => []
>>> foo.bars << Bar.new
> => [#<Bar:0x3e27330 @attributes={"name"=>nil,
"foo_id"=>nil},
> @new_record=true>]
>>> foo.save!
> ActiveRecord::RecordInvalid: Validation failed: Bars is invalid
> from c:/ruby/ruby-
>
1.8.4_16/lib/ruby/gems/1.8/gems/activerecord-1.14.2/lib/active_record/validations.rb:736:in`save!''
> from (irb):4
>>>
>
> So I guess AR doesn''t know how to save a new record that has child
> records?
> Is this the only way to do it:
>
> C:\ruby\workspace\eval>ruby script/console
> Loading development environment.
>>> foo = Foo.new
> => #<Foo:0x3e3c1e0 @attributes={"name"=>nil},
@new_record=true>
>>> foo.save
> => true
>>> foo.bars << Bar.new
> => [#<Bar:0x3e250b0 @errors=#<ActiveRecord::Errors:0x3e22878
@errors={},
> @base=#<Bar:0x3e250b0 ...>>,
@attributes={"name"=>nil, "foo_id"=>4,
> "id"=>11}, @new_record=false>]
>>> foo.save
> => true
>>>
>
> If that''s the case, I guess I should probably create a
> foo.save_with_barsmethod to encapsulate this. Is this how other
> people are handling saving a
> record that has_many other records?
--
Posted via http://www.ruby-forum.com/.
Paul Barry
2006-Jun-01 15:55 UTC
[Rails] Re: How do you create a controller & view to create a list of objects?
So now I''m trying to create a method that copies the bars into a
temporary
array, saves the Foo, then adds the bars back to the Foo, and save the foo
again. Seems to be the only way this is going to work. In the porcess,
I''ve run across something about Ruby that is weird (probably only weird
because I''m used to the Java way of things). Here''s my
method:
def save_new
logger.debug "self.bars = #{self.bars.inspect}"
tmp = self.bars
self.bars = []
logger.debug "tmp = #{tmp.inspect}"
logger.debug "self.bars = #{self.bars.inspect}"
end
Ignore the fact that this does nothing for now. Being a Java programmer
here, the way I read what this code does is:
Assign the variable tmp to point to the object that self.bars points to
Assign the variable self.bars to point to a new object, an empty array
So I would expect the variable tmp to still be pointing to the original
array that self.bars points to. But I guess that''s not the case with
Ruby?
When you run it, it prints this:
self.bars = [#<Bar:0x3ce0888 ...
tmp = []
self.bars = []
So I assume tmp is pointing to the variable (or maybe the word reference is
more appropriate?) self.bars, not what self.bars points to. And once I make
self.bars point to something else, tmp then in turn points to that. So in
otherwords, tmp is a reference to a reference, and self.bars is a reference
to an Array. Just looking for someone to confirm what I''m finding and
that
I actually understand what is going on.
On 6/1/06, Paul Barry <mail@paulbarry.com> wrote:>
> Alright, I''ve boiled this problem down to a specific AR thing,
using
> script/console instead of the forms and controllers:
>
> C:\ruby\workspace\eval>ruby script/console
> Loading development environment.
> >> foo = Foo.new
> => #<Foo:0x3e3c1e0 @attributes={"name"=>nil},
@new_record=true>
> >> foo.bars = []
> => []
> >> foo.bars << Bar.new
> => [#<Bar:0x3e27330 @attributes={"name"=>nil,
"foo_id"=>nil},
> @new_record=true>]
> >> foo.save!
> ActiveRecord::RecordInvalid: Validation failed: Bars is invalid
> from c:/ruby/ruby-
>
1.8.4_16/lib/ruby/gems/1.8/gems/activerecord-1.14.2/lib/active_record/validations.rb:736:in`save!''
> from (irb):4
> >>
>
> So I guess AR doesn''t know how to save a new record that has child
> records? Is this the only way to do it:
>
> C:\ruby\workspace\eval>ruby script/console
> Loading development environment.
> >> foo = Foo.new
> => #<Foo:0x3e3c1e0 @attributes={"name"=>nil},
@new_record=true>
> >> foo.save
> => true
> >> foo.bars << Bar.new
> => [#<Bar:0x3e250b0 @errors=#<ActiveRecord::Errors:0x3e22878
@errors={},
> @base=#<Bar:0x3e250b0 ...>>,
@attributes={"name"=>nil, "foo_id"=>4,
> "id"=>11}, @new_record=false>]
> >> foo.save
> => true
> >>
>
> If that''s the case, I guess I should probably create a
foo.save_with_barsmethod to encapsulate this. Is this how other people are
handling saving a
> record that has_many other records?
>
>
> On 6/1/06, Paul Barry <mail@paulbarry.com> wrote:
> >
> > I''m still having no luck with trying to create an object with
n child
> > objects through a form. I feel like I''m close though, hoping
someone can
> > help me figure it out. What basically happens is that when my form is
> > submitted to the controller, I have a Foo object and it has a bars
> > property. Everything is getting populated, the bars property has 5
Bar
> > objects, but each Bar object''s foo_id is nil. That makes
sense, because the
> > Foo itself hasn''t been created yet, so there is no id. My
hope would be
> > that AR would save the Foo to the DB first, then populate the foo_id
of each
> > Bar, then save each Bar. But instead I get 5 "Bars is
Invalid" errors, one
> > for each Bar in foo.bars. Has anybody been able to create an object
> > with child objects using RoR? If so, what am I doing wrong?
Here''s my
> > code:
> >
> > #Pretty standard model classes
> > class Foo < ActiveRecord::Base
> > has_many :bars
> > end
> >
> > class Bar < ActiveRecord::Base
> > belongs_to :foo
> > validates_presence_of :foo_id
> > end
> >
> > #Controller with just the relevant methods shown here, new and create
> > #I am doing something a little funky here
> > #The form submits to /foos/new instead of /foos/create
> > #The new method calls create if the request is a post
> > #This allows me to easily show the form again if there is a problem
> > #without explicitly rendering the template or calling the new method
> > class FoosController < ApplicationController
> >
> > ...
> >
> > def new
> > if request.post?
> > create
> > else
> > @foo = Foo.new
> > @bars = []
> > 5.times {|i| @bars.push Bar.new } #There''s probably a
cleaner way
> > to do this
> > end
> > end
> >
> > def create
> > @foo = Foo.new(params[:foo])
> > @foo.bars = params[:bar].values.collect {|bar| Bar.new(bar)}
> > if @foo.save
> > flash[:notice] = ''Foo was successfully
created.''
> > redirect_to :action => ''list''
> > end
> > @bars = @foo.bars
> > end
> >
> > ...
> >
> > end
> >
> >
> >
> > #The form partial for foo _form.rhtml:
> > <p><label
for="foo_name">Name</label><br/>
> > <%= text_field ''foo'', ''name''
%></p>
> >
> > <% @bars.each_index do |i| %>
> > <p><label for="bar_<%= i %>_name">Bar
#<%=i%>: </label><br/>
> > <%= text_field ''bar'', ''name'',
"index" => i %></p>
> > <% end %>
> >
> >
> >
> >
> > On 5/30/06, Paul Barry < mail@paulbarry.com> wrote:
> >
> > > I''m trying to figure out how to design a view and
controller to work
> > > with a simple collection. I have a Foo that has many Bars, so
here''s what I
> > > did:
> > >
> > > $ ruby script/generate model Foo
> > > $ ruby script/generate model Bar
> > > (Uncomment t.column, :name: :string in foo and bar migrations)
> > > (Edit Foo.rb and Bar.rb, add has_many :bars to Foo, belongs_to
:foo to
> > > Bar)
> > > $ rake db:migrate
> > > $ ruby script/generate scaffold Foo
> > >
> > > At this point you can create/edit foos, but no bars. So what I
want
> > > to be able to do it create 5 bars at the same time I create a
foo. So the
> > > form would look like this:
> > >
> > > Name: [_______________________]
> > > Bar #1 Name: [_______________________]
> > > Bar #2 Name: [_______________________]
> > > Bar #3 Name: [_______________________]
> > > Bar #4 Name: [_______________________]
> > > Bar #5 Name: [_______________________]
> > >
> > > So, trying to follow the example from
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html
> > > , I added this to app/views/foos/_form.rhtml:
> > >
> > > <% 5.times do |i| %>
> > > <p><label for="bar_<%= i
%>_name"/><br/>
> > > <%= text_field ''bar[]'',
''name'' %></p>
> > > <% end %>
> > >
> > > But I get this error:
> > >
> > > You have a nil object when you didn''t expect it!
> > > You might have expected an instance of ActiveRecord::Base.
> > > The error occured while evaluating nil.id_before_type_cast
> > >
> > > Extracted source (around line #9):
> > >
> > > 6:
> > > 7: <% 5.times do |i| %>
> > > 8: <p><label for="bar_<%= i
%>_name"/><br/>
> > > 9: <%= text_field ''bar[]'',
''name'' %></p>
> > > 10: <% end %>
> > > 11:
> > > 12: <!--[eoform:foo]-->
> > >
> > > Hmmm... so I guess I need a bar array that is initialized with
Bar
> > > objects? So I add this to the new method in the FooController:
> > >
> > > @bars = []
> > > 5.times do |i|
> > > @bars.push Bar.new
> > > end
> > >
> > > But then I get this error:
> > >
> > > undefined method `id_before_type_cast'' for
#<Array:0x3925ee8>
> > >
> > > Extracted source (around line #9):
> > >
> > > 6:
> > > 7: <% 5.times do |i| %>
> > > 8: <p><label for="bar_<%= i
%>_name"/><br/>
> > > 9: <%= text_field ''bars[]'',
''name'' %></p>
> > > 10: <% end %>
> > > 11:
> > > 12: <!--[eoform:foo]-->
> > >
> > > So I''m stuck, how are you supposed to do this?
> > >
> > >
> > >
> >
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
http://wrath.rubyonrails.org/pipermail/rails/attachments/20060601/f0db994b/attachment-0001.html
Ezra Zygmuntowicz
2006-Jun-01 19:50 UTC
[Rails] Re: How do you create a controller & view to create a list of objects?
Paul-
You are correct. In ruby variables are kind of like pointers to
objects. When you assign tmp = self.bars they both now point to the
same object. So when you assign self.bars = [] you are changing the
object that self.bars is pointing at and therefor the tmp var is
pointing at the same object. You can get around this by doing tmp =
self.bars.dup and then continue on with what you are doing.
But I don''t understand why you are trying to set self.bars to be an
empty array? Lets look at how you can accomplish this in your
controller. Since you have set validates_presence_of :foo_id in your
comments model you have to either use create to make a new foo or
foo.save before you can add comments to it. This is because there is
no foo_id for the bars to use until you save foo to the db and it
gets an id
# One liner ;)
@foo = Foo.create(:name => ''First Foo!'').bars <<
params[:bars].map
{ |bar| Bar.create(bar) }
#OR with error handling
if @foo = Foo.new(:name => ''First Foo!'').save
@foo.bars << params[:bars].map { |bar| Bar.new(bar) }
if @foo.bars.each {|bar| bar.valid?}
@foo.save
else
# bars have errors
end
else
# foo has errors
end
Depending on what you are trying to do you might want to look at
validates_associated instead of valiodates_presence_of :foo_id.
Cheers-
-Ezra
On Jun 1, 2006, at 8:54 AM, Paul Barry wrote:
> So now I''m trying to create a method that copies the bars into a
> temporary array, saves the Foo, then adds the bars back to the Foo,
> and save the foo again. Seems to be the only way this is going to
> work. In the porcess, I''ve run across something about Ruby that
is
> weird (probably only weird because I''m used to the Java way of
> things). Here''s my method:
>
> def save_new
> logger.debug "self.bars = #{self.bars.inspect}"
> tmp = self.bars
> self.bars = []
> logger.debug "tmp = #{tmp.inspect}"
> logger.debug " self.bars = #{self.bars.inspect}"
> end
>
> Ignore the fact that this does nothing for now. Being a Java
> programmer here, the way I read what this code does is:
>
> Assign the variable tmp to point to the object that self.bars
> points to
> Assign the variable self.bars to point to a new object, an empty array
>
> So I would expect the variable tmp to still be pointing to the
> original array that self.bars points to. But I guess that''s not
> the case with Ruby? When you run it, it prints this:
>
> self.bars = [#<Bar:0x3ce0888 ...
> tmp = []
> self.bars = []
>
> So I assume tmp is pointing to the variable (or maybe the word
> reference is more appropriate?) self.bars, not what self.bars
> points to. And once I make self.bars point to something else, tmp
> then in turn points to that. So in otherwords, tmp is a reference
> to a reference, and self.bars is a reference to an Array. Just
> looking for someone to confirm what I''m finding and that I
actually
> understand what is going on.
>
> On 6/1/06, Paul Barry <mail@paulbarry.com> wrote:
> Alright, I''ve boiled this problem down to a specific AR thing,
> using script/console instead of the forms and controllers:
>
> C:\ruby\workspace\eval>ruby script/console
> Loading development environment.
> >> foo = Foo.new
> => #<Foo:0x3e3c1e0 @attributes={"name"=>nil},
@new_record=true>
> >> foo.bars = []
> => []
> >> foo.bars << Bar.new
> => [#<Bar:0x3e27330 @attributes={"name"=>nil,
"foo_id"=>nil},
> @new_record=true>]
> >> foo.save!
> ActiveRecord::RecordInvalid: Validation failed: Bars is invalid
> from c:/ruby/ruby-1.8.4_16/lib/ruby/gems/1.8/gems/
> activerecord-1.14.2/lib/active_record/validations.rb:736:in
`save!''
> from (irb):4
> >>
>
> So I guess AR doesn''t know how to save a new record that has child
> records? Is this the only way to do it:
>
> C:\ruby\workspace\eval>ruby script/console
> Loading development environment.
> >> foo = Foo.new
> => #<Foo:0x3e3c1e0 @attributes={"name"=>nil},
@new_record=true>
> >> foo.save
> => true
> >> foo.bars << Bar.new
> => [#<Bar:0x3e250b0 @errors=#<ActiveRecord::Errors:0x3e22878
> @errors={}, @base=#<Bar:0x3e250b0 ...>>,
@attributes={"name"=>nil,
> "foo_id"=>4, "id"=>11}, @new_record=false>]
> >> foo.save
> => true
> >>
>
> If that''s the case, I guess I should probably create a
> foo.save_with_bars method to encapsulate this. Is this how other
> people are handling saving a record that has_many other records?
>
>
> On 6/1/06, Paul Barry < mail@paulbarry.com> wrote:
> I''m still having no luck with trying to create an object with n
> child objects through a form. I feel like I''m close though,
hoping
> someone can help me figure it out. What basically happens is that
> when my form is submitted to the controller, I have a Foo object
> and it has a bars property. Everything is getting populated, the
> bars property has 5 Bar objects, but each Bar object''s foo_id is
> nil. That makes sense, because the Foo itself hasn''t been created
> yet, so there is no id. My hope would be that AR would save the
> Foo to the DB first, then populate the foo_id of each Bar, then
> save each Bar. But instead I get 5 "Bars is Invalid" errors, one
> for each Bar in foo.bars. Has anybody been able to create an
> object with child objects using RoR? If so, what am I doing
> wrong? Here''s my code:
>
> #Pretty standard model classes
> class Foo < ActiveRecord::Base
> has_many :bars
> end
>
> class Bar < ActiveRecord::Base
> belongs_to :foo
> validates_presence_of :foo_id
> end
>
> #Controller with just the relevant methods shown here, new and create
> #I am doing something a little funky here
> #The form submits to /foos/new instead of /foos/create
> #The new method calls create if the request is a post
> #This allows me to easily show the form again if there is a problem
> #without explicitly rendering the template or calling the new method
> class FoosController < ApplicationController
>
> ...
>
> def new
> if request.post?
> create
> else
> @foo = Foo.new
> @bars = []
> 5.times {|i| @bars.push Bar.new } #There''s probably a
cleaner
> way to do this
> end
> end
>
> def create
> @foo = Foo.new(params[:foo])
> @foo.bars = params[:bar].values.collect {|bar| Bar.new(bar)}
> if @foo.save
> flash[:notice] = ''Foo was successfully created.''
> redirect_to :action => ''list''
> end
> @bars = @foo.bars
> end
>
> ...
>
> end
>
>
>
> #The form partial for foo _form.rhtml:
> <p><label
for="foo_name">Name</label><br/>
> <%= text_field ''foo'', ''name''
%></p>
>
> <% @bars.each_index do |i| %>
> <p><label for="bar_<%= i %>_name">Bar
#<%=i%>: </label><br/>
> <%= text_field ''bar'', ''name'',
"index" => i %></p>
> <% end %>
>
>
>
>
> On 5/30/06, Paul Barry < mail@paulbarry.com> wrote:
> I''m trying to figure out how to design a view and controller to
> work with a simple collection. I have a Foo that has many Bars, so
> here''s what I did:
>
> $ ruby script/generate model Foo
> $ ruby script/generate model Bar
> (Uncomment t.column, :name: :string in foo and bar migrations)
> (Edit Foo.rb and Bar.rb, add has_many :bars to Foo, belongs_to :foo
> to Bar)
> $ rake db:migrate
> $ ruby script/generate scaffold Foo
>
> At this point you can create/edit foos, but no bars. So what I
> want to be able to do it create 5 bars at the same time I create a
> foo. So the form would look like this:
>
> Name: [_______________________]
> Bar #1 Name: [_______________________]
> Bar #2 Name: [_______________________]
> Bar #3 Name: [_______________________]
> Bar #4 Name: [_______________________]
> Bar #5 Name: [_______________________]
>
> So, trying to follow the example from http://api.rubyonrails.org/
> classes/ActionView/Helpers/FormHelper.html , I added this to app/
> views/foos/_form.rhtml:
>
> <% 5.times do |i| %>
> <p><label for="bar_<%= i %>_name"/><br/>
> <%= text_field ''bar[]'', ''name''
%></p>
> <% end %>
>
> But I get this error:
>
> You have a nil object when you didn''t expect it!
> You might have expected an instance of ActiveRecord::Base.
> The error occured while evaluating nil.id_before_type_cast
>
> Extracted source (around line #9):
>
> 6:
> 7: <% 5.times do |i| %>
> 8: <p><label for="bar_<%= i
%>_name"/><br/>
> 9: <%= text_field ''bar[]'', ''name''
%></p>
> 10: <% end %>
> 11:
> 12: <!--[eoform:foo]-->
>
> Hmmm... so I guess I need a bar array that is initialized with Bar
> objects? So I add this to the new method in the FooController:
>
> @bars = []
> 5.times do |i|
> @bars.push Bar.new
> end
>
> But then I get this error:
>
> undefined method `id_before_type_cast'' for
#<Array:0x3925ee8>
>
> Extracted source (around line #9):
>
> 6:
> 7: <% 5.times do |i| %>
> 8: <p><label for="bar_<%= i
%>_name"/><br/>
> 9: <%= text_field ''bars[]'', ''name''
%></p>
> 10: <% end %>
> 11:
> 12: <!--[eoform:foo]-->
>
> So I''m stuck, how are you supposed to do this?
>
>
>
>
>
> _______________________________________________
> Rails mailing list
> Rails@lists.rubyonrails.org
> http://lists.rubyonrails.org/mailman/listinfo/rails
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
http://wrath.rubyonrails.org/pipermail/rails/attachments/20060601/6548dbd7/attachment-0001.html
Paul Barry
2006-Jun-01 21:45 UTC
[Rails] Re: How do you create a controller & view to create a list of objects?
>So when you assign self.bars = [] you are changing the object thatself.bars is pointing at and therefor the tmp var is pointing at the same object. Yeah, that''s the fundamental difference between Ruby and Java. In Java, self.bars = [] has no effect at all on the object that self.bars was originally pointing to. You are just making the reference self.bars point at a new object, not changing the object that self.bars points to. Anyway, I''ll give your controller code a try. That makes sense, why would I put the bars into foo, only to take them out temporarily and then add them back in. My thinking though was more along the lines of the separation between the controller and the model. The controller just wants the whole object graph to be saved, so delagate it to the model. But that just isn''t the way it works, because like you said, AR doesn''t populate the foo_id of each bar. Thanks for your help, I''ll let you know how it turns out. On 6/1/06, Ezra Zygmuntowicz <ezmobius@gmail.com> wrote:> > Paul- > You are correct. In ruby variables are kind of like pointers to objects. > When you assign tmp = self.bars they both now point to the same object. So > when you assign self.bars = [] you are changing the object that self.barsis pointing at and therefor the tmp var is pointing at the same object. You > can get around this by doing tmp = self.bars.dup and then continue on > with what you are doing. > > But I don''t understand why you are trying to set self.bars to be an empty > array? Lets look at how you can accomplish this in your controller. Since > you have set validates_presence_of :foo_id in your comments model you have > to either use create to make a new foo or foo.save before you can add > comments to it. This is because there is no foo_id for the bars to use until > you save foo to the db and it gets an id > > > # One liner ;) > @foo = Foo.create(:name => ''First Foo!'').bars << params[:bars].map { > |bar| Bar.create(bar) } > > #OR with error handling > if @foo = Foo.new(:name => ''First Foo!'').save > @foo.bars << params[:bars].map { |bar| Bar.new(bar) } > if @foo.bars.each {|bar| bar.valid?} > @foo.save > else > # bars have errors > end > else > # foo has errors > end > > > Depending on what you are trying to do you might want to look at > validates_associated instead of valiodates_presence_of :foo_id. > > > Cheers- > -Ezra > > On Jun 1, 2006, at 8:54 AM, Paul Barry wrote: > > So now I''m trying to create a method that copies the bars into a temporary > array, saves the Foo, then adds the bars back to the Foo, and save the foo > again. Seems to be the only way this is going to work. In the porcess, > I''ve run across something about Ruby that is weird (probably only weird > because I''m used to the Java way of things). Here''s my method: > > def save_new > logger.debug "self.bars = #{self.bars.inspect}" > tmp = self.bars > self.bars = [] > logger.debug "tmp = #{tmp.inspect}" > logger.debug " self.bars = #{self.bars.inspect}" > end > > Ignore the fact that this does nothing for now. Being a Java programmer > here, the way I read what this code does is: > > Assign the variable tmp to point to the object that self.bars points to > Assign the variable self.bars to point to a new object, an empty array > > So I would expect the variable tmp to still be pointing to the original > array that self.bars points to. But I guess that''s not the case with > Ruby? When you run it, it prints this: > > self.bars = [#<Bar:0x3ce0888 ... > tmp = [] > self.bars = [] > > So I assume tmp is pointing to the variable (or maybe the word reference > is more appropriate?) self.bars, not what self.bars points to. And once I > make self.bars point to something else, tmp then in turn points to that. > So in otherwords, tmp is a reference to a reference, and self.bars is a > reference to an Array. Just looking for someone to confirm what I''m finding > and that I actually understand what is going on. > > On 6/1/06, Paul Barry <mail@paulbarry.com> wrote: > > > > Alright, I''ve boiled this problem down to a specific AR thing, using > > script/console instead of the forms and controllers: > > > > C:\ruby\workspace\eval>ruby script/console > > Loading development environment. > > >> foo = Foo.new > > => #<Foo:0x3e3c1e0 @attributes={"name"=>nil}, @new_record=true> > > >> foo.bars = [] > > => [] > > >> foo.bars << Bar.new > > => [#<Bar:0x3e27330 @attributes={"name"=>nil, "foo_id"=>nil}, > > @new_record=true>] > > >> foo.save! > > ActiveRecord::RecordInvalid: Validation failed: Bars is invalid > > from c:/ruby/ruby- > > 1.8.4_16/lib/ruby/gems/1.8/gems/activerecord-1.14.2/lib/active_record/validations.rb:736:in`save!'' > > from (irb):4 > > >> > > > > So I guess AR doesn''t know how to save a new record that has child > > records? Is this the only way to do it: > > > > C:\ruby\workspace\eval>ruby script/console > > Loading development environment. > > >> foo = Foo.new > > => #<Foo:0x3e3c1e0 @attributes={"name"=>nil}, @new_record=true> > > >> foo.save > > => true > > >> foo.bars << Bar.new > > => [#<Bar:0x3e250b0 @errors=#<ActiveRecord::Errors:0x3e22878 @errors={}, > > @base=#<Bar:0x3e250b0 ...>>, @attributes={"name"=>nil, "foo_id"=>4, > > "id"=>11}, @new_record=false>] > > >> foo.save > > => true > > >> > > > > If that''s the case, I guess I should probably create a > > foo.save_with_bars method to encapsulate this. Is this how other people > > are handling saving a record that has_many other records? > > > > > > On 6/1/06, Paul Barry < mail@paulbarry.com> wrote: > > > > > > I''m still having no luck with trying to create an object with n child > > > objects through a form. I feel like I''m close though, hoping someone can > > > help me figure it out. What basically happens is that when my form is > > > submitted to the controller, I have a Foo object and it has a bars > > > property. Everything is getting populated, the bars property has 5 Bar > > > objects, but each Bar object''s foo_id is nil. That makes sense, because the > > > Foo itself hasn''t been created yet, so there is no id. My hope would be > > > that AR would save the Foo to the DB first, then populate the foo_id of each > > > Bar, then save each Bar. But instead I get 5 "Bars is Invalid" errors, one > > > for each Bar in foo.bars. Has anybody been able to create an object > > > with child objects using RoR? If so, what am I doing wrong? Here''s my > > > code: > > > > > > #Pretty standard model classes > > > class Foo < ActiveRecord::Base > > > has_many :bars > > > end > > > > > > class Bar < ActiveRecord::Base > > > belongs_to :foo > > > validates_presence_of :foo_id > > > end > > > > > > #Controller with just the relevant methods shown here, new and create > > > #I am doing something a little funky here > > > #The form submits to /foos/new instead of /foos/create > > > #The new method calls create if the request is a post > > > #This allows me to easily show the form again if there is a problem > > > #without explicitly rendering the template or calling the new method > > > class FoosController < ApplicationController > > > > > > ... > > > > > > def new > > > if request.post? > > > create > > > else > > > @foo = Foo.new > > > @bars = [] > > > 5.times {|i| @bars.push Bar.new } #There''s probably a cleaner > > > way to do this > > > end > > > end > > > > > > def create > > > @foo = Foo.new(params[:foo]) > > > @foo.bars = params[:bar].values.collect {|bar| Bar.new(bar)} > > > if @foo.save > > > flash[:notice] = ''Foo was successfully created.'' > > > redirect_to :action => ''list'' > > > end > > > @bars = @foo.bars > > > end > > > > > > ... > > > > > > end > > > > > > > > > > > > #The form partial for foo _form.rhtml: > > > <p><label for="foo_name">Name</label><br/> > > > <%= text_field ''foo'', ''name'' %></p> > > > > > > <% @bars.each_index do |i| %> > > > <p><label for="bar_<%= i %>_name">Bar #<%=i%>: </label><br/> > > > <%= text_field ''bar'', ''name'', "index" => i %></p> > > > <% end %> > > > > > > > > > > > > > > > On 5/30/06, Paul Barry < mail@paulbarry.com> wrote: > > > > > > > I''m trying to figure out how to design a view and controller to work > > > > with a simple collection. I have a Foo that has many Bars, so here''s what I > > > > did: > > > > > > > > $ ruby script/generate model Foo > > > > $ ruby script/generate model Bar > > > > (Uncomment t.column, :name: :string in foo and bar migrations) > > > > (Edit Foo.rb and Bar.rb, add has_many :bars to Foo, belongs_to :foo > > > > to Bar) > > > > $ rake db:migrate > > > > $ ruby script/generate scaffold Foo > > > > > > > > At this point you can create/edit foos, but no bars. So what I want > > > > to be able to do it create 5 bars at the same time I create a foo. So the > > > > form would look like this: > > > > > > > > Name: [_______________________] > > > > Bar #1 Name: [_______________________] > > > > Bar #2 Name: [_______________________] > > > > Bar #3 Name: [_______________________] > > > > Bar #4 Name: [_______________________] > > > > Bar #5 Name: [_______________________] > > > > > > > > So, trying to follow the example from http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html > > > > , I added this to app/views/foos/_form.rhtml: > > > > > > > > <% 5.times do |i| %> > > > > <p><label for="bar_<%= i %>_name"/><br/> > > > > <%= text_field ''bar[]'', ''name'' %></p> > > > > <% end %> > > > > > > > > But I get this error: > > > > > > > > You have a nil object when you didn''t expect it! > > > > You might have expected an instance of ActiveRecord::Base. > > > > The error occured while evaluating nil.id_before_type_cast > > > > > > > > Extracted source (around line #9): > > > > > > > > 6: > > > > 7: <% 5.times do |i| %> > > > > 8: <p><label for="bar_<%= i %>_name"/><br/> > > > > 9: <%= text_field ''bar[]'', ''name'' %></p> > > > > 10: <% end %> > > > > 11: > > > > 12: <!--[eoform:foo]--> > > > > > > > > Hmmm... so I guess I need a bar array that is initialized with Bar > > > > objects? So I add this to the new method in the FooController: > > > > > > > > @bars = [] > > > > 5.times do |i| > > > > @bars.push Bar.new > > > > end > > > > > > > > But then I get this error: > > > > > > > > undefined method `id_before_type_cast'' for #<Array:0x3925ee8> > > > > > > > > Extracted source (around line #9): > > > > > > > > 6: > > > > 7: <% 5.times do |i| %> > > > > 8: <p><label for="bar_<%= i %>_name"/><br/> > > > > 9: <%= text_field ''bars[]'', ''name'' %></p> > > > > 10: <% end %> > > > > 11: > > > > 12: <!--[eoform:foo]--> > > > > > > > > So I''m stuck, how are you supposed to do this? > > > > > > > > > > > > > > > > > > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails > > > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails > > >-------------- next part -------------- An HTML attachment was scrubbed... URL: http://wrath.rubyonrails.org/pipermail/rails/attachments/20060601/7db8b527/attachment-0001.html