gin 1.0.4 → 1.1.0

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