Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic Active Record Store accessors based off a user form?

Active Record Store allows you to serialize parameters inside a single cell.

I.e.

class User < ActiveRecord::Base
  store :options, accessors: [ :option1, :option2, :another_random_option ]
end

All the accessors are serialized inside the "options" column of the users table now.

u = User.new
u.option2 = 'some option'
u.option2 # => 'some option'

This works great for my application because I have to create many forms on a daily basis, where 90% of the form is the same (username, hobbies, interests, etc.) and then 10% are schema-less (random_option_here, another_random_option_in_another_form). I also never need to sort by the schema-less options.

What I did was I created 1 table for the 90% of the form fields that are always the same, and then I have another table with the last 10% of the fields (the reason I have another table is because this is a belongs_to relationship, so the user can have many rows in this table).

<%= form_tag do %> 
  <%= #render partial form for an object that has non-changing fields %>
  ...
  <%= #render a schema-less partial form based off an ID passed here %>
<% end >

Now the only problem is that every time I create a new field in the custom form, I have to add that parameter to the Active Record Store accessors, otherwise I get a method missing error. It would be nice if I could just go in and create as many View forms as I want for the schema-less fields and never update the accessors in the Model.

So my question is: Is there anyway to dynamically add all the user submitted custom fields to the accessors array, that way if the user submitted fields "some_random_option1221", "another_option_here" then I don't have to go into the accessors array and add that field?

Thanks!

like image 948
MichaelHajuddah Avatar asked Aug 21 '13 05:08

MichaelHajuddah


1 Answers

With rails 4 and postgresql the following works for me. It looks like with some minor tweaking the rails 3 store methods could also be used.

Call store_accessor dynamically on each of the field keys in the store hash owned by a given model instance. If you have a User model with a column named options of type hstore, then you can already access the options hash. (In rails 3, you would call the store method as in the code in your question to make the options method work.)

Create a method to do this whenever your user interface adds a new field. Then also call this method after_initialize so loading a user from the db will set up the field name accessors at load time. You might also want to call this method after_save.

class User < ActiveRecord::Base
  after_initialize :add_field_accessors
  after_save       :add_field_accessors

  def add_store_accessor field_name
    singleton_class.class_eval {store_accessor :options, field_name}
  end

  def add_field_accessors
    num_fields = options.try(:keys).try(:count) || 0
    options.keys.each {|field_name| add_store_accessor field_name} if num_fields > 0
  end
end

Then each user instance can have different store_accessor methods depending on what fields each user has in the options column in the db row for that user.

In your controller, depending on your preferred user interface for adding/removing/editing the options, you can have a helper method to build the options on a user, and call it from new, create, update, etc.

def build_options
  @user.options = options_hash
  @user.add_field_accessors
end

In rails 3, rather than calling store_accessor, you would be calling attr_accessor.

Note that you won't be able to call User.new(:option_1=>'some_option_value') because the User class object doesn't have the accessor methods (since each user instance could have different attributes.)

like image 153
Anatortoise House Avatar answered Sep 28 '22 05:09

Anatortoise House