grape-reload 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +43 -7
- data/lib/grape/reload/dependency_map.rb +1 -1
- data/lib/grape/reload/grape_api.rb +57 -36
- data/lib/grape/reload/rack.rb +7 -0
- data/lib/grape/reload/rack_builder.rb +18 -3
- data/lib/grape/reload/version.rb +1 -1
- data/lib/grape/reload/watcher.rb +3 -4
- data/lib/grape/reload.rb +1 -0
- data/lib/ripper/extract_constants.rb +80 -7
- data/spec/grape/reload/autoreload_interceptor_spec.rb +12 -4
- data/spec/grape/reload/dependency_map_spec.rb +0 -5
- data/spec/grape/reload/rack_builder_spec.rb +23 -1
- data/spec/grape/reload/watcher_spec.rb +33 -2
- data/spec/ripper/extract_constants_spec.rb +99 -5
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf764d2710409ccf448fb1a9b1878611ae510b28
|
4
|
+
data.tar.gz: 74bb786cd48bd89ac78e95e955680bb695e30846
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b918ce662a102851066936b2bb1c3a534af0dfbee84a900e0692f80095ef67905bdf47c8c7773c26619a6b64c8e826403bad8bfe184734cde1c8c035fc96b36
|
7
|
+
data.tar.gz: 21a8509583dc86cb23b3747536c32090fb88deeb415bb44ff277797193a95f6368d181ae51865e43d9f96fb4a72d4d92766cb6ca82e83ef2caf93844983d4291
|
data/README.md
CHANGED
@@ -23,21 +23,22 @@ Or install it yourself as:
|
|
23
23
|
|
24
24
|
In your config.ru you use Grape::RackBuilder to mount your apps:
|
25
25
|
|
26
|
-
|
26
|
+
Grape::RackBuilder.setup do
|
27
27
|
logger Logger.new(STDOUT)
|
28
28
|
add_source_path File.expand_path('**/*.rb', YOUR_APP_ROOT)
|
29
29
|
reload_threshold 1 # Reload sources not often one second
|
30
|
+
force_reloading true # Force reloading for any environment (not just dev), useful for testing
|
30
31
|
mount 'Your::App', to: '/'
|
31
32
|
mount 'Your::App1', to: '/app1'
|
32
33
|
end
|
33
34
|
|
34
|
-
run
|
35
|
+
run Grape::RackBuilder.boot!.application
|
35
36
|
|
36
|
-
Grape::Reload will resolve all class dependencies and load your files in appropriate order, so you don't need to include 'require' or 'require_relative'
|
37
|
+
Grape::Reload will resolve all class dependencies and load your files in appropriate order, so you don't need to include 'require' or 'require_relative' in your sources.
|
37
38
|
|
38
39
|
## Restrictions:
|
39
|
-
|
40
|
-
If you want to monkey-patch class in
|
40
|
+
### Monkey patching
|
41
|
+
If you want to monkey-patch class in code, you want to be reloaded, for any reason, you should use
|
41
42
|
|
42
43
|
AlreadyDefined.class_eval do
|
43
44
|
end
|
@@ -45,18 +46,53 @@ If you want to monkey-patch class in your code for any reason, you should use
|
|
45
46
|
instead of
|
46
47
|
|
47
48
|
class AlreadyDefined
|
48
|
-
end
|
49
|
+
end
|
49
50
|
|
50
51
|
because it confuses dependency resolver
|
51
52
|
|
53
|
+
### Full-qualified const name usage
|
54
|
+
Consider code
|
55
|
+
|
56
|
+
require 'some_file' # (declares SomeModule::SomeClass)
|
57
|
+
|
58
|
+
here_is_your_code(SomeClass)
|
59
|
+
|
60
|
+
Ruby will resolve SomeClass to SomeModule::SomeClass in runtime.
|
61
|
+
Dependency resolver will display an error, because it expects you to
|
62
|
+
use full-qualified class name in this situation.
|
63
|
+
Anyway, it would not raise exception anymore (since e5b58f4)
|
64
|
+
|
65
|
+
here_is_your_code(SomeModule::SomeClass)
|
66
|
+
|
67
|
+
### Other restrictions
|
68
|
+
|
69
|
+
Avoid declaring constants as follows
|
70
|
+
|
71
|
+
class AlreadyDeclaredModule::MyClass
|
72
|
+
end
|
73
|
+
|
74
|
+
use
|
75
|
+
|
76
|
+
module AlreadyDeclaredModule
|
77
|
+
class MyClass
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
instead
|
82
|
+
|
52
83
|
## Known issues
|
53
84
|
|
54
85
|
* It still lacks of good design :(
|
55
86
|
* MOAR TESTS!!!!111
|
56
87
|
|
88
|
+
## TODO
|
89
|
+
|
90
|
+
* example Grape application with Grape::Reload
|
91
|
+
* Spork integration example
|
92
|
+
|
57
93
|
## Contributing
|
58
94
|
|
59
|
-
1. Fork it ( https://github.com/
|
95
|
+
1. Fork it ( https://github.com/AlexYankee/grape-reload/fork )
|
60
96
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
61
97
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
62
98
|
4. Push to the branch (`git push origin my-new-feature`)
|
@@ -117,7 +117,7 @@ module Grape
|
|
117
117
|
filenames.each {|filename| Grape::RackBuilder.logger.error("Unresolved const reference #{klass} from: #{filename}".colorize(:red)) }
|
118
118
|
end
|
119
119
|
|
120
|
-
|
120
|
+
Grape::RackBuilder.logger.error("One or more unresolved dependencies found".colorize(:red)) if unresolved_classes.any?
|
121
121
|
end
|
122
122
|
end
|
123
123
|
|
@@ -36,47 +36,68 @@ end
|
|
36
36
|
module Grape
|
37
37
|
module Reload
|
38
38
|
module AutoreloadInterceptor
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
extend ActiveSupport::Concern
|
40
|
+
|
41
|
+
def add_head_not_allowed_methods_and_options_methods(*args, &block)
|
42
|
+
self.class.skip_declaration = true
|
43
|
+
super(*args, &block)
|
44
|
+
self.class.skip_declaration = false
|
45
|
+
end
|
46
|
+
|
47
|
+
module ClassMethods
|
48
|
+
attr_accessor :skip_declaration
|
49
|
+
|
50
|
+
def namespace(*args, &block)
|
51
|
+
@skip_declaration = true
|
52
|
+
class_declaration << [:namespace,args,block]
|
43
53
|
super(*args, &block)
|
54
|
+
@skip_declaration = false
|
44
55
|
end
|
56
|
+
|
57
|
+
[:set, :imbue, :mount, :route, :desc, :params, :helpers, :format, :formatter, :parser, :error_formatter, :content_type].each do |method|
|
58
|
+
eval <<METHOD
|
59
|
+
def #{method}(*args, &block)
|
60
|
+
class_declaration << [:#{method},args,block] unless @skip_declaration
|
61
|
+
super(*args, &block)
|
62
|
+
end
|
45
63
|
METHOD
|
46
|
-
|
64
|
+
end
|
47
65
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
send(decl[0],*deep_reconstantize.call(decl[1]),&decl[2])
|
55
|
-
}
|
56
|
-
change!
|
57
|
-
end
|
66
|
+
def reinit!
|
67
|
+
declaration = class_declaration.dup
|
68
|
+
@class_decl = []
|
69
|
+
endpoints_cache = endpoints
|
70
|
+
reset!
|
71
|
+
endpoints_cache.each { |e| e.options[:app].reinit! if e.options[:app].respond_to?('reinit!') }
|
58
72
|
|
59
|
-
|
73
|
+
declaration.each {|decl|
|
74
|
+
send(decl[0],*deep_reconstantize.call(decl[1]),&decl[2])
|
75
|
+
}
|
76
|
+
change!
|
77
|
+
end
|
60
78
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
79
|
+
def recursive_!
|
80
|
+
|
81
|
+
end
|
82
|
+
private
|
83
|
+
def class_declaration
|
84
|
+
@class_decl ||= []
|
85
|
+
end
|
86
|
+
def deep_reconstantize
|
87
|
+
proc = ->(value) {
|
88
|
+
case value
|
89
|
+
when Hash
|
90
|
+
Hash[value.each_pair.map { |k,v| [proc.call(k), proc.call(v)] }]
|
91
|
+
when Array
|
92
|
+
value.map { |v| proc.call(v) }
|
93
|
+
when Class
|
94
|
+
return value if value.to_s[0,2] == '#<'
|
95
|
+
value.to_s.constantize
|
96
|
+
else
|
97
|
+
value
|
98
|
+
end
|
99
|
+
}
|
100
|
+
end
|
80
101
|
end
|
81
102
|
end
|
82
103
|
end
|
@@ -87,7 +108,7 @@ Grape::API.singleton_class.class_eval do
|
|
87
108
|
alias_method :settings_shadowed, :settings
|
88
109
|
def inherited(*args)
|
89
110
|
inherited_shadowed(*args)
|
90
|
-
args.first.
|
111
|
+
args.first.class_eval do
|
91
112
|
include Grape::Reload::AutoreloadInterceptor
|
92
113
|
end
|
93
114
|
end
|
@@ -26,9 +26,9 @@ module Grape
|
|
26
26
|
end
|
27
27
|
|
28
28
|
class Config
|
29
|
-
attr_accessor :mounts, :sources, :options
|
29
|
+
attr_accessor :mounts, :sources, :options, :force_reloading
|
30
30
|
|
31
|
-
{environment: RACK_ENV, reload_threshold: 1, logger: LoggingStub}.each_pair do |attr, default|
|
31
|
+
{environment: RACK_ENV, reload_threshold: 1, logger: LoggingStub, force_reloading: false}.each_pair do |attr, default|
|
32
32
|
attr_accessor attr
|
33
33
|
define_method(attr) { |value = nil|
|
34
34
|
@options ||= {}
|
@@ -41,6 +41,10 @@ module Grape
|
|
41
41
|
(@sources ||= []) << glob
|
42
42
|
end
|
43
43
|
|
44
|
+
def use(*args, &block)
|
45
|
+
middleware << [args, block]
|
46
|
+
end
|
47
|
+
|
44
48
|
def mount(app_class, options)
|
45
49
|
mounts << MountConfig.new(
|
46
50
|
app_class: app_class,
|
@@ -52,6 +56,10 @@ module Grape
|
|
52
56
|
def mounts
|
53
57
|
@mounts ||= []
|
54
58
|
end
|
59
|
+
|
60
|
+
def middleware
|
61
|
+
@middleware ||= []
|
62
|
+
end
|
55
63
|
end
|
56
64
|
|
57
65
|
module ClassMethods
|
@@ -61,6 +69,7 @@ module Grape
|
|
61
69
|
end
|
62
70
|
|
63
71
|
def boot!
|
72
|
+
@rack_app = nil
|
64
73
|
Grape::Reload::Watcher.setup(sources: Grape::Reload::Sources.new(config.sources))
|
65
74
|
self
|
66
75
|
end
|
@@ -68,11 +77,17 @@ module Grape
|
|
68
77
|
def application
|
69
78
|
return @rack_app if @rack_app
|
70
79
|
mounts = config.mounts
|
80
|
+
middleware = config.middleware
|
81
|
+
force_reloading = config.force_reloading
|
71
82
|
environment = config.environment
|
72
83
|
reload_threshold = config.reload_threshold
|
73
84
|
@rack_app = ::Rack::Builder.new do
|
85
|
+
middleware.each do |parameters|
|
86
|
+
parameters.length == 1 ? use(*parameters.first) : use(*parameters.first, ¶meters.last)
|
87
|
+
end
|
88
|
+
|
74
89
|
mounts.each_with_index do |m|
|
75
|
-
if environment == 'development'
|
90
|
+
if (environment == 'development') || force_reloading
|
76
91
|
r = Rack::Builder.new
|
77
92
|
r.use Grape::ReloadMiddleware[reload_threshold]
|
78
93
|
r.run m.app_class
|
data/lib/grape/reload/version.rb
CHANGED
data/lib/grape/reload/watcher.rb
CHANGED
@@ -17,7 +17,7 @@ module Grape
|
|
17
17
|
return unless options[:force] || file_changed?(file)
|
18
18
|
|
19
19
|
Storage.prepare(file) # might call #safe_load recursively
|
20
|
-
logger.debug((file_new?(file) ? "loading" : "reloading") + "#{file}" )
|
20
|
+
logger.debug((file_new?(file) ? "loading" : "reloading") + " #{file}" )
|
21
21
|
begin
|
22
22
|
with_silence{ require(file) }
|
23
23
|
Storage.commit(file)
|
@@ -102,9 +102,8 @@ module Grape
|
|
102
102
|
changed_files_sorted.each{|f| safe_load(f)}
|
103
103
|
end
|
104
104
|
changed_files_sorted.map{|f| @sources.dependent_classes(f) }.flatten.uniq.each {|class_name|
|
105
|
-
|
106
|
-
|
107
|
-
end
|
105
|
+
next unless (klass = class_name.constantize).kind_of? Class
|
106
|
+
klass.reinit! if klass.respond_to?('reinit!')
|
108
107
|
}
|
109
108
|
end
|
110
109
|
|
data/lib/grape/reload.rb
CHANGED
@@ -53,7 +53,7 @@ class TraversingResult
|
|
53
53
|
end
|
54
54
|
|
55
55
|
variants << [ const_ary ]
|
56
|
-
@used << variants.map{|v| v.join('::')}
|
56
|
+
@used << (variants.map{|v| v.join('::')} << '::' + const)
|
57
57
|
end
|
58
58
|
else
|
59
59
|
@used << const
|
@@ -107,7 +107,7 @@ class TraversingResult
|
|
107
107
|
used = used.map {|variants| variants.map{|v| (v.start_with?('::') ? '' : (namespace || '') + '::') + v }}
|
108
108
|
end
|
109
109
|
|
110
|
-
result[:used] = result[:used].concat(used)
|
110
|
+
result[:used] = result[:used].concat(used).uniq
|
111
111
|
|
112
112
|
result
|
113
113
|
end
|
@@ -135,7 +135,7 @@ class ASTEntity
|
|
135
135
|
else
|
136
136
|
# Code position for identifier
|
137
137
|
return if node_ary.kind_of?(Array) and (node_ary.size == 2) and node_ary[0].kind_of?(Integer) and node_ary[1].kind_of?(Integer)
|
138
|
-
node_ary.map{|n|
|
138
|
+
node_ary.map{|n| node_for(n) }
|
139
139
|
end
|
140
140
|
end
|
141
141
|
end
|
@@ -157,7 +157,7 @@ class ASTEntity
|
|
157
157
|
when ASTEntity
|
158
158
|
e.collect_constants(result, context || (TraversingContext.new)) unless e.nil?
|
159
159
|
when Array
|
160
|
-
e.map{|e| e.collect_constants(result, context || (TraversingContext.new)) unless e.nil? }
|
160
|
+
e.flatten.map{|e| e.collect_constants(result, context || (TraversingContext.new)) unless e.nil? }
|
161
161
|
else
|
162
162
|
end
|
163
163
|
} unless @body.nil?
|
@@ -172,6 +172,27 @@ class ASTProgramDecl < ASTEntity
|
|
172
172
|
end
|
173
173
|
end
|
174
174
|
|
175
|
+
class ASTDef < ASTEntity
|
176
|
+
def self.ripper_id; :def end
|
177
|
+
def collect_constants(result, context)
|
178
|
+
result
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class ASTCommand < ASTEntity
|
183
|
+
def self.ripper_id; :command end
|
184
|
+
def initialize(*args)
|
185
|
+
@command = args.first[1]
|
186
|
+
super(*args)
|
187
|
+
end
|
188
|
+
def collect_constants(result, context)
|
189
|
+
@old_stop_collect_constants = context[:stop_collect_constants]
|
190
|
+
context[:stop_collect_constants] = nil unless %w{desc mount params}.index(@command).nil?
|
191
|
+
ret = super(result, context)
|
192
|
+
context[:stop_collect_constants] = @old_stop_collect_constants
|
193
|
+
ret
|
194
|
+
end
|
195
|
+
end
|
175
196
|
|
176
197
|
class ASTBody < ASTEntity
|
177
198
|
def self.ripper_id; :bodystmt end
|
@@ -207,8 +228,9 @@ class ASTTopConstRef < ASTEntity
|
|
207
228
|
def self.ripper_id; :top_const_ref end
|
208
229
|
def collect_constants(result, context)
|
209
230
|
context[:top] = true
|
210
|
-
super(result, context)
|
231
|
+
ret = super(result, context)
|
211
232
|
context[:top] = false
|
233
|
+
ret
|
212
234
|
end
|
213
235
|
end
|
214
236
|
|
@@ -239,6 +261,7 @@ class ASTConst < ASTEntity
|
|
239
261
|
@const_name = args[0]
|
240
262
|
end
|
241
263
|
def collect_constants(result, context)
|
264
|
+
return super(result, context) if context[:stop_collect_constants]
|
242
265
|
if context[:variable_assignment]
|
243
266
|
result.declare_const(@const_name)
|
244
267
|
else
|
@@ -262,7 +285,8 @@ class ASTConstPathRef < ASTEntity
|
|
262
285
|
@const = ASTEntity.node_for(args.last)
|
263
286
|
end
|
264
287
|
def collect_constants(result, context)
|
265
|
-
if context[:
|
288
|
+
return super(result, context) if context[:stop_collect_constants]
|
289
|
+
if context[:const_path_ref] || context[:method_add_arg]
|
266
290
|
r = TraversingResult.new
|
267
291
|
c = context.dup
|
268
292
|
c[:analyze_const] = false
|
@@ -280,6 +304,48 @@ class ASTConstPathRef < ASTEntity
|
|
280
304
|
end
|
281
305
|
end
|
282
306
|
|
307
|
+
class ASTMethodAddArg < ASTEntity
|
308
|
+
def self.ripper_id; :method_add_arg end
|
309
|
+
def initialize(*args)
|
310
|
+
@path = ASTEntity.node_for(args.first)
|
311
|
+
end
|
312
|
+
|
313
|
+
def collect_constants(result, context)
|
314
|
+
return super(result, context) if context[:stop_collect_constants]
|
315
|
+
if context[:method_add_arg]
|
316
|
+
r = TraversingResult.new
|
317
|
+
c = context.dup
|
318
|
+
c[:analyze_const] = false
|
319
|
+
path_consts = @path.collect_constants(r, context)
|
320
|
+
result.use_const(path_consts.used.join('::'), false)
|
321
|
+
else
|
322
|
+
r = TraversingResult.new
|
323
|
+
new_context = TraversingContext.new([], {method_add_arg: true, analyze_const: false})
|
324
|
+
path_consts = @path.collect_constants(r, new_context)
|
325
|
+
result.use_const(path_consts.used.join('::'))
|
326
|
+
end
|
327
|
+
result
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
class ASTDefs < ASTEntity
|
332
|
+
def self.ripper_id; :defs end
|
333
|
+
def collect_constants(result, context)
|
334
|
+
result
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
class ASTMethodAddBlock < ASTEntity
|
339
|
+
def self.ripper_id; :method_add_block end
|
340
|
+
|
341
|
+
def collect_constants(result, context)
|
342
|
+
context[:stop_collect_constants] = true
|
343
|
+
ret = super(result, context)
|
344
|
+
context[:stop_collect_constants] = nil
|
345
|
+
ret
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
283
349
|
class ASTModule < ASTEntity
|
284
350
|
def self.ripper_id; :module end
|
285
351
|
def initialize(*args)
|
@@ -300,8 +366,9 @@ class ASTVarField < ASTEntity
|
|
300
366
|
def self.ripper_id; :var_field end
|
301
367
|
def collect_constants(result, context)
|
302
368
|
context[:variable_assignment] = true
|
303
|
-
super(result, context)
|
369
|
+
ret = super(result, context)
|
304
370
|
context[:variable_assignment] = false
|
371
|
+
ret
|
305
372
|
end
|
306
373
|
end
|
307
374
|
|
@@ -313,6 +380,12 @@ class ASTRef < ASTEntity
|
|
313
380
|
end
|
314
381
|
end
|
315
382
|
|
383
|
+
class ASTLambda < ASTEntity
|
384
|
+
def self.ripper_id; :lambda end
|
385
|
+
def initialize(*args)
|
386
|
+
super(*(args[0..-2]))
|
387
|
+
end
|
388
|
+
end
|
316
389
|
|
317
390
|
class Ripper
|
318
391
|
def self.extract_constants(code)
|
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require_relative '../../../lib/grape/reload/grape_api'
|
3
3
|
describe Grape::Reload::AutoreloadInterceptor do
|
4
|
-
let(:api_class) {
|
4
|
+
let!(:api_class) {
|
5
5
|
nested_class = Class.new(Grape::API) do
|
6
|
-
|
7
|
-
|
6
|
+
namespace :nested do
|
7
|
+
get :route do
|
8
|
+
'nested route'
|
9
|
+
end
|
8
10
|
end
|
9
11
|
end
|
10
12
|
|
@@ -18,7 +20,7 @@ describe Grape::Reload::AutoreloadInterceptor do
|
|
18
20
|
}
|
19
21
|
|
20
22
|
describe '.reinit!' do
|
21
|
-
let(:app) {
|
23
|
+
let!(:app) {
|
22
24
|
app = Rack::Builder.new
|
23
25
|
app.run api_class
|
24
26
|
app
|
@@ -31,10 +33,16 @@ describe Grape::Reload::AutoreloadInterceptor do
|
|
31
33
|
get '/test_route'
|
32
34
|
expect(last_response).to succeed
|
33
35
|
expect(last_response.body).to eq('test')
|
36
|
+
get '/nested/nested/route'
|
37
|
+
expect(last_response).to succeed
|
38
|
+
expect(last_response.body).to eq('nested route')
|
34
39
|
api_class.reinit!
|
35
40
|
get '/test_route'
|
36
41
|
expect(last_response).to succeed
|
37
42
|
expect(last_response.body).to eq('test')
|
43
|
+
get '/nested/nested/route'
|
44
|
+
expect(last_response).to succeed
|
45
|
+
expect(last_response.body).to eq('nested route')
|
38
46
|
end
|
39
47
|
end
|
40
48
|
end
|
@@ -42,9 +42,4 @@ describe Grape::Reload::DependencyMap do
|
|
42
42
|
|
43
43
|
expect(dm.dependent_classes('file1')).to include('::Class2','::Class3')
|
44
44
|
end
|
45
|
-
|
46
|
-
it "raises error if dependencies can't be resolved" do
|
47
|
-
allow(dm).to receive(:map).and_return(wrong_class_map)
|
48
|
-
expect { dm.resolve_dependencies! }.to raise_error(Grape::Reload::UnresolvedDependenciesError)
|
49
|
-
end
|
50
45
|
end
|
@@ -11,6 +11,17 @@ describe Grape::RackBuilder do
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
}
|
14
|
+
let(:middleware) {
|
15
|
+
Class.new do
|
16
|
+
def initialize(app)
|
17
|
+
@app = app
|
18
|
+
end
|
19
|
+
def call(env)
|
20
|
+
@app.call(env)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
}
|
24
|
+
|
14
25
|
|
15
26
|
before do
|
16
27
|
builder.setup do
|
@@ -37,8 +48,16 @@ describe Grape::RackBuilder do
|
|
37
48
|
end
|
38
49
|
expect(config.mounts.size).to eq(2)
|
39
50
|
end
|
51
|
+
|
52
|
+
it 'allows to add middleware' do
|
53
|
+
builder.setup do
|
54
|
+
use middleware do
|
55
|
+
end
|
56
|
+
end
|
57
|
+
expect(config.middleware.size).to eq(1)
|
58
|
+
end
|
40
59
|
end
|
41
|
-
|
60
|
+
|
42
61
|
describe '.boot!' do
|
43
62
|
before(:each) do
|
44
63
|
builder.setup do
|
@@ -63,6 +82,7 @@ describe Grape::RackBuilder do
|
|
63
82
|
describe '.application' do
|
64
83
|
before(:each) do
|
65
84
|
builder.setup do
|
85
|
+
use middleware
|
66
86
|
mount 'Test::App1', to: '/test1'
|
67
87
|
mount 'Test::App2', to: '/test2'
|
68
88
|
end
|
@@ -72,6 +92,8 @@ describe Grape::RackBuilder do
|
|
72
92
|
expect{ @app = builder.application }.not_to raise_error
|
73
93
|
expect(@app).to be_an_instance_of(Rack::Builder)
|
74
94
|
def @app.get_map; @map end
|
95
|
+
def @app.get_use; @use end
|
96
|
+
expect(@app.get_use.size).to eq(1)
|
75
97
|
expect(@app.get_map.keys).to include('/test1','/test2')
|
76
98
|
end
|
77
99
|
end
|
@@ -2,8 +2,10 @@ require 'grape'
|
|
2
2
|
require 'spec_helper'
|
3
3
|
|
4
4
|
describe Grape::Reload::Watcher do
|
5
|
-
def app
|
6
|
-
|
5
|
+
def app
|
6
|
+
@app
|
7
|
+
end
|
8
|
+
before(:each) do
|
7
9
|
@app =
|
8
10
|
Grape::RackBuilder.setup do
|
9
11
|
add_source_path File.expand_path('**.rb', APP_ROOT)
|
@@ -31,6 +33,35 @@ describe Grape::Reload::Watcher do
|
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
36
|
+
describe 'force_reloading' do
|
37
|
+
before(:each) do
|
38
|
+
@app =
|
39
|
+
Grape::RackBuilder.setup do
|
40
|
+
add_source_path File.expand_path('**.rb', APP_ROOT)
|
41
|
+
add_source_path File.expand_path('**/*.rb', APP_ROOT)
|
42
|
+
environment 'test'
|
43
|
+
force_reloading true
|
44
|
+
reload_threshold 0
|
45
|
+
mount 'Test::App1', to: '/test1'
|
46
|
+
mount 'Test::App2', to: '/test2'
|
47
|
+
end.boot!.application
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'reloads files within any environment with force_reloading options set' do
|
51
|
+
get '/test1/test'
|
52
|
+
expect(last_response).to succeed
|
53
|
+
expect(last_response.body).to eq('test1 response')
|
54
|
+
|
55
|
+
with_changed_fixture 'app1/test1.rb' do
|
56
|
+
get '/test1/test'
|
57
|
+
expect(last_response).to succeed
|
58
|
+
expect(last_response.body).to eq('test1 response changed')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
|
34
65
|
it 'reloads mounted app file' do
|
35
66
|
get '/test1/mounted/test1'
|
36
67
|
expect(last_response).to succeed
|
@@ -60,10 +60,71 @@ CODE
|
|
60
60
|
def use_top_level
|
61
61
|
TopLevel.new
|
62
62
|
end
|
63
|
+
def self.method
|
64
|
+
SomeModule::ShouldntUse.call
|
65
|
+
end
|
66
|
+
end
|
67
|
+
CODE
|
68
|
+
}
|
69
|
+
|
70
|
+
let!(:deeply_nested) {
|
71
|
+
<<CODE
|
72
|
+
module Test
|
73
|
+
module Subtest
|
74
|
+
class App2 < Grape::API
|
75
|
+
end
|
76
|
+
end
|
63
77
|
end
|
64
78
|
CODE
|
65
79
|
}
|
66
80
|
|
81
|
+
# Sequel-related
|
82
|
+
let!(:class_reference_with_call) {
|
83
|
+
<<CODE
|
84
|
+
module Test
|
85
|
+
module Subtest
|
86
|
+
class App2 < Grape::API(:test)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
CODE
|
91
|
+
}
|
92
|
+
|
93
|
+
let!(:grape_desc_args) {
|
94
|
+
<<CODE
|
95
|
+
module Test
|
96
|
+
class App < Grape::API
|
97
|
+
group do
|
98
|
+
desc 'Blablabla',
|
99
|
+
entity: [Test::SomeAnotherEntity]
|
100
|
+
get :test do
|
101
|
+
SomeClass.usage
|
102
|
+
'test2 response'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
CODE
|
108
|
+
}
|
109
|
+
|
110
|
+
let!(:class_level_call_with_args) {
|
111
|
+
<<CODE
|
112
|
+
module Test
|
113
|
+
class TestClass
|
114
|
+
UseModule::UseClass.call(arg)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
CODE
|
118
|
+
}
|
119
|
+
|
120
|
+
let!(:lambda_class_usage) {
|
121
|
+
<<CODE
|
122
|
+
some_method ->(arg) {
|
123
|
+
ModuleName::ClassName.call(arg)
|
124
|
+
}
|
125
|
+
CODE
|
126
|
+
}
|
127
|
+
|
67
128
|
it 'extract consts from code1 correctly' do
|
68
129
|
consts = Ripper.extract_constants(code1)
|
69
130
|
expect(consts[:declared].flatten).to include(
|
@@ -81,11 +142,15 @@ CODE
|
|
81
142
|
'::Test3::AnotherClass',
|
82
143
|
'::Test::NotExists::Test1',
|
83
144
|
'::SomeExternalClass',
|
84
|
-
'::Superclass'
|
85
|
-
'::SomeClass1',
|
86
|
-
'::SomeClass2'
|
145
|
+
'::Superclass'
|
87
146
|
)
|
88
147
|
|
148
|
+
expect(consts[:used].flatten).not_to include(
|
149
|
+
'::SomeClass1',
|
150
|
+
'::SomeClass2'
|
151
|
+
)
|
152
|
+
|
153
|
+
|
89
154
|
end
|
90
155
|
it 'extract consts from code2 correctly' do
|
91
156
|
consts = Ripper.extract_constants(code2)
|
@@ -98,8 +163,37 @@ CODE
|
|
98
163
|
'::Test::Mount2',
|
99
164
|
'::Test::Mount10',
|
100
165
|
'::Test::SomeAnotherEntity',
|
101
|
-
'::SomeClass',
|
102
|
-
'::TopLevel'
|
103
166
|
)
|
167
|
+
|
168
|
+
expect(consts[:used].flatten).not_to include(
|
169
|
+
'::SomeClass',
|
170
|
+
'::TopLevel'
|
171
|
+
)
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'extracts consts used in deeply nested modules up to root namespace' do
|
176
|
+
consts = Ripper.extract_constants(deeply_nested)
|
177
|
+
expect(consts[:used].flatten).to include('::Grape::API')
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'extracts const with call (sequel-related)' do
|
181
|
+
consts = Ripper.extract_constants(class_reference_with_call)
|
182
|
+
expect(consts[:used].flatten).to include('::Grape::API')
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'extracts consts from desc method args' do
|
186
|
+
consts = Ripper.extract_constants(grape_desc_args)
|
187
|
+
expect(consts[:used].flatten).to include('::Test::SomeAnotherEntity')
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'does not mess up class name when class level method called with argument' do
|
191
|
+
consts = Ripper.extract_constants(class_level_call_with_args)
|
192
|
+
expect(consts[:used].flatten).to include('::UseModule::UseClass')
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'does not include classes used in lambdas' do
|
196
|
+
consts = Ripper.extract_constants(lambda_class_usage)
|
197
|
+
expect(consts[:used].flatten).not_to include('::ModuleName::ClassName')
|
104
198
|
end
|
105
199
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grape-reload
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- AMar4enko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-10-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: grape
|
@@ -212,6 +212,7 @@ files:
|
|
212
212
|
- lib/grape/reload.rb
|
213
213
|
- lib/grape/reload/dependency_map.rb
|
214
214
|
- lib/grape/reload/grape_api.rb
|
215
|
+
- lib/grape/reload/rack.rb
|
215
216
|
- lib/grape/reload/rack_builder.rb
|
216
217
|
- lib/grape/reload/storage.rb
|
217
218
|
- lib/grape/reload/version.rb
|