nakajima-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,21 @@
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
+ if deleted = delete(entry)
9
+ unshift(deleted)
10
+ end
11
+ end
12
+ end
13
+
14
+ def move_to_back(*entries)
15
+ entries.each do |entry|
16
+ if deleted = delete(entry)
17
+ push(deleted)
18
+ end
19
+ end
20
+ end
21
+ 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,75 @@
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, resource_path('/') do |params|
13
+ call(:finder, params)
14
+ end
15
+ end
16
+
17
+ def new!
18
+ map :new, resource_path('/new') do |params|
19
+ proxy(params).new
20
+ end
21
+ end
22
+
23
+ def edit!
24
+ map :edit, resource_path('/:id/edit') do |params|
25
+ call(:record, params)
26
+ end
27
+ end
28
+
29
+ def show!
30
+ map :show, resource_path('/:id') do |params|
31
+ call(:record, params)
32
+ end
33
+ end
34
+
35
+ def create!
36
+ map :create, resource_path('/'), :verb => :post do |params|
37
+ result = proxy(params).new
38
+ result.attributes = parse_for_attributes(params)
39
+ result.save
40
+ result
41
+ end
42
+ end
43
+
44
+ def update!
45
+ map :update, resource_path('/:id'), :verb => :put do |params|
46
+ result = call(:record, params)
47
+ result.attributes = parse_for_attributes(params)
48
+ result.save
49
+ result
50
+ end
51
+ end
52
+
53
+ def destroy!
54
+ map :destroy, resource_path('/:id'), :verb => :delete do |params|
55
+ result = call(:record, params)
56
+ result.destroy
57
+ :ok
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def parse_for_attributes(params, name=model.name.downcase)
64
+ if handler = accepts[params[:format].try(:to_sym)]
65
+ handler.call params[name]
66
+ else
67
+ params.nest!
68
+ params[name] ||= { }
69
+ params[name][parent.model_id] = params[parent.model_id] if parent
70
+ params[name]
71
+ end
72
+ end
73
+ end
74
+ end
75
+ 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,159 @@
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 map(name, path, opts={}, &block)
106
+ opts[:verb] ||= :get
107
+ klass = self
108
+
109
+ context.send(opts[:verb], path) { klass.templated(self, name, opts, &block) }
110
+ context.send(opts[:verb], "#{path}.:format") { klass.serialized(self, name, opts, &block) }
111
+ end
112
+
113
+ def call(method, params)
114
+ proxy(params).instance_exec(params, &send(method))
115
+ end
116
+
117
+ def proxy(params={})
118
+ return model if parent.nil?
119
+ fake_params = params.dup
120
+ fake_params.merge!("id" => params[parent.model_id])
121
+ fake_params.make_indifferent!
122
+ parent.call(:record, fake_params).try(prefix) || model
123
+ end
124
+
125
+ def model_id
126
+ "#{model.name.downcase}_id".to_sym
127
+ end
128
+
129
+ def options
130
+ @options ||= {
131
+ :only => [:new, :edit, :show, :create, :update, :destroy, :index],
132
+ :prefix => Extlib::Inflection.tableize(model.name),
133
+ :protect => [],
134
+ :formats => { },
135
+ :renderer => :erb,
136
+ :children => [],
137
+ :to_param => :id,
138
+ :nest_params => true,
139
+ :credentials => {
140
+ :username => 'admin',
141
+ :password => 'password',
142
+ :realm => 'TheApp.com'
143
+ },
144
+ :accepts => {
145
+ :yaml => proc { |string| YAML.load(string) },
146
+ :json => proc { |string| JSON.parse(string) },
147
+ :xml => proc { |string| Hash.from_xml(string)['hash'] }
148
+ }
149
+ }
150
+ end
151
+
152
+ private
153
+
154
+ def get_or_set_option(name, args, opts={})
155
+ args.length > 0 ? yield : options[name]
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,53 @@
1
+ module Sinatra
2
+ module Hat
3
+ module Responses
4
+ def templated(event, name, opts={}, &block)
5
+ event.protect!(:realm => credentials[:realm], &authenticator) if protecting?(name)
6
+
7
+ root = File.join(Sinatra.application.options.views, prefix)
8
+ result = block.call(event.params)
9
+ event.instance_variable_set ivar_name(result), result
10
+ return opts[:verb] == :get ?
11
+ event.render(renderer, name, :views_directory => root) :
12
+ event.redirect(redirection_path(result))
13
+ end
14
+
15
+ def serialized(event, name, opts={}, &block)
16
+ format = event.params[:format].to_sym
17
+
18
+ event.protect!(:realm => credentials[:realm], &authenticator) if protecting?(name)
19
+
20
+ if accepts[format] or opts[:verb].eql?(:get)
21
+ event.content_type format rescue nil
22
+ object = block.call(event.params)
23
+ handle = formats[format.to_sym]
24
+ result = handle ? handle.call(object) : object.try("to_#{format}")
25
+ return result unless result.nil?
26
+ end
27
+
28
+ throw :halt, [
29
+ 406, [
30
+ "The `#{format}` format is not supported.\n",
31
+ "Valid Formats: #{accepts.keys.join(', ')}\n",
32
+ ].join("\n")
33
+ ]
34
+ end
35
+
36
+ private
37
+
38
+ def protecting?(name)
39
+ protect.include?(:all) or protect.include?(name)
40
+ end
41
+
42
+ def redirection_path(result)
43
+ result.is_a?(Symbol) ?
44
+ "/#{prefix}" :
45
+ "/#{prefix}/#{result.send(to_param)}"
46
+ end
47
+
48
+ def ivar_name(result)
49
+ "@" + (result.respond_to?(:each) ? prefix : model.name.downcase)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,22 @@
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 'actions'
12
+ require 'responses'
13
+ require 'maker'
14
+
15
+ load 'auth.rb'
16
+
17
+ Rack::File::MIME_TYPES['json'] = 'text/x-json'
18
+ Rack::File::MIME_TYPES['yaml'] = 'text/x-yaml'
19
+
20
+ def mount(model, opts={}, &block)
21
+ Sinatra::Hat::Maker.new(model).define(self, opts, &block)
22
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nakajima-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 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: extlib
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: sinatra
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: "0"
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
+ description:
43
+ email: patnakajima@gmail.com
44
+ executables: []
45
+
46
+ extensions: []
47
+
48
+ extra_rdoc_files: []
49
+
50
+ files:
51
+ - lib/core_ext
52
+ - lib/core_ext/hash.rb
53
+ - lib/core_ext/array.rb
54
+ - lib/core_ext/object.rb
55
+ - lib/sinatras-hat
56
+ - lib/sinatras-hat/maker.rb
57
+ - lib/sinatras-hat/actions.rb
58
+ - lib/sinatras-hat/auth.rb
59
+ - lib/sinatras-hat/responses.rb
60
+ - lib/sinatras-hat.rb
61
+ has_rdoc: false
62
+ homepage:
63
+ post_install_message:
64
+ rdoc_options: []
65
+
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ version:
80
+ requirements: []
81
+
82
+ rubyforge_project:
83
+ rubygems_version: 1.2.0
84
+ signing_key:
85
+ specification_version: 2
86
+ summary: Simple REST-ful resources with Sinatra.
87
+ test_files: []
88
+