开发者

JS Style Object Referencing in Ruby

Given how expressive Ruby is, I'm wondering if anyone has ever tried to create a class or module that will mimick JS object syntax. For instance, in JS I can of course do this:

[1]  var obj = {a: 'b'};
[2]  obj.c = 'd';
[3]  obj.a = 123
[4]  obj['e'] = 'f';
[5]  obj.e = obj['a']

In Ruby I can, as of now, have code that will permit me to do something like this:

[1]  obj = {'a' => 'b'}.to_js
[2]  # obj.c = 'd' << This is what I can't solve, 'c' is first defined here.
[3]  obj.a = 123;
[4]  obj['e'] = 'f'
[5]  obj.e = obj['a']

As long as I get a symbol through square brackets or on initialization, I can easily store the K/V pair and create instance methods for the setters and getters.

However, I haven't been able to figure out how to create an object that will respond to 'c' if it's not defined, then do some magic. For instance,

  • I can iterate over the hash coming in, and set up accessors
  • I can use []=(k, v) for the bracket notation and do the same thing.

My progress in the second style up to this point.

There's a very specific Object#respond_to_missing? which can take a specific symbol in it; but can't respond in the general case.

There's three possible ways that I can think of solving the commented out, second invocation style:

  • Make the object not an instance of a class but a Method at all times. For instance, the following works:
class A
  def b; puts 'c'; end
end
def always
  A.new
end

c = always
c.b

Given this, there may be a possible way to do some kind of error catching and then hook the object accordingly.

  • Utilize some fantastic ruby feature that I haven't heard of. Ruby is such a rich language, this may already be quite easy.

  • Override the error handling mechanics somehow. I don't have any idea how someone would do this in the large general case, but basically 开发者_JS百科you'd be telling ruby that if any instance of some class failed to respond to a method, you'd have some generalized handler to deal with things.

Anyway, if anyone has any idea here, it would be a pretty fun exercise I think. Thanks!


Probably better ways of doing this:

class Hash
  def method_missing(symbol, opts = nil)
    string = symbol.to_s
    self[string[0..-2].to_sym] = opts if string[-1..-1] == '=' and opts
    self[symbol]
  end
end

If you want to subclass, than you can do this:

class JSHash < Hash
  def method_missing(symbol, opts = nil)
    string = symbol.to_s
    self[string[0..-2].to_sym] = opts if string[-1..-1] == '=' and opts
    self[symbol]
  end
end

class Hash
  def to_js
    JSHash.new.merge! self
  end
end

I'll keep this open for a while in case someone else has an idea of a better way here.


I have stumbled across this questions years later but still someone may find this useful.

The Hashie::Mash gem provides exactly this behaviour.

require 'hashie/mash'

obj = Hashie::Mash.new ({'a' => 'b'})
obj.c = 'd'
obj.a = 123
obj['e'] = 'f'
obj.e = obj['a']

p obj # #<Hashie::Mash a=123 c="d" e=123>

p obj.to_hash # {"a"=>123, "c"=>"d", "e"=>123}

It does also its magic upon assigning nested hashes and converts them to mashes:

require 'hashie/mash'

obj = Hashie::Mash.new ({'a' => 'b'})
obj.c = 3
obj.d =  { e: { f: {g: { h:  5 } } } }

puts obj.d.e.f.g.h # 5

puts obj.to_hash # {"a"=>"b", "c"=>3, "d"=>{"e"=>{"f"=>{"g"={"h"=>5}}}}}

The price here is the performance. In simple Measurement the Hashie::Mash is app. 10 times slower than Hash:

require 'benchmark'
require 'hashie/mash'

n = 1000000
time = Benchmark.measure do
  hash = {}
  (0..n).each do |key|
    hash[key] = key.to_s
  end
end
puts 'Standard Hash'
puts time

n = 1000000
time = Benchmark.measure do
  hash = Hashie::Mash.new
  (0..n).each do |key|
    hash[key] = key.to_s
  end
end
puts 'Hashie::Mash'
puts time

On my machine the results are:

Standard Hash
  0.330000   0.030000   0.360000 (  0.367405)
Hashie::Mash
  3.350000   0.040000   3.390000 (  3.394001)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜