Thanks for the feedback, Ryan, I think you're right. It never occurred to me that save() and the accompanying life-cycle methods were wrapped in a transaction w/in the framework, but after your comment I did some experimenting and that appears to be the case. (Adding an after_save method that raises an exception and calling save() rolls back a model update that otherwise saves successfully.)
It's news to me (good news, I think, though I'm still digesting the implications) that ActiveRecord is running save() + life cycle methods in a transaction. Will have to dig deeper into the ActiveRecord code - thanks for the insight!
For those who are dealing with date_select tag with this method of updating related models in one form, one easy workaround i have found is to use a text_field instead of the date_select to enter your date data … it's not a perfect one, but …
You can also do it by hand by usind select_date_tag and so on …
Ryan,
Validation and error displays are tricky. Maybe a collection good practices can serve as a valuable episode.
Here're some questions:
1. validates_associated on tasks is nice: what if I want to create a blank project without any task? I don't want it preventing me from creating a project; and later I want to edit this blank project and add tasks.
2. Will "task.new_record?" in the partial fails in a blank project case (no task) if you try to edit it, since task will be a nil object?
@Joe, sorry about that, source is updated to include that line now.
@Rich, I haven't tested this fully, but I believe if an exception is raised in a callback (save_tasks method) then it will properly rollback the save of the project and other tasks. Is it not working for you?
@Jason Jason, By handling the destroy through hidden fields, it gives the user the opportunity to Cancel all changes. Nothing persists until the form is submitted which makes for a nice user experience. Of course different apps warrant different behaviors.
My question - what about transactions? In this simple example, it's probably not a big deal if there's an error saving tasks after the project has updated, but in general, I want all of my models to be saved or none (if failure). The rails before/after methods are convenient, but I see them more as ways to fulfill pre- and post- conditions v. implementing core save operations. I'm in the midst of implementing a multi-model save now and have overridden save/save! to handle the logic of saving all the models in a single transaction. I'm not sure this is the best way, but it allows me to wrap all model updates in a single transaction.
We use acts_as_ferret, which is very slick and convenient but makes me nervous for similar reasons - indexing occurs after save, so if indexing fails it doesn't rollback the database. Thus the index can get out of sync with the model.
These are big issues in Java land, but I rarely hear them talked about in the Rubyverse. Was curious if anyone else shared these concerns. Perhaps they are simply not as important to the types of apps that Rails developers tend to build?
+1 on any future episodes dealing with associations, errors or validation for multi-model save operations. This seems to be a corner of Rails where good practices aren't well documented (if even established).
Found the problem. validates_associated :tasks in the project.rb file. It is missing from the source listed above, but I was able to catch the error watching the video again. Thanks.
I am having problems with validations on the task attributes on update. All the validations work wonderful on create. But for some reason, and I am assuming it is because the task_attributes=(attributes) method just assigns all the attributes to a task on update. How can one make sure the same validations pass for a task on update of a project when the task attributes are directly assigned?
Thanks, it would also be great to know if there is a way to have errors encounted during Ajax calls return in the same way (it write similar stack trace html into some page.) I hate staring at things then having to examine the full stack trace in the development.log file ... any ideas?
Thanks for the comments everyone. I'll try to answer some...
@TheCompWiz, there's no need to call update_attributes here because the attributes have already been set earlier. A simple save is all that's needed.
@Ted, yeah, I should have made the save_tasks method private. Good catch.
@Brandon, the show_title? is another helper method which just checks an instance variable I set earlier. Here's the code:
http://pastie.caboo.se/108319
@magistrator, I wish there was a way to disable this automatic validation message but I'm not sure how. Instead what I have done is built a custom error message handler which I can instruct to ignore certain validation error messages.
@Michael, I'll try to put up the full project source code in a day or so.
@Gilles & Darryl, I wish I had an easy answer for the date select or checkbox fields not working, but unfortunately I don't. You'll have to make them manually.
On top of this, checkboxes are tricky because the browser only sends them if they are checked. This means they won't be organized into the proper task attribute hashes. You either need to keep track of them separately or use some javascript to set a hidden field when they are changed.
I stumbled on the problem with select tags, and using html_options instead of options.
Related to that, checkboxes and radio buttons don't have html_options, so you have to manually create the elements (using check_box_tag and radio_button_tag respectively).
Otherwise, because Rails tries to be helpful when dealing with unchecking checkboxes, you get weird behaviour when updating a project, along the lines of getting extra, but blank, tasks.
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?
Great screencast ! But i have one issue, i have read on how to use this technique with select or collection_select, but what about a date_select, the above workaround doesn't work for it ? Any clue to get out of this, anyone plz ?
I have a problem that I can't figure out for the life of me.
I understand the part about
Task: <%= f.text_field :name, :index => nil %>
I must be formatting something wrong or not writing this part write, but how do you set a select list to :index => nil?
Here is my code:
<%= f.select(:phone_type, [ ['',''], ['Home', 'Home'],
['Work', 'Work'], ['Cell', 'Cell'], ['Fax', 'Fax'],
['Other', 'Other'] ]) %>
I have tried :index => nil, tried :selected => nil after the array, but am receiving 'Conflicting types for parameter containers. Expected an instance of Array, but found an instance of Hash.' in my log file. Any help please?
There is a bug in railscasts code. When you point your browser to non existent episode (for example http://www.railscasts.com/episodes/100), than your application will crash. I think some <tt>begin/rescue/end</tt> could help ;]
I typically reverse the custom find methods so that I also have the option of specifying the type of find I want to do (i.e. :first, :all, array of ids, etc.). So my code would look something like:
<code>
<pre>
def self.find_incomplete(*args, &blk)
with_scope :find => {:conditions => ['complete = ?', false],
:order => 'created_at DESC'} do
find *args, &blk
end
end
</pre>
</code>
The &blk is not really necessary but if helps future-proof the interface.
I have the same question. It seems there is a lot of refactor that needs to be done to accommodate multiple sub models.
I posted this question to "refactor my code" to see what the community can come up with. Here is the URL. http://refactormycode.com/codes/87-combine-contact-attributes-in-model
I've got problems with validation of tasks. The has_many relation silently puts up additional validators, which are invoked at the time you try to save the projects model.
The problem is that these hidden validators affect the errors of the project by adding messages like "tasks is invalid" for each invalid task. (In fact, in my case the model names are different, so I'm giving the approximate messages.)
Now, I am seeing two issues with that. First, is there any way to localize the messages produced by has_many? Second, is it anyhow possible to disable adding these messages at all (I want only one message for all failures and I want it customized).
which I guess makes it seem like a regular instance variable, which can be accessed plainly as 'some_virtual_attribute' or with self.some_virtual_attribute.
"maybe I will make it [display error messages] a futur episode depending on the demand"
... there is demand ;-)
beside "the demand" there is an other reason to go into this topic: "additional options for presenting model errors -(Changeset #7870)"
Is there some way of having (virtual) attributes passed from the view without defining a method in the model? I.e. I have several virtual attributes that the user enters into a form in the view, and then in the model I use a method to query a web service before saving the results from that web service to the actual db (i.e. method is called with :before_save).
I have a question. Let's say my project has many tasks, but it also has many tags and I want the project form to be able to accommodate building two models in reference to a project on the same form. How would one refactor the following *_attributes=(*_attributes) methods to avoid duplication?
def task_attributes=(task_attributes)
task_attributes.each do |attributes|
if attributes[:id].blank?
tasks.build(attributes)
else
task = tasks.detect { |t| t.id == attributes[:id].to_i }
task.attributes = attributes
end
end
end
def tag_attributes=(tag_attributes)
tag_attributes.each do |attributes|
if attributes[:id].blank?
tag.build(attributes)
else
tag = tags.detect { |t| t.id == attributes[:id].to_i }
tag.attributes = attributes
end
end
end
This is the most awesome yet! I agree with the many others! "Complex Forms Part 4" Single Form Multiple Model Validations! That would be so much help to so many people. Thanks for your amazing work.
Nice tutorial Ryan, I took at stab at pluginizing this technique and have uploaded the plugin and a sample project using it at: http://code.google.com/p/multimodel-forms/
If anyone has any suggestion feel free to email me (my email is on the project home page), or just add it under "Issues". Im currently working on view helpers so try and simplify the views alot.
I love this technique and use it on a few projects I'm working on. A few bits of contention from an accessibility stand point:
1. You should use label tags instead of plain text.
2. One thing that I'd like to see more of is unobtrusive javascript. It's pretty easy to pick up and makes pages more accessible. I think it'd be great for you to push this since you're in a position to reach a lot of people.
Thanks ryan, this has to be one of the longest railscasts.
i would like an episode on error messages. i have two models, one is post, the other is photo. photo is a model using image_column, users can upload 'photo' while they create a post, but it's also important to display the error message of 'photo' while saving, as image_column does validations like format check and size check. i guess quite a lot people are doing it this way, so i really hope there will be an episode on error messages.
Nice episode. I agree that the ":index => nil" snippets are a bit ugly, so I submitted a patch that adds support to pass the index option to the fields_for helper:
http://dev.rubyonrails.org/ticket/9883
Please feel free to "+1" the patch if this patch looks of interest to you.
As I was watching this episode, I notice in your application layout you have a helper called show_title?, related to your "Pretty Page Titles" from episode 30. I was wondering how that particular little snippet works.
I love these screencasts! I've been working on Rails for almost a year now, and you seem to come out with the one that I need just as I need it.
I got to generate the users_controller by executing "script/generate authenticated user" first, then "script/generate authenticated user session" to generate the session files.
Hey Ryan,
thanks for another curious ingenious screencast. Before I was doing that Javascript stuff with rendering a partial into a string and then inserting this with js. But your method is more rubyish.
But one thing is getting me a headache. You are using :index => nil in the form helpers for text_field. I tried to use that with select but it does not work. The index is rendered anyway. Do you have any clue?
I hope somebody convinces you do to a part 4 covering the error messages :-)
Just a matter of style preference, shouldn't save_tasks in Project be marked as private? Perhaps it's just my old-school tendencies but I try to make everything private by default and only expose what needs to be exposed. I understand if you just didn't bother in order to keep the episode on topic.
hey piter was faster then me ...
ok, about the ids... If you think about it giving unique ids will get really difficult as soon as you start adding and removing new rows. You could remove the id totaly but the other problem is that you should use labels for your fields. If you go for label followed by field you would need the id for the for-attribute. So the only decent way to go (beside some complicated js that guaranties unique ids) is to go by the seldom used possibility of putting the field inside of the label - so you don't have a need for the for-attribute <label><input/></label>
@Ilya & Andrew, I have yet to test this extensively in edge rails and 2.0 preview. I'll do that and try to post the fixes in the show notes.
@George, that's an interesting idea. I hadn't thought of that! I'm wondering if it will be anymore difficult to carry the state across when validation fails. I'll have to play around with it. Thanks!
One thing came to mind, though: instead of sending destroy flags from the client side, wouldn't it be easier to set the flag in Project#task_attributes= for any record for which there's no attributes passed in?
Thanks for the feedback, Ryan, I think you're right. It never occurred to me that save() and the accompanying life-cycle methods were wrapped in a transaction w/in the framework, but after your comment I did some experimenting and that appears to be the case. (Adding an after_save method that raises an exception and calling save() rolls back a model update that otherwise saves successfully.)
It's news to me (good news, I think, though I'm still digesting the implications) that ActiveRecord is running save() + life cycle methods in a transaction. Will have to dig deeper into the ActiveRecord code - thanks for the insight!
One last question for me about this episode : Is it possible to use this technique with HABTM relationships ?
For those who are dealing with date_select tag with this method of updating related models in one form, one easy workaround i have found is to use a text_field instead of the date_select to enter your date data … it's not a perfect one, but …
You can also do it by hand by usind select_date_tag and so on …
Ryan,
Validation and error displays are tricky. Maybe a collection good practices can serve as a valuable episode.
Here're some questions:
1. validates_associated on tasks is nice: what if I want to create a blank project without any task? I don't want it preventing me from creating a project; and later I want to edit this blank project and add tasks.
2. Will "task.new_record?" in the partial fails in a blank project case (no task) if you try to edit it, since task will be a nil object?
@Joe, sorry about that, source is updated to include that line now.
@Rich, I haven't tested this fully, but I believe if an exception is raised in a callback (save_tasks method) then it will properly rollback the save of the project and other tasks. Is it not working for you?
@Jason Jason, By handling the destroy through hidden fields, it gives the user the opportunity to Cancel all changes. Nothing persists until the form is submitted which makes for a nice user experience. Of course different apps warrant different behaviors.
Quick question:
Why handle the destroying through javascript and hidden fields? Why not quickly trash it at the scene via ajax?
Great stuff, Ryan, thank you!
My question - what about transactions? In this simple example, it's probably not a big deal if there's an error saving tasks after the project has updated, but in general, I want all of my models to be saved or none (if failure). The rails before/after methods are convenient, but I see them more as ways to fulfill pre- and post- conditions v. implementing core save operations. I'm in the midst of implementing a multi-model save now and have overridden save/save! to handle the logic of saving all the models in a single transaction. I'm not sure this is the best way, but it allows me to wrap all model updates in a single transaction.
We use acts_as_ferret, which is very slick and convenient but makes me nervous for similar reasons - indexing occurs after save, so if indexing fails it doesn't rollback the database. Thus the index can get out of sync with the model.
These are big issues in Java land, but I rarely hear them talked about in the Rubyverse. Was curious if anyone else shared these concerns. Perhaps they are simply not as important to the types of apps that Rails developers tend to build?
+1 on any future episodes dealing with associations, errors or validation for multi-model save operations. This seems to be a corner of Rails where good practices aren't well documented (if even established).
Found the problem. validates_associated :tasks in the project.rb file. It is missing from the source listed above, but I was able to catch the error watching the video again. Thanks.
I also forgot about the after_update :save_tasks method where the save is set to false. Any ideas?
I am having problems with validations on the task attributes on update. All the validations work wonderful on create. But for some reason, and I am assuming it is because the task_attributes=(attributes) method just assigns all the attributes to a task on update. How can one make sure the same validations pass for a task on update of a project when the task attributes are directly assigned?
Thanks, it would also be great to know if there is a way to have errors encounted during Ajax calls return in the same way (it write similar stack trace html into some page.) I hate staring at things then having to examine the full stack trace in the development.log file ... any ideas?
Thanks for the comments everyone. I'll try to answer some...
@TheCompWiz, there's no need to call update_attributes here because the attributes have already been set earlier. A simple save is all that's needed.
@Ted, yeah, I should have made the save_tasks method private. Good catch.
@Brandon, the show_title? is another helper method which just checks an instance variable I set earlier. Here's the code:
http://pastie.caboo.se/108319
@magistrator, I wish there was a way to disable this automatic validation message but I'm not sure how. Instead what I have done is built a custom error message handler which I can instruct to ignore certain validation error messages.
@Michael, I'll try to put up the full project source code in a day or so.
@Gilles & Darryl, I wish I had an easy answer for the date select or checkbox fields not working, but unfortunately I don't. You'll have to make them manually.
On top of this, checkboxes are tricky because the browser only sends them if they are checked. This means they won't be organized into the proper task attribute hashes. You either need to keep track of them separately or use some javascript to set a hidden field when they are changed.
I stumbled on the problem with select tags, and using html_options instead of options.
Related to that, checkboxes and radio buttons don't have html_options, so you have to manually create the elements (using check_box_tag and radio_button_tag respectively).
Otherwise, because Rails tries to be helpful when dealing with unchecking checkboxes, you get weird behaviour when updating a project, along the lines of getting extra, but blank, tasks.
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?
Great screencast ! But i have one issue, i have read on how to use this technique with select or collection_select, but what about a date_select, the above workaround doesn't work for it ? Any clue to get out of this, anyone plz ?
@Joe,
You might want to try add the :index option to the html_options like this
<%= f.collection_select(:phone_type, @phone_types, "id", "name", {}, :index => nil) %>
Hope this helps!
@Joe,
Any luck there Joe with using select lists? I'm also trying this out but I'm getting the same message as yourself.
Any breakthroughs then mail me.
matthew.langm at gmail.com
I have a problem that I can't figure out for the life of me.
I understand the part about
Task: <%= f.text_field :name, :index => nil %>
I must be formatting something wrong or not writing this part write, but how do you set a select list to :index => nil?
Here is my code:
<%= f.select(:phone_type, [ ['',''], ['Home', 'Home'],
['Work', 'Work'], ['Cell', 'Cell'], ['Fax', 'Fax'],
['Other', 'Other'] ]) %>
I have tried :index => nil, tried :selected => nil after the array, but am receiving 'Conflicting types for parameter containers. Expected an instance of Array, but found an instance of Hash.' in my log file. Any help please?
Ryan: You didn't post the code for the javascript mark_for_destroy function.
Here it is for everyone who needs it:
function mark_for_destroy(element) {
$(element).next('.should_destroy').value = 1
$(element).up('.task').hide();
}
There is a bug in railscasts code. When you point your browser to non existent episode (for example http://www.railscasts.com/episodes/100), than your application will crash. I think some <tt>begin/rescue/end</tt> could help ;]
Hi Ryan,
Great screencast! Instead of text boxes, should it just be as easy to use multiple select lists?
Thanks,
Matthew
One other thing to consider is if first name or last name is an empty string. You can do something like:
[first_name, middle_initial_with_period, last_name].reject(&:blank?) * ' '
This will reject all blank values.
I typically reverse the custom find methods so that I also have the option of specifying the type of find I want to do (i.e. :first, :all, array of ids, etc.). So my code would look something like:
<code>
<pre>
def self.find_incomplete(*args, &blk)
with_scope :find => {:conditions => ['complete = ?', false],
:order => 'created_at DESC'} do
find *args, &blk
end
end
</pre>
</code>
The &blk is not really necessary but if helps future-proof the interface.
Refactored! Check it out
@ Joe
I have the same question. It seems there is a lot of refactor that needs to be done to accommodate multiple sub models.
I posted this question to "refactor my code" to see what the community can come up with. Here is the URL. http://refactormycode.com/codes/87-combine-contact-attributes-in-model
Hi Ryan,
very nice screencast. Can you send me the sourcecode of the rails project, i am so sluggardly ;-)
Michael
I've got problems with validation of tasks. The has_many relation silently puts up additional validators, which are invoked at the time you try to save the projects model.
The problem is that these hidden validators affect the errors of the project by adding messages like "tasks is invalid" for each invalid task. (In fact, in my case the model names are different, so I'm giving the approximate messages.)
Now, I am seeing two issues with that. First, is there any way to localize the messages produced by has_many? Second, is it anyhow possible to disable adding these messages at all (I want only one message for all failures and I want it customized).
Anyway, thanks for the great job, Ryan!
Indeed, putting field inside of the label is good practice, but AFAIR Internet Explorer doesn't respect such syntax. Correct me if i'm wrong...
Ok I'm a newb but I'm a persistent one, so I found out after a while. Here it is, in case someone else stumbles on it. Just do:
attr_acessible :some_virtual_attribute
attr_accessor :some_virtual_attribute
etc
which I guess makes it seem like a regular instance variable, which can be accessed plainly as 'some_virtual_attribute' or with self.some_virtual_attribute.
"maybe I will make it [display error messages] a futur episode depending on the demand"
... there is demand ;-)
beside "the demand" there is an other reason to go into this topic: "additional options for presenting model errors -(Changeset #7870)"
Is there some way of having (virtual) attributes passed from the view without defining a method in the model? I.e. I have several virtual attributes that the user enters into a form in the view, and then in the model I use a method to query a web service before saving the results from that web service to the actual db (i.e. method is called with :before_save).
I have a question. Let's say my project has many tasks, but it also has many tags and I want the project form to be able to accommodate building two models in reference to a project on the same form. How would one refactor the following *_attributes=(*_attributes) methods to avoid duplication?
def task_attributes=(task_attributes)
task_attributes.each do |attributes|
if attributes[:id].blank?
tasks.build(attributes)
else
task = tasks.detect { |t| t.id == attributes[:id].to_i }
task.attributes = attributes
end
end
end
def tag_attributes=(tag_attributes)
tag_attributes.each do |attributes|
if attributes[:id].blank?
tag.build(attributes)
else
tag = tags.detect { |t| t.id == attributes[:id].to_i }
tag.attributes = attributes
end
end
end
This is the most awesome yet! I agree with the many others! "Complex Forms Part 4" Single Form Multiple Model Validations! That would be so much help to so many people. Thanks for your amazing work.
Nice tutorial Ryan, I took at stab at pluginizing this technique and have uploaded the plugin and a sample project using it at: http://code.google.com/p/multimodel-forms/
If anyone has any suggestion feel free to email me (my email is on the project home page), or just add it under "Issues". Im currently working on view helpers so try and simplify the views alot.
I love this technique and use it on a few projects I'm working on. A few bits of contention from an accessibility stand point:
1. You should use label tags instead of plain text.
2. One thing that I'd like to see more of is unobtrusive javascript. It's pretty easy to pick up and makes pages more accessible. I think it'd be great for you to push this since you're in a position to reach a lot of people.
Thanks ryan, this has to be one of the longest railscasts.
i would like an episode on error messages. i have two models, one is post, the other is photo. photo is a model using image_column, users can upload 'photo' while they create a post, but it's also important to display the error message of 'photo' while saving, as image_column does validations like format check and size check. i guess quite a lot people are doing it this way, so i really hope there will be an episode on error messages.
once again, thanks ryan for a great episode!!
Nice episode. I agree that the ":index => nil" snippets are a bit ugly, so I submitted a patch that adds support to pass the index option to the fields_for helper:
http://dev.rubyonrails.org/ticket/9883
Please feel free to "+1" the patch if this patch looks of interest to you.
As I was watching this episode, I notice in your application layout you have a helper called show_title?, related to your "Pretty Page Titles" from episode 30. I was wondering how that particular little snippet works.
I love these screencasts! I've been working on Rails for almost a year now, and you seem to come out with the one that I need just as I need it.
JS question:
How do I make the mark_for_destroy function more generic so I can apply it to multiple models (ie. phones, email addresses, etc.)
right now it is the following:
$(element).up('.phone').hide();
I'm not proficient in JS so any basic help is appreciated. Thanks!
I got to generate the users_controller by executing "script/generate authenticated user" first, then "script/generate authenticated user session" to generate the session files.
Hi Wes,
Thanks for that. I was stuck often because of that "empty hash" thing...
I don't know why I always tap into that trap.
Taalas,
You have to pass an empty hash as an option before the :index => nil
Something like this will work:
f.select :field, [ "option 1", "option 2" ], {}, :index => nil
Figured this out from http://dev.rubyonrails.org/ticket/589
Hey Ryan,
thanks for another curious ingenious screencast. Before I was doing that Javascript stuff with rendering a partial into a string and then inserting this with js. But your method is more rubyish.
But one thing is getting me a headache. You are using :index => nil in the form helpers for text_field. I tried to use that with select but it does not work. The index is rendered anyway. Do you have any clue?
Hey Ryan, another great episode.
I hope somebody convinces you do to a part 4 covering the error messages :-)
Just a matter of style preference, shouldn't save_tasks in Project be marked as private? Perhaps it's just my old-school tendencies but I try to make everything private by default and only expose what needs to be exposed. I understand if you just didn't bother in order to keep the episode on topic.
Just out of curiosity... is there any reason you chose to use "task.save" as opposed to "task.update_attributes"?
hey piter was faster then me ...
ok, about the ids... If you think about it giving unique ids will get really difficult as soon as you start adding and removing new rows. You could remove the id totaly but the other problem is that you should use labels for your fields. If you go for label followed by field you would need the id for the for-attribute. So the only decent way to go (beside some complicated js that guaranties unique ids) is to go by the seldom used possibility of putting the field inside of the label - so you don't have a need for the for-attribute <label><input/></label>
@Ilya & Andrew, I have yet to test this extensively in edge rails and 2.0 preview. I'll do that and try to post the fixes in the show notes.
@George, that's an interesting idea. I hadn't thought of that! I'm wondering if it will be anymore difficult to carry the state across when validation fails. I'll have to play around with it. Thanks!
thanks for this awesome screencast.
Hi Ryan,
Nice screencast! :)
One thing came to mind, though: instead of sending destroy flags from the client side, wouldn't it be easier to set the flag in Project#task_attributes= for any record for which there's no attributes passed in?