sinatras-hat 0.0.2

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.
@@ -0,0 +1,17 @@
1
+ class Array
2
+ def extract_options!
3
+ last.is_a?(Hash) ? pop : { }
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
+ end
@@ -0,0 +1,24 @@
1
+ class Hash
2
+ def make_indifferent!
3
+ keys_values = self.dup
4
+ indifferent = Hash.new { |h,k| h[k.to_s] if Symbol === k }
5
+ replace(indifferent)
6
+ merge!(keys_values)
7
+ end
8
+
9
+ def nest!
10
+ new_params = Hash.new.make_indifferent!
11
+ each_pair do |full_key, value|
12
+ this_param = new_params
13
+ split_keys = full_key.split(/\]\[|\]|\[/)
14
+ split_keys.each_index do |index|
15
+ break if split_keys.length == index + 1
16
+ this_param[split_keys[index]] ||= Hash.new.make_indifferent!
17
+ this_param = this_param[split_keys[index]]
18
+ end
19
+ this_param[split_keys.last] = value
20
+ end
21
+ clear
22
+ replace(new_params)
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ require 'metaid'
2
+
3
+ class Object
4
+ def tap
5
+ yield self
6
+ self
7
+ end
8
+
9
+ def try(m, *a, &b)
10
+ respond_to?(m) ? send(m, *a, &b) : nil
11
+ end
12
+
13
+ def with(hash)
14
+ hash.each do |key, value|
15
+ meta_def(key) { hash[key] } unless respond_to?(key)
16
+ meta_def("#{key}=") { |v| hash[key] = v } unless respond_to?("#{key}=")
17
+ end
18
+
19
+ return unless block_given?
20
+
21
+ result = yield
22
+
23
+ hash.each do |key, value|
24
+ meta_eval { remove_method(key) }
25
+ meta_eval { remove_method("#{key}=") }
26
+ end
27
+
28
+ result
29
+ end
30
+
31
+ module InstanceExecHelper; end
32
+ include InstanceExecHelper
33
+ def instance_exec(*args, &block)
34
+ begin
35
+ old_critical, Thread.critical = Thread.critical, true
36
+ n = 0
37
+ n += 1 while respond_to?(mname="__instance_exec#{n}")
38
+ InstanceExecHelper.module_eval{ define_method(mname, &block) }
39
+ ensure
40
+ Thread.critical = old_critical
41
+ end
42
+ begin
43
+ ret = send(mname, *args)
44
+ ensure
45
+ InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
46
+ end
47
+ ret
48
+ end
49
+ end
@@ -0,0 +1,23 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__), 'core_ext')
2
+ $LOAD_PATH << File.join(File.dirname(__FILE__), 'sinatras-hat')
3
+
4
+ require 'erb'
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
+
16
+ load 'auth.rb'
17
+
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
@@ -0,0 +1,67 @@
1
+ module Sinatra
2
+ module Hat
3
+ 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
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,59 @@
1
+ # from http://www.gittr.com/index.php/archive/sinatra-basic-authentication-selectively-applied
2
+ # adapted by pat nakajima for sinatra's hat
3
+ module Sinatra
4
+ module Authorization
5
+ class ProtectedAction
6
+ attr_reader :credentials, :context, :block
7
+
8
+ def initialize(context, credentials={}, &block)
9
+ @credentials, @context, @block = credentials, context, block
10
+ end
11
+
12
+ def check!
13
+ unauthorized! unless auth.provided?
14
+ bad_request! unless auth.basic?
15
+ unauthorized! unless authorize(*auth.credentials)
16
+ end
17
+
18
+ def remote_user
19
+ auth.username
20
+ end
21
+
22
+ private
23
+
24
+ def authorize(username, password)
25
+ block.call(username, password)
26
+ end
27
+
28
+ def unauthorized!
29
+ context.header 'WWW-Authenticate' => %(Basic realm="#{credentials[:realm]}")
30
+ throw :halt, [ 401, 'Authorization Required' ]
31
+ end
32
+
33
+ def bad_request!
34
+ throw :halt, [ 400, 'Bad Request' ]
35
+ end
36
+
37
+ def auth
38
+ @auth ||= Rack::Auth::Basic::Request.new(context.request.env)
39
+ end
40
+ end
41
+
42
+ module Helpers
43
+ def protect!(credentials, &block)
44
+ return if authorized?
45
+ guard = ProtectedAction.new(self, credentials, &block)
46
+ guard.check!
47
+ request.env['REMOTE_USER'] = guard.remote_user
48
+ end
49
+
50
+ def authorized?
51
+ request.env['REMOTE_USER']
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ helpers do
58
+ include Sinatra::Authorization::Helpers
59
+ end
@@ -0,0 +1,228 @@
1
+ module Sinatra
2
+ module Hat
3
+ class Maker
4
+ attr_accessor :parent
5
+ attr_reader :model, :context, :options
6
+
7
+ include Actions, Responses
8
+
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!
19
+ end
20
+
21
+ def mount(klass, opts={}, &block)
22
+ child = Maker.new(klass)
23
+ child.parent = self
24
+ child.define(context, opts, &block)
25
+ child
26
+ end
27
+
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
40
+
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
54
+ 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
68
+ end
69
+
70
+ def children(*args)
71
+ result = get_or_set_option(:children, args) do
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
87
+ end
88
+
89
+ def finder(&block)
90
+ if block_given?
91
+ @finder = block
92
+ else
93
+ @finder ||= proc { |params| all }
94
+ end
95
+ end
96
+
97
+ def record(&block)
98
+ if block_given?
99
+ @record = block
100
+ else
101
+ @record ||= proc { |params| first(:id => params[:id]) }
102
+ end
103
+ end
104
+
105
+ def create(&block)
106
+ if block_given?
107
+ @create = block
108
+ else
109
+ @create ||= proc do |model, params|
110
+ result = model.new
111
+ result.attributes = params[model_name]
112
+ result.save ? result : nil
113
+ end
114
+ end
115
+ end
116
+
117
+ def update(&block)
118
+ if block_given?
119
+ @update = block
120
+ else
121
+ @update ||= proc do |record, params|
122
+ record.attributes = params[model_name]
123
+ record.save ? record : false
124
+ end
125
+ end
126
+ end
127
+
128
+ def destroy(&block)
129
+ if block_given?
130
+ @destroy = block
131
+ else
132
+ @destroy ||= proc do |record, params|
133
+ record.destroy
134
+ :destroyed
135
+ end
136
+ end
137
+ end
138
+
139
+ def map(name, path, opts={}, &block)
140
+ opts[:verb] ||= :get
141
+ klass = self
142
+ actions[name] = Action.new(self, name, block)
143
+
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
159
+ end
160
+
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
+ ]
168
+ 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>"
175
+ end
176
+
177
+ def call(method, params)
178
+ proxy(params).instance_exec(params, &send(method))
179
+ end
180
+
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
187
+ end
188
+
189
+ def model_id
190
+ "#{model_name}_id".to_sym
191
+ end
192
+
193
+ def model_name
194
+ model.name.downcase
195
+ end
196
+
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
+ }
219
+ end
220
+
221
+ private
222
+
223
+ def get_or_set_option(name, args, opts={})
224
+ args.length > 0 ? yield : options[name]
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,62 @@
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
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatras-hat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Pat Nakajima
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-10-16 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: extlib
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: sinatra
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
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
+ description:
46
+ email: patnakajima@gmail.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files: []
52
+
53
+ files:
54
+ - lib/core_ext
55
+ - lib/core_ext/hash.rb
56
+ - lib/core_ext/array.rb
57
+ - lib/core_ext/object.rb
58
+ - lib/sinatras-hat
59
+ - lib/sinatras-hat/maker.rb
60
+ - lib/sinatras-hat/actions.rb
61
+ - lib/sinatras-hat/auth.rb
62
+ - lib/sinatras-hat/responses.rb
63
+ - lib/sinatras-hat.rb
64
+ has_rdoc: false
65
+ homepage:
66
+ post_install_message:
67
+ rdoc_options: []
68
+
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ version:
83
+ requirements: []
84
+
85
+ rubyforge_project:
86
+ rubygems_version: 1.3.1
87
+ signing_key:
88
+ specification_version: 2
89
+ summary: Simple REST-ful resources with Sinatra.
90
+ test_files: []
91
+