module Camping::Controllers

Public Instance Methods

REST(r, options = {}) click to toggle source

Calling REST "<resource name>" creates a controller with the appropriate routes and maps your #REST methods to standard Camping controller mehods. This is meant to be used in your Controllers module in place of R <routes>.

Your #REST class should define the following methods:

  • create

  • read(id)

  • update(id)

  • destroy(id)

  • list

Routes will be automatically created based on the resource name fed to the #REST method. Your class must have the same (but CamelCaps'ed) name as the resource name. So if your resource name is 'kittens', your controller class must be Kittens.

For example:

module Foobar::Controllers
  class Kittens < REST 'kittens'
    # POST /kittens
    def create
    end

    # GET /kittens/(\d+)
    def read(id)
    end

    # PUT /kittens/(\d+)
    def update(id)
    end

    # DELETE /kittens/(\d+)
    def destroy(id)
    end

    # GET /kittens
    def list
    end
  end
end

Custom actions are also possible. For example, to implement a 'meow' action simply add a 'meow' method to the above controller:

# POST/GET/PUT/DELETE /kittens/meow
# POST/GET/PUT/DELETE /kittens/(\d+)/meow
def meow(id)
end

Note that a custom action will respond to all four HTTP methods (POST/GET/PUT/DELETE).

Optionally, you can specify a :prefix key that will prepend the given string to the routes. For example, the following will create all of the above routes, prefixed with “/pets” (i.e. POST '/pets/kittens', GET '/pets/kittens/(\d+)', etc.):

module Foobar::Controllers
  class Items < REST 'kittens', :prefix => '/pets'
    # ...
  end
end

Format-based routing similar to that in ActiveResource is also implemented. For example, to get a list of kittens in XML format, place a GET call to /kittens.xml. See the documentation for the render() method for more info.

# File lib/reststop.rb, line 300
def REST(r, options = {})
  crud = R "#{options[:prefix]}/#{r}/([0-9a-zA-Z]+)/([a-z_]+)(?:\.[a-z]+)?",
    "#{options[:prefix]}/#{r}/([0-9a-zA-Z]+)(?:\.[a-z]+)?",
    "#{options[:prefix]}/#{r}/([a-z_]+)(?:\.[a-z]+)?",
    "#{options[:prefix]}/#{r}(?:\.[a-z]+)?"
    
  crud.module_eval do
    meta_def(:restful?){true}
    
    $LOG.debug("Creating RESTful controller for #{r.inspect} using Reststop #{::Reststop::VERSION::STRING}") if $LOG
    
    def get(id_or_custom_action = nil, custom_action =  nil) # :nodoc:
      id = @input[:id] if @input[:id]
      
      custom_action = @input[:action] if @input[:action]
      
      if self.methods.include? id_or_custom_action
        custom_action ||= id_or_custom_action
        id ||= nil
      else
        id ||= id_or_custom_action
      end
      
      id = id.to_i if id && id =~ /^[0-9]+$/
      
      @format = Controllers.read_format(@input, @env)
      
      begin
        if id.nil? && @input[:id].nil?
          custom_action ? send(custom_action) : list
        else
          custom_action ? send(custom_action, id || @input[:id]) : read(id || @input[:id])
        end
      rescue NoMethodError => e
        # FIXME: this is probably not a good way to do this, but we need to somehow differentiate
        #        between 'no such route' vs. other NoMethodErrors
        if e.message =~ /no such method/
          return no_method(e)
        else
          raise e
        end
      rescue ActiveRecord::RecordNotFound => e
        return not_found(e)
      end
    end
    
    
    def post(custom_action = nil) # :nodoc:
      @format = Controllers.read_format(@input, @env)
      custom_action ? send(custom_action) : create
    end
    
    
    def put(id, custom_action = nil) # :nodoc:
      id = id.to_i if id =~ /^[0-9]+$/
      @format = Controllers.read_format(@input, @env)
      custom_action ? send(custom_action, id || @input[:id]) : update(id || @input[:id])
    end
    
    
    def delete(id, custom_action = nil) # :nodoc:
      id = id.to_i if id =~ /^[0-9]+$/
      @format = Controllers.read_format(@input, @env)
      custom_action ? send(custom_action, id || @input[:id]) : destroy(id || @input[:id])
    end
    
    private
    def _error(message, status_code = 500, e = nil)
      @status = status_code
      @message = message
      begin
        render "error_#{status_code}".intern
      rescue NoMethodError
        if @format.to_s == 'XML'
          "<error code='#{status_code}'>#{@message}</error>"
        else
          out  = "<strong>#{@message}</strong>"
          out += "<pre style='color: #bbb'><strong>#{e.class}: #{e}</strong>\n#{e.backtrace.join("\n")}</pre>" if e
          out
        end
      end
    end
    
    def no_method(e)
      _error("No controller method responds to this route!", 501, e)
    end
    
    def not_found(e)
      _error("Record not found!", 404, e)
    end
  end
  crud
end
_error(message, status_code = 500, e = nil) click to toggle source
# File lib/reststop.rb, line 367
def _error(message, status_code = 500, e = nil)
  @status = status_code
  @message = message
  begin
    render "error_#{status_code}".intern
  rescue NoMethodError
    if @format.to_s == 'XML'
      "<error code='#{status_code}'>#{@message}</error>"
    else
      out  = "<strong>#{@message}</strong>"
      out += "<pre style='color: #bbb'><strong>#{e.class}: #{e}</strong>\n#{e.backtrace.join("\n")}</pre>" if e
      out
    end
  end
end
no_method(e) click to toggle source
# File lib/reststop.rb, line 383
def no_method(e)
  _error("No controller method responds to this route!", 501, e)
end
not_found(e) click to toggle source
# File lib/reststop.rb, line 387
def not_found(e)
  _error("Record not found!", 404, e)
end