Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API Versioning: Supporting Active Model Serializers namespacing #49

Merged
merged 5 commits into from
May 17, 2016
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
3 changes: 3 additions & 0 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

# Offense count: 25
# Configuration parameters: AllowURI, URISchemes.
Metrics/AbcSize:
Max: 20

Metrics/LineLength:
Max: 179

Expand Down
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Use [active_model_serializers](https://github.com/rails-api/active_model_seriali
[![Build Status](https://api.travis-ci.org/jrhe/grape-active_model_serializers.png)](http://travis-ci.org/jrhe/grape-active_model_serializers) [![Dependency Status](https://gemnasium.com/jrhe/grape-active_model_serializers.png)](https://gemnasium.com/jrhe/grape-active_model_serializers) [![Code Climate](https://codeclimate.com/github/jrhe/grape-active_model_serializers.png)](https://codeclimate.com/github/jrhe/grape-active_model_serializers)

## Breaking Changes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets move this into an UPGRADING document like https://github.com/ruby-grape/grape/blob/master/UPGRADING.md?

#### v1.4.0
* *BREAKING* Changes behaviour in serializer namespacing when using Grape API versioning. See [API versioning](https://github.com/jrhe/grape-active_model_serializers#api-versioning)
#### v1.0.0
* *BREAKING* Changes behaviour of root keys when serialising arrays. See [Array roots](https://github.com/jrhe/grape-active_model_serializers#array-roots)

Expand Down Expand Up @@ -78,6 +80,83 @@ end
# root = people
```

### API versioning

If you haven't declared an API version in Grape, nothing change.
Copy link
Member

@dblock dblock May 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nothing changes or no changes are required.


If your Grape API is versioned, which means you have declared at least one version in Grape, ie:

```ruby
module CandyBar
class Core < Grape::API
version 'candy_bar', using: :header, vendor: 'acme'

# My shiny endpoints
# ...
end
end

module Chocolate
class Core < Grape::API
version 'chocolate', using: :header, vendor: 'acme'

# My shiny endpoints
# ...
end
end

class API < Grape::API
format :json
formatter :json, Grape::Formatter::ActiveModelSerializers

mount CandyBar::Core
mount Chocolate::Core
end
```

You'll have to namespace your serializers according to each version, ie:

```ruby
module CandyBar
class UserSerializer < ActiveModel::Serializer
attributes :first_name, :last_name, :email
end
end

module Chocolate
class UserSerializer < ActiveModel::Serializer
attributes :first_name, :last_name
end
end
```

Which allow you to keep your serializers easier to maintain and more organized.

```
app
└── api
├── chocolate
└── core.rb
└── candy_bar
└── core.rb
api.rb
└── serializers
├── chocolate
└── user_serializer.rb
└── candy_bar
└── user_serializer.rb
```

or alternatively:

```
└── serializers
├── chocolate_user_serializer.rb
└── candy_bar_user_serializer.rb
```

Thus, ActiveModelSerializer will fetch automatically the right serializer to render.

### Manually specifying serializer options

```ruby
Expand Down Expand Up @@ -185,6 +264,8 @@ Enjoy :)
#### Next
* Adds support for Grape 0.10.x

#### v1.4.0
* Adds support for active model serializer namespace

#### v1.2.1
* Adds support for active model serializer 0.9.x
Expand Down
8 changes: 6 additions & 2 deletions lib/grape-active_model_serializers/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ def call(resource, env)
def fetch_serializer(resource, env)
endpoint = env['api.endpoint']
options = build_options_from_endpoint(endpoint)
ams_options = {}.tap do |ns|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is almost too much Ruby IMO :)

ams_options = {}
ams_options[:namespace] = options[:version].try(:classify) if options.try(:[], :version)

# Extracting declared version from Grape
ns[:namespace] = options[:version].try(:classify) if options.try(:[], :version)
end

serializer = options.fetch(:serializer, ActiveModel::Serializer.serializer_for(resource))
serializer = options.fetch(:serializer, ActiveModel::Serializer.serializer_for(resource, ams_options))
return nil unless serializer

options[:scope] = endpoint unless options.key?(:scope)
Expand All @@ -28,7 +32,7 @@ def fetch_serializer(resource, env)
def other_options(env)
options = {}
ams_meta = env['ams_meta'] || {}
meta = ams_meta.delete(:meta)
meta = ams_meta.delete(:meta)
meta_key = ams_meta.delete(:meta_key)
options[:meta_key] = meta_key if meta && meta_key
options[meta_key || :meta] = meta if meta
Expand Down
2 changes: 1 addition & 1 deletion lib/grape-active_model_serializers/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Grape
module ActiveModelSerializers
VERSION = '1.3.1'
VERSION = '1.4.0'
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
require 'spec_helper'
require 'grape-active_model_serializers/formatter'

describe Grape::Formatter::ActiveModelSerializers do
describe 'with a versioned API' do
subject { Grape::Formatter::ActiveModelSerializers }

describe 'serializer options from namespace' do
let(:app) { Class.new(Grape::API) }

before do
app.format :json
app.formatter :json, Grape::Formatter::ActiveModelSerializers
app.version 'v1', using: :param

app.namespace('space') do |ns|
ns.get('/', root: false, apiver: 'v1') do
{ user: { first_name: 'JR', last_name: 'HE', email: '[email protected]' } }
end
end
end

it 'should read serializer options like "root"' do
expect(described_class.build_options_from_endpoint(app.endpoints.first)).to include :root
end
end

describe '.fetch_serializer' do
let(:user) { User.new(first_name: 'John', email: '[email protected]') }

if Grape::Util.const_defined?('InheritableSetting')
let(:endpoint) { Grape::Endpoint.new(Grape::Util::InheritableSetting.new, path: '/', method: 'foo', version: 'v1', root: false) }
else
let(:endpoint) { Grape::Endpoint.new({}, path: '/', method: 'foo', version: 'v1', root: false) }
end

let(:env) { { 'api.endpoint' => endpoint } }

before do
def endpoint.current_user
@current_user ||= User.new(first_name: 'Current user')
end

def endpoint.default_serializer_options
{ only: :only, except: :except }
end
end

subject { described_class.fetch_serializer(user, env) }

it { should be_a V1::UserSerializer }

it 'should have correct scope set' do
expect(subject.scope.current_user).to eq(endpoint.current_user)
end

it 'should read default serializer options' do
expect(subject.instance_variable_get('@only')).to eq([:only])
expect(subject.instance_variable_get('@except')).to eq([:except])
end

it 'should read serializer options like "root"' do
expect(described_class.build_options_from_endpoint(endpoint).keys).to include :root
end
end
end
end
5 changes: 5 additions & 0 deletions spec/support/serializers/v1/user_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module V1
class UserSerializer < ActiveModel::Serializer
attributes :first_name, :last_name, :email
end
end