raisin 0.0.2 → 0.0.4
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.
- data/README.md +8 -0
- data/lib/raisin/api/dsl.rb +177 -0
- data/lib/raisin/api.rb +29 -202
- data/lib/raisin/base.rb +5 -169
- data/lib/raisin/exposable.rb +7 -4
- data/lib/raisin/middleware.rb +31 -0
- data/lib/raisin/namespace.rb +2 -0
- data/lib/raisin/rails/request.rb +12 -1
- data/lib/raisin/rails/routes.rb +10 -2
- data/lib/raisin/railtie.rb +0 -3
- data/lib/raisin/router.rb +16 -51
- data/lib/raisin/version.rb +1 -1
- data/lib/raisin/version_constraint.rb +12 -0
- data/lib/raisin.rb +3 -3
- metadata +5 -4
- data/lib/raisin/middleware/base.rb +0 -13
- data/lib/raisin/middleware/header.rb +0 -39
data/README.md
CHANGED
@@ -150,6 +150,14 @@ Raisin.configure do |c|
|
|
150
150
|
end
|
151
151
|
```
|
152
152
|
|
153
|
+
If you are using versionning via header, you also need to add a middleware to your application stack
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
#config/application.rb
|
157
|
+
|
158
|
+
config.middleware.use Raisin::Middleware
|
159
|
+
```
|
160
|
+
|
153
161
|
## Contributing
|
154
162
|
|
155
163
|
1. Fork it
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module Raisin
|
2
|
+
module DSL
|
3
|
+
%w(get head post put delete).each do |via|
|
4
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
5
|
+
def #{via}(path = '/', options = {}, &block)
|
6
|
+
path = normalize_path(path)
|
7
|
+
method_name = options.key?(:as) ? options[:as].to_s : extract_method_name(path, :#{via})
|
8
|
+
|
9
|
+
klass = self.const_set method_name.camelize, Class.new(@_klass, &block)
|
10
|
+
|
11
|
+
if current_namespace && current_namespace.expose?
|
12
|
+
current_namespace.exposures.each do |name, b|
|
13
|
+
klass.send(:expose, name, &b)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
current_namespace.add(method_name) if current_namespace
|
18
|
+
|
19
|
+
routes << [:#{via}, path, default_route(method_name)]
|
20
|
+
end
|
21
|
+
EOF
|
22
|
+
end
|
23
|
+
|
24
|
+
def included(&block)
|
25
|
+
self.action_klass.class_eval(&block) if block_given?
|
26
|
+
end
|
27
|
+
|
28
|
+
def member(&block)
|
29
|
+
namespace(':id') do
|
30
|
+
resource = self.api_name.singularize
|
31
|
+
expose(resource) { resource.camelize.constantize.send :find, params[:id] }
|
32
|
+
instance_eval(&block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def nested_into_resource(parent)
|
37
|
+
parent = parent.to_s
|
38
|
+
sing = parent.singularize
|
39
|
+
id = "#{sing}_id"
|
40
|
+
|
41
|
+
@_namespaces << Namespace.new("#{parent}/:#{id}")
|
42
|
+
current_namespace.expose(sing) { sing.camelize.constantize.send :find, params[id.to_sym]}
|
43
|
+
@_namespaces << Namespace.new(@_prefix)
|
44
|
+
@_prefix = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def single_resource
|
48
|
+
@_single_resource = true
|
49
|
+
@_prefix = @_prefix.singularize if prefix?
|
50
|
+
end
|
51
|
+
|
52
|
+
def prefix(prefix)
|
53
|
+
@_prefix = prefix
|
54
|
+
end
|
55
|
+
|
56
|
+
def description(desc)
|
57
|
+
# noop
|
58
|
+
end
|
59
|
+
|
60
|
+
def expose(*args, &block)
|
61
|
+
current_namespace.expose(*args, &block)
|
62
|
+
end
|
63
|
+
|
64
|
+
def namespace(path, &block)
|
65
|
+
path = path.sub(%r(\A/?#{@_prefix}), '') if prefix?
|
66
|
+
@_namespaces.push Namespace.new(path)
|
67
|
+
yield
|
68
|
+
process_filters
|
69
|
+
@_namespaces.pop
|
70
|
+
end
|
71
|
+
|
72
|
+
%w(before around after).each do |type|
|
73
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
74
|
+
def #{type}(*args, &block)
|
75
|
+
return unless current_namespace
|
76
|
+
current_namespace.filter(:#{type}, args, &block)
|
77
|
+
end
|
78
|
+
EOF
|
79
|
+
end
|
80
|
+
|
81
|
+
protected
|
82
|
+
|
83
|
+
def prefix?
|
84
|
+
!!@_prefix
|
85
|
+
end
|
86
|
+
|
87
|
+
def single_resource?
|
88
|
+
!!@_single_resource
|
89
|
+
end
|
90
|
+
|
91
|
+
def current_namespace
|
92
|
+
@_namespaces.at(0)
|
93
|
+
end
|
94
|
+
|
95
|
+
def current_namespace?
|
96
|
+
@_namespaces.length > 0
|
97
|
+
end
|
98
|
+
|
99
|
+
def process_filters
|
100
|
+
current_namespace.filters.each_pair { |type, filters|
|
101
|
+
filters.each do |name, block|
|
102
|
+
superclass.send("#{type}_filter", name, only: current_namespace.methods, &block)
|
103
|
+
end
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
def default_route(method)
|
108
|
+
"#{modules_prefix}#{self.api_name}##{method}"
|
109
|
+
end
|
110
|
+
|
111
|
+
def modules_prefix
|
112
|
+
@modules_prefix ||= begin
|
113
|
+
modules = self.name.split('::').slice(0..-2)
|
114
|
+
modules.empty? ? '' : "#{modules.map!(&:downcase).join('/')}/"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Get method name from path
|
120
|
+
# Example:
|
121
|
+
# / => :index
|
122
|
+
# /users/:id => :users
|
123
|
+
# /users/:id/addresses => :addresses
|
124
|
+
#
|
125
|
+
def extract_method_name(path, via)
|
126
|
+
return method_name_for_single_resource(path, via) if single_resource?
|
127
|
+
|
128
|
+
if path =~ %r(\A/?#{@_prefix}\z)
|
129
|
+
return via == :get ? 'index' : 'create'
|
130
|
+
end
|
131
|
+
|
132
|
+
parts = path.split('/').reverse!
|
133
|
+
|
134
|
+
return parts.find { |part| !part.start_with?(':') } if parts.first != ':id'
|
135
|
+
|
136
|
+
case via
|
137
|
+
when :get
|
138
|
+
'show'
|
139
|
+
when :put
|
140
|
+
'update'
|
141
|
+
when :delete
|
142
|
+
'destroy'
|
143
|
+
else
|
144
|
+
raise "Cannot extract method name from #{path}"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def method_name_for_single_resource(path, via)
|
149
|
+
if path =~ %r(\A/?#{@_prefix}\z)
|
150
|
+
case via
|
151
|
+
when :get
|
152
|
+
'show'
|
153
|
+
when :post
|
154
|
+
'create'
|
155
|
+
when :put
|
156
|
+
'update'
|
157
|
+
when :delete
|
158
|
+
'destroy'
|
159
|
+
end
|
160
|
+
else
|
161
|
+
path.split('/').reverse!.last
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# Creates path with version, namespace and
|
167
|
+
# given path, then normalizes it
|
168
|
+
#
|
169
|
+
def normalize_path(path)
|
170
|
+
parts = []
|
171
|
+
parts << @_prefix unless !prefix? || path =~ %r(\A/?#{@_prefix})
|
172
|
+
parts.concat @_namespaces.reject { |n| path =~ %r(/#{n.path}) }.map!(&:path) if current_namespace?
|
173
|
+
parts << path.to_s unless path == '/'
|
174
|
+
parts.join('/')
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
data/lib/raisin/api.rb
CHANGED
@@ -1,26 +1,22 @@
|
|
1
|
-
|
2
|
-
class MiddlewareStack < ActionDispatch::MiddlewareStack
|
3
|
-
class Middleware < ActionDispatch::MiddlewareStack::Middleware
|
4
|
-
def update(args)
|
5
|
-
@args = args
|
6
|
-
end
|
7
|
-
end
|
8
|
-
end
|
1
|
+
require 'raisin/api/dsl'
|
9
2
|
|
3
|
+
module Raisin
|
10
4
|
class API
|
11
|
-
cattr_accessor :middleware_stack
|
12
|
-
@@middleware_stack = Raisin::MiddlewareStack.new
|
13
5
|
|
6
|
+
#
|
7
|
+
# Returns a proc representing our action to be called in the given
|
8
|
+
# environment.
|
9
|
+
#
|
14
10
|
def self.action(name, klass = ActionDispatch::Request)
|
15
|
-
|
16
|
-
self.const_get(name.camelize).new.dispatch(:call, klass.new(env))
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.use(*args, &block)
|
21
|
-
middleware_stack.use(*args, &block)
|
11
|
+
->(env) { self.const_get(name.camelize).new.dispatch(:call, klass.new(env)) }
|
22
12
|
end
|
23
13
|
|
14
|
+
#
|
15
|
+
# The abstract class that is inherited by all actions.
|
16
|
+
#
|
17
|
+
# It includes actions DSL, default call method, global authentication
|
18
|
+
# filter and global respond_to
|
19
|
+
#
|
24
20
|
def self.action_klass
|
25
21
|
@_klass ||= begin
|
26
22
|
klass = Class.new(::Raisin::Base)
|
@@ -35,10 +31,14 @@ module Raisin
|
|
35
31
|
end
|
36
32
|
end
|
37
33
|
|
38
|
-
def self.action_klass=(klass)
|
34
|
+
def self.action_klass=(klass) # :nodoc:
|
39
35
|
@_klass = klass
|
40
36
|
end
|
41
37
|
|
38
|
+
#
|
39
|
+
# Resets routes and namespaces.
|
40
|
+
# Sets default prefix to api_name (e.g PostsAPI => 'api')
|
41
|
+
#
|
42
42
|
def self.reset
|
43
43
|
@_routes = []
|
44
44
|
@_prefix = self.api_name
|
@@ -46,202 +46,29 @@ module Raisin
|
|
46
46
|
@_single_resource = false
|
47
47
|
end
|
48
48
|
|
49
|
+
#
|
50
|
+
# Resets routes and namespaces for each new API class.
|
51
|
+
# The action class is copied for some reasons (??)
|
52
|
+
#
|
49
53
|
def self.inherited(subclass)
|
50
54
|
super
|
51
55
|
subclass.reset
|
52
|
-
subclass.middleware_stack = self.middleware_stack.dup
|
53
56
|
subclass.action_klass = self.action_klass.dup
|
54
57
|
end
|
55
58
|
|
59
|
+
#
|
60
|
+
# Returns the last part of the api's name, underscored, without the ending
|
61
|
+
# <tt>API</tt>. For instance, PostsAPI returns <tt>posts</tt>.
|
62
|
+
# Namespaces are left out, so Admin::PostsAPI returns <tt>posts</tt> as well.
|
63
|
+
#
|
56
64
|
def self.api_name
|
57
65
|
@api_name ||= self.name.demodulize.sub(/api/i, '').underscore
|
58
66
|
end
|
59
67
|
|
60
|
-
def self.
|
61
|
-
m = middleware_stack.find { |m| m == klass }
|
62
|
-
if m
|
63
|
-
m.update klass.merge(m.args, args)
|
64
|
-
else
|
65
|
-
self.use(klass, *args)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def self.routes
|
68
|
+
def self.routes # :nodoc:
|
70
69
|
@_routes
|
71
70
|
end
|
72
71
|
|
73
|
-
|
74
|
-
%w(get head post put delete).each do |via|
|
75
|
-
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
76
|
-
def #{via}(path = '/', options = {}, &block)
|
77
|
-
path = normalize_path(path)
|
78
|
-
method_name = options.key?(:as) ? options[:as].to_s : extract_method_name(path, :#{via})
|
79
|
-
|
80
|
-
klass = self.const_set method_name.camelize, Class.new(@_klass, &block)
|
81
|
-
klass.send(:expose, current_namespace.exposure, &(current_namespace.lazy_expose)) if current_namespace.try(:expose?)
|
82
|
-
|
83
|
-
current_namespace.add(method_name) if current_namespace
|
84
|
-
|
85
|
-
routes << [:#{via}, path, default_route(method_name)]
|
86
|
-
end
|
87
|
-
EOF
|
88
|
-
end
|
89
|
-
|
90
|
-
def included(&block)
|
91
|
-
self.action_klass.class_eval(&block) if block_given?
|
92
|
-
end
|
93
|
-
|
94
|
-
def member(&block)
|
95
|
-
namespace(':id') do
|
96
|
-
resource = self.api_name.singularize
|
97
|
-
expose(resource) { resource.camelize.constantize.send :find, params[:id] }
|
98
|
-
instance_eval(&block)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def nested_into_resource(parent)
|
103
|
-
parent = parent.to_s
|
104
|
-
sing = parent.singularize
|
105
|
-
id = "#{sing}_id"
|
106
|
-
|
107
|
-
@_namespaces << Namespace.new("#{parent}/:#{id}")
|
108
|
-
current_namespace.expose(sing) { sing.camelize.constantize.send :find, params[id.to_sym]}
|
109
|
-
@_namespaces << Namespace.new(@_prefix)
|
110
|
-
@_prefix = nil
|
111
|
-
end
|
112
|
-
|
113
|
-
def single_resource
|
114
|
-
@_single_resource = true
|
115
|
-
@_prefix = @_prefix.singularize if prefix?
|
116
|
-
end
|
117
|
-
|
118
|
-
def prefix(prefix)
|
119
|
-
@_prefix = prefix
|
120
|
-
end
|
121
|
-
|
122
|
-
def description(desc)
|
123
|
-
# noop
|
124
|
-
end
|
125
|
-
|
126
|
-
def expose(*args, &block)
|
127
|
-
current_namespace.expose(*args, &block)
|
128
|
-
end
|
129
|
-
|
130
|
-
def namespace(path, &block)
|
131
|
-
path = path.sub(%r(\A/?#{@_prefix}), '') if prefix?
|
132
|
-
@_namespaces.push Namespace.new(path)
|
133
|
-
yield
|
134
|
-
process_filters
|
135
|
-
@_namespaces.pop
|
136
|
-
end
|
137
|
-
|
138
|
-
%w(before around after).each do |type|
|
139
|
-
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
140
|
-
def #{type}(*args, &block)
|
141
|
-
return unless current_namespace
|
142
|
-
current_namespace.filter(:#{type}, args, &block)
|
143
|
-
end
|
144
|
-
EOF
|
145
|
-
end
|
146
|
-
|
147
|
-
protected
|
148
|
-
|
149
|
-
def prefix?
|
150
|
-
!!@_prefix
|
151
|
-
end
|
152
|
-
|
153
|
-
def single_resource?
|
154
|
-
!!@_single_resource
|
155
|
-
end
|
156
|
-
|
157
|
-
def current_namespace
|
158
|
-
@_namespaces.at(0)
|
159
|
-
end
|
160
|
-
|
161
|
-
def current_namespace?
|
162
|
-
@_namespaces.length > 0
|
163
|
-
end
|
164
|
-
|
165
|
-
def process_filters
|
166
|
-
current_namespace.filters.each_pair { |type, filters|
|
167
|
-
filters.each do |name, block|
|
168
|
-
superclass.send("#{type}_filter", name, only: current_namespace.methods, &block)
|
169
|
-
end
|
170
|
-
}
|
171
|
-
end
|
172
|
-
|
173
|
-
def default_route(method)
|
174
|
-
"#{modules_prefix}#{self.api_name}##{method}"
|
175
|
-
end
|
176
|
-
|
177
|
-
def modules_prefix
|
178
|
-
@modules_prefix ||= begin
|
179
|
-
modules = self.name.split('::').slice(0..-2)
|
180
|
-
modules.empty? ? '' : "#{modules.map!(&:downcase).join('/')}/"
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
#
|
185
|
-
# Get method name from path
|
186
|
-
# Example:
|
187
|
-
# / => :index
|
188
|
-
# /users/:id => :users
|
189
|
-
# /users/:id/addresses => :addresses
|
190
|
-
#
|
191
|
-
def extract_method_name(path, via)
|
192
|
-
return method_name_for_single_resource(path, via) if single_resource?
|
193
|
-
|
194
|
-
if path =~ %r(\A/?#{@_prefix}\z)
|
195
|
-
return via == :get ? 'index' : 'create'
|
196
|
-
end
|
197
|
-
|
198
|
-
parts = path.split('/').reverse!
|
199
|
-
|
200
|
-
return parts.find { |part| !part.start_with?(':') } if parts.first != ':id'
|
201
|
-
|
202
|
-
case via
|
203
|
-
when :get
|
204
|
-
'show'
|
205
|
-
when :put
|
206
|
-
'update'
|
207
|
-
when :delete
|
208
|
-
'destroy'
|
209
|
-
else
|
210
|
-
raise "Cannot extract method name from #{path}"
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
def method_name_for_single_resource(path, via)
|
215
|
-
if path =~ %r(\A/?#{@_prefix}\z)
|
216
|
-
case via
|
217
|
-
when :get
|
218
|
-
'show'
|
219
|
-
when :post
|
220
|
-
'create'
|
221
|
-
when :put
|
222
|
-
'update'
|
223
|
-
when :delete
|
224
|
-
'destroy'
|
225
|
-
end
|
226
|
-
else
|
227
|
-
path.split('/').reverse!.last
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
#
|
232
|
-
# Creates path with version, namespace and
|
233
|
-
# given path, then normalizes it
|
234
|
-
#
|
235
|
-
def normalize_path(path)
|
236
|
-
parts = []
|
237
|
-
parts << @_prefix unless !prefix? || path =~ %r(\A/?#{@_prefix})
|
238
|
-
parts.concat @_namespaces.reject { |n| path =~ %r(/#{n.path}) }.map!(&:path) if current_namespace?
|
239
|
-
parts << path.to_s unless path == '/'
|
240
|
-
parts.join('/')
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
extend DSL
|
245
|
-
|
72
|
+
extend Raisin::DSL
|
246
73
|
end
|
247
74
|
end
|
data/lib/raisin/base.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
module Raisin
|
2
|
+
|
3
|
+
#
|
4
|
+
# Abstract class for all actions.
|
5
|
+
#
|
2
6
|
class Base < ActionController::Metal
|
3
7
|
abstract!
|
4
8
|
|
@@ -15,8 +19,6 @@ module Raisin
|
|
15
19
|
def perform_caching=(*); end
|
16
20
|
def helpers_path=(*); end
|
17
21
|
def allow_forgery_protection=(*); end
|
18
|
-
# def helper_method(*); end
|
19
|
-
# def helper(*); end
|
20
22
|
end
|
21
23
|
|
22
24
|
extend Compatibility
|
@@ -44,17 +46,10 @@ module Raisin
|
|
44
46
|
include mod
|
45
47
|
}
|
46
48
|
|
47
|
-
def self._expose(name, &block)
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
49
|
def self.controller_path
|
52
50
|
@controller_path ||= name && name.sub(/\:\:[^\:]+$/, '').sub(/api$/i, '').underscore
|
53
51
|
end
|
54
52
|
|
55
|
-
#
|
56
|
-
# Remove nil prefixes from our anonymous classes
|
57
|
-
#
|
58
53
|
def _prefixes
|
59
54
|
@_prefixes ||= begin
|
60
55
|
parent_prefixes = self.class.parent_prefixes
|
@@ -67,171 +62,12 @@ module Raisin
|
|
67
62
|
end
|
68
63
|
|
69
64
|
#
|
70
|
-
#
|
71
|
-
# called in the normal process only in tests
|
65
|
+
# In test env, action is not :call. This is a bit of a hack
|
72
66
|
#
|
73
67
|
def process(action, *args)
|
74
68
|
super(:call, *args)
|
75
69
|
end
|
76
70
|
|
77
|
-
# class << self
|
78
|
-
# attr_internal_reader :routes, :current_namespace
|
79
|
-
|
80
|
-
# alias :current_namespace? :current_namespace
|
81
|
-
|
82
|
-
# %w(get head post put delete).each do |via|
|
83
|
-
# class_eval <<-EOF, __FILE__, __LINE__ + 1
|
84
|
-
# def #{via}(path, options = nil, &block)
|
85
|
-
# path = normalize_path(path)
|
86
|
-
# method_name = extract_method_name(path, :#{via})
|
87
|
-
|
88
|
-
# endpoint = Endpoint.new
|
89
|
-
# endpoint.instance_eval(&block)
|
90
|
-
|
91
|
-
# Rails.logger.warn("WARNING: redefinition of method " << method_name) if method_defined?(method_name)
|
92
|
-
# define_method(method_name, &(endpoint.response_body))
|
93
|
-
|
94
|
-
# current_namespace.add(method_name) if current_namespace?
|
95
|
-
|
96
|
-
# routes << [:#{via}, path, default_route(method_name)]
|
97
|
-
# end
|
98
|
-
# EOF
|
99
|
-
# end
|
100
|
-
|
101
|
-
# def prefix(prefix)
|
102
|
-
# @_prefix = prefix
|
103
|
-
# end
|
104
|
-
|
105
|
-
# def prefix?
|
106
|
-
# @_prefix
|
107
|
-
# end
|
108
|
-
|
109
|
-
# def description(desc)
|
110
|
-
# # noop
|
111
|
-
# end
|
112
|
-
|
113
|
-
# # def get(path, options = nil, &block)
|
114
|
-
# # path = normalize_path(path)
|
115
|
-
# # method_name = extract_method_name(path, :get)
|
116
|
-
|
117
|
-
# # endpoint = Endpoint.new
|
118
|
-
# # endpoint.instance_eval(&block)
|
119
|
-
|
120
|
-
# # define_method(method_name, &(endpoint.response_body))
|
121
|
-
|
122
|
-
# # current_namespace.add(method_name) if current_namespace?
|
123
|
-
|
124
|
-
# # routes << [:get, path, default_route(method_name)]
|
125
|
-
# # end
|
126
|
-
# # alias_method :head, :get
|
127
|
-
|
128
|
-
# # def post(path, options = nil, &block)
|
129
|
-
# # path = normalize_path(path)
|
130
|
-
# # method_name = extract_method_name(path)
|
131
|
-
|
132
|
-
# # define_method(method_name, &block)
|
133
|
-
|
134
|
-
# # routes << [:post, path, default_route(method_name)]
|
135
|
-
# # end
|
136
|
-
|
137
|
-
# # def put(path, options = nil, &block)
|
138
|
-
# # path = normalize_path(path)
|
139
|
-
# # method_name = extract_method_name(path)
|
140
|
-
|
141
|
-
# # define_method(method_name, &block)
|
142
|
-
|
143
|
-
# # routes << [:put, path, default_route(method_name)]
|
144
|
-
# # end
|
145
|
-
# # alias_method :patch, :put
|
146
|
-
|
147
|
-
# # def delete(path, options = nil, &block)
|
148
|
-
# # path = normalize_path(path)
|
149
|
-
# # method_name = extract_method_name(path)
|
150
|
-
|
151
|
-
# # define_method(method_name, &block)
|
152
|
-
|
153
|
-
# # routes << [:delete, path, default_route(method_name)]
|
154
|
-
# # end
|
155
|
-
|
156
|
-
# def namespace(path, &block)
|
157
|
-
# path = path.sub(%r(\A/?#{@_prefix}), '') if prefix?
|
158
|
-
# old_namespace, @_current_namespace = current_namespace, Namespace.new(path)
|
159
|
-
# yield
|
160
|
-
# process_filters
|
161
|
-
# @_current_namespace = old_namespace
|
162
|
-
# end
|
163
|
-
|
164
|
-
# %w(before around after).each do |type|
|
165
|
-
# class_eval <<-EOF, __FILE__, __LINE__ + 1
|
166
|
-
# def #{type}(*args, &block)
|
167
|
-
# return unless current_namespace?
|
168
|
-
# current_namespace.filter(:#{type}, args, &block)
|
169
|
-
# end
|
170
|
-
# EOF
|
171
|
-
# end
|
172
|
-
|
173
|
-
# protected
|
174
|
-
|
175
|
-
# def process_filters
|
176
|
-
# current_namespace.filters.each_pair { |type, filters|
|
177
|
-
# filters.each do |name, block|
|
178
|
-
# superclass.send("#{type}_filter", name, only: current_namespace.methods, &block)
|
179
|
-
# end
|
180
|
-
# }
|
181
|
-
# end
|
182
|
-
|
183
|
-
# def default_route(method)
|
184
|
-
# "#{modules_prefix}#{self.api_name}##{method}"
|
185
|
-
# end
|
186
|
-
|
187
|
-
# def modules_prefix
|
188
|
-
# @modules_prefix ||= begin
|
189
|
-
# modules = self.name.split('::').slice(0..-2)
|
190
|
-
# modules.empty? ? '' : "#{modules.join('/')}/"
|
191
|
-
# end
|
192
|
-
# end
|
193
|
-
|
194
|
-
# #
|
195
|
-
# # Get method name from path
|
196
|
-
# # Example:
|
197
|
-
# # / => :index
|
198
|
-
# # /users/:id => :users
|
199
|
-
# # /users/:id/addresses => :addresses
|
200
|
-
# #
|
201
|
-
# def extract_method_name(path, via)
|
202
|
-
# return :index if path =~ %r(\A/?#{@_prefix}\z)
|
203
|
-
|
204
|
-
# parts = path.split('/').reverse!
|
205
|
-
|
206
|
-
# return parts.find { |part| !part.start_with?(':') } if parts.first != ':id'
|
207
|
-
|
208
|
-
# case via
|
209
|
-
# when :get
|
210
|
-
# :show
|
211
|
-
# when :post
|
212
|
-
# :create
|
213
|
-
# when :put
|
214
|
-
# :update
|
215
|
-
# when :delete
|
216
|
-
# :destroy
|
217
|
-
# else
|
218
|
-
# raise "Cannot extract method name from #{path}"
|
219
|
-
# end
|
220
|
-
# end
|
221
|
-
|
222
|
-
# #
|
223
|
-
# # Creates path with version, namespace and
|
224
|
-
# # given path, then normalizes it
|
225
|
-
# #
|
226
|
-
# def normalize_path(path)
|
227
|
-
# parts = []
|
228
|
-
# parts << @_prefix unless !@_prefix || path =~ %r(\A/?#{@_prefix})
|
229
|
-
# parts << current_namespace.path unless !current_namespace? || path =~ %r(/#{current_namespace.path})
|
230
|
-
# parts << path.to_s unless path == '/'
|
231
|
-
# parts.join('/')
|
232
|
-
# end
|
233
|
-
# end
|
234
|
-
|
235
71
|
ActiveSupport.run_load_hooks(:action_controller, self)
|
236
72
|
end
|
237
73
|
end
|
data/lib/raisin/exposable.rb
CHANGED
@@ -3,16 +3,19 @@ module Raisin
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :exposures
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
@exposures = []
|
7
11
|
end
|
8
12
|
|
9
13
|
def expose(name, &block)
|
10
|
-
@
|
11
|
-
@lazy_expose = block
|
14
|
+
@exposures << [name, block]
|
12
15
|
end
|
13
16
|
|
14
17
|
def expose?
|
15
|
-
|
18
|
+
!exposures.empty?
|
16
19
|
end
|
17
20
|
end
|
18
21
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Raisin
|
2
|
+
class Middleware
|
3
|
+
ACCEPT_REGEXP = /application\/vnd\.(?<vendor>[a-z]+)-(?<version>v[0-9]+)\+(?<format>[a-z]+)?/
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
@vendor = Configuration.version.vendor
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
@env = env
|
12
|
+
if verify_accept_header
|
13
|
+
@app.call(@env)
|
14
|
+
else
|
15
|
+
[406, {}, []]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def verify_accept_header
|
22
|
+
if (matches = ACCEPT_REGEXP.match(@env['HTTP_ACCEPT'])) && @vendor == matches[:vendor]
|
23
|
+
@env['raisin.version'] = matches[:version]
|
24
|
+
@env['raisin.format'] = "application/#{matches[:format]}"
|
25
|
+
true
|
26
|
+
else
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/raisin/namespace.rb
CHANGED
data/lib/raisin/rails/request.rb
CHANGED
@@ -1,7 +1,18 @@
|
|
1
1
|
module Raisin
|
2
2
|
module ApiFormat
|
3
3
|
def formats
|
4
|
-
@env["action_dispatch.request.formats"] ||=
|
4
|
+
@env["action_dispatch.request.formats"] ||=
|
5
|
+
if @env.key?('raisin.format')
|
6
|
+
Array(Mime::Type.lookup(@env['raisin.format']))
|
7
|
+
elsif parameters[:format]
|
8
|
+
Array(Mime[parameters[:format]])
|
9
|
+
elsif use_accept_header && valid_accept_header
|
10
|
+
accepts
|
11
|
+
elsif xhr?
|
12
|
+
[Mime::JS]
|
13
|
+
else
|
14
|
+
[Mime::HTML]
|
15
|
+
end
|
5
16
|
end
|
6
17
|
end
|
7
18
|
end
|
data/lib/raisin/rails/routes.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'raisin/version_constraint'
|
2
|
+
|
1
3
|
module ActionDispatch::Routing
|
2
4
|
class Mapper
|
3
5
|
|
@@ -5,8 +7,14 @@ module ActionDispatch::Routing
|
|
5
7
|
#
|
6
8
|
#
|
7
9
|
def mount_api(raisin_api)
|
8
|
-
raisin_api.
|
9
|
-
|
10
|
+
raisin_api.versions.each do |version, routes|
|
11
|
+
next if routes.empty?
|
12
|
+
|
13
|
+
send(:constraints, Raisin::VersionConstraint.new(version)) {
|
14
|
+
routes.each do |method, path, endpoint|
|
15
|
+
send(method, path, to: endpoint)
|
16
|
+
end
|
17
|
+
}
|
10
18
|
end
|
11
19
|
end
|
12
20
|
end
|
data/lib/raisin/railtie.rb
CHANGED
data/lib/raisin/router.rb
CHANGED
@@ -1,36 +1,6 @@
|
|
1
1
|
module Raisin
|
2
2
|
class Router
|
3
|
-
|
4
|
-
ALL = 'all'
|
5
|
-
|
6
|
-
attr_reader :version, :type, :options
|
7
|
-
|
8
|
-
def initialize(version, options = {})
|
9
|
-
@version = version.to_s
|
10
|
-
@type = options.delete(:using).try(:to_sym) || Configuration.version.using
|
11
|
-
@options = { vendor: Configuration.version.vendor }.merge!(options)
|
12
|
-
|
13
|
-
validate!
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def validate!
|
19
|
-
raise 'Missing :using options for version' unless type
|
20
|
-
|
21
|
-
case type
|
22
|
-
when :header
|
23
|
-
raise 'Missing :vendor options when using header versionning' unless options[:vendor]
|
24
|
-
when :path
|
25
|
-
raise ':all cannot be used with path versionning' if version == ALL
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.reset
|
31
|
-
@_current_version = nil
|
32
|
-
@_routes = []
|
33
|
-
end
|
3
|
+
ALL_VERSIONS = :all
|
34
4
|
|
35
5
|
#
|
36
6
|
# Reset class variables on the subclass when inherited
|
@@ -39,31 +9,37 @@ module Raisin
|
|
39
9
|
subclass.reset
|
40
10
|
end
|
41
11
|
|
12
|
+
def self.reset
|
13
|
+
@_current_version = nil
|
14
|
+
@_versions = { ALL_VERSIONS => [] }
|
15
|
+
end
|
16
|
+
|
42
17
|
class << self
|
43
|
-
attr_internal_accessor :
|
18
|
+
attr_internal_accessor :versions, :current_version
|
44
19
|
|
45
20
|
def mount(api)
|
46
|
-
|
47
|
-
|
21
|
+
if version?(:header)
|
22
|
+
@_versions[self.current_version].concat(api.routes)
|
23
|
+
else
|
24
|
+
@_versions[ALL_VERSIONS].concat(version?(:path) ? pathed_routes(api.routes) : api.routes)
|
25
|
+
end
|
48
26
|
end
|
49
27
|
|
50
28
|
#
|
51
29
|
# Set version for current block
|
52
30
|
#
|
53
31
|
def version(version, options = {}, &block)
|
54
|
-
|
32
|
+
version = version.to_s
|
33
|
+
self.current_version = version
|
34
|
+
@_versions[version] = [] if version?(:header)
|
55
35
|
yield
|
56
36
|
self.current_version = nil
|
57
37
|
end
|
58
38
|
|
59
39
|
private
|
60
40
|
|
61
|
-
def mount_version_middleware(api)
|
62
|
-
api.use_or_update Middleware::Header, self.current_version.version, self.current_version.options
|
63
|
-
end
|
64
|
-
|
65
41
|
def version?(type)
|
66
|
-
self.current_version &&
|
42
|
+
self.current_version && Configuration.version.using == type
|
67
43
|
end
|
68
44
|
|
69
45
|
def pathed_routes(routes)
|
@@ -75,16 +51,5 @@ module Raisin
|
|
75
51
|
end
|
76
52
|
end
|
77
53
|
|
78
|
-
# #
|
79
|
-
# # Make the API a rack endpoint
|
80
|
-
# #
|
81
|
-
# def self.call(env)
|
82
|
-
# @_route_set.freeze unless @_route_set.frozen?
|
83
|
-
# @_route_set.call(env)
|
84
|
-
# end
|
85
|
-
|
86
|
-
# Mount Raisin::Base into the api
|
87
|
-
#
|
88
|
-
|
89
54
|
end
|
90
55
|
end
|
data/lib/raisin/version.rb
CHANGED
data/lib/raisin.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
1
1
|
require 'raisin/version'
|
2
2
|
|
3
|
-
require 'raisin/middleware/base'
|
4
|
-
require 'raisin/middleware/header'
|
5
|
-
|
6
3
|
require 'raisin/configuration'
|
7
4
|
|
8
5
|
require 'raisin/exposable'
|
@@ -13,6 +10,9 @@ require 'raisin/router'
|
|
13
10
|
require 'raisin/base'
|
14
11
|
require 'raisin/api'
|
15
12
|
|
13
|
+
require 'raisin/middleware'
|
14
|
+
require 'raisin/version_constraint'
|
15
|
+
|
16
16
|
module Raisin
|
17
17
|
def self.configure
|
18
18
|
yield Configuration if block_given?
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: raisin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-20 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: An opiniated micro-framework to easily build elegant API on top of Rails
|
15
15
|
email:
|
@@ -25,12 +25,12 @@ files:
|
|
25
25
|
- Rakefile
|
26
26
|
- lib/raisin.rb
|
27
27
|
- lib/raisin/api.rb
|
28
|
+
- lib/raisin/api/dsl.rb
|
28
29
|
- lib/raisin/base.rb
|
29
30
|
- lib/raisin/configuration.rb
|
30
31
|
- lib/raisin/endpoint.rb
|
31
32
|
- lib/raisin/exposable.rb
|
32
|
-
- lib/raisin/middleware
|
33
|
-
- lib/raisin/middleware/header.rb
|
33
|
+
- lib/raisin/middleware.rb
|
34
34
|
- lib/raisin/mixin.rb
|
35
35
|
- lib/raisin/namespace.rb
|
36
36
|
- lib/raisin/rails/request.rb
|
@@ -45,6 +45,7 @@ files:
|
|
45
45
|
- lib/raisin/testing/rspec/unit_helper.rb
|
46
46
|
- lib/raisin/testing/rspec/unit_test.rb
|
47
47
|
- lib/raisin/version.rb
|
48
|
+
- lib/raisin/version_constraint.rb
|
48
49
|
- raisin.gemspec
|
49
50
|
homepage: https://github.com/ccocchi/raisin
|
50
51
|
licenses: []
|
@@ -1,39 +0,0 @@
|
|
1
|
-
module Raisin
|
2
|
-
module Middleware
|
3
|
-
class Header < Base
|
4
|
-
|
5
|
-
def self.merge(base, other)
|
6
|
-
base_options, other_options = base.pop, other.pop
|
7
|
-
[base.concat(other), base_options.merge!(other_options)]
|
8
|
-
end
|
9
|
-
|
10
|
-
attr_reader :options, :versions
|
11
|
-
|
12
|
-
def initialize(app, versions, options = {})
|
13
|
-
super
|
14
|
-
@options = options
|
15
|
-
@versions = Array(versions)
|
16
|
-
end
|
17
|
-
|
18
|
-
def call(env)
|
19
|
-
@env = env
|
20
|
-
return [406, {}, ["You shall not pass!"]] unless verify_http_accept_header
|
21
|
-
super
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def verify_http_accept_header
|
27
|
-
header = @env['HTTP_ACCEPT']
|
28
|
-
if (matches = %r{application/vnd\.(?<vendor>[a-z]+)-(?<version>v[0-9]+)\+(?<format>[a-z]+)?}.match(header)) &&
|
29
|
-
(versions.include?(matches[:version]) || versions.include?(Raisin::Router::Version::ALL)) &&
|
30
|
-
(!options.key?(:vendor) || options[:vendor] == matches[:vendor])
|
31
|
-
|
32
|
-
@env['raisin.request.formats'] = [Mime::Type.lookup("application/#{matches[:format]}")]
|
33
|
-
return true
|
34
|
-
end
|
35
|
-
false
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|