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-19

Kernel#qualified_const_get

The question of how to get a class by name comes up with some regularity on the ruby-talk and rails mailing lists. The first response is usually to use Object::const_get. The response to that is that it doesn't handle classes within namespaces, e.g. Foo::Bar. I might argue that Object::const_get("Foo::Bar") should do the right thing and retrieve the value of Bar from the Foo class/module, but the fact of the matter is that it does not. Having an itch to scratch, I wrote Kernel#qualified_const_get:

module Kernel
  def qualified_const_get(str)
    path = str.to_s.split('::')
    from_root = path[0].empty?
    if from_root
      from_root = []
      path = path[1..-1]
    else
      start_ns = ((Class === self)||(Module === self)) ? self : self.class
      from_root = start_ns.to_s.split('::')
    end
    until from_root.empty?
      begin
        return (from_root+path).inject(Object) { |ns,name| ns.const_get(name) }
      rescue NameError
        from_root.delete_at(-1)
      end
    end
    path.inject(Object) { |ns,name| ns.const_get(name) }
  end
end

One of the advantages of this usage is that it handles partially qualified constant names. The following does the right thing in both cases:

require 'qualified_const_get'

module Foo
  module Bar
    class Baz
      def initialize
        puts 'You found me!'
      end
    end
  end
  def self.find_it
    klass = qualified_const_get("Bar::Baz")
    klass.new
  end
  module Quux
    def self.find_it
      klass = qualified_const_get("Bar::Baz")
      klass.new
    end
  end
end

Foo::find_it
Foo::Quux::find_it
Enjoy!

Labels: , ,

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: ,