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)
精彩评论