views:

73

answers:

2

Hello, I'd like to learn the smart way to store unstructured data in a DATA field

so for example, if I have the Table AuditLog with the fields id | model_name | data

And in Data I want to store various types of data which changes based on the model_name. I also want it in data so I can output these records w/o using JOINs for performance reasons.

Examples 1 | photo | {photoid: 1, photoname: "blah"} 1 | comment | {comment: 1, commentcontent: "blah", parent_type:"photo", parent_id: "1"}

Suggestions on how to input and output?

thanks

+1  A: 

I'm not sure if I understood your question properly. But I will try to answer it anyway.

If you need to convert the model to JSON it's super simple. Just call .to_json on the model. Then you can store it the TEXT column.

To read it back you have two options:

  • read the value as a Ruby hash: ActiveSupport::JSON.decode(some_json_text)

  • read the value as an object - the original model: take the hash you got above and pass it to SomeModel.new as a parameter. But in this case you have to be careful - associated collections will not work, id will not be set, etc.

There are many options how to customize the behavior and it's not clear what's best for you. I recommend to read this help page: http://railsapi.com/doc/rails-v3.0.0/classes/ActiveModel/Serializers/JSON.html and customize your solution as appropriate.

In the following example I'm not using the .to_json method on the model as the approach in the example is much more customizable. For example you can control the behavior of associations (if they are going to be stored into the log or not), etc. Read the linked document and you'll see.

And here comes the sample implementation (read comments inside)

class AuditLog < ActiveRecord::Base

  #
  # Stores shallow snapshot (without nesting into associations) of current
  # model into the log in the JSON form
  #
  def self.audit(m)
    # encode passed model into the JSON text
    json = ActiveSupport::JSON.encode(m)
    # create new log record and store it to the database
    create(:model => m.class,
           :data => json)
  end

  #
  # Returns the logged item as a Ruby hash
  #
  # Your can access the result with ["attribute_name"]
  #
  def get_as_hash
    # get the hash
    result = ActiveSupport::JSON.decode(self.data)
    # normally the json is { model_name => { attributes ... } } and we want the
    # inner hash - so let's take it
    result.values.first
  end

  #
  # Returns the logged item as an original model (like Post, Comment, etc.)
  #
  # Beware that associations are filled only if they are stored in audit method
  # So in case of Post: post.comments will always return an empty array
  #
  def get_as_model
    # get hash
    hash = get_as_hash
    # create instance of the class model and pass the hash to init
    # attribute values
    m = self.model.constantize.new(hash)
    # the initializator above ignore :id so let's set it manually
    m.id = hash[:id]
    # return the model
    m
  end
end

And you can use it this way:

class Post < ActiveRecord::Base
  has_many :comments

  # after any successful change store the model to log
  after_save :audit

  private

  # store the whole model to the log
  def audit
    AuditLog.audit(self)
  end
end

Hope you'll enjoy it!

pawien
A: 

This sounds like it would be a great use case for a NoSQL database such as MongoDB. There's a Rails gem for it called Mongoid (http://mongoid.org/) that makes it easier.

If you want to keep ActiveRecord, you can always serialize the data field as a hash. http://api.rubyonrails.org/classes/ActiveRecord/Base.html#method-c-serialize

You would do something like:

class AuditLog < ActiveRecord::Base
  serialize :data, Hash
end

Then you could use it like:

@log = AuditLog.find(1)
@log.data = {:photo => {:photoid: 1, :photoname: "blah"}}

@log.data[:photo][:photoname] => "blah"
Beerlington