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