class RESTHome

Attributes

base_uri[RW]
basic_auth[RW]
cookies[RW]
request_method[R]
request_options[R]
request_url[R]
response[R]

Public Class Methods

namespace(path_prefix) { || ... } click to toggle source
# File lib/resthome.rb, line 173
def self.namespace(path_prefix)
  @path_prefix ||= []
  @path_prefix.push path_prefix
  yield
  @path_prefix.pop
end
rest(resource_name, collection_name, base_path, options={}) click to toggle source

Creates routes for a RESTful API

resource_name is the name of the items returned by the API, collection_name is the plural name of the items, base_path is the path to the collection

Sets up 5 most common RESTful routes

Example

/customers.json GET list of customers, POST to create a customer
/customers/1.json GET a customers, PUT to edit a customer, DELETE to delete a customer
JSON response returns {'customer': {'id':1, 'name':'Joe', ...}}

Setup the RESTful routes

rest :customer, :customers, '/customers.json'
# same as
route :customers, '/customers.json', :resource => :customer
route :create_customer, '/customers.json', :resource => :customer
route :customer, '/customers/:customer_id.json', :resource => :customer
route :edit_customer, '/customers/:customer_id.json', :resource => :customer
route :delete_customer, '/customers/:customer_id.json', :resource => :customer

Following methods are created

customers # return an array of customers
create_customer :name => 'Smith' # returns {'id' => 2, 'name' => 'Smith'}
customer 1 # return data for customer 1
edit_customer 1, :name => 'Joesph'
delete_customer 1
# File lib/resthome.rb, line 208
def self.rest(resource_name, collection_name, base_path, options={})
  options[:resource] ||= resource_name
  self.route collection_name, base_path, options
  self.route resource_name, base_path.sub(/(\.[a-zA-Z0-9]+)$/, "/:#{resource_name}_id\\1"), options
  self.route "edit_#{resource_name}", base_path.sub(/(\.[a-zA-Z0-9]+)$/, "/:#{resource_name}_id\\1"), options
  self.route "create_#{resource_name}", base_path, options
  self.route "delete_#{resource_name}", base_path.sub(/(\.[a-zA-Z0-9]+)$/, "/:#{resource_name}_id\\1"), options
end
route(name, path, options={}, &block) click to toggle source

Defines a web service route

Arguments

name of the method to create name has special meaning.

  • If starts with create or add the method will be set to POST.

  • If starts with edit or update the method will be set to PUT.

  • If starts with delete the method will be set to DELETE.

  • Else by default the method is GET.

path is the path to the web service

Options

:method

The request method get/post/put/delete. Default is get.

:expected_status

Expected status code of the response, will raise InvalidResponse. Can be an array of codes.

:return

The method to call, the class to create or a Proc to call before method returns.

:resource

The name of the element to return from the response.

:no_body

Removes the body argument from a post/put route

:query

Default set of query arguments

:body

Default set of body arguments

# File lib/resthome.rb, line 43
  def self.route(name, path, options={}, &block)
    args = path.scan /:[a-z_]+/
    path = "#{@path_prefix.join if @path_prefix}#{path}"
    function_args = args.collect{ |arg| arg[1..-1] }

    body_args = get_function_args options, :body
    function_args += body_args.map { |a| a.downcase.gsub(/[^a-z0-9_]/, '_').sub(/^\d+/, '') }

    query_args = get_function_args options, :query
    function_args += query_args.map { |a| a.downcase.gsub(/[^a-z0-9_]/, '_').sub(/^\d+/, '') }

    method = options[:method]
    expected_status = options[:expected_status]
    if method.nil?
      if name.to_s =~ /^(create|add|edit|update|delete)_/
        case $1
        when 'create'
          method = 'post'
          expected_status ||= [200, 201]
        when 'add'
          method = 'post'
          expected_status ||= [200, 201]
        when 'edit'
          method = 'put'
          expected_status ||= 200
        when 'update'
          method = 'put'
          expected_status ||= 200
        when 'delete'
          method = 'delete'
          expected_status ||= [200, 204]
        end
      else
        method = 'get'
        expected_status ||= 200
      end
    end

    method = method.to_s
    function_args << 'body' if (method == 'post' || method == 'put') && options[:no_body].nil?
    function_args << 'options={}, &block'

    method_src = <<-METHOD
    def #{name}(#{function_args.join(',')})
      path = "#{path}"
    METHOD

    args.each_with_index do |arg, idx|
      method_src << "path.sub! '#{arg}', URI.escape(#{function_args[idx]}.to_s)\n"
    end

    if options[:no_body]
      if options[:body]
        method_src << "options[:body] = #{options[:body].inspect}.merge(options[:body] || {})\n"
      elsif body_args.size > 0
        method_src << "options[:body] ||= {}\n"
      end
    else
      if method == 'post' || method == 'put'
        if options[:resource]
          method_src << "options[:body] = {'#{options[:resource].to_s}' => body}\n"
        elsif options[:body]
          method_src << "options[:body] = #{options[:body].inspect}.merge(body || {})\n"
        else
          method_src << "options[:body] = body\n"
        end
      end
    end

    if options[:query]
      method_src << "options[:query] = #{options[:query].inspect}.merge(options[:query] || {})\n"
    elsif query_args.size > 0
      method_src << "options[:query] ||= {}\n"
    end

    body_args.each_with_index do |arg, idx|
      idx += args.size
      method_src << "options[:body]['#{arg}'] = #{function_args[idx]}\n"
    end

    query_args.each_with_index do |arg, idx|
      idx += body_args.size + args.size
      method_src << "options[:query]['#{arg}'] = #{function_args[idx]}\n"
    end

    method_src << "request :#{method}, path, options\n"

    if expected_status
      if expected_status.is_a?(Array)
        method_src << 'raise InvalidResponse.new "Invalid response code #{response.code}" if ! [' + expected_status.join(',') + "].include?(response.code)\n"
      else
        method_src << 'raise InvalidResponse.new "Invalid response code #{response.code}" if response.code != ' + expected_status.to_s + "\n"
      end
    end

    return_method = 'nil'
    if options[:return].nil? || options[:return].is_a?(Proc)
      block ||= options[:return]
      if block
        register_route_block name, block
        return_method = "self.class.route_blocks['#{name}']"
      end
    elsif options[:return].is_a?(Class)
      return_method = options[:return].to_s
    else
      return_method = ":#{options[:return]}"
    end

    resource = options[:resource] ? "'#{options[:resource]}'" : 'nil'

    method_src << "parse_response!\n"

    method_src << "_handle_response response, :resource => #{resource}, :return => #{return_method}, &block\n"

    method_src << "end\n"

    if options[:instance]
      options[:instance].instance_eval method_src, __FILE__, __LINE__
    elsif options[:class]
      options[:class].class_eval method_src, __FILE__, __LINE__
    else
      self.class_eval method_src, __FILE__, __LINE__
    end
  end

Private Class Methods

get_function_args(options, fld) click to toggle source
# File lib/resthome.rb, line 352
def self.get_function_args(options, fld)
  args = []
  if options[fld]
    options[fld].each do |n, v|
      next unless v.is_a?(Symbol)
      idx = v.to_s.gsub(/[^\d]/, '').to_i
      args[idx] = n.to_s
      options[fld].delete n
    end
    args.compact!
  end
  args
end

Public Instance Methods

build_options!(options) click to toggle source

Adds the basic_auth and cookie options This method should be overwritten as needed.

# File lib/resthome.rb, line 224
def build_options!(options)
  options[:basic_auth] = self.basic_auth if self.basic_auth
  if @cookies
    options[:headers] ||= {}
    options[:headers]['cookie'] = @cookies.to_a.collect{|c| "#{c[0]}=#{c[1]}"}.join('; ') + ';'
  end
end
build_url(path) click to toggle source

Creates the url

# File lib/resthome.rb, line 218
def build_url(path)
  "#{self.base_uri || self.class.base_uri}#{path}"
end
parse_response!() click to toggle source

Called after every valid request. Useful for parsing response headers. This method should be overwritten as needed.

# File lib/resthome.rb, line 305
def parse_response!
  save_cookies!
end
request(method, path, options) click to toggle source

Makes the request using HTTParty. Saves the method, path and options used.

# File lib/resthome.rb, line 233
def request(method, path, options)
  build_options! options
  url = build_url path
  @request_method = method
  @request_url = url
  @request_options = options

  @response = self.class.send(method, url, options)
end
route(name, path, options={}) click to toggle source

Adds a route to the current object

# File lib/resthome.rb, line 169
def route(name, path, options={})
  self.class.route name, path, options.merge(:instance => self)
end
save(name, body, options={}) click to toggle source

Will either call edit_<name> or add_<name> based on wether or not the body exists.

# File lib/resthome.rb, line 244
def save(name, body, options={})
  id = body[:id] || body['id']
  if id
    if self.class.method_defined?("edit_#{name}")
      self.send("edit_#{name}", id, body, options)
    elsif self.class.method_defined?("update_#{name}")
      self.send("update_#{name}", id, body, options)
    else
      raise MethodMissing.new "No edit/update method found for #{name}"
    end
  else
    if self.class.method_defined?("add_#{name}")
      self.send("add_#{name}", body, options)
    elsif self.class.method_defined?("create_#{name}")
      self.send("create_#{name}", body, options)
    else
      raise MethodMissing.new "No add/create method found for #{name}"
    end
  end
end
save_cookies(data) click to toggle source

Parse an array of Set-cookie headers

# File lib/resthome.rb, line 296
def save_cookies(data)
  @cookies ||= {}
  data.delete_if{ |c| c.blank? }.collect { |cookie| parts = cookie.split("\; "); parts[0] ? parts[0].split('=') : nil }.each do |c|
    @cookies[c[0].strip] = c[1].strip if c && c[0] && c[1]
  end
end
save_cookies!() click to toggle source

Convenience method for saving all cookies by default called from parse_response!.

# File lib/resthome.rb, line 290
def save_cookies!
  return unless @response.headers.to_hash['set-cookie']
  save_cookies @response.headers.to_hash['set-cookie']
end