In this third and final episode on complex forms I will show you how to edit a project and multiple tasks all in one form. This includes removing and adding tasks dynamically as well. See the show notes for updated code.
I believe that you should delete :id from parameters before model going to save or you will get error like this: "Can't mass-assign these protected attributes: id".
Here is the code of method in the Project model that will fix that issue:
def task_attributes=(task_attributes)
task_attributes.each do |attributes|
unless id = attributes.delete(:id)
tasks.build(attributes)
else
task = tasks.detect { |t| t.id == id.to_i }
task.attributes = attributes
end
end
end
Have you tried out this technique in Rails 2.0 preview? The task_attributes= method doesn't seem to be getting called on @project.update_attributes.
However, the Task#update method is still getting called, so I wonder if this was another change in Rails 2.0, eliminating the need for the after_update filter on Project.
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?
@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!
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 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?
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.
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.
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.
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.
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.
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.
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
"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)"
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).
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
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 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?
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 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 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 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?
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.
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).
@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.
@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?
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?
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 …
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!
@john, if the project has no tasks then it is treated as an empty array so neither of the issues you mentioned should be a problem. But I haven't tested it extensively, does it not work?
@Gilles, I'm pretty sure you can adapt this to work with HABTM. One thing you'll have to do is decide how the removal process will work. Does it just remove the join or the end model? Everything else is very similar.
@Skyblaze, no PDF, but I'll be writing a few tutorials on railsforum.com in the near future showing this technique with possible additions.
Just to close out the transaction discussion, this from the Rails doc:
Save and destroy are automatically wrapped in a transaction.
Both Base#save and Base#destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction depend on or you can raise exceptions in the callbacks to rollback.
I think that we could simplify this example a lot when not worrying weather task is new or updated.
On each update we could first delete all existing tasks and then add all submitted:
def task_attributes=(task_attributes)
tasks.clear
task_attributes.each do |attributes|
tasks.build(attributes)
end
end
This way we could remove all marking of new and deleted records and save_tasks method.
We should probably had to mark tasks association as :dependent => :delete_all and add transaction in controller so we don't lose tasks if some validation fails.
More on complex forms and validation is very welcome. I'm trying to do the reverse of your relationship here. I'm trying to create a User that belongs_to a group. I've discovered how to do that with "build_group", but I need validate the created group before saving the user, if the user is creating a new group. Otherwise, I just want to validates_presence_of the group selected in a drop down. The whole validation system, with callbacks and conditionals, is pretty hard to get a good overview handle on.
@Igor, interesting idea. Destroying the tasks each time would probably work, however I hesitate to do this. There may be extra state (such as created_at) that we need to stay persistent. There might be other associations with the task that would be lost as well.
@RainChen, from my experience calling @project.save will only save the tasks if the project itself is new as well. This is why I only handle the callback on after_update. Although I haven't tested this extensively.
@tonghoho, I don't think having two "[]" in a form field name will work properly. Instead you'll have to keep track of the sub tasks separately. Sorry I can't go into detail.
Thank you for the cast. Instead of complicating things with the destroy logic and doing all nasty stuff, how about a call to link_to_remote to destroy the element?
For future Googlers, I didn't find the real solution, but what I ended up doing was leveraging the Chronic gem to parse natural language date-time expressions.
I've using the code from this episode, weirdly enough, no matter I try to remove a task or edit task value, the code "@project.update_attributes(params[:project]) " in project controller has always turned out to be false, therefore redirect me to the edit page, where the task values ain't changed. I've spent a day to figure out what's wrong, still got no clue.
Let me just say Ryan: thou are the man. My need for this and your creation of these podcasts came hand in hand. Props. Also, thanks a lot @Ilya for the error fixes when using the latest edge.
@Joan: try 'puts params[:project]' before that line of code in your controller and then check your development.log to see what it's spitting out. If it's not normal, then my guess if that you have a form builder problem in one of your views. Good Luck!
if you use the after_update instead of the after_save callback and you are creating a project for the first time (aka, not updating) and you create a task then delete it and then hit save the deleted task will still be created. this happens because save_tasks callback is not called on the create of the project model so you don't do the check for should_destroy?. one fix is to just use after_save instead of after_update, although i guess this results in a double save on the initial creation of a project.
another fix is to add a before_create alongside the after_update:
before_create :bc
def bc
fields_to_destroy = []
fields.each do |field|
fields_to_destroy << field if field.marked_for_destroy?
end
fields_to_destroy.each {|field| fields.delete(field) }
end
i think this is optimal because it won't even hit the db for new tasks on a new project that are marked for delete. but changing the callback to before_save is much simpler and less prone to bugs
I have items that belong to a studyform. I am trying to save both an item name and and item type into each studyform. My _item partial has:
<% fields_for "studyform[item_attributes][]", item do |f| %>
<p>Item: <%= f.text_field :name, :index => nil %>
Type: <%= f.text_field :type, :index => nil %>
<% if item.new_record? %>etc
The name saves into the items database but the type is always null. Any help would be appreciated.
The helper inserts a hidden field after checkboxes to have it send 0 if the box isn't checked.
Unfortunately it means that when it is checked both the 1 and the 0 get sent and it thinks there are 2 sets of attributes to save. It can also mean the values get applied to the wrong rows of data.
You shouldn't use .detect to pull up the appropriate task. To keep the task associated with the Project, use the association finder method tasks.find_by_id(params[:id])
This way you avoid instantiating a lot of unnecessary task models. This is essential when a model may have thousands of sub-models.
@Ryan: I may have an easier method of removing tasks, but I'll need to test it when combined with the concepts in this set of screen casts.
I'm using line items of an invoice, but I'll use your terminology. So I have a collection of @tasks from the database, and a new empty array: new_tasks. I loop through the posted params, searching for an existing entry in @tasks, which I then update. If not found, I build a new task instead. Either way I append that task to the new_tasks array.
When I assign the @project.tasks = new_tasks and save @project, Rails handles the removals for me, simply by the fact that they don't exist in the collection... smart! They don't exist in the collection because they weren't posted and added to new_tasks. So I have no need to mark for destroy or even call destroy.
I have a bunch of code in the controller, and I'm not using the virtual attributes you mentioned... so I'm really looking forward to combining these to methods and seeing if it all works!
For me it works perfectly but now i have to add several "third" models that are related to my second model. How can i do that. Anyway an issue with this code is the fact that we will have input fields with same ids...
Ryan -- Thanks for the screencasts. They are extremely helpful.
@Paul, I made it work with checkboxes. I used check_box_tag. I use the checkbox value in the model (in def choice_attributes) to set the value of the field I want set, rather than setting it directly with the checkbox, like so:
It's a workaround but it works (and work s for radios, too). Then on the form end, I manually check the box like so:
<% is_checked = choice.correct == 1 ? true : false; %> and render the check box this way:
<%= check_box_tag('question[correct_choice_id][]',choice.id, is_checked,{}) %>
(that's on edit -- on new, when I don't have question.correct.choice I have named the checkbox with a counter from within the partial, and run essentially the same thing. I'm not sure if this will be clear or not but it does work for me so hopefully it helps.
I've found a couple of issues here that maybe only affect me, but I'll post in case someone else experiences this.
First, the "should_destroy" attribute was showing up in the first item regardless of which item was removed. The issue was that the 'should_destroy' hidden fields had no value and so did not end up in the correct array. I switched to hidden_field_tag with a default 0 value and it's fixed.
Second, when passing strings with non-alpha chars the URL encoding for javascript was running once for each layer of the array. So '-n/a-' was becoming "-n%25252Fa-", I did a 3.times{ attr[:string] = URI.unescape(attr[:string])} within the task_attributes= method. Hackish, but it works.
I was a bit confused by the workaround in the should_destroy? method, until I remembered that MySQL lacks a genuine BOOLEAN datatype.
With PostgreSQL, you can define a boolean field in your model (without a trailing ?), and a predicate method (with the trailing ?) is automatically there in your model. Handy.
I can edit and delete tasks with no problem, but when I try to create a new task (or several at once) the very first of the previously saved tasks gets wiped out. I copied Ryan's code line for line and am running this in Rails 2.0.
ALmost forgot to mention, I only have this problem when I have the marked_for_destroy hidden field and js link in that partial. I remove them and can add tasks with no problem. Odd..
How do I reference the project[task_attributes][] fields for auto_complete_field functions... Does anyone have an example using auto_complete_field and fields_for?
Only one question.. I'm trying to do this with a habtm relationship (possible topic for another screenie? ;)) and unfortunately it doesn't save the linking table when updating attributes. :(
Does anyone have an idea how to tell rails to manually save it?
Ryan, this was a terrific tutorial. I'd definitely be interested in seeing more on validations, etc.
I'm also curious as to how you might take a progressive enhancement approach. The current method seems to require Javascript. I tried using a TaskController to provide web services, but if I make it polymorphic (Project and ToDoList), all hell breaks loose... or at least, it looks really ugly.
I like the fact that nothing is lost until the entire project is saved, so I'd prefer to keep it that way, but adding and removing tasks becomes difficult without Javascript.
Thanks so much for another great screencast series. However, I found it a little bit complicated and cluttered with all these hidden fields, javascript and such. So I took a slightly different approach. First, I modifed the task partial so that it passes always a model id:
I find this series very useful. I've implemented some stuff using the technique you've described and it works great, but I'm interested how could I make file upload using this very technique (gmail like upload). Can you help me please?
I found this railscast series to be particularly useful! After stumbling across Jamis Buck's post about moving associated creations to the model your screen cast really cleared up the concept for me.
Validations had me puzzled, but I finally figured it out. I've posted what worked for me in my situation over on my <a href='http://psifire.livejournal.com/119168.html'>Live Journal</a> page... since I don't have a real blog.
@ Gernot Kogler: I think that your idea can not work - I have been experimenting quite a lot with it and I think that all duplicate named imputs will get lost (for example if you have twho or more new tasks). The trick of putting the new items to the array only works if there is bare [] in the name of INPUT field. SO I suggest changing your conditional so it gives empty string for new tasks.
I unfortunately ran into a problem while using the code. My Task model has more than one field (name, priority and estimated cost) and they all are required (i.e. there is a validates_presence_of rule for each of them).
The problem happens when the user updates an existing task. More precisely when the user deletes the content of one of the text field and then clicks the remove link for that task.
As you can guess, since the remove links just hides the task, an incomplete task is sent to the controller and since validation happens before the after_callback is called, validation fails, the task is not destroyed and the controller redirects to the update page (with an error saying that the task is invalid)
Fortunately, the solution is not too difficult, all we need is to add a method in the Task class that returns false if the task should be destroyed and the pass that method as an "if" parameter to all validation rules for the task.
Your screencast helped a lot solving the problem which I previously solved in a rather complicated manner.
I have a question to ask.
What if my project has 3 different type of tasks ->
A task can be either a
- File
-File Upload(Model : File)
-HTML ADD (Model : File)
-Link (Model : Link)
Now, I want three links at the bottom of the project to add file/html/link where html and file use the same model.
Now, in case of add html i want to redirect to a WYSYWIG editor(displaying it in the same page could get dirty) from where user can write the html and save it. I think a popup would be helpful in this case. And, then saving would redirect to the same form, from where you can save the project. Any heads-up on how this can be implemented ?
+1 for screencast on this.
Hi guys, this is a great tutorial! i have tried to get it to work with photos aswell but I can't seem to get it right. I am using a polymorphic photo table which may well be the problem. Does anyone have any suggestions on how i could get it to work?
What happens if you create more than one new task? i don't see how your code will work, the params parser is designed to ignore all same named parameters after the first rather than to collect them. That is how the checkbox hack works.
I have never seen an "up" (or "next") function that moves through the HTML DOM and return an element of a certain class. It sounds useful. Can someone post an online reference for this function?
Does anyone have any insight into how to adapt this example for has_many :through associations? The build aspect of this example breaks it -- I've poste dabout it here -- http://railsforum.com/viewtopic.php?id=14580
Any thought about how to handle security related issue such as mass assignment? Is there a clean way to combine your approach with using attr_accessible?
I follow the tutorial and everything worked great. My only problem is that with what I am I add a new "task"(picture in my case) it overwrites the previous picture. If I add more than one picture, it overwrites previous pictures and adds the new ones. Any ideas on how to fix this? it works great if I don't wrap the file_field in an if statement, but I really don't want it to show up before a picture that has already been submitted....just want the ability to remove. Thanks
I'm really running into road block after road block trying to figure out how to get associated validation working properly. At this point all I can get is an error message that says 'Model is invalid' rather than the preferred child-model's attribute error message.
We really need a screen cast on how to get this kind of validation working.
that is, display the error msg for each field in asset (child model). If you have a grandchild model, you can do the same probably.
finally, here's my method for displaying the error msg
def error_msg_for( record, field=nil)
return '' if record.errors.empty?
if field
a = []
record.errors.each do |attr,msg|
a << msg if attr == field
end
%(<div class="field_error">#{field} #{a.join('<br>')}</div>) unless a.empty?
else # if field == nil
%(<div class="errorExplanation">There was a problem with the data you submitted. Please fix the error(s) below then re-submit.</div>)
end
end
so I can put
<%= error_msg_for(@post) %>
without a field parameter at the top of the form, it'll display the generic "you have an error" msg when there's any error in the parent or child.
you may notice I check for possible multiple error msgs for the same field. I don't know if this ever happens or not.
also, the field name needs to match exactly with the attribute. if you're not getting the error msg back as expected, dump out errors array to see what's in it.
Has anyone come up with a way to get rid of the HTML validation errors rails creates with double underscores and non-unique id attributes when setting :index => nil ?
partial: http://pastie.caboo.se/149027
In the log the array is messed.
"set_mappings"=>[{"file_name_pattern"=>"*pricelistsample*"}, {"format"=>"1", "file_name_pattern"=>"*availability*"}, {"format"=>"1", "id"=>"953125641"}, {"id"=>"996332877"}]
It looks like rails collect the array in a wrong way. What could it be?
Hopefully you're still reading comments for this Railscast. This series has helped me out with my current application, thanks!
One quick question: if I use a check_box in my partial when using fields_for, it always tries to submit two entries for every one displayed on the page. I'm assuming this is because the check_box helper adds a hidden field for when the box is not checked. Is there any way around this?
for those having the problem of tasks not being saved or deleted on Update. i had the same problem and i just tracked it down (i'm usin Rails 2.0 as well and didn't have to change anything from Ryan's example -- nice work by the way!). anyway, my issue was i inadvertently had a single = sign in the task_attributes method. i had this:
I'm curious: what is the best way to limit the number of fields the user can add during both create and update? For instance in my application I'm using this to add picture upload fields, but a user has a max of 9 pictures to they're allowed to upload.
As a side note: Loved these 3 Eps! Thank you so much for taking the time to demo RoR to the masses. Many blessings to you as you work toward your 100th episode!
I'd love to see how validation errors are highlighted in fields that are dynamically added to the form - if you added some task fields and these failedvalidation, when the default view for the form is reloaded (to highlight the errors) the additional fields that were added in the previous step (that failed validation) wouldn't be there...
Is there any strategy for using this technique with radio buttons? The problem being that instead of having separate "button groups" for each task, you end up with one huge button group for all the radio buttons across tasks, because they all use the same "name" for radio widgets in each task.
Your association validation error message pastie rocks!
Here's an enhancement to it that strips out the useless "is invalid" error messages so you're only left with the useful association validation messages:
Hi i have the following problem. I followed the screen cast to create
complex forms from railscats so i create a partial view for my
equipments list, so the client can add several different equipments, I
also made another view in case looking for the equipment inside the
select box. When i choose a equipment from the view (find_equipment) i
created there is no problem when there is only one equipment inside
the client form, but when I another equipment and I try to select it
from the select view (find_equipment) it does not update the select
box it should, it update the first select box.
I have a similar problem with observe_field, it should update the
properties for a certain product, but it only update the first select
list the other lists are not updated.
Great series! I did have one question about the Complex Forms parts, and that is how to degrade if javascript is disabled. Is there a way to allow link_to_function to "submit" to another url so you can recreate the objects and then add a new task and render the original form or am I thinking about it all wrong?
Thank you everyone for your excellent comments. Sorry I haven't gotten around to respond to them all.
I just updated the code in the notes to use a better approach. This is taken from the recipe I wrote for Advanced Rails Recipes. Hopefully it will solve the majority of these problems.
@David Blankenship, good question. I would probably hide the link when javascript is disabled, then you could handle adding tasks through a non multi-model form by going through a tasks controller "new" action for each task after the project has been created.
I've blogged about how to get around the issue of the radio buttons having the same ids at "http://zilkey.com/2008/4/5/complex-forms-with-correct-ids":http://zilkey.com/2008/4/5/complex-forms-with-correct-ids
The code you've posted here isn't the same as that in the Advanced Rails Recipes book (e.g. the task partial is significantly different). I'm wondering which code is more up-to-date, particularly in the light of errata item 31586 posted at the book's site.
@Jeff, thanks for posting this! I included the link in the show notes.
@Chris, good eye. I modified the code slightly from the Advanced Rails Recipes book by moving some into the "fields_for_task" helper method. For simplicity, the recipe in the book does this inline in the view using a local variable, but recommends moving it into a helper method. Since that note wasn't included here I wanted to encourage the best practice of not setting variables in the view. The code itself still does the same thing, it's just moved around a bit.
I have, probably, a dumb question:
For forms such as this, where we're adding multiple tasks at once to a new form, I understand (I believe) that they're adding new post variables like project[]task[] etc, and editing existing values like project[1]task[3] etc.
My question: wouldn't it be clearer, and perhaps more semantically correct, to ALWAYS assign a sequential integer as the array index (so that new record combinations might also look like project[4]task[2]) and simply rely on a rested id tag in order to identify which records have previously been saved (existing) and those that lack an id (new)?
I'm sure I'm missing something, but it just feels like that postulated format ties in better with the object model by utilizing the correct location for the id data, and relieves the problem of the post parser having to make educated guesses as to which data pairs should be grouped into an individual object.
Ryan - thanks for the great screencasts - I hope you keep them coming!
WARNING: Solution doesn't work with Edge Rails (2.0.3) when Task has nested fields! (Unless I'm not building the names of the inputs correctly... but I'm fairly certain I am!)
I've found this (Rails2) solution has problems when Task itself has nested fields - at least in Edge Rails (2.0.3). The request parameter parser won't parse the request into the new_task_attributes array correctly.
Example: Let's say Task has_one :person, and Person comprises fields 'first_name' and 'last_name'. You'd probably end up with a form input similar to the following (of course you'd choose what the actual name of the attribute 'person_attributes' would be):
While everything works, I am not able to update tasks using Firefox. I can in IE! I can create new records though! Any ideas what I need to check? Thanks.
That said--is the after_update callback absolutely necessary?
It looks to me like it works if you call task.save from within Project.existing_task_attributes= (right after the line "task.attributes = attributes").
I have used this sublime recipe as the basis for a kicked-up-a-notch version, with a more complex database schema and it's working perfectly so far.
The point where I'm having problems is if, for instance you also have a Category model that has_many :tasks and you have to select which category the task :belongs_to before clicking the add_task_link.
What I have tried -unsuccessfully- to do is when you call page.inset_html, to pass in something like :object => Task.new(:category_id => what_should_i_put_here?), being (as you might have figured out) "what_should_i_put_here?" the main problem. I can get the category_id value from the form field, but I can not insert it within the page.insert_html method.
Thank you.
the codes didn't work for me well (updated version or no) and i guess i can blame the complexity or disorderliness of my project code. with the help of a friend, i was able to find a way around my predicament with these complex forms. i use collection_select instead of text_fields too. anyway, while my friend was a genius being able to figure out how to do the updating (edit or adding a new record) of multiple forms on the spot, he wasn't able to figure out how to delete records (plus he had somewhere to go to).
but wow, i still can't believe how i managed to figure out a very simple way to do it. it's a bit of simple common sense, actually.
i used the javascripty "remove" in conjunction with the server side's "destroy".
<% if task.new_record? %>
<%= link_to_function "Remove field", "$(this).up('.task').remove()" %>
<% else %>
<%= link_to 'Cancel task', task, :confirm => 'Are you sure?', :method => :delete %>
<% end %>
The excerpt from the book on this topic was insanely helpful. Thanks for all the good work on this and other screencasts and reference materials. I've learned more about Rails from you than any other single source, hands down.
@Scott, and everyone fighting the checkbox problem with dup fields. I fixed your code. The else if in the javascript didnt run right and I found it more suitable to remove the name field of the hidden field rather than attempt to toggle its value--which did not seem to work.
My solution also allows you to still use check_box instead of check_box_tag. The only mod to the form is to add :onchange => "toggle_name(this);" to the checkbox form element. Then simply declare the toggle_name function in your appplication.js file as such:
This works great but I have a problem. My partial is an invoice item field with amount, price and total sum (and of course there can be many of these). I'd like the total sum to be calculated by Rails when amount or price is changed. Because the form fields have no unique id's, I cant' get this to work with remote_function.
It works if I calculate with JavaScript by traversing trough the DOM like so:
Great article and a lifesaver BUT.. when you start doing things like time_select the custom forms_for_x is no longer rendered properly (namely the hidden fields generated by this time_select).. Not sure if this would effect other rails tags.. if so it's not as versatile as once thought and would have a limited use.
BUG: In case number of attributes is more than one in the partial, browser does not groups it right and sends wrong data.
Example:
Data expected :
new_part_attributes[]["ordering"]=>"1",
new_part_attributes[]["inline"]
=> "false"
new_part_attributes[]["wfile"]=>#<ActionController::UploadedStringIO:0xb602ea20>
Data Send By Browser:
"new_part_attributes"=>[{"ordering"=>"1", "inline"=>"false", "wfile"=>#<ActionController::UploadedStringIO:0xb602ea20>}, {"ordering"=>"2", "inline"=>"true"}, {"wfile"=>#<ActionController::UploadedStringIO:0xb602cd38>}]
Possible solution is send index also in the array, so that it groups it right.
Ok, so I bought the book, and keyed in the example except I changed the names of tables to fit my project. It didn't work. I beat around the bushes for a couple of days, but couldn't find my bug.
Then I keyed it in using the exact line for line coding from the bug. Still same problem.
Then I found this page, and downloaded the code.
After adding a named route for projects (the code does not have it - I had to figure this part out), it still doesn't work. I get no errors, just neither my project nor my tasks ever get saved.
Has anyone else actually got any of this to work? I mean I would hate to be completely wasting my time - I could code it the hard way in far less time than I have already wasted here.
I am pretty sure my problem is in that routing thing that is not provided in the code, railscast or the book. Perhaps the version he uses has different defaults for routing or something. Anyhow, I have wasted 5 good coding days on what looked like a good idea, but just won't work for me at all - and the logs give no clues that I can use.
Ok, so I got it working by mashing together some code from above into the code in the book, and replacing all the instances of project_path with actions and ids as appropriate in my controllers and views.
@Dominic: I (after a bit of futsing with mistyped fields) have indeed gotten this to work for the simple model used in the railscast. What version of Rails are you running?
@All: While implementing this technique with my own application, I got through the first and second casts in this series without problem, but when I went through the steps in this one, I now get the following in my log:
/!\ FAILSAFE /!\ Tue Jul 22 15:49:37 -0700 2008
Status: 500 Internal Server Error
Conflicting types for parameter containers. Expected an instance of Hash but found an instance of Array. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value.
I'm gonna keep digging and see if I can figure out why something is getting sent an array that is supposed to be a hash (my guess is that it is the params hash), but I figured I would drop a line and see if anyone could quickly spot my problem here. I'm still pretty new to Rails, so any help is appreciated.
So, has anyone effectively made this work with compound models?
EX. - Where a Project can have multiple Tasks, which can have multiple Milestones, which can have multiple Assignees,...
I seem to have everything working other than getting the parameter names to be correctly parented.
I've thought of several solutions such as passing into each partial a 'parent_attr' local which (if not nil) would be prepended to the field names. But I don't think this is the most elegant method.
any other suggestions would be greatly appreciated.
I fixed my issue - it was with my method of calling a collection_select in my form. I had :index => '' under my html options. I think I tried this at some point while I was trying to get thing to work in part II, but found another solution and failed to take out that bit (Incidentally, the solution which worked was calling the collection_select directly on the task_form, which I had trouble doing at first because I didn't realize that when you did that you had to drop what would have otherwise been the first argument, task, in this case). So anyway, I think I'm good to go.
@glennpow: I think I'm going to have to do what you are trying to accomplish as well, so I will share any good solution I may come up with - I would also be grateful if you did likewise.
@mellifluidic et al.
So with the latest edge rails, there's a new feature for mass-assigning hash attributes, which uses an ":accessible => true" parameter appended to any has_one, has_many, etc in the model file.
http://github.com/rails/rails/commit/e0750d6a5c7f621e4ca12205137c0b135cab444a
I think this is similar to the "attr_accessible" method used before in the user.rb, but this also seems to allow these sort of forms to be created:
<% form_for @user do |f| %>
<%= f.text_field :login %>
<% fields_for :phone_numbers do |pn_f| %>
<%= pn_f.text_field :area_code %>
<%= pn_f.text_field :number %>
<% end %>
<%= submit_tag %>
<% end %>
So you don't have to mess around with "[new_phone_attributes]" or any custom attribute setter methods. This is GREAT!
I've only just learned about this, as it's very new, but I'll try it out and post any findings.
One thing that I already see as a possible snag is the use of AJAX-updated selects (for instance) in any of these nested resource form-segments.
EX. If I have a user with N number of addresses, where each address form has a country_id SELECT with a dynamically-updated region_id SELECT, then there will probably be conflicts when one country SELECT tries to update it's (and only it's) region_id SELECT.
Because it's a partial, the id and name attributes are duplicated, so the autocomplete only works on the first instance of the partial, not to mention it's invalid XHTML to have dupe id's.
Awesome! Looks sweet. I was searching around on Rails Forums for solutions after just sort of winging it, and came across the following thread: http://railsforum.com/viewtopic.php?id=14905.
It looks like the attribute-fu plugin tidies things up similarly. When I get a chance I'll play around with both methods and see what I can see.
I should mention though that I've got an even more complicated situation - the kind of fields that I use for each subtasks depending on what kind of subtask you are dealing with. So imagine that either the task or the subtask has a select which when selected renders the appropriate fields for that selection. I'm gonna take this as it comes, but it's down the road for me so I'm keeping an eye on it.
Has anyone tested this in IE7? IE7 does not seem to like the prototype calls to remove the element from the form IF it was added via "Add a task" or other javascript call. Works fine in IE6 and FF (and Safari I'd imagine)
I'm wondering if I did something stupid (I did make some changes to fit it into my app), since nobody else has mentioned this problem, but my tests (luckily) turned up a nasty bug when I implemented this solution (which is fantastic otherwise, btw!)
The problem comes from existing_task_attributes=. Since that runs before the parent model (Project) is validated, existing children (Tasks) will be changed/deleted in the DB even if the parent model fails validation. I haven't actually tried Ryan's Project/Task code myself, but in my own app this would allow these sorts of scenarios:
1. Delete all children on edit page, but parent requires children (here if a Project required 1 or more tasks). Parent fails validation and goes back to edit page, but children have already been deleted. If you cancel back out of edit, children are gone from DB even though parent never updated.
2. Delete all existing children, and add new one that is invalid. (Say a task requires a name and you don't add one, so that child fails validation.) Parent will fail validation if it validates associated records and will go back to edit page, but existing records will have been deleted. Again, if you cancel back out of edit, children are gone from DB even though parent never updated.
3. Modify existing child(ren), and add new one that is invalid. Like above, parent validation fails, but child is still updated.
Is this just a quirk of my app, or a bug nobody has hit/noticed/commented on?
My fix was just to wrap a transaction around the update_attributes call (and make it update_attributes! so it would throw an exception on failure) and move the else clause to a rescue block. This way, if the parent model validation fails all the other changes to the children are rolled back as well. Tested and it works, though check out my blog post for a gotcha about testing Rails transactions: http://freevirusesandspyware.com/2008/08/testing-rails-transactions.html
Is there a way to count the number of times you have clicked on "Add a task" without using Javascript? I'm using a radio button in the partial and need a way to distinguish if one of the added partials contains the radio button that was the selected.
Yes, I'm having issues with this method and a date_select as well. When submitting the form it throws a:
Status: 500 Internal Server Error
Conflicting types for parameter containers. Expected an instance of Array but found an instance of Hash. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were {"date1(1i)"=>"2008", "date1(2i)"=>"8", "date1(3i)"=>"15", "date2(1i)"=>"2008", "date2(2i)"=>"8", "date2(3i)"=>"15"}.)
In my application I would like to automatically number my tasks and display the number on the page. So far all of my attempts have resulted in the same number for all lines after the first, e.g. 1, 2, 2, 2.
In my task model:
@@line_no = 1
def initialize(attributes = nil)
super
unless line_no?
self.line_no = @@line_no
@@line_no += 1
end
end
Similar problem like Tim: how to (auto-)number the tasks? I don't mind if on remove it isn't actually shown correct, but after commit the task should be written correctly (1, 2, 3, ...) to the database.
I would really like to see a screencast covering those methods but with a has_many :through, cleaning associated records and then rebuild them in a callback would be an example.
That was what i was looking for, where I have one problem to enhance it a little bit for some dynamic usage with radio buttons.
I use a different model, but to make it easier to understand it I'll use the task and project model to explain my problem.
I use select instead of a text_field in _task.erb, which works. But the database query should depend on a radio button where I ask which type should be used.
The reason for that is that a task database entry uses two fields to point to two other tables.
The dynamic part is to use either (tasklow_id and p.tasklow.title) or (taskhigh_id and p.taskhigh.title). While defaulting to tasklow.
Btw, one of tasklow_id or taskhigh_id is NULL and the other one has a value.
Refactoring the select code to a partial could be a way to go and use onclick for the radio button, but I have no idea how to avoid getting many select fields and how to get the initial select box since the onclick works only onclick.
Can someone post their working validation example? Actually, I have it all working, EXCEPT for the extra parent level messages of "Tasks is invalid" (in my case not Tasks, but you get the idea.) I don't want these messages since I have the correct task error messages following the main project messages.
I know some others earlier in this thread have mentioned the same thing, but I'm looking for what people have done about it. Does someone have a snippet of code that shows the best way to handle this? I would think it would just involve iterating over the errors and removing the messages I don't want? I'd like to see the best way to handle this, though.
Ryan,
These three screencasts really pack a punch! thanks a lot for this series especially. There is one small glitch i am running into though.. hoping someone can point out the cause of the bug..
i have a complex form with fields from 3 models in the edit and new templates. 2 of them work just fine. in the fields from the 3rd model, there is a collection_select field where i get the following error
/!\ FAILSAFE /!\ Tue Sep 09 10:07:52 +0530 2008
Status: 500 Internal Server Error
Conflicting types for parameter containers. Expected an instance of Array but found an instance of Hash. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were {"1"=>{"days"=>"7"}}.)
i have set ':index => nil' in the code.. here is my code for that field
yourstruly_vinay: I think if you use :index => "" instead of nil, it'll work. A quirk in collection_select.
Now my problem:
I'm in the same boat as glennpow and others in that I'm befuddled by compound model creation: I can get two models to parent correctly as in the screencasts, but if I try to get to a third level, no soap.
My situation:
I have an Invite model that is a proxy for new (mass assignment) User creation:
This works fine, as in Project/Task creation in the screencasts. However, I also want to have a new Group Membership created for each of the (new) Users created in the same transaction (all Invites are scoped to a Group):
I can generate everything fine with two sets of virtual attributes (for the User and the Membership), EXCEPT the (new) User id for the Membership (If I try to hardwire something like :user_id => @invite.users.id, I get an id like '1546789'. I assume this is because I'm not nesting the Membership creation within the User creation within the Invite. Or maybe I'm just being a dopey newbie....
If anyone has successfully done a multi-level, multi-model form, I'd love to see the code.
@Tony V: thanks for the reply. Although, i found out that :index has to be passed with the html_options for collection_select and its family of form helpers.
Ref: http://dev.rubyonrails.org/ticket/589
I am getting another error that noone else seems to be getting, oddly enough. i am getting an "ActiveRecord::ReadOnlyRecord" error on the save_tasks method. It is raised because when we do tasks.each, the record is instantiated as a read only. (Ref: http://api.rubyonrails.com/classes/ActiveRecord/ReadOnlyRecord.html). The solution is here - http://learn.theworkinggroup.ca/2007/2/6/activerecord-readonlyrecord-error-what. However, im not sure if its great programming because of the many queries raised. Any work arounds? hope that was helpful for someone.
I did this for an HABTM association. The join table records are not getting deleted on removing the child records. Everything else is fine though. So it is just the clutter in the join table that has to be removed. Just wondering if Pascal, pimpmaster or any other managed a solution to that..
I had a page and the association was files. You could upload numerous files by clicking on the "Add file" link.
If I only had one file the validation code worked fine. If I had multiple it was running through the association and adding the errors to the base for the page model for every instance of the field.
For example. If I had a new page with 1 file (file required) and I didn't upload the file I would get 1 message saying:
File: no file uploaded
If I had two instances of the file added and I didn't upload the file I was getting 4 errors:
File: no file uploaded.
This is because 2 fields with the name :file were getting passed in so it was running the association validations twice so 2 errors each time yielded 4 errors for file.
I added the code to store and array of each field the the validation was run on and push the name of the field into the array after each. They use a statement to check if the name of the field is already in the array and not run the association validations if this is the case. This eliminated the extra validations on the association field. See the pastie:
http://pastie.org/271260
If anyone has a better way please respond.
chrisff your code was the basis and Kevin your error patch to remove the "'Association' is invalid" was a huge help.
I have used both of your pasties and modified them slightly for the error validation.
I got to try out Michael's multimodel-forms plugin. It works quite well.
I also had to add radio buttons to my stuff and I used Sarah's solution.. I feel this is a much better solution to the problem than using javascript as it is more difficult to validate. Unfortunately right now I've only done this hacked into Michael's plugin and it's not very DRY:
in_attributes.each_with_index do |attributes, j|
# I just added these two lines
attributes["correct_answer"] = attributes.include?("correct_answer_id") ? 1 : 0
attributes.delete("correct_answer_id")
Good stuff guys, it's a big help.
As for validation - any thoughts on server-side validation to make sure there are a certain number of associated (belongs_to) records at all times? So for example you can only make a Book that has at least two Chapters. Or what about something like at least one Chapter has to be flagged (flag column) as "introduction"?
Also wrt my last comment - I used radio buttons, but mine don't require multiple radios per record, only one because they're selecting one of the records to flag. I got lucky in that what I wanted to do fit the broken situation.
Didn't want anyone to think it was a solution for radio buttons where you need to define a set for each record.
for those who get trouble with check_box adding more than rigth rows, due to unchecked value hidden field, my work arround was:
-at the task_attributes method setter loop (at project.rb model), I changed the "tasks.build(attributes)" line, to "tasks.build(attributes) unless attributes.values.length == 1", where attributes.values.length represents the quantity of fields for each task sent by the form.
I took the idea from http://railsforum.com/viewtopic.php?id=1063
I have also having the problem where the params hash isn't properly created for attribues. I have the following example:
<input type='hidden' name='t[test][][1]' value='1'>
<input type='hidden' name='t[test][][2]' value='2'>
<input type='hidden' name='t[test][][3]' value='3'>
<input type='hidden' name='t[test][][1]' value='4'>
<input type='hidden' name='t[test][][2]' value='5'>
<input type='hidden' name='t[test][][3]' value='6'>
<input type='hidden' name='t[test][][1]' value='7'>
<input type='hidden' name='t[test][][2]' value='8'>
<input type='hidden' name='t[test][][3]' value='9'>
which creates the following params[:t][:test] => [{"1"=>"1"}, {"1"=>"4"}, {"1"=>"7", "2"=>"2"}, {"2"=>"5"}, {"2"=>"8", "3"=>"3"}, {"3"=>"6"}, {"3"=>"9"}]
As you can see the grouping isn't being properly assigned - i.e. params[:t][:test][0] should be { :1 => "1", :2 => "2", :3 => "3" }
I'm having problems with the code in this line: "attributes = task_attributes[task.id.to_s]". I Keep getting the error "can't convert String into Integer" if I take out the to_s method it goes away, but the existing tasks get deleted. Anyone ideas why?
I'm running into a weird problem I can't seem to figure out. I've got the code from the book working per se. However, no matter how many new attribute fields I add via the RJS link (in my case, it is "Add Another Child"), only the child entered into the original fields (i.e.: the first Child/Task field presented on load) is sent back to the controller. None of the rest are sent. If you click "Delete" and delete all the fields, then add one more back, the data in that new field isn't saved either, so it seems that for some reason only the original field is saved.
Strangely, after inspecting the generated source, it all looks fine and as if it has been compiled correctly. The HTML generated by the original view (with the initial child/task) is identical to the HTML generated when you delete the first child/task field and immediately add another one back, yet the latter doesn't send anything back to the controller.
Excellent tutorial! I have a problem regarding has_many and the dependent => :destroy command. I have two models, one for an Item and another for Variation but when I delete my item the variations connected to it isn't deleted, even though I have the :dependent => :destroy attribute set.
A short preview can be found at http://pastie.org/327247.
I can get my variations when I view the item, I can update and everything. It's just when I want to delete the Item. Any suggestions?
Is there a way to monitor the task in the "new" event, and if it already exists select the existing id rather than creating a new one. That would be awesome, but I'm stumped, and I'm a noob.
I have a relationship many to many between two objects "factura" and "factura_producto". I've implemented this code to update many record into "factura_producto" table.
This send the following error:
Rendered orden/_factura_producto (0.00848)
Rendered orden/_factura_producto (0.00238)
Rendered orden/_factura_producto (0.00243)
Rendered orden/_factura_producto (0.00614)
Rendered orden/_factura_producto (0.00221)
Rendered orden/_factura_producto (0.00219)
Rendered orden/_factura_producto (0.00788)
Rendered orden/_factura_producto (0.00254)
Rendered orden/_factura_producto (0.00239)
Rendered orden/_factura_producto (0.00194)
Rendered orden/_fields (0.13827)
Completed in 0.21036 (4 reqs/sec) | Rendering: 0.14042 (66%) | DB: 0.03088 (14%) | 200 OK [http://localhost/orden/edit/1]
Conflicting types for parameter containers. Expected an instance of Hash, but found an instance of Array. This can be caused by passing Array and Hash based paramters qs[]=value&qs[key]=value.
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/cgi_ext/cgi_methods.rb:204:in `type_conflict!'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/cgi_ext/cgi_methods.rb:168:in `container'
I've been using this method for a while, but I think I've found a problem with it.
If you delete all of the tasks in one fell swoop, by the looks of development.log the existing_task_attributes set of fields isn't serialised at all. This means that when update_attributes is called, the existing_task_attributes= method in the Project model isn't called, hence none of the tasks are removed.
This seems a little crazy, but the problem is very real for me!
Things become really complicated when trying to add an autocomplete functionality to a dynamic form field. I started a thread here http://railsforum.com/viewtopic.php?pid=83963#p83963
I have multiple models which I want to create. I have products and categories. The categories work like the tasks Ryan goes through in this episode, but I want products as well to be dynamically added and removed so in the partial for the category i added another partial for a product and I added helpers and extended the model.
However when I click the add category button then I get an error: missing } after property list
http://localhost:3000/shops/new#
Line 2
The buttons for removing and adding products works fine. If I remove the helpers for products then the category buttons works fine again.
Help!
Anyone who has had success using this with multiple models?
One question, is it possible to do something similar with a show form and 2 sub models. i.e show a projext and be able to add tasks as well as entries to a projectlog.
I tried simply adding a form to a show view to create the extra model but seem to have much success
Great cast, and an even better update :). Thanks for that!
I'm currently struggeling with the following problem though:
If you delete a task but then have a failing validation on update (e.g. an empty project name) the task is deleted anyways.
Maybe not a big issue but inconsistant with the transaction model ("all-or-nothing").
The reason for this to happen is quite obvious if you watch the logs: the sql-DELETE is encapsulated by an individual BEGIN-COMMIT frame.
I've added a checkbox to the tasks to mark them if they're done. Unfortunately, every time i mark a task as completed, it will be duplicated and the next task has the same name but becomes completed. If i hit save again, the procedure repeats again.
Any idea where the error could be? The whole code code is 1:1 the same as yours, except my addition
<%= tasks_form.check_box :is_completed, :index => nil %> is completed
Has anybody used this in combination with cucumber and webrat for testing?
I would like to use wabrats fill_in method to fill in two or more tasks of the project (as in Ryan's example). However, as those fields are generated from the same partial they all have the same name and id.
Has anybody found a way to tell webrat to use the second or third field with a certain name/id?
Has anyone solved the collection_select issue. If so I'd love some help. I keep getting the errors described by @joe above.
if I enter a {} for the html options I get a nil object when evaluating nil.attributes=
w/o the {} in my collection_select I get a 500 error... Conflicting types for parameter containers. Expected an instance of Array but found an instance of Hash.
I'd be interested to know how one might allow a user to create multiple projects AND multiple tasks on one page with jscript. I couldn't find a rails only method for keeping each new project separate from one another. Right now I'm using a jscript iterator for each new project created that creates a new 'row' in a multi-dimensional array. Works but seems like a hack method. I'd much prefer a method that doesn't require javascript for separating new projects from each other.
Recently i came across the same issue with date_select and time_select that @Erik, @Tim and many others are facing.
Status: 500 Internal Server Error
Conflicting types for parameter containers. Expected an instance of Hash but found an instance of Array. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value.
Here is the workaround for the issue:
http://agilerails.wordpress.com/2009/03/11/date_select-time_select-doesnt-work-with-auto_prefix-object/
I creating a simple shopping cart, where you can add / remove itens, but since the field have the same name because :index => nil, the observe_field I have is not working.
Does anyone have some tip on using oberve_field here?
I try to follow your cast to use multiple model update in a single form.
But i have a problem and don't find any solution.
I have validation for the model let's say the primary. In your example in the project model.
When a fill up badly the form at the first time (i have the correct error message) and i correct it afterward, I have strange thing in the database.
Let second model (task in your example) has no reference to the first model. Project_id is NULL
I have look in more detail what 's happen. And when i have the validation error at the first time, the task object are created in the DB and at the second time, i have alreeady the object in the DB, thus this isn't new_mouton_attributes but existing_mouton_attributes that is used!
Can you help me, i can send you my code if you want
Is it possible to create a nested form building children model who themselves have a has_many relationship with their own children and successfully implemented this on the one form. Further to this, I am trying to implement this using ajax and wondered (again: if this is even possible?) how to pass the formbuilder object back to my partial.
One other quick one: I noticed that to date their seems to be only javascript workarounds when adding more than one child in the 2.3.2 accepts_nested_attributes_for. Are you aware of any work/changes pending to resolve this?
Found a way that appears to work which basically is a hybrid of the pre 2.3 way where I maintain the :index=>nil attribute and the 2.3 methodology. I also gave up on the form_builder and constructed the naming of the fields by hand circumventing the need to pass form builder objects either locally or by ajax.
The "create" method does not change. If the checkbox is checked, then saves the "1" to the DB. In case the checkbox is not checked, I solve it with a default value of "0" in the DB.
In the case of the "update" method, what I do is iterate through the params[:task] array and check the value of the task_attribute:
params[:project][:task_attributes].each do |task|
if task[:completed] != "1"
task[:completed] = "0"
end
end
I think it is the simplest and cleanest solution I have found.
Thanks for the info Ryan! I am glad that in later railscasts the example code is available for download. For those who are trying to get this to work today, you can download example code from my google code repo at:
A couple of differences:
-- changed the application concept a little to fit what I am trying to build. The structure is almost identical but I changed the names of the models.
-- replaced prototype with jQuery so the javascript is a little different from what you find in the cast.
-- this example code was build on rails 2.3.2.
I am having some conflicts between jquery and setting defaults in javascript_include_tag. Does anyone know how Tyler converted the railscast prototype javascript to use jquery instead?
Hi Ryan, This is a grt screencast. I tried it out and everything works besides one thing, which is adding new task. I can edit the task, i can delete the task but i can not add a new task. All the code is the same as shown in your screencast...any reason why?
Thanks a lot Ryan for the highlighting the hidden secrets of Rails ActiveRecord and all others. Mass assignment made easy via params[:project][existing_task_attributes][]...
A tip for someone who might be hunting for Mass assignment in a belongs to many through a third connecting table Scenario -
Proposal can have many Sections via Proposalsections.
Decided by a checkbox to create/delete a new/existing Proposalsection record for a Proposal.
----
code for view - with checkboxes
for openingsection in @sections
<li class="topItem clear">
<%@pso=Proposalsection.find_by_proposal_id_and_section_id(@proposal.id, openingsection.id)%>
<% if @pso.nil? %>
<%= check_box_tag "proposal[proposalsection_attributes][][section_id]", openingsection.id %>
<% else %>
<%= check_box_tag "proposal[existing_proposalsection_attributes][#{@pso.id}][section_id]", openingsection.id, :checked=>true %>
<% end %>
<%= openingsection.name %></li>
end
-----
code for controller - saving Proposalsections with Proposal fields
params[:proposal][:existing_proposalsection_attributes] ||= {}
@proposal=Proposal.find(params[:id])
@proposal.update_attributes(params[:proposal])
end
It worked for me after wasting full day tweaking over it.
Nitin, can you clarify where the opensection.id is defined? Is it part of 'ProposalSections' or 'Proposals'?
Thanks.
This may be just what I need to solve a similar membership type challenge i'm having with my view.
I am having the hardest time figuring out why the foreign ID field is not being automatically populated in my site, which I've based my form off of Ryan's spectacular tutorial (thanks Ryan). From what I understand, the foreign id is put into the task when the build method is called in the line "@project.tasks.build". Then the foreign id is saved in the Project model under the "existing_task_attributes" method. Is this all correct or is the foreign id populated in some other way?
Ryan,
Thanks for all your screencasts! They are lifesavers!
I am really having trouble with the updating of attributes. The build of a new project (routinglist) works great...but I get the error "'can't convert String to Integer" in the Existing_Detail_Attributes= method. the creation of a new RoutingList with Details works fine, it is only when I try to update the RoutingList and save the attributes. If I change detail.id.to_s to just detail.id, I get no errors, but it deletes all my details.
Any ideas? BTW I am running this on Rails 2.0.2 (I upgraded to 2.3.3 but I never could get Mongrel to start, so I am still using 2.0.2)
Here are the 2 relevant methods in the model (detail.id=13):
def new_detail_attributes=(detail_attributes)
detail_attributes.each do |attributes|
routlist_details.build(attributes)
end
end
def existing_detail_attributes=(detail_attributes)
routlist_details.reject(&:new_record?).each do |detail|
logger.info 'detail='+detail.to_yaml
attributes = detail_attributes[detail.id.to_s]
if attributes
detail.attributes = attributes
else
routlist_details.delete(detail)
end
end
end
I'm struggling with multiple radio box groups. I don't want (ideally) to solve this problem with javascript if I can help it. Has anyone figured a way to get multiple radio button groups working without JS?
in the controller (update), this was causing a problem for me, so I removed it and all works fine.
However, by removing this, would you see any implications? I know you use if when checking for existing attributes (of course), but in my case I have a nested model where the child model is created when the parent model is created and can contain blank field. Therefore, it doesn't matter about checking for new or current attributes...I simply have attributes whatever.
As such, what is the purpose of this line of code?
Hi, thanks for great tutorials ! I tried build complex form for habtm associtions, but I get this error: ActiveRecord::ReadOnlyRecord. Please can you help me and describe me how it works and how to fix it? Many thanks.
I used this approach with remote form. The hash that's created when the form is posted (non-Ajax) is different from the Ajax (Form.serialize). The ajax hash does not create the desired hash. Instead of creating
[{:a=>1, :b=>2}, {:a=>3, :b=>4}]
it creates
[{:a=>1}, {:b=>2}, {:a=>3}, {:b=>4}]
Any solutions here? Is this a known bug with rails?
If I has several tasks, say 50 or more, I don't want to add a single text field but search for a particular task. Any ideas in which direction I should go?
I have get an error on the video tutorial, I have the ActiveRecord::ReadOnlyRecord error when I try to edit a project, and I can't figure out how to correct it :/
I have implemented a similar situation, however, I have a "Copy" facility. In your domain, this would create a new Project based on an existing one, and new Tasks based on existing ones too.
However, I can create a new Project, but not new Tasks - how do I achieve this?
I just got this to work in Rails 2.3.8 and Ruby 1.9 using the updated code on this page, but I had to change one thing, and I wanted to share my solution. I had to add a 'project_id' column to my tasks table. Until I added this column, I could create a new Project, but not edit it, and this was the reason. If you're getting an error saying 'no such column... task.project_id' then try this. Thanks for the amazing work Ryan!
Those new to this episode, like myself, should check out Ryan's updated GitHub repo for this episode: https://github.com/ryanb/complex-form-examples, where there is a branch for doing this unobtrusively with jQuery. Tim Riley forked it, and has a great working copy here: https://github.com/timriley/complex-form-examples
Piggybacking on their work, I made a jQuery plugin to help clean up the js process: https://github.com/mboynes/nestedAttributes
I believe that you should delete :id from parameters before model going to save or you will get error like this: "Can't mass-assign these protected attributes: id".
Here is the code of method in the Project model that will fix that issue:
def task_attributes=(task_attributes)
task_attributes.each do |attributes|
unless id = attributes.delete(:id)
tasks.build(attributes)
else
task = tasks.detect { |t| t.id == id.to_i }
task.attributes = attributes
end
end
end
That was probably the most intense Railscast yet. Unbelievable how much ground you covered.
Excellent stuff as always
+1 vote for a future episode on associated validations.
:)
Have you tried out this technique in Rails 2.0 preview? The task_attributes= method doesn't seem to be getting called on @project.update_attributes.
However, the Task#update method is still getting called, so I wonder if this was another change in Rails 2.0, eliminating the need for the after_update filter on Project.
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?
@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!
Just out of curiosity... is there any reason you chose to use "task.save" as opposed to "task.update_attributes"?
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.
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?
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
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.
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!
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.
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.
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!!
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.
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.
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.
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
"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)"
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!
Hi Ryan,
very nice screencast. Can you send me the sourcecode of the rails project, i am so sluggardly ;-)
Michael
@ 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
Refactored! Check it out
Hi Ryan,
Great screencast! Instead of text boxes, should it just be as easy to use multiple select lists?
Thanks,
Matthew
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 ;]
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();
}
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?
@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
@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!
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 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 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 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?
I also forgot about the after_update :save_tasks method where the save is set to false. Any ideas?
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.
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).
Quick question:
Why handle the destroying through javascript and hidden fields? Why not quickly trash it at the scene via ajax?
@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.
@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?
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?
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 …
One last question for me about this episode : Is it possible to use this technique with HABTM relationships ?
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!
Anyway i still think that short particular arguments like this are better explained in mini-pdf. Will you release them in the future?
@john, if the project has no tasks then it is treated as an empty array so neither of the issues you mentioned should be a problem. But I haven't tested it extensively, does it not work?
@Gilles, I'm pretty sure you can adapt this to work with HABTM. One thing you'll have to do is decide how the removal process will work. Does it just remove the join or the end model? Everything else is very similar.
@Skyblaze, no PDF, but I'll be writing a few tutorials on railsforum.com in the near future showing this technique with possible additions.
Just to close out the transaction discussion, this from the Rails doc:
Save and destroy are automatically wrapped in a transaction.
Both Base#save and Base#destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction depend on or you can raise exceptions in the callbacks to rollback.
http://caboo.se/doc/classes/ActiveRecord/Transactions/ClassMethods.html
I think that we could simplify this example a lot when not worrying weather task is new or updated.
On each update we could first delete all existing tasks and then add all submitted:
def task_attributes=(task_attributes)
tasks.clear
task_attributes.each do |attributes|
tasks.build(attributes)
end
end
This way we could remove all marking of new and deleted records and save_tasks method.
We should probably had to mark tasks association as :dependent => :delete_all and add transaction in controller so we don't lose tasks if some validation fails.
@Gilles, for the date_select, use
:index=>''
rather than
:index=>nil
[quote]
after_update :save_tasks
def save_tasks
tasks.each do |t|
if t.should_destroy?
t.destroy
else
t.save(false)
end
end
end
[/quote]
when @project.save, it automatically save the @project.tasks. Then Rails calling the callback save_tasks ?
Would the same task do update twice ?
For moving error messages from failed task validations up to the project error messages, I added this "after_validations" method to Project:
http://pastie.caboo.se/109132
More on complex forms and validation is very welcome. I'm trying to do the reverse of your relationship here. I'm trying to create a User that belongs_to a group. I've discovered how to do that with "build_group", but I need validate the created group before saving the user, if the user is creating a new group. Otherwise, I just want to validates_presence_of the group selected in a drop down. The whole validation system, with callbacks and conditionals, is pretty hard to get a good overview handle on.
I love your work. Thank you!
what about adding task has many sub_tasks
In field_for what name I have to use
"project[task_attributes][][sub_task_attributes][]" is not work.
+1 for another screencast discussing error messages and validations in greater detail.
@Igor, interesting idea. Destroying the tasks each time would probably work, however I hesitate to do this. There may be extra state (such as created_at) that we need to stay persistent. There might be other associations with the task that would be lost as well.
@RainChen, from my experience calling @project.save will only save the tasks if the project itself is new as well. This is why I only handle the callback on after_update. Although I haven't tested this extensively.
@tonghoho, I don't think having two "[]" in a form field name will work properly. Instead you'll have to keep track of the sub tasks separately. Sorry I can't go into detail.
Thank you for the cast. Instead of complicating things with the destroy logic and doing all nasty stuff, how about a call to link_to_remote to destroy the element?
I have a little bit different situation. In this screencast we have project-task but I have task-project situation (contact-group in my case)
So, the form had been saving even though there were validation errors in Group. valid? saved the day
if @contact.group.valid? && @contact.save
flash[:notice] = 'New contact has been created'
redirect_to
end
In the view:
<% if @contact && @contact.errors %>
<%= error_messages_for 'contact' %>
<% if @contact.group %>
<% @group = @contact.group %>
<%= error_messages_for 'group' %>
<% end %>
<% end %>
This might be Edge Rails only, but instead of setting an instance variable for <tt>error_messages_for</tt> you can do this:
<code>
%lt!-- in _task.rhtml -->
<%= error_messages_for(:task, :object => task) %>
</code>
I've noticed that this method really dislikes date-time select boxes.
For future Googlers, I didn't find the real solution, but what I ended up doing was leveraging the Chronic gem to parse natural language date-time expressions.
I've using the code from this episode, weirdly enough, no matter I try to remove a task or edit task value, the code "@project.update_attributes(params[:project]) " in project controller has always turned out to be false, therefore redirect me to the edit page, where the task values ain't changed. I've spent a day to figure out what's wrong, still got no clue.
Let me just say Ryan: thou are the man. My need for this and your creation of these podcasts came hand in hand. Props. Also, thanks a lot @Ilya for the error fixes when using the latest edge.
@Joan: try 'puts params[:project]' before that line of code in your controller and then check your development.log to see what it's spitting out. If it's not normal, then my guess if that you have a form builder problem in one of your views. Good Luck!
Thank you, Henry. I finally was able to found out the problem, it's actually caused by the failure of validation in task model.
nice cast, there is one bug though.
if you use the after_update instead of the after_save callback and you are creating a project for the first time (aka, not updating) and you create a task then delete it and then hit save the deleted task will still be created. this happens because save_tasks callback is not called on the create of the project model so you don't do the check for should_destroy?. one fix is to just use after_save instead of after_update, although i guess this results in a double save on the initial creation of a project.
another fix is to add a before_create alongside the after_update:
before_create :bc
def bc
fields_to_destroy = []
fields.each do |field|
fields_to_destroy << field if field.marked_for_destroy?
end
fields_to_destroy.each {|field| fields.delete(field) }
end
i think this is optimal because it won't even hit the db for new tasks on a new project that are marked for delete. but changing the callback to before_save is much simpler and less prone to bugs
sorry for any confusion: replace 'field' with 'task'
here is a better example:
before_create :bc
def bc
tasks.dup.each do |task|
tasks.delete(field) if task.marked_for_destroy?
end
end
Steve, I believe that issue is actually handled by the logic in the view code:
<% if task.new_record? %>
<%= link_to_function "remove", "$(this).up('.task').remove()" %>
<% else %>
When you're creating a new project, you just delete the entire field for the task rather than marking it for deletion.
I have items that belong to a studyform. I am trying to save both an item name and and item type into each studyform. My _item partial has:
<% fields_for "studyform[item_attributes][]", item do |f| %>
<p>Item: <%= f.text_field :name, :index => nil %>
Type: <%= f.text_field :type, :index => nil %>
<% if item.new_record? %>etc
The name saves into the items database but the type is always null. Any help would be appreciated.
Has anyone made this work with check boxes?
The helper inserts a hidden field after checkboxes to have it send 0 if the box isn't checked.
Unfortunately it means that when it is checked both the 1 and the 0 get sent and it thinks there are 2 sets of attributes to save. It can also mean the values get applied to the wrong rows of data.
You shouldn't use .detect to pull up the appropriate task. To keep the task associated with the Project, use the association finder method tasks.find_by_id(params[:id])
This way you avoid instantiating a lot of unnecessary task models. This is essential when a model may have thousands of sub-models.
Does this still work with the latest Rails trunk? I get this error: Can't mass-assign these protected attributes: id
@Ryan: I may have an easier method of removing tasks, but I'll need to test it when combined with the concepts in this set of screen casts.
I'm using line items of an invoice, but I'll use your terminology. So I have a collection of @tasks from the database, and a new empty array: new_tasks. I loop through the posted params, searching for an existing entry in @tasks, which I then update. If not found, I build a new task instead. Either way I append that task to the new_tasks array.
When I assign the @project.tasks = new_tasks and save @project, Rails handles the removals for me, simply by the fact that they don't exist in the collection... smart! They don't exist in the collection because they weren't posted and added to new_tasks. So I have no need to mark for destroy or even call destroy.
I have a bunch of code in the controller, and I'm not using the virtual attributes you mentioned... so I'm really looking forward to combining these to methods and seeing if it all works!
For me it works perfectly but now i have to add several "third" models that are related to my second model. How can i do that. Anyway an issue with this code is the fact that we will have input fields with same ids...
Ryan -- Thanks for the screencasts. They are extremely helpful.
@Paul, I made it work with checkboxes. I used check_box_tag. I use the checkbox value in the model (in def choice_attributes) to set the value of the field I want set, rather than setting it directly with the checkbox, like so:
attributes[:correct] = correct_choice_id.include?(attributes[:counter_id]) ? 1 : 0
It's a workaround but it works (and work s for radios, too). Then on the form end, I manually check the box like so:
<% is_checked = choice.correct == 1 ? true : false; %> and render the check box this way:
<%= check_box_tag('question[correct_choice_id][]',choice.id, is_checked,{}) %>
(that's on edit -- on new, when I don't have question.correct.choice I have named the checkbox with a counter from within the partial, and run essentially the same thing. I'm not sure if this will be clear or not but it does work for me so hopefully it helps.
I've found a couple of issues here that maybe only affect me, but I'll post in case someone else experiences this.
First, the "should_destroy" attribute was showing up in the first item regardless of which item was removed. The issue was that the 'should_destroy' hidden fields had no value and so did not end up in the correct array. I switched to hidden_field_tag with a default 0 value and it's fixed.
Second, when passing strings with non-alpha chars the URL encoding for javascript was running once for each layer of the array. So '-n/a-' was becoming "-n%25252Fa-", I did a 3.times{ attr[:string] = URI.unescape(attr[:string])} within the task_attributes= method. Hackish, but it works.
I'm having the same problem as Paul, is there an easy solution anywhere?
Thank you so much for putting this together. It was incredibly helpful! Please add my vote to your doing a part 4 on validation.
Thanks a lot for this Ryan.
+1 for a separate validations screencast!
Ryan -
Thanks for the great screencasts.
I was a bit confused by the workaround in the should_destroy? method, until I remembered that MySQL lacks a genuine BOOLEAN datatype.
With PostgreSQL, you can define a boolean field in your model (without a trailing ?), and a predicate method (with the trailing ?) is automatically there in your model. Handy.
Strange problem here..
I can edit and delete tasks with no problem, but when I try to create a new task (or several at once) the very first of the previously saved tasks gets wiped out. I copied Ryan's code line for line and am running this in Rails 2.0.
Anyone else face this issue?
ALmost forgot to mention, I only have this problem when I have the marked_for_destroy hidden field and js link in that partial. I remove them and can add tasks with no problem. Odd..
How do I reference the project[task_attributes][] fields for auto_complete_field functions... Does anyone have an example using auto_complete_field and fields_for?
Awesome screencast series, thank you so much!
Only one question.. I'm trying to do this with a habtm relationship (possible topic for another screenie? ;)) and unfortunately it doesn't save the linking table when updating attributes. :(
Does anyone have an idea how to tell rails to manually save it?
Ryan, this was a terrific tutorial. I'd definitely be interested in seeing more on validations, etc.
I'm also curious as to how you might take a progressive enhancement approach. The current method seems to require Javascript. I tried using a TaskController to provide web services, but if I make it polymorphic (Project and ToDoList), all hell breaks loose... or at least, it looks really ugly.
I like the fact that nothing is lost until the entire project is saved, so I'd prefer to keep it that way, but adding and removing tasks becomes difficult without Javascript.
Thanks so much for another great screencast series. However, I found it a little bit complicated and cluttered with all these hidden fields, javascript and such. So I took a slightly different approach. First, I modifed the task partial so that it passes always a model id:
<%= fields_for "project[tasks][#{task.new_record? ? 0 : task.id}]", task do |task_form| %>
Now I can determine which tasks to create, update and delete in the method task_attributes= like this:
def task_attributes=(task_attributes)
task_attributes.each do |id, attributes|
if id.to_i == 0
tasks.build(attributes)
else
task = tasks.detect{|p| p.id == id.to_i}
tasks.attributes = attributes
end
end
tasks.reject{|t| t.new_record? || task_attributes.has_key?(t.id.to_s)}.each {|t| t.mark_for_destroy}
end
Works like a charm and keeps my views clean.
Hi Ryan.
I find this series very useful. I've implemented some stuff using the technique you've described and it works great, but I'm interested how could I make file upload using this very technique (gmail like upload). Can you help me please?
I found this railscast series to be particularly useful! After stumbling across Jamis Buck's post about moving associated creations to the model your screen cast really cleared up the concept for me.
Validations had me puzzled, but I finally figured it out. I've posted what worked for me in my situation over on my <a href='http://psifire.livejournal.com/119168.html'>Live Journal</a> page... since I don't have a real blog.
@ Gernot Kogler: I think that your idea can not work - I have been experimenting quite a lot with it and I think that all duplicate named imputs will get lost (for example if you have twho or more new tasks). The trick of putting the new items to the array only works if there is bare [] in the name of INPUT field. SO I suggest changing your conditional so it gives empty string for new tasks.
Ryan,
great tutorial.
I unfortunately ran into a problem while using the code. My Task model has more than one field (name, priority and estimated cost) and they all are required (i.e. there is a validates_presence_of rule for each of them).
The problem happens when the user updates an existing task. More precisely when the user deletes the content of one of the text field and then clicks the remove link for that task.
As you can guess, since the remove links just hides the task, an incomplete task is sent to the controller and since validation happens before the after_callback is called, validation fails, the task is not destroyed and the controller redirects to the update page (with an error saying that the task is invalid)
Fortunately, the solution is not too difficult, all we need is to add a method in the Task class that returns false if the task should be destroyed and the pass that method as an "if" parameter to all validation rules for the task.
def do_validation
!should_destroy?
end
validates_presence_of :name, :priority, :estimated_cost, :if => :do_validation
Ryan,
would it be possible to have an RSS feed for tracking the comments for each episode?
In some cases, the comments/questions and answers are quite interesting too.
Thanks again for the great work
+1 for Pascal's suggestion
habtm would rock for this
I have a patch that avoids the empty {} if you use the index => nil with a select tag at http://dev.rubyonrails.org/ticket/10416
I also see URL-escaped characters on child nodes. Problem is I can't unescape them. They stay escaped for some reason. :/
rails 2.0.1
I came up with a solution for checkboxes based on Ryan's suggestion of using javascript.
application helper
http://pastie.caboo.se/131145
application javascript
http://pastie.caboo.se/131144
Your screencast helped a lot solving the problem which I previously solved in a rather complicated manner.
I have a question to ask.
What if my project has 3 different type of tasks ->
A task can be either a
- File
-File Upload(Model : File)
-HTML ADD (Model : File)
-Link (Model : Link)
Now, I want three links at the bottom of the project to add file/html/link where html and file use the same model.
Now, in case of add html i want to redirect to a WYSYWIG editor(displaying it in the same page could get dirty) from where user can write the html and save it. I think a popup would be helpful in this case. And, then saving would redirect to the same form, from where you can save the project. Any heads-up on how this can be implemented ?
+1 for screencast on this.
Hi guys, this is a great tutorial! i have tried to get it to work with photos aswell but I can't seem to get it right. I am using a polymorphic photo table which may well be the problem. Does anyone have any suggestions on how i could get it to work?
@Gernot Kogler:
What happens if you create more than one new task? i don't see how your code will work, the params parser is designed to ignore all same named parameters after the first rather than to collect them. That is how the checkbox hack works.
Im getting this error:
Symbol as array index
on def task_attributes
why this ?
What kind of javascript is this?
"up('.task')"
I have never seen an "up" (or "next") function that moves through the HTML DOM and return an element of a certain class. It sounds useful. Can someone post an online reference for this function?
Does anyone have any insight into how to adapt this example for has_many :through associations? The build aspect of this example breaks it -- I've poste dabout it here -- http://railsforum.com/viewtopic.php?id=14580
any help would be awesome!
Any thought about how to handle security related issue such as mass assignment? Is there a clean way to combine your approach with using attr_accessible?
I follow the tutorial and everything worked great. My only problem is that with what I am I add a new "task"(picture in my case) it overwrites the previous picture. If I add more than one picture, it overwrites previous pictures and adds the new ones. Any ideas on how to fix this? it works great if I don't wrap the file_field in an if statement, but I really don't want it to show up before a picture that has already been submitted....just want the ability to remove. Thanks
<div class="picture">
<% fields_for "housing[picture_attributes][]", picture do |pic_form| %>
<p>
<% if picture.new_record?%>
Upload A picture:
<%= pic_form.file_field :uploaded_data , :index => nil %>
<%= link_to_function "remove", "$(this).up('.picture').remove()" %>
<%end%>
<% unless picture.new_record? %>
<img src="<%=picture.public_filename(:thumb)%>" />
<%= link_to_function "remove", "mark_pic_for_destroy(this)" %>
<%= pic_form.hidden_field :id , :index => nil %>
<%= pic_form.hidden_field :should_destroy , :index => nil, :class => 'should_destroy' %>
<%end%>
</p>
<% end %>
I'm really running into road block after road block trying to figure out how to get associated validation working properly. At this point all I can get is an error message that says 'Model is invalid' rather than the preferred child-model's attribute error message.
We really need a screen cast on how to get this kind of validation working.
here's how I handle error msgs. If someone can suggest improvements, I'd appreciate it.
In this example: post = parent model, asset = child model
I check @post.errors. if it's not empty, I display some generic msg, like: you have one or more errors, pls fix then submit again.
@post.errors is an activerecord object, an array of attr=>msg like this
['title'=>"title can't be blank",'body'=>"body can't be blank',...]
Next, I display the error msg for each field right above it, like this
<label for="post_title">Title</label><br />
<%= error_msg_for @post, 'title' %>
<%= f.text_field :title, :size => 60 %>
<br />
<br />
<label for="post_body">Body</label><br />
<%= error_msg_for @post, 'body' %>
<%= f.text_area :body, :cols => 60, :rows => 15 %>
in my child model, asset, do similar thing
<% fields_for "post[asset_attributes][]", asset do |f| %>
<%= error_msg_for asset, 'name' %>
<%= f.text_field :name, :size => 15 %>
...
<% end %>
that is, display the error msg for each field in asset (child model). If you have a grandchild model, you can do the same probably.
finally, here's my method for displaying the error msg
def error_msg_for( record, field=nil)
return '' if record.errors.empty?
if field
a = []
record.errors.each do |attr,msg|
a << msg if attr == field
end
%(<div class="field_error">#{field} #{a.join('<br>')}</div>) unless a.empty?
else # if field == nil
%(<div class="errorExplanation">There was a problem with the data you submitted. Please fix the error(s) below then re-submit.</div>)
end
end
so I can put
<%= error_msg_for(@post) %>
without a field parameter at the top of the form, it'll display the generic "you have an error" msg when there's any error in the parent or child.
you may notice I check for possible multiple error msgs for the same field. I don't know if this ever happens or not.
also, the field name needs to match exactly with the attribute. if you're not getting the error msg back as expected, dump out errors array to see what's in it.
Juan, I found for some reason it only works if you have a file_field for ALL the pics. So for the existing pics, I'd simply hide them
<%= pic_form.file_field :uploaded_data , :index => nil, style=>'display:none' %>
not pretty, but it works
Has anyone come up with a way to get rid of the HTML validation errors rails creates with double underscores and non-unique id attributes when setting :index => nil ?
partial: http://pastie.caboo.se/149027
In the log the array is messed.
"set_mappings"=>[{"file_name_pattern"=>"*pricelistsample*"}, {"format"=>"1", "file_name_pattern"=>"*availability*"}, {"format"=>"1", "id"=>"953125641"}, {"id"=>"996332877"}]
It looks like rails collect the array in a wrong way. What could it be?
It is known problem which is not resolved in rails 2.0 and has the Ticket #10101
http://dev.rubyonrails.org/ticket/10101
Thanks for attention.
Hi Ryan,
Hopefully you're still reading comments for this Railscast. This series has helped me out with my current application, thanks!
One quick question: if I use a check_box in my partial when using fields_for, it always tries to submit two entries for every one displayed on the page. I'm assuming this is because the check_box helper adds a hidden field for when the box is not checked. Is there any way around this?
Thanks! -- Bryan
for those having the problem of tasks not being saved or deleted on Update. i had the same problem and i just tracked it down (i'm usin Rails 2.0 as well and didn't have to change anything from Ryan's example -- nice work by the way!). anyway, my issue was i inadvertently had a single = sign in the task_attributes method. i had this:
photo = photos.detect { |p| p.id = attributes[:id].to_i }
so an assignment was happening, rather than the correct:
photo = photos.detect { |p| p.id == attributes[:id].to_i }
that fixed my issue.
I'm curious: what is the best way to limit the number of fields the user can add during both create and update? For instance in my application I'm using this to add picture upload fields, but a user has a max of 9 pictures to they're allowed to upload.
As a side note: Loved these 3 Eps! Thank you so much for taking the time to demo RoR to the masses. Many blessings to you as you work toward your 100th episode!
I'd love to see how validation errors are highlighted in fields that are dynamically added to the form - if you added some task fields and these failedvalidation, when the default view for the form is reloaded (to highlight the errors) the additional fields that were added in the previous step (that failed validation) wouldn't be there...
Is there any strategy for using this technique with radio buttons? The problem being that instead of having separate "button groups" for each task, you end up with one huge button group for all the radio buttons across tasks, because they all use the same "name" for radio widgets in each task.
@chrisff
Your association validation error message pastie rocks!
Here's an enhancement to it that strips out the useless "is invalid" error messages so you're only left with the useful association validation messages:
http://pastie.caboo.se/165073
I am having the same issue as Harlan Crystal I am using Radio buttons but they are being all grouped into one big group
Thanks a lot!
Hi i have the following problem. I followed the screen cast to create
complex forms from railscats so i create a partial view for my
equipments list, so the client can add several different equipments, I
also made another view in case looking for the equipment inside the
select box. When i choose a equipment from the view (find_equipment) i
created there is no problem when there is only one equipment inside
the client form, but when I another equipment and I try to select it
from the select view (find_equipment) it does not update the select
box it should, it update the first select box.
I have a similar problem with observe_field, it should update the
properties for a certain product, but it only update the first select
list the other lists are not updated.
Here is the source code:
_equipment_services.html.erb
<div class="equipment">
<% fields_for "voip[equipments_services_attributes]
[]",equipment_services do |e|%>
<p class="titulo">Información Equipos</p>
<p>
Activo Fijo
<
%=e.select("equipment_id",Equipment.find(:all,:conditions=>"estado=6",:order=>"numero_activo_fijo").collect{|
p|[p.numero_activo
_fijo, p.id]},:index=>nil)%>
<% if equipment_services.new_record? %>
<%="Estado Activo"%> <%=e.hidden_field("estado",:value=>6)
%>
<%= link_to_function "Eliminar",
"this.up('.equipment').remove()" %>
<%else%>
<=% link_to_function "Eliminar",
"mark_for_destroy(this,'.contact')" %>
<%= e.hidden_field(:id,:index=>nil) %>
<%= "Estado" %>
<%=
e.select("estado",Code.find(:first, :conditions=>["id=5"]).elements.collect{|
p| [p.descripcion,p.id]},:index=>nil) %>
<%=
e.hidden_field(:should_destroy,:index=>nil, :class=>"should_destroy")
%>
<%end%>
<%=link_to "Crear Nuevo
Equipo",add_equipment_equipment_path,:popup =>
["Agregar_Nuevo_Equipo","height=600, width=700"]%>
<%= link_to "Buscar Equipos",
{:action=>"find_equipment", :controller=>"show_select"}, :popup =>
["Show_Equipment","height=60
0, width=700"]%>
</p>
<%end %>
</div>
javascript function
function set_value(id,value)
{
opener.document.getElementById(id).value=value;
window.close();
}
find_equipment.html.erb
<table>
<tr>
<% form_tag find_equipment_show_select_path, :method=>"get" do%>
<td>Buscar Usuario:</td>
<td><%=select_tag "busqueda","<option value='1'>Modelo</
option><option value='2' selected='true'>Número Activo Fijo</op
tion><option value='3'>Número Serie</option><option
value='4'>Dirección MAC</option>"%>
<td><%=text_field_tag :search, params[:search]%></td>
<td colspan=3><%=submit_tag "Buscar", :name=>nil%></td>
<%end%>
</tr>
<tr>
<th>Modelo</th>
<th>Marca</th>
<th>Activo Fijo</th>
<th>Número de serie</th>
<th> Tipo Equipo </th>
</tr>
<%
switch=false
for equipment in @equips
%>
<tr class="<%= if switch
"TRPAR"
else
"TRIMPAR"
end %>
<% switch=!switch %>">
<td><%=equipment.modelo%></td>
<td><%=Code.getDescripcion(equipment.marca)%></td>
<td><
%=link_to_function("#{equipment.numero_activo_fijo}","set_value('voip_equipments_services_attributes__equipment_id','#{equi
pment.id}')")%></td>
<td><%=equipment.numero_serie%></td>
<td><%=Code.getDescripcion(equipment.code_id)%></td>
</tr>
<% end %>
</table>
Can anyone help me please? i'm kind of lost here
Great series! I did have one question about the Complex Forms parts, and that is how to degrade if javascript is disabled. Is there a way to allow link_to_function to "submit" to another url so you can recreate the objects and then add a new task and render the original form or am I thinking about it all wrong?
Thank you everyone for your excellent comments. Sorry I haven't gotten around to respond to them all.
I just updated the code in the notes to use a better approach. This is taken from the recipe I wrote for Advanced Rails Recipes. Hopefully it will solve the majority of these problems.
@David Blankenship, good question. I would probably hide the link when javascript is disabled, then you could handle adding tasks through a non multi-model form by going through a tasks controller "new" action for each task after the project has been created.
I've blogged about how to get around the issue of the radio buttons having the same ids at "http://zilkey.com/2008/4/5/complex-forms-with-correct-ids":http://zilkey.com/2008/4/5/complex-forms-with-correct-ids
The code you've posted here isn't the same as that in the Advanced Rails Recipes book (e.g. the task partial is significantly different). I'm wondering which code is more up-to-date, particularly in the light of errata item 31586 posted at the book's site.
Many thanks for your very fine Railscasts.
@Jeff, thanks for posting this! I included the link in the show notes.
@Chris, good eye. I modified the code slightly from the Advanced Rails Recipes book by moving some into the "fields_for_task" helper method. For simplicity, the recipe in the book does this inline in the view using a local variable, but recommends moving it into a helper method. Since that note wasn't included here I wanted to encourage the best practice of not setting variables in the view. The code itself still does the same thing, it's just moved around a bit.
I have, probably, a dumb question:
For forms such as this, where we're adding multiple tasks at once to a new form, I understand (I believe) that they're adding new post variables like project[]task[] etc, and editing existing values like project[1]task[3] etc.
My question: wouldn't it be clearer, and perhaps more semantically correct, to ALWAYS assign a sequential integer as the array index (so that new record combinations might also look like project[4]task[2]) and simply rely on a rested id tag in order to identify which records have previously been saved (existing) and those that lack an id (new)?
I'm sure I'm missing something, but it just feels like that postulated format ties in better with the object model by utilizing the correct location for the id data, and relieves the problem of the post parser having to make educated guesses as to which data pairs should be grouped into an individual object.
Is there anyway for fields_for to look at the scope of the enclosing form_for and change it's scope accordingly?
Depending on the form, sometimes I need the fields_for to be scoped as
"parent[object_attributes]"
other times I would like it to be
"parent's parent[parent][object_attributes]"
I have been passing it around to the partial using :locals, but it seems like there must be a better way.
Ryan - thanks for the great screencasts - I hope you keep them coming!
WARNING: Solution doesn't work with Edge Rails (2.0.3) when Task has nested fields! (Unless I'm not building the names of the inputs correctly... but I'm fairly certain I am!)
I've found this (Rails2) solution has problems when Task itself has nested fields - at least in Edge Rails (2.0.3). The request parameter parser won't parse the request into the new_task_attributes array correctly.
Example: Let's say Task has_one :person, and Person comprises fields 'first_name' and 'last_name'. You'd probably end up with a form input similar to the following (of course you'd choose what the actual name of the attribute 'person_attributes' would be):
project[new_task_attributes][][name]=taskname1
project[new_task_attributes][][person_attributes][first_name]=person1-first
project[new_task_attributes][][person_attributes][last_name]=person1-last
project[new_task_attributes][][name]=taskname2
project[new_task_attributes][][person_attributes][first_name]=person2-first
project[new_task_attributes][][person_attributes][last_name]=person2-last
Edge Rails (2.0.3) creates an very wrong parameters hash for this:
"project"=>
{ ...project-data...,
"new_task_attributes"=> [
{"person_attributes"=>{"first_name"=>"person1-first"}},
{"person_attributes"=>{"last_name"=>"person1-last"}, "name" => "taskname1"},
{"person_attributes"=>{"first_name"=>"person2-first"}},
{"person_attributes"=>{"last_name"=>"person2-last"}, "name" => "taskname2"}
],
}
Just a warning to folks out there who are trying this solution, but have nested fields as well.
While everything works, I am not able to update tasks using Firefox. I can in IE! I can create new records though! Any ideas what I need to check? Thanks.
In the revised example I found that I had to use the index option on my fields when I was specifying a to_param method for my equivalent Tasks model
Genius code--thanks so much!
That said--is the after_update callback absolutely necessary?
It looks to me like it works if you call task.save from within Project.existing_task_attributes= (right after the line "task.attributes = attributes").
Is that right?
Hello Ryan,
I have used this sublime recipe as the basis for a kicked-up-a-notch version, with a more complex database schema and it's working perfectly so far.
The point where I'm having problems is if, for instance you also have a Category model that has_many :tasks and you have to select which category the task :belongs_to before clicking the add_task_link.
What I have tried -unsuccessfully- to do is when you call page.inset_html, to pass in something like :object => Task.new(:category_id => what_should_i_put_here?), being (as you might have figured out) "what_should_i_put_here?" the main problem. I can get the category_id value from the form field, but I can not insert it within the page.insert_html method.
Thank you.
the codes didn't work for me well (updated version or no) and i guess i can blame the complexity or disorderliness of my project code. with the help of a friend, i was able to find a way around my predicament with these complex forms. i use collection_select instead of text_fields too. anyway, while my friend was a genius being able to figure out how to do the updating (edit or adding a new record) of multiple forms on the spot, he wasn't able to figure out how to delete records (plus he had somewhere to go to).
but wow, i still can't believe how i managed to figure out a very simple way to do it. it's a bit of simple common sense, actually.
i used the javascripty "remove" in conjunction with the server side's "destroy".
<% if task.new_record? %>
<%= link_to_function "Remove field", "$(this).up('.task').remove()" %>
<% else %>
<%= link_to 'Cancel task', task, :confirm => 'Are you sure?', :method => :delete %>
<% end %>
hope that will help anyone...
Ryan, i am getting an weird error when i combine the use of "fields_for" and "collection_select" at the partial code.
There's someone having the same issue, any tip?
http://railsforum.com/viewtopic.php?id=18598
The excerpt from the book on this topic was insanely helpful. Thanks for all the good work on this and other screencasts and reference materials. I've learned more about Rails from you than any other single source, hands down.
@Scott, and everyone fighting the checkbox problem with dup fields. I fixed your code. The else if in the javascript didnt run right and I found it more suitable to remove the name field of the hidden field rather than attempt to toggle its value--which did not seem to work.
My solution also allows you to still use check_box instead of check_box_tag. The only mod to the form is to add :onchange => "toggle_name(this);" to the checkbox form element. Then simply declare the toggle_name function in your appplication.js file as such:
http://pastie.org/214828
The code I posted does not appear to work properly under IE due to its nextSibling implementation. I'll try to find a more robust solution on monday.
This works great but I have a problem. My partial is an invoice item field with amount, price and total sum (and of course there can be many of these). I'd like the total sum to be calculated by Rails when amount or price is changed. Because the form fields have no unique id's, I cant' get this to work with remote_function.
It works if I calculate with JavaScript by traversing trough the DOM like so:
<% item_form.text_field :amount, :onkeyup =>
";$(this).up('.invoice_item').down('.sum').value=$(this).up('.invoice_item').down('.price').getValue()*$(this).getValue();" %>
But I can't use the $(this).up... thing in remote_function :update => ... and can't use RJS because there's no unique id for the fields.
Is there any solution for this?
I'm using Rails 2.1 and the updated code above and getting "undefined method `new_record?' for "1":String" when I update a record.
The error comes from this line in the #existing_task_attributes method:
tasks.reject(&:new_record?).each do |task|
All good. The error was mine.
Great article and a lifesaver BUT.. when you start doing things like time_select the custom forms_for_x is no longer rendered properly (namely the hidden fields generated by this time_select).. Not sure if this would effect other rails tags.. if so it's not as versatile as once thought and would have a limited use.
On rails 2.1 - updates I get:
can't convert String into Integer
app/models/poll.rb:47:in `[]'
app/models/poll.rb:47:in `existing_choice_attributes='
app/models/poll.rb:46:in `each'
app/models/poll.rb:46:in `existing_choice_attributes='
/Library/Ruby/Gems/1.8/gems/activerecord-2.1.0/lib/active_record/base.rb:2361:in `send'
/Library/Ruby/Gems/1.8/gems/activerecord-2.1.0/lib/active_record/base.rb:2361:in `attributes='
/Library/Ruby/Gems/1.8/gems/activerecord-2.1.0/lib/active_record/base.rb:2360:in `each'
/Library/Ruby/Gems/1.8/gems/activerecord-2.1.0/lib/active_record/base.rb:2360:in `attributes='
/Library/Ruby/Gems/1.8/gems/activerecord-2.1.0/lib/active_record/base.rb:2261:in `update_attributes'
If I update existing_choice_attributes's choice.id.to_s to choice.id it works, but will destroy my choices when updating the same form twice.
BUG: In case number of attributes is more than one in the partial, browser does not groups it right and sends wrong data.
Example:
Data expected :
new_part_attributes[]["ordering"]=>"1",
new_part_attributes[]["inline"]
=> "false"
new_part_attributes[]["wfile"]=>#<ActionController::UploadedStringIO:0xb602ea20>
new_part_attributes[]["ordering"]=>"2"
new_part_attributes[]["inline"]=>"true"
new_part_attributes[]=><ActionController::UploadedStringIO:0xb602cd38>
Data Send By Browser:
"new_part_attributes"=>[{"ordering"=>"1", "inline"=>"false", "wfile"=>#<ActionController::UploadedStringIO:0xb602ea20>}, {"ordering"=>"2", "inline"=>"true"}, {"wfile"=>#<ActionController::UploadedStringIO:0xb602cd38>}]
Possible solution is send index also in the array, so that it groups it right.
And also, what about doing the same thing for Gmail type file upload ?
In that case, your previous code, in which you used to set should_destroy? would work better.
I get the same "can't convert String into Integer" error as John (3 posts above)
Any help would be much appreciated.
Thanks
Umm, found the solution to the problem above.
Looks like I forgot to remove the ":index => nil" part from the task helper
Ok, so I bought the book, and keyed in the example except I changed the names of tables to fit my project. It didn't work. I beat around the bushes for a couple of days, but couldn't find my bug.
Then I keyed it in using the exact line for line coding from the bug. Still same problem.
Then I found this page, and downloaded the code.
After adding a named route for projects (the code does not have it - I had to figure this part out), it still doesn't work. I get no errors, just neither my project nor my tasks ever get saved.
Has anyone else actually got any of this to work? I mean I would hate to be completely wasting my time - I could code it the hard way in far less time than I have already wasted here.
I am pretty sure my problem is in that routing thing that is not provided in the code, railscast or the book. Perhaps the version he uses has different defaults for routing or something. Anyhow, I have wasted 5 good coding days on what looked like a good idea, but just won't work for me at all - and the logs give no clues that I can use.
Ok, so I got it working by mashing together some code from above into the code in the book, and replacing all the instances of project_path with actions and ids as appropriate in my controllers and views.
@Dominic: I (after a bit of futsing with mistyped fields) have indeed gotten this to work for the simple model used in the railscast. What version of Rails are you running?
@All: While implementing this technique with my own application, I got through the first and second casts in this series without problem, but when I went through the steps in this one, I now get the following in my log:
/!\ FAILSAFE /!\ Tue Jul 22 15:49:37 -0700 2008
Status: 500 Internal Server Error
Conflicting types for parameter containers. Expected an instance of Hash but found an instance of Array. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value.
I'm gonna keep digging and see if I can figure out why something is getting sent an array that is supposed to be a hash (my guess is that it is the params hash), but I figured I would drop a line and see if anyone could quickly spot my problem here. I'm still pretty new to Rails, so any help is appreciated.
Thanks
@Dominic: I started making my last post before you made yours, so I didn't see that as I wrote. Sounds like you are good to go though.
So, has anyone effectively made this work with compound models?
EX. - Where a Project can have multiple Tasks, which can have multiple Milestones, which can have multiple Assignees,...
I seem to have everything working other than getting the parameter names to be correctly parented.
I've thought of several solutions such as passing into each partial a 'parent_attr' local which (if not nil) would be prepended to the field names. But I don't think this is the most elegant method.
any other suggestions would be greatly appreciated.
I fixed my issue - it was with my method of calling a collection_select in my form. I had :index => '' under my html options. I think I tried this at some point while I was trying to get thing to work in part II, but found another solution and failed to take out that bit (Incidentally, the solution which worked was calling the collection_select directly on the task_form, which I had trouble doing at first because I didn't realize that when you did that you had to drop what would have otherwise been the first argument, task, in this case). So anyway, I think I'm good to go.
@glennpow: I think I'm going to have to do what you are trying to accomplish as well, so I will share any good solution I may come up with - I would also be grateful if you did likewise.
Cheers
@mellifluidic et al.
So with the latest edge rails, there's a new feature for mass-assigning hash attributes, which uses an ":accessible => true" parameter appended to any has_one, has_many, etc in the model file.
http://github.com/rails/rails/commit/e0750d6a5c7f621e4ca12205137c0b135cab444a
I think this is similar to the "attr_accessible" method used before in the user.rb, but this also seems to allow these sort of forms to be created:
<% form_for @user do |f| %>
<%= f.text_field :login %>
<% fields_for :phone_numbers do |pn_f| %>
<%= pn_f.text_field :area_code %>
<%= pn_f.text_field :number %>
<% end %>
<%= submit_tag %>
<% end %>
So you don't have to mess around with "[new_phone_attributes]" or any custom attribute setter methods. This is GREAT!
I've only just learned about this, as it's very new, but I'll try it out and post any findings.
One thing that I already see as a possible snag is the use of AJAX-updated selects (for instance) in any of these nested resource form-segments.
EX. If I have a user with N number of addresses, where each address form has a country_id SELECT with a dynamically-updated region_id SELECT, then there will probably be conflicts when one country SELECT tries to update it's (and only it's) region_id SELECT.
hm, anyways. I'll be back.
Anyone know how to use text_field_with_auto_complete in place of f.text_field in the partial?
Using Ryan's example:
<%= text_field_with_auto_complete :task_attributes, :name, :object => task %>
Because it's a partial, the id and name attributes are duplicated, so the autocomplete only works on the first instance of the partial, not to mention it's invalid XHTML to have dupe id's.
Thanks,
Mark
@glennpow (et al.):
Awesome! Looks sweet. I was searching around on Rails Forums for solutions after just sort of winging it, and came across the following thread: http://railsforum.com/viewtopic.php?id=14905.
It looks like the attribute-fu plugin tidies things up similarly. When I get a chance I'll play around with both methods and see what I can see.
I should mention though that I've got an even more complicated situation - the kind of fields that I use for each subtasks depending on what kind of subtask you are dealing with. So imagine that either the task or the subtask has a select which when selected renders the appropriate fields for that selection. I'm gonna take this as it comes, but it's down the road for me so I'm keeping an eye on it.
Has anyone tested this in IE7? IE7 does not seem to like the prototype calls to remove the element from the form IF it was added via "Add a task" or other javascript call. Works fine in IE6 and FF (and Safari I'd imagine)
Any ideas?
I'm wondering if I did something stupid (I did make some changes to fit it into my app), since nobody else has mentioned this problem, but my tests (luckily) turned up a nasty bug when I implemented this solution (which is fantastic otherwise, btw!)
The problem comes from existing_task_attributes=. Since that runs before the parent model (Project) is validated, existing children (Tasks) will be changed/deleted in the DB even if the parent model fails validation. I haven't actually tried Ryan's Project/Task code myself, but in my own app this would allow these sorts of scenarios:
1. Delete all children on edit page, but parent requires children (here if a Project required 1 or more tasks). Parent fails validation and goes back to edit page, but children have already been deleted. If you cancel back out of edit, children are gone from DB even though parent never updated.
2. Delete all existing children, and add new one that is invalid. (Say a task requires a name and you don't add one, so that child fails validation.) Parent will fail validation if it validates associated records and will go back to edit page, but existing records will have been deleted. Again, if you cancel back out of edit, children are gone from DB even though parent never updated.
3. Modify existing child(ren), and add new one that is invalid. Like above, parent validation fails, but child is still updated.
Is this just a quirk of my app, or a bug nobody has hit/noticed/commented on?
My fix was just to wrap a transaction around the update_attributes call (and make it update_attributes! so it would throw an exception on failure) and move the else clause to a rescue block. This way, if the parent model validation fails all the other changes to the children are rolled back as well. Tested and it works, though check out my blog post for a gotcha about testing Rails transactions: http://freevirusesandspyware.com/2008/08/testing-rails-transactions.html
Is there a way to count the number of times you have clicked on "Add a task" without using Javascript? I'm using a radio button in the partial and need a way to distinguish if one of the added partials contains the radio button that was the selected.
the default date_select will break this quite spectacularly. I dont have a workaround yet but want to give a heads up.
Yes, I'm having issues with this method and a date_select as well. When submitting the form it throws a:
Status: 500 Internal Server Error
Conflicting types for parameter containers. Expected an instance of Array but found an instance of Hash. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were {"date1(1i)"=>"2008", "date1(2i)"=>"8", "date1(3i)"=>"15", "date2(1i)"=>"2008", "date2(2i)"=>"8", "date2(3i)"=>"15"}.)
Any ideas?
In my application I would like to automatically number my tasks and display the number on the page. So far all of my attempts have resulted in the same number for all lines after the first, e.g. 1, 2, 2, 2.
In my task model:
@@line_no = 1
def initialize(attributes = nil)
super
unless line_no?
self.line_no = @@line_no
@@line_no += 1
end
end
Ideas?
Similar problem like Tim: how to (auto-)number the tasks? I don't mind if on remove it isn't actually shown correct, but after commit the task should be written correctly (1, 2, 3, ...) to the database.
I would really like to see a screencast covering those methods but with a has_many :through, cleaning associated records and then rebuild them in a callback would be an example.
Thank you Ryan for your casts.
That was what i was looking for, where I have one problem to enhance it a little bit for some dynamic usage with radio buttons.
I use a different model, but to make it easier to understand it I'll use the task and project model to explain my problem.
I use select instead of a text_field in _task.erb, which works. But the database query should depend on a radio button where I ask which type should be used.
The reason for that is that a task database entry uses two fields to point to two other tables.
Here is the _task.erb code which I try to use:
<tr class="task">
<% fields_for_task(task) do |task_form| %>
<td id="radio">
Task Low<%= radio_button_tag('type', 0, checked = true) %><br>
Task High<%= radio_button_tag('type', 1, checked = false) %>
</td>
<td id="select">
<%= task_form.select :todo_id, Todo.find(:all, :conditions => "tasklow_id is not NULL").collect {|p| [ "#{p.tasklow.title}", p.id ] }, { :include_blank => true } %>
</td>
<td><%= link_to_function "remove", "$(this).up('.slot').remove()" %></td>
<% end %>
</tr>
The dynamic part is to use either (tasklow_id and p.tasklow.title) or (taskhigh_id and p.taskhigh.title). While defaulting to tasklow.
Btw, one of tasklow_id or taskhigh_id is NULL and the other one has a value.
Refactoring the select code to a partial could be a way to go and use onclick for the radio button, but I have no idea how to avoid getting many select fields and how to get the initial select box since the onclick works only onclick.
Can someone post their working validation example? Actually, I have it all working, EXCEPT for the extra parent level messages of "Tasks is invalid" (in my case not Tasks, but you get the idea.) I don't want these messages since I have the correct task error messages following the main project messages.
I know some others earlier in this thread have mentioned the same thing, but I'm looking for what people have done about it. Does someone have a snippet of code that shows the best way to handle this? I would think it would just involve iterating over the errors and removing the messages I don't want? I'd like to see the best way to handle this, though.
Ryan,
These three screencasts really pack a punch! thanks a lot for this series especially. There is one small glitch i am running into though.. hoping someone can point out the cause of the bug..
i have a complex form with fields from 3 models in the edit and new templates. 2 of them work just fine. in the fields from the 3rd model, there is a collection_select field where i get the following error
/!\ FAILSAFE /!\ Tue Sep 09 10:07:52 +0530 2008
Status: 500 Internal Server Error
Conflicting types for parameter containers. Expected an instance of Array but found an instance of Hash. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were {"1"=>{"days"=>"7"}}.)
i have set ':index => nil' in the code.. here is my code for that field
<p><label for="frequency">Frequency (in days)</label><br/>
<%= h.collection_select :days, Dates.find(:all), :no_of_days, :no_of_days, :index=>nil %></p>
every other field has ':index => nil' in place and the html code generates fine. any thoughts?
yourstruly_vinay: I think if you use :index => "" instead of nil, it'll work. A quirk in collection_select.
Now my problem:
I'm in the same boat as glennpow and others in that I'm befuddled by compound model creation: I can get two models to parent correctly as in the screencasts, but if I try to get to a third level, no soap.
My situation:
I have an Invite model that is a proxy for new (mass assignment) User creation:
(invite) has_many => :users
(user) belongs_to => :invite
This works fine, as in Project/Task creation in the screencasts. However, I also want to have a new Group Membership created for each of the (new) Users created in the same transaction (all Invites are scoped to a Group):
(invite) has_many => :memberships
(user/group) has_many :groups/:users, :through => :memberships
(group) has_many :invites
I can generate everything fine with two sets of virtual attributes (for the User and the Membership), EXCEPT the (new) User id for the Membership (If I try to hardwire something like :user_id => @invite.users.id, I get an id like '1546789'. I assume this is because I'm not nesting the Membership creation within the User creation within the Invite. Or maybe I'm just being a dopey newbie....
If anyone has successfully done a multi-level, multi-model form, I'd love to see the code.
Thanks for your ideas.
@Tony V: thanks for the reply. Although, i found out that :index has to be passed with the html_options for collection_select and its family of form helpers.
Ref: http://dev.rubyonrails.org/ticket/589
I am getting another error that noone else seems to be getting, oddly enough. i am getting an "ActiveRecord::ReadOnlyRecord" error on the save_tasks method. It is raised because when we do tasks.each, the record is instantiated as a read only. (Ref: http://api.rubyonrails.com/classes/ActiveRecord/ReadOnlyRecord.html). The solution is here - http://learn.theworkinggroup.ca/2007/2/6/activerecord-readonlyrecord-error-what. However, im not sure if its great programming because of the many queries raised. Any work arounds? hope that was helpful for someone.
I did this for an HABTM association. The join table records are not getting deleted on removing the child records. Everything else is fine though. So it is just the clutter in the join table that has to be removed. Just wondering if Pascal, pimpmaster or any other managed a solution to that..
@chrisff (post 51) and @Kevin Triplett (post 114)
I was running into the following problem.
I had a page and the association was files. You could upload numerous files by clicking on the "Add file" link.
If I only had one file the validation code worked fine. If I had multiple it was running through the association and adding the errors to the base for the page model for every instance of the field.
For example. If I had a new page with 1 file (file required) and I didn't upload the file I would get 1 message saying:
File: no file uploaded
If I had two instances of the file added and I didn't upload the file I was getting 4 errors:
File: no file uploaded.
This is because 2 fields with the name :file were getting passed in so it was running the association validations twice so 2 errors each time yielded 4 errors for file.
I added the code to store and array of each field the the validation was run on and push the name of the field into the array after each. They use a statement to check if the name of the field is already in the array and not run the association validations if this is the case. This eliminated the extra validations on the association field. See the pastie:
http://pastie.org/271260
If anyone has a better way please respond.
chrisff your code was the basis and Kevin your error patch to remove the "'Association' is invalid" was a huge help.
I have used both of your pasties and modified them slightly for the error validation.
~ Tom
Rick-
See my post here for info on how I solved the duplicate validation message problem:
http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/df8f450149b71b1/3f6aea404f962b30?lnk=gst&q=hagius#3f6aea404f962b30
Thanks Walker. I took a slightly modified approach of what you did. I replied in the google group (charlesrc) to your message:
http://groups.google.com/group/rubyonrails-talk/msg/163ada44b0efb1a3
I got to try out Michael's multimodel-forms plugin. It works quite well.
I also had to add radio buttons to my stuff and I used Sarah's solution.. I feel this is a much better solution to the problem than using javascript as it is more difficult to validate. Unfortunately right now I've only done this hacked into Michael's plugin and it's not very DRY:
in_attributes.each_with_index do |attributes, j|
# I just added these two lines
attributes["correct_answer"] = attributes.include?("correct_answer_id") ? 1 : 0
attributes.delete("correct_answer_id")
Good stuff guys, it's a big help.
As for validation - any thoughts on server-side validation to make sure there are a certain number of associated (belongs_to) records at all times? So for example you can only make a Book that has at least two Chapters. Or what about something like at least one Chapter has to be flagged (flag column) as "introduction"?
Also wrt my last comment - I used radio buttons, but mine don't require multiple radios per record, only one because they're selecting one of the records to flag. I got lucky in that what I wanted to do fit the broken situation.
Didn't want anyone to think it was a solution for radio buttons where you need to define a set for each record.
How does this work if you have a has_one relationship? Say, for example, Project has_one :billing_detail, where BillingDetail belongs_to :project?
I'd like to present the project and it's billing details in one form for new/edit.
I have created a plugin that implements this functionality. Please check it out here: http://github.com/sixty4bit/acts_as_virtual_attribute
I used the older should_destroy? method instead of creating a blank array in the container.
Why did I create this plugin? Because my current project has 50 of these on one model.
Ahoy,
First of all, thanks Ryan, you're still my #1 RubyHero :D
I've create a much simpler version of Ryan's example with a single select box and a list of checkboxes to store the associations.
Also uses client-side javascript and the code is less, without hacks.
Take a look at: http://clair.ro/rc/2008/10/13/has_many-with-select-box-and-checkboxes/
first, thanks ryan!
for those who get trouble with check_box adding more than rigth rows, due to unchecked value hidden field, my work arround was:
-at the task_attributes method setter loop (at project.rb model), I changed the "tasks.build(attributes)" line, to "tasks.build(attributes) unless attributes.values.length == 1", where attributes.values.length represents the quantity of fields for each task sent by the form.
I took the idea from http://railsforum.com/viewtopic.php?id=1063
I have also having the problem where the params hash isn't properly created for attribues. I have the following example:
<input type='hidden' name='t[test][][1]' value='1'>
<input type='hidden' name='t[test][][2]' value='2'>
<input type='hidden' name='t[test][][3]' value='3'>
<input type='hidden' name='t[test][][1]' value='4'>
<input type='hidden' name='t[test][][2]' value='5'>
<input type='hidden' name='t[test][][3]' value='6'>
<input type='hidden' name='t[test][][1]' value='7'>
<input type='hidden' name='t[test][][2]' value='8'>
<input type='hidden' name='t[test][][3]' value='9'>
which creates the following params[:t][:test] => [{"1"=>"1"}, {"1"=>"4"}, {"1"=>"7", "2"=>"2"}, {"2"=>"5"}, {"2"=>"8", "3"=>"3"}, {"3"=>"6"}, {"3"=>"9"}]
As you can see the grouping isn't being properly assigned - i.e. params[:t][:test][0] should be { :1 => "1", :2 => "2", :3 => "3" }
Any advice at all on this is great appreciated.
I'm having problems with the code in this line: "attributes = task_attributes[task.id.to_s]". I Keep getting the error "can't convert String into Integer" if I take out the to_s method it goes away, but the existing tasks get deleted. Anyone ideas why?
Ok...found what was MY problem...
Note to self: Adapt the code to your liking!
Uninstalling and reinstalling rails fixed my problem of the grouping of the dynamic files.
I'm running into a weird problem I can't seem to figure out. I've got the code from the book working per se. However, no matter how many new attribute fields I add via the RJS link (in my case, it is "Add Another Child"), only the child entered into the original fields (i.e.: the first Child/Task field presented on load) is sent back to the controller. None of the rest are sent. If you click "Delete" and delete all the fields, then add one more back, the data in that new field isn't saved either, so it seems that for some reason only the original field is saved.
Strangely, after inspecting the generated source, it all looks fine and as if it has been compiled correctly. The HTML generated by the original view (with the initial child/task) is identical to the HTML generated when you delete the first child/task field and immediately add another one back, yet the latter doesn't send anything back to the controller.
Blows my mind, anyone else seen similar problems?
I am getting a
syntax error, unexpected '='
at params[:project][:existing_task_attributes] ||= {}
Can you help me out?
Sorry the problem got fixed. It was a silly mistake. I had a space between || and =.
Great example Ryan!
Here is an elegant solution for the problems caused by array and hash index clashed (also works with date_select)
http://gist.github.com/22824
My solution is only tested in rails 2.1..
Hi Ryan,
Excellent tutorial! I have a problem regarding has_many and the dependent => :destroy command. I have two models, one for an Item and another for Variation but when I delete my item the variations connected to it isn't deleted, even though I have the :dependent => :destroy attribute set.
A short preview can be found at http://pastie.org/327247.
I can get my variations when I view the item, I can update and everything. It's just when I want to delete the Item. Any suggestions?
Kind regards,
Niklas
Hi Ryan,
Is there a way to monitor the task in the "new" event, and if it already exists select the existing id rather than creating a new one. That would be awesome, but I'm stumped, and I'm a noob.
Thanks,
Chris
Ubercool! The new code from Advance Rails Recipes works fantastic. (Rails 2.2.2)
Hi all,
I have a relationship many to many between two objects "factura" and "factura_producto". I've implemented this code to update many record into "factura_producto" table.
This send the following error:
Rendered orden/_factura_producto (0.00848)
Rendered orden/_factura_producto (0.00238)
Rendered orden/_factura_producto (0.00243)
Rendered orden/_factura_producto (0.00614)
Rendered orden/_factura_producto (0.00221)
Rendered orden/_factura_producto (0.00219)
Rendered orden/_factura_producto (0.00788)
Rendered orden/_factura_producto (0.00254)
Rendered orden/_factura_producto (0.00239)
Rendered orden/_factura_producto (0.00194)
Rendered orden/_fields (0.13827)
Completed in 0.21036 (4 reqs/sec) | Rendering: 0.14042 (66%) | DB: 0.03088 (14%) | 200 OK [http://localhost/orden/edit/1]
Conflicting types for parameter containers. Expected an instance of Hash, but found an instance of Array. This can be caused by passing Array and Hash based paramters qs[]=value&qs[key]=value.
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/cgi_ext/cgi_methods.rb:204:in `type_conflict!'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/cgi_ext/cgi_methods.rb:168:in `container'
great information!
Is this still the proper way to do nested forms?
Does it matter how many levels your nested form is?
I've been using this method for a while, but I think I've found a problem with it.
If you delete all of the tasks in one fell swoop, by the looks of development.log the existing_task_attributes set of fields isn't serialised at all. This means that when update_attributes is called, the existing_task_attributes= method in the Project model isn't called, hence none of the tasks are removed.
This seems a little crazy, but the problem is very real for me!
Things become really complicated when trying to add an autocomplete functionality to a dynamic form field. I started a thread here http://railsforum.com/viewtopic.php?pid=83963#p83963
I have a problem.
When I try to update the tasks, It's not updated but it's inserted in new row in database.
What's wrong?
I am getting a syntax error in task.rhtml: "undefined local variable or method `task' for #<ActionView::Base:0xb692bbf0>"
Why?
@Luvi c.198
Check to make sure the form for on your edit page is this:
- form_for :item, :url => item_path(@item), :html => { :method => 'put' } do |f|
Otherwise it will call the create action instead of the update action.
I have multiple models which I want to create. I have products and categories. The categories work like the tasks Ryan goes through in this episode, but I want products as well to be dynamically added and removed so in the partial for the category i added another partial for a product and I added helpers and extended the model.
However when I click the add category button then I get an error: missing } after property list
http://localhost:3000/shops/new#
Line 2
The buttons for removing and adding products works fine. If I remove the helpers for products then the category buttons works fine again.
Help!
Anyone who has had success using this with multiple models?
As usual, great cast.
One question, is it possible to do something similar with a show form and 2 sub models. i.e show a projext and be able to add tasks as well as entries to a projectlog.
I tried simply adding a form to a show view to create the extra model but seem to have much success
any suggestions would be great
Michael
Great cast, and an even better update :). Thanks for that!
I'm currently struggeling with the following problem though:
If you delete a task but then have a failing validation on update (e.g. an empty project name) the task is deleted anyways.
Maybe not a big issue but inconsistant with the transaction model ("all-or-nothing").
The reason for this to happen is quite obvious if you watch the logs: the sql-DELETE is encapsulated by an individual BEGIN-COMMIT frame.
Is there any way to avoid that?
Thanks!
Rainer
Ryan, nice episode, many thanks!
I've added a checkbox to the tasks to mark them if they're done. Unfortunately, every time i mark a task as completed, it will be duplicated and the next task has the same name but becomes completed. If i hit save again, the procedure repeats again.
Any idea where the error could be? The whole code code is 1:1 the same as yours, except my addition
<%= tasks_form.check_box :is_completed, :index => nil %> is completed
Any suggestions would be great, thanks!
Pascal
Great episode! Thank you so much.
Has anybody used this in combination with cucumber and webrat for testing?
I would like to use wabrats fill_in method to fill in two or more tasks of the project (as in Ryan's example). However, as those fields are generated from the same partial they all have the same name and id.
Has anybody found a way to tell webrat to use the second or third field with a certain name/id?
Thanks for your inspirations!
Björn
Absolute wizardry :) Thank you!
Has anyone solved the collection_select issue. If so I'd love some help. I keep getting the errors described by @joe above.
if I enter a {} for the html options I get a nil object when evaluating nil.attributes=
w/o the {} in my collection_select I get a 500 error... Conflicting types for parameter containers. Expected an instance of Array but found an instance of Hash.
I'd be interested to know how one might allow a user to create multiple projects AND multiple tasks on one page with jscript. I couldn't find a rails only method for keeping each new project separate from one another. Right now I'm using a jscript iterator for each new project created that creates a new 'row' in a multi-dimensional array. Works but seems like a hack method. I'd much prefer a method that doesn't require javascript for separating new projects from each other.
Hi,
Thanks! for the screencast, it was very helpful.
Recently i came across the same issue with date_select and time_select that @Erik, @Tim and many others are facing.
Status: 500 Internal Server Error
Conflicting types for parameter containers. Expected an instance of Hash but found an instance of Array. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value.
Here is the workaround for the issue:
http://agilerails.wordpress.com/2009/03/11/date_select-time_select-doesnt-work-with-auto_prefix-object/
@Hemant Just used that workaround for date_select. Works perfectly on 2.2.2.
Hello Ryan,
Great screencast!
I creating a simple shopping cart, where you can add / remove itens, but since the field have the same name because :index => nil, the observe_field I have is not working.
Does anyone have some tip on using oberve_field here?
Thanks!
Luciano
Hello Ryan,
thx for your great work.
I try to follow your cast to use multiple model update in a single form.
But i have a problem and don't find any solution.
I have validation for the model let's say the primary. In your example in the project model.
When a fill up badly the form at the first time (i have the correct error message) and i correct it afterward, I have strange thing in the database.
Let second model (task in your example) has no reference to the first model. Project_id is NULL
I have look in more detail what 's happen. And when i have the validation error at the first time, the task object are created in the DB and at the second time, i have alreeady the object in the DB, thus this isn't new_mouton_attributes but existing_mouton_attributes that is used!
Can you help me, i can send you my code if you want
thank in advance
Raphael
Thank you! you helped me a lot!
Hi Ryan.
First thanks for a great episode.
Is it possible to create a nested form building children model who themselves have a has_many relationship with their own children and successfully implemented this on the one form. Further to this, I am trying to implement this using ajax and wondered (again: if this is even possible?) how to pass the formbuilder object back to my partial.
One other quick one: I noticed that to date their seems to be only javascript workarounds when adding more than one child in the 2.3.2 accepts_nested_attributes_for. Are you aware of any work/changes pending to resolve this?
Appreciate all your work
Michael
Found a way that appears to work which basically is a hybrid of the pre 2.3 way where I maintain the :index=>nil attribute and the 2.3 methodology. I also gave up on the form_builder and constructed the naming of the fields by hand circumventing the need to pass form builder objects either locally or by ajax.
Cheers
Michael
Hi, I wanted to share my solution for the checkboxes problem.
I simply use check_box_tag. For new:
<%= check_box_tag("project[task_attributes][][completed]", "1", false) %>
For edit:
<%= check_box_tag("project[task_attributes][][completed]", "1", task.completed == 1) %>
The "create" method does not change. If the checkbox is checked, then saves the "1" to the DB. In case the checkbox is not checked, I solve it with a default value of "0" in the DB.
In the case of the "update" method, what I do is iterate through the params[:task] array and check the value of the task_attribute:
params[:project][:task_attributes].each do |task|
if task[:completed] != "1"
task[:completed] = "0"
end
end
I think it is the simplest and cleanest solution I have found.
Regards,
Marcelo
That checkbox problem was a bear to get working for me. check_box_tag wouldn't work for updating and check_box wouldn't work for new tasks.
Here was my solution:
# in the partial
<% if @prefix == "new" %>
<%= check_box_tag("project[# {@prefix}_task_attributes][][some_value]", "1") %>
<% else %>
<%= sf_form.check_box 'some_value', {}, "1", "0" %>
<% end %>
# in the helper change prefix to @prefix
Thanks for the info Ryan! I am glad that in later railscasts the example code is available for download. For those who are trying to get this to work today, you can download example code from my google code repo at:
https://gone-cat-open-source-software.googlecode.com/svn/tags/herbs-test-railscast-75
A couple of differences:
-- changed the application concept a little to fit what I am trying to build. The structure is almost identical but I changed the names of the models.
-- replaced prototype with jQuery so the javascript is a little different from what you find in the cast.
-- this example code was build on rails 2.3.2.
Hi!!!
I want downloadable code of this episode.
Tyler Gannon your said URL is password protected
Use http to see Tyler's code.
http://gone-cat-open-source-software.googlecode.com/svn/tags/herbs-test-railscast-75
I am having some conflicts between jquery and setting defaults in javascript_include_tag. Does anyone know how Tyler converted the railscast prototype javascript to use jquery instead?
Hi Ryan, This is a grt screencast. I tried it out and everything works besides one thing, which is adding new task. I can edit the task, i can delete the task but i can not add a new task. All the code is the same as shown in your screencast...any reason why?
This is great! Exactly the use case I was battling with. You saved my day definitely.
Hi Ryan,
Thanks for the great complex form railscasts! Do you have plan to update them for 2.3 with Nested Model Forms?
Thanks a lot Ryan for the highlighting the hidden secrets of Rails ActiveRecord and all others. Mass assignment made easy via params[:project][existing_task_attributes][]...
A tip for someone who might be hunting for Mass assignment in a belongs to many through a third connecting table Scenario -
Proposal can have many Sections via Proposalsections.
Decided by a checkbox to create/delete a new/existing Proposalsection record for a Proposal.
----
code for view - with checkboxes
for openingsection in @sections
<li class="topItem clear">
<%@pso=Proposalsection.find_by_proposal_id_and_section_id(@proposal.id, openingsection.id)%>
<% if @pso.nil? %>
<%= check_box_tag "proposal[proposalsection_attributes][][section_id]", openingsection.id %>
<% else %>
<%= check_box_tag "proposal[existing_proposalsection_attributes][#{@pso.id}][section_id]", openingsection.id, :checked=>true %>
<% end %>
<%= openingsection.name %></li>
end
-----
code for controller - saving Proposalsections with Proposal fields
params[:proposal][:existing_proposalsection_attributes] ||= {}
@proposal=Proposal.find(params[:id])
@proposal.update_attributes(params[:proposal])
end
It worked for me after wasting full day tweaking over it.
code for my last post seperately -
for view -
<script src='http://pastie.org/521362.js'></script>
for controller -
<script src='http://pastie.org/521366.js'></script>
Nitin, can you clarify where the opensection.id is defined? Is it part of 'ProposalSections' or 'Proposals'?
Thanks.
This may be just what I need to solve a similar membership type challenge i'm having with my view.
Foreign ID Question:
I am having the hardest time figuring out why the foreign ID field is not being automatically populated in my site, which I've based my form off of Ryan's spectacular tutorial (thanks Ryan). From what I understand, the foreign id is put into the task when the build method is called in the line "@project.tasks.build". Then the foreign id is saved in the Project model under the "existing_task_attributes" method. Is this all correct or is the foreign id populated in some other way?
Thanks,
Thomas
Its a big arm pump finally getting the hang of all this. Thanks for the cast, as well as the recipe entry about this!
Just to the poster before me (as I was doing it with selects), here is a good tutorial.
check out http://guides.rubyonrails.org/form_helpers.html#making-select-boxes-with-ease
Ryan,
Thanks for all your screencasts! They are lifesavers!
I am really having trouble with the updating of attributes. The build of a new project (routinglist) works great...but I get the error "'can't convert String to Integer" in the Existing_Detail_Attributes= method. the creation of a new RoutingList with Details works fine, it is only when I try to update the RoutingList and save the attributes. If I change detail.id.to_s to just detail.id, I get no errors, but it deletes all my details.
Any ideas? BTW I am running this on Rails 2.0.2 (I upgraded to 2.3.3 but I never could get Mongrel to start, so I am still using 2.0.2)
Here are the 2 relevant methods in the model (detail.id=13):
def new_detail_attributes=(detail_attributes)
detail_attributes.each do |attributes|
routlist_details.build(attributes)
end
end
def existing_detail_attributes=(detail_attributes)
routlist_details.reject(&:new_record?).each do |detail|
logger.info 'detail='+detail.to_yaml
attributes = detail_attributes[detail.id.to_s]
if attributes
detail.attributes = attributes
else
routlist_details.delete(detail)
end
end
end
Thanks - Frank
I'm struggling with multiple radio box groups. I don't want (ideally) to solve this problem with javascript if I can help it. Has anyone figured a way to get multiple radio button groups working without JS?
Thanks!
how can I define dafault values for task, for example
Hi Ryan,
in terms of the addition of
params[:project][:existing_task_attributes] ||= {}
in the controller (update), this was causing a problem for me, so I removed it and all works fine.
However, by removing this, would you see any implications? I know you use if when checking for existing attributes (of course), but in my case I have a nested model where the child model is created when the parent model is created and can contain blank field. Therefore, it doesn't matter about checking for new or current attributes...I simply have attributes whatever.
As such, what is the purpose of this line of code?
Many thanks,
Doc
@chrisff (post 51) and @Kevin Triplett (post 114)
I need to create custom validate message. I have a association model which has 2 associations .
For example i have list of companies as company_profile.rb
has_many:group_supported_school
In group_supported_school
i have two fields school_id and company_profile_id
associations are
belongs_to:company_profile
belongs_to:school
i need to validate the school_id here which is valid or not.
Its working fine but validation saying as default
Group supported school is invalid .
can u suggest me the way to do
This is great! Just what I needed! One comment - this doesn't work so well with a datetime_select:
# projects_helper.rb
def fields_for_task(task, &block)
prefix = task.new_record? ? 'new' : 'existing'
fields_for("project[#{prefix}_task_attributes][]", task, &block)
end
Results in:
/!\ FAILSAFE /!\ Thu Oct 01 14:11:41 -0400 2009
Status: 500 Internal Server Error
expected Hash (got Array) for param `new_broadcast_attributes'
I had to hack it to look something like this:
def fields_for_task(task, &block)
projectattributes = task.new_record? ? 'new_task_attributes][][' : 'existing_task_attributes]['
fields_for("event[#{projectattributes}]", task, &block)
end
Datetime_select on a new project would fill in the empty [] with its own data. This wasn't a problem on an edit since the [] contained a task ID.
When I click the destroy link I get the following error:
TypeError: Result of expression '$(element).next('.should_destroy')' [undefined] is not an object.
Have double checked the code, and all other aspects work. Any thoughts?
I am not able to get validation message on update. Kindly help me in this...
Hi, thanks for great tutorials ! I tried build complex form for habtm associtions, but I get this error: ActiveRecord::ReadOnlyRecord. Please can you help me and describe me how it works and how to fix it? Many thanks.
Solved :D. I overwrite readonly? method in controller so:
def readonly?
false
end
This tutorial is great and ive got it 90% working with my app. everything is happening except the Delete part.
The Javascript works up to where it hides the Task input box but it doesnt set the should_destroy value ... if i inspect the Params, its always null..
any ideas?
Hi Ryan,
I got this error
undefined method `reflect_on_association' for NilClass:Class
on my application helper, AFAIK reflect_on_association is Rails method, please help
thanks
I used this approach with remote form. The hash that's created when the form is posted (non-Ajax) is different from the Ajax (Form.serialize). The ajax hash does not create the desired hash. Instead of creating
[{:a=>1, :b=>2}, {:a=>3, :b=>4}]
it creates
[{:a=>1}, {:b=>2}, {:a=>3}, {:b=>4}]
Any solutions here? Is this a known bug with rails?
testing comment
Excellent railscast Ryan.
If I has several tasks, say 50 or more, I don't want to add a single text field but search for a particular task. Any ideas in which direction I should go?
Many thanks,
John
Hi Ryan,
First,thanks for all your great videos.
I have get an error on the video tutorial, I have the ActiveRecord::ReadOnlyRecord error when I try to edit a project, and I can't figure out how to correct it :/
Best,
Greg
Just to say a big thank you and to report that the code works great on Rails 2.2.2.
Thanks again.
Very useful Screencast, but I have one question.
I have implemented a similar situation, however, I have a "Copy" facility. In your domain, this would create a new Project based on an existing one, and new Tasks based on existing ones too.
However, I can create a new Project, but not new Tasks - how do I achieve this?
In answer to my own question, I think the key is this line of code:
tasks.reject(&:new_record?).each do |task|
I don't want to throw away any tasks that are already there, but add them to the database instead, with new keys.
Any ideas?
hi
i have tried the same code ..when i am tried to add a new task its showing
RJS error:
TypeError: Element.insert is not a function
I have added the <%= javascript_include_tag :defaults %> in layout
files also
can you guys help on this ...
I just got this to work in Rails 2.3.8 and Ruby 1.9 using the updated code on this page, but I had to change one thing, and I wanted to share my solution. I had to add a 'project_id' column to my tasks table. Until I added this column, I could create a new Project, but not edit it, and this was the reason. If you're getting an error saying 'no such column... task.project_id' then try this. Thanks for the amazing work Ryan!
Those new to this episode, like myself, should check out Ryan's updated GitHub repo for this episode: https://github.com/ryanb/complex-form-examples, where there is a branch for doing this unobtrusively with jQuery. Tim Riley forked it, and has a great working copy here: https://github.com/timriley/complex-form-examples
Piggybacking on their work, I made a jQuery plugin to help clean up the js process: https://github.com/mboynes/nestedAttributes
Thanks, Ryan! Awesome work.
The link to Advanced Rails Recipes is broken.
The proper link to the Advanced Rails Recipes should be:
http://media.pragprog.com/titles/fr_arr/multiple_models_one_form.pdf
What about accepts_nested_attributes?
In case anyone gets stumped by
task.save(false)
, as of rails 3 usetask.save(validate: false)
.See 3.0 release notes:
save(false) is deprecated, in favor of save(:validate => false)
and the API.The related error you may see is
Hi Ryan,
It looks like the caboo.se domain name has lapsed and has been taken over by a squatter.
Your pastie link to the original code will work if you update it to:
http://pastie.org/172566
Thanks so much for your screencasts. I refer back to them frequently.