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.
@@ -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
@@ -1,8 +1,7 @@
1
1
  class Hash
2
2
  def make_indifferent!
3
3
  keys_values = self.dup
4
- indifferent = Hash.new { |h,k| h[k.to_s] if Symbol === k }
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
@@ -6,10 +6,6 @@ class Object
6
6
  self
7
7
  end
8
8
 
9
- def try(m, *a, &b)
10
- respond_to?(m) ? send(m, *a, &b) : nil
11
- end
12
-
13
9
  def with(hash)
14
10
  hash.each do |key, value|
15
11
  meta_def(key) { hash[key] } unless respond_to?(key)
@@ -1,23 +1,23 @@
1
- $LOAD_PATH << File.join(File.dirname(__FILE__), 'core_ext')
2
- $LOAD_PATH << File.join(File.dirname(__FILE__), 'sinatras-hat')
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__))
3
2
 
4
- require 'erb'
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
- load 'auth.rb'
8
+ require 'core_ext/array'
9
+ require 'core_ext/hash'
10
+ require 'core_ext/object'
11
+ require 'core_ext/module'
17
12
 
18
- Rack::File::MIME_TYPES['json'] = 'text/x-json'
19
- Rack::File::MIME_TYPES['yaml'] = 'text/x-yaml'
20
-
21
- def mount(model, opts={}, &block)
22
- Sinatra::Hat::Maker.new(model).define(self, opts, &block)
23
- end
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'
@@ -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 generate_actions!
5
- only.each { |action| send("#{action}!") }
6
- children.each do |resource|
7
- mount(resource)
8
- end
9
- end
10
-
11
- def index!
12
- map :index, '/' do |params|
13
- call(:finder, params)
14
- end
15
- end
16
-
17
- def new!
18
- map :new, '/new' do |params|
19
- proxy(params).new
20
- end
21
- end
22
-
23
- def edit!
24
- map :edit, '/:id/edit' do |params|
25
- call(:record, params)
26
- end
27
- end
28
-
29
- def show!
30
- map :show, '/:id' do |params|
31
- call(:record, params)
32
- end
33
- end
34
-
35
- def create!
36
- map :create, '/', :verb => :post do |params|
37
- create[proxy(params), parse_for_attributes(params)]
38
- end
39
- end
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, :context, :block
6
+ attr_reader :credentials, :request, :block
7
7
 
8
- def initialize(context, credentials={}, &block)
9
- @credentials, @context, @block = credentials, context, block
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
- context.header 'WWW-Authenticate' => %(Basic realm="#{credentials[:realm]}")
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(context.request.env)
38
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
39
39
  end
40
40
  end
41
41
 
42
42
  module Helpers
43
- def protect!(credentials, &block)
44
- return if authorized?
45
- guard = ProtectedAction.new(self, credentials, &block)
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
@@ -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
- attr_accessor :parent
5
- attr_reader :model, :context, :options
5
+ include Sinatra::Hat::Extendor
6
+ include Sinatra::Authorization::Helpers
6
7
 
7
- include Actions, Responses
8
+ attr_reader :klass, :app
8
9
 
9
- def initialize(model)
10
- @model = model
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
- def mount(klass, opts={}, &block)
22
- child = Maker.new(klass)
23
- child.parent = self
24
- child.define(context, opts, &block)
25
- child
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
- def resource_path(suffix)
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 parents
42
- @parents ||= parent ? Array(parent) + parent.parents : []
43
- end
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 only(*args)
57
- result = get_or_set_option(:only, args) do
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 children(*args)
71
- result = get_or_set_option(:children, args) do
72
- self.children = args
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
- [result].flatten
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 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
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
- @finder = block
50
+ options[:finder] = block
92
51
  else
93
- @finder ||= proc { |params| all }
52
+ options[:finder]
94
53
  end
95
54
  end
96
55
 
97
56
  def record(&block)
98
57
  if block_given?
99
- @record = block
58
+ options[:record] = block
100
59
  else
101
- @record ||= proc { |params| first(:id => params[:id]) }
60
+ options[:record]
102
61
  end
103
62
  end
104
63
 
105
- def create(&block)
64
+ def authenticator(&block)
106
65
  if block_given?
107
- @create = block
66
+ options[:authenticator] = block
108
67
  else
109
- @create ||= proc do |model, params|
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 update(&block)
118
- if block_given?
119
- @update = block
72
+ def only(*actions)
73
+ if actions.empty?
74
+ options[:only] ||= Set.new(options[:only])
120
75
  else
121
- @update ||= proc do |record, params|
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 destroy(&block)
129
- if block_given?
130
- @destroy = block
80
+ def protect(*actions)
81
+ credentials.merge!(actions.extract_options!)
82
+
83
+ if actions.empty?
84
+ options[:protect] ||= Set.new([])
131
85
  else
132
- @destroy ||= proc do |record, params|
133
- record.destroy
134
- :destroyed
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 map(name, path, opts={}, &block)
140
- opts[:verb] ||= :get
141
- klass = self
142
- actions[name] = Action.new(self, name, block)
92
+ def prefix
93
+ options[:prefix] ||= model.plural
94
+ end
143
95
 
144
- context.send(opts[:verb], resource_path(path)) do
145
- begin
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 rescue_format_error(e)
162
- throw :halt, [
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
- # TODO Create a real template for this error.
171
- def rescue_template_error(e)
172
- msg = "<pre>There was a problem with your view template:\n\n "
173
- msg << e.message
174
- msg << "\n</pre>"
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 call(method, params)
178
- proxy(params).instance_exec(params, &send(method))
117
+ def inspect
118
+ "maker: #{klass}"
179
119
  end
180
120
 
181
- def proxy(params={})
182
- return model if parent.nil?
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 model_id
190
- "#{model_name}_id".to_sym
125
+ def responder
126
+ @responder ||= Responder.new(self)
191
127
  end
192
128
 
193
- def model_name
194
- model.name.downcase
129
+ def model
130
+ @model ||= Model.new(self)
195
131
  end
196
132
 
197
- def options
198
- @options ||= {
199
- :only => [:new, :edit, :show, :create, :update, :destroy, :index],
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 get_or_set_option(name, args, opts={})
224
- args.length > 0 ? yield : options[name]
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.0.3
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: 2008-10-16 00:00:00 -04:00
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/auth.rb
64
- - lib/sinatras-hat/responses.rb
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:
@@ -1,15 +0,0 @@
1
- module Sinatra
2
- module Hat
3
- class Action
4
- attr_reader :maker, :handler
5
-
6
- def initialize(maker, name, handler)
7
- @maker, @handler = maker, handler
8
- end
9
-
10
- def handle(event)
11
- handler[event.params]
12
- end
13
- end
14
- end
15
- end
@@ -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