├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── .rubocop_todo.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dangerfile ├── Gemfile ├── Guardfile ├── LICENSE.txt ├── README.md ├── RELEASING.md ├── Rakefile ├── UPGRADING.md ├── grape-active_model_serializers.gemspec ├── lib ├── .rspec ├── .travis.yml ├── grape-active_model_serializers.rb └── grape-active_model_serializers │ ├── endpoint_extension.rb │ ├── error_formatter.rb │ ├── formatter.rb │ ├── options_builder.rb │ ├── serializer_resolver.rb │ └── version.rb └── spec ├── features └── grape-active_model_serializers │ └── render_spec.rb ├── grape-active_model_serializers ├── endpoint_extension_spec.rb ├── error_formatter_spec.rb ├── formatter_spec.rb └── versioned_api_formatter_spec.rb ├── grape └── active_model_serializers │ ├── options_builder_spec.rb │ └── serializer_resolver_spec.rb ├── integration └── sequel_spec.rb ├── old_grape_ams_spec.rb ├── spec_helper.rb └── support ├── api ├── users_api.rb └── v4 │ └── users_api.rb ├── models ├── blog_post.rb └── user.rb └── serializers ├── blog_post_serializer.rb ├── user_serializer.rb ├── v1 └── user_serializer.rb ├── v2 └── user_serializer.rb ├── v3 └── user_serializer.rb ├── v4 └── user_serializer.rb └── v5 └── user_serializer.rb /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | danger: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - uses: ruby/setup-ruby@v1 10 | with: 11 | ruby-version: 3.4.4 12 | - name: Install dependencies 13 | run: bundle install --jobs 4 --retry 3 14 | - name: Run Danger 15 | run: | 16 | # the token is public, has public_repo scope and belongs to the grape-bot user owned by @dblock, this is ok 17 | TOKEN=$(echo -n Z2hwX2lYb0dPNXNyejYzOFJyaTV3QUxUdkNiS1dtblFwZTFuRXpmMwo= | base64 --decode) 18 | DANGER_GITHUB_API_TOKEN=$TOKEN bundle exec danger --verbose 19 | 20 | test: 21 | runs-on: ubuntu-latest 22 | strategy: 23 | matrix: 24 | ruby-version: [3.4.4, 3.3.8, 3.2.8, 3.1.7] 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: ruby/setup-ruby@v1 28 | with: 29 | ruby-version: ${{ matrix.ruby-version }} 30 | - name: Install dependencies 31 | run: bundle install --jobs 4 --retry 3 32 | - name: Run tests 33 | run: bundle exec rake 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | *.sassc 3 | .sass-cache 4 | capybara-*.html 5 | .rspec 6 | /.bundle 7 | /vendor/bundle 8 | /log/* 9 | /tmp/* 10 | /db/*.sqlite3 11 | /public/system/* 12 | /coverage/ 13 | /spec/tmp/* 14 | **.orig 15 | rerun.txt 16 | pickle-email-*.html 17 | Gemfile.lock 18 | .DS_Store 19 | *.gem 20 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | AllCops: 4 | Exclude: 5 | - Guardfile 6 | - grape-active_model_serializers.gemspec 7 | NewCops: enable 8 | 9 | Style/BlockDelimiters: 10 | Enabled: false 11 | 12 | Style/FrozenStringLiteralComment: 13 | Enabled: false 14 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2018-03-14 14:59:23 -0400 using RuboCop version 0.53.0. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | # Configuration parameters: Include. 11 | # Include: **/*.gemfile, **/Gemfile, **/gems.rb 12 | Bundler/DuplicatedGem: 13 | Exclude: 14 | - 'Gemfile' 15 | 16 | # Offense count: 1 17 | Metrics/AbcSize: 18 | Max: 18 19 | 20 | # Offense count: 23 21 | # Configuration parameters: CountComments, ExcludedMethods. 22 | Metrics/BlockLength: 23 | Max: 133 24 | 25 | # Offense count: 1 26 | Metrics/CyclomaticComplexity: 27 | Max: 9 28 | 29 | # Offense count: 1 30 | # Configuration parameters: CountComments. 31 | Metrics/MethodLength: 32 | Max: 15 33 | 34 | # Offense count: 1 35 | Metrics/PerceivedComplexity: 36 | Max: 10 37 | 38 | # Offense count: 2 39 | # Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms. 40 | # AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS 41 | Naming/FileName: 42 | Exclude: 43 | - 'lib/grape-active_model_serializers.rb' 44 | - 'spec/grape-active_model_serializers_spec.rb' 45 | 46 | # Offense count: 5 47 | Style/Documentation: 48 | Exclude: 49 | - 'spec/**/*' 50 | - 'test/**/*' 51 | - 'lib/grape-active_model_serializers/endpoint_extension.rb' 52 | - 'lib/grape-active_model_serializers/error_formatter.rb' 53 | - 'lib/grape-active_model_serializers/formatter.rb' 54 | - 'lib/grape-active_model_serializers/options_builder.rb' 55 | - 'lib/grape-active_model_serializers/serializer_resolver.rb' 56 | 57 | # Offense count: 1 58 | # Configuration parameters: MinBodyLength. 59 | Style/GuardClause: 60 | Exclude: 61 | - 'lib/grape-active_model_serializers/serializer_resolver.rb' 62 | 63 | # Offense count: 2 64 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. 65 | # URISchemes: http, https 66 | Layout/LineLength: 67 | Max: 87 68 | 69 | Lint/ConstantDefinitionInBlock: 70 | Exclude: 71 | - spec/integration/sequel_spec.rb 72 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### 2.0.0 (Next) 4 | 5 | * [#96](https://github.com/ruby-grape/grape-active_model_serializers/pull/96): Add compatibility for Grape 2.3 - [@samsonjs](https://github.com/samsonjs). 6 | * [#98](https://github.com/ruby-grape/grape-active_model_serializers/pull/98): Out with Travis CI, in with GitHub Actions - [@samsonjs](https://github.com/samsonjs). 7 | * Your contribution here. 8 | 9 | ### 1.5.2 (2018/03/14) 10 | 11 | * [#76](https://github.com/ruby-grape/grape-active_model_serializers/pull/76): Add custom error formatter - [@xn](https://github.com/xn). 12 | 13 | ### 1.5.1 (2017/04/25) 14 | 15 | * [#74](https://github.com/ruby-grape/grape-active_model_serializers/pull/74): Add support for Sequel - [@drn](https://github.com/drn). 16 | * [#73](https://github.com/ruby-grape/grape-active_model_serializers/pull/73): Ensure all AMS options are passed through - [@drn](https://github.com/drn). 17 | * [#65](https://github.com/ruby-grape/grape-active_model_serializers/pull/65): Added Danger, PR linter - [@dblock](https://github.com/dblock). 18 | * [#63](https://github.com/ruby-grape/grape-active_model_serializers/pull/63): Pass adapter options through render - [@drn](https://github.com/drn). 19 | 20 | ### 1.5.0 (2016/08/24) 21 | 22 | * [#61](https://github.com/ruby-grape/grape-active_model_serializers/pull/61): Adds support for collection serializers - [@drn](https://github.com/drn). 23 | * [#60](https://github.com/ruby-grape/grape-active_model_serializers/pull/60): Namespace serializer inference - [@drn](https://github.com/drn). 24 | * [#59](https://github.com/ruby-grape/grape-active_model_serializers/pull/59): Refactor option and serializer resolution - [@drn](https://github.com/drn). 25 | * [#57](https://github.com/ruby-grape/grape-active_model_serializers/pull/57): Solve line length linter issues - [@drn](https://github.com/drn). 26 | * [#54](https://github.com/ruby-grape/grape-active_model_serializers/pull/54): Adding support for ASM v0.10. Drops support for ASM v0.9 - [@drn](https://github.com/drn). 27 | 28 | ### 1.4.0 (2016/07/14) 29 | 30 | * [#49](https://github.com/ruby-grape/grape-active_model_serializers/pull/49): Adds support for active model serializer namespace - [@syntaxTerr0r](https://github.com/syntaxTerr0r). 31 | * [#36](https://github.com/ruby-grape/grape-active_model_serializers/pull/36), [#50](https://github.com/jrhe/grape-active_model_serializers/pull/50): Added support through Grape 0.16.x - [@dblock](https://github.com/dblock). 32 | 33 | ### 1.3.2 (2015/02/27) 34 | 35 | * [#40](https://github.com/ruby-grape/grape-active_model_serializers/pull/40): Use env to pass AMS meta around - [@dblock](https://github.com/dblock). 36 | * [#39](https://github.com/ruby-grape/grape-active_model_serializers/pull/39): Look for namespace and other options to configure serializers - [@jwkoelewijn](https://github.com/jwkoelewijn). 37 | 38 | ### 1.3.1 (2014/11/20) 39 | 40 | * [#30](https://github.com/ruby-grape/grape-active_model_serializers/pull/30): Read options from default_serializer_options - [@siong1987](https://github.com/siong1987). 41 | * [#24](https://github.com/ruby-grape/grape-active_model_serializers/pull/24): Makes it possible to use `current_user` within serializers - [@sonxurxo](https://github.com/sonxurxo). 42 | 43 | ### 1.2.1 (2014/07/23) 44 | 45 | * [#21](https://github.com/ruby-grape/grape-active_model_serializers/pull/21): Correctly fetch serialization scope - [@radanskoric](https://github.com/radanskoric). 46 | * [#18](https://github.com/ruby-grape/grape-active_model_serializers/pull/18): Added support for active model serializer 0.9.x - [@sbounmy](https://github.com/sbounmy). 47 | 48 | * [#15](https://github.com/ruby-grape/grape-active_model_serializers/pull/15): Added `render` syntactic sugar - [@zph](https://github.com/zph). 49 | * [#14](https://github.com/ruby-grape/grape-active_model_serializers/pull/14): Fix: `default_root` method to support symbol route in Grape - [@wjp2013](https://github.com/wjp2013). 50 | * [#12](https://github.com/ruby-grape/grape-active_model_serializers/pull/12): Added support for `current_user` - [@kpassapk](https://github.com/kpassapk). 51 | * [#11](https://github.com/ruby-grape/grape-active_model_serializers/pull/11): Fixed require path - [@schickling](https://github.com/schickling). 52 | 53 | ### 1.0.1 (2013/09/09) 54 | 55 | * [#6](https://github.com/ruby-grape/grape-active_model_serializers/pull/6): Conform to ActiveModel::Serializers way of determining array-ness - [@tfe](https://github.com/tfe). 56 | * [#4](https://github.com/ruby-grape/grape-active_model_serializers/pull/4): Support for namespace options and rely more on active_model_serializers - [@johnallen3d](https://github.com/johnallen3d). 57 | * [#1](https://github.com/ruby-grape/grape-active_model_serializers/pull/1): Fix: Grape::ActiveModelSerializers for models with compound names - [@george](https://github.com/george). 58 | 59 | ### 1.0.0 (2013/09/09) 60 | 61 | * Initial public release - [@jrhe](https://github.com/jrhe). 62 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Grape-Swagger 2 | 3 | This project is work of [many contributors](https://github.com/ruby-grape/grape-active_model_serializers/graphs/contributors). 4 | You're encouraged to submit [pull requests](https://github.com/ruby-grape/grape-active_model_serializers/pulls), 5 | [propose features and discuss issues](https://github.com/ruby-grape/grape-active_model_serializers/issues). 6 | When in doubt, ask a question in the [Grape Google Group](http://groups.google.com/group/ruby-grape). 7 | 8 | In the examples below, substitute your Github username for `contributor` in URLs. 9 | 10 | ## Fork the Project 11 | 12 | Fork the [project on Github](https://github.com/ruby-grape/grape-active_model_serializers) and check out your copy. 13 | 14 | ``` 15 | git clone https://github.com/contributor/grape-active_model_serializers.git 16 | cd grape-active_model_serializers 17 | git remote add upstream https://github.com/ruby-grape/grape-active_model_serializers.git 18 | ``` 19 | 20 | ## Create a Topic Branch 21 | 22 | Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. 23 | 24 | ``` 25 | git checkout master 26 | git pull upstream master 27 | git checkout -b my-feature-branch 28 | ``` 29 | 30 | ## Bundle Install and Test 31 | 32 | Ensure that you can build the project and run tests. 33 | 34 | ``` 35 | bundle install 36 | bundle exec rake 37 | ``` 38 | 39 | ## Write Tests 40 | 41 | Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. 42 | Add to [spec](spec). 43 | 44 | We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. 45 | 46 | ## Write Code 47 | 48 | Implement your feature or bug fix. 49 | 50 | Ruby style is enforced with [RuboCop](https://github.com/bbatsov/rubocop). 51 | Run `bundle exec rubocop` and fix any style issues highlighted. 52 | 53 | Make sure that `bundle exec rake` completes without errors. 54 | 55 | ## Write Documentation 56 | 57 | Document any external behavior in the [README](README.md). 58 | 59 | ## Update Changelog 60 | 61 | Add a line to [CHANGELOG](CHANGELOG.md) under *Next Release*. 62 | Make it look like every other line, including your name and link to your Github account. 63 | 64 | ## Commit Changes 65 | 66 | Make sure git knows your name and email address: 67 | 68 | ``` 69 | git config --global user.name "Your Name" 70 | git config --global user.email "contributor@example.com" 71 | ``` 72 | 73 | Writing good commit logs is important. A commit log should describe what changed and why. 74 | 75 | ``` 76 | git add ... 77 | git commit 78 | ``` 79 | 80 | ## Push 81 | 82 | ``` 83 | git push origin my-feature-branch 84 | ``` 85 | 86 | ## Make a Pull Request 87 | 88 | Go to https://github.com/contributor/grape-active_model_serializers and select your feature branch. 89 | Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. 90 | 91 | ## Rebase 92 | 93 | If you've been working on a change for a while, rebase with upstream/master. 94 | 95 | ``` 96 | git fetch upstream 97 | git rebase upstream/master 98 | git push origin my-feature-branch -f 99 | ``` 100 | 101 | ## Update CHANGELOG Again 102 | 103 | Update the [CHANGELOG](CHANGELOG.md) with the pull request number. A typical entry looks as follows. 104 | 105 | ``` 106 | * [#123](https://github.com/ruby-grape/grape-active_model_serializers/pull/123): Reticulated splines - [@contributor](https://github.com/contributor). 107 | ``` 108 | 109 | Amend your previous commit and force push the changes. 110 | 111 | ``` 112 | git commit --amend 113 | git push origin my-feature-branch -f 114 | ``` 115 | 116 | ## Check on Your Pull Request 117 | 118 | Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above. 119 | 120 | ## Be Patient 121 | 122 | It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there! 123 | 124 | ## Thank You 125 | 126 | Please do know that we really appreciate and value your time and work. We love you, really. 127 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | danger.import_dangerfile(gem: 'ruby-grape-danger') 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | case version = ENV['GRAPE_VERSION'] || '~> 2.3.0' 6 | when 'HEAD' 7 | gem 'grape', github: 'intridea/grape' 8 | else 9 | gem 'grape', version 10 | end 11 | 12 | group :test do 13 | gem 'rack-test' 14 | gem 'ruby-grape-danger', '~> 0.2.0', require: false 15 | gem 'sequel', '~> 5.91', require: false 16 | gem 'sqlite3' 17 | end 18 | 19 | group :development, :test do 20 | gem 'guard-rspec' 21 | gem 'listen', '~> 3.9.0' 22 | gem 'rake' 23 | gem 'rspec' 24 | gem 'rubocop', '1.75.7' 25 | end 26 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | guard 'rspec' do 5 | watch(%r{^spec/.+_spec\.rb$}) 6 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 7 | watch('spec/spec_helper.rb') { 'spec' } 8 | 9 | # Rails example 10 | watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 11 | watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } 12 | watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } 13 | watch(%r{^spec/support/(.+)\.rb$}) { 'spec' } 14 | watch('config/routes.rb') { 'spec/routing' } 15 | watch('app/controllers/application_controller.rb') { 'spec/controllers' } 16 | 17 | # Capybara features specs 18 | watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" } 19 | 20 | # Turnip features and steps 21 | watch(%r{^spec/acceptance/(.+)\.feature$}) 22 | watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } 23 | end 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Jonathan Evans 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | - [Grape::ActiveModelSerializers](#grapeactivemodelserializers) 4 | - [Installation](#installation) 5 | - [Dependencies](#dependencies) 6 | - [Usage](#usage) 7 | - [Require grape-active_model_serializers](#require-grape-active_model_serializers) 8 | - [Tell your API to use Grape::Formatter::ActiveModelSerializers](#tell-your-api-to-use-grapeformatteractivemodelserializers) 9 | - [Writing Serializers](#writing-serializers) 10 | - [Serializers are inferred by active_record model names](#serializers-are-inferred-by-active_record-model-names) 11 | - [Array Roots](#array-roots) 12 | - [API Versioning](#api-versioning) 13 | - [Manually specifying serializer / adapter options](#manually-specifying-serializer--adapter-options) 14 | - [Custom Metadata](#custom-metadata) 15 | - [Default Serializer Options](#default-serializer-options) 16 | - [Current User](#current-user) 17 | - [Full Example](#full-example) 18 | - [Contributing](#contributing) 19 | - [History](#history) 20 | 21 | # Grape::ActiveModelSerializers 22 | 23 | Use [active_model_serializers](https://github.com/rails-api/active_model_serializers) with [Grape](https://github.com/intridea/grape)! 24 | 25 | [![Gem Version](https://badge.fury.io/rb/grape-active_model_serializers.svg)](https://badge.fury.io/rb/grape-active_model_serializers) 26 | [![Build Status](https://github.com/ruby-grape/grape-active_model_serializers/actions/workflows/ci.yml/badge.svg)](https://github.com/ruby-grape/grape-active_model_serializers/actions/workflows/ci.yml) [![Code Climate](https://codeclimate.com/github/ruby-grape/grape-active_model_serializers.svg)](https://codeclimate.com/github/ruby-grape/grape-active_model_serializers) 27 | 28 | ## Installation 29 | 30 | Add the `grape` and `grape-active_model_serializers` gems to Gemfile and run `bundle install`. 31 | 32 | ```ruby 33 | gem 'grape-active_model_serializers' 34 | ``` 35 | 36 | See [UPGRADING](UPGRADING.md) if you're upgrading from a previous version. 37 | 38 | ## Dependencies 39 | 40 | * >= Ruby v3.1 41 | * >= [grape](https://github.com/ruby-grape/grape) v2.3.0 42 | * >= [active_model_serializers](https://github.com/rails-api/active_model_serializers) v0.10.0 43 | 44 | ## Usage 45 | 46 | ### Require grape-active_model_serializers 47 | 48 | ```ruby 49 | # config.ru 50 | require 'grape-active_model_serializers' 51 | ``` 52 | 53 | ### Tell your API to use Grape::Formatter::ActiveModelSerializers 54 | 55 | ```ruby 56 | class API < Grape::API 57 | format :json 58 | formatter :json, Grape::Formatter::ActiveModelSerializers 59 | 60 | # Serializes errors with ActiveModel::Serializer::ErrorSerializer if an ActiveModel. 61 | # Serializer conforms to the adapter, ex: json, jsonapi. 62 | # So an error formatted with a jsonapi formatter would render as per: 63 | # http://jsonapi.org/format/#error-objects 64 | error_formatter :json, Grape::Formatter::ActiveModelSerializers 65 | end 66 | ``` 67 | 68 | ### Writing Serializers 69 | 70 | See [active_model_serializers](https://github.com/rails-api/active_model_serializers) 71 | 72 | 73 | ### Serializers are inferred by active_record model names 74 | 75 | `grape-active_model_serializers` will search for serializers for the objects returned by your grape API. 76 | 77 | ```ruby 78 | namespace :users do 79 | get ":id" do 80 | @user = User.find(params[:id]) 81 | end 82 | end 83 | ``` 84 | In this case, as User objects are being returned, grape-active_model_serializers will look for a serializer named UserSerializer. 85 | 86 | ### Array Roots 87 | 88 | When serializing an array, the array root is set to the innermost namespace name if there is one, otherwise it is set to the route name. 89 | 90 | In the following API the array root is `users`. 91 | 92 | ```ruby 93 | namespace :users do 94 | get ":id" do 95 | @user = User.find(params[:id]) 96 | end 97 | end 98 | ``` 99 | 100 | In the following example the array root is `people`. 101 | 102 | ```ruby 103 | get "people" do 104 | @user = User.all 105 | end 106 | ``` 107 | 108 | ### API Versioning 109 | 110 | If your Grape API is versioned you must namespace your serializers accordingly. 111 | 112 | For example, given the following API. 113 | 114 | ```ruby 115 | module CandyBar 116 | class Core < Grape::API 117 | version 'candy_bar', using: :header, vendor: 'acme' 118 | end 119 | end 120 | 121 | module Chocolate 122 | class Core < Grape::API 123 | version 'chocolate', using: :header, vendor: 'acme' 124 | end 125 | end 126 | 127 | class API < Grape::API 128 | format :json 129 | formatter :json, Grape::Formatter::ActiveModelSerializers 130 | 131 | mount CandyBar::Core 132 | mount Chocolate::Core 133 | end 134 | ``` 135 | 136 | Namespace your serializers according to each version. 137 | 138 | ```ruby 139 | module CandyBar 140 | class UserSerializer < ActiveModel::Serializer 141 | attributes :first_name, :last_name, :email 142 | end 143 | end 144 | 145 | module Chocolate 146 | class UserSerializer < ActiveModel::Serializer 147 | attributes :first_name, :last_name 148 | end 149 | end 150 | ``` 151 | 152 | This keeps serializers organized. 153 | 154 | ``` 155 | app 156 | └── api 157 | ├── chocolate 158 | └── core.rb 159 | └── candy_bar 160 | └── core.rb 161 | api.rb 162 | └── serializers 163 | ├── chocolate 164 | └── user_serializer.rb 165 | └── candy_bar 166 | └── user_serializer.rb 167 | ``` 168 | 169 | Or as follows. 170 | 171 | ``` 172 | └── serializers 173 | ├── chocolate_user_serializer.rb 174 | └── candy_bar_user_serializer.rb 175 | ``` 176 | 177 | ActiveModelSerializer will fetch automatically the right serializer to render. 178 | 179 | ### Manually specifying serializer / adapter options 180 | 181 | ```ruby 182 | # Serializer and adapter options can be specified on routes or namespaces. 183 | namespace 'foo', serializer: BarSerializer do 184 | get "/" do 185 | # will use "bar" serializer 186 | end 187 | 188 | # Options specified on a route or namespace override those of the containing namespace. 189 | get "/home", serializer: HomeSerializer do 190 | # will use "home" serializer 191 | end 192 | 193 | # All standard options for `ActiveModel::Serializers` are supported. 194 | get "/fancy_homes", root: 'world', each_serializer: FancyHomesSerializer 195 | ... 196 | end 197 | end 198 | ``` 199 | 200 | ```ruby 201 | # Serializer and adapter options can also be specified in the body of the route 202 | resource :users do 203 | get '/:id' do 204 | if conditional 205 | # uses UserSerializer and configured default adapter automatically 206 | current_user 207 | else 208 | # uses specified serializer and adapter 209 | render current_user, serializer: ErrorSerializer, adapter: :attributes 210 | end 211 | end 212 | end 213 | ``` 214 | 215 | ```ruby 216 | # Adhoc serializer options can be specified in the body of the route 217 | resource :users do 218 | get '/:id' do 219 | render current_user, extra: { adhoc_name_option: 'value' } 220 | end 221 | end 222 | 223 | class UserSerializer 224 | def name 225 | instance_options[:adhoc_name_option] # accessible in instance_options 226 | end 227 | end 228 | ``` 229 | 230 | ### Custom Metadata 231 | 232 | ```ruby 233 | # Control any additional metadata using meta and meta_key 234 | get "/homes" 235 | collection = Home.all 236 | render collection, { meta: { page: 5, current_page: 3 }, meta_key: :pagination_info } 237 | end 238 | ``` 239 | 240 | ### Default Serializer Options 241 | 242 | ```ruby 243 | helpers do 244 | def default_serializer_options 245 | { only: params[:only], except: params[:except] } 246 | end 247 | end 248 | ``` 249 | 250 | ### Current User 251 | 252 | One of the nice features of ActiveModel::Serializers is that it provides access to the authorization context via the `current_user`. 253 | 254 | In Grape, you can get the same behavior by defining a `current_user` helper method. 255 | 256 | ```ruby 257 | helpers do 258 | def current_user 259 | @current_user ||= User.where(access_token: params[:token]).first 260 | end 261 | 262 | def authenticate! 263 | error!('401 Unauthenticated', 401) unless current_user 264 | end 265 | end 266 | ``` 267 | 268 | Then, in your serializer, you could show or hide some elements based on the current user's permissions. 269 | 270 | ```ruby 271 | class PostSerializer < ActiveModel::Serializer 272 | def include_admin_comments? 273 | current_user.roles.member? :admin 274 | end 275 | end 276 | ``` 277 | 278 | *Note*: in the [0.9.x stable version of active model serializers](https://github.com/rails-api/active_model_serializers/tree/0-9-stable#customizing-scope), you have to access current user on scope - so `scope.current_user`. 279 | 280 | ### Full Example 281 | 282 | ```ruby 283 | class User < ActiveRecord::Base 284 | attr_accessor :first_name, :last_name, :password, :email 285 | end 286 | 287 | class UserSerializer < ActiveModel::Serializer 288 | attributes :first_name, :last_name 289 | end 290 | 291 | class API < Grape::API 292 | get("/home") do 293 | User.new({first_name: 'JR', last_name: 'HE', email: 'contact@jrhe.co.uk'}) 294 | end 295 | end 296 | 297 | API.new.get "/home" # => '{ user: { first_name: "JR", last_name: "HE" } }' 298 | ``` 299 | 300 | ## Contributing 301 | 302 | See [CONTRIBUTING](CONTRIBUTING.md). 303 | 304 | ## History 305 | 306 | Structured and based upon [grape-rabl](https://github.com/ruby-grape/grape-rabl). 307 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing Grape::ActiveModelSerializers 2 | 3 | There're no particular rules about when to release grape-active_model_serializers. Release bug fixes frequently, features not so frequently and breaking API changes rarely. 4 | 5 | ### Release 6 | 7 | Run tests, check that all tests succeed locally. 8 | 9 | ``` 10 | bundle install 11 | rake 12 | ``` 13 | 14 | Check that the last build succeeded in [Travis CI](https://travis-ci.org/ruby-grape/grape-active_model_serializers) for all supported platforms. 15 | 16 | Change "Next Release" in [CHANGELOG.md](CHANGELOG.md) to the new version. 17 | 18 | ``` 19 | ### 0.7.2 (February 6, 2014) 20 | ``` 21 | 22 | Remove the line with "Your contribution here.", since there will be no more contributions to this release. 23 | 24 | Commit your changes. 25 | 26 | ``` 27 | git add CHANGELOG.md lib/grape-active_model_serializers/version.rb 28 | git commit -m "Preparing for release, 0.7.2." 29 | git push origin master 30 | ``` 31 | 32 | Release. 33 | 34 | ``` 35 | $ rake release 36 | 37 | grape-active_model_serializers 0.7.2 built to pkg/grape-active_model_serializers-0.7.2.gem. 38 | Tagged v0.7.2. 39 | Pushed git commits and tags. 40 | Pushed grape-active_model_serializers 0.7.2 to rubygems.org. 41 | ``` 42 | 43 | ### Prepare for the Next Version 44 | 45 | Modify [lib/grape-active_model_serializers/version.rb](lib/grape-active_model_serializers/version.rb), increment the third number (eg. change `0.7.2` to `0.7.3`). 46 | 47 | 48 | ```ruby 49 | module Grape 50 | module ActiveModelSerializers 51 | VERSION = '0.7.3'.freeze 52 | end 53 | end 54 | ``` 55 | 56 | Add the next release to [CHANGELOG.md](CHANGELOG.md). 57 | 58 | ``` 59 | #### 0.7.3 (Next) 60 | 61 | * Your contribution here. 62 | ``` 63 | 64 | Commit your changes. 65 | 66 | ``` 67 | git add CHANGELOG.md lib/grape-active_model_serializers/version.rb 68 | git commit -m "Preparing for next development iteration, 0.7.3." 69 | git push origin master 70 | ``` 71 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | 3 | require 'bundler/gem_tasks' 4 | 5 | require 'rspec/core' 6 | require 'rspec/core/rake_task' 7 | RSpec::Core::RakeTask.new(:spec) do |spec| 8 | spec.rspec_opts = ['-fd -c'] 9 | spec.pattern = FileList['spec/**/*_spec.rb'] 10 | end 11 | 12 | require 'rubocop/rake_task' 13 | RuboCop::RakeTask.new(:rubocop) 14 | 15 | task default: %i[rubocop spec] 16 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | ### Upgrading to v1.5.0 2 | 3 | #### ASM v0.10.x support added, v0.9.x support dropped 4 | 5 | [ASM](https://github.com/rails-api/active_model_serializers) introduced 6 | breaking changes with ASM v0.10.x. Support has been introduced in v1.5.0. 7 | If you require support for older version of ASM, please lock your Gemfile 8 | to the relevant version. 9 | 10 | #### Non-collection Serializer Resolution 11 | 12 | Serializer resolution now uses the following strategy: 13 | 14 | 1. Defined by the resource 15 | 16 | ``` 17 | class UsersApi < Grape::Api 18 | resource :users do 19 | get '/:id', serializer: SpecifiedUserSerializer do 20 | current_user 21 | end 22 | end 23 | end 24 | 25 | class User 26 | def serializer_class 27 | SpecifiedUserSerializer 28 | end 29 | end 30 | ``` 31 | 32 | 2. Specified by options 33 | 34 | ``` 35 | class UsersApi < Grape::Api 36 | resource :users do 37 | get '/:id', serializer: SpecifiedUserSerializer do 38 | current_user 39 | end 40 | end 41 | end 42 | ``` 43 | 44 | 2. Namespace inferred 45 | 46 | ``` 47 | class V1::UsersApi < Grape::Api 48 | resource :users do 49 | get '/:id' do 50 | current_user 51 | end 52 | end 53 | end 54 | # 'V1::UsersApi'.deconstantize => 'V1' 55 | # searches for serializers in the V1:: namespace with the name UserSerializer 56 | 57 | # used 58 | class V1::UserSerializer 59 | ... 60 | end 61 | 62 | # not used since V1::UserSerializer resolved first 63 | class UserSerializer 64 | ... 65 | end 66 | ``` 67 | 68 | 3. Version inferred 69 | 70 | ``` 71 | class UsersApi < Grape::Api 72 | version 'v2' 73 | 74 | resource :users do 75 | get '/:id' do 76 | current_user 77 | end 78 | end 79 | end 80 | # 'v2'.classify => V2 81 | # searches for serializers in the V2:: namespace with the name UserSerializer 82 | 83 | # used 84 | class V2::UserSerializer 85 | ... 86 | end 87 | 88 | # not used since V2::UserSerializer resolved first 89 | class UserSerializer 90 | ... 91 | end 92 | ``` 93 | 94 | 4. Default ASM lookup 95 | 96 | ``` 97 | class V1::UsersApi < Grape::Api 98 | version 'v2' 99 | 100 | resource :users do 101 | get '/:id' do 102 | current_user 103 | end 104 | end 105 | end 106 | # searches for serializers in the V1:: namespace, none found 107 | # searches for serializers in the V2:: namespace, none found 108 | 109 | # used as no other serializers were found 110 | class UserSerializer 111 | ... 112 | end 113 | ``` 114 | 115 | #### Collection Serializer Resolution 116 | 117 | Serializer resolution for collections also uses the above strategy, but has 118 | the added option of overriding the member serializer if the `each_serializer` 119 | options is specified. 120 | 121 | ``` 122 | class UsersApi < Grape::Api 123 | resource :users do 124 | get each_serializer: SpecifiedUserSerializer do 125 | User.all 126 | end 127 | end 128 | end 129 | ``` 130 | 131 | 132 | ### Upgrading to v1.4.0 133 | 134 | #### Changes in Serializer Namespacing 135 | 136 | Version 1.4.0 introduced changes in serializer namespacing when using Grape 137 | API versioning. 138 | 139 | If you haven't declared an API version in Grape, nothing changed. 140 | 141 | If your Grape API is versioned, which means you have declared at least one 142 | version in Grape, you must namespace your serializers accordingly. 143 | 144 | For example, given the following API. 145 | 146 | ```ruby 147 | module CandyBar 148 | class Core < Grape::API 149 | version 'candy_bar', using: :header, vendor: 'acme' 150 | end 151 | end 152 | 153 | module Chocolate 154 | class Core < Grape::API 155 | version 'chocolate', using: :header, vendor: 'acme' 156 | end 157 | end 158 | 159 | class API < Grape::API 160 | format :json 161 | formatter :json, Grape::Formatter::ActiveModelSerializers 162 | 163 | mount CandyBar::Core 164 | mount Chocolate::Core 165 | end 166 | ``` 167 | 168 | Namespace serializers according to each version. 169 | 170 | ```ruby 171 | module CandyBar 172 | class UserSerializer < ActiveModel::Serializer 173 | attributes :first_name, :last_name, :email 174 | end 175 | end 176 | 177 | module Chocolate 178 | class UserSerializer < ActiveModel::Serializer 179 | attributes :first_name, :last_name 180 | end 181 | end 182 | ``` 183 | 184 | This will allow you to keep your serializers easier to maintain and more 185 | organized. 186 | 187 | ``` 188 | app 189 | └── api 190 | ├── chocolate 191 | └── core.rb 192 | └── candy_bar 193 | └── core.rb 194 | api.rb 195 | └── serializers 196 | ├── chocolate 197 | └── user_serializer.rb 198 | └── candy_bar 199 | └── user_serializer.rb 200 | ``` 201 | 202 | or alternatively: 203 | 204 | ``` 205 | └── serializers 206 | ├── chocolate_user_serializer.rb 207 | └── candy_bar_user_serializer.rb 208 | ``` 209 | 210 | Thus, ActiveModelSerializer will fetch automatically the right serializer to 211 | render. 212 | 213 | ### Upgrading to v1.0.0 214 | 215 | #### Changes to Array Roots 216 | 217 | When serializing an array, the array root is set to the innermost namespace 218 | name if there is one, otherwise it is set to the route name. 219 | 220 | In the following example the array root is `users`. 221 | 222 | ```ruby 223 | namespace :users do 224 | get ":id" do 225 | @user = User.find(params[:id]) 226 | end 227 | end 228 | ``` 229 | 230 | In the following example the array root is `people`. 231 | 232 | ```ruby 233 | get "people" do 234 | @user = User.all 235 | end 236 | ``` 237 | -------------------------------------------------------------------------------- /grape-active_model_serializers.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/grape-active_model_serializers/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.authors = ['Jonathan Richard Henry Evans'] 6 | gem.email = ['contact@jrhe.co.uk'] 7 | gem.summary = 'Use active_model_serializer in grape' 8 | gem.description = 'Provides a Formatter for the Grape API DSL to emit objects serialized with active_model_serializers.' 9 | gem.homepage = 'https://github.com/ruby-grape/grape-active_model_serializers' 10 | 11 | gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } 12 | gem.files = `git ls-files`.split("\n") 13 | gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 14 | gem.name = 'grape-active_model_serializers' 15 | gem.require_paths = ['lib'] 16 | gem.version = Grape::ActiveModelSerializers::VERSION 17 | gem.licenses = ['MIT'] 18 | 19 | gem.add_dependency 'grape', '>= 2.3.0' 20 | gem.add_dependency 'active_model_serializers', '>= 0.10.0' 21 | end 22 | -------------------------------------------------------------------------------- /lib/.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format=documentation 3 | -------------------------------------------------------------------------------- /lib/.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 1.9.2 3 | - 1.9.3 4 | - 2.0.0 5 | - 2.1.2 6 | - ruby-head 7 | - rbx-19mode 8 | 9 | env: 10 | - JRUBY_OPTS="--1.9" 11 | -------------------------------------------------------------------------------- /lib/grape-active_model_serializers.rb: -------------------------------------------------------------------------------- 1 | require 'active_model_serializers' 2 | require 'grape' 3 | require 'grape-active_model_serializers/endpoint_extension' 4 | require 'grape-active_model_serializers/error_formatter' 5 | require 'grape-active_model_serializers/formatter' 6 | require 'grape-active_model_serializers/serializer_resolver' 7 | require 'grape-active_model_serializers/options_builder' 8 | require 'grape-active_model_serializers/version' 9 | -------------------------------------------------------------------------------- /lib/grape-active_model_serializers/endpoint_extension.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Make the Grape::Endpoint quack like a ActionController 3 | # 4 | # This allows us to rely on the ActiveModel::Serializer#build_json method 5 | # to lookup the approriate serializer. 6 | # 7 | module Grape 8 | module EndpointExtension 9 | attr_accessor :controller_name 10 | 11 | def namespace_options 12 | if respond_to?(:inheritable_setting) 13 | inheritable_setting.namespace 14 | else 15 | settings[:namespace] ? settings[:namespace].options : {} 16 | end 17 | end 18 | 19 | def route_options 20 | if respond_to?(:inheritable_setting) 21 | inheritable_setting.route 22 | else 23 | options[:route_options] 24 | end 25 | end 26 | 27 | def self.included(base) 28 | mattr_accessor :_serialization_scope 29 | self._serialization_scope = :current_user 30 | 31 | base.class_eval do 32 | def serialization_scope 33 | return unless _serialization_scope 34 | return unless respond_to?(_serialization_scope, true) 35 | 36 | send(_serialization_scope) 37 | end 38 | end 39 | end 40 | 41 | def render(resources, extra_options = {}) 42 | options = extra_options.symbolize_keys 43 | env['ams_meta'] = options.slice( 44 | :meta, :meta_key 45 | ) 46 | env['ams_adapter'] = options.slice( 47 | :adapter, :serializer, :each_serializer, :include, 48 | :fields, :key_transform, :links, :namespace 49 | ) 50 | env['ams_extra'] = options[:extra] 51 | resources 52 | end 53 | 54 | def default_serializer_options; end 55 | 56 | def url_options; end 57 | end 58 | 59 | Endpoint.include EndpointExtension 60 | end 61 | -------------------------------------------------------------------------------- /lib/grape-active_model_serializers/error_formatter.rb: -------------------------------------------------------------------------------- 1 | module Grape 2 | module ErrorFormatter 3 | class ActiveModelSerializers < Base 4 | class << self 5 | def call(message, backtrace, options = {}, env = nil, original_exception = nil) 6 | message = present(message, env) if respond_to?(:present) 7 | message = wrap_message(message) 8 | 9 | rescue_options = options[:rescue_options] || {} 10 | if rescue_options[:backtrace] && backtrace && !backtrace.empty? 11 | message = message.merge(backtrace: backtrace) 12 | end 13 | if rescue_options[:original_exception] && original_exception 14 | message = message 15 | .merge(original_exception: original_exception.inspect) 16 | end 17 | if ::Grape.const_defined? :Json 18 | ::Grape::Json.dump(message) 19 | else 20 | ::MultiJson.dump(message) 21 | end 22 | end 23 | 24 | private 25 | 26 | def wrap_message(message) 27 | if active_model?(message) 28 | ::ActiveModelSerializers::SerializableResource.new( 29 | message, 30 | serializer: ActiveModel::Serializer::ErrorSerializer 31 | ).as_json 32 | elsif ok_to_pass_through?(message) 33 | message 34 | else 35 | { error: message } 36 | end 37 | end 38 | 39 | def active_model?(message) 40 | message.respond_to?(:errors) && 41 | message.errors.is_a?(ActiveModel::Errors) 42 | end 43 | 44 | def ok_to_pass_through?(message) 45 | message.is_a?(Exceptions::ValidationErrors) || 46 | message.is_a?(Hash) 47 | end 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/grape-active_model_serializers/formatter.rb: -------------------------------------------------------------------------------- 1 | module Grape 2 | module Formatter 3 | class ActiveModelSerializers 4 | class << self 5 | def call(resource, env) 6 | options = build_options(resource, env) 7 | serializer = fetch_serializer(resource, options) 8 | 9 | if serializer 10 | ::ActiveModelSerializers::Adapter.create( 11 | serializer, options 12 | ).to_json 13 | else 14 | Grape::Formatter::Json.call(resource, env) 15 | end 16 | end 17 | 18 | def build_options(resource, env) 19 | Grape::ActiveModelSerializers::OptionsBuilder.new( 20 | resource, env 21 | ).options 22 | end 23 | 24 | def fetch_serializer(resource, options) 25 | Grape::ActiveModelSerializers::SerializerResolver.new( 26 | resource, options 27 | ).serializer 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/grape-active_model_serializers/options_builder.rb: -------------------------------------------------------------------------------- 1 | module Grape 2 | module ActiveModelSerializers 3 | class OptionsBuilder 4 | def initialize(resource, env) 5 | self.resource = resource 6 | self.env = env 7 | end 8 | 9 | def options 10 | @options ||= begin 11 | options = endpoint_options 12 | options[:scope] = endpoint unless options.key?(:scope) 13 | options.merge!(default_root_options) unless options.key?(:root) 14 | options.merge!(meta_options) 15 | options.merge!(adapter_options) 16 | options.merge!(extra_options) 17 | options 18 | end 19 | end 20 | 21 | private 22 | 23 | attr_accessor :resource, :env 24 | 25 | def endpoint_options 26 | [ 27 | endpoint.default_serializer_options || {}, 28 | endpoint.namespace_options, 29 | endpoint.route_options, 30 | endpoint.options, 31 | endpoint.options.fetch(:route_options) 32 | ].reduce(:merge) 33 | end 34 | 35 | def endpoint 36 | @endpoint ||= env['api.endpoint'] 37 | end 38 | 39 | # array root is the innermost namespace name ('space') if there is one, 40 | # otherwise the route name (e.g. get 'name') 41 | def default_root_options 42 | return {} unless resource.respond_to?(:to_ary) 43 | 44 | if innermost_scope 45 | { root: innermost_scope.space } 46 | else 47 | { root: endpoint.options[:path].first.to_s.split('/').last } 48 | end 49 | end 50 | 51 | def innermost_scope 52 | if endpoint.respond_to?(:namespace_stackable) 53 | endpoint.namespace_stackable(:namespace).last 54 | else 55 | endpoint.settings.peek[:namespace] 56 | end 57 | end 58 | 59 | def meta_options 60 | options = {} 61 | meta_options = env['ams_meta'] || {} 62 | meta = meta_options[:meta] 63 | meta_key = meta_options[:meta_key] 64 | options[:meta] = meta if meta 65 | options[:meta_key] = meta_key if meta && meta_key 66 | options 67 | end 68 | 69 | def adapter_options 70 | env['ams_adapter'] || {} 71 | end 72 | 73 | def extra_options 74 | options = env['ams_extra'] || {} 75 | return options if options.is_a?(Hash) 76 | 77 | raise 'Extra options must be a hash' 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/grape-active_model_serializers/serializer_resolver.rb: -------------------------------------------------------------------------------- 1 | module Grape 2 | module ActiveModelSerializers 3 | class SerializerResolver 4 | def initialize(resource, options) 5 | self.resource = resource 6 | self.options = options 7 | end 8 | 9 | def serializer 10 | serializer_class&.new(resource, serializer_options) 11 | end 12 | 13 | private 14 | 15 | attr_accessor :resource, :options 16 | 17 | def serializer_class 18 | return @serializer_class if defined?(@serializer_class) 19 | 20 | @serializer_class = resource_defined_class 21 | @serializer_class ||= collection_class 22 | @serializer_class ||= options[:serializer] 23 | @serializer_class ||= namespace_inferred_class 24 | @serializer_class ||= version_inferred_class 25 | @serializer_class ||= resource_serializer_class 26 | end 27 | 28 | def serializer_options 29 | if collection_serializer? && !options.key?(:serializer) 30 | options.merge(each_serializer_option) 31 | else 32 | options 33 | end 34 | end 35 | 36 | def collection_serializer? 37 | serializer_class == ActiveModel::Serializer.config.collection_serializer 38 | end 39 | 40 | def each_serializer_option 41 | serializer_class = options[:each_serializer] 42 | serializer_class ||= namespace_inferred_class 43 | serializer_class ||= version_inferred_class 44 | serializer_class ? { serializer: serializer_class } : {} 45 | end 46 | 47 | def resource_defined_class 48 | resource.serializer_class if resource.respond_to?(:serializer_class) 49 | end 50 | 51 | def collection_class 52 | if resource.respond_to?(:to_ary) || resource.respond_to?(:all) 53 | ActiveModel::Serializer.config.collection_serializer 54 | end 55 | end 56 | 57 | def namespace_inferred_class 58 | return nil unless options.key?(:for) 59 | 60 | namespace = options[:for].to_s.deconstantize 61 | "#{namespace}::#{resource_serializer_klass}".safe_constantize 62 | end 63 | 64 | def version_inferred_class 65 | return nil unless options.key?(:version) 66 | 67 | "#{version}::#{resource_serializer_klass}".safe_constantize 68 | end 69 | 70 | def version 71 | options[:version].try(:classify) 72 | end 73 | 74 | def resource_serializer_klass 75 | @resource_serializer_klass ||= [ 76 | resource_namespace, 77 | "#{resource_klass}Serializer" 78 | ].compact.join('::') 79 | end 80 | 81 | def resource_klass 82 | resource_class.name.demodulize 83 | end 84 | 85 | def resource_namespace 86 | klass = resource_class.name.deconstantize 87 | klass.empty? ? nil : klass 88 | end 89 | 90 | def resource_class 91 | if resource.respond_to?(:klass) 92 | resource.klass 93 | elsif resource.respond_to?(:to_ary) || resource.respond_to?(:all) 94 | resource.first.class 95 | else 96 | resource.class 97 | end 98 | end 99 | 100 | def resource_serializer_class 101 | ActiveModel::Serializer.serializer_for(resource) 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/grape-active_model_serializers/version.rb: -------------------------------------------------------------------------------- 1 | module Grape 2 | module ActiveModelSerializers 3 | VERSION = '2.0.0'.freeze 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/features/grape-active_model_serializers/render_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'securerandom' 3 | 4 | describe '#render' do 5 | let(:app) { Class.new(Grape::API) } 6 | 7 | before do 8 | ActiveModelSerializers.config.adapter = :json 9 | app.format :json 10 | app.formatter :json, Grape::Formatter::ActiveModelSerializers 11 | end 12 | 13 | def get_resource_with(meta) 14 | url = "/#{SecureRandom.hex}" 15 | app.get(url) do 16 | render User.new(first_name: 'Jeff'), meta 17 | end 18 | get url 19 | JSON.parse last_response.body 20 | end 21 | 22 | context 'with meta key' do 23 | it 'includes meta key and content' do 24 | result = get_resource_with(meta: { total: 2 }) 25 | expect(result).to have_key('meta') 26 | expect(result.fetch('meta')).to eq('total' => 2) 27 | end 28 | end 29 | 30 | context 'with a custom meta_key' do 31 | it 'includes the custom meta key name' do 32 | result = get_resource_with(meta: { total: 2 }, meta_key: :custom_key_name) 33 | expect(result).to have_key('custom_key_name') 34 | expect(result.fetch('custom_key_name')).to eq('total' => 2) 35 | end 36 | 37 | it 'ignores a lonely meta_key' do 38 | result = get_resource_with(meta_key: :custom_key_name) 39 | expect(result).not_to have_key('meta') 40 | expect(result).not_to have_key('custom_key_name') 41 | end 42 | end 43 | 44 | context 'junk keys' do 45 | it 'ignores junk keys' do 46 | result = get_resource_with(junk_key: { total: 2 }) 47 | expect(result).not_to have_key('junk_key') 48 | end 49 | 50 | it 'ignores empty meta_key' do 51 | result = get_resource_with(meta: { total: 2 }, meta_key: nil) 52 | expect(result).to have_key('meta') 53 | end 54 | 55 | it 'ignores empty meta' do 56 | result = get_resource_with(meta: nil) 57 | expect(result).not_to have_key('meta') 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/grape-active_model_serializers/endpoint_extension_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Grape::EndpointExtension' do 4 | if Grape::Util.const_defined?('InheritableSetting') 5 | subject do 6 | Grape::Endpoint.new( 7 | Grape::Util::InheritableSetting.new, 8 | path: '/', 9 | method: 'foo' 10 | ) 11 | end 12 | else 13 | subject do 14 | Grape::Endpoint.new({}, path: '/', method: 'foo') 15 | end 16 | end 17 | 18 | let(:serializer) { Grape::Formatter::ActiveModelSerializers } 19 | let(:user) { 20 | Object.new do 21 | def name 22 | 'sven' 23 | end 24 | end 25 | } 26 | let(:users) { [user, user] } 27 | 28 | describe '#render' do 29 | let(:env) { {} } 30 | let(:env_key) { nil } 31 | 32 | before do 33 | allow(subject).to receive(:env).and_return(env) 34 | end 35 | 36 | shared_examples_for 'option capture' do 37 | it 'captures options' do 38 | subject.render(users, options) 39 | expect(env[env_key]).to eq(options) 40 | end 41 | end 42 | 43 | shared_examples_for 'skipped option capture' do 44 | it 'does not capture options' do 45 | subject.render(users, options) 46 | expect(env[env_key]).to eq({}) 47 | end 48 | end 49 | 50 | it { should respond_to(:render) } 51 | 52 | context 'meta options' do 53 | let(:env_key) { 'ams_meta' } 54 | let(:meta_full) { { meta: meta_content } } 55 | 56 | context 'meta' do 57 | let(:options) { { meta: { total: 2 } } } 58 | it_behaves_like 'option capture' 59 | end 60 | 61 | context 'meta_key' do 62 | let(:options) { { meta_key: 'custom_meta' } } 63 | it_behaves_like 'option capture' 64 | end 65 | 66 | context 'unknown option' do 67 | let(:options) { { unknown: 'value' } } 68 | it_behaves_like 'skipped option capture' 69 | end 70 | end 71 | 72 | context 'adapter options' do 73 | let(:options) { {} } 74 | let(:env_key) { 'ams_adapter' } 75 | 76 | context 'adapter' do 77 | let(:options) { { adapter: :json } } 78 | it_behaves_like 'option capture' 79 | end 80 | 81 | context 'include' do 82 | let(:options) { { include: '*' } } 83 | it_behaves_like 'option capture' 84 | end 85 | 86 | context 'fields' do 87 | let(:options) { { fields: [:id] } } 88 | it_behaves_like 'option capture' 89 | end 90 | 91 | context 'key_transform' do 92 | let(:options) { { key_transform: :camel_lower } } 93 | it_behaves_like 'option capture' 94 | end 95 | 96 | context 'links' do 97 | let(:links_object) { 98 | { 99 | href: 'http://example.com/api/posts', 100 | meta: { 101 | count: 10 102 | } 103 | } 104 | } 105 | let(:options) { { links: links_object } } 106 | it_behaves_like 'option capture' 107 | end 108 | 109 | context 'namespace' do 110 | let(:options) { { namespace: V4 } } 111 | it_behaves_like 'option capture' 112 | end 113 | 114 | context 'unknown option' do 115 | let(:options) { { unknown: 'value' } } 116 | it_behaves_like 'skipped option capture' 117 | end 118 | end 119 | 120 | context 'extra options' do 121 | let(:env_key) { 'ams_extra' } 122 | 123 | context 'namespace' do 124 | let(:options) { { extra: { option: 'value' } } } 125 | 126 | it 'captures options' do 127 | subject.render(users, options) 128 | expect(env[env_key]).to eq(options[:extra]) 129 | end 130 | end 131 | 132 | context 'unknown option' do 133 | let(:options) { { unknown: 'value' } } 134 | 135 | it 'does not capture options' do 136 | subject.render(users, options) 137 | expect(env[env_key]).to eq(nil) 138 | end 139 | end 140 | end 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /spec/grape-active_model_serializers/error_formatter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'grape-active_model_serializers/error_formatter' 3 | 4 | describe Grape::ErrorFormatter::ActiveModelSerializers do 5 | subject { Grape::ErrorFormatter::ActiveModelSerializers } 6 | let(:backtrace) { ['Line:1'] } 7 | let(:options) { {} } 8 | let(:env) { { 'api.endpoint' => app.endpoints.first } } 9 | let(:original_exception) { StandardError.new('oh noes!') } 10 | 11 | let(:app) { 12 | Class.new(Grape::API) do |app| 13 | app.format :json 14 | app.formatter :jsonapi, Grape::Formatter::ActiveModelSerializers 15 | app.error_formatter :jsonapi, Grape::ErrorFormatter::ActiveModelSerializers 16 | 17 | app.namespace('space') do |ns| 18 | ns.get('/', root: false) do 19 | error!(message) 20 | end 21 | end 22 | end 23 | } 24 | let(:foo) { 25 | Class.new { 26 | include ActiveModel::Model 27 | 28 | attr_accessor :name 29 | 30 | def initialize(attributes = {}) 31 | super 32 | errors.add(:name, 'We don\'t like bears') 33 | end 34 | } 35 | } 36 | 37 | before do 38 | ActiveModel::Serializer.config.adapter = :json_api 39 | end 40 | 41 | after do 42 | ActiveModel::Serializer.config.adapter = :json 43 | end 44 | 45 | describe '#call' do 46 | context 'message is an activemodel' do 47 | let(:message) { 48 | foo.new(name: 'bar') 49 | } 50 | it 'formats the error' do 51 | result = subject 52 | .call(message, backtrace, options, env, original_exception) 53 | json_hash = JSON.parse(result) 54 | 55 | expected_result = { 56 | 'errors' => [ 57 | { 58 | 'source' => { 59 | 'pointer' => '/data/attributes/name' 60 | }, 61 | 'detail' => 'We don\'t like bears' 62 | } 63 | ] 64 | } 65 | 66 | expect(json_hash == expected_result).to eq(true) 67 | end 68 | end 69 | 70 | context 'message is hash like' do 71 | let(:message) { { 'errors' => ['error'] } } 72 | it 'passes the message through' do 73 | result = subject 74 | .call(message, backtrace, options, env, original_exception) 75 | json_hash = JSON.parse(result) 76 | 77 | expect(json_hash == message).to eq(true) 78 | end 79 | end 80 | 81 | context 'message is text' do 82 | let(:message) { 'error' } 83 | it 'wraps the error' do 84 | result = subject 85 | .call(message, backtrace, options, env, original_exception) 86 | json_hash = JSON.parse(result) 87 | 88 | expect(json_hash == { 'error' => message }).to eq(true) 89 | end 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /spec/grape-active_model_serializers/formatter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'grape-active_model_serializers/formatter' 3 | 4 | describe Grape::Formatter::ActiveModelSerializers do 5 | subject { Grape::Formatter::ActiveModelSerializers } 6 | 7 | describe 'serializer options from namespace' do 8 | let(:app) { Class.new(Grape::API) } 9 | 10 | before do 11 | app.format :json 12 | app.formatter :json, Grape::Formatter::ActiveModelSerializers 13 | 14 | app.namespace('space') do |ns| 15 | ns.get('/', root: false) do 16 | { user: { first_name: 'JR', last_name: 'HE' } } 17 | end 18 | end 19 | end 20 | 21 | let(:env) { { 'api.endpoint' => app.endpoints.first } } 22 | let(:options) { described_class.build_options(nil, env) } 23 | 24 | it 'should read serializer options like "root"' do 25 | expect(options).to include(:root) 26 | end 27 | end 28 | 29 | describe '.fetch_serializer' do 30 | let(:user) { User.new(first_name: 'John') } 31 | 32 | let(:params) { { path: '/', method: 'foo', root: false } } 33 | if Grape::Util.const_defined?('InheritableSetting') 34 | let(:setting) { Grape::Util::InheritableSetting.new } 35 | else 36 | let(:setting) { {} } 37 | end 38 | let(:endpoint) { Grape::Endpoint.new(setting, params) } 39 | 40 | let(:env) { { 'api.endpoint' => endpoint } } 41 | 42 | before do 43 | def endpoint.current_user 44 | @current_user ||= User.new(first_name: 'Current user') 45 | end 46 | 47 | def endpoint.default_serializer_options 48 | { only: :only, except: :except } 49 | end 50 | end 51 | 52 | let(:options) { described_class.build_options(user, env) } 53 | subject { described_class.fetch_serializer(user, options) } 54 | 55 | let(:instance_options) { subject.instance_variable_get(:@instance_options) } 56 | 57 | it { should be_a UserSerializer } 58 | 59 | it 'should have correct scope set' do 60 | expect(subject.scope.current_user).to eq(endpoint.current_user) 61 | end 62 | 63 | it 'should read default serializer options' do 64 | expect(instance_options[:only]).to eq(:only) 65 | expect(instance_options[:except]).to eq(:except) 66 | end 67 | 68 | it 'should read serializer options like "root"' do 69 | expect(options).to include(:root) 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /spec/grape-active_model_serializers/versioned_api_formatter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'grape-active_model_serializers/formatter' 3 | 4 | describe Grape::Formatter::ActiveModelSerializers do 5 | describe 'with a versioned API' do 6 | subject { Grape::Formatter::ActiveModelSerializers } 7 | 8 | describe 'serializer options from namespace' do 9 | let(:app) { Class.new(Grape::API) } 10 | 11 | before do 12 | app.format :json 13 | app.formatter :json, Grape::Formatter::ActiveModelSerializers 14 | app.version 'v1', using: :param 15 | 16 | app.namespace('space') do |ns| 17 | ns.get('/', root: false, apiver: 'v1') do 18 | { 19 | user: { 20 | first_name: 'JR', 21 | last_name: 'HE', 22 | email: 'jrhe@github.com' 23 | } 24 | } 25 | end 26 | end 27 | end 28 | 29 | let(:env) { { 'api.endpoint' => app.endpoints.first } } 30 | let(:options) { described_class.build_options(nil, env) } 31 | 32 | it 'should read serializer options like "root"' do 33 | expect(options).to include(:root) 34 | end 35 | end 36 | 37 | describe '.fetch_serializer' do 38 | let(:user) { User.new(first_name: 'John', email: 'j.doe@internet.com') } 39 | 40 | let(:params) { { path: '/', method: 'foo', version: 'v1', root: false } } 41 | if Grape::Util.const_defined?('InheritableSetting') 42 | let(:setting) { Grape::Util::InheritableSetting.new } 43 | else 44 | let(:setting) { {} } 45 | end 46 | let(:endpoint) { Grape::Endpoint.new(setting, params) } 47 | let(:env) { { 'api.endpoint' => endpoint } } 48 | 49 | before do 50 | def endpoint.current_user 51 | @current_user ||= User.new(first_name: 'Current user') 52 | end 53 | 54 | def endpoint.default_serializer_options 55 | { only: :only, except: :except } 56 | end 57 | end 58 | 59 | let(:options) { described_class.build_options(user, env) } 60 | subject { described_class.fetch_serializer(user, options) } 61 | 62 | let(:instance_options) { subject.send(:instance_options) } 63 | 64 | it { should be_a V1::UserSerializer } 65 | 66 | it 'should have correct scope set' do 67 | expect(subject.scope.current_user).to eq(endpoint.current_user) 68 | end 69 | 70 | it 'should read default serializer options' do 71 | expect(instance_options[:only]).to eq(:only) 72 | expect(instance_options[:except]).to eq(:except) 73 | end 74 | 75 | it 'should read serializer options like "root"' do 76 | expect(options).to include(:root) 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/grape/active_model_serializers/options_builder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Grape::ActiveModelSerializers::OptionsBuilder do 4 | let(:resolver) { described_class.new(resource, env) } 5 | let(:resource) { User.new } 6 | let(:env) { { 'api.endpoint' => UsersApi.endpoints.first } } 7 | 8 | context '#options' do 9 | let(:options) { resolver.options } 10 | 11 | context 'meta options' do 12 | let(:env) { super().merge('ams_meta' => meta_options) } 13 | let(:meta_options) { 14 | { 15 | meta: meta, 16 | meta_key: meta_key 17 | } 18 | } 19 | let(:meta) { { sample: 'metadata' } } 20 | let(:meta_key) { 'specified_key' } 21 | 22 | context 'meta option set' do 23 | context 'meta_key set' do 24 | it 'includes meta' do 25 | expect(options[:meta]).to eq(meta) 26 | end 27 | 28 | it 'includes meta_key' do 29 | expect(options[:meta_key]).to eq(meta_key) 30 | end 31 | end 32 | 33 | context 'meta_key unset' do 34 | let(:meta_key) { nil } 35 | 36 | it 'includes meta' do 37 | expect(options[:meta]).to eq(meta) 38 | end 39 | 40 | it 'does not include meta_key' do 41 | expect(options.keys).not_to include(:meta_key) 42 | end 43 | end 44 | end 45 | 46 | context 'meta option unset' do 47 | let(:meta) { nil } 48 | 49 | context 'meta_key set' do 50 | it 'does not include meta' do 51 | expect(options.keys).not_to include(:meta) 52 | end 53 | 54 | it 'does not include meta_key' do 55 | expect(options.keys).not_to include(:meta_key) 56 | end 57 | end 58 | 59 | context 'meta_key unset' do 60 | let(:meta_key) { nil } 61 | 62 | it 'does not include meta' do 63 | expect(options.keys).not_to include(:meta) 64 | end 65 | 66 | it 'does not include meta_key' do 67 | expect(options.keys).not_to include(:meta_key) 68 | end 69 | end 70 | end 71 | end 72 | 73 | context 'adapter options' do 74 | let(:env) { super().merge('ams_adapter' => adapter_options) } 75 | let(:adapter_options) { {} } 76 | 77 | context 'adapter' do 78 | let(:adapter_options) { { adapter: adapter } } 79 | let(:adapter) { :attributes } 80 | 81 | it 'includes adapter as top-level option' do 82 | expect(options[:adapter]).to eq(adapter) 83 | end 84 | end 85 | 86 | context 'serializer' do 87 | let(:adapter_options) { { serializer: serializer } } 88 | let(:serializer) { V2::UserSerializer } 89 | 90 | it 'includes serializer as top-level option' do 91 | expect(options[:serializer]).to eq(serializer) 92 | end 93 | end 94 | 95 | context 'each_serializer' do 96 | let(:adapter_options) { { each_serializer: each_serializer } } 97 | let(:each_serializer) { V2::UserSerializer } 98 | 99 | it 'includes each_serializer as top-level option' do 100 | expect(options[:each_serializer]).to eq(each_serializer) 101 | end 102 | end 103 | end 104 | 105 | context 'extra options' do 106 | let(:env) { super().merge('ams_extra' => extra_options) } 107 | 108 | context 'hash' do 109 | let(:extra_options) { { extra: 'info' } } 110 | 111 | it 'includes extra options in top-level options' do 112 | expect(options.keys).to include(:extra) 113 | end 114 | end 115 | 116 | context 'not a hash' do 117 | let(:extra_options) { 'extra' } 118 | 119 | it 'raises an exception' do 120 | expect { 121 | options 122 | }.to raise_exception( 123 | 'Extra options must be a hash' 124 | ) 125 | end 126 | end 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /spec/grape/active_model_serializers/serializer_resolver_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # asserts serializer resolution order: 4 | # 1. resource_defined_class # V1::UserSerializer 5 | # 2. collection_class # CollectionSerializer, V2::UserSerializer 6 | # 3. options[:serializer] # V3::UserSerializer 7 | # 4. namespace_inferred_class # V4::UserSerializer 8 | # 5. version_inferred_class # V5::UserSerializer 9 | # 6. resource_serializer_class # UserSerializer 10 | # 7. missing resource # nil 11 | 12 | describe Grape::ActiveModelSerializers::SerializerResolver do 13 | let(:resolver) { described_class.new(resource, options) } 14 | let(:resource) { User.new } 15 | let(:options) { 16 | { 17 | serializer: options_serializer_class, # options defined 18 | for: V4::UsersApi, # namespace inference 19 | version: 'v5' # version inference 20 | } 21 | } 22 | # resource defined 23 | let(:resource_defined?) { true } 24 | let(:defined_serializer_class) { V1::UserSerializer } 25 | # options defined 26 | let(:options_serializer_class) { V3::UserSerializer } 27 | 28 | let(:serializer) { resolver.serializer } 29 | 30 | before do 31 | if resource_defined? 32 | allow(resource).to receive(:respond_to?).and_call_original 33 | allow(resource).to receive(:respond_to?).with(:to_ary) { true } 34 | allow(resource).to receive(:serializer_class) { defined_serializer_class } 35 | end 36 | end 37 | 38 | context 'resource defined' do 39 | it 'returns serializer' do 40 | expect(serializer).to be_kind_of(defined_serializer_class) 41 | end 42 | end 43 | 44 | context 'not resource defined' do 45 | let(:resource_defined?) { false } 46 | 47 | context 'resource collection' do 48 | let(:resource) { [User.new] } 49 | let(:serializer_class) { ActiveModel::Serializer::CollectionSerializer } 50 | 51 | it 'returns serializer' do 52 | expect(serializer).to be_kind_of(serializer_class) 53 | end 54 | 55 | context 'each serializer' do 56 | let(:options) { 57 | super().except(:serializer).merge( 58 | each_serializer: V2::UserSerializer 59 | ) 60 | } 61 | let(:each_serializer) { serializer.send(:options)[:serializer] } 62 | 63 | context 'each_serializer option' do 64 | it 'returns expected serializer' do 65 | expect(each_serializer).to eq(V2::UserSerializer) 66 | end 67 | end 68 | 69 | context 'no each_serializer option' do 70 | let(:options) { super().except(:each_serializer) } 71 | 72 | context 'namespace inferred' do 73 | it 'returns expected serializer' do 74 | expect(each_serializer).to eq(V4::UserSerializer) 75 | end 76 | end 77 | 78 | context 'not namespace inferred' do 79 | let(:options) { super().except(:for) } 80 | 81 | context 'version inferred' do 82 | it 'returns expected serializer' do 83 | expect(each_serializer).to eq(V5::UserSerializer) 84 | end 85 | end 86 | 87 | context 'not version inferred' do 88 | let(:options) { super().except(:version) } 89 | 90 | it 'returns nil' do 91 | expect(each_serializer).to eq(nil) 92 | end 93 | end 94 | end 95 | end 96 | end 97 | end 98 | 99 | context 'not resource collection' do 100 | context 'specified by options' do 101 | it 'returns specified serializer' do 102 | expect(serializer).to be_kind_of(V3::UserSerializer) 103 | end 104 | end 105 | 106 | context 'not specified by options' do 107 | let(:options) { super().except(:serializer) } 108 | 109 | context 'namespace inferred' do 110 | it 'returns inferred serializer' do 111 | expect(serializer).to be_kind_of(V4::UserSerializer) 112 | end 113 | end 114 | 115 | context 'not namespace inferred' do 116 | let(:options) { super().except(:for) } 117 | 118 | context 'version inferred' do 119 | it 'returns inferred serializer' do 120 | expect(serializer).to be_kind_of(V5::UserSerializer) 121 | end 122 | end 123 | 124 | context 'not version inferred' do 125 | let(:options) { super().except(:version) } 126 | 127 | context 'ASM resolved' do 128 | it 'returns serializer' do 129 | expect(serializer).to be_kind_of(UserSerializer) 130 | end 131 | end 132 | 133 | context 'not ASM resolved' do 134 | let(:resource) { nil } 135 | 136 | it 'returns nil' do 137 | expect(serializer).to eq(nil) 138 | end 139 | end 140 | end 141 | end 142 | end 143 | end 144 | end 145 | end 146 | -------------------------------------------------------------------------------- /spec/integration/sequel_spec.rb: -------------------------------------------------------------------------------- 1 | require 'sequel' 2 | require 'spec_helper' 3 | 4 | describe 'Sequel Integration' do 5 | before do 6 | DB = Sequel.sqlite unless defined?(DB) 7 | DB.create_table(:users) do 8 | primary_key :id 9 | String :name 10 | end 11 | ActiveModelSerializers.config.adapter = :json 12 | app.format :json 13 | app.formatter :json, Grape::Formatter::ActiveModelSerializers 14 | end 15 | 16 | after do 17 | DB.drop_table(:users) 18 | Object.send(:remove_const, :SequelUser) 19 | Object.send(:remove_const, :SequelUserSerializer) 20 | end 21 | 22 | let!(:model) { 23 | SequelUser = Class.new(Sequel::Model(:users)) do 24 | include ActiveModel::Serialization 25 | def self.model_name 26 | 'User' 27 | end 28 | end 29 | } 30 | let!(:serializer) { 31 | SequelUserSerializer = Class.new(ActiveModel::Serializer) do 32 | attributes :id, :name 33 | end 34 | } 35 | let(:app) { Class.new(Grape::API) } 36 | 37 | context 'collection' do 38 | let!(:users) { 39 | [ 40 | model.create(name: 'one'), 41 | model.create(name: 'two') 42 | ] 43 | } 44 | 45 | it 'renders' do 46 | app.get('/users') { render SequelUser.dataset } 47 | response = get '/users' 48 | expect(JSON.parse(response.body)).to eq( 49 | 'users' => [ 50 | { 'id' => 1, 'name' => 'one' }, 51 | { 'id' => 2, 'name' => 'two' } 52 | ] 53 | ) 54 | end 55 | end 56 | 57 | context 'member' do 58 | let!(:user) { model.create(name: 'name') } 59 | 60 | it 'renders' do 61 | app.get('/user/1') { render SequelUser.first } 62 | response = get '/user/1' 63 | expect(JSON.parse(response.body)).to eq( 64 | 'user' => { 'id' => 1, 'name' => 'name' } 65 | ) 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/old_grape_ams_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'support/models/user' 3 | require 'support/models/blog_post' 4 | require 'support/serializers/user_serializer' 5 | require 'support/serializers/blog_post_serializer' 6 | require 'grape-active_model_serializers' 7 | 8 | describe Grape::ActiveModelSerializers do 9 | let(:app) { Class.new(Grape::API) } 10 | subject { last_response.body } 11 | 12 | before do 13 | ActiveModelSerializers.config.adapter = :json 14 | app.format :json 15 | app.formatter :json, Grape::Formatter::ActiveModelSerializers 16 | end 17 | 18 | it 'should respond with proper content-type' do 19 | app.get('/home/users', serializer: UserSerializer) do 20 | User.new 21 | end 22 | get('/home/users') 23 | expect(last_response.headers['Content-Type']).to eql 'application/json' 24 | end 25 | 26 | context 'serializer is set to nil' do 27 | before do 28 | app.get('/home', serializer: nil) do 29 | { user: { first_name: 'JR', last_name: 'HE' } } 30 | end 31 | end 32 | it 'uses the built in grape serializer' do 33 | get('/home') 34 | expect(subject).to eq( 35 | '{"user":{"first_name":"JR","last_name":"HE"}}' 36 | ) 37 | end 38 | end 39 | 40 | context "serializer isn't set" do 41 | before do 42 | app.get('/home') do 43 | User.new( 44 | first_name: 'JR', 45 | last_name: 'HE', 46 | email: 'contact@jrhe.co.uk' 47 | ) 48 | end 49 | end 50 | 51 | it 'infers the serializer' do 52 | get '/home' 53 | expect(subject).to eq( 54 | '{"user":{"first_name":"JR","last_name":"HE"}}' 55 | ) 56 | end 57 | end 58 | 59 | it 'serializes arrays of objects' do 60 | app.get('/users') do 61 | user = User.new( 62 | first_name: 'JR', 63 | last_name: 'HE', 64 | email: 'contact@jrhe.co.uk' 65 | ) 66 | [user, user] 67 | end 68 | 69 | get '/users' 70 | expect(subject).to eq( 71 | '{"users":[' \ 72 | '{"first_name":"JR","last_name":"HE"},' \ 73 | '{"first_name":"JR","last_name":"HE"}' \ 74 | ']}' 75 | ) 76 | end 77 | 78 | context 'models with compound names' do 79 | it "generates the proper 'root' node for individual objects" do 80 | app.get('/home') do 81 | BlogPost.new(title: 'Grape AM::S Rocks!', body: 'Really, it does.') 82 | end 83 | 84 | get '/home' 85 | expect(subject).to eq( 86 | '{"blog_post":' \ 87 | '{"title":"Grape AM::S Rocks!","body":"Really, it does."}' \ 88 | '}' 89 | ) 90 | end 91 | 92 | it "generates the proper 'root' node for serialized arrays" do 93 | app.get('/blog_posts') do 94 | blog_post = BlogPost.new( 95 | title: 'Grape AM::S Rocks!', 96 | body: 'Really, it does.' 97 | ) 98 | [blog_post, blog_post] 99 | end 100 | 101 | get '/blog_posts' 102 | expect(subject).to eq( 103 | '{"blog_posts":[' \ 104 | '{"title":"Grape AM::S Rocks!","body":"Really, it does."},' \ 105 | '{"title":"Grape AM::S Rocks!","body":"Really, it does."}' \ 106 | ']}' 107 | ) 108 | end 109 | end 110 | 111 | it 'uses namespace options when provided' do 112 | app.namespace :admin, serializer: UserSerializer do 113 | get('/jeff') do 114 | User.new(first_name: 'Jeff') 115 | end 116 | end 117 | 118 | get '/admin/jeff' 119 | expect(subject).to eq( 120 | '{"user":{"first_name":"Jeff","last_name":null}}' 121 | ) 122 | end 123 | 124 | context 'route is in a namespace' do 125 | it 'uses the name of the closest namespace for the root' do 126 | app.namespace :admin do 127 | get('/jeff') do 128 | user = User.new(first_name: 'Jeff') 129 | [user, user] 130 | end 131 | end 132 | 133 | get '/admin/jeff' 134 | expect(subject).to eq( 135 | '{"admin":[' \ 136 | '{"first_name":"Jeff","last_name":null},' \ 137 | '{"first_name":"Jeff","last_name":null}' \ 138 | ']}' 139 | ) 140 | end 141 | end 142 | 143 | context 'route is not in a namespace' do 144 | it 'uses the of the route for the root' do 145 | app.get('/people') do 146 | user = User.new(first_name: 'Jeff') 147 | [user, user] 148 | end 149 | 150 | get '/people' 151 | expect(subject).to eq( 152 | '{"people":[' \ 153 | '{"first_name":"Jeff","last_name":null},' \ 154 | '{"first_name":"Jeff","last_name":null}' \ 155 | ']}' 156 | ) 157 | end 158 | end 159 | end 160 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 3 | 4 | require 'bundler' 5 | Bundler.setup :default, :test 6 | 7 | require 'active_model_serializers' 8 | require 'active_support/core_ext/hash/conversions' 9 | require 'active_support/json' 10 | require 'rspec' 11 | require 'rack/test' 12 | require 'grape-active_model_serializers' 13 | 14 | RSpec.configure do |config| 15 | config.include Rack::Test::Methods 16 | end 17 | 18 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each { |f| require f } 19 | -------------------------------------------------------------------------------- /spec/support/api/users_api.rb: -------------------------------------------------------------------------------- 1 | class UsersApi < Grape::API 2 | resource :users do 3 | desc 'all users' 4 | get do 5 | [User.new] 6 | end 7 | 8 | desc 'specified user' 9 | get '/:id' do 10 | User.new 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/support/api/v4/users_api.rb: -------------------------------------------------------------------------------- 1 | module V4 2 | class UsersApi < Grape::API 3 | resource :users do 4 | desc 'all users' 5 | get do 6 | [User.new] 7 | end 8 | 9 | desc 'specified user' 10 | get '/:id' do 11 | User.new 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/support/models/blog_post.rb: -------------------------------------------------------------------------------- 1 | class BlogPost 2 | include ActiveModel::Serialization 3 | 4 | attr_accessor :title, :body 5 | 6 | def self.model_name 7 | to_s 8 | end 9 | 10 | def initialize(params = {}) 11 | params.each do |k, v| 12 | instance_variable_set("@#{k}", v) unless v.nil? 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/support/models/user.rb: -------------------------------------------------------------------------------- 1 | class User 2 | include ActiveModel::Serialization 3 | 4 | attr_accessor :first_name, :last_name, :password, :email 5 | 6 | def self.model_name 7 | to_s 8 | end 9 | 10 | def initialize(params = {}) 11 | params.each do |k, v| 12 | instance_variable_set("@#{k}", v) unless v.nil? 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/support/serializers/blog_post_serializer.rb: -------------------------------------------------------------------------------- 1 | class BlogPostSerializer < ActiveModel::Serializer 2 | attributes :title, :body 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/serializers/user_serializer.rb: -------------------------------------------------------------------------------- 1 | class UserSerializer < ActiveModel::Serializer 2 | attributes :first_name, :last_name 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/serializers/v1/user_serializer.rb: -------------------------------------------------------------------------------- 1 | module V1 2 | class UserSerializer < ActiveModel::Serializer 3 | attributes :first_name, :last_name, :email 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/serializers/v2/user_serializer.rb: -------------------------------------------------------------------------------- 1 | module V2 2 | class UserSerializer < ActiveModel::Serializer 3 | attributes :first_name, :last_name, :email 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/serializers/v3/user_serializer.rb: -------------------------------------------------------------------------------- 1 | module V3 2 | class UserSerializer < ActiveModel::Serializer 3 | attributes :first_name, :last_name, :email 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/serializers/v4/user_serializer.rb: -------------------------------------------------------------------------------- 1 | module V4 2 | class UserSerializer < ActiveModel::Serializer 3 | attributes :first_name, :last_name, :email 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/serializers/v5/user_serializer.rb: -------------------------------------------------------------------------------- 1 | module V5 2 | class UserSerializer < ActiveModel::Serializer 3 | attributes :first_name, :last_name, :email 4 | end 5 | end 6 | --------------------------------------------------------------------------------