1
+ require 'grape/router'
2
+
1
3
module Grape
2
- # The RemountableAPI class can replace most API classes, except for the base one that is to be mounted in rack.
4
+ # The RemountableAPI class can replace all API classes
3
5
# should subclass this class in order to build an API.
4
6
class RemountableAPI
7
+ # Class methods that we want to call on the RemountableAPI rather than on the API object
8
+ NON_OVERRIDABLE = %I[ define_singleton_method instance_variable_set inspect class is_a? ! kind_of? respond_to? ] . freeze
9
+
5
10
class << self
11
+ attr_accessor :base_instance
6
12
# When inherited, will create a list of all instances (times the API was mounted)
7
13
# It will listen to the setup required to mount that endpoint, and replicate it on any new instance
8
- def inherited ( remountable_class )
9
- remountable_class . instance_variable_set ( :@instances , [ ] )
10
- remountable_class . instance_variable_set ( :@setup , [ ] )
14
+ def inherited ( remountable_class , base_instance_parent = Grape ::API )
15
+ remountable_class . initial_setup ( base_instance_parent )
16
+ remountable_class . override_all_methods
17
+ remountable_class . make_inheritable
18
+ end
11
19
12
- base_instance = Class . new ( Grape ::API )
13
- base_instance . define_singleton_method ( :configuration ) { { } }
20
+ # Initialize the instance variables on the remountable class, and the base_instance
21
+ # an instance that will be used to create the set up but will not be mounted
22
+ def initial_setup ( base_instance_parent )
23
+ @instances = [ ]
24
+ @setup = [ ]
25
+ @base_parent = base_instance_parent
26
+ @base_instance = mount_instance
27
+ end
28
+
29
+ # Redefines all methods so that are forwarded to add_setup and recorded
30
+ def override_all_methods
31
+ ( base_instance . methods - NON_OVERRIDABLE ) . each do |method_override |
32
+ define_singleton_method ( method_override ) do |*args , &block |
33
+ add_setup ( method_override , *args , &block )
34
+ end
35
+ end
36
+ end
14
37
15
- remountable_class . instance_variable_set ( :@base_instance , base_instance )
16
- base_instance . constants . each do |constant_name |
17
- remountable_class . const_set ( constant_name , base_instance . const_get ( constant_name ) )
38
+ # When classes inheriting from this RemountableAPI child, we also want the instances to inherit from our instance
39
+ def make_inheritable
40
+ define_singleton_method ( :inherited ) do |sub_remountable |
41
+ Grape ::RemountableAPI . inherited ( sub_remountable , base_instance )
18
42
end
19
43
end
20
44
21
45
# The remountable class can have a configuration hash to provide some dynamic class-level variables.
22
46
# For instance, a descripcion could be done using: `desc configuration[:description]` if it may vary
23
47
# depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
24
48
# too much, you may actually want to provide a new API rather than remount it.
25
- def new_instance ( configuration : { } )
26
- instance = Class . new ( Grape :: API )
49
+ def mount_instance ( configuration : { } )
50
+ instance = Class . new ( @base_parent )
27
51
instance . instance_variable_set ( :@configuration , configuration )
28
52
instance . define_singleton_method ( :configuration ) { @configuration }
29
53
replay_setup_on ( instance )
@@ -39,25 +63,21 @@ def replay_setup_on(instance)
39
63
end
40
64
end
41
65
66
+ def respond_to? ( method , include_private = false )
67
+ super ( method , include_private ) || base_instance . respond_to? ( method , include_private )
68
+ end
69
+
42
70
private
43
71
44
72
# Adds a new stage to the set up require to get a Grape::API up and running
45
73
def add_setup ( method , *args , &block )
46
74
setup_stage = { method : method , args : args , block : block }
47
75
@setup << setup_stage
48
- @base_instance . send ( setup_stage [ :method ] , *setup_stage [ :args ] , &setup_stage [ :block ] )
49
- end
50
-
51
- def method_missing ( method , *args , &block )
52
- if respond_to_missing? ( method , true )
53
- add_setup ( method , *args , &block )
54
- else
55
- super
76
+ last_response = nil
77
+ @instances . each do |instance |
78
+ last_response = instance . send ( setup_stage [ :method ] , *setup_stage [ :args ] , &setup_stage [ :block ] )
56
79
end
57
- end
58
-
59
- def respond_to_missing? ( name , include_private = false )
60
- @base_instance . respond_to? ( name , include_private )
80
+ last_response
61
81
end
62
82
end
63
83
end
0 commit comments