lib/api.rb in servicy-0.0.3 vs lib/api.rb in servicy-0.0.5

- old
+ new

@@ -1,91 +1,109 @@ -# TODO: Investigate this kind of thing using Delegator to see if I can get -# a speed boost. Each exported method would have to be dynamically defined -# in the API class to do contract checking and then pass that on to the -# delegated object. I don't, however, know if that's actually faster than -# using method_missing. Investigate module Servicy class API - def self.create(klass, method_descriptions) - @klass = klass - method_descriptions = { class: method_descriptions[:class].inject({}) { |h, info| h[info[:method].to_sym] = info; h }, - instance: method_descriptions[:instance].inject({}) { |h, info| h[info[:method].to_sym] = info; h } + def self.create(klass, mds) + method_descriptions = { class: mds[:class].inject({}) { |h, info| h[info[:method].to_sym] = info; h }, + instance: mds[:instance].inject({}) { |h, info| h[info[:method].to_sym] = info; h } } # We inherit from API here so that in the future we can add functionality # for setting up servers, service discovery, etc. # This may change to Client at some point... Who # knows. magic_class = Class.new(Servicy::API) do - @@base_class = klass - @@method_descriptions = method_descriptions - def initialize(*args) - @instance = @@base_class.new(*args) + @instance = self.class.const_get('BASE_CLASS').new(*args) end - def method_descriptions - @@method_descriptions + def self.set_remote(client) + @remote = client end + def self.remote? + @remote + end + def self.client + @remote + end + # TODO: I'm thinking that I might be able to do this with + # #define_method's instead. That will spead things up aroud 2-5x. + # The magic happens in the two method_missing methods. # Meta-programming-averse; gaze upon my works, ye haughty, and despair! def method_missing(method, *args, &block) # Verify the contract - data = method_descriptions[:instance][method] - raise NoMethodError.new("#{@klass.to_s} does not expose an API endpoint called, #{method.to_s}") unless data + data = self.class.const_get('METHOD_DESCRIPTIONS')[:instance][method] + raise NoMethodError.new("#{self.class.const_get('BASE_CLASS')} does not expose an API endpoint called, #{method.to_s}") unless data data[:contract].valid_args?(*args) # Dispatch the result - result = @instance.send(method, *args, &block) + result = nil + if self.class.remote? + params = data[:contract].params.each_with_index.inject({}) do |h, (param, index)| + h[param.to_s] = args[index] + h + end + result = self.class.client.transport.remote_request(method, params) + else + result = @instance.send(method, *args, &block) + end # Check the result data[:contract].valid_return?(*args, result) # And we are good to go result end # Make sure that we can respond to the things we actually do respond to. def respond_to?(method) - method_descriptions[:instance].include?(method) || super + self.class.const_get('METHOD_DESCRIPTIONS')[:instance].include?(method) || super end def self.method_missing(method, *args, &block) # Verify the contract - data = @@method_descriptions[:class][method] - raise NoMethodError.new("#{@klass.to_s} does not expose an API endpoint called, #{method.to_s}") unless data + data = self.const_get('METHOD_DESCRIPTIONS')[:class][method] + raise NoMethodError.new("#{self.const_get('BASE_CLASS')} does not expose an API endpoint called, #{method.to_s}") unless data data[:contract].valid_args?(*args) # Dispatch the result - result = @@base_class.send(method, *args, &block) + result = nil + if remote? + params = data[:contract].params.each_with_index.inject({}) do |h, (param, index)| + h[param.to_s] = args[index] + h + end + result = client.transport.remote_request(method, params) + else + result = self.const_get('BASE_CLASS').send(method, *args, &block) + end # Check the result data[:contract].valid_return?(*args, result) # And we are good to go result end def self.respond_to?(method) - @@method_descriptions[:class].include?(method) || super + const_get('METHOD_DESCRIPTIONS')[:class].include?(method) || super end # Send back either all the documentation, or just for a particular # method. def self.docs(method=nil) if method.nil? - return @@method_descriptions.map do |(type, methods)| + return const_get('METHOD_DESCRIPTIONS').map do |(type, methods)| methods.values.map { |v| v[:docs] } end.flatten else search_in = [:class, :instance] if method.is_a?(String) search_in = method[0] == '.' ? [:class] : (method[0] == '#' ? [:instance] : search_in) end search_in.each do |type| - @@method_descriptions[type].each do |(name,stuff)| + const_get('METHOD_DESCRIPTIONS')[type].each do |(name,stuff)| return stuff[:docs] if method.to_s =~ /(\.|#)?#{name.to_s}$/ end end return nil end @@ -93,10 +111,12 @@ end magic_class.extend(ExtraMethods) klass.constants(false).each do |const| magic_class.const_set(const, klass.const_get(const)) end + magic_class.const_set('BASE_CLASS', klass) + magic_class.const_set('METHOD_DESCRIPTIONS', method_descriptions) # Give it a good name class_name = "#{klass.to_s}API" Object.const_set(class_name, magic_class) end @@ -114,13 +134,8 @@ version = self.const_get(:VERSION) rescue '1.0.0' port = self.const_get(:PORT) rescue 1234 heartbeat_port = self.const_get(:HEARTBEAT_PORT) rescue port { name: name, host: 'localhost', port: port, version: version, heartbeat_port: heartbeat_port } - end - - # Sets the remote configuration options - def set_remote_configuration(config) - @remote_config = config end end end