Dynamic Active Record Store Accessors Based Off a User Form

activeadmin and dynamic store accessors fails on new resource

SOLUTION:

I traced what AA was doing to figure out the minimum number of commands needed. It was necessary to add code to build_new_resource to ensure that any new resource AA built had the correct :category field, and once doing so, make the call to dynamically add the store_accessor keys to the newly built instance.

Now users can create their own original schemas and records that use them, without any further programming! I hope others find this useful, I certainly will.

There are a couple ugly solutions here, one is that adding the parameters to the active admin new route call is not expected by AA, but it still works. I guess this parameter could be passed in some other way, but quick and dirty does the job. The other is that I had to have the form generate a session variable to store what kind of schema was used, in order for the post-form-submission build to know, since pressing the "Create Move" button clears the params from the url.

The operations are as follows: for a model called Move with field :data that should be dynamically serialized into fields according to the json schema tables, both
admin/moves/new?category="cleave" and admin/moves/#/edit find the "cleave" schema from the schema table, and correctly create and populate a form with the serialized parameters. And, direct writes to the db

m=Move.new(category: "cleave")          ==> true
m.update(name: "t2", quality: "fine") ==> true

work as expected. The schema table is defined as:

require "json-schema"
class SampleActionSchema < ApplicationRecord
validates :category, uniqueness: { case_sensitive: false }, allow_nil: false, allow_blank: true
validate :schema_is_json_schema

def self.schema_keys(categ)
sas=SampleActionSchema.find_by(category: categ)
schema_keys= sas.nil? ? [] : sas[:schema]["properties"].keys.map{|k| k.to_sym}
end

private

def schema_is_json_schema
metaschema = JSON::Validator.validator_for_name("draft4").metaschema
unless JSON::Validator.validate(metaschema, schema)
errors.add :schema, 'not a compliant json schema'
end
end
end

The Move table that employs this schema is:

class Move < ApplicationRecord
after_initialize :add_field_accessors

def add_field_accessors
if category!=""
keys=SampleActionSchema.schema_keys(category)
keys.each {|k| singleton_class.class_eval{store_accessor :data, k}}
end
end
end

Finally, the working controller:

ActiveAdmin.register Move do
permit_params do
#choice 1 is for new records, choice 2 is for editing existing
categ = @_params[:category] || (@_params[:move][:category] if @_params[:move]) || ""
keys=SampleActionSchema.schema_keys(categ)
prms = [:name, :data] + keys
end

form do |f|
new=f.object.new_record?
f.object.category=params[:category] if new
if new
session[:current_category]=params[:category]
f.object.add_field_accessors
else
session[:current_category] = ""
end
keys=SampleActionSchema.schema_keys(f.object.category)
f.inputs do
f.input :name
f.input :category
keys.each {|k| f.input k}
end
f.actions
end

controller do
def build_new_resource
r=super
r.assign_attributes(category: session[:current_category])
r.add_field_accessors
r
end
end
end

rails 4.1 / active record data_store hash not being initialized/serialized

This might have to do with the fact that upon object initialization there is no value in p.post_garbage.

p.post_garbage["the_stuff"] = "something"

Is trying to implicitly treat post_garbage as a hash.
Which would end up being similar to

unset_variable = nil
unset_variable['hash_key'] = 'hash_value'

Using the serialize method for an attribute tells Rails it should expect to condense an Object into a key value map and store it in the database as text. But that doesn't necessarily mean your object is instantiated with this as a hash. It could be any object in particular. But that object needs to be assigned to your attribute before it can be serialized. Serialize may work out the magic to store and reconstitute it on load, but it will not make any assumptions about the form of that object if uninitialized. So without any indication of what object post_garbage is Ruby will not expect it to be a hash.

As for why does it not return an empty has when empty? There is a difference between an empty object and a missing object. (ie: {} vs nil). They are not the same thing, often a missing object is treated like an empty object. But there is value in that distinction, which I expect Rails preserves when loading a serialized attribute.

Perhaps you should try initializing it on create with something like this?

class Post < ActiveRecord::Base
serialize :post_garbage
after_initialize :default_values

private
def default_values
self.post_garbage ||= {}
end
end

ActiveRecord::Base.store automatic typecasting

As of Rails 3.2.7 there is not a way to automatically typecast values. I'll update this question if I ever come across a way to do it :/

How can I create dynamic form field to store/update hash sets in Rails?

Might need tweaking but i used similar code with check-boxes and it worked!

<% @hotel.roomtypes.each do |roomtype| %>
<%= f.label roomtype.name %>
<%= f.select :"reserved_rooms[roomtype.id]", ((0..50).map {|i| [i,i] }), :include_blank => "" %>
<% end %>

Update serialized fields without validation?

You should be able to simply update the attribute and then call save:

foo.bar2 = 'an update to bar2'
foo.save(validate: false)

How do I set an attr_accessor for a dynamic instance variable?

this answer doesn't pollutes the class space, example.. if i do mine.my_number 4 then the other instances of Mine will not get the my_4 method.. this happens because we use the singleton class of the object instead of the class.

class Mine
def my_number num
singleton_class.class_eval { attr_accessor "my_#{num}" }
send("my_#{num}=", num)
end
end

a = Mine.new
b = Mine.new
a.my_number 10 #=> 10
a.my_10 #=> 10
b.my_10 #=> NoMethodError

Rails ActiveRecord::Base before_save overwriting the write_ accessor for saving phone number

By defining def original_number=(value) method you are overriding the default accessor method. So you wouldnt need to do anything on before_save.
Although the code should look like

def original_number=(value)
num = value.to_s.gsub(/[^0-9+]/, "")
write_attribute(:original_number, num.to_i)
end


Related Topics



Leave a reply



Submit