class ApiEndpoint
attr_reader :fields
module FieldDispatcher
def method_missing(field, *args)
expanded_path = "#{@root}#{field}"
if fields.has_key? expanded_path
fields[expanded_path]
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
end
Y2xhc3MgQXBpRW5kcG9pbnQKICBhdHRyX3JlYWRlciA6ZmllbGRzCgogIG1vZHVsZSBGaWVsZERpc3BhdGNoZXIKICAgIGRlZiBtZXRob2RfbWlzc2luZyhmaWVsZCwgKmFyZ3MpCiAgICAgIGV4cGFuZGVkX3BhdGggPSAiI3tAcm9vdH0je2ZpZWxkfSIKCiAgICAgIGlmIGZpZWxkcy5oYXNfa2V5PyBleHBhbmRlZF9wYXRoCiAgICAgICAgZmllbGRzW2V4cGFuZGVkX3BhdGhdCiAgICAgIGVsc2lmIGZpZWxkcy5rZXlzLmFueT8geyB8a2V5fCBrZXkuc3RhcnRfd2l0aD8gIiN7ZXhwYW5kZWRfcGF0aH0uIiB9CiAgICAgICAgQXBpSW50ZXJtZWRpYXRlLm5ldyhlbmRwb2ludCwgIiN7ZXhwYW5kZWRfcGF0aH0uIikKICAgICAgZWxzZQogICAgICAgIHN1cGVyCiAgICAgIGVuZAogICAgZW5kCiAgZW5kCgogIGNsYXNzIEFwaUludGVybWVkaWF0ZQogICAgYXR0cl9yZWFkZXIgOmVuZHBvaW50CgogICAgZGVmIGluaXRpYWxpemUoZW5kcG9pbnQsIHJvb3QpCiAgICAgIEBlbmRwb2ludCwgQHJvb3QgPSBlbmRwb2ludCwgcm9vdAogICAgZW5kCgogICAgZGVmIGZpZWxkcwogICAgICBAZW5kcG9pbnQuZmllbGRzCiAgICBlbmQKCiAgICBpbmNsdWRlIEZpZWxkRGlzcGF0Y2hlcgogIGVuZAoKICBkZWYgaW5pdGlhbGl6ZShmaWVsZHM9e30pCiAgICBAZmllbGRzID0gZmllbGRzCiAgICBAcm9vdCAgID0gIiIKICBlbmQKCiAgaW5jbHVkZSBGaWVsZERpc3BhdGNoZXIKCiAgcHJvdGVjdGVkCgogIGRlZiBlbmRwb2ludAogICAgc2VsZgogIGVuZAplbmQKCnJlcXVpcmUgJ21pbml0ZXN0L2F1dG9ydW4nCgpjbGFzcyBUZXN0QXBpRW5kcG9pbnQgPCBNaW5pVGVzdDo6VW5pdDo6VGVzdENhc2UKICBkZWYgc2V0dXAKICAgIEBhcGkgPSBBcGlFbmRwb2ludC5uZXcoewogICAgICAnZm9vJyAgICAgPT4gJ2JhcicsCiAgICAgICdqcC5ob2dlJyA9PiAnZnVnYScsCiAgICAgICdqcC5waXlvJyA9PiAncGl5bycsCiAgICAgICdqcC5sZWdhY3kucnVieScgPT4gJzEuNi41JwogICAgfSkKICBlbmQKCiAgZGVmIHRlc3RfcGxhaW4KICAgIGFzc2VydF9lcXVhbCAnYmFyJywgQGFwaS5mb28KICAgIGFzc2VydF9yYWlzZXMgTm9NZXRob2RFcnJvciBkbwogICAgICBAYXBpLmZvb2JhcgogICAgZW5kCiAgZW5kCgogIGRlZiB0ZXN0X25lc3RlZAogICAgYXNzZXJ0X2VxdWFsICdmdWdhJywgQGFwaS5qcC5ob2dlCiAgICBhc3NlcnRfZXF1YWwgJ3BpeW8nLCBAYXBpLmpwLnBpeW8KICAgIGFzc2VydF9yYWlzZXMgTm9NZXRob2RFcnJvciBkbwogICAgICBAYXBpLmpwLmZvb2JhcgogICAgZW5kCiAgZW5kCgogIGRlZiB0ZXN0X2RlZXBseV9uZXN0ZWQKICAgIGFzc2VydF9lcXVhbCAnMS42LjUnLCBAYXBpLmpwLmxlZ2FjeS5ydWJ5CiAgICBhc3NlcnRfcmFpc2VzIE5vTWV0aG9kRXJyb3IgZG8KICAgICAgQGFwaS5qcC5sZWdhY3kuZm9vYmFyCiAgICBlbmQKICBlbmQKZW5k