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