Skip to content

Commit 37cce04

Browse files
committed
Makes Remountable API a valid drop-in replacement to Grape::API
1 parent 641959c commit 37cce04

File tree

2 files changed

+45
-25
lines changed

2 files changed

+45
-25
lines changed

lib/grape/dsl/routing.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ def do_not_route_options!
8080
def mount(mounts, with: {})
8181
mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
8282
mounts.each_pair do |app, path|
83-
if app.is_a?(Class) && app.ancestors.include?(Grape::RemountableAPI)
84-
return mount(app.new_instance(configuration: with) => path)
83+
if app.respond_to?(:mount_instance)
84+
return mount(app.mount_instance(configuration: with) => path)
8585
end
8686
in_setting = inheritable_setting
8787

lib/grape/remountable_api.rb

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,53 @@
1+
require 'grape/router'
2+
13
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
35
# should subclass this class in order to build an API.
46
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+
510
class << self
11+
attr_accessor :base_instance
612
# When inherited, will create a list of all instances (times the API was mounted)
713
# 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
1119

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
1437

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)
1842
end
1943
end
2044

2145
# The remountable class can have a configuration hash to provide some dynamic class-level variables.
2246
# For instance, a descripcion could be done using: `desc configuration[:description]` if it may vary
2347
# depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
2448
# 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)
2751
instance.instance_variable_set(:@configuration, configuration)
2852
instance.define_singleton_method(:configuration) { @configuration }
2953
replay_setup_on(instance)
@@ -39,25 +63,21 @@ def replay_setup_on(instance)
3963
end
4064
end
4165

66+
def respond_to?(method, include_private = false)
67+
super(method, include_private) || base_instance.respond_to?(method, include_private)
68+
end
69+
4270
private
4371

4472
# Adds a new stage to the set up require to get a Grape::API up and running
4573
def add_setup(method, *args, &block)
4674
setup_stage = { method: method, args: args, block: block }
4775
@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])
5679
end
57-
end
58-
59-
def respond_to_missing?(name, include_private = false)
60-
@base_instance.respond_to?(name, include_private)
80+
last_response
6181
end
6282
end
6383
end

0 commit comments

Comments
 (0)