gin 1.0.4 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +16 -0
- data/Manifest.txt +12 -2
- data/README.rdoc +33 -0
- data/Rakefile +5 -0
- data/TODO.rdoc +7 -0
- data/bin/gin +158 -0
- data/lib/gin.rb +19 -2
- data/lib/gin/app.rb +420 -173
- data/lib/gin/cache.rb +65 -0
- data/lib/gin/config.rb +174 -18
- data/lib/gin/constants.rb +4 -0
- data/lib/gin/controller.rb +219 -11
- data/lib/gin/core_ext/gin_class.rb +16 -0
- data/lib/gin/errorable.rb +16 -2
- data/lib/gin/filterable.rb +29 -19
- data/lib/gin/reloadable.rb +1 -1
- data/lib/gin/request.rb +11 -0
- data/lib/gin/router.rb +185 -61
- data/lib/gin/rw_lock.rb +109 -0
- data/lib/gin/test.rb +702 -0
- data/public/gin.css +15 -3
- data/test/app/layouts/bar.erb +9 -0
- data/test/app/layouts/foo.erb +9 -0
- data/test/app/views/bar.erb +1 -0
- data/test/mock_app.rb +94 -0
- data/test/mock_config/invalid.yml +2 -0
- data/test/test_app.rb +160 -45
- data/test/test_cache.rb +57 -0
- data/test/test_config.rb +108 -13
- data/test/test_controller.rb +201 -11
- data/test/test_errorable.rb +1 -1
- data/test/test_gin.rb +9 -0
- data/test/test_helper.rb +3 -1
- data/test/test_router.rb +33 -0
- data/test/test_rw_lock.rb +65 -0
- data/test/test_test.rb +627 -0
- metadata +86 -6
- data/.autotest +0 -23
- data/.gitignore +0 -7
@@ -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
|
-
|
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.
|
data/lib/gin/filterable.rb
CHANGED
@@ -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
|
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
|
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
|
data/lib/gin/reloadable.rb
CHANGED
@@ -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,
|
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
|
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
|
-
|
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 =
|
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?{|
|
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
|
-
|
55
|
-
|
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
|
-
|
81
|
-
|
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
|
106
|
+
@routes.each(&block)
|
88
107
|
end
|
89
108
|
end
|
90
109
|
|
91
110
|
|
92
|
-
|
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
|
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
|
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,
|
247
|
+
mount = Mount.new(ctrl, base_path, &block)
|
124
248
|
|
125
|
-
mount.each_route do |
|
249
|
+
mount.each_route do |route|
|
126
250
|
curr_node = @routes_tree
|
127
251
|
|
128
|
-
|
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 =
|
134
|
-
route
|
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
|
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
|
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, :
|
288
|
+
# path_to FooController, :show, id: 123
|
167
289
|
# #=> "/foo/123"
|
168
290
|
#
|
169
|
-
# path_to :show_foo, :
|
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
|
-
|
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
|
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
|
193
|
-
#
|
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
|
-
|
342
|
+
route = curr_node.value
|
218
343
|
|
219
|
-
|
220
|
-
|
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
|
-
|
347
|
+
[*route.target, path_params]
|
224
348
|
end
|
225
349
|
end
|