roy 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in roy.gemspec
4
+ gemspec
@@ -0,0 +1,20 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ roy (0.0.1)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ minitest (2.6.1)
10
+ rack (1.3.4)
11
+ rack-test (0.6.1)
12
+ rack (>= 1.0)
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ minitest
19
+ rack-test
20
+ roy!
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009-2011 François "madx" Vaux <madx@yapok.org>
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,83 @@
1
+ Roy
2
+ ===
3
+
4
+ Roy is a tiny module that aims to make any Ruby object Rack-friendly and provite
5
+ it with a REST-like interface.
6
+
7
+ Roy tries to be as less invasive as possible. In provides your objects with a
8
+ `#call` method that takes a Rack environment and dispatchs to a regular method
9
+ named after the HTTP method you want to catch.
10
+
11
+ ## Tests
12
+
13
+ You can run the tests by running `rake test`. They are written with Minitest.
14
+
15
+ ## Example
16
+
17
+ ``` ruby
18
+ class MessageQueue
19
+ include Roy
20
+
21
+ roy allow: [:get, :post, :delete]
22
+
23
+ def initialize
24
+ @stack = []
25
+ end
26
+
27
+ def get
28
+ @stack.inspect
29
+ end
30
+
31
+ def post
32
+ halt 403 unless roy.params[:item]
33
+ @stack << roy.params[:item].strip
34
+ get
35
+ end
36
+
37
+ def delete
38
+ @stack.shift.inspect
39
+ end
40
+ end
41
+ ```
42
+
43
+ ## Docs
44
+
45
+ ### Configuration
46
+
47
+ The `roy` class method is used to define access control and method prefix. The
48
+ following example should be self-explanatory enough:
49
+
50
+ ``` ruby
51
+ class Example
52
+ include Roy
53
+ roy allow: [:get], prefix: :http_
54
+
55
+ def http_get(*args)
56
+ "get"
57
+ end
58
+ end
59
+ ```
60
+
61
+ ### Environement
62
+
63
+ Inside your handler methods, you have access to a `roy` readable attribute which
64
+ is a struct containing the following fields:
65
+
66
+ * `env`: the Rack environment
67
+ * `response`: a `Rack::Response` object that will be returned by `call`
68
+ * `request`: a `Rack::Request` build from the environment
69
+ * `header`: a hash of headers that is part of `response`
70
+ * `params`: parameters extracted from the query string and the request body
71
+ * `conf`: the configuration set via `::roy`
72
+
73
+ The keys for `params` can be accessed either via a `String` or a `Symbol`
74
+
75
+ ### Control flow
76
+
77
+ Your handler methods are run inside a `catch` block which will catch the `:halt`
78
+ symbol. You can then use `throw` to abort a method but you must return an array
79
+ composed of a status code and a message.
80
+
81
+ Roy provides a `halt` method that takes a status code and an optional message.
82
+ If there is no message it uses the default message from
83
+ `Rack::Utils::HTTP_STATUS_CODES`
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.pattern = 'test/*_test.rb'
6
+ end
@@ -0,0 +1,67 @@
1
+ require 'set'
2
+ require 'rack'
3
+ require 'roy/version'
4
+
5
+ module Roy
6
+ Env = Struct.new(:env, :request, :response, :headers, :params, :conf)
7
+
8
+ def self.included(base)
9
+ base.send(:extend, ClassMethods)
10
+ end
11
+
12
+ attr_reader :roy
13
+
14
+ def call(env)
15
+ @roy = Env.new.tap { |e|
16
+ e.env = env
17
+ e.request = Rack::Request.new(env)
18
+ e.response = Rack::Response.new
19
+ e.headers = e.response.header
20
+ e.params = e.request.GET.merge(e.request.POST)
21
+ e.params.default_proc = proc do |hash, key|
22
+ hash[key.to_s] if Symbol === key
23
+ end
24
+ e.conf = self.class.conf
25
+ }
26
+
27
+ method = roy.env['REQUEST_METHOD'].downcase.to_sym
28
+ args = roy.env['PATH_INFO'].sub(/^\/+/, '').split(/\/+/).map { |arg|
29
+ Rack::Utils.unescape(arg)
30
+ }
31
+
32
+ method, was_head = :get, true if method == :head
33
+
34
+ roy.response.status, body = catch(:halt) do
35
+ halt(405) unless roy.conf.allow.include?(method)
36
+ prefixed_method = :"#{roy.conf.prefix}#{method}"
37
+ [roy.response.status, send(prefixed_method, *args)]
38
+ end
39
+
40
+ roy.response.write(body) unless was_head
41
+ roy.response.finish
42
+ end
43
+
44
+ def halt(code, message=nil)
45
+ throw :halt, [code, message || Rack::Utils::HTTP_STATUS_CODES[code]]
46
+ end
47
+
48
+ module ClassMethods
49
+ attr_reader :conf
50
+
51
+ def roy(options={})
52
+ @conf ||= Struct.new(:allow, :prefix).new
53
+ conf.allow ||= Set.new
54
+ conf.prefix ||= :''
55
+
56
+ options.each do |k,v|
57
+ case k
58
+ when :allow
59
+ conf.allow.merge(v)
60
+ conf.allow.add(:head) if v.member?(:get)
61
+ when :prefix
62
+ conf.prefix = v
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ module Roy
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "roy/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "roy"
7
+ s.version = Roy::VERSION
8
+ s.authors = ["madx"]
9
+ s.email = ["madx@yapok.org"]
10
+ s.homepage = "https://github.com/madx/roy"
11
+ s.summary = 'make your objects REST-friendly'
12
+ s.description =
13
+ "roy is a small library which allows every Ruby object to be used\n" <<
14
+ "as a Rack application."
15
+
16
+ s.rubyforge_project = "roy"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+
23
+ # specify any dependencies here; for example:
24
+ s.add_development_dependency "minitest"
25
+ s.add_development_dependency "rack-test"
26
+ # s.add_runtime_dependency "rest-client"
27
+ end
@@ -0,0 +1,91 @@
1
+ require_relative 'helper'
2
+
3
+ class RemoteLog
4
+ include Roy
5
+
6
+ attr_reader :history
7
+
8
+ def initialize
9
+ @history = []
10
+ end
11
+
12
+ roy allow: [:get, :put, :custom]
13
+
14
+ def get(*args)
15
+ history.inspect
16
+ end
17
+
18
+ def put(*args)
19
+ Roy.halt 400 unless roy.params[:body]
20
+ history << roy.params[:body]
21
+ history << roy.params[:foo] if roy.params[:foo]
22
+ get
23
+ end
24
+
25
+ def custom(*args)
26
+ args.join('+')
27
+ end
28
+ end
29
+
30
+ class RoyTest < MiniTest::Unit::TestCase
31
+ include Rack::Test::Methods
32
+
33
+ def app
34
+ RemoteLog.new
35
+ end
36
+
37
+ def test_provide_call
38
+ assert_respond_to app, :call
39
+ end
40
+
41
+ def test_provide_roy
42
+ assert_respond_to app.class, :roy
43
+ end
44
+
45
+ def test_forward_allowed_methods
46
+ get '/'
47
+ ok!
48
+ assert_equal app.get, last_response.body
49
+ end
50
+
51
+ def test_block_forbidden_methods
52
+ post '/'
53
+ fail!
54
+ assert_equal 405, last_response.status
55
+ end
56
+
57
+
58
+ def test_set_allowed_methods
59
+ assert_includes app.class.conf.allow, :get
60
+ assert_includes app.class.conf.allow, :put
61
+ refute_includes app.class.conf.allow, :post
62
+ end
63
+
64
+ def test_allowing_get_allows_head
65
+ assert_includes app.class.conf.allow, :head
66
+ end
67
+
68
+ def test_roy_halt
69
+ assert_throws :halt do
70
+ app.halt 200
71
+ end
72
+ end
73
+
74
+ def test_head_does_not_have_contents
75
+ head '/'
76
+ ok!
77
+ assert_equal '', last_response.body
78
+ end
79
+
80
+ def test_params
81
+ put '/?foo=bar', :body => 'hello'
82
+ ok!
83
+ assert_equal %w(hello bar).inspect, last_response.body
84
+ end
85
+
86
+ def test_path_components_as_method_arguments
87
+ request '/a/b/c', :method => 'CUSTOM'
88
+ ok!
89
+ assert_equal 'a+b+c', last_response.body
90
+ end
91
+ end
@@ -0,0 +1,20 @@
1
+ require 'bundler'
2
+ Bundler.require(:default, :development)
3
+
4
+ require 'minitest/autorun'
5
+ require 'rack/test'
6
+ require 'roy'
7
+
8
+ module CustomTestMethods
9
+ private
10
+
11
+ def ok!
12
+ assert_predicate last_response, :ok?
13
+ end
14
+
15
+ def fail!
16
+ refute_predicate last_response, :ok?
17
+ end
18
+ end
19
+
20
+ Rack::Test::Methods.send(:include, CustomTestMethods)
@@ -0,0 +1,24 @@
1
+ require_relative 'helper'
2
+
3
+ class TestObject
4
+ include Roy
5
+
6
+ roy allow: [:get], prefix: :http_
7
+
8
+ def http_get(*args)
9
+ 'success'
10
+ end
11
+ end
12
+
13
+ class PrefixTest < MiniTest::Unit::TestCase
14
+ include Rack::Test::Methods
15
+
16
+ def app
17
+ TestObject.new
18
+ end
19
+
20
+ def test_prefixing
21
+ get '/'
22
+ assert_equal 'success', last_response.body
23
+ end
24
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: roy
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - madx
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-10-19 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: minitest
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ type: :development
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: rack-test
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: "0"
35
+ type: :development
36
+ version_requirements: *id002
37
+ description: |-
38
+ roy is a small library which allows every Ruby object to be used
39
+ as a Rack application.
40
+ email:
41
+ - madx@yapok.org
42
+ executables: []
43
+
44
+ extensions: []
45
+
46
+ extra_rdoc_files: []
47
+
48
+ files:
49
+ - Gemfile
50
+ - Gemfile.lock
51
+ - LICENSE
52
+ - README.md
53
+ - Rakefile
54
+ - lib/roy.rb
55
+ - lib/roy/version.rb
56
+ - roy.gemspec
57
+ - test/base_test.rb
58
+ - test/helper.rb
59
+ - test/prefix_test.rb
60
+ homepage: https://github.com/madx/roy
61
+ licenses: []
62
+
63
+ post_install_message:
64
+ rdoc_options: []
65
+
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ requirements: []
81
+
82
+ rubyforge_project: roy
83
+ rubygems_version: 1.8.5
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: make your objects REST-friendly
87
+ test_files:
88
+ - test/base_test.rb
89
+ - test/helper.rb
90
+ - test/prefix_test.rb