sinatras-hat 0.0.3 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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