class Smooth::Api
Attributes
Public Class Methods
# File lib/smooth/api.rb, line 17 def self.default @default ||= Smooth::Api.new(:default) end
# File lib/smooth/api.rb, line 26 def initialize(name, options = {}) @name = name.to_s @options = options @_resources = {} @_policies = {} end
Public Instance Methods
All Actions taken against the Smooth
API are run 'as' some current user. Example:
Running a command:
api.as(jonathan).i_would_like_to .run_command("books.create").with(title:'Sup boo')
Running a query
api.as(soederpop).i_would_like_to .query("books").with(subject:"how to...")
# File lib/smooth/api.rb, line 59 def as(current_user, &block) proxy = DslProxy.new(current_user, self) proxy.instance_eval(&block) if block_given? proxy end
# File lib/smooth/api.rb, line 219 def authentication_strategy(option = nil, key = nil) return @authentication_strategy || [:header, 'X-AUTH-TOKEN'] if option.nil? unless option.nil? key = case when key.present? key when option.to_sym == :param :auth_token when option.to_sym == :header 'X-AUTH-TOKEN' end end @authentication_strategy = [option, key] end
The Smooth
API Provides a Rack Compatible interface so we can mount in sinatra or rails or whatever
# File lib/smooth/api.rb, line 42 def call(env) sinatra.call(env) end
# File lib/smooth/api.rb, line 175 def documentation_base { api_meta: { resource_names: resource_names, resource_groups: resource_group_names } } end
# File lib/smooth/api.rb, line 194 def expose_interface_documentation_via(sinatra) api = self sinatra.send :get, '/interface' do api.interface_documentation.to_json end sinatra.send :get, '/interface/:resource_name' do docs = api.interface_documentation[params[:resource_name]] docs.to_json end end
# File lib/smooth/api.rb, line 265 def has_resource?(resource_name) resources.key?(resource_name.to_sym) end
# File lib/smooth/api.rb, line 81 def inspect "Smooth API: #{ name } Resources: #{ resource_names }" end
# File lib/smooth/api.rb, line 184 def interface_documentation resource_keys.reduce(documentation_base) do |memo, key| memo.tap do if resource = resource(key) memo[resource.resource_name || key.to_s] = resource.interface_documentation end end end end
The Smooth
API is a gateway for the commands and queries that can be run by users against its resources
# File lib/smooth/api.rb, line 36 def lookup(path) lookup_object_by(path) end
The API will rely on the configured authentication method to determine who the user is. Given some request params and request headers
# File lib/smooth/api.rb, line 88 def lookup_current_user(params, headers) auth_strategy, key = authentication_strategy case when auth_strategy == :param && parts = params[key] user_class.find_for_token_authentication(parts) when auth_strategy == :header && parts = headers[key] user_class.find_for_token_authentication(parts) else user_class.anonymous(params, headers) end end
Look up object by path. Used to route requests to commands or queries.
Example:
lookup('books.create') #=> CreateBook
# File lib/smooth/api.rb, line 147 def lookup_object_by(path) path = path.to_s resource_name, object_name = path.split(Smooth.config.object_path_separator) resource_object = resource(resource_name) case when object_name == 'query' || object_name == 'serializer' resource_object.fetch(object_name.to_sym, :default) when object_name.nil? resource_object else resource_object.fetch(:command, object_name) end end
The Policy
will provide an ability file that we can run a user though. The Policy
can be overridden by the resource, too. A policy will pass an object path
# File lib/smooth/api.rb, line 104 def lookup_policy(_params, _headers) {}.to_mash # TODO # # Implement: # # I think Smooth replaces too much of cancan to rely on it. # # I think the model where the resource inherits from the api, and # the api policy just white lists or black lists commands for given # user roles, will be sufficient end
The Smooth
API provides an Asynchronous interface.
# File lib/smooth/api.rb, line 118 def perform_async(object_path, payload = {}) worker.perform_async serialize_for_async(object_path, payload) end
# File lib/smooth/api.rb, line 250 def policy(policy_name, options = {}, &block) if obj = _policies[policy_name.to_sym] obj.apply_options(options) unless options.empty? obj.instance_eval(&block) if block_given? obj elsif options.empty? && !block_given? nil elsif block_given? obj = Smooth::Api::Policy.new(policy_name, options, &block) _policies[policy_name.to_sym] = obj end end
# File lib/smooth/api.rb, line 269 def resource(resource_name, options = {}, &block) api_name = name existing = _resources[resource_name.to_s.downcase] if existing existing.apply_options(options) unless options.empty? existing.instance_eval(&block) if block_given? existing elsif options.empty? && !block_given? existing = nil elsif block_given? created = Smooth::Resource.new(resource_name, options, &block).tap do |obj| obj.api_name = api_name end _resources[resource_name.to_s.downcase] = created end end
# File lib/smooth/api.rb, line 171 def resource_group_names _resources.values.map(&:group_description).compact end
# File lib/smooth/api.rb, line 163 def resource_keys _resources.keys end
# File lib/smooth/api.rb, line 167 def resource_names _resources.values.map(&:resource_name).compact end
Takes a request to do something and serializes the arguments in the memory store. The request will be dispatched to the background job handler and then resumed with the same arguments.
Note: Rails Global ID will be a good replacement for this
# File lib/smooth/api.rb, line 127 def serialize_for_async(object_path, payload) key = "#{ name }".parameterize + ":cmd:#{ String.random_token(16) }" request = { api: name, object_path: object_path, payload: payload } Smooth.config.memory_store.write(key, request) key end
The Smooth
API generates a sinatra app to be able to the various resources and run commands, queries, etc.
# File lib/smooth/api.rb, line 67 def sinatra app = @sinatra_application_klass ||= Class.new(Sinatra::Base) @sinatra ||= begin _resources.each do |_name, resource| resource.router && resource.router.apply_to(app) end expose_interface_documentation_via(app) app end end
# File lib/smooth/api.rb, line 212 def user_class(user_klass = nil, &block) @user_class = user_klass if user_klass.present? @user_class || User @user_class.class_eval(&block) if block_given? @user_class end
# File lib/smooth/api.rb, line 207 def version(config = nil) @_version_config = config if config @_version_config end
# File lib/smooth/api.rb, line 236 def worker(&block) worker_name = "#{ name }".camelize + 'Worker' if worker_klass = Smooth::Api.const_get(worker_name) rescue nil @worker_klass = worker_klass else Object.const_get(worker_name, @worker_klass = Class.new(Smooth::Command::AsyncWorker)) end @worker_klass.instance_eval(&block) @worker_klass end