class ApiEndpoint
attr_reader :fields
module FieldDispatcher
def method_missing(method, *args)
setter = method.to_s.end_with?('=')
if setter
field = method[0..-2]
else
field = method
end
expanded_path = "#{@root}#{field}"
if fields.has_key? expanded_path
if setter
fields[expanded_path] = args.first
else
fields[expanded_path]
end
elsif fields.keys.any? { |key| key.start_with? "#{expanded_path}." }
ApiIntermediate.new(endpoint, "#{expanded_path}.")
else
super
end
end
end
class ApiIntermediate
attr_reader :endpoint
def initialize(endpoint, root)
@endpoint, @root = endpoint, root
end
def fields
@endpoint.fields
end
include FieldDispatcher
end
def initialize(fields={})
@fields = fields
@root = ""
end
include FieldDispatcher
protected
def endpoint
self
end
end
require 'minitest/autorun'
class TestApiEndpoint < MiniTest::Unit::TestCase
def setup
@api = ApiEndpoint.new({
'foo' => 'bar',
'jp.hoge' => 'fuga',
'jp.piyo' => 'piyo',
'jp.legacy.ruby' => '1.6.5'
})
end
def test_plain
assert_equal 'bar', @api.foo
assert_raises NoMethodError do
@api.foobar
end
end
def test_nested
assert_equal 'fuga', @api.jp.hoge
assert_equal 'piyo', @api.jp.piyo
assert_raises NoMethodError do
@api.jp.foobar
end
end
def test_deeply_nested
assert_equal '1.6.5', @api.jp.legacy.ruby
assert_raises NoMethodError do
@api.jp.legacy.foobar
end
end
def test_set
assert_raises NoMethodError do
@api.quick = true
end
@api.foo = "baz"
assert_equal "baz", @api.fields['foo']
@api.jp.hoge = 'not actually'
assert_equal 'not actually', @api.fields['jp.hoge']
end
end
Y2xhc3MgQXBpRW5kcG9pbnQKICBhdHRyX3JlYWRlciA6ZmllbGRzCgogIG1vZHVsZSBGaWVsZERpc3BhdGNoZXIKICAgIGRlZiBtZXRob2RfbWlzc2luZyhtZXRob2QsICphcmdzKQogICAgICBzZXR0ZXIgPSBtZXRob2QudG9fcy5lbmRfd2l0aD8oJz0nKQoKICAgICAgaWYgc2V0dGVyCiAgICAgICAgZmllbGQgPSBtZXRob2RbMC4uLTJdCiAgICAgIGVsc2UKICAgICAgICBmaWVsZCA9IG1ldGhvZAogICAgICBlbmQKCiAgICAgIGV4cGFuZGVkX3BhdGggPSAiI3tAcm9vdH0je2ZpZWxkfSIKCiAgICAgIGlmIGZpZWxkcy5oYXNfa2V5PyBleHBhbmRlZF9wYXRoCiAgICAgICAgaWYgc2V0dGVyCiAgICAgICAgICBmaWVsZHNbZXhwYW5kZWRfcGF0aF0gPSBhcmdzLmZpcnN0CiAgICAgICAgZWxzZQogICAgICAgICAgZmllbGRzW2V4cGFuZGVkX3BhdGhdCiAgICAgICAgZW5kCiAgICAgIGVsc2lmIGZpZWxkcy5rZXlzLmFueT8geyB8a2V5fCBrZXkuc3RhcnRfd2l0aD8gIiN7ZXhwYW5kZWRfcGF0aH0uIiB9CiAgICAgICAgQXBpSW50ZXJtZWRpYXRlLm5ldyhlbmRwb2ludCwgIiN7ZXhwYW5kZWRfcGF0aH0uIikKICAgICAgZWxzZQogICAgICAgIHN1cGVyCiAgICAgIGVuZAogICAgZW5kCiAgZW5kCgogIGNsYXNzIEFwaUludGVybWVkaWF0ZQogICAgYXR0cl9yZWFkZXIgOmVuZHBvaW50CgogICAgZGVmIGluaXRpYWxpemUoZW5kcG9pbnQsIHJvb3QpCiAgICAgIEBlbmRwb2ludCwgQHJvb3QgPSBlbmRwb2ludCwgcm9vdAogICAgZW5kCgogICAgZGVmIGZpZWxkcwogICAgICBAZW5kcG9pbnQuZmllbGRzCiAgICBlbmQKCiAgICBpbmNsdWRlIEZpZWxkRGlzcGF0Y2hlcgogIGVuZAoKICBkZWYgaW5pdGlhbGl6ZShmaWVsZHM9e30pCiAgICBAZmllbGRzID0gZmllbGRzCiAgICBAcm9vdCAgID0gIiIKICBlbmQKCiAgaW5jbHVkZSBGaWVsZERpc3BhdGNoZXIKCiAgcHJvdGVjdGVkCgogIGRlZiBlbmRwb2ludAogICAgc2VsZgogIGVuZAplbmQKCnJlcXVpcmUgJ21pbml0ZXN0L2F1dG9ydW4nCgpjbGFzcyBUZXN0QXBpRW5kcG9pbnQgPCBNaW5pVGVzdDo6VW5pdDo6VGVzdENhc2UKICBkZWYgc2V0dXAKICAgIEBhcGkgPSBBcGlFbmRwb2ludC5uZXcoewogICAgICAnZm9vJyAgICAgPT4gJ2JhcicsCiAgICAgICdqcC5ob2dlJyA9PiAnZnVnYScsCiAgICAgICdqcC5waXlvJyA9PiAncGl5bycsCiAgICAgICdqcC5sZWdhY3kucnVieScgPT4gJzEuNi41JwogICAgfSkKICBlbmQKCiAgZGVmIHRlc3RfcGxhaW4KICAgIGFzc2VydF9lcXVhbCAnYmFyJywgQGFwaS5mb28KICAgIGFzc2VydF9yYWlzZXMgTm9NZXRob2RFcnJvciBkbwogICAgICBAYXBpLmZvb2JhcgogICAgZW5kCiAgZW5kCgogIGRlZiB0ZXN0X25lc3RlZAogICAgYXNzZXJ0X2VxdWFsICdmdWdhJywgQGFwaS5qcC5ob2dlCiAgICBhc3NlcnRfZXF1YWwgJ3BpeW8nLCBAYXBpLmpwLnBpeW8KICAgIGFzc2VydF9yYWlzZXMgTm9NZXRob2RFcnJvciBkbwogICAgICBAYXBpLmpwLmZvb2JhcgogICAgZW5kCiAgZW5kCgogIGRlZiB0ZXN0X2RlZXBseV9uZXN0ZWQKICAgIGFzc2VydF9lcXVhbCAnMS42LjUnLCBAYXBpLmpwLmxlZ2FjeS5ydWJ5CiAgICBhc3NlcnRfcmFpc2VzIE5vTWV0aG9kRXJyb3IgZG8KICAgICAgQGFwaS5qcC5sZWdhY3kuZm9vYmFyCiAgICBlbmQKICBlbmQKCiAgZGVmIHRlc3Rfc2V0CiAgICBhc3NlcnRfcmFpc2VzIE5vTWV0aG9kRXJyb3IgZG8KICAgICAgQGFwaS5xdWljayA9IHRydWUKICAgIGVuZAoKICAgIEBhcGkuZm9vID0gImJheiIKICAgIGFzc2VydF9lcXVhbCAiYmF6IiwgQGFwaS5maWVsZHNbJ2ZvbyddCgogICAgQGFwaS5qcC5ob2dlID0gJ25vdCBhY3R1YWxseScKICAgIGFzc2VydF9lcXVhbCAnbm90IGFjdHVhbGx5JywgQGFwaS5maWVsZHNbJ2pwLmhvZ2UnXQogIGVuZAplbmQ=