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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* Your contribution here.

* [#58](https://github.com/ruby-grape/grape-rabl/pull/58): Support static and request-aware configured template roots while preserving explicit request roots and cache isolation - [@dblock](https://github.com/dblock).
* [#57](https://github.com/ruby-grape/grape-rabl/pull/57): Remove the obsolete Code Climate badge, use HTTPS and RubyGems for the gem badge, and scope the CI badge to master pushes - [@dblock](https://github.com/dblock).
* [#56](https://github.com/ruby-grape/grape-rabl/pull/56): Merge Coveralls coverage across the stable Ruby/Grape matrix and refresh the coverage badge - [@dblock](https://github.com/dblock).
* [#55](https://github.com/ruby-grape/grape-rabl/pull/55): Replace deprecated `Tilt::Cache` with a synchronized Hash cache for templates and layouts - [@dblock](https://github.com/dblock).
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Use [Rabl](https://github.com/nesquena/rabl) templates in [Grape](https://github
- [Upgrading](#upgrading)
- [Usage](#usage)
- [Setup view root directory](#setup-view-root-directory)
- [Configure request-aware view roots](#configure-request-aware-view-roots)
- [Tell your API to use Grape::Formatter::Rabl](#tell-your-api-to-use-grapeformatterrabl)
- [Use rabl templates conditionally](#use-rabl-templates-conditionally)
- [Use rabl layout](#use-rabl-layout)
Expand Down Expand Up @@ -52,6 +53,20 @@ use Rack::Config do |env|
end
```

### Configure request-aware view roots

Instead of setting a root with middleware, configure a static directory or a zero-argument Proc/lambda:

```ruby
Grape::Rabl.configure do |config|
config.tilt_root = -> { File.join('/path/to/views', env['api.version'].to_s) }
end
```

The Proc runs in the current Grape endpoint's context, so it can access `env`, `params`, and API helpers. The example uses `/path/to/views/v1` for version `v1` and `/path/to/views` for an unversioned request. Version directories are opt-in: a static `config.tilt_root` or existing `env['api.tilt.root']` is used unchanged.

An explicit `env['api.tilt.root']` takes precedence over the configured root, and the Proc is not called in that case. Resolution happens only when rendering a Rabl template; the resolved root is stored in the request environment, not back into configuration. Templates, partials, and layouts use that root, and cached templates remain separated by resolved directory and format. Resolver exceptions propagate; returning `nil` or `false` raises the missing-root configuration error.

### Tell your API to use Grape::Formatter::Rabl

```ruby
Expand Down
3 changes: 2 additions & 1 deletion lib/grape-rabl/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
module Grape
module Rabl
class Configuration
attr_accessor :cache_template_loading
attr_accessor :cache_template_loading, :tilt_root

def initialize
@cache_template_loading = false
@tilt_root = nil
end
end
end
Expand Down
6 changes: 5 additions & 1 deletion lib/grape-rabl/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ def rabl_template
end

def set_view_root
raise "Use Rack::Config to set 'api.tilt.root' in config.ru"
root = Grape::Rabl.configuration.tilt_root
env['api.tilt.root'] = root.is_a?(Proc) ? endpoint.instance_exec(&root) : root
return if env['api.tilt.root']

raise "Use Rack::Config to set 'api.tilt.root' in config.ru or configure 'tilt_root'"
end

def tilt_template(template)
Expand Down
95 changes: 95 additions & 0 deletions spec/grape_rabl_formatter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,101 @@
end
end

describe 'configured template roots' do
let(:app) { Class.new(Grape::API) }
let(:request_root) { nil }
let(:root) do
lambda {
File.join(__dir__, 'views', env['api.version'] == 'v2' ? 'layout_test' : '')
}
end

before do
Grape::Rabl.configuration.tilt_root = root
Grape::Rabl::Formatter.tilt_cache.clear
app.version %w[v1 v2], using: :path
app.format :json
app.formatter :json, Grape::Formatter::Rabl
override = request_root
app.before { env['api.tilt.root'] = override } if override
app.get('/user', rabl: 'user', rabl_locals: { details: 'base template' }) do
@user = Struct.new(:name).new('Jane')
@project = Struct.new(:name).new('First')
@status = 200
end
end

after do
Grape::Rabl.reset_configuration!
Grape::Rabl::Formatter.tilt_cache.clear
end

[false, true].each do |cache|
context "with template caching #{cache ? 'enabled' : 'disabled'}" do
before do
Grape::Rabl.configuration.cache_template_loading = cache
end

it 'resolves templates and layouts per request without leaking roots' do
user = { 'user' => { 'name' => 'Jane', 'project' => { 'name' => 'First' } } }
base_user = { 'user' => user.fetch('user').merge('details' => 'base template') }

get '/v1/user'
expect(JSON.parse(last_response.body)).to eq(base_user)

get '/v2/user'
expect(JSON.parse(last_response.body)).to eq('status' => 200, 'result' => user)

get '/v1/user'
expect(JSON.parse(last_response.body)).to eq(base_user)
expect(Grape::Rabl.configuration.tilt_root).to equal(root)
end
end
end

context 'a static configured root' do
let(:root) { File.join(__dir__, 'views') }

it 'does not automatically append the API version' do
get '/v2/user'
expect(JSON.parse(last_response.body)).to have_key('user')
expect(last_request.env['api.tilt.root']).to eq(root)
end
end

context 'an explicit request root' do
let(:root) { -> { raise 'configured root should not run' } }
let(:request_root) { File.join(__dir__, 'views') }

it 'takes precedence over the configured root' do
get '/v1/user'
expect(JSON.parse(last_response.body)).to have_key('user')
end
end

context 'a missing resolved root' do
let(:root) { -> {} }

it 'raises the configuration error' do
expect { get '/v1/user' }.to raise_error(RuntimeError, /configure 'tilt_root'/)
end
end

context 'a failing root resolver' do
let(:root) { -> { raise 'root lookup failed' } }

it 'propagates the error' do
expect { get '/v1/user' }.to raise_error(RuntimeError, 'root lookup failed')
end

it 'does not run when falling back to the default formatter' do
app.get('/health') { { healthy: true } }
get '/v1/health'
expect(JSON.parse(last_response.body)).to eq('healthy' => true)
end
end
end

describe 'Grape::Rabl formatter' do
subject do
Class.new(Grape::API)
Expand Down
2 changes: 1 addition & 1 deletion spec/grape_rabl_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def app

it 'should raise error about root directory' do
subject.get('/home', rabl: true) { nil }
expect { get '/home' }.to raise_error(RuntimeError, "Use Rack::Config to set 'api.tilt.root' in config.ru")
expect { get '/home' }.to raise_error(RuntimeError, /Use Rack::Config to set 'api.tilt.root' in config.ru/)
end

context 'titl root is setup' do
Expand Down
Loading