If you store the fields in a serialized Hash like in this episode then there's no convenient way to select/sort by these values using ActiveRecord (but you could sort them in Ruby using Enumerable#sort_by. You should check out Hstore (episode #345).
Great cast, interesting and well timed. Quick note that the products/show code is missing form the show notes. But I think we can handle that ourselves ;-)
Wow! I feel like I need to watch this several times in slow motion to absorb it. It's amazing how much you can pack into 14 minutes. It also seems like really understanding dynamic forms could simplify my database architecture.
nice, the smell of the dark side :)
but it's better to avoid this kind of structures because it kills searching, sorting, filtering and other common features... don't you think?
I'm not sure if I'm thinking this through correctly. Would people agree that ProductField names should not be update-able. In the serialized hash the key would remain what it was originally as, so unless the developer goes back and sanitizes all of the product properties in the database, or adds some method_missing translations to the ProductType class a back end client name update would break all of the existing products of that type. Maybe?
Actually had this problem in a project a while back. Ended up using a UUID as the property's hash key, but using a display_name when displaying them, allowing the field names to change without breaking the rest of it. It was complicated, though, and I'm not sure doing so would work well with Hstore. It did add a few extra layers of abstraction and wasn't very fun to work with. @phillyslick also makes a good point that deleting fields doesn't delete the data from the properties hash. The only way we found to work around that was to update all the items of a certain type if the type changed.
This is a great cast, but in my experience doing this kind of thing can bring you down a pretty slippery slope if the users aren't warned of the consequences. With great flexibility comes great responsibility, and diligence and well tested rake tasks should be built into the system.
Absolutely unbelievable. My partner and I just finished building this very type of functionality into an app. It took us months to figure out a plausible approach, and here it is in 15 minutes. Someone give this man a medal.
A little OT, but here's a link to SO with a way to hack Struct into a Rails project. I've just spent the morning reading up on Struct, OpenStruct, etc. and am amazed with the possibilities!
Didn't really know how to approach using OpenStruct or Struct before this episode. Thanks, Ryan!
I immediately cringed when I saw you eschewing the built-in ActiveModel::Validations validators for a custom validate_properties method. Then when your solution to allowing a more complex field to be added was to hard code it I lost all faith.
I actually had to do something similar about a year ago for a project collecting electronic signatures for forms. To meet my needs I used a dynamically defined proxy object that would allow the use of any built in validators on any of the attributes, then passed that to the form builder. I'd love to see this thought through better, but it already more effectively re-uses components that have already been built and tested.
Corey - could you go into a little more detail on the proxy object? I'm looking to do something along the lines of letting users (admins) make new forms for the public to submit data to. Following this screen cast I've got it implemented but I'm a Ruby and RoR n00bie.
I've the situation where what data I need to collect is custom per situation, so hard coding Models isn't an option so I am using the Admin's building of a collection of form fields and then dynamically building a model that is then used to make the form for the public and store the data in Mongodb. I've read a bit on factory design patterns and template patterns but I've not yet tried to apply this within rails to even see if it would work.
I'm throwing an error trying to recreate this example. Instead of products, I am using forms. Any help or explanation would be very helpful, because this is over my head, but i'd like to learn what is going on here.
NoMethodError in Forms#new
Showing /Users/Admin/Documents/development/form_builder/app/views/forms/fields/_text_field.html.erb where line #3 raised:
undefined method `human_attribute_name' for OpenStruct:Class
Extracted source (around line #3):
Actually I got that wrong... Here is the code that worked for me. Again the link I put in the initial response was where I got this code...
Products Controller
def product_params
params.require(:product).permit(:name, :product_type_id).tap do |whitelisted|
whitelisted[:properties] = params[:product][:properties]
end
Alan - the issue was in the product_types_controller, but your solution is in the products_controller. There might also be a problem in the products_controller, but initially, since product_types contains the :fields_attributes hash, the solution needs to be in permitting params[:product[:fields_attributes]] or somesuch. I notice the solution link you gave from stack makes the same mistake. Or, is it a mistake?
I want to create a dynamic form but there is no model associated with the nested form. I get errors when trying to implement what Ryan has told. Anyone knows how to get around it?
Careful on saving DynamicFormEntries field name.
I had to use the field id because of certain field names were rails reserved words which cannot be used.
Nice cast. I actually had a need for this kind of relation but did it using an EAV style set up rather than serialised arrays.
In case you would use Hstore (PostgreSQL) do you think it would be simpler and faster?
+1
How would you select or sort by these dynamic values with activerecord?
If you store the fields in a serialized Hash like in this episode then there's no convenient way to select/sort by these values using ActiveRecord (but you could sort them in Ruby using
Enumerable#sort_by
. You should check out Hstore (episode #345).Four years later - I would say, put them in a JSONB field and sort using PostgreSQL's native support for it.
You can also check out my nested form fields gem: https://github.com/ncri/nested_form_fields
I like the
OpenStruct.new
usage in this episode. This episode is really a very sharp. Thank you Ryan.+1 for the OpenStruct. It really makes it rails way. I used to add value: hash['field'] to each dynamic field and that was evil ;)
I'm using it in rails 4 ruby 2.2 and isn't working anything I should know?, thks
Great cast, interesting and well timed. Quick note that the products/show code is missing form the show notes. But I think we can handle that ourselves ;-)
Wow! I feel like I need to watch this several times in slow motion to absorb it. It's amazing how much you can pack into 14 minutes. It also seems like really understanding dynamic forms could simplify my database architecture.
nice, the smell of the dark side :)
but it's better to avoid this kind of structures because it kills searching, sorting, filtering and other common features... don't you think?
Using Hstore would do searching, sorting, filtering you can index with Hstore to be fast
+1 for hstore
I'm not sure if I'm thinking this through correctly. Would people agree that ProductField names should not be update-able. In the serialized hash the key would remain what it was originally as, so unless the developer goes back and sanitizes all of the product properties in the database, or adds some method_missing translations to the ProductType class a back end client name update would break all of the existing products of that type. Maybe?
Yes. Also if you delete a product_field from the product_type, the data remains.
This is nice if you've got a store that you or a couple of people manage, but wouldn't really cut it for anything larger than that.
Actually had this problem in a project a while back. Ended up using a UUID as the property's hash key, but using a
display_name
when displaying them, allowing the field names to change without breaking the rest of it. It was complicated, though, and I'm not sure doing so would work well with Hstore. It did add a few extra layers of abstraction and wasn't very fun to work with. @phillyslick also makes a good point that deleting fields doesn't delete the data from the properties hash. The only way we found to work around that was to update all the items of a certain type if the type changed.This is a great cast, but in my experience doing this kind of thing can bring you down a pretty slippery slope if the users aren't warned of the consequences. With great flexibility comes great responsibility, and diligence and well tested rake tasks should be built into the system.
This could work nicely with Mongoid to create custom fields in MongoDB.
Now Ryan, if you can just change this from products to personnel records and add workflows, the app I need to write will be done.
Thanks again, Ryan.
Fantastic episode, has helped me a lot. Can I suggest a tweak to the remove_fields coffee script :
$(document).on 'click', 'form .remove_fields', (event) ->
$('input[name$="[_destroy]"]', $(this).siblings()).val('1')
$(this).closest('fieldset').hide()
event.preventDefault()
This makes it less dependent on the position of the hidden destroy field.
Perfect, I was using Simple Form then I had to use a different css path for that.
Absolutely unbelievable. My partner and I just finished building this very type of functionality into an app. It took us months to figure out a plausible approach, and here it is in 15 minutes. Someone give this man a medal.
A little OT, but here's a link to SO with a way to hack Struct into a Rails project. I've just spent the morning reading up on Struct, OpenStruct, etc. and am amazed with the possibilities!
Didn't really know how to approach using OpenStruct or Struct before this episode. Thanks, Ryan!
http://stackoverflow.com/questions/2240535/how-do-i-use-hash-keys-as-methods-on-a-class/4064219#4064219
It seems that I should prebuild :fields in controller:
def new
@product_type = ProductType.new
@product_type.fields.build
...
end
or it won't show.
If you need to add nested-forms dynamically. You can have a look at
http://stackoverflow.com/questions/15072932/dynamically-adding-fixed-number-of-prepopulated-nested-forms-using-cocoon-and-ra/15275314#15275314
If I would opt by using an external table (properties -> name, value); how it would looks like?
I think the models could be something like this.
But, what are the best way to bind this to the view?
Old I know, but I'm actually wanting to do this too. Did you ever get this figured out?
I immediately cringed when I saw you eschewing the built-in
ActiveModel::Validations
validators for a customvalidate_properties
method. Then when your solution to allowing a more complex field to be added was to hard code it I lost all faith.I actually had to do something similar about a year ago for a project collecting electronic signatures for forms. To meet my needs I used a dynamically defined proxy object that would allow the use of any built in validators on any of the attributes, then passed that to the form builder. I'd love to see this thought through better, but it already more effectively re-uses components that have already been built and tested.
Corey - could you go into a little more detail on the proxy object? I'm looking to do something along the lines of letting users (admins) make new forms for the public to submit data to. Following this screen cast I've got it implemented but I'm a Ruby and RoR n00bie.
I've the situation where what data I need to collect is custom per situation, so hard coding Models isn't an option so I am using the Admin's building of a collection of form fields and then dynamically building a model that is then used to make the form for the public and store the data in Mongodb. I've read a bit on factory design patterns and template patterns but I've not yet tried to apply this within rails to even see if it would work.
I'm throwing an error trying to recreate this example. Instead of products, I am using forms. Any help or explanation would be very helpful, because this is over my head, but i'd like to learn what is going on here.
NoMethodError in Forms#new
Showing /Users/Admin/Documents/development/form_builder/app/views/forms/fields/_text_field.html.erb where line #3 raised:
undefined method `human_attribute_name' for OpenStruct:Class
Extracted source (around line #3):
1:
2: <%= f.label field.name %>
3: <%= f.text_field field.name %>
4:
Trace of template inclusion: app/views/forms/_form.html.erb, app/views/forms/new.html.erb
Parameters:
{"utf8"=>"✓",
"form_type_id"=>"3"}
Please ignore the above question I can't seem to find an edit button. The issue was from another gem.
If I'm using hstore and implementing the shirt_sizes example, how would I go about "handling the serialization manually" as suggested?
Hi Ryan, thx for this great episode !
One question:
How could i make paperclip to work with your tutorial ?
Thx !
did anyone figure out how to get it to work with ruby 2.0?
I liked this.
I was trying this in Ruby 2, with Rails 4.
The
attr_attributes
no longer works. Permitting the:fields_attributes
didn't cut it, since there is an unknown number of fields. See thisI ended up permitting all parameters with:
params.require(:product_type).permit!
I know it's not safe to do so. If somebody knows a proper way to validate the product type params I would love to hear it.
From my understanding you have to use tap and then whitelist the dynamic fields. Here is the code I used in my application.
Here is the resource I used to figure this out.
Link
I hope that helps.
Actually I got that wrong... Here is the code that worked for me. Again the link I put in the initial response was where I got this code...
Alan - the issue was in the product_types_controller, but your solution is in the products_controller. There might also be a problem in the products_controller, but initially, since product_types contains the :fields_attributes hash, the solution needs to be in permitting params[:product[:fields_attributes]] or somesuch. I notice the solution link you gave from stack makes the same mistake. Or, is it a mistake?
If you are still interested, you can checkout my forked repo on github - https://github.com/asang/403-dynamic-forms
How would you do model validation to ensure that user must add at least one field?
As I followed the tutorial, it return 'Can't mass-assign protected attributes: destroy' when I attempted to create the product type. How come ?
Hi,
I want to create a dynamic form but there is no model associated with the nested form. I get errors when trying to implement what Ryan has told. Anyone knows how to get around it?
Careful on saving DynamicFormEntries field name.
I had to use the field id because of certain field names were rails reserved words which cannot be used.
I am facing undefined method `fields' for #ProductType:0x007ffb8305a100 at http://localhost:3000/products/new?utf8=%E2%9C%93&product_type_id=1
my app/views/products/_form.html.erb file extraction of the lines which are causing the problem
<%= f.fields_for :properties, OpenStruct.new(@product.properties) do |builder| %>
<% @product.product_type.fields.each do |field| %>
<%= render "products/fields/#{field.field_type}", field: field, f: builder %>
Have you inserted
<%= f.hidden_field :tracker_type_id %>
in the app/views/products/_form.html.erb
<%= f.hidden_field :product_type_id %>
should be
100.times
puts "*********Thank You************************"
end
Awesome Job
Hats off Ryan.
Exactly what I was looking for. Sure hope you come back to do more RailsCasts soon Ryan!
No idea why but I needed to change:
$(this).closest('fieldset').hide()
into:
$(this).parent().hide()
for the removing to work.
it was way too fast for me :'(
hi guys :)
how create a search form for dynamic form ??
Thanks. Now I understand everything is how it all works.
Thanks for the post, this is helpful for my blog