Molfar
Molfar

Reputation: 1501

Create form fields for hash attribute

I have a model Product, which has a properties attribute. It stores it in a single database column as a hash (following http://api.rubyonrails.org/classes/ActiveRecord/Store.html)

class Product < ActiveRecord::Base
  store :properties
end

How can I create dynamic form fields for this property attribute (which is a hash)? I'm interested in ideologically correct way of doing this ("rails way"). I guess that there is need to use fields_for helper. But I do not fully understand how to do it. Tell me the correct way of solving this problem, please.

In result, I'd like to get a working form like shown on image.

enter image description here

Where a user can add unlimited number of fields and give any property names and its values.

Upvotes: 12

Views: 10517

Answers (3)

Ecnalyr
Ecnalyr

Reputation: 5802

To be brief, you want to have a form that contains this within it somewhere:

<% @product.properties_hash.each do |k,v| %>
  <%= f.field_for k %>
  <%= f.field_for v %>
  <# link to action that will remove this key/value pair from the serialized hash saved in the database %>
<% end -%>

You're going to have to create a blank key and value pair somehow (either here in the form just using the form helpers or by adding a blank(ish) key and value pair to the end of the hash itself by modifying the product in the controller after you load it into @product (like with a @product.add_blank_properties method). Otherwise you won't have 'blank' fields at the end of the loop. . .

I could go into more detail about adding a new blank line for a property after you create one using the previously existing blank line, but by the time you get this far you should have a good grasp on what you need to look for to solve that (and there are plenty of resources out there (you will probably be using ajax for this).

Upvotes: 0

vvohra87
vvohra87

Reputation: 5664

The rails way needn't include the limitation of using a single table, ideally, you can do this in a very rails way with 2 tables. Moving on.

You shouldn't use Active Record :store for this purpose in my opinion.

That implementation is ideal for situations where the developers need to store model metadata that is flexible in-code but well-defined at any given point of time. Which is to say, you need to specify keys in the model.

There is another pit-fall, you can't run SQL queries on the resulting serialized text that is saved.

If you insist, you can do this:

In your model:

class Product < ActiveRecord::Base
  store :properties

  def prop_hash
    self.properties.collect{|k,v| [k,v]}
  end

  def prop_hash=(param_hash)
    # need to ensure deleted values from form don't persist        
    self.properties.clear 
    param_hash.each do |name, value|
      self.properties[name.to_sym] = value
    end  
  end

end

In the view:

<%= form_for @product do |f| %>
  <% f.object.prop_hash.each do |k,v| %>
    <%= text_field 'product[prop_hash][][name]', k %>
    <%= text_field 'product[prop_hash][][value]', v %>
  <% end %>
<% end %>

Then you can also add an 'add another property' link which should use JS to insert another pair of inputs with the names product[prop_hash][][name] and product[prop_hash][][value] respectively.

Long ago I had rolled a custom implementation for metadata which saves the keys in serialized XML, for one reason - it can be queried in SQL. The link to my blog article http://geniitech.tumblr.com/post/14916592782/storing-metadata-as-xml-hash-in-ror

Upvotes: 13

Sandip Ransing
Sandip Ransing

Reputation: 7733

I think you should be using rails standard scaffold but here you may need few customization's.

1) Need to declare resourceful route for properties property name will become resource id 2) Index view will iterate over properties hash to populate relevant fields.

product.properties.each do |name, value|
   puts name
   puts value
end

2) Create action should create a record if doesn't exist (based on name) already otherwise give an error

product.properties[:price] = 10

3) Update will find property based on name and update it hash way

product.properties[:price] = 25

4) delete will actually delete the key-value pair from hash based on property name

product.properties.delete(:price)

Upvotes: -2

Related Questions