This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'rubygems' | |
#require 'ruby-debug' # @techarch : commented out since only needed for local debugging | |
require 'markaby' # @techarch : added explicit require | |
require 'camping' # @techarch | |
require 'camping/session' # @techarch : added explicit require since session has changed in Camping 2.0 | |
gem 'RedCloth' # @techarch : added since it is referenced in the Posts model | |
require 'redcloth' # @techarch : added | |
#gem 'camping', '~> 2.0' | |
gem 'camping' , '>= 1.9.354' # @techarch : updated version | |
#gem 'reststop', '~> 0.3' | |
=begin # @techarch : commented out since only needed for local debugging | |
$: << '../../camping-camping/lib' | |
$: << '../lib' | |
require 'camping-unabridged' | |
require 'camping/ar' | |
require 'camping/session' | |
=end | |
#begin # @techarch : commented out since only needed for local debugging | |
# try to use local copy of library | |
#require '../lib/reststop2' | |
require './reststop2.rb' # @techarch : adjusted so that it is located in the same current folder | |
#rescue LoadError | |
# # ... otherwise default to rubygem | |
# require 'reststop' | |
#end | |
Camping.goes :Blog | |
module Blog | |
include Camping::Session | |
include Reststop | |
Controllers.extend Reststop::Controllers | |
end | |
module Blog::Base | |
alias camping_render render | |
alias camping_service service | |
include Reststop::Base | |
alias service reststop_service | |
alias render reststop_render | |
end | |
module Blog::Models | |
class Post < Base | |
belongs_to :user | |
before_save do |record| | |
cloth = RedCloth.new(record.body) | |
cloth.hard_breaks = false | |
record.html_body = cloth.to_html | |
end | |
end | |
class Comment < Base; belongs_to :user; end | |
class User < Base; end | |
class BasicFields < V 1.1 | |
def self.up | |
create_table :blog_posts, :force => true do |t| | |
t.integer :user_id, :null => false | |
t.string :title, :limit => 255 | |
t.text :body, :html_body | |
t.timestamps | |
end | |
create_table :blog_users, :force => true do |t| | |
t.string :username, :password | |
end | |
create_table :blog_comments, :force => true do |t| | |
t.integer :post_id, :null => false | |
t.string :username | |
t.text :body, :html_body | |
t.timestamps | |
end | |
User.create :username => 'admin', :password => 'camping' | |
end | |
def self.down | |
drop_table :blog_posts | |
drop_table :blog_users | |
drop_table :blog_comments | |
end | |
end | |
end | |
module Blog::Controllers | |
extend Reststop::Controllers | |
class Login < R '/login' # @techarch : added explicit login controller | |
def get | |
render :_login | |
end | |
end | |
class Posts < REST 'posts' | |
# POST /posts | |
def create | |
require_login! | |
@post = Post.create :title => (input.post_title || input.title), # @techarch : allow for REST-client based update | |
:body => (input.post_body || input.body), # @techarch : allow for REST-client based update | |
:user_id => @state.user_id | |
redirect R(@post) | |
end | |
# GET /posts/1 | |
# GET /posts/1.xml | |
def read(post_id) | |
@post = Post.find(post_id) | |
@comments = Models::Comment.find(:all, :conditions => ['post_id = ?', post_id]) | |
render :view | |
end | |
# PUT /posts/1 | |
def update(post_id) | |
require_login! | |
@post = Post.find(post_id) | |
@post.update_attributes :title => (input.post_title || input.title), # @techarch : allow for REST-client based update | |
:body => (input.post_body || input.body) # @techarch : allow for REST-client based update | |
redirect R(@post) | |
end | |
# DELETE /posts/1 | |
def delete(post_id) | |
require_login! | |
@post = Post.find post_id | |
if @post.destroy | |
redirect R(Posts) | |
else | |
_error("Unable to delete post #{@post.id}", 500) | |
end | |
end | |
# GET /posts | |
# GET /posts.xml | |
def list | |
@posts = Post.all(:order => 'updated_at DESC') | |
s=render :index | |
s | |
end | |
# GET /posts/new | |
def new | |
#@state.user_id = 1 # @techarch : commented out as was probably hard-coded for testing purpose | |
require_login! | |
@user = User.find @state.user_id # @techarch : added since we need the user info | |
@post = Post.new | |
render :add | |
end | |
# GET /posts/1/edit | |
def edit(post_id) | |
require_login! | |
@user = User.find @state.user_id # @techarch : added since we need the user info | |
@post = Post.find(post_id) | |
render :edit | |
end | |
end | |
class Comments < REST 'comments' | |
# POST /comments | |
def create | |
Models::Comment.create(:username => (input.post_username || input.username), # @techarch : allow for REST-client based update | |
:body => (input.post_body || input.body), # @techarch : allow for REST-client based update | |
:post_id => input.post_id) | |
redirect R(Posts, input.post_id) | |
end | |
end | |
class Sessions < REST 'sessions' | |
# POST /sessions | |
def create | |
@user = User.find_by_username_and_password(input.username, input.password) | |
if @user | |
@state.user_id = @user.id | |
redirect R(Posts) | |
else | |
@info = 'Wrong username or password.' | |
end | |
render :login | |
end | |
# DELETE /sessions | |
def delete | |
@state.user_id = nil | |
redirect R(Posts) # @techarch : changed redirect from Index (does not exist) to Posts | |
end | |
end | |
# You can use old-fashioned Camping controllers too! | |
class Style < R '/styles.css' | |
def get | |
@headers["Content-Type"] = "text/css; charset=utf-8" | |
@body = %{ | |
body { | |
font-family: Utopia, Georga, serif; | |
} | |
h1.header { | |
background-color: #fef; | |
margin: 0; padding: 10px; | |
} | |
div.content { | |
padding: 10px; | |
} | |
} | |
end | |
end | |
end | |
module Blog::Helpers | |
alias_method :_R, :R | |
remove_method :R | |
include Reststop::Helpers | |
def logged_in? | |
!!@state.user_id | |
end | |
def require_login! | |
unless logged_in? | |
redirect(R(Blog::Controllers::Login)) # @techarch: add explicit route | |
throw :halt | |
end | |
end | |
end | |
module Blog::Views | |
extend Reststop::Views | |
module HTML | |
include Blog::Controllers | |
include Blog::Views | |
def layout | |
html do | |
head do | |
title 'blog' | |
link :rel => 'stylesheet', :type => 'text/css', | |
:href => self/'/styles.css', :media => 'screen' | |
end | |
body do | |
h1.header { a 'blog', :href => R(Posts) } | |
div.content do | |
self << yield | |
end | |
end | |
end | |
end | |
def index | |
if @posts.empty? | |
p 'No posts found.' | |
else | |
for post in @posts | |
_post(post) | |
end | |
end | |
p { a 'Add', :href => R(Posts, 'new') } | |
end | |
def login | |
p { b @login } | |
p { a 'Continue', :href => R(Posts, 'new') } | |
end | |
def logout | |
p "You have been logged out." | |
p { a 'Continue', :href => R(Posts) } | |
end | |
def add | |
if @user | |
_form(@post, :action => R(Posts)) | |
else | |
_login | |
end | |
end | |
def edit | |
if @user | |
_form(@post, :action => R(@post), :method => :put) | |
else | |
_login | |
end | |
end | |
def view | |
_post(@post) | |
p "Comment for this post:" | |
for c in @comments | |
h1 c.username | |
p c.body | |
end | |
form :action => R(Comments), :method => 'post' do | |
label 'Name', :for => 'post_username'; br | |
input :name => 'post_username', :type => 'text'; br | |
label 'Comment', :for => 'post_body'; br | |
textarea :name => 'post_body' do; end; br | |
input :type => 'hidden', :name => 'post_id', :value => @post.id | |
input :type => 'submit' | |
end | |
end | |
# partials | |
def _login | |
form :action => R(Sessions), :method => 'post' do | |
label 'Username', :for => 'username'; br | |
input :name => 'username', :type => 'text'; br | |
label 'Password', :for => 'password'; br | |
input :name => 'password', :type => 'text'; br | |
input :type => 'submit', :name => 'login', :value => 'Login' | |
end | |
end | |
def _post(post) | |
h1 post.title | |
p post.body | |
p do | |
[a("Edit", :href => R(Posts, post.id, 'edit')), a("View", :href => R(Posts, post.id, 'edit'))].join " | " | |
end | |
end | |
def _form(post, opts) | |
form(:action => R(Sessions), :method => 'delete') do | |
p do | |
span "You are logged in as #{@user.username}" | |
span " | " | |
button(:type => 'submit') {'Logout'} | |
end | |
end | |
form({:method => 'post'}.merge(opts)) do | |
label 'Title', :for => 'post_title'; br | |
input :name => 'post_title', :type => 'text', | |
:value => post.title; br | |
label 'Body', :for => 'post_body'; br | |
textarea post.body, :name => 'post_body'; br | |
input :type => 'hidden', :name => 'post_id', :value => post.id | |
input :type => 'submit' | |
end | |
end | |
end | |
default_format :HTML | |
module XML | |
def layout | |
yield | |
end | |
def index | |
@posts.to_xml(:root => 'blog') | |
end | |
def view | |
@post.to_xml(:root => 'post') | |
end | |
end | |
end | |
def Blog.create | |
dbconfig = YAML.load(File.read('config/database.yml')) # @techarch | |
Camping::Models::Base.establish_connection dbconfig['development'] # @techarch | |
Blog::Models.create_schema :assume => (Blog::Models::Post.table_exists? ? 1.0 : 0.0) | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$:.unshift File.dirname(__FILE__) | |
require 'net/http' | |
require 'net/https' | |
require 'uri' | |
require 'cgi' | |
require 'timeout' | |
begin | |
require 'xml_simple' | |
rescue LoadError | |
require 'rubygems' | |
require 'active_support' | |
begin | |
require 'xml_simple' | |
rescue LoadError | |
require 'xmlsimple' | |
end | |
end | |
# A very simple REST client, best explained by example: | |
# | |
# # Retrieve a Kitten and print its name and colour | |
# kitten = Restr.get('http://example.com/kittens/1.xml') | |
# puts kitten['name'] | |
# puts kitten['colour'] | |
# | |
# # Create a Kitten | |
# kitten = Restr.post('http://example.com/kittens.xml', | |
# :name => 'batman', :colour => 'black') | |
# | |
# # Update a Kitten | |
# kitten = Restr.put('http://example.com/kittens/1.xml', | |
# :age => '6 months') | |
# | |
# # Delete a Kitten :( | |
# kitten = Restr.delete('http://example.com/kittens/1.xml') | |
# | |
# # Retrieve a list of Kittens | |
# kittens = Restr.get('http://example.com/kittens.xml') | |
# | |
# When the response to a Restr request has content type 'text/xml', the | |
# response body will be parsed from XML into a nested Hash (using XmlSimple | |
# -- see http://xml-simple.rubyforge.org/). Otherwise the response is | |
# returned untouched, as a String. | |
# | |
# | |
# === Authentication | |
# | |
# If the remote REST resource requires authentication (Restr only supports | |
# HTTP Basic authentication, for now): | |
# | |
# Restr.get('http://example.com/kittens/1.xml, {}, | |
# {:username => 'foo', :password => 'bar'}) | |
# | |
# === Logging | |
# | |
# A standard Ruby Logger can be attached to the Restr client like so: | |
# | |
# logger = Logger.new('restr.log') | |
# logger.level = Logger::DEBUG | |
# Restr.logger = logger | |
# | |
# Restr will now log its activity to the given Logger. | |
# The default_logger can be overridden by supplying a :logger option to | |
# a client call: | |
# | |
# kitten_logger = Logger.new('kitten.log'} | |
# Restr.get('http://example.com/kittens/1.xml, {}, | |
# {:logger => kitten_logger) | |
class Restr | |
module VERSION #:nodoc: | |
MAJOR = 0 | |
MINOR = 5 | |
TINY = 2 | |
STRING = [MAJOR, MINOR, TINY].join('.') | |
end | |
@@logger = nil | |
@@request_timeout = 180 | |
cattr_accessor :request_timeout | |
def self.logger=(logger) | |
@@logger = logger.dup | |
# ActiveSupport's BufferedLogger doesn't seem to support progname= :( | |
@@logger.progname = self.name if @@logger.respond_to?(:progname) | |
end | |
def self.logger | |
@@logger | |
end | |
def self.method_missing(method, *args) | |
self.do(method, args[0], args[1] || {}, args[2]) | |
end | |
def self.do(method, url, params = {}, options = {}) | |
#puts "METHOD: #{method.inspect}" | |
#puts "URL: #{url.inspect}" | |
#puts "PARAMS: #{params.inspect}" | |
#puts "OPTIONS: #{options.inspect}" | |
uri = URI.parse(url) | |
params = {} unless params | |
options = {} unless options | |
logger = options[:logger] || self.logger | |
method_mod = method.to_s.downcase.capitalize | |
unless Net::HTTP.const_defined?(method_mod) | |
raise InvalidRequestMethod, | |
"Callback method #{method.inspect} is not a valid HTTP request method." | |
end | |
if method_mod == 'Get' | |
q = params.collect{|k,v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"}.join("&") | |
if uri.query | |
uri.query += "&#{q}" | |
else | |
uri.query = q | |
end | |
end | |
req = Net::HTTP.const_get(method_mod).new(uri.request_uri) | |
if options[:username] || options['username'] | |
req.basic_auth options[:username] || options['username'], options[:password] || options['password'] | |
end | |
if params.kind_of?(Hash) && method_mod != 'Get' && method_mod != 'get' | |
req.set_form_data(params, '&') | |
end | |
logger.debug("Sending #{method.inspect} request to #{url.inspect} "+ | |
(method.to_s == 'get' ? "params" : "data")+" #{params.inspect}"+ | |
(options.blank? ? "" : " with options #{options.inspect}}")+".") if logger | |
client = Net::HTTP.new(uri.host, uri.port) | |
client.use_ssl = (uri.scheme == 'https') | |
timeout = Restr.request_timeout | |
client.read_timeout = timeout | |
req.add_field("Cookie", @cookie) if @cookie # @techarch : added the cookie header | |
begin | |
res = client.start do |http| | |
http.request(req) | |
end | |
rescue Timeout::Error | |
res = TimeoutError, "Request timed out after #{timeout} seconds." | |
end | |
@cookie = res.response['Set-Cookie'] # @techarch : save the cookie so we can send it back later (in the next request) | |
case res | |
when Net::HTTPSuccess | |
if res.content_type =~ /[\/+]xml$/ | |
logger.debug("Got XML response: \n#{res.body}") if logger | |
return XmlSimple.xml_in(res.body, | |
'forcearray' => false, | |
'keeproot' => false | |
) | |
else | |
logger.debug("Got #{res.content_type.inspect} response: \n#{res.body}") if logger | |
return res.body | |
end | |
when TimeoutError | |
logger.debug(res) if logger | |
return XmlSimple.xml_in(res, | |
'forcearray' => false, | |
'keeproot' => false | |
) | |
else | |
$LAST_ERROR_BODY = res.body # FIXME: this is dumb... need a better way of reporting errors | |
$LAST_ERROR_RESPONSE = res # this is currently unused within Restr, but may be useful for debugging | |
logger.error("Got error response '#{res.message}(#{res.code})': #{res.body.blank? ? '(blank response body)' : res.body}") if logger | |
res.error! | |
end | |
end | |
class InvalidRequestMethod < Exception | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Unfinished attempt at re-writing Reststop for Camping 2.0 | |
# Oogly, oogly, oogly. | |
# | |
# Might be easier to just fork Camping, implement the restful stuff, and call it Resting :) | |
# | |
# I think all of the routing is taken care of, but there's something weird going on with #reststop_render | |
# Rack complains about invalid output (or something). | |
# | |
# Right now you'll have to do some weird gymnastics to get this hooked in to a Camping app... | |
# Something like: | |
# | |
# Camping.goes :Blog | |
# | |
# module Blog | |
# include Reststop | |
# end | |
# | |
# module Blog::Base | |
# alias camping_render render | |
# alias camping_service service | |
# include Reststop::Base | |
# alias service reststop_service | |
# alias render reststop_render | |
# end | |
# | |
# module Blog::Controllers | |
# extend Reststop::Controllers | |
# ... | |
# end | |
# | |
# module Blog::Helpers | |
# alias_method :_R, :R | |
# remove_method :R | |
# include Reststop::Helpers | |
# ... | |
# end | |
# | |
# module Blog::Views | |
# extend Reststop::Views | |
# ... | |
# end | |
# | |
# The hope is that this could all get taken care of in a | |
# `include Reststop` call (via overriding of #extended) | |
$LOG = Logger.new(STDOUT) | |
module Reststop | |
module Base | |
def reststop_service(*a) | |
if @env['REQUEST_METHOD'] == 'POST' && (input['_method'] == 'put' || input['_method'] == 'delete') | |
@env['REQUEST_METHOD'] = input._method.upcase | |
@method = input._method | |
end | |
@a0=a[0] if !a.empty? | |
camping_service(*a) | |
end | |
# Overrides Camping's render method to add the ability to specify a format | |
# module when rendering a view. | |
# | |
# The format can also be specified in other ways (shown in this order | |
# of precedence): | |
# | |
# 1. By providing a second parameter to render() | |
# (eg: <tt>render(:foo, :HTML)</tt>) | |
# 2. By setting the @format variable | |
# 3. By providing a 'format' parameter in the request (i.e. input[:format]) | |
# 4. By adding a file-format extension to the url (e.g. /items.xml or | |
# /items/2.html). | |
# | |
# For example, you could have: | |
# | |
# module Foobar::Views | |
# | |
# module HTML | |
# def foo | |
# # ... render some HTML content | |
# end | |
# end | |
# | |
# module RSS | |
# def foo | |
# # ... render some RSS content | |
# end | |
# end | |
# | |
# end | |
# | |
# Then in your controller, you would call render() like this: | |
# | |
# render(:foo, :HTML) # render the HTML version of foo | |
# | |
# or | |
# | |
# render(:foo, :RSS) # render the RSS version of foo | |
# | |
# or | |
# | |
# @format = :RSS | |
# render(:foo) # render the RSS version of foo | |
# | |
# or | |
# | |
# # url is /foobar/1?format=RSS | |
# render(:foo) # render the RSS version of foo | |
# | |
# or | |
# | |
# # url is /foobar/1.rss | |
# render(:foo) # render the RSS version of foo | |
# | |
# If no format is specified, render() will behave like it normally does in | |
# Camping, by looking for a matching view method directly | |
# in the Views module. | |
# | |
# You can also specify a default format module by calling | |
# <tt>default_format</tt> after the format module definition. | |
# For example: | |
# | |
# module Foobar::Views | |
# module HTML | |
# # ... etc. | |
# end | |
# default_format :HTML | |
# end | |
# | |
def reststop_render(action, format = nil) | |
format ||= @format | |
if format.nil? | |
begin | |
ct = CONTENT_TYPE | |
rescue NameError | |
ct = 'text/html' | |
end | |
@headers['Content-Type'] ||= ct | |
camping_render(action) | |
else | |
# m = Mab.new({}, self) # @techarch : replaced by section below | |
# mod = "Camping::Views::#{format.to_s}".constantize # @techarch : replaced by section below | |
# @techarch - begin added section | |
app_name = self.class.name.split("::").first # @techarch : get the name of the app | |
mab = (app_name + '::Mab').constantize # @techarch : get the Mab class | |
m = mab.new({}, self) # @techarch : instantiate Mab | |
mod = (app_name + "::Views::#{format.to_s}").constantize # @techarch : get the right Views format class | |
# @techarch - end added section | |
m.extend mod | |
begin | |
ct = mod::CONTENT_TYPE | |
rescue NameError | |
ct = "text/#{format.to_s.downcase}" | |
end | |
@headers['Content-Type'] = ct | |
s = m.capture{m.send(action)} | |
#s = m.capture{send(:layout){s}} if /^_/!~a[0].to_s and m.respond_to?(:layout) # @techarch : commented since a[0] no longer exist | |
s = m.capture{send(:layout){s}} if /^_/!~@method.to_s and m.respond_to?(:layout) # @techarch : replaced a[0] by @method (not 100% sure that's right though) | |
s | |
end | |
end | |
end | |
module Views | |
# Call this inside your Views module to set a default format. | |
# | |
# For example: | |
# | |
# module Foobar::Views | |
# module HTML | |
# # ... etc. | |
# end | |
# default_format :XML | |
# end | |
def default_format(m) | |
mod = "#{self}::#{m.to_s}".constantize | |
mab = self.to_s.gsub('::Views','::Mab').constantize # @techarch : get the Mab class | |
mab.class_eval{include mod} | |
end | |
end | |
module Helpers | |
# Overrides Camping's routing helper to make it possible to route RESTful resources. | |
# | |
# Some usage examples: | |
# | |
# R(Kittens) # /kittens | |
# R(Kittens, 'new') # /kittens/new | |
# R(Kittens, 1, 'meow') # /kittens/1/meow | |
# R(@kitten) # /kittens/1 | |
# R(@kitten, 'meow') # /kittens/1/meow | |
# R(Kittens, 'list', :colour => 'black') # /kittens/list?colour=black | |
# | |
# The current output format is retained, so if the current <tt>@format</tt> is <tt>:XML</tt>, | |
# the URL will be /kittens/1.xml rather than /kittens/1. | |
# | |
# Note that your controller names might not be loaded if you're calling <tt>R</tt> inside a | |
# view module. In that case you should use the fully qualified name (i.e. Myapp::Controllers::Kittens) | |
# or include the Controllers module into your view module. | |
def R(c, *g) | |
# @techarch : broke down the code to make it easier to troubleshoot | |
# @techarch : begin added section | |
cl = c.class.name.split("::").last.pluralize | |
app_name = c.class.name.split("::").first | |
ctrl_cl = app_name + '::Controllers' # @techarch : get to the Controllers using the current app | |
ctrl = (app_name != 'Class') ? ctrl_cl.constantize : Controllers | |
# @techarch - end added section | |
if ctrl.constants.include?(cl) #@techarch updated to use new cl variable | |
path = "/#{cl.underscore}/#{c.id}" | |
path << ".#{@format.to_s.downcase}" if @format | |
path << "/#{g.shift}" unless g.empty? | |
self / path | |
elsif c.respond_to?(:restful?) && c.restful? | |
base = c.name.split("::").last.underscore | |
id_or_action = g.shift | |
if id_or_action.to_s =~ /\d+/ #@techarch needed a to_s after id_or_action to allow pattern matching | |
id = id_or_action | |
action = g.shift | |
else | |
action = id_or_action | |
end | |
path = "/#{base}" | |
path << "/#{id}" if id | |
path << "/#{action}" if action | |
path << ".#{@format.to_s.downcase}" if @format | |
#@techarch substituted U for u=Rack::Utils | |
u=Rack::Utils | |
path << "?#{g.collect{|a|a.collect{|k,v| u.escape(k)+"="+u.escape(v)}.join("&")}.join("&")}" unless g.empty? # FIXME: undefined behaviour if there are multiple arguments left | |
return path | |
else | |
_R(c, *g) | |
end | |
end # def R | |
end # module Helpers | |
module Controllers | |
def self.determine_format(input, env) #:nodoc: | |
if input[:format] && !input[:format].empty? | |
input[:format].upcase.intern | |
elsif env['PATH_INFO'] =~ /\.([a-z]+)$/ | |
$~[1].upcase.intern | |
end | |
end | |
# Calling <tt>REST "<resource name>"</tt> 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 <tt>R <routes></tt>. | |
# | |
# 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. <b>Your class must have the same (but CamelCaps'ed) | |
# name as the resource name.</b> 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 <tt>:prefix</tt> 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. <tt>POST '/pets/kittens'</tt>, <tt>GET '/pets/kittens/(\d+)'</tt>, | |
# 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 | |
# <tt>GET</tt> call to <tt>/kittens.xml</tt>. | |
# See the documentation for the render() method for more info. | |
# | |
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 #{'pull version number here'}") 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 = Reststop::Controllers.determine_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 = Reststop::Controllers.determine_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 = Reststop::Controllers.determine_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 = Reststop::Controllers.determine_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 # def REST | |
end # module Controllers | |
end # module Reststop | |
module Markaby | |
class Builder | |
# Modifies Markaby's 'form' generator so that if a 'method' parameter | |
# is supplied, a hidden '_method' input is automatically added. | |
def form(*args, &block) | |
options = args[0] if args && args[0] && args[0].kind_of?(Hash) | |
inside = capture &block | |
if options && options.has_key?(:method) | |
inside = input(:type => 'hidden', :name => '_method', :value => options[:method]) + | |
inside | |
if options[:method].to_s === 'put' || options[:method].to_s == 'delete' | |
options[:method] = 'post' | |
end | |
end | |
tag!(:form, options || args[0]) {inside} | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Prerequisites: | |
# 1) gem install xml-simple | |
# 2) gem install restr | |
gem 'xml-simple' | |
gem 'restr' | |
require 'restr' | |
logger = Logger.new('restr.log') | |
logger.level = Logger::DEBUG | |
Restr.logger = logger | |
u0 = "http://localhost:3301/sessions.xml" | |
o = { :username=>'admin', :password=>'camping'} | |
p0=Restr.post(u0,o) | |
u1 = "http://localhost:3301/posts/1.xml" | |
p = Restr.get(u1,o) | |
# Modify the title | |
p['title']='HOT off the presses: ' + p['title'] | |
# Update the resource | |
p2=Restr.put(u1,p,o) | |
u3="http://localhost:3301/posts.xml" | |
p3={ :title=>'Brand new REST-issued post', :body=>'RESTstop makes it happen!!!'} | |
p4=Restr.post(u2,p3) | |
u3="http://localhost:3301/posts/4.xml" | |
p5=Restr.delete(u3) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment