ActiveRecord helper for serialized hashes
Posted on September 19, 2012
ActiveRecord’s serialize functionality is quite useful when you want some schema-less data within a relational database. I have found this particularly useful for two cases:
- Design elements on a page
- Authentication data for various (unknown) external services
However, the one thing that is missing from the built in functionality is the nice getter/setters that ActiveRecord provides. Let’s face it, hash syntax can be pretty annoying.
Here’s a helper I created to automagically generate attribute methods for keys in a serialized hash. There’s also a bit of functionality to interpret the data as markdown and return HTML. Enjoy!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module HasSerializedHash | |
extend ActiveSupport::Concern | |
def render_html(text) | |
@@markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML, | |
autolink: true) | |
text ||= "" #needs to be string | |
@@markdown.render(text).html_safe | |
end | |
module ClassMethods | |
@@serialized_hashes ||= {} | |
def serialized_hash_keys(attr) | |
@@serialized_hashes[attr] | |
end | |
def has_serialized_hash(attr_name, *keys) | |
serialize attr_name, Hash | |
@@serialized_hashes[attr_name] = keys | |
attr_accessible *keys | |
keys.each do |k| | |
#getter | |
define_method "#{k}" do | |
h = self.send(attr_name) | |
h[k] | |
end | |
#getter html | |
define_method "#{k}_html" do | |
h = self.send(attr_name)[k] | |
self.render_html(h) | |
end | |
#setter | |
define_method "#{k}=" do |value| | |
h = self.send(attr_name) | |
h[k] = value | |
end | |
#exists | |
define_method "#{k}?" do | |
h = self.send(attr_name) | |
h[k].present? | |
end | |
end | |
define_method "all_#{attr_name}_exist?" do | |
h = self.send(attr_name) | |
keys.all? {|s| h.key? s} | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'spec_helper' | |
class TestHasSerializedHash | |
include ActiveRecord::AttributeMethods::Serialization | |
include ActiveModel::MassAssignmentSecurity | |
include HasSerializedHash | |
attr_accessor :my_hash | |
has_serialized_hash :my_hash, :key1, :key2 | |
def initialize | |
@my_hash = {} | |
@hash1 = {} | |
end | |
end | |
describe TestHasSerializedHash do | |
let(:subject) { TestHasSerializedHash.new } | |
let(:markdown) { "This is *awesome*. www.howaboutwe.com" } | |
def should_render_markdown_to_html(rendered) | |
rendered.should include("<em>awesome</em>") | |
rendered.should include("http://www.howaboutwe.com") | |
end | |
it "should have a getter method for each key" do | |
subject.should be_respond_to(:key1) | |
subject.should be_respond_to(:key2) | |
end | |
it "should translate text to markdown via #render_html" do | |
rendered = subject.render_html(markdown) | |
should_render_markdown_to_html(rendered) | |
end | |
context "html getter" do | |
it "should have a getter html method for each key" do | |
subject.should be_respond_to(:key1_html) | |
subject.should be_respond_to(:key2_html) | |
end | |
it "should translate markdown to html" do | |
subject.key1 = markdown | |
rendered = subject.key1_html | |
should_render_markdown_to_html(rendered) | |
end | |
end | |
it "should have a setter method for each key" do | |
subject.should be_respond_to(:key1=) | |
subject.should be_respond_to(:key2=) | |
end | |
it "should have an exists method for each key" do | |
subject.should be_respond_to(:key1?) | |
subject.should be_respond_to(:key2?) | |
end | |
it "should be able to set a value via the setter" do | |
subject.key1 = "hello" | |
subject.my_hash[:key1].should == "hello" | |
end | |
it "should be able to get the value via the getter" do | |
subject.my_hash[:key2] = "hiii" | |
subject.key2.should == "hiii" | |
end | |
it "should return the correct values from exists methods" do | |
subject.key1 = "exist" | |
subject.should be_key1 | |
subject.should_not be_key2 | |
end | |
it "should have all_my_hash_exist? method" do | |
subject.should be_respond_to(:all_my_hash_exist?) | |
end | |
it "should return correct value for all_my_hash_exist? methods" do | |
subject.all_my_hash_exist?.should be_false | |
subject.key1 = "hello" | |
subject.all_my_hash_exist?.should be_false | |
subject.key2 = "hii" | |
subject.all_my_hash_exist?.should be_true | |
end | |
it ".serialized_hash_keys" do | |
TestHasSerializedHash.serialized_hash_keys(:my_hash).should include(:key1, :key2) | |
end | |
it "can mass assign attributes" do | |
TestHasSerializedHash.should_receive(:attr_accessible).with(:key1, :key2) | |
TestHasSerializedHash.has_serialized_hash :my_hash, :key1, :key2 | |
end | |
end |
Got something to say?