sinatras-hat 0.0.3 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/core_ext/array.rb +0 -12
- data/lib/core_ext/hash.rb +2 -3
- data/lib/core_ext/module.rb +14 -0
- data/lib/core_ext/object.rb +0 -4
- data/lib/sinatras-hat.rb +19 -19
- data/lib/sinatras-hat/actions.rb +44 -59
- data/lib/sinatras-hat/{auth.rb → authentication.rb} +9 -13
- data/lib/sinatras-hat/extendor.rb +22 -0
- data/lib/sinatras-hat/hash_mutator.rb +18 -0
- data/lib/sinatras-hat/logger.rb +36 -0
- data/lib/sinatras-hat/maker.rb +97 -163
- data/lib/sinatras-hat/model.rb +77 -0
- data/lib/sinatras-hat/resource.rb +47 -0
- data/lib/sinatras-hat/responder.rb +94 -0
- data/lib/sinatras-hat/response.rb +53 -0
- data/lib/sinatras-hat/router.rb +42 -0
- metadata +15 -18
- data/lib/sinatras-hat/action.rb +0 -15
- data/lib/sinatras-hat/responses.rb +0 -62
data/lib/core_ext/array.rb
CHANGED
@@ -2,16 +2,4 @@ class Array
|
|
2
2
|
def extract_options!
|
3
3
|
last.is_a?(Hash) ? pop : { }
|
4
4
|
end
|
5
|
-
|
6
|
-
def move_to_front(*entries)
|
7
|
-
entries.each do |entry|
|
8
|
-
unshift(entry) if delete(entry)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def move_to_back(*entries)
|
13
|
-
entries.each do |entry|
|
14
|
-
push(entry) if delete(entry)
|
15
|
-
end
|
16
|
-
end
|
17
5
|
end
|
data/lib/core_ext/hash.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
class Hash
|
2
2
|
def make_indifferent!
|
3
3
|
keys_values = self.dup
|
4
|
-
|
5
|
-
replace(indifferent)
|
4
|
+
replace(Hash.new { |h,k| h[k.to_s] if Symbol === k })
|
6
5
|
merge!(keys_values)
|
7
6
|
end
|
8
7
|
|
@@ -10,7 +9,7 @@ class Hash
|
|
10
9
|
new_params = Hash.new.make_indifferent!
|
11
10
|
each_pair do |full_key, value|
|
12
11
|
this_param = new_params
|
13
|
-
split_keys = full_key.split(/\]\[|\]|\[/)
|
12
|
+
split_keys = full_key.to_s.split(/\]\[|\]|\[/)
|
14
13
|
split_keys.each_index do |index|
|
15
14
|
break if split_keys.length == index + 1
|
16
15
|
this_param[split_keys[index]] ||= Hash.new.make_indifferent!
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Module
|
2
|
+
def delegate(*methods)
|
3
|
+
options = methods.pop
|
4
|
+
raise ArgumentError, "Delegation needs a target." unless options.is_a?(Hash) && to = options[:to]
|
5
|
+
|
6
|
+
methods.each do |method|
|
7
|
+
module_eval(<<-EOS, "(__DELEGATION__)", 1)
|
8
|
+
def #{method}(*args, &block)
|
9
|
+
#{to}.__send__(#{method.inspect}, *args, &block)
|
10
|
+
end
|
11
|
+
EOS
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/core_ext/object.rb
CHANGED
data/lib/sinatras-hat.rb
CHANGED
@@ -1,23 +1,23 @@
|
|
1
|
-
$LOAD_PATH << File.join(File.dirname(__FILE__)
|
2
|
-
$LOAD_PATH << File.join(File.dirname(__FILE__), 'sinatras-hat')
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__))
|
3
2
|
|
4
|
-
require '
|
3
|
+
require 'rubygems'
|
4
|
+
require 'benchmark'
|
5
|
+
require 'sinatra/base'
|
5
6
|
require 'extlib'
|
6
|
-
require 'dm-core'
|
7
|
-
require 'dm-serializer'
|
8
|
-
require 'array'
|
9
|
-
require 'hash'
|
10
|
-
require 'object'
|
11
|
-
require 'action'
|
12
|
-
require 'actions'
|
13
|
-
require 'responses'
|
14
|
-
require 'maker'
|
15
7
|
|
16
|
-
|
8
|
+
require 'core_ext/array'
|
9
|
+
require 'core_ext/hash'
|
10
|
+
require 'core_ext/object'
|
11
|
+
require 'core_ext/module'
|
17
12
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
13
|
+
require 'sinatras-hat/logger'
|
14
|
+
require 'sinatras-hat/extendor'
|
15
|
+
require 'sinatras-hat/authentication'
|
16
|
+
require 'sinatras-hat/hash_mutator'
|
17
|
+
require 'sinatras-hat/resource'
|
18
|
+
require 'sinatras-hat/response'
|
19
|
+
require 'sinatras-hat/responder'
|
20
|
+
require 'sinatras-hat/model'
|
21
|
+
require 'sinatras-hat/router'
|
22
|
+
require 'sinatras-hat/actions'
|
23
|
+
require 'sinatras-hat/maker'
|
data/lib/sinatras-hat/actions.rb
CHANGED
@@ -1,65 +1,50 @@
|
|
1
1
|
module Sinatra
|
2
2
|
module Hat
|
3
|
+
# Contains all of the actions that Sinatra's Hat supports.
|
4
|
+
# Each action states a name, a path, optionally, the HTTP
|
5
|
+
# verb, then a block which takes a request object, optionally
|
6
|
+
# loads data using the :finder or :record options, then
|
7
|
+
# responds, based on whether or not the action was a success
|
8
|
+
#
|
9
|
+
# NOTE: only the :create action renders a different :failure
|
3
10
|
module Actions
|
4
|
-
def
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
def update!
|
42
|
-
map :update, '/:id', :verb => :put do |params|
|
43
|
-
update[call(:record, params), parse_for_attributes(params)]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def destroy!
|
48
|
-
map :destroy, '/:id', :verb => :delete do |params|
|
49
|
-
destroy[call(:record, params), parse_for_attributes(params)]
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def parse_for_attributes(params, name=model.name.downcase)
|
56
|
-
if handler = accepts[params[:format].try(:to_sym)]
|
57
|
-
params.merge(name => handler.call(params[name]))
|
58
|
-
else
|
59
|
-
params.nest!
|
60
|
-
params[name] ||= { }
|
61
|
-
params[name][parent.model_id] = params[parent.model_id] if parent
|
62
|
-
params
|
11
|
+
def self.included(map)
|
12
|
+
map.action :destroy, '/:id', :verb => :delete do |request|
|
13
|
+
record = model.find(request.params) || responder.not_found(request)
|
14
|
+
record.destroy
|
15
|
+
responder.success(:destroy, request, record)
|
16
|
+
end
|
17
|
+
|
18
|
+
map.action :new, '/new' do |request|
|
19
|
+
new_record = model.new(request.params)
|
20
|
+
responder.success(:new, request, new_record)
|
21
|
+
end
|
22
|
+
|
23
|
+
map.action :update, '/:id', :verb => :put do |request|
|
24
|
+
record = model.update(request.params) || responder.not_found(request)
|
25
|
+
result = record.save ? :success : :failure
|
26
|
+
responder.send(result, :update, request, record)
|
27
|
+
end
|
28
|
+
|
29
|
+
map.action :edit, '/:id/edit' do |request|
|
30
|
+
record = model.find(request.params) || responder.not_found(request)
|
31
|
+
responder.success(:edit, request, record)
|
32
|
+
end
|
33
|
+
|
34
|
+
map.action :show, '/:id' do |request|
|
35
|
+
record = model.find(request.params) || responder.not_found(request)
|
36
|
+
responder.success(:show, request, record)
|
37
|
+
end
|
38
|
+
|
39
|
+
map.action :create, '/', :verb => :post do |request|
|
40
|
+
record = model.new(request.params)
|
41
|
+
result = record.save ? :success : :failure
|
42
|
+
responder.send(result, :create, request, record)
|
43
|
+
end
|
44
|
+
|
45
|
+
map.action :index, '/' do |request|
|
46
|
+
records = model.all(request.params)
|
47
|
+
responder.success(:index, request, records)
|
63
48
|
end
|
64
49
|
end
|
65
50
|
end
|
@@ -3,10 +3,10 @@
|
|
3
3
|
module Sinatra
|
4
4
|
module Authorization
|
5
5
|
class ProtectedAction
|
6
|
-
attr_reader :credentials, :
|
6
|
+
attr_reader :credentials, :request, :block
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
@credentials, @
|
8
|
+
def initialize(request, credentials={}, &block)
|
9
|
+
@credentials, @request, @block = credentials, request, block
|
10
10
|
end
|
11
11
|
|
12
12
|
def check!
|
@@ -26,7 +26,7 @@ module Sinatra
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def unauthorized!
|
29
|
-
|
29
|
+
request.response.headers['WWW-Authenticate'] = %(Basic realm="#{credentials[:realm]}")
|
30
30
|
throw :halt, [ 401, 'Authorization Required' ]
|
31
31
|
end
|
32
32
|
|
@@ -35,25 +35,21 @@ module Sinatra
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def auth
|
38
|
-
@auth ||= Rack::Auth::Basic::Request.new(
|
38
|
+
@auth ||= Rack::Auth::Basic::Request.new(request.env)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
module Helpers
|
43
|
-
def protect!(
|
44
|
-
return if authorized?
|
45
|
-
guard = ProtectedAction.new(
|
43
|
+
def protect!(request)
|
44
|
+
return if authorized?(request)
|
45
|
+
guard = ProtectedAction.new(request, credentials, &authenticator)
|
46
46
|
guard.check!
|
47
47
|
request.env['REMOTE_USER'] = guard.remote_user
|
48
48
|
end
|
49
49
|
|
50
|
-
def authorized?
|
50
|
+
def authorized?(request)
|
51
51
|
request.env['REMOTE_USER']
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
56
|
-
|
57
|
-
helpers do
|
58
|
-
include Sinatra::Authorization::Helpers
|
59
|
-
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Hat
|
3
|
+
# This module gives both Sinatra::Base and Sinatra::Hat::Maker
|
4
|
+
# the #mount method, which is used to mount resources. When
|
5
|
+
# mount is called in an instance of Maker, it sets the new
|
6
|
+
# instance's parent.
|
7
|
+
module Extendor
|
8
|
+
def mount(klass, options={}, &block)
|
9
|
+
use Rack::MethodOverride unless kind_of?(Sinatra::Hat::Maker)
|
10
|
+
|
11
|
+
Maker.new(klass, options).tap do |maker|
|
12
|
+
maker.parent = self if kind_of?(Sinatra::Hat::Maker)
|
13
|
+
maker.setup(@app || self)
|
14
|
+
maker.instance_eval(&block) if block_given?
|
15
|
+
maker.generate_routes!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Sinatra::Base.extend(Sinatra::Hat::Extendor)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Hat
|
3
|
+
# Used for specifying custom responses using a corny DSL.
|
4
|
+
class HashMutator
|
5
|
+
def initialize(hash)
|
6
|
+
@hash = hash
|
7
|
+
end
|
8
|
+
|
9
|
+
def success(&block)
|
10
|
+
@hash[:success] = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def failure(&block)
|
14
|
+
@hash[:failure] = block
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Hat
|
3
|
+
# TODO This needs to be using Rack::CommonLogger
|
4
|
+
class Logger
|
5
|
+
def initialize(maker)
|
6
|
+
@maker = maker
|
7
|
+
end
|
8
|
+
|
9
|
+
def info(msg)
|
10
|
+
say msg
|
11
|
+
end
|
12
|
+
|
13
|
+
def debug(msg)
|
14
|
+
say msg
|
15
|
+
end
|
16
|
+
|
17
|
+
def warn(msg)
|
18
|
+
say msg
|
19
|
+
end
|
20
|
+
|
21
|
+
def error(msg)
|
22
|
+
say msg
|
23
|
+
end
|
24
|
+
|
25
|
+
def fatal(msg)
|
26
|
+
say msg
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def say(msg)
|
32
|
+
puts msg if @maker.app and @maker.app.logging
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/sinatras-hat/maker.rb
CHANGED
@@ -1,227 +1,161 @@
|
|
1
1
|
module Sinatra
|
2
2
|
module Hat
|
3
|
+
# This is where it all comes together
|
3
4
|
class Maker
|
4
|
-
|
5
|
-
|
5
|
+
include Sinatra::Hat::Extendor
|
6
|
+
include Sinatra::Authorization::Helpers
|
6
7
|
|
7
|
-
|
8
|
+
attr_reader :klass, :app
|
8
9
|
|
9
|
-
def
|
10
|
-
@
|
11
|
-
with(options)
|
12
|
-
end
|
13
|
-
|
14
|
-
def define(context, opts={}, &block)
|
15
|
-
@context = context
|
16
|
-
@options.merge!(opts)
|
17
|
-
instance_eval(&block) if block_given?
|
18
|
-
generate_actions!
|
10
|
+
def self.actions
|
11
|
+
@actions ||= { }
|
19
12
|
end
|
20
13
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
14
|
+
# enables the douche-y DSL you see in actions.rb
|
15
|
+
def self.action(name, path, options={}, &block)
|
16
|
+
verb = options[:verb] || :get
|
17
|
+
Router.cache << [verb, name, path]
|
18
|
+
actions[name] = { :path => path, :verb => verb, :fn => block }
|
26
19
|
end
|
20
|
+
|
21
|
+
include Sinatra::Hat::Actions
|
27
22
|
|
28
|
-
|
29
|
-
resources = parents + [self]
|
30
|
-
path = resources.inject("") do |memo, maker|
|
31
|
-
memo += eql?(maker) ?
|
32
|
-
"/#{maker.prefix}" :
|
33
|
-
"/#{maker.prefix}/:#{maker.model.name}_id"
|
34
|
-
end
|
35
|
-
(path + suffix).tap do |s|
|
36
|
-
s.downcase!
|
37
|
-
s.gsub!(%r(/$), '')
|
38
|
-
end
|
39
|
-
end
|
23
|
+
# ======================================================
|
40
24
|
|
41
|
-
def
|
42
|
-
@
|
43
|
-
|
44
|
-
|
45
|
-
def protect(*args)
|
46
|
-
opts = args.extract_options!
|
47
|
-
credentials.update(opts)
|
48
|
-
actions = get_or_set_option(:protect, args) do
|
49
|
-
self.protect += args
|
50
|
-
self.protect.uniq!
|
51
|
-
end
|
52
|
-
|
53
|
-
[actions].flatten
|
25
|
+
def initialize(klass, overrides={})
|
26
|
+
@klass = klass
|
27
|
+
options.merge!(overrides)
|
28
|
+
with(options)
|
54
29
|
end
|
55
|
-
|
56
|
-
def
|
57
|
-
|
58
|
-
self.only = args
|
59
|
-
self.only.uniq!
|
60
|
-
end
|
61
|
-
|
62
|
-
result = Array(result)
|
63
|
-
|
64
|
-
result.move_to_back(:index)
|
65
|
-
result.move_to_front(:new, :edit)
|
66
|
-
|
67
|
-
result
|
30
|
+
|
31
|
+
def setup(app)
|
32
|
+
@app = app
|
68
33
|
end
|
69
34
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
73
|
-
self.children.uniq!
|
74
|
-
end
|
35
|
+
def handle(action, request)
|
36
|
+
request.error(404) unless only.include?(action)
|
37
|
+
protect!(request) if protect.include?(action)
|
75
38
|
|
76
|
-
|
39
|
+
log_with_benchmark(request, action) do
|
40
|
+
instance_exec(request, &self.class.actions[action][:fn])
|
41
|
+
end
|
77
42
|
end
|
78
43
|
|
79
|
-
def
|
80
|
-
|
81
|
-
@authenticator = block
|
82
|
-
else
|
83
|
-
@authenticator ||= proc { |username, password|
|
84
|
-
credentials[:username] == username and credentials[:password] == password
|
85
|
-
}
|
86
|
-
end
|
44
|
+
def after(action)
|
45
|
+
yield HashMutator.new(responder.defaults[action])
|
87
46
|
end
|
88
47
|
|
89
48
|
def finder(&block)
|
90
49
|
if block_given?
|
91
|
-
|
50
|
+
options[:finder] = block
|
92
51
|
else
|
93
|
-
|
52
|
+
options[:finder]
|
94
53
|
end
|
95
54
|
end
|
96
55
|
|
97
56
|
def record(&block)
|
98
57
|
if block_given?
|
99
|
-
|
58
|
+
options[:record] = block
|
100
59
|
else
|
101
|
-
|
60
|
+
options[:record]
|
102
61
|
end
|
103
62
|
end
|
104
63
|
|
105
|
-
def
|
64
|
+
def authenticator(&block)
|
106
65
|
if block_given?
|
107
|
-
|
66
|
+
options[:authenticator] = block
|
108
67
|
else
|
109
|
-
|
110
|
-
result = model.new
|
111
|
-
result.attributes = params[model_name]
|
112
|
-
result.save ? result : nil
|
113
|
-
end
|
68
|
+
options[:authenticator]
|
114
69
|
end
|
115
70
|
end
|
116
71
|
|
117
|
-
def
|
118
|
-
if
|
119
|
-
|
72
|
+
def only(*actions)
|
73
|
+
if actions.empty?
|
74
|
+
options[:only] ||= Set.new(options[:only])
|
120
75
|
else
|
121
|
-
|
122
|
-
record.attributes = params[model_name]
|
123
|
-
record.save ? record : false
|
124
|
-
end
|
76
|
+
Set.new(options[:only] = actions)
|
125
77
|
end
|
126
78
|
end
|
127
79
|
|
128
|
-
def
|
129
|
-
|
130
|
-
|
80
|
+
def protect(*actions)
|
81
|
+
credentials.merge!(actions.extract_options!)
|
82
|
+
|
83
|
+
if actions.empty?
|
84
|
+
options[:protect] ||= Set.new([])
|
131
85
|
else
|
132
|
-
|
133
|
-
|
134
|
-
:
|
135
|
-
end
|
86
|
+
actions == [:all] ?
|
87
|
+
Set.new(options[:protect] = only) :
|
88
|
+
Set.new(options[:protect] = actions)
|
136
89
|
end
|
137
90
|
end
|
138
91
|
|
139
|
-
def
|
140
|
-
|
141
|
-
|
142
|
-
actions[name] = Action.new(self, name, block)
|
92
|
+
def prefix
|
93
|
+
options[:prefix] ||= model.plural
|
94
|
+
end
|
143
95
|
|
144
|
-
|
145
|
-
|
146
|
-
klass.templated(self, name, opts)
|
147
|
-
rescue Errno::ENOENT => e
|
148
|
-
klass.rescue_template_error(e)
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
context.send(opts[:verb], "#{resource_path(path)}.:format") do
|
153
|
-
begin
|
154
|
-
klass.serialized(self, name, opts)
|
155
|
-
rescue UnsupportedFormat => e
|
156
|
-
klass.rescue_format_error(e)
|
157
|
-
end
|
158
|
-
end
|
96
|
+
def parents
|
97
|
+
@parents ||= parent ? Array(parent) + parent.parents : []
|
159
98
|
end
|
160
99
|
|
161
|
-
def
|
162
|
-
|
163
|
-
406, [
|
164
|
-
"The `#{e.format}` format is not supported.\n",
|
165
|
-
"Valid Formats: #{accepts.keys.join(', ')}\n",
|
166
|
-
].join("\n")
|
167
|
-
]
|
100
|
+
def resource_path(*args)
|
101
|
+
resource.path(*args)
|
168
102
|
end
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
103
|
+
|
104
|
+
def options
|
105
|
+
@options ||= {
|
106
|
+
:only => Set.new(Maker.actions.keys),
|
107
|
+
:parent => nil,
|
108
|
+
:finder => proc { |model, params| model.all },
|
109
|
+
:record => proc { |model, params| model.find_by_id(params[:id]) },
|
110
|
+
:protect => [ ],
|
111
|
+
:formats => { },
|
112
|
+
:credentials => { :username => 'username', :password => 'password', :realm => "The App" },
|
113
|
+
:authenticator => proc { |username, password| [username, password] == [:username, :password].map(&credentials.method(:[])) }
|
114
|
+
}
|
175
115
|
end
|
176
116
|
|
177
|
-
def
|
178
|
-
|
117
|
+
def inspect
|
118
|
+
"maker: #{klass}"
|
179
119
|
end
|
180
120
|
|
181
|
-
def
|
182
|
-
|
183
|
-
fake_params = params.dup
|
184
|
-
fake_params.merge!("id" => params[parent.model_id])
|
185
|
-
fake_params.make_indifferent!
|
186
|
-
parent.call(:record, fake_params).try(prefix) || model
|
121
|
+
def generate_routes!
|
122
|
+
Router.new(self).generate(@app)
|
187
123
|
end
|
188
124
|
|
189
|
-
def
|
190
|
-
|
125
|
+
def responder
|
126
|
+
@responder ||= Responder.new(self)
|
191
127
|
end
|
192
128
|
|
193
|
-
def
|
194
|
-
model.
|
129
|
+
def model
|
130
|
+
@model ||= Model.new(self)
|
195
131
|
end
|
196
132
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
:prefix => Extlib::Inflection.tableize(model.name),
|
201
|
-
:actions => { },
|
202
|
-
:protect => [ ],
|
203
|
-
:formats => { },
|
204
|
-
:renderer => :erb,
|
205
|
-
:children => [],
|
206
|
-
:to_param => :id,
|
207
|
-
:nest_params => true,
|
208
|
-
:credentials => {
|
209
|
-
:username => 'admin',
|
210
|
-
:password => 'password',
|
211
|
-
:realm => 'TheApp.com'
|
212
|
-
},
|
213
|
-
:accepts => {
|
214
|
-
:yaml => proc { |string| YAML.load(string) },
|
215
|
-
:json => proc { |string| JSON.parse(string) },
|
216
|
-
:xml => proc { |string| Hash.from_xml(string)['hash'] }
|
217
|
-
}
|
218
|
-
}
|
133
|
+
# TODO Hook this into Rack::CommonLogger
|
134
|
+
def logger
|
135
|
+
@logger ||= Logger.new(self)
|
219
136
|
end
|
220
137
|
|
221
138
|
private
|
222
139
|
|
223
|
-
def
|
224
|
-
|
140
|
+
def log_with_benchmark(request, action)
|
141
|
+
msg = [ ]
|
142
|
+
msg << "#{request.env['REQUEST_METHOD']} #{request.env['PATH_INFO']}"
|
143
|
+
msg << "Params: #{request.params.inspect}"
|
144
|
+
msg << "Action: #{action.to_s.upcase}"
|
145
|
+
|
146
|
+
logger.info ">> " + msg.join(' | ')
|
147
|
+
|
148
|
+
result = nil
|
149
|
+
|
150
|
+
t = Benchmark.realtime { result = yield }
|
151
|
+
|
152
|
+
logger.info " Request finished in #{t} sec."
|
153
|
+
|
154
|
+
result
|
155
|
+
end
|
156
|
+
|
157
|
+
def resource
|
158
|
+
@resource ||= Resource.new(self)
|
225
159
|
end
|
226
160
|
end
|
227
161
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Hat
|
3
|
+
# A wrapper around the model class that we're mounting
|
4
|
+
class Model
|
5
|
+
attr_reader :maker
|
6
|
+
|
7
|
+
delegate :options, :klass, :prefix, :to => :maker
|
8
|
+
|
9
|
+
def initialize(maker)
|
10
|
+
@maker = maker
|
11
|
+
end
|
12
|
+
|
13
|
+
def all(params)
|
14
|
+
params.make_indifferent!
|
15
|
+
options[:finder].call(proxy(params), params)
|
16
|
+
end
|
17
|
+
|
18
|
+
def find(params)
|
19
|
+
params.make_indifferent!
|
20
|
+
options[:record].call(proxy(params), params)
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_owner(params)
|
24
|
+
params = parent_params(params)
|
25
|
+
options[:record].call(proxy(params), params)
|
26
|
+
end
|
27
|
+
|
28
|
+
def update(params)
|
29
|
+
if record = find(params)
|
30
|
+
params.nest!
|
31
|
+
record.attributes = (params[singular] || { })
|
32
|
+
record
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def new(params={})
|
37
|
+
params.nest!
|
38
|
+
proxy(params).new(params[singular] || { })
|
39
|
+
end
|
40
|
+
|
41
|
+
def plural
|
42
|
+
klass.name.snake_case.plural
|
43
|
+
end
|
44
|
+
|
45
|
+
def singular
|
46
|
+
klass.name.snake_case.singular
|
47
|
+
end
|
48
|
+
|
49
|
+
def foreign_key
|
50
|
+
"#{singular}_id".to_sym
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def proxy(params)
|
56
|
+
return klass unless parent
|
57
|
+
owner = parent.find_owner(params)
|
58
|
+
if owner and owner.respond_to?(plural)
|
59
|
+
owner.send(plural)
|
60
|
+
else
|
61
|
+
klass
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def parent_params(params)
|
66
|
+
_params = params.dup.to_mash
|
67
|
+
_params.merge! :id => _params.delete(foreign_key)
|
68
|
+
_params
|
69
|
+
end
|
70
|
+
|
71
|
+
def parent
|
72
|
+
return nil unless maker.parent
|
73
|
+
maker.parent.model
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Hat
|
3
|
+
# Handles the logic of generating a path for a given resource,
|
4
|
+
# taking any and all parents into consideration.
|
5
|
+
class Resource
|
6
|
+
def initialize(maker)
|
7
|
+
@maker = maker
|
8
|
+
end
|
9
|
+
|
10
|
+
def path(suffix, record=nil)
|
11
|
+
suffix = suffix.dup
|
12
|
+
|
13
|
+
path = resources.inject("") do |memo, maker|
|
14
|
+
memo += fragment(maker, record)
|
15
|
+
end
|
16
|
+
|
17
|
+
suffix.gsub!('/:id', "/#{record.id}") if record
|
18
|
+
|
19
|
+
clean(path + suffix)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def fragment(maker, record)
|
25
|
+
@maker.eql?(maker) ?
|
26
|
+
"/#{maker.prefix}" :
|
27
|
+
"/#{maker.prefix}/" + interpolate(maker, record)
|
28
|
+
end
|
29
|
+
|
30
|
+
def interpolate(maker, record)
|
31
|
+
foreign_key = maker.model.foreign_key
|
32
|
+
result = record ? record.send(foreign_key) : foreign_key
|
33
|
+
result.inspect
|
34
|
+
end
|
35
|
+
|
36
|
+
def clean(s)
|
37
|
+
s.downcase!
|
38
|
+
s.gsub!(%r(/$), '')
|
39
|
+
s
|
40
|
+
end
|
41
|
+
|
42
|
+
def resources
|
43
|
+
@maker.parents + [@maker]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Hat
|
3
|
+
# The responder assigns data to instance variables, then either
|
4
|
+
# gets the appropriate response proc and instance_exec's it in the
|
5
|
+
# context of a new Response object, or serializes the data.
|
6
|
+
class Responder
|
7
|
+
delegate :model, :to => :maker
|
8
|
+
|
9
|
+
attr_reader :maker
|
10
|
+
|
11
|
+
def initialize(maker)
|
12
|
+
@maker = maker
|
13
|
+
end
|
14
|
+
|
15
|
+
def defaults
|
16
|
+
@defaults ||= {
|
17
|
+
:show => {
|
18
|
+
:success => proc { |data| render(:show) },
|
19
|
+
:failure => proc { |data| redirect('/') }
|
20
|
+
},
|
21
|
+
|
22
|
+
:index => {
|
23
|
+
:success => proc { |data| render(:index) },
|
24
|
+
:failure => proc { |data| redirect('/') }
|
25
|
+
},
|
26
|
+
|
27
|
+
:create => {
|
28
|
+
:success => proc { |data| redirect(data) },
|
29
|
+
:failure => proc { |data| render(:new) }
|
30
|
+
},
|
31
|
+
|
32
|
+
:new => {
|
33
|
+
:success => proc { |data| render(:new) },
|
34
|
+
:failure => proc { |data| redirect('/') }
|
35
|
+
},
|
36
|
+
|
37
|
+
:edit => {
|
38
|
+
:success => proc { |data| render(:edit) }
|
39
|
+
},
|
40
|
+
|
41
|
+
:destroy => {
|
42
|
+
:success => proc { |data| redirect(resource_path('/')) }
|
43
|
+
},
|
44
|
+
|
45
|
+
:update => {
|
46
|
+
:success => proc { |data| redirect(data) },
|
47
|
+
:failure => proc { |data| render(:edit) }
|
48
|
+
}
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def success(name, request, data)
|
53
|
+
handle(:success, name, request, data)
|
54
|
+
end
|
55
|
+
|
56
|
+
def failure(name, request, data)
|
57
|
+
handle(:failure, name, request, data)
|
58
|
+
end
|
59
|
+
|
60
|
+
def serialize(request, data)
|
61
|
+
name = request.params[:format].to_sym
|
62
|
+
formatter = to_format(name)
|
63
|
+
formatter[data] || request.error(406)
|
64
|
+
end
|
65
|
+
|
66
|
+
def not_found(request)
|
67
|
+
request.not_found
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def handle(result, name, request, data)
|
73
|
+
if format = request.params[:format]
|
74
|
+
serialize(request, data)
|
75
|
+
else
|
76
|
+
request.instance_variable_set(ivar_name(data), data)
|
77
|
+
response = Response.new(maker, request)
|
78
|
+
response.instance_exec(data, &defaults[name][result])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def ivar_name(data)
|
83
|
+
"@" + (data.respond_to?(:each) ? model.plural : model.singular)
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_format(name)
|
87
|
+
maker.formats[name] || Proc.new do |data|
|
88
|
+
method_name = "to_#{name}"
|
89
|
+
data.respond_to?(method_name) ? data.send(method_name) : nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Sinatra
|
2
|
+
class NoTemplateError < StandardError; end
|
3
|
+
|
4
|
+
module Hat
|
5
|
+
# Tells Sinatra what to do next.
|
6
|
+
class Response
|
7
|
+
attr_reader :maker
|
8
|
+
|
9
|
+
delegate :resource_path, :to => :maker
|
10
|
+
|
11
|
+
def initialize(maker, request)
|
12
|
+
@maker = maker
|
13
|
+
@request = request
|
14
|
+
end
|
15
|
+
|
16
|
+
def render(action)
|
17
|
+
begin
|
18
|
+
@request.erb action.to_sym, :views_directory => views
|
19
|
+
rescue Errno::ENOENT
|
20
|
+
no_template! "Can't find #{File.expand_path(File.join(views, action.to_s))}.erb"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def redirect(*args)
|
25
|
+
@request.redirect url_for(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def no_template!(msg)
|
31
|
+
raise NoTemplateError.new(msg)
|
32
|
+
end
|
33
|
+
|
34
|
+
def views
|
35
|
+
@views ||= begin
|
36
|
+
if views_dir = @request.options.views
|
37
|
+
File.join(views_dir, maker.prefix)
|
38
|
+
else
|
39
|
+
no_template! "Make sure you set the :views option!"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def url_for(resource, *args)
|
45
|
+
case resource
|
46
|
+
when String then resource
|
47
|
+
when Symbol then resource_path(Maker.actions[resource][:path], *args)
|
48
|
+
else resource_path('/:id', resource)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Hat
|
3
|
+
# Tells Sinatra which routes to generate. The routes
|
4
|
+
# created automatically when the actions are loaded.
|
5
|
+
class Router
|
6
|
+
delegate :resource_path, :logger, :to => :maker
|
7
|
+
|
8
|
+
attr_reader :maker, :app
|
9
|
+
|
10
|
+
def self.cache
|
11
|
+
@cache ||= []
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(maker)
|
15
|
+
@maker = maker
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate(app)
|
19
|
+
@app = app
|
20
|
+
|
21
|
+
Router.cache.each do |route|
|
22
|
+
map(*route)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def map(method, action, path)
|
29
|
+
path = resource_path(path)
|
30
|
+
|
31
|
+
handler = lambda do |request|
|
32
|
+
maker.handle(action, request)
|
33
|
+
end
|
34
|
+
|
35
|
+
logger.info ">> route for #{maker.klass} #{action}:\t#{method.to_s.upcase}\t#{path}"
|
36
|
+
|
37
|
+
app.send(method, path) { handler[self] }
|
38
|
+
app.send(method, "#{path}.:format") { handler[self] }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinatras-hat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pat Nakajima
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-01-17 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -32,16 +32,6 @@ dependencies:
|
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: "0"
|
34
34
|
version:
|
35
|
-
- !ruby/object:Gem::Dependency
|
36
|
-
name: metaid
|
37
|
-
type: :runtime
|
38
|
-
version_requirement:
|
39
|
-
version_requirements: !ruby/object:Gem::Requirement
|
40
|
-
requirements:
|
41
|
-
- - ">="
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
version: "0"
|
44
|
-
version:
|
45
35
|
description:
|
46
36
|
email: patnakajima@gmail.com
|
47
37
|
executables: []
|
@@ -52,16 +42,23 @@ extra_rdoc_files: []
|
|
52
42
|
|
53
43
|
files:
|
54
44
|
- lib/core_ext
|
55
|
-
- lib/core_ext/hash.rb
|
56
45
|
- lib/core_ext/array.rb
|
46
|
+
- lib/core_ext/hash.rb
|
47
|
+
- lib/core_ext/module.rb
|
57
48
|
- lib/core_ext/object.rb
|
58
49
|
- lib/sinatras-hat
|
59
|
-
- lib/sinatras-hat.rb
|
60
|
-
- lib/sinatras-hat/maker.rb
|
61
|
-
- lib/sinatras-hat/action.rb
|
62
50
|
- lib/sinatras-hat/actions.rb
|
63
|
-
- lib/sinatras-hat/
|
64
|
-
- lib/sinatras-hat/
|
51
|
+
- lib/sinatras-hat/authentication.rb
|
52
|
+
- lib/sinatras-hat/extendor.rb
|
53
|
+
- lib/sinatras-hat/hash_mutator.rb
|
54
|
+
- lib/sinatras-hat/logger.rb
|
55
|
+
- lib/sinatras-hat/maker.rb
|
56
|
+
- lib/sinatras-hat/model.rb
|
57
|
+
- lib/sinatras-hat/resource.rb
|
58
|
+
- lib/sinatras-hat/responder.rb
|
59
|
+
- lib/sinatras-hat/response.rb
|
60
|
+
- lib/sinatras-hat/router.rb
|
61
|
+
- lib/sinatras-hat.rb
|
65
62
|
has_rdoc: false
|
66
63
|
homepage:
|
67
64
|
post_install_message:
|
data/lib/sinatras-hat/action.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
module Sinatra
|
2
|
-
module Hat
|
3
|
-
# TODO: Move these to Action class?
|
4
|
-
module Responses
|
5
|
-
class UnsupportedFormat < StandardError
|
6
|
-
attr_reader :format
|
7
|
-
|
8
|
-
def initialize(format)
|
9
|
-
@format = format
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def templated(event, name, opts={})
|
14
|
-
event.protect!(:realm => credentials[:realm], &authenticator) if protecting?(name)
|
15
|
-
|
16
|
-
root = File.join(Sinatra.application.options.views, prefix)
|
17
|
-
result = actions[name].handle(event)
|
18
|
-
event.instance_variable_set ivar_name(result), result
|
19
|
-
return opts[:verb] == :get ?
|
20
|
-
event.render(renderer, name, :views_directory => root) :
|
21
|
-
event.redirect(redirection_path(result))
|
22
|
-
end
|
23
|
-
|
24
|
-
def serialized(event, name, opts={})
|
25
|
-
format = event.params[:format].to_sym
|
26
|
-
|
27
|
-
event.protect!(:realm => credentials[:realm], &authenticator) if protecting?(name)
|
28
|
-
|
29
|
-
if accepts[format] or opts[:verb].eql?(:get)
|
30
|
-
event.content_type(format) rescue nil
|
31
|
-
object = actions[name].handle(event)
|
32
|
-
result = serializer_for(format).call(object)
|
33
|
-
return result unless result.nil?
|
34
|
-
end
|
35
|
-
|
36
|
-
raise UnsupportedFormat.new(format)
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
def serializer_for(format)
|
42
|
-
formats[format.to_sym] ||= proc do |object|
|
43
|
-
object.try("to_#{format}")
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def protecting?(name)
|
48
|
-
protect.include?(:all) or protect.include?(name)
|
49
|
-
end
|
50
|
-
|
51
|
-
def redirection_path(result)
|
52
|
-
result.is_a?(Symbol) ?
|
53
|
-
"/#{prefix}" :
|
54
|
-
"/#{prefix}/#{result.send(to_param)}"
|
55
|
-
end
|
56
|
-
|
57
|
-
def ivar_name(result)
|
58
|
-
"@" + (result.respond_to?(:each) ? prefix : model.name.downcase)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|