raisin 0.0.1
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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/raisin/api.rb +251 -0
- data/lib/raisin/base.rb +237 -0
- data/lib/raisin/configuration.rb +25 -0
- data/lib/raisin/endpoint.rb +39 -0
- data/lib/raisin/exposable.rb +18 -0
- data/lib/raisin/middleware/base.rb +13 -0
- data/lib/raisin/middleware/header.rb +39 -0
- data/lib/raisin/mixin.rb +53 -0
- data/lib/raisin/namespace.rb +25 -0
- data/lib/raisin/rails/request.rb +9 -0
- data/lib/raisin/rails/routes.rb +34 -0
- data/lib/raisin/railtie.rb +13 -0
- data/lib/raisin/router.rb +90 -0
- data/lib/raisin/testing/rspec/api_helper.rb +12 -0
- data/lib/raisin/testing/rspec/dsl.rb +97 -0
- data/lib/raisin/testing/rspec/functional_test.rb +28 -0
- data/lib/raisin/testing/rspec/test_request.rb +21 -0
- data/lib/raisin/testing/rspec/unit_helper.rb +34 -0
- data/lib/raisin/testing/rspec/unit_test.rb +21 -0
- data/lib/raisin/testing/rspec.rb +10 -0
- data/lib/raisin/version.rb +3 -0
- data/lib/raisin.rb +22 -0
- data/raisin.gemspec +19 -0
- metadata +73 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2012 ccocchi
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Raisin
|
|
2
|
+
|
|
3
|
+
Elegant, modular and performant APIs in Rails
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
gem 'raisin'
|
|
10
|
+
|
|
11
|
+
And then execute:
|
|
12
|
+
|
|
13
|
+
$ bundle
|
|
14
|
+
|
|
15
|
+
Or install it yourself as:
|
|
16
|
+
|
|
17
|
+
$ gem install raisin
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
Soon
|
|
22
|
+
|
|
23
|
+
## Contributing
|
|
24
|
+
|
|
25
|
+
1. Fork it
|
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/raisin/api.rb
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
module Raisin
|
|
2
|
+
class MiddlewareStack < ActionDispatch::MiddlewareStack
|
|
3
|
+
class Middleware < ActionDispatch::MiddlewareStack::Middleware
|
|
4
|
+
def update(args)
|
|
5
|
+
@args = args
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def build(action, app=nil, &block)
|
|
10
|
+
super(app, &block)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class API
|
|
15
|
+
class_attribute :middleware_stack
|
|
16
|
+
self.middleware_stack = Raisin::MiddlewareStack.new
|
|
17
|
+
|
|
18
|
+
def self.action(name, klass = ActionDispatch::Request)
|
|
19
|
+
middleware_stack.build(name) do |env|
|
|
20
|
+
self.const_get(name.camelize).new.dispatch(:call, klass.new(env))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.use(*args, &block)
|
|
25
|
+
middleware_stack.use(*args, &block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.action_klass
|
|
29
|
+
@_klass ||= begin
|
|
30
|
+
klass = Class.new(::Raisin::Base)
|
|
31
|
+
klass.send(:include, Raisin::Mixin)
|
|
32
|
+
|
|
33
|
+
if Configuration.enable_auth_by_default && Configuration.default_auth_method
|
|
34
|
+
klass.send(:before_filter, Configuration.default_auth_method)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
klass.send(:respond_to, *Configuration.response_formats)
|
|
38
|
+
klass
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.action_klass=(klass)
|
|
43
|
+
@_klass = klass
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.reset
|
|
47
|
+
@_routes = []
|
|
48
|
+
@_prefix = self.api_name
|
|
49
|
+
@_namespaces = []
|
|
50
|
+
@_single_resource = false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.inherited(subclass)
|
|
54
|
+
subclass.reset
|
|
55
|
+
subclass.middleware_stack = self.middleware_stack.dup
|
|
56
|
+
subclass.action_klass = self.action_klass.dup
|
|
57
|
+
super
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.api_name
|
|
61
|
+
@api_name ||= self.name.demodulize.sub(/api/i, '').underscore
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.use_or_update(klass, *args)
|
|
65
|
+
m = middleware_stack.find { |m| m == klass }
|
|
66
|
+
if m
|
|
67
|
+
m.update klass.merge(m.args, args)
|
|
68
|
+
else
|
|
69
|
+
self.use(klass, *args)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.routes
|
|
74
|
+
@_routes
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
module DSL
|
|
78
|
+
%w(get head post put delete).each do |via|
|
|
79
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
|
80
|
+
def #{via}(path = '/', options = {}, &block)
|
|
81
|
+
path = normalize_path(path)
|
|
82
|
+
method_name = options.key?(:as) ? options[:as].to_s : extract_method_name(path, :#{via})
|
|
83
|
+
|
|
84
|
+
klass = self.const_set method_name.camelize, Class.new(@_klass, &block)
|
|
85
|
+
klass.send(:expose, current_namespace.exposure, &(current_namespace.lazy_expose)) if current_namespace.try(:expose?)
|
|
86
|
+
|
|
87
|
+
current_namespace.add(method_name) if current_namespace
|
|
88
|
+
|
|
89
|
+
routes << [:#{via}, path, default_route(method_name)]
|
|
90
|
+
end
|
|
91
|
+
EOF
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def included(&block)
|
|
95
|
+
self.action_klass.class_eval(&block) if block_given?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def member(&block)
|
|
99
|
+
namespace(':id') do
|
|
100
|
+
resource = self.api_name.singularize
|
|
101
|
+
expose(resource) { resource.camelize.constantize.send :find, params[:id] }
|
|
102
|
+
instance_eval(&block)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def nested_into_resource(parent)
|
|
107
|
+
parent = parent.to_s
|
|
108
|
+
sing = parent.singularize
|
|
109
|
+
id = "#{sing}_id"
|
|
110
|
+
|
|
111
|
+
@_namespaces << Namespace.new("#{parent}/:#{id}")
|
|
112
|
+
current_namespace.expose(sing) { sing.camelize.constantize.send :find, params[id.to_sym]}
|
|
113
|
+
@_namespaces << Namespace.new(@_prefix)
|
|
114
|
+
@_prefix = nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def single_resource
|
|
118
|
+
@_single_resource = true
|
|
119
|
+
@_prefix = @_prefix.singularize if prefix?
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def prefix(prefix)
|
|
123
|
+
@_prefix = prefix
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def description(desc)
|
|
127
|
+
# noop
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def expose(*args, &block)
|
|
131
|
+
current_namespace.expose(*args, &block)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def namespace(path, &block)
|
|
135
|
+
path = path.sub(%r(\A/?#{@_prefix}), '') if prefix?
|
|
136
|
+
@_namespaces.push Namespace.new(path)
|
|
137
|
+
yield
|
|
138
|
+
process_filters
|
|
139
|
+
@_namespaces.pop
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
%w(before around after).each do |type|
|
|
143
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
|
144
|
+
def #{type}(*args, &block)
|
|
145
|
+
return unless current_namespace
|
|
146
|
+
current_namespace.filter(:#{type}, args, &block)
|
|
147
|
+
end
|
|
148
|
+
EOF
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
protected
|
|
152
|
+
|
|
153
|
+
def prefix?
|
|
154
|
+
!!@_prefix
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def single_resource?
|
|
158
|
+
!!@_single_resource
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def current_namespace
|
|
162
|
+
@_namespaces.at(0)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def current_namespace?
|
|
166
|
+
@_namespaces.length > 0
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def process_filters
|
|
170
|
+
current_namespace.filters.each_pair { |type, filters|
|
|
171
|
+
filters.each do |name, block|
|
|
172
|
+
superclass.send("#{type}_filter", name, only: current_namespace.methods, &block)
|
|
173
|
+
end
|
|
174
|
+
}
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def default_route(method)
|
|
178
|
+
"#{modules_prefix}#{self.api_name}##{method}"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def modules_prefix
|
|
182
|
+
@modules_prefix ||= begin
|
|
183
|
+
modules = self.name.split('::').slice(0..-2)
|
|
184
|
+
modules.empty? ? '' : "#{modules.map!(&:downcase).join('/')}/"
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
#
|
|
189
|
+
# Get method name from path
|
|
190
|
+
# Example:
|
|
191
|
+
# / => :index
|
|
192
|
+
# /users/:id => :users
|
|
193
|
+
# /users/:id/addresses => :addresses
|
|
194
|
+
#
|
|
195
|
+
def extract_method_name(path, via)
|
|
196
|
+
return method_name_for_single_resource(path, via) if single_resource?
|
|
197
|
+
|
|
198
|
+
if path =~ %r(\A/?#{@_prefix}\z)
|
|
199
|
+
return via == :get ? 'index' : 'create'
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
parts = path.split('/').reverse!
|
|
203
|
+
|
|
204
|
+
return parts.find { |part| !part.start_with?(':') } if parts.first != ':id'
|
|
205
|
+
|
|
206
|
+
case via
|
|
207
|
+
when :get
|
|
208
|
+
'show'
|
|
209
|
+
when :put
|
|
210
|
+
'update'
|
|
211
|
+
when :delete
|
|
212
|
+
'destroy'
|
|
213
|
+
else
|
|
214
|
+
raise "Cannot extract method name from #{path}"
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def method_name_for_single_resource(path, via)
|
|
219
|
+
if path =~ %r(\A/?#{@_prefix}\z)
|
|
220
|
+
case via
|
|
221
|
+
when :get
|
|
222
|
+
'show'
|
|
223
|
+
when :post
|
|
224
|
+
'create'
|
|
225
|
+
when :put
|
|
226
|
+
'update'
|
|
227
|
+
when :delete
|
|
228
|
+
'destroy'
|
|
229
|
+
end
|
|
230
|
+
else
|
|
231
|
+
path.split('/').reverse!.last
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
#
|
|
236
|
+
# Creates path with version, namespace and
|
|
237
|
+
# given path, then normalizes it
|
|
238
|
+
#
|
|
239
|
+
def normalize_path(path)
|
|
240
|
+
parts = []
|
|
241
|
+
parts << @_prefix unless !prefix? || path =~ %r(\A/?#{@_prefix})
|
|
242
|
+
parts.concat @_namespaces.reject { |n| path =~ %r(/#{n.path}) }.map!(&:path) if current_namespace?
|
|
243
|
+
parts << path.to_s unless path == '/'
|
|
244
|
+
parts.join('/')
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
extend DSL
|
|
249
|
+
|
|
250
|
+
end
|
|
251
|
+
end
|
data/lib/raisin/base.rb
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
module Raisin
|
|
2
|
+
class Base < ActionController::Metal
|
|
3
|
+
abstract!
|
|
4
|
+
|
|
5
|
+
module Compatibility
|
|
6
|
+
def cache_store; end
|
|
7
|
+
def cache_store=(*); end
|
|
8
|
+
def assets_dir=(*); end
|
|
9
|
+
def javascripts_dir=(*); end
|
|
10
|
+
def stylesheets_dir=(*); end
|
|
11
|
+
def page_cache_directory=(*); end
|
|
12
|
+
def asset_path=(*); end
|
|
13
|
+
def asset_host=(*); end
|
|
14
|
+
def relative_url_root=(*); end
|
|
15
|
+
def perform_caching=(*); end
|
|
16
|
+
def helpers_path=(*); end
|
|
17
|
+
def allow_forgery_protection=(*); end
|
|
18
|
+
# def helper_method(*); end
|
|
19
|
+
# def helper(*); end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
extend Compatibility
|
|
23
|
+
|
|
24
|
+
MODULES = [
|
|
25
|
+
AbstractController::Helpers,
|
|
26
|
+
ActionController::UrlFor,
|
|
27
|
+
ActionController::Rendering,
|
|
28
|
+
ActionController::Renderers::All,
|
|
29
|
+
|
|
30
|
+
ActionController::ConditionalGet,
|
|
31
|
+
|
|
32
|
+
ActionController::RackDelegation,
|
|
33
|
+
ActionController::MimeResponds,
|
|
34
|
+
ActionController::ImplicitRender,
|
|
35
|
+
ActionController::DataStreaming,
|
|
36
|
+
|
|
37
|
+
AbstractController::Callbacks,
|
|
38
|
+
ActionController::Rescue,
|
|
39
|
+
|
|
40
|
+
ActionController::Instrumentation
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
MODULES.each { |mod|
|
|
44
|
+
include mod
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def self._expose(name, &block)
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.controller_path
|
|
52
|
+
@controller_path ||= name && name.sub(/\:\:[^\:]+$/, '').sub(/api$/i, '').underscore
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
#
|
|
56
|
+
# Remove nil prefixes from our anonymous classes
|
|
57
|
+
#
|
|
58
|
+
def _prefixes
|
|
59
|
+
@_prefixes ||= begin
|
|
60
|
+
parent_prefixes = self.class.parent_prefixes
|
|
61
|
+
parent_prefixes.compact.unshift(controller_path)#.map! { |pr| pr.split('/').last }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def action_name
|
|
66
|
+
self.class.name.demodulize.underscore
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
#
|
|
70
|
+
# `call` is the only method to be processed. #process is not
|
|
71
|
+
# called in the normal process only in tests
|
|
72
|
+
#
|
|
73
|
+
def process(action, *args)
|
|
74
|
+
super(:call, *args)
|
|
75
|
+
end
|
|
76
|
+
|
|
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
|
+
ActiveSupport.run_load_hooks(:action_controller, self)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Raisin
|
|
2
|
+
class VersionConfig
|
|
3
|
+
attr_accessor :vendor
|
|
4
|
+
attr_writer :using
|
|
5
|
+
|
|
6
|
+
def using
|
|
7
|
+
@using || :header
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module Configuration
|
|
12
|
+
mattr_accessor :enable_auth_by_default
|
|
13
|
+
@@enable_auth_by_default = false
|
|
14
|
+
|
|
15
|
+
mattr_accessor :default_auth_method
|
|
16
|
+
@@default_auth_method = :authenticate_user! # Devise FTW
|
|
17
|
+
|
|
18
|
+
mattr_accessor :response_formats
|
|
19
|
+
@@response_formats = [:json]
|
|
20
|
+
|
|
21
|
+
def self.version
|
|
22
|
+
@version_config ||= VersionConfig.new
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Raisin
|
|
2
|
+
class Endpoint
|
|
3
|
+
include Exposable
|
|
4
|
+
|
|
5
|
+
attr_reader :response_body, :auth_method, :formats
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@response_body = nil
|
|
9
|
+
@auth_method = nil
|
|
10
|
+
@formats = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def response(&block)
|
|
14
|
+
@response_body = block
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def has_response?
|
|
18
|
+
!!response_body
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def desc(description)
|
|
22
|
+
# noop
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def format(*mime_types)
|
|
26
|
+
@formats.concat mime_types
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def enable_auth(method = Configuration.default_auth_method)
|
|
30
|
+
return if Configuration.enable_auth_by_default
|
|
31
|
+
@auth_method = method
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def skip_auth(method = Configuration.default_auth_method)
|
|
35
|
+
return unless Configuration.enable_auth_by_default
|
|
36
|
+
@auth_method = method
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Raisin
|
|
2
|
+
module Exposable
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
attr_reader :exposure, :lazy_expose
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def expose(name, &block)
|
|
10
|
+
@exposure = name
|
|
11
|
+
@lazy_expose = block
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def expose?
|
|
15
|
+
!!@exposure
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
data/lib/raisin/mixin.rb
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Raisin
|
|
2
|
+
module Mixin
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
def call
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def response(&block)
|
|
10
|
+
define_method(:call, &block) if block_given?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def desc(description)
|
|
14
|
+
# noop
|
|
15
|
+
end
|
|
16
|
+
alias_method :description, :desc
|
|
17
|
+
|
|
18
|
+
def format(*args)
|
|
19
|
+
self.class_eval <<-EOF, __FILE__, __LINE__ + 1
|
|
20
|
+
respond_to(*#{args})
|
|
21
|
+
EOF
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def enable_auth(method = nil)
|
|
25
|
+
method ||= Configuration.default_auth_method
|
|
26
|
+
send(:before_filter, method) unless Configuration.enable_auth_by_default
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def disable_auth(method = nil)
|
|
30
|
+
method ||= Configuration.default_auth_method
|
|
31
|
+
send(:skip_before_filter, method) if Configuration.enable_auth_by_default
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def expose(name, &block)
|
|
35
|
+
if block_given?
|
|
36
|
+
define_method(name) do |*args|
|
|
37
|
+
ivar = "@#{name}"
|
|
38
|
+
|
|
39
|
+
if instance_variable_defined?(ivar)
|
|
40
|
+
instance_variable_get(ivar)
|
|
41
|
+
else
|
|
42
|
+
instance_variable_set(ivar, instance_exec(block, *args, &block))
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
else
|
|
46
|
+
attr_reader name
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
helper_method name
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Raisin
|
|
2
|
+
class Namespace
|
|
3
|
+
include Exposable
|
|
4
|
+
|
|
5
|
+
attr_reader :path, :methods, :filters
|
|
6
|
+
|
|
7
|
+
def initialize(path)
|
|
8
|
+
@path = path
|
|
9
|
+
@methods = []
|
|
10
|
+
@filters = {
|
|
11
|
+
before: [],
|
|
12
|
+
after: [],
|
|
13
|
+
around: []
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add(method)
|
|
18
|
+
@methods << method
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def filter(type, *args, &block)
|
|
22
|
+
@filters[type] << [args.first, block]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module ActionDispatch::Routing
|
|
2
|
+
class Mapper
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
#
|
|
6
|
+
#
|
|
7
|
+
def mount_api(raisin_api)
|
|
8
|
+
raisin_api.routes.each do |method, path, endpoint|
|
|
9
|
+
send(method, path, to: endpoint) # get '/users', to: 'users#index'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class RouteSet
|
|
15
|
+
class Dispatcher
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# Allow to use controller like 'UsersAPI' instead of 'UsersController'
|
|
19
|
+
#
|
|
20
|
+
def controller_reference_with_api(controller_param)
|
|
21
|
+
controller_name = "#{controller_param.camelize}Api"
|
|
22
|
+
unless controller = @controllers[controller_param]
|
|
23
|
+
controller = @controllers[controller_param] =
|
|
24
|
+
ActiveSupport::Dependencies.reference(controller_name)
|
|
25
|
+
end
|
|
26
|
+
controller.get(controller_name)
|
|
27
|
+
rescue NameError
|
|
28
|
+
controller_reference_without_api(controller_param)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
alias_method_chain :controller_reference, :api
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'raisin/rails/routes'
|
|
2
|
+
require 'raisin/rails/request'
|
|
3
|
+
|
|
4
|
+
module Raisin
|
|
5
|
+
class Railtie < Rails::Railtie
|
|
6
|
+
|
|
7
|
+
# Force routes to be loaded if we are doing any eager load.
|
|
8
|
+
config.before_eager_load { |app| app.reload_routes! }
|
|
9
|
+
|
|
10
|
+
initializer "raisin.initialize" do |app|
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module Raisin
|
|
2
|
+
class Router
|
|
3
|
+
class Version
|
|
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
|
|
34
|
+
|
|
35
|
+
#
|
|
36
|
+
# Reset class variables on the subclass when inherited
|
|
37
|
+
#
|
|
38
|
+
def self.inherited(subclass)
|
|
39
|
+
subclass.reset
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class << self
|
|
43
|
+
attr_internal_accessor :routes, :current_version
|
|
44
|
+
|
|
45
|
+
def mount(api)
|
|
46
|
+
mount_version_middleware(api) if version?(:header)
|
|
47
|
+
self.routes.concat(version?(:path) ? pathed_routes(api.routes) : api.routes)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#
|
|
51
|
+
# Set version for current block
|
|
52
|
+
#
|
|
53
|
+
def version(version, options = {}, &block)
|
|
54
|
+
self.current_version = Version.new(version, options)
|
|
55
|
+
yield
|
|
56
|
+
self.current_version = nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
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
|
+
def version?(type)
|
|
66
|
+
self.current_version && self.current_version.type == type
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def pathed_routes(routes)
|
|
70
|
+
self.routes.map! { |via, path, opts|
|
|
71
|
+
path.append('/') unless path.start_with?('/')
|
|
72
|
+
path.append(current_version.version)
|
|
73
|
+
[via, path, opts]
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
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
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require 'raisin/testing/rspec/unit_helper'
|
|
2
|
+
require 'raisin/testing/rspec/api_helper'
|
|
3
|
+
|
|
4
|
+
module Raisin
|
|
5
|
+
module Testing
|
|
6
|
+
module RSpec
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
#
|
|
10
|
+
#
|
|
11
|
+
module HttpMethods
|
|
12
|
+
%w(get head post put delete).each do |verb|
|
|
13
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
|
14
|
+
def #{verb}(*args)
|
|
15
|
+
method = self.example.metadata[:current_method].to_s.downcase
|
|
16
|
+
super(method, *args)
|
|
17
|
+
end
|
|
18
|
+
EOF
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#
|
|
23
|
+
#
|
|
24
|
+
#
|
|
25
|
+
module DSL
|
|
26
|
+
%w(get head post put delete).each do |verb|
|
|
27
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
|
28
|
+
def #{verb}(path_or_klass, &block)
|
|
29
|
+
self.metadata[:current_klass] = _klass_from_path(path_or_klass, :#{verb})
|
|
30
|
+
self.instance_eval(&block)
|
|
31
|
+
self.metadata.delete(:current_klass)
|
|
32
|
+
end
|
|
33
|
+
EOF
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def unit(&block)
|
|
37
|
+
_describe_controller(self.metadata[:current_klass], self, &block).tap do |klass|
|
|
38
|
+
klass.send(:include, Raisin::RSpecUnitHelper)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def response(format = :json, &block)
|
|
43
|
+
self.metadata[:type] = :api
|
|
44
|
+
_describe_controller(self.metadata[:current_klass], self, &block).tap do |klass|
|
|
45
|
+
klass.send(:include, Raisin::RSpecApiHelper)
|
|
46
|
+
klass.send(:include, HttpMethods)
|
|
47
|
+
klass.send(:render_views) if ::Raisin::Configuration.testing_render_views
|
|
48
|
+
klass.send(:before) {
|
|
49
|
+
request.accept = format.is_a?(Symbol) ? "application/#{format}" : format
|
|
50
|
+
}
|
|
51
|
+
self.metadata.delete(:type)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
protected
|
|
56
|
+
|
|
57
|
+
def _klass_from_path(path_or_klass, via)
|
|
58
|
+
api_klass = _find_first_api_klass
|
|
59
|
+
|
|
60
|
+
case path_or_klass
|
|
61
|
+
when String
|
|
62
|
+
method_name = api_klass.send(:extract_method_name, api_klass.send(:normalize_path, path_or_klass), via)
|
|
63
|
+
self.metadata[:current_method] = method_name
|
|
64
|
+
api_klass.const_get(method_name.camelize)
|
|
65
|
+
when Symbol
|
|
66
|
+
self.metadata[:current_method] = path_or_klass
|
|
67
|
+
api_klass.const_get(path_or_klass.to_s.camelize)
|
|
68
|
+
else
|
|
69
|
+
path_or_klass
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def _find_first_api_klass
|
|
74
|
+
metadata = self.metadata[:example_group]
|
|
75
|
+
klass = nil
|
|
76
|
+
|
|
77
|
+
until metadata.nil? || klass.respond_to?(:new)
|
|
78
|
+
klass = metadata[:description_args].first
|
|
79
|
+
metadata = metadata[:example_group]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
klass
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def _describe_controller(klass, parent, &block)
|
|
86
|
+
result = parent.describe(klass, &block)
|
|
87
|
+
result.instance_eval <<-EOF
|
|
88
|
+
def controller_class
|
|
89
|
+
#{klass}
|
|
90
|
+
end
|
|
91
|
+
EOF
|
|
92
|
+
result
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'raisin/testing/rspec/test_request'
|
|
2
|
+
|
|
3
|
+
module Raisin
|
|
4
|
+
module FunctionalTest
|
|
5
|
+
def self.append_features(base)
|
|
6
|
+
base.class_eval do
|
|
7
|
+
include RSpec::Rails::ControllerExampleGroup
|
|
8
|
+
extend ClassMethods
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
def controller_class
|
|
16
|
+
metadata = self.metadata[:example_group]
|
|
17
|
+
klass = nil
|
|
18
|
+
|
|
19
|
+
until metadata.nil? || klass.respond_to?(:new)
|
|
20
|
+
klass = metadata[:description_args].first
|
|
21
|
+
metadata = metadata[:example_group]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
klass.respond_to?(:new) ? klass : super
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module ActionController
|
|
2
|
+
class TestRequest < ActionDispatch::TestRequest
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# Transforms Raisin controller's path as a standart Rails path
|
|
6
|
+
# before assigning parameters to the request
|
|
7
|
+
#
|
|
8
|
+
# Example:
|
|
9
|
+
# controller_path: 'users_api#index', action: 'index'
|
|
10
|
+
# becomes
|
|
11
|
+
# controller_path: 'users', action: 'index'
|
|
12
|
+
#
|
|
13
|
+
def assign_parameters_with_api(routes, controller_path, action, parameters = {})
|
|
14
|
+
controller_path.sub!(/_api/, '')
|
|
15
|
+
controller_path.sub!(/\/?#{action}/, '')
|
|
16
|
+
assign_parameters_without_api(routes, controller_path, action, parameters)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
alias_method_chain :assign_parameters, :api
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Raisin
|
|
2
|
+
module UnitHelper
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
module ClassMethods
|
|
6
|
+
def controller_class
|
|
7
|
+
metadata = self.metadata[:example_group]
|
|
8
|
+
klass = nil
|
|
9
|
+
|
|
10
|
+
until metadata.nil? || klass.respond_to?(:new)
|
|
11
|
+
klass = metadata[:description_args].first
|
|
12
|
+
metadata = metadata[:example_group]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
klass.respond_to?(:new) ? klass : super
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def controller
|
|
20
|
+
self.class.controller_class.new.tap do |c|
|
|
21
|
+
c.request = request
|
|
22
|
+
c.response = response
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def request
|
|
27
|
+
@request ||= ActionDispatch::TestRequest.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def response
|
|
31
|
+
@response ||= ActionDispatch::TestResponse.new
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'raisin/testing/rspec/unit_helper'
|
|
2
|
+
|
|
3
|
+
module Raisin
|
|
4
|
+
module UnitTest
|
|
5
|
+
if defined?(RSpec::Rails)
|
|
6
|
+
include RSpec::Rails::RailsExampleGroup
|
|
7
|
+
include RSpec::Rails::Matchers::RedirectTo
|
|
8
|
+
include RSpec::Rails::Matchers::RenderTemplate
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.append_features(base)
|
|
12
|
+
base.class_eval do
|
|
13
|
+
include Raisin::UnitHelper
|
|
14
|
+
subject { controller }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# require 'rspec'
|
|
2
|
+
# require 'raisin/testing/rspec/dsl'
|
|
3
|
+
require 'raisin/testing/rspec/unit_test'
|
|
4
|
+
require 'raisin/testing/rspec/functional_test'
|
|
5
|
+
|
|
6
|
+
# require 'raisin/testing/rspec/helper'
|
|
7
|
+
|
|
8
|
+
# RSpec.configure do |c|
|
|
9
|
+
# c.extend(Raisin::Testing::RSpec::DSL)
|
|
10
|
+
# end
|
data/lib/raisin.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'raisin/version'
|
|
2
|
+
|
|
3
|
+
require 'raisin/middleware/base'
|
|
4
|
+
require 'raisin/middleware/header'
|
|
5
|
+
|
|
6
|
+
require 'raisin/configuration'
|
|
7
|
+
|
|
8
|
+
require 'raisin/exposable'
|
|
9
|
+
require 'raisin/namespace'
|
|
10
|
+
require 'raisin/mixin'
|
|
11
|
+
|
|
12
|
+
require 'raisin/router'
|
|
13
|
+
require 'raisin/base'
|
|
14
|
+
require 'raisin/api'
|
|
15
|
+
|
|
16
|
+
module Raisin
|
|
17
|
+
def self.configure
|
|
18
|
+
yield Configuration if block_given?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
require 'raisin/railtie'
|
data/raisin.gemspec
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'raisin/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |gem|
|
|
7
|
+
gem.name = "raisin"
|
|
8
|
+
gem.version = Raisin::VERSION
|
|
9
|
+
gem.authors = ["ccocchi"]
|
|
10
|
+
gem.email = ["cocchi.c@gmail.com"]
|
|
11
|
+
gem.description = %q{An opiniated micro-framework to easily build elegant API on top of Rails}
|
|
12
|
+
gem.summary = %q{An opiniated micro-framework to easily build elegant API on top of Rails}
|
|
13
|
+
gem.homepage = "https://github.com/ccocchi/raisin"
|
|
14
|
+
|
|
15
|
+
gem.files = `git ls-files`.split($/)
|
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
gem.require_paths = ["lib"]
|
|
19
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: raisin
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- ccocchi
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2012-11-13 00:00:00.000000000 Z
|
|
13
|
+
dependencies: []
|
|
14
|
+
description: An opiniated micro-framework to easily build elegant API on top of Rails
|
|
15
|
+
email:
|
|
16
|
+
- cocchi.c@gmail.com
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- .gitignore
|
|
22
|
+
- Gemfile
|
|
23
|
+
- LICENSE.txt
|
|
24
|
+
- README.md
|
|
25
|
+
- Rakefile
|
|
26
|
+
- lib/raisin.rb
|
|
27
|
+
- lib/raisin/api.rb
|
|
28
|
+
- lib/raisin/base.rb
|
|
29
|
+
- lib/raisin/configuration.rb
|
|
30
|
+
- lib/raisin/endpoint.rb
|
|
31
|
+
- lib/raisin/exposable.rb
|
|
32
|
+
- lib/raisin/middleware/base.rb
|
|
33
|
+
- lib/raisin/middleware/header.rb
|
|
34
|
+
- lib/raisin/mixin.rb
|
|
35
|
+
- lib/raisin/namespace.rb
|
|
36
|
+
- lib/raisin/rails/request.rb
|
|
37
|
+
- lib/raisin/rails/routes.rb
|
|
38
|
+
- lib/raisin/railtie.rb
|
|
39
|
+
- lib/raisin/router.rb
|
|
40
|
+
- lib/raisin/testing/rspec.rb
|
|
41
|
+
- lib/raisin/testing/rspec/api_helper.rb
|
|
42
|
+
- lib/raisin/testing/rspec/dsl.rb
|
|
43
|
+
- lib/raisin/testing/rspec/functional_test.rb
|
|
44
|
+
- lib/raisin/testing/rspec/test_request.rb
|
|
45
|
+
- lib/raisin/testing/rspec/unit_helper.rb
|
|
46
|
+
- lib/raisin/testing/rspec/unit_test.rb
|
|
47
|
+
- lib/raisin/version.rb
|
|
48
|
+
- raisin.gemspec
|
|
49
|
+
homepage: https://github.com/ccocchi/raisin
|
|
50
|
+
licenses: []
|
|
51
|
+
post_install_message:
|
|
52
|
+
rdoc_options: []
|
|
53
|
+
require_paths:
|
|
54
|
+
- lib
|
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
|
+
none: false
|
|
57
|
+
requirements:
|
|
58
|
+
- - ! '>='
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
62
|
+
none: false
|
|
63
|
+
requirements:
|
|
64
|
+
- - ! '>='
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubyforge_project:
|
|
69
|
+
rubygems_version: 1.8.21
|
|
70
|
+
signing_key:
|
|
71
|
+
specification_version: 3
|
|
72
|
+
summary: An opiniated micro-framework to easily build elegant API on top of Rails
|
|
73
|
+
test_files: []
|