grape-reload 0.0.3 → 0.0.4
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.
- 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
|