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