diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ed49631..db18736 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -7,6 +7,9 @@ # Offense count: 25 # Configuration parameters: AllowURI, URISchemes. +Metrics/AbcSize: + Max: 20 + Metrics/LineLength: Max: 179 diff --git a/.travis.yml b/.travis.yml index a1d0072..5f65c6a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,8 @@ language: ruby sudo: false rvm: - - 2.1.1 + - 2.3.1 + - 2.2.5 - 1.9.3 - rbx-2.2.10 - jruby-19mode diff --git a/README.md b/README.md index 6cac695..3c977b9 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 @@ -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 diff --git a/lib/grape-active_model_serializers/formatter.rb b/lib/grape-active_model_serializers/formatter.rb index edede6d..067ced1 100644 --- a/lib/grape-active_model_serializers/formatter.rb +++ b/lib/grape-active_model_serializers/formatter.rb @@ -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) @@ -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 diff --git a/lib/grape-active_model_serializers/version.rb b/lib/grape-active_model_serializers/version.rb index b7b5adb..803b464 100644 --- a/lib/grape-active_model_serializers/version.rb +++ b/lib/grape-active_model_serializers/version.rb @@ -1,5 +1,5 @@ module Grape module ActiveModelSerializers - VERSION = '1.3.1' + VERSION = '1.4.0' end end diff --git a/spec/grape-active_model_serializers/versioned_api_formatter_spec.rb b/spec/grape-active_model_serializers/versioned_api_formatter_spec.rb new file mode 100644 index 0000000..0586bbc --- /dev/null +++ b/spec/grape-active_model_serializers/versioned_api_formatter_spec.rb @@ -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 diff --git a/spec/support/serializers/v1/user_serializer.rb b/spec/support/serializers/v1/user_serializer.rb new file mode 100644 index 0000000..6eca472 --- /dev/null +++ b/spec/support/serializers/v1/user_serializer.rb @@ -0,0 +1,5 @@ +module V1 + class UserSerializer < ActiveModel::Serializer + attributes :first_name, :last_name, :email + end +end