zeeraw
zeeraw

Reputation: 5207

Rails 3 - Building forms from Serialized Data

I've been working on a rails project where I am needed to serialize permissions for user roles and store in the database. As far as that goes I'm all good. Now my problem comes when I want to modify the serialized data from a rails generated form.

I acted on instinct and tried with the expected behavior. That would be to use something like this:

f.check_box :permissions_customer_club_events_read

But as no getters or setters exist for the serialized data, this doesn't work (obviously :p). Now I wonder how I would go about tackling this problem and the only thing that comes to mind is dynamically generating getter and setter methods from my serialized hash.

Example:

def permissions_customer_club_events_read=(val)
  permissions[:customer][:club][:events][:read] = val
end

def permissions_customer_club_events_read
  permissions[:customer][:club][:events][:read]
end

Anyone understand what I'm getting at?

Here is my Model:

class User::Affiliation::Role < ActiveRecord::Base
  require 'yajl'

  class YajlCoder
    def dump data
      Yajl.dump data
    end
    def load data
      return unless data
      Yajl.load data
    end
  end

  serialize :permissions, YajlCoder.new

  after_initialize :init

  def init
    ## Sets base permission structure ##
    self.permissions ||= YAML.load_file("#{Rails.root}/config/permissions.yml")
  end

end

Upvotes: 2

Views: 747

Answers (3)

zeeraw
zeeraw

Reputation: 5207

This worked out for me in the end, this is how I solved it.


serialize :permissions, YajlCoder.new

after_initialize :init

def init
  self.permissions ||= YAML.load_file("#{Rails.root}/config/permissions.yml")['customer']
  build_attributes_from self.permissions, :permissions
end

private

def build_attributes_from store, prefix, path=[]
  store.each do |k,v|
    if v.class == Hash
      build_attributes_from v, prefix, ( path + [k] )
    else
      create_attr_accessors_from prefix, ( path + [k] )
    end
  end
end

def create_attr_accessors_from prefix, path=[]
  method_name = prefix.to_s + "_" + path.join('_')
  class << self
    self
  end.send :define_method, method_name do
    self.permissions.dig(:path => path)
  end
  class << self
    self
  end.send :define_method, "#{method_name}=" do |value|
    self.permissions.dig(:path => path, :value => value)
  end
end

And some monkey patching for hashes...

class Hash
  def dig(args={})

    path  = args[:path].to_enum   ||  []
    value = args[:value]          ||  nil

    if value == nil
      path.inject(self) do |location, key|
        location.respond_to?(:keys) ? location[key] : nil
      end
    else
      path.inject(self) do |location, key|
        location[key] = ( location[key].class == Hash ) ? location[key] : value
      end
    end
  end
end

Now getter and setter methods are generated for all of the serialized fields.

Upvotes: 0

Robin
Robin

Reputation: 21884

Sorry if I did not understand the question ;)

You could have a customdata module, included in your model, and use method_missing:

module CustomData
    def self.included(base)
        base.instance_eval do
        after_save        :save_data
    end

    def method_missing(method, *args, &block)
        if method.to_s =~ /^data_/
            data[method] ? data[method] : nil
        else
            super
        end
     end

      def data
          @data ||= begin
            #get and return your data
          end
      end

      private

      def save_data

      end
end

With this method, you would have to use f.check_box :data_permissions_customer_club_events_read

It's not really complete, but I hope you get the idea ;) attr_bucket seems like a good solution too.

Upvotes: 0

skorks
skorks

Reputation: 4366

I suggest you have a look at something like attr_bucket. Ostensibly, this can be used to solve some inheritance annoyances, but it will also solve your problem for you. Here is the essence.

It looks like you know what all your permissions are, but you want to serialize all of them into the same database field. But within your actual rails app, you want to treat all your permissions as if they were totally separate fields. This is exactly what a solution like attr_bucket will let you do. Let's take your example, you would do something like this:

class User::Affiliation::Role < ActiveRecord::Base
  attr_bucket :permissions => [:permissions_customer_club_events_read, :permissions_customer_club_events_write, :permission_do_crazy_things]

  after_initialize :init

  def init
    ## Sets base permission structure ##
    self.permissions ||= YAML.load_file("#{Rails.root}/config/permissions.yml")
  end
end

Now you will be able to use permissions_customer_club_events_read, permissions_customer_club_events_write, permission_do_crazy_things as if they were separate database fields (this includes using them in forms etc.), but when you actually save your objects all those fields would get 'bucketed' together and serialized into the :permissions field.

The only caveat is the serialization mechanism, I believe attr_bucket will serialize everything using YAML, whereas you were using JSON. If this doesn't matter then you're golden, otherwise you might need to patch attr_bucket to use json instead of YAML which should be pretty straight forward.

Upvotes: 1

Related Questions