Hashable
I keep running into situations in which I want to be able to get the (readable) attributes of an object into a hash. I finally decided to write a little module to help me with that. What pushed me over the edge was actually XML-RPC in a Rails app and wanting an easy way to serialize model objects. It can be used either by inclusion and using the attr_hashable and attr_hashable_cond class methods in the including class then #to_h on objects, or its module method obj_to_hash can be used to produce a hash from an arbitrary object and list of fields. Here's the code:
module Hashable def self.check_field(obj, field) check = nil begin check = obj.send("#{field}?") rescue check = obj.send(field) end return check end def self.obj_to_hash(obj, hashable_fields) hashable_fields.inject({}) { |hash,(field,always)| hash[field] = obj.send(field) if always || check_field(obj, field) hash } end def self.included(klass) class << klass def hashable_fields @hashable_fields ||= {} if self.superclass.respond_to? :hashable_fields @hashable_fields.merge!(self.superclass.hashable_fields) { |key,oldval,newval| oldval } end return @hashable_fields end private def attr_hashable(*fields) hash = hashable_fields fields.each { |field| hash[field.to_sym] = true } end def attr_hashable_cond(*fields) hash = hashable_fields fields.each { |field| hash[field.to_sym] = false } end end end def to_h Hashable.obj_to_hash(self, self.class.hashable_fields) end endHere's a sample usage of the Hashable.obj_to_hash method.
% irb -rhashable irb(main):001:0> x = Struct.new(:a, :b, :c, :y, :z).new(1, [2,2], 'three', nil, nil) => #<struct> a=1, b=[2, 2], c="three", y=nil, z=nil> irb(main):002:0> Hashable.obj_to_hash(x, :a=>true, :c=>false, :y=>false, :z=>true) => {:z=>nil, :c=>"three", :a=>1} irb(main):003:0> exit %
And here's an example of use by inclusion. Note that inheritance works as expected, even when a parent class is reopened.
require 'hashable' class Foo include Hashable attr_accessor :a, :b attr_hashable :a attr_hashable_cond :b end class Bar < Foo attr_accessor :c attr_hashable :c end class Baz < Bar attr_accessor :z attr_hashable :z end puts "Baz.hashable_fields = #{Baz.hashable_fields.inspect}" x = Baz.new x.a = 1 x.c = 'three' x.z = [26] puts "x.to_h = #{x.to_h.inspect}" class Foo attr_accessor :y attr_hashable :y end puts "Baz.hashable_fields = #{Baz.hashable_fields.inspect}" puts "x.to_h = #{x.to_h.inspect}"When run, this code produces:
% ruby hashable_example.rb Baz.hashable_fields = {:z=>true, :a=>true, :b=>false, :c=>true} x.to_h = {:z=>[26], :a=>1, :c=>"three"} Baz.hashable_fields = {:z=>true, :a=>true, :b=>false, :y=>true, :c=>true} x.to_h = {:z=>[26], :a=>1, :y=>nil, :c=>"three"} %Enjoy!
Labels: Metaprogramming, Ruby
0 Comments:
Post a Comment
<< Home