class Smooth::Api

Attributes

_policies[RW]
_resources[RW]
_version_config[RW]
name[RW]

Public Class Methods

default() click to toggle source
# File lib/smooth/api.rb, line 17
def self.default
  @default ||= Smooth::Api.new(:default)
end
new(name, options = {}) click to toggle source
# File lib/smooth/api.rb, line 26
def initialize(name, options = {})
  @name       = name.to_s
  @options    = options

  @_resources   = {}
  @_policies    = {}
end

Public Instance Methods

as(current_user, &block) click to toggle source

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
authentication_strategy(option = nil, key = nil) click to toggle source
# 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
call(env) click to toggle source

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
documentation_base() click to toggle source
# File lib/smooth/api.rb, line 175
def documentation_base
  {
    api_meta: {
      resource_names: resource_names,
      resource_groups: resource_group_names
    }
  }
end
expose_interface_documentation_via(sinatra) click to toggle source
# 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
has_resource?(resource_name) click to toggle source
# File lib/smooth/api.rb, line 265
def has_resource?(resource_name)
  resources.key?(resource_name.to_sym)
end
inspect() click to toggle source
# File lib/smooth/api.rb, line 81
def inspect
  "Smooth API: #{ name } Resources: #{ resource_names }"
end
interface_documentation() click to toggle source
# 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
lookup(path) click to toggle source

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
lookup_current_user(params, headers) click to toggle source

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
lookup_object_by(path) click to toggle source

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
lookup_policy(_params, _headers) click to toggle source

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
perform_async(object_path, payload = {}) click to toggle source

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
policy(policy_name, options = {}, &block) click to toggle source
# 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
resource(resource_name, options = {}, &block) click to toggle source
# 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
resource_group_names() click to toggle source
# File lib/smooth/api.rb, line 171
def resource_group_names
  _resources.values.map(&:group_description).compact
end
resource_keys() click to toggle source
# File lib/smooth/api.rb, line 163
def resource_keys
  _resources.keys
end
resource_names() click to toggle source
# File lib/smooth/api.rb, line 167
def resource_names
  _resources.values.map(&:resource_name).compact
end
serialize_for_async(object_path, payload) click to toggle source

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
sinatra() click to toggle source

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
user_class(user_klass = nil, &block) click to toggle source
# 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
version(config = nil) click to toggle source
# File lib/smooth/api.rb, line 207
def version(config = nil)
  @_version_config = config if config
  @_version_config
end
worker(&block) click to toggle source
# 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