sinatras-hat 0.0.2

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