Ruby, iOS, and Other Development

A place to share useful code snippets, ideas, and techniques

All code in posted articles shall be considered public domain unless otherwise noted.
Comments remain the property of their authors.

2006-05-18

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

end
Here'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: ,

0 Comments:

Post a Comment

<< Home