Skip to content

Commit

Permalink
[AutoLoad] Autoload Fixes (#1904)
Browse files Browse the repository at this point in the history
* [Autoload] allows all bits of Grape to be autoloaded

* [AutoLoad] creates a compile! method to pre load an API class

* [AutoLoad] Addresses rubocop rules, and adds a readme line

* [AutoLoad] Adds changes to changelog

* Addresses issues with danger

* Makes sure the check happens inside the Mutex

* Syntax on the README

* [AutoLoad] does not take the lock to try to compile is an instance is already present

* [AutoLoad] Tests the new methods compile! and instance_for_rack
  • Loading branch information
myxoh authored and dblock committed Sep 6, 2019
1 parent 5476b27 commit 45b1288
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 74 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#### Features

* Your contribution here.
* [#1904](https://github.com/ruby-grape/grape/pull/1904): Allows Grape to load files on startup rather than on the first call - [@myxoh](https://github.com/myxoh).

#### Fixes

Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- [Installation](#installation)
- [Basic Usage](#basic-usage)
- [Mounting](#mounting)
- [All](#all)
- [Rack](#rack)
- [ActiveRecord without Rails](#activerecord-without-rails)
- [Alongside Sinatra (or other frameworks)](#alongside-sinatra-or-other-frameworks)
Expand Down Expand Up @@ -261,6 +262,17 @@ end

## Mounting

### All


By default Grape will compile the routes on the first route, it is possible to pre-load routes using the `compile!` method.

```ruby
Twitter::API.compile!
```

This can be added to your `config.ru` (if using rackup), `application.rb` (if using rails), or any file that loads your server.

### Rack

The above sample creates a Rack application that can be run from a rackup `config.ru` file
Expand All @@ -270,6 +282,13 @@ with `rackup`:
run Twitter::API
```

(With pre-loading you can use)

```ruby
Twitter::API.compile!
run Twitter::API
```

And would respond to the following routes:

GET /api/statuses/public_timeline
Expand Down
156 changes: 90 additions & 66 deletions lib/grape.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,106 +54,124 @@ module Http

module Exceptions
extend ::ActiveSupport::Autoload
autoload :Base
autoload :Validation
autoload :ValidationArrayErrors
autoload :ValidationErrors
autoload :MissingVendorOption
autoload :MissingMimeType
autoload :MissingOption
autoload :InvalidFormatter
autoload :InvalidVersionerOption
autoload :UnknownValidator
autoload :UnknownOptions
autoload :UnknownParameter
autoload :InvalidWithOptionForRepresent
autoload :IncompatibleOptionValues
autoload :MissingGroupTypeError, 'grape/exceptions/missing_group_type'
autoload :UnsupportedGroupTypeError, 'grape/exceptions/unsupported_group_type'
autoload :InvalidMessageBody
autoload :InvalidAcceptHeader
autoload :InvalidVersionHeader
autoload :MethodNotAllowed
autoload :InvalidResponse
eager_autoload do
autoload :Base
autoload :Validation
autoload :ValidationArrayErrors
autoload :ValidationErrors
autoload :MissingVendorOption
autoload :MissingMimeType
autoload :MissingOption
autoload :InvalidFormatter
autoload :InvalidVersionerOption
autoload :UnknownValidator
autoload :UnknownOptions
autoload :UnknownParameter
autoload :InvalidWithOptionForRepresent
autoload :IncompatibleOptionValues
autoload :MissingGroupTypeError, 'grape/exceptions/missing_group_type'
autoload :UnsupportedGroupTypeError, 'grape/exceptions/unsupported_group_type'
autoload :InvalidMessageBody
autoload :InvalidAcceptHeader
autoload :InvalidVersionHeader
autoload :MethodNotAllowed
autoload :InvalidResponse
end
end

module Extensions
extend ::ActiveSupport::Autoload

autoload :DeepMergeableHash
autoload :DeepSymbolizeHash
autoload :DeepHashWithIndifferentAccess
autoload :Hash

eager_autoload do
autoload :DeepMergeableHash
autoload :DeepSymbolizeHash
autoload :DeepHashWithIndifferentAccess
autoload :Hash
end
module ActiveSupport
extend ::ActiveSupport::Autoload

autoload :HashWithIndifferentAccess
eager_autoload do
autoload :HashWithIndifferentAccess
end
end

module Hashie
extend ::ActiveSupport::Autoload

autoload :Mash
eager_autoload do
autoload :Mash
end
end
end

module Middleware
extend ::ActiveSupport::Autoload
autoload :Base
autoload :Versioner
autoload :Formatter
autoload :Error
autoload :Globals
autoload :Stack
eager_autoload do
autoload :Base
autoload :Versioner
autoload :Formatter
autoload :Error
autoload :Globals
autoload :Stack
end

module Auth
extend ::ActiveSupport::Autoload
autoload :Base
autoload :DSL
autoload :StrategyInfo
autoload :Strategies
eager_autoload do
autoload :Base
autoload :DSL
autoload :StrategyInfo
autoload :Strategies
end
end

module Versioner
extend ::ActiveSupport::Autoload
autoload :Path
autoload :Header
autoload :Param
autoload :AcceptVersionHeader
eager_autoload do
autoload :Path
autoload :Header
autoload :Param
autoload :AcceptVersionHeader
end
end
end

module Util
extend ::ActiveSupport::Autoload
autoload :InheritableValues
autoload :StackableValues
autoload :ReverseStackableValues
autoload :InheritableSetting
autoload :StrictHashConfiguration
autoload :Registrable
eager_autoload do
autoload :InheritableValues
autoload :StackableValues
autoload :ReverseStackableValues
autoload :InheritableSetting
autoload :StrictHashConfiguration
autoload :Registrable
end
end

module ErrorFormatter
extend ::ActiveSupport::Autoload
autoload :Base
autoload :Json
autoload :Txt
autoload :Xml
eager_autoload do
autoload :Base
autoload :Json
autoload :Txt
autoload :Xml
end
end

module Formatter
extend ::ActiveSupport::Autoload
autoload :Json
autoload :SerializableHash
autoload :Txt
autoload :Xml
eager_autoload do
autoload :Json
autoload :SerializableHash
autoload :Txt
autoload :Xml
end
end

module Parser
extend ::ActiveSupport::Autoload
autoload :Json
autoload :Xml
eager_autoload do
autoload :Json
autoload :Xml
end
end

module DSL
Expand All @@ -177,19 +195,25 @@ module DSL

class API
extend ::ActiveSupport::Autoload
autoload :Helpers
eager_autoload do
autoload :Helpers
end
end

module Presenters
extend ::ActiveSupport::Autoload
autoload :Presenter
eager_autoload do
autoload :Presenter
end
end

module ServeFile
extend ::ActiveSupport::Autoload
autoload :FileResponse
autoload :FileBody
autoload :SendfileResponse
eager_autoload do
autoload :FileResponse
autoload :FileBody
autoload :SendfileResponse
end
end
end

Expand Down
20 changes: 14 additions & 6 deletions lib/grape/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module Grape
# should subclass this class in order to build an API.
class API
# Class methods that we want to call on the API rather than on the API object
NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration]).freeze
NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile!]).freeze

class << self
attr_accessor :base_instance, :instances
Expand Down Expand Up @@ -48,11 +48,6 @@ def override_all_methods!
# (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
# NOTE: This will only be called on an API directly mounted on RACK
def call(*args, &block)
instance_for_rack = if never_mounted?
base_instance
else
mounted_instances.first
end
instance_for_rack.call(*args, &block)
end

Expand Down Expand Up @@ -111,8 +106,21 @@ def method_missing(method, *args, &block)
end
end

def compile!
require 'grape/eager_load'
instance_for_rack.compile! # See API::Instance.compile!
end

private

def instance_for_rack
if never_mounted?
base_instance
else
mounted_instances.first
end
end

# Adds a new stage to the set up require to get a Grape::API up and running
def add_setup(method, *args, &block)
setup_step = { method: method, args: args, block: block }
Expand Down
9 changes: 7 additions & 2 deletions lib/grape/api/instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def change!
# the headers, and the body. See [the rack specification]
# (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
def call(env)
LOCK.synchronize { compile } unless instance
compile!
call!(env)
end

Expand All @@ -79,9 +79,14 @@ def cascade(value = nil)
end
end

def compile!
return if instance
LOCK.synchronize { compile unless instance }
end

# see Grape::Router#recognize_path
def recognize_path(path)
LOCK.synchronize { compile } unless instance
compile!
instance.router.recognize_path(path)
end

Expand Down
18 changes: 18 additions & 0 deletions lib/grape/eager_load.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Grape.eager_load!
Grape::Http.eager_load!
Grape::Exceptions.eager_load!
Grape::Extensions.eager_load!
Grape::Extensions::ActiveSupport.eager_load!
Grape::Extensions::Hashie.eager_load!
Grape::Middleware.eager_load!
Grape::Middleware::Auth.eager_load!
Grape::Middleware::Versioner.eager_load!
Grape::Util.eager_load!
Grape::ErrorFormatter.eager_load!
Grape::Formatter.eager_load!
Grape::Parser.eager_load!
Grape::DSL.eager_load!
Grape::API.eager_load!
Grape::Presenters.eager_load!
Grape::ServeFile.eager_load!
Rack::Head # AutoLoads the Rack::Head
34 changes: 34 additions & 0 deletions spec/grape/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,40 @@ class DummyFormatClass
end
end

describe '.compile!' do
it 'requires the grape/eager_load file' do
expect(app).to receive(:require).with('grape/eager_load') { nil }
app.compile!
end

it 'compiles the instance for rack!' do
stubbed_object = double(:instance_for_rack)
allow(app).to receive(:instance_for_rack) { stubbed_object }
end
end

# NOTE: this method is required to preserve the ability of pre-mounting
# the root API into a namespace, it may be deprecated in the future.
describe 'instance_for_rack' do
context 'when the app was not mounted' do
it 'returns the base_instance' do
expect(app.send(:instance_for_rack)).to eq app.base_instance
end
end

context 'when the app was mounted' do
it 'returns the first mounted instance' do
mounted_app = app
Class.new(Grape::API) do
namespace 'new_namespace' do
mount mounted_app
end
end
expect(app.send(:instance_for_rack)).to eq app.send(:mounted_instances).first
end
end
end

describe 'filters' do
it 'adds a before filter' do
subject.before { @foo = 'first' }
Expand Down

0 comments on commit 45b1288

Please sign in to comment.