Complex forms often lead to complex controllers, but that doesn't have to be the case. In this episode see how you can create multiple models through a single form while keeping the controller clean.
Thanks Ryan, this episode is a great summary on how to create multiple models in one action. However, a while ago I had trouble building associated models from a custom setter method that is called during initialization (like in your case). Under some circumstances, the associated models (tasks in your example) were vanishing. The full story: http://zargony.com/2007/09/17/vanishing-records-on-creating-multiple-models-in-one-action/
@Skyblaze: Using separate actions might look unnecessary, but in fact, it makes life easier if it comes to error handling, restful routes and/or ActiveResource.
@Skyblaze:
This is the RESTful way of doing stuff.
A resource expects to have a 'new' action for entering a new object. This even extended to new.xml in edge rails.
Basically if you share controller actions for 2(or more) purposes, it can get ugly really fast. Splitting an action by using if/then/else (request.post? ) just to share a few basic lines isn't worth the cluttering. If you want to DRY up the code, use a before_filter for setting up the basic @instance variable.
@Zargony, interesting. Perhaps a better solution would be to store the contents in an instance variable and handle the association creating in a callback instead of directly in the accessor method.
@Jermaine, yes, the technique I'll show in the upcoming episodes is better then what I wrote about. I'm planning to update these articles soon.
Ok for the rest so do you advise to not use anymore the postback actions style of coding?
For Rayan:
We would love some episodes on complete deployment with capistrano 2.0 and maybe a subversion only one that explain the basics of subversion.
I am also waiting for the longer episodes on how to write basic apps
Thanks for the topic, my controllers can be more slim now.
Do you use not official way to share controllers and view among different applications? (e.g plugems, engines, gems,..)
Ryan, Thanks, I love this idea of creating and updating children in the parent model rather than the controller. So today I made fields_for person[mugshot] and person[address], even though Person only has_one of each of these. Then I made setter methods in the Person model: def address= (address_hash) and the same for mugshot. Works a treat and so much cleaner!!
@HappyCoder, the presenter pattern is interesting, but I'm skeptical. Adding another layer of abstraction and delegation is a big cost, and I have yet to see a good example of it which justifies this cost. Instead I prefer the more direct approach like the virtual attributes you see here.
However, I do think there are many patterns around this concept of presenter pattern which are useful. But, I would only apply it through refactoring to make sure it's exactly what your app needs. Otherwise, you risk adding unnecessary complexity.
I guess you could say every presenter pattern is different depending on the needs of the app.
@DAZ, the difference is that .create will save the model to the database right there, but .build will only build the model in memory and not try to save it.
Hi Ryan, great video, one question, I use a date widget called "datetime toolbocks", they dont provide a method on the formbuilder, so I have to use the normal helpers, for example I use: "toolbocks_date_select object method" syntax on it. Given this, is there a way to use fields_for to make this work?
@Joel, I'm not familiar with the plugin so I don't know. Basically you just need to be concerned about the resulting HTML. Make sure it has that same "project[task_attributes][]" prefix in the name of the fields and it should work. If you can't prefix the name of the fields then you'll have to either hack it or look for a different solution.
Ryan, have you ever tried this approach with date_select. I have a modified version of your example working (I backed off the fancy date widget) but when I add:
<%= task_form.date_select :duedate, :use_month_numbers => true,:order => [:month,:day,:year], :include_blank => true %>
I get the following cgi error:
Conflicting types for parameter containers. Expected an instance of Array, but found an instance of Hash. This can be caused by passing Array and Hash based paramters qs[]=value&qs[key]=value.
/Applications/Locomotive2/Bundles/standardRailsSept2006.locobundle/i386/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/cgi_ext/cgi_methods.rb:204:in `type_conflict!'
@Joel, yeah, looking at the HTML source it generates you can see it removes the "[]" in the name of the field. Not sure why date_select does this or what the best alternative is.
Is there a way to build such complex form when the models have been generated using the scaffold resource generator, so that we could keep the generated REST code (i.e model + controllers, minus the views) ?
I'm working on a project where I have the equivalent of a Project resource with nested Task resources and I'm struggling to find a nice way to build such complex form while keeping the generated routes and controllers.
Ryan this is beautiful! You might remember me from the railsforum, we've talked about this topic.
Looks like you found it out. I'm still amazed with the amount of lines I removed from my code when I implemented these ideas.
Great screencast, thanks.
Thanks for the great screen cast Ryan. You're right on time for an issue I was tackling. Thanks a lot.
I only kind of remember a way of setting up a class so that one name encompasses several models. For example: Employees have names, addresses and phone numbers. The class Employee contains the models names, addresses and phone_numbers. The point is that you can reference each of the models by calling on the Employee class. Could this fit into your solution or is this a whole new bag of worms?
I set the prefix in the loop to be a constant + id of the task. Then when im creating the tasks I fish out the param for the date. Not the prettiest and it nullifies some of the elegance of this approach but it works.
@Rene, this should work fine with the code generated by scaffold resource. The code I start out with in the screencast is not far from that so you just need to make the appropriate changes in the view and model.
@foozilla, tasks.build will only create the model in memory where as tasks.create will save it to the database. I don't want to save it to the database yet in case validation fails (it will be saved later when the project is saved) so I use tasks.build.
@Bryce, it's hard to say without knowing more about your problem domain. I recommend making a post on railsforum.com where you can discuss the details.
does this mean that the tasks are created by the ProjectsController (as opposed to by the TasksController generated by scaffold_resource)?
I was under the assumption that in order to be RESTful, one should use the TasksController.create action for creating a new task. (and TasksController.edit in order to update).
@Rene, the tasks controller doesn't play any part here, it's all in the projects controller. Since we're going through only one form here it only uses one controller.
I suppose some REST extremists may not like this, but I don't see any problem with it. We aren't adding anymore actions to the controller. In fact there's very little code in the controller concerning tasks. It's primarily concerned with creating the project and the rest is done in the project model through the parameters which are passed to it.
Supposing every project can have upto 10 tasks but not necessarily 10 for every project and the controller and view look same as in the tutorial, how can I filter out the tasks fields that are left blank?
Hey Ryan, this approach seems to have the same problem with collection_select as it does with date_select. May be an issue with all selects. Given this im not sure this approach is valuable unless your form will only have simple text fields.
thanks
Joel
@Kyle, you can do a "blank?" check to only create tasks where the fields aren't blank.
http://pastie.caboo.se/104922
@Dennis, this is a little out of the scope of this tutorial. I recommend posting your question on railsforum.com if you haven't already.
@Joel, collection_select has worked for me with this approach. Are you certain it's not working? I was on edge rails when I tested it so maybe that's part of the issue.
I'm having an issue, the model I'm using in a similar fashion to your Task model has no id column (it's a hm:t join table). I'm getting this error when i try and run it:
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.column
Great screencast Ryan!
But, is there way to build in memory say... tag, for article with :through => :tagged_article association.
Like:
def new
@article = Article.new
@article.tags.build
end
Great Railscast. However, I'm left curious to see how you will perform validation. In a current system I'm working on, I have a form for a user with an embedded address model. I post via ajax, validate the user fields then the address fields, and use RJS to highlight the specific form fields where an error occurred. Your technique promises to make that whole process much simpler. Can I request that you follow up in railscast 75? :)
ok I fugured out how to do this. first I put the form in _form.rhtml and I am using it for both new and edit. then I updated the virtual attribute to chect to se if it is getting an array(as in new tasks) or a hash(updating the tasks).
def task_attributes=(task_attributes)
if task_attributes.class == Array
task_attributes.each do |attributes|
tasks.build(attributes)
end
else
task_attributes.each do |id, attributes|
task = Task.find(id)
task.update_attributes(attributes)
end
end
end
@Yuval, I'm not sure I'll have time to get into the details of validation in the next episode, but I'll include it in the code sample online or something.
@Sintaxi, the next episode will show you how to handle editing the project with the tasks.
How do you end up displaying the task info in the index.rhtml form? I have no idea how you do this? Your projects controller index method finds all projects, but in your tutorial, you are able to display the tasks with the project. What is the rhtml code that can display a task for a project? Anyone know how to help?
Thanks for putting these together, they have been a great help!
My issue is that I would like to do this over multiple relationships. For example: what if a task had multiple requirements? How can I do this with still only calling Project.new(params[:project]) and having everything still set automatically?
One serious problem I've been running into using this pattern is validations, especially on the child model.
If, for example, the tasks model had this:
validates_presence_of :project_id
Then you will not be able to save the in-memory project. Reason is when the project is asked if it's valid? it'll cascade down and ask it's children tasks if they're all also valid? - if you're enforcing on the task level the need for an existing project_id, then that validation will fail since the parent project has not yet been saved, there's no id.
I would be very curious to see if you or anyone else has found a clean solution to this problem. Recently I've resorted to some nastiness like so:
In the loop in tasks_attributes= method, work-around the fact that .build does not assign a task's .project_id (understandable) but also does NOT assign the task's .project:
tasks.buid(attributes).project = self
Then in the validation in the task model, do this:
validates_presence_of :project_id, :if => lambda {|record| record.project.nil? }
It's not clean, it's not cohesive, but it works. I'd love to hear if someone came up with something better.
I'd also be happy to hear throries regarding why this:
t = some_project.tasks.build :a => :b, :c => :d.....
will not populate t.project - it's obvious that rails knows what the parent of the built task is, do why doesn't it assign it (object-reference wise - has nothing to do with whether the parent is saved or not....)
hi ryan
thanks for the great railscasts they are a great help!
now, im having a little prroblem trying to implement this one.
When i call the New project view which includes the code for the childs, my app seem not to recognize the child because it says it doesnot find one of the tasks fields:
undefined method `description' for #<Task:0x455e8a4>
hi ryan,
this is drivin me crazy, it just does not work in my case.
i always get that undefined method error up there in my last post.
im trying to give a user(tablename = "users", model name = "User") the possibility to have many emails(table name = "emails", model name ="Email")
im using REST, might this be the problem?
thanks
oh and i forgot:
following code gives me a set of column headers with the names of the fields which rails does not find using your code:
<% for column in user.emails.content_columns %>
<th><%= column.name %></th>
<% end %>
Can you use the technique that you described for an arbitrarily nested creation? Assume that I want to have
Projects
Tasks
People
and I wanted to be able to add multiple tasks and multiple people on a single input form, can I still get away with that? It seems like the id substitution that Rails does may be a problem.
Thanks for the great work you do, I check in every week.
One thing I noticed in the build source code for Active Record (at least in Edge Rails) is that you don't actually need to loop through task_attributes to build the tasks objects -- build will recursively do it for you.
def task_attributes=(task_attributes)
task_attributes.each do |attributes|
tasks.build(attributes)
end
end
becomes
def task_attributes=(task_attributes)
task_attributes.build(attributes)
end
Thanks for the example. I tried it and it worked with the project/tasks. However, when I tried to follow your example on task/requirements it didn't work.
I believe that in your example, it should have been <% for requirement in task.requirements %> instead of <% for requirement in @project.task.requirements %> as task doesn't exist.
I tried both, and the former returned an empty array for requirements, and the latter throws some error on the page.
Could you please show me a working example or correct me?
Today is the first time I actually play around with rails.
@James OKelly: and for all who are getting erros like @profile[interest_descriptions] is not allowed as an instance variable name ... it took me half day before I have found out that (probably) the 1.1.6 version I am using behaves differently and can not generate names like project[task_attributes][][text] which are needed here.
Moreover if you bypass fields_for and other helpers altohether it will NOT convert such values to the array within the params hash structure, but forget all but one item. And as the whole method depends strongly on that, you are out of luck. Do the same as me UPGRADE to 1.2.x series. On Debian it means bypasing apt-get and use gem installation of rails.
@gorn I'm not certain that's the correct solution. I am on a pretty edgy version of Rails (well past 1.2.3) and I am getting this issue as well. Maybe it's the specific version that I am on that is messed up.
Does anyone else have any answers to the dreaded instance variable not found issue?
Hi there, i have managed to implement updating the two models at once and was wondering if it is possible to do three? The third model im using (facilities) habtm the second (room_types). Ideally i would like to be able use check boxes to update the data. Any ideas?
I am having a problem with the "this.up" method in Internet Explorer. Any time it is called, there is a "object doesn't support this property or method".
Tried searching Google but it seems impossible to search for "this.up" since it instead searches for "this up".
I'm having a little problem with the "project[task_attributes][]" part. I've watched the screencast on virtual attributes, I have "def task_attributes=(attributes)" in my model, but I get the message "`@project[task_attributes]' is not allowed as an instance variable name". It works fine when I have "project[task_attributes]" in my form without the extra brackets, but I want to be able to support an array. What am I missing here?
For '... is not allowed as an instance variable name' I ended up using this for each field and removing it from the fields_for call completely. I'm running Rails 2.0.2.
Everyone getting the "is not allowed as an instance variable name" error: one possibility is that you're feeding fields_for a nil object as the second argument.
The error message is really misleading, since it has nothing to do with the format of the string you're sending. My guess is that, failing the existence of the object, it tries to transform the string into a variable.
Ryan, what a wonderful screencast, thank you.
However, when I follow your instructions, the text_fields for my child model do not appear. Playing with the model in the ruby script/console I discovered that parent.children.build creates an empty array of children the very first time it is called, and a @parent.children.size of 1, but the correct number of children the second time it is called.
My parent class is Organ (organization) which inherits from Party. Party has many Addresses.
>> o = Organ.new
=> [#<Organ:0x4a3001c @attributes={ etc.}, @new_record=true>]
>> o.addresses.build
=> [#<Address:0x4a1bf40 @attributes={ etc.}, @new_record=true>]
>> o.addresses.size
=> 1
>> o.addresses
=> []
>> o.addresses.build
=> [#<Address:0x4a15f50 @attributes={ etc.}, @new_record=true>]
>> o.addresses.size
=> 1
>> o.addresses
=> [#<Address:0x4a15f50 @attributes={ etc.}, @new_record=true>]
It seems the culprit was the call to parent.children.size right after parent.children.build. Once I removed that call in my view (where the number of children was to be displayed), the child fields appeared as they should!
So i realize this is late, but no one on railsforum seems to know how to help, or just dont care to...
Will this not work for creating a model with multiple attributes? My parameters are keep getting screwed up. Lets say we are creating or updating a task that also has a completed_on attribute, well instead of getting a task model for the ones im editing/creating, it somehow sends a seperate set of paramaters for each attribute. So say I have 2 tasks, I will have params like so...
"task_attributes => [{"task" => "foo"}, {{"task" => "bar"}, {"completed_on" => "1/2/3"}, {"completed_on" => ""}, {"should_destroy" => ""},{"should_destroy" => ""}]"
@levi I too am seeing the parameters get wonky when using multiple attributes in the child object. Let me know if you discover a solution, I will do the same.
`@project[service_attributes]' is not allowed as an instance variable name
I would simply pass locals as in the first example which works fine except that when I add link_to_function and pass in a new object I get the same error.
I've double checked everything and can't work out what the problem is. On Rails 2.02
I have a question about the build method (as do many others I see). I can't seem to find any documentation for this method but from what I gather it bubbles up through to the ActiveRecord::Associations package. I understand what build does but could you possibly point me in the right direction for documentation just so I can learn a bit more about it? I'm still a bit of a noob!
Ryan, thank-you for this informative screen cast! I have been taking a slightly different approach with the code for saving child properties in the controller, but your approach is much cleaner. Thanks again.
Ryan, I love the screencasts! They have taught me an incredible amount in a short time.
I'm having a good deal of problems with this one though. I'm trying to do pretty much exactly what you have above, with one wrinkle: multiple fields for the second model.
I have an order, and items on the order. So I need to be able to setup the order (shipping address etc.) and then add items on the same form. The catch is, I need to define price, quantity and the item_id from the form. When I use your handy for loop in the model virtual attribute, I end up getting "is invalid" because I am trying to create a new record for every variable (price, qty, id). Is there any trick around this? I haven't been able
I wonder why you use two separate methods for new and create actions. Why don't you use postbacks?
Thanks Ryan, this episode is a great summary on how to create multiple models in one action. However, a while ago I had trouble building associated models from a custom setter method that is called during initialization (like in your case). Under some circumstances, the associated models (tasks in your example) were vanishing. The full story: http://zargony.com/2007/09/17/vanishing-records-on-creating-multiple-models-in-one-action/
@Skyblaze: Using separate actions might look unnecessary, but in fact, it makes life easier if it comes to error handling, restful routes and/or ActiveResource.
@Skyblaze:
This is the RESTful way of doing stuff.
A resource expects to have a 'new' action for entering a new object. This even extended to new.xml in edge rails.
Basically if you share controller actions for 2(or more) purposes, it can get ugly really fast. Splitting an action by using if/then/else (request.post? ) just to share a few basic lines isn't worth the cluttering. If you want to DRY up the code, use a before_filter for setting up the basic @instance variable.
Ryan,
I remember that you've written a nice tutorial about this on the railsforum website a year ago.
http://www.railsforum.com/viewtopic.php?id=717
Hopefully in the next part there will be some RJS goodness.
- Jermaine
@Zargony, interesting. Perhaps a better solution would be to store the contents in an instance variable and handle the association creating in a callback instead of directly in the accessor method.
@Jermaine, yes, the technique I'll show in the upcoming episodes is better then what I wrote about. I'm planning to update these articles soon.
Hi Ryan,
thank you so much, great episode.
Again, I can't wait the next episode!
Unfortunatelly have to wait another week ;-)
greetinx
Rafael
Ok for the rest so do you advise to not use anymore the postback actions style of coding?
For Rayan:
We would love some episodes on complete deployment with capistrano 2.0 and maybe a subversion only one that explain the basics of subversion.
I am also waiting for the longer episodes on how to write basic apps
Thanks for the topic, my controllers can be more slim now.
Do you use not official way to share controllers and view among different applications? (e.g plugems, engines, gems,..)
Ryan, you always make screencasts on topics that puzzle me exactly when I really need a solution :-)
My friend told me about another one interesting solution http://blog.jayfields.com/2007/03/rails-presenter-pattern.html
You should make a screencast about it.
Ryan, Thanks, I love this idea of creating and updating children in the parent model rather than the controller. So today I made fields_for person[mugshot] and person[address], even though Person only has_one of each of these. Then I made setter methods in the Person model: def address= (address_hash) and the same for mugshot. Works a treat and so much cleaner!!
Ryan:
Thanks so much for this episode!!! I've been waiting for it for so long!!! Can't wait for the next one.
Alek
@HappyCoder, the presenter pattern is interesting, but I'm skeptical. Adding another layer of abstraction and delegation is a big cost, and I have yet to see a good example of it which justifies this cost. Instead I prefer the more direct approach like the virtual attributes you see here.
However, I do think there are many patterns around this concept of presenter pattern which are useful. But, I would only apply it through refactoring to make sure it's exactly what your app needs. Otherwise, you risk adding unnecessary complexity.
I guess you could say every presenter pattern is different depending on the needs of the app.
Awesome!
I've been waiting a long time for you to post this one.
Very much looking forward to the next one and even hoping it will turn into a 3-part series ;)
Could somebody explain the .build method? It looks similar to .create ... are there any differences and/or reasons for using it? thanks.
@DAZ, the difference is that .create will save the model to the database right there, but .build will only build the model in memory and not try to save it.
So, you could say ".build" is like ".new".
Hi Ryan, great video, one question, I use a date widget called "datetime toolbocks", they dont provide a method on the formbuilder, so I have to use the normal helpers, for example I use: "toolbocks_date_select object method" syntax on it. Given this, is there a way to use fields_for to make this work?
@Joel, I'm not familiar with the plugin so I don't know. Basically you just need to be concerned about the resulting HTML. Make sure it has that same "project[task_attributes][]" prefix in the name of the fields and it should work. If you can't prefix the name of the fields then you'll have to either hack it or look for a different solution.
thanks, how would I trick collection_select into working this way (If I can figure this out, the rest will work:
<%= collection_select "project[task_attributes][]", :resource_id, entities, :id, :nickname } %>
when I try this, I get "ActionView::TemplateError (`@project[task_attributes]' is not allowed as an instance variable name)"
I tried similar thing on check_box_tag and it works correctly (I think)
new Railscast suggestion,
how about something on Model Observer classes?
I recently discovered these in restful_auth and find them very nice for extracting all the filters and grouping them in a small file.
Although I'm not finding much discussion/documentation on their usage.
@Joel, you would call collection_select through the form builder like the other fields:
<%= task_form.collection_select :resource_id, entities, :id, :nickname %>
You can do the same for the "check_box" method and other form field helper methods.
Ryan, have you ever tried this approach with date_select. I have a modified version of your example working (I backed off the fancy date widget) but when I add:
<%= task_form.date_select :duedate, :use_month_numbers => true,:order => [:month,:day,:year], :include_blank => true %>
I get the following cgi error:
Conflicting types for parameter containers. Expected an instance of Array, but found an instance of Hash. This can be caused by passing Array and Hash based paramters qs[]=value&qs[key]=value.
/Applications/Locomotive2/Bundles/standardRailsSept2006.locobundle/i386/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/cgi_ext/cgi_methods.rb:204:in `type_conflict!'
@Joel, yeah, looking at the HTML source it generates you can see it removes the "[]" in the name of the field. Not sure why date_select does this or what the best alternative is.
Ryan,
Is there a way to build such complex form when the models have been generated using the scaffold resource generator, so that we could keep the generated REST code (i.e model + controllers, minus the views) ?
I'm working on a project where I have the equivalent of a Project resource with nested Task resources and I'm struggling to find a nice way to build such complex form while keeping the generated routes and controllers.
Ryan this is beautiful! You might remember me from the railsforum, we've talked about this topic.
Looks like you found it out. I'm still amazed with the amount of lines I removed from my code when I implemented these ideas.
Great screencast, thanks.
Is @project.tasks.build same as
@project.tasks.create ???
thanks
Will you do a cast on caching on rails.
is there way to cache part of the page?
thanks much Ryan
Thanks for the great screen cast Ryan. You're right on time for an issue I was tackling. Thanks a lot.
I only kind of remember a way of setting up a class so that one name encompasses several models. For example: Employees have names, addresses and phone numbers. The class Employee contains the models names, addresses and phone_numbers. The point is that you can reference each of the models by calling on the Employee class. Could this fit into your solution or is this a whole new bag of worms?
Thanks again,
Bryce
Well I got dates to work using a hack, if anyone has this problem, not sure its the best way but I just used:
<%= select_date (task.duedate, :prefix => aprefix %>
I set the prefix in the loop to be a constant + id of the task. Then when im creating the tasks I fish out the param for the date. Not the prettiest and it nullifies some of the elegance of this approach but it works.
@Rene, this should work fine with the code generated by scaffold resource. The code I start out with in the screencast is not far from that so you just need to make the appropriate changes in the view and model.
@foozilla, tasks.build will only create the model in memory where as tasks.create will save it to the database. I don't want to save it to the database yet in case validation fails (it will be saved later when the project is saved) so I use tasks.build.
@Bryce, it's hard to say without knowing more about your problem domain. I recommend making a post on railsforum.com where you can discuss the details.
Ryan,
does this mean that the tasks are created by the ProjectsController (as opposed to by the TasksController generated by scaffold_resource)?
I was under the assumption that in order to be RESTful, one should use the TasksController.create action for creating a new task. (and TasksController.edit in order to update).
@Rene, the tasks controller doesn't play any part here, it's all in the projects controller. Since we're going through only one form here it only uses one controller.
I suppose some REST extremists may not like this, but I don't see any problem with it. We aren't adding anymore actions to the controller. In fact there's very little code in the controller concerning tasks. It's primarily concerned with creating the project and the rest is done in the project model through the parameters which are passed to it.
Thanks a lot Ryan,
Pragmatic REST is good too. I guess the TasksController could still be useful if I want to present the Projects and Tasks as ActiveResources
Thanks for the episode Ryan, although I wonder why you couldn't have done it a week earlier as it would have saved me 2 days of work!!
Great Episode - saved a ton of ugly work.
Slightly Off Topic - I thought "Task" was a reserved word.
Good point, just noticed Task is in the reserved words list on the wiki:
http://wiki.rubyonrails.org/rails/pages/ReservedWords
Anyone know why? So far I haven't had any issues with it.
That was really nice. Thank you.
I know i'm going to get destroyed for this comment: but maybe they want to ensure task is used solely for rake tasks? Doh...i'm sure thats wrong
Supposing every project can have upto 10 tasks but not necessarily 10 for every project and the controller and view look same as in the tutorial, how can I filter out the tasks fields that are left blank?
Thanks ryan :)
How do I do if I have 3 models where
agent: mas_many :orders
orders habtm :channels
channels habtm :orders
How to save the channels within agent[channel_attribut] so it get the right order_id?
Hey Ryan, this approach seems to have the same problem with collection_select as it does with date_select. May be an issue with all selects. Given this im not sure this approach is valuable unless your form will only have simple text fields.
thanks
Joel
@Kyle, you can do a "blank?" check to only create tasks where the fields aren't blank.
http://pastie.caboo.se/104922
@Dennis, this is a little out of the scope of this tutorial. I recommend posting your question on railsforum.com if you haven't already.
@Joel, collection_select has worked for me with this approach. Are you certain it's not working? I was on edge rails when I tested it so maybe that's part of the issue.
I'm having an issue, the model I'm using in a similar fashion to your Task model has no id column (it's a hm:t join table). I'm getting this error when i try and run it:
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.column
any idea on how to fix this?
Thanks for the awesome screencasts
So having a form that deals with more than one model, what is the best way to handle errors?
@Tal, it's hard to say what the problem is without seeing the code and stack trace. I recommend posting on railsforum.com about it.
@Aslan, I'll be covering how to handle validation errors in the near future.
Great screencast Ryan!
But, is there way to build in memory say... tag, for article with :through => :tagged_article association.
Like:
def new
@article = Article.new
@article.tags.build
end
Thanks
@Alex, I don't think the has_many :through association gives you that "build" method. Instead you'll have to build the join model like this:
@article.tagged_articles.build(:tag => Tag.new)
Great Railscast. However, I'm left curious to see how you will perform validation. In a current system I'm working on, I have a form for a user with an embedded address model. I post via ajax, validate the user fields then the address fields, and use RJS to highlight the specific form fields where an error occurred. Your technique promises to make that whole process much simpler. Can I request that you follow up in railscast 75? :)
how about updating the project/tasks? when creating, task_attributes is an array but when you update it is a hash.
ok I fugured out how to do this. first I put the form in _form.rhtml and I am using it for both new and edit. then I updated the virtual attribute to chect to se if it is getting an array(as in new tasks) or a hash(updating the tasks).
def task_attributes=(task_attributes)
if task_attributes.class == Array
task_attributes.each do |attributes|
tasks.build(attributes)
end
else
task_attributes.each do |id, attributes|
task = Task.find(id)
task.update_attributes(attributes)
end
end
end
@Yuval, I'm not sure I'll have time to get into the details of validation in the next episode, but I'll include it in the code sample online or something.
@Sintaxi, the next episode will show you how to handle editing the project with the tasks.
Thanks Ryan, I look forward to reading it.
How do you end up displaying the task info in the index.rhtml form? I have no idea how you do this? Your projects controller index method finds all projects, but in your tutorial, you are able to display the tasks with the project. What is the rhtml code that can display a task for a project? Anyone know how to help?
Well I answered my own question.
in index.rhtml
<% for task in @project.tasks %>
Task: <%= task.name %>
<% end %>
Hi Ryan!
Thanks for putting these together, they have been a great help!
My issue is that I would like to do this over multiple relationships. For example: what if a task had multiple requirements? How can I do this with still only calling Project.new(params[:project]) and having everything still set automatically?
I have a similar issue to @Ryan Lundie, creating the same effect with a many-to-many relationship.
I just viewed the tutorial for the first time tonight and am going to tackle it... hopefully I can adapt this tutorial to my needs.
In any case, these tutorials are great. Thanks so much for putting your time into creating these for everyone.
Mark
What is the projects_path variable? I don't see any mention of how it gets set.
@Mark
Alright, I think I have the issue figured out.
For the example above, if a task had multiple requirements, your view would look something like the following for the requirements:
<% for requirement in @project.task.requirements %>
<% fields_for "project[task_attributes][requirement_attributes]", requirement do |requirement_form| %>
<p>
Requirement: <%= requirement_form.text_field :name %>
</p>
<% end %>
<% end %>
Then in the task model:
def requirement_attributes=(requirement_attributes)
requirement_attributes.each do |attributes|
self.requirements.build(attributes)
end
end
This seems to work.
Opps. I missed a set of brakets. It should look like this:
<% fields_for "project[task_attributes][requirement_attributes][]", requirement do |requirement_form| %> (Empty set at end)
Thanks for another great episode Ryan
One serious problem I've been running into using this pattern is validations, especially on the child model.
If, for example, the tasks model had this:
validates_presence_of :project_id
Then you will not be able to save the in-memory project. Reason is when the project is asked if it's valid? it'll cascade down and ask it's children tasks if they're all also valid? - if you're enforcing on the task level the need for an existing project_id, then that validation will fail since the parent project has not yet been saved, there's no id.
I would be very curious to see if you or anyone else has found a clean solution to this problem. Recently I've resorted to some nastiness like so:
In the loop in tasks_attributes= method, work-around the fact that .build does not assign a task's .project_id (understandable) but also does NOT assign the task's .project:
tasks.buid(attributes).project = self
Then in the validation in the task model, do this:
validates_presence_of :project_id, :if => lambda {|record| record.project.nil? }
It's not clean, it's not cohesive, but it works. I'd love to hear if someone came up with something better.
I'd also be happy to hear throries regarding why this:
t = some_project.tasks.build :a => :b, :c => :d.....
will not populate t.project - it's obvious that rails knows what the parent of the built task is, do why doesn't it assign it (object-reference wise - has nothing to do with whether the parent is saved or not....)
What version of rails are you using for this Ryan? I have almost the exact same needs (a set of textboxes for a has_many) and I get:
@profile[interest_descriptions] is not allowed as an instance variable name
I am running the 2.0 PR. Any light?
My code is here: http://pastie.caboo.se/110538
I don't see anything out of whack, though I did go to a birthday party last night, hopeful my hangover isn't making me miss something stupid.
hi ryan
thanks for the great railscasts they are a great help!
now, im having a little prroblem trying to implement this one.
When i call the New project view which includes the code for the childs, my app seem not to recognize the child because it says it doesnot find one of the tasks fields:
undefined method `description' for #<Task:0x455e8a4>
Extracted source (around line #11):
8: <tr><td><%= "Task:".t%></td><td><%= f.text_field :task, :size => 60 %></td></tr>
9: <% for task in @user.tasks %>
10: <%fields_for "...", task do |task_form| %>
11: <tr><td><%= "Task:".t%></td><td><%= task_form.text_field :description, :size => 60 %></td></tr>
12: <%end %>
13: <%end%>
hi ryan,
this is drivin me crazy, it just does not work in my case.
i always get that undefined method error up there in my last post.
im trying to give a user(tablename = "users", model name = "User") the possibility to have many emails(table name = "emails", model name ="Email")
im using REST, might this be the problem?
thanks
oh and i forgot:
following code gives me a set of column headers with the names of the fields which rails does not find using your code:
<% for column in user.emails.content_columns %>
<th><%= column.name %></th>
<% end %>
Hi Ryan, just as a question is the code on new.rhtml valid html? I was just getting a little confused.
Can you use the technique that you described for an arbitrarily nested creation? Assume that I want to have
Projects
Tasks
People
and I wanted to be able to add multiple tasks and multiple people on a single input form, can I still get away with that? It seems like the id substitution that Rails does may be a problem.
Thanks for the great work you do, I check in every week.
In your code you point the form url at project_path, where is that set and is there a Railscast about what it is etc?
Great stuff though, this is exactly what I need to do :) Thanks
I'm getting an "undefined local variable or method 'task'" error when trying to save the form.
Any ideas what I might have missed?
@Chris Lock: Perhaps you need "map.resources :projects" at the top of your config/routes.rb file?
Cheers Cliff, I think I found out what he's doing via another cast :) http://media.railscasts.com/videos/034_named_routes.mov
One thing I noticed in the build source code for Active Record (at least in Edge Rails) is that you don't actually need to loop through task_attributes to build the tasks objects -- build will recursively do it for you.
def task_attributes=(task_attributes)
task_attributes.each do |attributes|
tasks.build(attributes)
end
end
becomes
def task_attributes=(task_attributes)
task_attributes.build(attributes)
end
Try it out.
@Ryan,
Thanks for the example. I tried it and it worked with the project/tasks. However, when I tried to follow your example on task/requirements it didn't work.
I believe that in your example, it should have been <% for requirement in task.requirements %> instead of <% for requirement in @project.task.requirements %> as task doesn't exist.
I tried both, and the former returned an empty array for requirements, and the latter throws some error on the page.
Could you please show me a working example or correct me?
Today is the first time I actually play around with rails.
Thanks & regards,
voki
@James OKelly: and for all who are getting erros like @profile[interest_descriptions] is not allowed as an instance variable name ... it took me half day before I have found out that (probably) the 1.1.6 version I am using behaves differently and can not generate names like project[task_attributes][][text] which are needed here.
Moreover if you bypass fields_for and other helpers altohether it will NOT convert such values to the array within the params hash structure, but forget all but one item. And as the whole method depends strongly on that, you are out of luck. Do the same as me UPGRADE to 1.2.x series. On Debian it means bypasing apt-get and use gem installation of rails.
Hope it will same half day to others :-)
@gorn I'm not certain that's the correct solution. I am on a pretty edgy version of Rails (well past 1.2.3) and I am getting this issue as well. Maybe it's the specific version that I am on that is messed up.
Does anyone else have any answers to the dreaded instance variable not found issue?
thanks all.
Hi there, i have managed to implement updating the two models at once and was wondering if it is possible to do three? The third model im using (facilities) habtm the second (room_types). Ideally i would like to be able use check boxes to update the data. Any ideas?
I am having a problem with the "this.up" method in Internet Explorer. Any time it is called, there is a "object doesn't support this property or method".
Tried searching Google but it seems impossible to search for "this.up" since it instead searches for "this up".
Any help would be appreciated
I'm having a little problem with the "project[task_attributes][]" part. I've watched the screencast on virtual attributes, I have "def task_attributes=(attributes)" in my model, but I get the message "`@project[task_attributes]' is not allowed as an instance variable name". It works fine when I have "project[task_attributes]" in my form without the extra brackets, but I want to be able to support an array. What am I missing here?
For '... is not allowed as an instance variable name' I ended up using this for each field and removing it from the fields_for call completely. I'm running Rails 2.0.2.
f.hidden_field :id, { :name => 'foo[bar][][id]' }
Works, but is annoyingly repetitive.
Everyone getting the "is not allowed as an instance variable name" error: one possibility is that you're feeding fields_for a nil object as the second argument.
The error message is really misleading, since it has nothing to do with the format of the string you're sending. My guess is that, failing the existence of the object, it tries to transform the string into a variable.
For all the folks with "is not allowed as an instance variable name"... make sure you've got the ":object => Xxx.new" in this bit:
page.insert_html :bottom, :tasks, :partial => 'task', :object => Task.new
That was my problem, maybe its yours.
Ryan, what a wonderful screencast, thank you.
However, when I follow your instructions, the text_fields for my child model do not appear. Playing with the model in the ruby script/console I discovered that parent.children.build creates an empty array of children the very first time it is called, and a @parent.children.size of 1, but the correct number of children the second time it is called.
My parent class is Organ (organization) which inherits from Party. Party has many Addresses.
>> o = Organ.new
=> [#<Organ:0x4a3001c @attributes={ etc.}, @new_record=true>]
>> o.addresses.build
=> [#<Address:0x4a1bf40 @attributes={ etc.}, @new_record=true>]
>> o.addresses.size
=> 1
>> o.addresses
=> []
>> o.addresses.build
=> [#<Address:0x4a15f50 @attributes={ etc.}, @new_record=true>]
>> o.addresses.size
=> 1
>> o.addresses
=> [#<Address:0x4a15f50 @attributes={ etc.}, @new_record=true>]
What could be going on here?
It seems the culprit was the call to parent.children.size right after parent.children.build. Once I removed that call in my view (where the number of children was to be displayed), the child fields appeared as they should!
So i realize this is late, but no one on railsforum seems to know how to help, or just dont care to...
Will this not work for creating a model with multiple attributes? My parameters are keep getting screwed up. Lets say we are creating or updating a task that also has a completed_on attribute, well instead of getting a task model for the ones im editing/creating, it somehow sends a seperate set of paramaters for each attribute. So say I have 2 tasks, I will have params like so...
"task_attributes => [{"task" => "foo"}, {{"task" => "bar"}, {"completed_on" => "1/2/3"}, {"completed_on" => ""}, {"should_destroy" => ""},{"should_destroy" => ""}]"
@levi I too am seeing the parameters get wonky when using multiple attributes in the child object. Let me know if you discover a solution, I will do the same.
For me things are going ok until I try to re-factor into partials.
This works fine:
<% for task in @project.tasks%>
<%= render :partial => 'task', :locals => {:task => task} %>
<% end %>
but using collection...
<%= render :partial => 'task', :collection -> @project.tasks %>
results in an error:
`@project[service_attributes]' is not allowed as an instance variable name
I would simply pass locals as in the first example which works fine except that when I add link_to_function and pass in a new object I get the same error.
I've double checked everything and can't work out what the problem is. On Rails 2.02
Hi Ryan, thanks for the great ideas!
I have a question about the build method (as do many others I see). I can't seem to find any documentation for this method but from what I gather it bubbles up through to the ActiveRecord::Associations package. I understand what build does but could you possibly point me in the right direction for documentation just so I can learn a bit more about it? I'm still a bit of a noob!
Thanks
Tristan
Hi Ryan, Thx for all your railscasts, they are really great!
Do you have any idea how to build a form tis way for double nested models? For example, a Company has many Offices, and an Office has one address.
I'd like to have a form where you can add a company with one or more associated offices along with the address of each office.
I tried the following, but the attributes of address don't get passed for some reason
<% fields_for "company[office_attributes][]", office do |office_form| %>
<p>
office:<%= office_form.text_field :name %><br />
<% fields_for "company[office_attributes][][address_attributes]", Address.new do |address_form| %>
<%= address_form.text_field :street %>
<% end %>
</p>
<% end %>
<% end %>
any help would be appreciated.
thx in advance!
Ben
Hi Ryan, thx for all these great videos. I wonder if you can upload pictures to the database in this way?
Ryan, thank-you for this informative screen cast! I have been taking a slightly different approach with the code for saving child properties in the controller, but your approach is much cleaner. Thanks again.
Hello Ryan, I have the same question as Ben. Could you spend some thoughts on it please? Thanks!
Ryan, I love the screencasts! They have taught me an incredible amount in a short time.
I'm having a good deal of problems with this one though. I'm trying to do pretty much exactly what you have above, with one wrinkle: multiple fields for the second model.
I have an order, and items on the order. So I need to be able to setup the order (shipping address etc.) and then add items on the same form. The catch is, I need to define price, quantity and the item_id from the form. When I use your handy for loop in the model virtual attribute, I end up getting "is invalid" because I am trying to create a new record for every variable (price, qty, id). Is there any trick around this? I haven't been able