Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .rerun
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--pattern {app.rb,api/**/*.rb,app/**/*.rb,config/**/*.rb}
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gem 'rack-contrib', '~> 1.4.0'

group :development do
gem 'rake'
gem 'rerun', '~> 0.14', require: false
gem 'rubocop'
end

Expand Down
13 changes: 13 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ GEM
addressable (>= 2.1.1)
eventmachine (>= 0.12.9)
eventmachine (1.2.7)
ffi (1.17.4)
ffi (1.17.4-arm64-darwin)
git-version-bump (0.19.1)
goliath (1.0.7)
async-rack
Expand Down Expand Up @@ -89,6 +91,10 @@ GEM
json (3.1.0)
language_server-protocol (3.17.0.6)
lint_roller (1.1.0)
listen (3.10.1)
logger
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
log4r (1.1.10)
logger (1.7.0)
minitest (6.0.6)
Expand Down Expand Up @@ -118,11 +124,16 @@ GEM
rack (>= 1.3)
rainbow (3.1.1)
rake (13.4.2)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
readline (0.0.4)
reline
regexp_parser (2.13.1)
reline (0.7.0)
io-console (~> 0.5)
rerun (0.14.0)
listen (~> 3.0)
rspec (3.13.2)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
Expand Down Expand Up @@ -163,6 +174,7 @@ GEM
zeitwerk (2.8.3)

PLATFORMS
arm64-darwin-23
ruby

DEPENDENCIES
Expand All @@ -174,6 +186,7 @@ DEPENDENCIES
rack-contrib (~> 1.4.0)
rack-test
rake
rerun (~> 0.14)
rspec
rubocop
simplecov
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ $ bundle exec ruby app.rb -sv

`app.rb` enables Goliath's application runner. Loading the environment for Rake or tests loads only the API, avoiding an extra server starting when those processes exit.

### Development Reloading

To pick up code changes automatically, run:

```
bundle exec rerun -- ruby app.rb -sv -e development
```

[Rerun](https://github.com/alexch/rerun) watches `app.rb` and Ruby files under `api/`, `app/`, and `config/`, restarting the server when files are added, changed, or removed. For example, edit the response in `api/ping.rb`, save, and request `/api/ping` again after the server restarts. On shared filesystems that do not deliver file notifications, add `--force-polling` before `--`.

The whole process restarts deliberately: Grape compiles routes and retains references to mounted API classes, so reloading individual files with `Rack::Reloader` does not reliably update mounted routes. Requests can be interrupted during a restart; use this command only for development. The plain `bundle exec ruby app.rb -sv` command above does not watch or reload files.

### Hello World

Navigate to http://localhost:9000/api/ping with a browser or use `curl`.
Expand Down
65 changes: 65 additions & 0 deletions spec/development_reloading_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
require 'spec_helper'
require 'fileutils'
require 'net/http'
require 'rbconfig'
require 'socket'
require 'tmpdir'
require 'timeout'

describe 'Development reloading' do
def wait_for_response(port, path, body)
Timeout.timeout(30) do
loop do
begin
response = Net::HTTP.start('127.0.0.1', port, open_timeout: 1, read_timeout: 1) { |http| http.get(path) }
return response if response.code == '200' && response.body == body
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, EOFError, Net::ReadTimeout
# A restart briefly closes the listening socket and existing connections.
end
sleep 0.1
end
end
end

it 'replaces mounted routes and responses after an API file changes' do
Dir.mktmpdir('grape-on-goliath-reloading') do |directory|
root = File.expand_path('..', __dir__)
FileUtils.cp_r(%w[api app config app.rb .rerun].map { |path| File.join(root, path) }, directory)
socket = TCPServer.new('127.0.0.1', 0)
port = socket.addr[1]
socket.close

File.open(File.join(directory, 'server.log'), 'w+') do |log|
pid = Process.spawn(
RbConfig.ruby, '-e', '$stdout.sync = true; load Gem.bin_path("rerun", "rerun")', '--',
'--background', '--no-notify', '--force-polling', '--',
RbConfig.ruby, 'app.rb', '-sv', '-e', 'development', '-p', port.to_s,
chdir: directory, pgroup: true, out: log, err: %i[child out]
)
begin
expect(wait_for_response(port, '/api/ping', '{"ping":"pong"}').code).to eq('200')
Timeout.timeout(30) do
sleep 0.1 until File.read(log.path).include?('[rerun] Watching ')
end

path = File.join(directory, 'api/ping.rb')
File.write(path, File.read(path).sub("get '/ping'", "get '/updated'").sub("'pong'", "'updated'"))

expect(wait_for_response(port, '/api/updated', '{"ping":"updated"}').code).to eq('200')
expect(Net::HTTP.get_response(URI("http://127.0.0.1:#{port}/api/ping")).code).to eq('404')
rescue Timeout::Error
log.rewind
raise "Development server did not return the expected response:\n#{log.read}"
ensure
Process.kill('TERM', -pid)
begin
Timeout.timeout(10) { Process.wait(pid) }
rescue Timeout::Error
Process.kill('KILL', -pid)
Process.wait(pid)
end
end
end
end
end
end
Loading