Skip to content

Commit

Permalink
Merge pull request #49 from syntaxTerr0r/versioned-api
Browse files Browse the repository at this point in the history
API Versioning: Supporting Active Model Serializers namespacing
  • Loading branch information
dblock committed May 17, 2016
2 parents 1594eb0 + 8889b5a commit 4190cbf
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 3 deletions.
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
#### 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.

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|
# 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: 'jrhe@github.com' } }
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: 'j.doe@internet.com') }

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

0 comments on commit 4190cbf

Please sign in to comment.