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 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
- 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
- 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
- middleware_stack.build do |env|
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.use_or_update(klass, *args)
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
- module DSL
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
- # `call` is the only method to be processed. #process is not
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
@@ -3,16 +3,19 @@ module Raisin
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- attr_reader :exposure, :lazy_expose
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
- @exposure = name
11
- @lazy_expose = block
14
+ @exposures << [name, block]
12
15
  end
13
16
 
14
17
  def expose?
15
- !!@exposure
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
@@ -5,6 +5,8 @@ module Raisin
5
5
  attr_reader :path, :methods, :filters
6
6
 
7
7
  def initialize(path)
8
+ super
9
+
8
10
  @path = path
9
11
  @methods = []
10
12
  @filters = {
@@ -1,7 +1,18 @@
1
1
  module Raisin
2
2
  module ApiFormat
3
3
  def formats
4
- @env["action_dispatch.request.formats"] ||= @env['raisin.request.formats'] || super
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
@@ -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.routes.each do |method, path, endpoint|
9
- send(method, path, to: endpoint) # get '/users', to: 'users#index'
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
@@ -6,8 +6,5 @@ module Raisin
6
6
 
7
7
  # Force routes to be loaded if we are doing any eager load.
8
8
  config.before_eager_load { |app| app.reload_routes! }
9
-
10
- initializer "raisin.initialize" do |app|
11
- end
12
9
  end
13
10
  end
data/lib/raisin/router.rb CHANGED
@@ -1,36 +1,6 @@
1
1
  module Raisin
2
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
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 :routes, :current_version
18
+ attr_internal_accessor :versions, :current_version
44
19
 
45
20
  def mount(api)
46
- mount_version_middleware(api) if version?(:header)
47
- self.routes.concat(version?(:path) ? pathed_routes(api.routes) : api.routes)
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
- self.current_version = Version.new(version, options)
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 && self.current_version.type == type
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
@@ -1,3 +1,3 @@
1
1
  module Raisin
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -0,0 +1,12 @@
1
+ module Raisin
2
+ class VersionConstraint
3
+ def initialize(version)
4
+ @version = version
5
+ @bypass = version == Router::ALL_VERSIONS
6
+ end
7
+
8
+ def matches?(req)
9
+ @bypass || @version == req.env['raisin.version']
10
+ end
11
+ end
12
+ end
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.2
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-04 00:00:00.000000000 Z
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/base.rb
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,13 +0,0 @@
1
- module Raisin
2
- module Middleware
3
- class Base
4
- def initialize(app, *args)
5
- @app = app
6
- end
7
-
8
- def call(env)
9
- @app.call(env)
10
- end
11
- end
12
- end
13
- end
@@ -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