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 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