gin 1.0.4 → 1.1.0

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.
@@ -1,6 +1,22 @@
1
1
  module GinClass #:nodoc:
2
2
  private
3
3
 
4
+ def opt_reader name, *names
5
+ names.unshift name
6
+ names.each do |n|
7
+ define_method(n){ @options[n] }
8
+ end
9
+ end
10
+
11
+
12
+ def class_rproxy name, *names
13
+ names.unshift name
14
+ names.each do |n|
15
+ define_method(n){ self.class.send(n) }
16
+ end
17
+ end
18
+
19
+
4
20
  def class_proxy name, *names
5
21
  names.unshift name
6
22
  names.each do |n|
data/lib/gin/errorable.rb CHANGED
@@ -7,6 +7,19 @@ module Gin::Errorable
7
7
 
8
8
 
9
9
  module ClassMethods
10
+ def self.extended obj # :nodoc:
11
+ obj.__setup_errorable
12
+ end
13
+
14
+ def inherited subclass # :nodoc:
15
+ subclass.__setup_errorable
16
+ super
17
+ end
18
+
19
+ def __setup_errorable # :nodoc:
20
+ @err_handlers = {}
21
+ end
22
+
10
23
 
11
24
  ##
12
25
  # Define an error handler for this Controller. Configurable with exceptions
@@ -51,7 +64,7 @@ module Gin::Errorable
51
64
  # Hash of error handlers defined by Gin::Controller.error.
52
65
 
53
66
  def error_handlers
54
- @err_handlers ||= {}
67
+ @err_handlers
55
68
  end
56
69
 
57
70
 
@@ -82,7 +95,8 @@ module Gin::Errorable
82
95
  end
83
96
 
84
97
 
85
- class_proxy :error_handlers, :error_handler_for
98
+ class_rproxy :error_handlers
99
+ class_proxy :error_handler_for
86
100
 
87
101
  ##
88
102
  # Calls the appropriate error handlers for the given error.
@@ -11,6 +11,32 @@ module Gin::Filterable
11
11
 
12
12
  module ClassMethods
13
13
 
14
+ def self.extended obj # :nodoc:
15
+ obj.__setup_filterable
16
+ end
17
+
18
+ def inherited subclass # :nodoc:
19
+ subclass.__setup_filterable
20
+ super
21
+ end
22
+
23
+
24
+ def __setup_filterable # :nodoc:
25
+ @before_filters = {nil => []}
26
+ superclass.before_filters.each{|k,v| @before_filters[k] = v.dup if v } if
27
+ superclass.respond_to?(:before_filters)
28
+
29
+ @after_filters = {nil => []}
30
+ superclass.after_filters.each{|k,v| @after_filters[k] = v.dup if v } if
31
+ superclass.respond_to?(:after_filters)
32
+
33
+ @filters = {}
34
+ pfilters = self.superclass.filters if
35
+ self.superclass.respond_to?(:filters)
36
+ @filters = pfilters.dup if pfilters
37
+ end
38
+
39
+
14
40
  ##
15
41
  # Create a filter for controller actions.
16
42
  # filter :logged_in do
@@ -30,8 +56,7 @@ module Gin::Filterable
30
56
  # This attribute is inherited.
31
57
 
32
58
  def filters
33
- @filters ||= self.superclass.respond_to?(:filters) ?
34
- self.superclass.filters.dup : {}
59
+ @filters
35
60
  end
36
61
 
37
62
 
@@ -102,13 +127,6 @@ module Gin::Filterable
102
127
  # This attribute is inherited.
103
128
 
104
129
  def before_filters
105
- return @before_filters if @before_filters
106
- @before_filters ||= {nil => []}
107
-
108
- if superclass.respond_to?(:before_filters)
109
- superclass.before_filters.each{|k,v| @before_filters[k] = v.dup }
110
- end
111
-
112
130
  @before_filters
113
131
  end
114
132
 
@@ -145,13 +163,6 @@ module Gin::Filterable
145
163
  # List of after filters.
146
164
 
147
165
  def after_filters
148
- return @after_filters if @after_filters
149
- @after_filters ||= {nil => []}
150
-
151
- if superclass.respond_to?(:after_filters)
152
- superclass.after_filters.each{|k,v| @after_filters[k] = v.dup }
153
- end
154
-
155
166
  @after_filters
156
167
  end
157
168
 
@@ -183,9 +194,8 @@ module Gin::Filterable
183
194
  end
184
195
  end
185
196
 
186
-
187
- class_proxy :filters, :before_filters, :after_filters,
188
- :before_filters_for, :after_filters_for
197
+ class_rproxy :filters, :before_filters, :after_filters
198
+ class_proxy :before_filters_for, :after_filters_for
189
199
 
190
200
  ##
191
201
  # Chain-call filters from an action. Raises the filter exception if any
@@ -37,7 +37,7 @@ module Gin::Reloadable #:nodoc:
37
37
 
38
38
 
39
39
  def gin_constants
40
- return @gin_constants if @gin_constants
40
+ return @gin_constants if defined?(@gin_constants) && @gin_constants
41
41
  @gin_constants = {Gin.object_id => ::Gin}
42
42
 
43
43
  each_constant(Gin) do |name, const, _|
data/lib/gin/request.rb CHANGED
@@ -32,6 +32,17 @@ class Gin::Request < Rack::Request
32
32
  end
33
33
 
34
34
 
35
+ def ip
36
+ if addr = @env['HTTP_X_FORWARDED_FOR']
37
+ (addr.split(',').grep(/\d\./).first || @env['REMOTE_ADDR']).to_s.strip
38
+ else
39
+ @env['REMOTE_ADDR']
40
+ end
41
+ end
42
+
43
+ alias remote_ip ip
44
+
45
+
35
46
  private
36
47
 
37
48
  M_BOOLEAN = /^true|false$/ #:nodoc:
data/lib/gin/router.rb CHANGED
@@ -1,7 +1,32 @@
1
+ ##
2
+ # The Gin::Router class is the core of how Gin maps HTTP requests to
3
+ # a Controller and action (target), and vice-versa.
4
+ #
5
+ # router = Gin::Router.new
6
+ # router.add FooController do
7
+ # get :index, "/"
8
+ # get :show, "/:id"
9
+ # end
10
+ #
11
+ # router.path_to FooController, :show, id: 123
12
+ # #=> "/foo/123"
13
+ #
14
+ # router.path_to :show_foo, id: 123
15
+ # #=> "/foo/123"
16
+ #
17
+ # router.resources_for "get", "/foo/123"
18
+ # #=> [FooController, :show, {:id => "123"}]
19
+
1
20
  class Gin::Router
2
21
 
22
+ # Raised when a Route fails to build a path due to missing params
23
+ # or an invalid route target.
3
24
  class PathArgumentError < Gin::Error; end
4
25
 
26
+ ##
27
+ # Class for building temporary groups of routes for a given controller.
28
+ # Used by the Gin::App.mount DSL.
29
+
5
30
  class Mount
6
31
  DEFAULT_ACTION_MAP = {
7
32
  :index => %w{get /},
@@ -20,55 +45,43 @@ class Gin::Router
20
45
  end
21
46
 
22
47
 
23
- def initialize ctrl, base_path, sep="/", &block
24
- @sep = sep
48
+ def initialize ctrl, base_path, &block
25
49
  @ctrl = ctrl
26
50
  @routes = []
27
51
  @actions = []
28
- @base_path = base_path.split(@sep)
52
+ @base_path = base_path
29
53
 
30
54
  instance_eval(&block) if block_given?
31
55
  defaults unless block_given?
32
56
  end
33
57
 
34
58
 
35
- # Create restful routes if they aren't taken already.
59
+ ##
60
+ # Create and add default restful routes if they aren't taken already.
61
+
36
62
  def defaults default_verb=nil
37
- default_verb = (default_verb || 'get').to_s.downcase
63
+ default_verb = default_verb || 'get'
38
64
 
39
65
  (@ctrl.actions - @actions).each do |action|
40
66
  verb, path = DEFAULT_ACTION_MAP[action]
41
67
  verb, path = [default_verb, "/#{action}"] if verb.nil?
42
68
 
43
69
  add(verb, action, path) unless verb.nil? ||
44
- @routes.any?{|(r,n,(c,a,p))| r == make_route(verb, path)[0] }
70
+ @routes.any?{|route| route === [verb, path] }
45
71
  end
46
72
  end
47
73
 
48
74
 
75
+ ##
76
+ # Create routes for all standard verbs and add them to the Mount instance.
77
+
49
78
  def any action, path=nil
50
79
  VERBS.each{|verb| send verb, action, path}
51
80
  end
52
81
 
53
82
 
54
- def make_route verb, path
55
- param_keys = []
56
- route = [verb].concat @base_path
57
- route.concat path.split(@sep)
58
- route.delete_if{|part| part.empty?}
59
-
60
- route.map! do |part|
61
- if part[0] == ":"
62
- param_keys << part[1..-1]
63
- "%s"
64
- else
65
- part
66
- end
67
- end
68
-
69
- [route, param_keys]
70
- end
71
-
83
+ ##
84
+ # Create a single route and add it to the Mount instance.
72
85
 
73
86
  def add verb, action, *args
74
87
  path = args.shift if String === args[0]
@@ -77,19 +90,122 @@ class Gin::Router
77
90
  path ||= action.to_s
78
91
  name ||= :"#{action}_#{@ctrl.controller_name}"
79
92
 
80
- route, param_keys = make_route(verb, path)
81
- @routes << [route, name, [@ctrl, action, param_keys]]
93
+ path = File.join(@base_path, path)
94
+ target = [@ctrl, action]
95
+
96
+ route = Route.new(verb, path, target, name)
97
+ @routes << route
82
98
  @actions << action.to_sym
83
99
  end
84
100
 
85
101
 
102
+ ##
103
+ # Iterate through through all the routes in the Mount.
104
+
86
105
  def each_route &block
87
- @routes.each{|(route, name, value)| block.call(route, name, value) }
106
+ @routes.each(&block)
88
107
  end
89
108
  end
90
109
 
91
110
 
92
- class Node
111
+ ##
112
+ # Represents an HTTP path and path matcher, with inline params, and new path
113
+ # generation functionality.
114
+ #
115
+ # r = Route.new "get", "/foo/:id.:format", [FooController, :show], :show_foo
116
+ # r.to_path id: 123, format: "json"
117
+ # #=> "/foo/123.json"
118
+
119
+ class Route
120
+ # Parsed out path param key names.
121
+ attr_reader :param_keys
122
+
123
+ # Array of path parts for tree-based matching.
124
+ attr_reader :match_keys
125
+
126
+ # Computed path String with wildcards.
127
+ attr_reader :path
128
+
129
+ # Target of the route, in this case an Array with controller and action.
130
+ attr_reader :target
131
+
132
+ # Arbitrary name of the route.
133
+ attr_reader :name
134
+
135
+ SEP = "/" # :nodoc:
136
+ VAR_MATCHER = /:(\w+)/ # :nodoc:
137
+ PARAM_MATCHER = "(.*?)" # :nodoc:
138
+
139
+
140
+ def initialize verb, path, target, name
141
+ @target = target
142
+ @name = name
143
+ build verb, path
144
+ end
145
+
146
+
147
+ ##
148
+ # Render a route path by giving it inline (and other) params.
149
+ # route.to_path id: 123, format: "json", foo: "bar"
150
+ # #=> "/foo/123.json?foo=bar"
151
+
152
+ def to_path params={}
153
+ rendered_path = @path.dup
154
+ rendered_path = rendered_path % @param_keys.map do |k|
155
+ params.delete(k) || params.delete(k.to_sym) ||
156
+ raise(PathArgumentError, "Missing param #{k}")
157
+ end unless @param_keys.empty?
158
+
159
+ rendered_path << "?#{Gin.build_query(params)}" unless params.empty?
160
+ rendered_path
161
+ end
162
+
163
+
164
+ ##
165
+ # Returns true if the argument matches the route_id.
166
+ # The route id is an array with verb and original path.
167
+
168
+ def === other
169
+ @route_id == other
170
+ end
171
+
172
+
173
+ private
174
+
175
+ def build verb, path
176
+ verb = verb.to_s.downcase
177
+
178
+ @path = ""
179
+ @param_keys = []
180
+ @match_keys = []
181
+ @route_id = [verb, path]
182
+
183
+ parts = [verb].concat path.split(SEP)
184
+
185
+ parts.each_with_index do |p, i|
186
+ next if p.empty?
187
+
188
+ part = Regexp.escape(p).gsub!(VAR_MATCHER) do
189
+ @param_keys << $1
190
+ PARAM_MATCHER
191
+ end
192
+
193
+ if part == PARAM_MATCHER
194
+ part = "%s"
195
+ elsif $1
196
+ part = /^#{part}$/
197
+ else
198
+ part = p
199
+ end
200
+
201
+ @path << "#{SEP}#{p.gsub(VAR_MATCHER, "%s")}" if i > 0
202
+ @match_keys << part
203
+ end
204
+ end
205
+ end
206
+
207
+
208
+ class Node # :nodoc:
93
209
  attr_accessor :value
94
210
 
95
211
  def initialize
@@ -100,15 +216,22 @@ class Gin::Router
100
216
  @children[key]
101
217
  end
102
218
 
103
- def add_child key, val=nil
219
+ def match key
220
+ @children.keys.each do |k|
221
+ next unless Regexp === k
222
+ m = k.match key
223
+ return [@children[k], m[1..-1]] if m
224
+ end
225
+ nil
226
+ end
227
+
228
+ def add_child key
104
229
  @children[key] ||= Node.new
105
- @children[key].value = val unless val.nil?
106
230
  end
107
231
  end
108
232
 
109
233
 
110
- def initialize separator="/" # :nodoc:
111
- @sep = separator
234
+ def initialize
112
235
  @routes_tree = Node.new
113
236
  @routes_lookup = {}
114
237
  end
@@ -116,31 +239,30 @@ class Gin::Router
116
239
 
117
240
  ##
118
241
  # Add a Controller to the router with a base path.
242
+ # Used by Gin::App.mount.
119
243
 
120
244
  def add ctrl, base_path=nil, &block
121
245
  base_path ||= ctrl.controller_name
122
246
 
123
- mount = Mount.new(ctrl, base_path, @sep, &block)
247
+ mount = Mount.new(ctrl, base_path, &block)
124
248
 
125
- mount.each_route do |route_ary, name, val|
249
+ mount.each_route do |route|
126
250
  curr_node = @routes_tree
127
251
 
128
- route_ary.each do |part|
252
+ route.match_keys.each do |part|
129
253
  curr_node.add_child part
130
254
  curr_node = curr_node[part]
131
255
  end
132
256
 
133
- curr_node.value = val
134
- route = [route_ary[0], "/" << route_ary[1..-1].join(@sep), val[2]]
135
-
136
- @routes_lookup[name] = route if name
137
- @routes_lookup[val[0..1]] = route
257
+ curr_node.value = route
258
+ @routes_lookup[route.name] = route if route.name
259
+ @routes_lookup[route.target] = route
138
260
  end
139
261
  end
140
262
 
141
263
 
142
264
  ##
143
- # Check if a Controller and action combo has a route.
265
+ # Check if a Controller and action pair has a route.
144
266
 
145
267
  def has_route? ctrl, action
146
268
  !!@routes_lookup[[ctrl, action]]
@@ -148,7 +270,7 @@ class Gin::Router
148
270
 
149
271
 
150
272
  ##
151
- # Yield every Controller, action, route combination.
273
+ # Yield every route, controller, action combinations the router knows about.
152
274
 
153
275
  def each_route &block
154
276
  @routes_lookup.each do |key,route|
@@ -163,34 +285,33 @@ class Gin::Router
163
285
  # provided with the needed params. Routes with missing path params will raise
164
286
  # MissingParamError. Returns a String starting with "/".
165
287
  #
166
- # path_to FooController, :show, :id => 123
288
+ # path_to FooController, :show, id: 123
167
289
  # #=> "/foo/123"
168
290
  #
169
- # path_to :show_foo, :id => 123
291
+ # path_to :show_foo, id: 123
170
292
  # #=> "/foo/123"
293
+ #
294
+ # path_to :show_foo, id: 123, more: true
295
+ # #=> "/foo/123?more=true"
171
296
 
172
297
  def path_to *args
173
298
  key = Class === args[0] ? args.slice!(0..1) : args.shift
174
- verb, route, param_keys = @routes_lookup[key]
299
+ route = @routes_lookup[key]
175
300
  raise PathArgumentError, "No route for #{Array(key).join("#")}" unless route
176
301
 
177
302
  params = (args.pop || {}).dup
178
303
 
179
- route = route.dup
180
- route = route % param_keys.map do |k|
181
- params.delete(k) || params.delete(k.to_sym) ||
182
- raise(PathArgumentError, "Missing param #{k}")
183
- end unless param_keys.empty?
184
-
185
- route << "?#{Gin.build_query(params)}" unless params.empty?
186
-
187
- route
304
+ route.to_path(params)
188
305
  end
189
306
 
190
307
 
191
308
  ##
192
- # Takes a path and returns an array of 3 items:
193
- # [controller_class, action_symbol, path_params_hash]
309
+ # Takes a path and returns an array with the
310
+ # controller class, action symbol, processed path params.
311
+ #
312
+ # router.resources_for "get", "/foo/123"
313
+ # #=> [FooController, :show, {:id => "123"}]
314
+ #
194
315
  # Returns nil if no match was found.
195
316
 
196
317
  def resources_for http_verb, path
@@ -208,18 +329,21 @@ class Gin::Router
208
329
  param_vals << key
209
330
  curr_node = curr_node["%s"]
210
331
 
332
+ elsif child_and_matches = curr_node.match(key)
333
+ param_vals.concat child_and_matches[1]
334
+ curr_node = child_and_matches[0]
335
+
211
336
  else
212
337
  return
213
338
  end
214
339
  end
215
340
 
216
341
  return unless curr_node.value
217
- rsc = curr_node.value.dup
342
+ route = curr_node.value
218
343
 
219
- rsc[-1] = param_vals.empty? ?
220
- Hash.new :
221
- rsc[-1].inject({}){|h, name| h[name] = param_vals.shift; h}
344
+ path_params = param_vals.empty? ?
345
+ {} : route.param_keys.inject({}){|h, name| h[name] = param_vals.shift; h}
222
346
 
223
- rsc
347
+ [*route.target, path_params]
224
348
  end
225
349
  end