nakajima-sinatras-hat 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +18 -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 +82 -163
- data/lib/sinatras-hat/model.rb +77 -0
- data/lib/sinatras-hat/resource.rb +47 -0
- data/lib/sinatras-hat/responder.rb +96 -0
- data/lib/sinatras-hat/response.rb +53 -0
- data/lib/sinatras-hat/router.rb +42 -0
- metadata +14 -16
- 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,22 @@
|
|
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 'sinatra/base'
|
5
5
|
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
6
|
|
16
|
-
|
7
|
+
require 'core_ext/array'
|
8
|
+
require 'core_ext/hash'
|
9
|
+
require 'core_ext/object'
|
10
|
+
require 'core_ext/module'
|
17
11
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
12
|
+
require 'sinatras-hat/logger'
|
13
|
+
require 'sinatras-hat/extendor'
|
14
|
+
require 'sinatras-hat/authentication'
|
15
|
+
require 'sinatras-hat/hash_mutator'
|
16
|
+
require 'sinatras-hat/resource'
|
17
|
+
require 'sinatras-hat/response'
|
18
|
+
require 'sinatras-hat/responder'
|
19
|
+
require 'sinatras-hat/model'
|
20
|
+
require 'sinatras-hat/router'
|
21
|
+
require 'sinatras-hat/actions'
|
22
|
+
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,146 @@
|
|
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
|
-
@
|
25
|
+
def initialize(klass, overrides={})
|
26
|
+
@klass = klass
|
27
|
+
options.merge!(overrides)
|
28
|
+
with(options)
|
43
29
|
end
|
44
|
-
|
45
|
-
def
|
46
|
-
|
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
|
30
|
+
|
31
|
+
def setup(app)
|
32
|
+
@app = app
|
54
33
|
end
|
55
|
-
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
self.only.uniq!
|
60
|
-
end
|
61
|
-
|
62
|
-
result = Array(result)
|
34
|
+
|
35
|
+
def handle(action, request)
|
36
|
+
request.error(404) unless only.include?(action)
|
37
|
+
protect!(request) if protect.include?(action)
|
63
38
|
|
64
|
-
|
65
|
-
|
39
|
+
logger.info ">> #{request.env['REQUEST_METHOD']} #{request.env['PATH_INFO']}"
|
40
|
+
logger.info " action: #{action.to_s.upcase}"
|
41
|
+
logger.info " params: #{request.params.inspect}"
|
66
42
|
|
67
|
-
|
43
|
+
instance_exec(request, &self.class.actions[action][:fn])
|
68
44
|
end
|
69
45
|
|
70
|
-
def
|
71
|
-
|
72
|
-
self.children = args
|
73
|
-
self.children.uniq!
|
74
|
-
end
|
75
|
-
|
76
|
-
[result].flatten
|
77
|
-
end
|
78
|
-
|
79
|
-
def authenticator(&block)
|
80
|
-
if block_given?
|
81
|
-
@authenticator = block
|
82
|
-
else
|
83
|
-
@authenticator ||= proc { |username, password|
|
84
|
-
credentials[:username] == username and credentials[:password] == password
|
85
|
-
}
|
86
|
-
end
|
46
|
+
def after(action)
|
47
|
+
yield HashMutator.new(responder.defaults[action])
|
87
48
|
end
|
88
49
|
|
89
50
|
def finder(&block)
|
90
51
|
if block_given?
|
91
|
-
|
52
|
+
options[:finder] = block
|
92
53
|
else
|
93
|
-
|
54
|
+
options[:finder]
|
94
55
|
end
|
95
56
|
end
|
96
57
|
|
97
58
|
def record(&block)
|
98
59
|
if block_given?
|
99
|
-
|
60
|
+
options[:record] = block
|
100
61
|
else
|
101
|
-
|
62
|
+
options[:record]
|
102
63
|
end
|
103
64
|
end
|
104
65
|
|
105
|
-
def
|
66
|
+
def authenticator(&block)
|
106
67
|
if block_given?
|
107
|
-
|
68
|
+
options[:authenticator] = block
|
108
69
|
else
|
109
|
-
|
110
|
-
result = model.new
|
111
|
-
result.attributes = params[model_name]
|
112
|
-
result.save ? result : nil
|
113
|
-
end
|
70
|
+
options[:authenticator]
|
114
71
|
end
|
115
72
|
end
|
116
73
|
|
117
|
-
def
|
118
|
-
if
|
119
|
-
|
74
|
+
def only(*actions)
|
75
|
+
if actions.empty?
|
76
|
+
options[:only] ||= Set.new(options[:only])
|
120
77
|
else
|
121
|
-
|
122
|
-
record.attributes = params[model_name]
|
123
|
-
record.save ? record : false
|
124
|
-
end
|
78
|
+
Set.new(options[:only] = actions)
|
125
79
|
end
|
126
80
|
end
|
127
81
|
|
128
|
-
def
|
129
|
-
|
130
|
-
|
82
|
+
def protect(*actions)
|
83
|
+
credentials.merge!(actions.extract_options!)
|
84
|
+
|
85
|
+
if actions.empty?
|
86
|
+
options[:protect] ||= Set.new([])
|
131
87
|
else
|
132
|
-
|
133
|
-
|
134
|
-
:
|
135
|
-
end
|
88
|
+
actions == [:all] ?
|
89
|
+
Set.new(options[:protect] = only) :
|
90
|
+
Set.new(options[:protect] = actions)
|
136
91
|
end
|
137
92
|
end
|
138
93
|
|
139
|
-
def
|
140
|
-
|
141
|
-
|
142
|
-
actions[name] = Action.new(self, name, block)
|
94
|
+
def prefix
|
95
|
+
@prefix ||= options[:prefix] || model.plural
|
96
|
+
end
|
143
97
|
|
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
|
98
|
+
def parents
|
99
|
+
@parents ||= parent ? Array(parent) + parent.parents : []
|
159
100
|
end
|
160
101
|
|
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
|
-
]
|
102
|
+
def resource_path(*args)
|
103
|
+
resource.path(*args)
|
168
104
|
end
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
105
|
+
|
106
|
+
def options
|
107
|
+
@options ||= {
|
108
|
+
:only => Set.new([:index, :show, :new, :create, :edit, :update, :destroy]),
|
109
|
+
:parent => nil,
|
110
|
+
:finder => proc { |model, params| model.all },
|
111
|
+
:record => proc { |model, params| model.find_by_id(params[:id]) },
|
112
|
+
:protect => [ ],
|
113
|
+
:formats => { },
|
114
|
+
:credentials => { :username => 'username', :password => 'password', :realm => "The App" },
|
115
|
+
:authenticator => proc { |username, password| [username, password] == [:username, :password].map(&credentials.method(:[])) }
|
116
|
+
}
|
175
117
|
end
|
176
118
|
|
177
|
-
def
|
178
|
-
|
119
|
+
def inspect
|
120
|
+
"maker: #{klass}"
|
179
121
|
end
|
180
122
|
|
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
|
123
|
+
def generate_routes!
|
124
|
+
Router.new(self).generate(@app)
|
187
125
|
end
|
188
126
|
|
189
|
-
def
|
190
|
-
|
127
|
+
def responder
|
128
|
+
@responder ||= Responder.new(self)
|
191
129
|
end
|
192
130
|
|
193
|
-
def
|
194
|
-
model.
|
131
|
+
def model
|
132
|
+
@model ||= Model.new(self)
|
195
133
|
end
|
196
134
|
|
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
|
-
}
|
135
|
+
# TODO Hook this into Rack::CommonLogger
|
136
|
+
def logger
|
137
|
+
@logger ||= Logger.new(self)
|
219
138
|
end
|
220
139
|
|
221
140
|
private
|
222
141
|
|
223
|
-
def
|
224
|
-
|
142
|
+
def resource
|
143
|
+
@resource ||= Resource.new(self)
|
225
144
|
end
|
226
145
|
end
|
227
146
|
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(record, maker)
|
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(record, maker)
|
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,96 @@
|
|
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
|
+
DEFAULTS = {
|
10
|
+
:show => {
|
11
|
+
:success => proc { |data| render(:show) },
|
12
|
+
:failure => proc { |data| redirect('/') }
|
13
|
+
},
|
14
|
+
|
15
|
+
:index => {
|
16
|
+
:success => proc { |data| render(:index) },
|
17
|
+
:failure => proc { |data| redirect('/') }
|
18
|
+
},
|
19
|
+
|
20
|
+
:create => {
|
21
|
+
:success => proc { |data| redirect(data) },
|
22
|
+
:failure => proc { |data| render(:new) }
|
23
|
+
},
|
24
|
+
|
25
|
+
:new => {
|
26
|
+
:success => proc { |data| render(:new) },
|
27
|
+
:failure => proc { |data| redirect('/') }
|
28
|
+
},
|
29
|
+
|
30
|
+
:edit => {
|
31
|
+
:success => proc { |data| render(:edit) }
|
32
|
+
},
|
33
|
+
|
34
|
+
:destroy => {
|
35
|
+
:success => proc { |data| redirect(resource_path('/')) }
|
36
|
+
},
|
37
|
+
|
38
|
+
:update => {
|
39
|
+
:success => proc { |data| redirect(data) },
|
40
|
+
:failure => proc { |data| render(:edit) }
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
attr_reader :maker
|
45
|
+
|
46
|
+
def initialize(maker)
|
47
|
+
@maker = maker
|
48
|
+
end
|
49
|
+
|
50
|
+
def defaults
|
51
|
+
@defaults ||= DEFAULTS.dup
|
52
|
+
end
|
53
|
+
|
54
|
+
def success(name, request, data)
|
55
|
+
handle(:success, name, request, data)
|
56
|
+
end
|
57
|
+
|
58
|
+
def failure(name, request, data)
|
59
|
+
handle(:failure, name, request, data)
|
60
|
+
end
|
61
|
+
|
62
|
+
def serialize(request, data)
|
63
|
+
name = request.params[:format].to_sym
|
64
|
+
formatter = to_format(name)
|
65
|
+
formatter[data] || request.error(406)
|
66
|
+
end
|
67
|
+
|
68
|
+
def not_found(request)
|
69
|
+
request.not_found
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def handle(result, name, request, data)
|
75
|
+
if format = request.params[:format]
|
76
|
+
serialize(request, data)
|
77
|
+
else
|
78
|
+
request.instance_variable_set(ivar_name(data), data)
|
79
|
+
response = Response.new(maker, request)
|
80
|
+
response.instance_exec(data, &defaults[name][result])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def ivar_name(data)
|
85
|
+
"@" + (data.respond_to?(:each) ? model.plural : model.singular)
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_format(name)
|
89
|
+
maker.formats[name] || Proc.new do |data|
|
90
|
+
method_name = "to_#{name}"
|
91
|
+
data.respond_to?(method_name) ? data.send(method_name) : nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
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: nakajima-sinatras-hat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
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 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -30,15 +30,6 @@ dependencies:
|
|
30
30
|
- !ruby/object:Gem::Version
|
31
31
|
version: "0"
|
32
32
|
version:
|
33
|
-
- !ruby/object:Gem::Dependency
|
34
|
-
name: metaid
|
35
|
-
version_requirement:
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: "0"
|
41
|
-
version:
|
42
33
|
description:
|
43
34
|
email: patnakajima@gmail.com
|
44
35
|
executables: []
|
@@ -49,15 +40,22 @@ extra_rdoc_files: []
|
|
49
40
|
|
50
41
|
files:
|
51
42
|
- lib/core_ext
|
52
|
-
- lib/core_ext/hash.rb
|
53
43
|
- lib/core_ext/array.rb
|
44
|
+
- lib/core_ext/hash.rb
|
45
|
+
- lib/core_ext/module.rb
|
54
46
|
- lib/core_ext/object.rb
|
55
47
|
- lib/sinatras-hat
|
56
|
-
- lib/sinatras-hat/maker.rb
|
57
|
-
- lib/sinatras-hat/action.rb
|
58
48
|
- lib/sinatras-hat/actions.rb
|
59
|
-
- lib/sinatras-hat/
|
60
|
-
- lib/sinatras-hat/
|
49
|
+
- lib/sinatras-hat/authentication.rb
|
50
|
+
- lib/sinatras-hat/extendor.rb
|
51
|
+
- lib/sinatras-hat/hash_mutator.rb
|
52
|
+
- lib/sinatras-hat/logger.rb
|
53
|
+
- lib/sinatras-hat/maker.rb
|
54
|
+
- lib/sinatras-hat/model.rb
|
55
|
+
- lib/sinatras-hat/resource.rb
|
56
|
+
- lib/sinatras-hat/responder.rb
|
57
|
+
- lib/sinatras-hat/response.rb
|
58
|
+
- lib/sinatras-hat/router.rb
|
61
59
|
- lib/sinatras-hat.rb
|
62
60
|
has_rdoc: false
|
63
61
|
homepage:
|
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
|