Compare commits

..

66 commits

Author SHA1 Message Date
c985c86410
Merge pull request #103 from samsonjs/ruby-4
Add Ruby 4.0.0 to test matrix
2026-01-02 11:52:32 -08:00
55f3861ae3
Set default Grape version to ~> 3.0.0 in Gemfile 2026-01-02 11:50:51 -08:00
af29f9c76e
Add Ruby 4.0.0 to test matrix 2026-01-02 11:50:39 -08:00
7ca250a305
Preparing for next development iteration, 2.0.2. 2025-12-08 13:42:40 -08:00
61673fe322
Preparing for release, 2.0.1. 2025-12-08 13:40:57 -08:00
26767f5fac
Merge pull request #100 from samsonjs/fix/99-grape-3-support
Add support for Grape 3.0 and test in CI
2025-12-08 13:36:32 -08:00
6f1e4037bc
Add support for Grape 3.0 and test in CI 2025-12-08 13:31:38 -08:00
Mateus Nava
cce31fe187
fix: Accept nil serializer (#92)
* fix: Accept nil serializer

* Obey the law

---------

Co-authored-by: Mateus Nava <mateus.nava@corp.globo.com>
Co-authored-by: Sami Samhuri <sami@samhuri.net>
2025-06-08 09:21:41 -04:00
7f759b6560
Preparing for next development iteration, 2.0.1. 2025-06-02 19:09:00 -07:00
1a1c8b8173
Preparing for release, 2.0.0. 2025-06-02 19:04:32 -07:00
9941241efc
Update RELEASING.md with correct changelog format 2025-06-02 19:04:13 -07:00
Daniel (dB.) Doubrovkine
6ca5a7c5cf
Merge pull request #98 from samsonjs/ci-github-actions
Switch to GitHub Actions for CI and update dependencies
2025-05-26 07:13:10 -04:00
b609ebc639
Use GitHub Actions for build status badge in Readme 2025-05-25 14:38:50 -07:00
dcc17a065d
PR feedback: fix Danger's GitHub token, run on all branches 2025-05-25 14:34:16 -07:00
0da8ef5bd3
Split up workflow into 2 jobs 2025-05-24 16:12:34 -07:00
0a11b5d101
Out with Travis CI, in with GitHub Actions
* Update dependencies and appease the robot overlords, rubocop and
  danger.
2025-05-24 15:59:00 -07:00
096fd41c11
Bump version to 2.0 and update changelog 2025-04-23 15:27:00 -07:00
7e58753b76
Fix compatibility with Grape 2.3 2025-04-23 15:23:37 -07:00
Daniel Doubrovkine (dB.) @dblockdotorg
eb200812c8
Merge pull request #89 from olleolleolle/patch-1
README: Remove defunct badge
2019-01-28 15:48:16 -05:00
Olle Jonsson
a8ecfa3daf
README: Remove defunct badge 2019-01-27 23:47:20 +01:00
Daniel Doubrovkine (dB.) @dblockdotorg
7e9c064f88
Merge pull request #87 from dblock/rubocop-0.53.0
Upgraded RuboCop to 0.53.0.
2018-03-14 15:36:56 -04:00
dblock
791c1b0f5e Moved development gems out of .gemspec. 2018-03-14 14:59:50 -04:00
dblock
7670d9aed7 Upgraded RuboCop to 0.53.0. 2018-03-14 14:58:08 -04:00
Daniel Doubrovkine (dB.) @dblockdotorg
1995ba4676
Merge pull request #86 from dblock/fix-bigger-equal
Fix: markdown for >=.
2018-03-14 14:56:39 -04:00
dblock
fcb4c3a8c5 Fix: markdown for >=. 2018-03-14 14:56:04 -04:00
dblock
27ad60099e Fix ruby-grape links. [ci skip] 2018-03-14 14:51:53 -04:00
dblock
003e69d2bb Preparing for next development iteration, 1.5.3. Closes #84. 2018-03-14 14:50:16 -04:00
dblock
dc01b70f26 Preparing for release, 1.5.2. 2018-03-14 14:49:18 -04:00
dblock
03af2a385a Added a RELEASING doc. 2018-03-14 14:48:54 -04:00
Daniel Doubrovkine (dB.) @dblockdotorg
cea5cf9d3e
Merge pull request #85 from nicolasleger/patch-1
[CI] Test against Ruby 2.4 and 2.5
2018-03-14 14:42:55 -04:00
Nicolas Leger
c5d7a8b071
[CI] Test against Ruby 2.4 and 2.5 2018-03-14 09:15:08 +01:00
Daniel Doubrovkine (dB.) @dblockdotorg
4b7a08fecb Merge pull request #80 from xn/add_documentation_for_error_formatter
add documentation for error formatter
2017-10-22 17:55:10 -04:00
xn
8b8da16ffe add documentation 2017-10-22 13:36:18 -05:00
Christian Trosclair
b2654190c1 add custom error formatter (#76) 2017-10-20 10:26:32 -04:00
Darren Cheng
fab476772e Prep for v1.5.1 release. 2017-04-25 19:19:50 -07:00
Darren Cheng
6566fb517f Merge pull request #73 from ruby-grape/pass-ams-options
Ensure all AMS options are passed through.
2017-04-25 19:13:21 -07:00
Darren Cheng
1b09c69174 Ensure all AMS options are passed through.
See the following for details:
* https://github.com/rails-api/active_model_serializers/blob/master/docs/general/rendering.md
* https://github.com/rails-api/active_model_serializers/blob/master/docs/general/adapters.md
2017-04-25 19:13:03 -07:00
Darren Cheng
da18531590 Merge pull request #74 from ruby-grape/support-sequel
Ensure support for Sequel.
2017-04-25 19:10:06 -07:00
Darren Cheng
eb28eb2636 Add Sequel integration spec. 2017-04-24 17:02:11 -07:00
Darren Cheng
ed5d5b4ca4 Ensure support for Sequel models and datasets. 2017-04-22 17:10:24 -07:00
Daniel Doubrovkine (dB.) @dblockdotorg
2748d5ba2b Merge pull request #63 from ruby-grape/adapter-options
Pass adapter options through calls to render.
2016-09-09 14:20:14 -04:00
Darren Cheng
0963774ecc Whitelist adapter options and support extra options. 2016-09-09 10:54:33 -07:00
Darren Cheng
4315930a55 Pass adapter options through calls to render. 2016-09-07 00:06:25 -07:00
Daniel Doubrovkine (dB.) @dblockdotorg
f304c8e487 Merge pull request #65 from dblock/danger
Added Danger, PR linter.
2016-09-05 10:46:59 -04:00
dblock
c83394cb20 Added Danger, PR linter. 2016-09-05 10:36:10 -04:00
Darren Cheng
bc7a0b0420 Merge pull request #62 from drn/prep-v1.5.0
Prep v1.5.0
2016-08-24 10:49:04 -07:00
Darren Cheng
22ea34d159 Update gem version to v1.5.0. 2016-08-24 10:47:59 -07:00
Darren Cheng
12bff3bcf2 Prep CHANGELOG for v1.5.0 release. 2016-08-24 10:47:59 -07:00
Darren Cheng
5ae81261ab Update UPGRADING document for v1.5.0 release. 2016-08-22 19:06:11 -07:00
Daniel Doubrovkine (dB.) @dblockdotorg
5360021287 Added gem version badge. [ci skip] 2016-07-26 13:39:08 -04:00
Daniel Doubrovkine (dB.) @dblockdotorg
eda39c5833 Merge pull request #61 from drn/collection-serializer-support
Implement support for collection serializers.
2016-07-26 13:36:53 -04:00
Darren Cheng
6bb205731a Implement support for collection serializers. 2016-07-26 10:21:34 -07:00
Darren Cheng
2a4be72387 Lock rubocop version. 2016-07-24 15:17:59 -07:00
Daniel Doubrovkine (dB.) @dblockdotorg
eb43f68af8 Merge pull request #60 from drn/namespace-inference
Namespace inferred serializer resolution.
2016-07-23 23:02:33 -04:00
Darren Cheng
629afb9887 Namespace inferred serializer resolution. 2016-07-22 15:18:04 -07:00
Daniel Doubrovkine (dB.) @dblockdotorg
f76ec6dcff Merge pull request #59 from drn/refactor
Refactor option and serializer resolution.
2016-07-22 15:39:47 -04:00
Darren Cheng
ac9466192d Refactor option and serializer resolution. 2016-07-21 16:03:46 -07:00
Daniel Doubrovkine (dB.) @dblockdotorg
37786d6a38 Merge pull request #57 from drn/line-length-linter
Solve line length linter issues.
2016-07-15 08:53:58 -04:00
Darren Cheng
1dd73e9cd7 Solve line length linter issues. 2016-07-14 20:09:29 -07:00
Darren Cheng
a48d57c2ba Update additional references from jrhe to ruby-grape. 2016-07-14 19:36:27 -07:00
Darren Cheng
fb34024e04 Merge pull request #54 from Thanx/apply_adapter_config_value
Update codebase to support AMS v0.10.x.
2016-07-14 19:28:17 -07:00
Darren Cheng
8f1687b7f6 Ensure grape version is >= v0.8.0. 2016-07-14 19:25:59 -07:00
Darren Cheng
d2a5bb4c4e Add dependency notes to README. 2016-07-14 19:25:58 -07:00
Darren Cheng
62faac16c3 Drop Ruby v2.1 support. 2016-07-14 19:25:58 -07:00
Darren Cheng
35abadd8f7 Upgrade codebase to support latest AMS version. 2016-07-14 19:25:58 -07:00
Naoki Kobayashi
78229c9dea Use ActiveModelSerializers::Adapter 2016-07-14 19:17:43 -07:00
38 changed files with 1450 additions and 206 deletions

38
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,38 @@
name: CI
on: [push, pull_request]
jobs:
danger:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: 4.0.0
- name: Install dependencies
run: bundle install --jobs 4 --retry 3
- name: Run Danger
run: |
# the token is public, has public_repo scope and belongs to the grape-bot user owned by @dblock, this is ok
TOKEN=$(echo -n Z2hwX2lYb0dPNXNyejYzOFJyaTV3QUxUdkNiS1dtblFwZTFuRXpmMwo= | base64 --decode)
DANGER_GITHUB_API_TOKEN=$TOKEN bundle exec danger --verbose
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: [4.0.0, 3.4.8, 3.3.10, 3.2.9]
grape-version: ['~> 3.0.0', '~> 2.3.0']
steps:
- uses: actions/checkout@v6
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
- name: Install dependencies
run: bundle install --jobs 4 --retry 3
env:
GRAPE_VERSION: ${{ matrix.grape-version }}
- name: Run tests
run: bundle exec rake
env:
GRAPE_VERSION: ${{ matrix.grape-version }}

View file

@ -1,2 +1,13 @@
inherit_from: .rubocop_todo.yml
AllCops:
Exclude:
- Guardfile
- grape-active_model_serializers.gemspec
NewCops: enable
Style/BlockDelimiters:
Enabled: false
Style/FrozenStringLiteralComment:
Enabled: false

View file

@ -1,27 +1,71 @@
# This configuration was generated by `rubocop --auto-gen-config`
# on 2015-01-13 18:47:14 -0500 using RuboCop version 0.28.0.
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2018-03-14 14:59:23 -0400 using RuboCop version 0.53.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 25
# Configuration parameters: AllowURI, URISchemes.
# Offense count: 1
# Configuration parameters: Include.
# Include: **/*.gemfile, **/Gemfile, **/gems.rb
Bundler/DuplicatedGem:
Exclude:
- 'Gemfile'
# Offense count: 1
Metrics/AbcSize:
Max: 20
Max: 18
Metrics/LineLength:
Max: 179
# Offense count: 23
# Configuration parameters: CountComments, ExcludedMethods.
Metrics/BlockLength:
Max: 133
# Offense count: 7
Style/Documentation:
Enabled: false
# Offense count: 1
Metrics/CyclomaticComplexity:
Max: 9
# Offense count: 1
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 15
# Offense count: 1
Metrics/PerceivedComplexity:
Max: 10
# Offense count: 2
# Configuration parameters: Exclude.
Style/FileName:
Enabled: false
# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms.
# 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
Naming/FileName:
Exclude:
- 'lib/grape-active_model_serializers.rb'
- 'spec/grape-active_model_serializers_spec.rb'
# Offense count: 4
Style/RegexpLiteral:
MaxSlashes: 0
# Offense count: 5
Style/Documentation:
Exclude:
- 'spec/**/*'
- 'test/**/*'
- 'lib/grape-active_model_serializers/endpoint_extension.rb'
- 'lib/grape-active_model_serializers/error_formatter.rb'
- 'lib/grape-active_model_serializers/formatter.rb'
- 'lib/grape-active_model_serializers/options_builder.rb'
- 'lib/grape-active_model_serializers/serializer_resolver.rb'
# Offense count: 1
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Exclude:
- 'lib/grape-active_model_serializers/serializer_resolver.rb'
# Offense count: 2
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Layout/LineLength:
Max: 87
Lint/ConstantDefinitionInBlock:
Exclude:
- spec/integration/sequel_spec.rb

View file

@ -1,36 +0,0 @@
language: ruby
sudo: false
matrix:
include:
- rvm: 2.3.1
env: GRAPE_VERSION=0.8.0
- rvm: 2.3.1
env: GRAPE_VERSION=0.9.0
- rvm: 2.3.1
env: GRAPE_VERSION=0.10.1
- rvm: 2.3.1
env: GRAPE_VERSION=0.12.0
- rvm: 2.3.1
env: GRAPE_VERSION=0.13.0
- rvm: 2.3.1
env: GRAPE_VERSION=0.14.0
- rvm: 2.3.1
env: GRAPE_VERSION=0.15.0
- rvm: 2.3.1
env: GRAPE_VERSION=0.16.2
- rvm: 2.3.1
env: GRAPE_VERSION=HEAD
- rvm: 2.3.0
- rvm: 2.2.5
- rvm: 2.1
- rvm: rbx-2
- rvm: jruby-19mode
- rvm: ruby-head
- rvm: jruby-head
allow_failures:
- rvm: rbx-2
- rvm: jruby-19mode
- rvm: ruby-head
- rvm: jruby-head

View file

@ -1,40 +1,69 @@
## Changelog
### 1.5.0 (Next)
### 2.0.2 (Next)
* Your contribution here.
### 1.4.0 (July 14, 2016)
### 2.0.1 (2025/12/08)
* [#49](https://github.com/jrhe/grape-active_model_serializers/pull/49): Adds support for active model serializer namespace - [@syntaxTerr0r](https://github.com/syntaxTerr0r).
* [#36](https://github.com/jrhe/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).
* [#92](https://github.com/ruby-grape/grape-active_model_serializers/pull/92): Fix: accept nil serializer - [@mateusnava](https://github.com/mateusnava).
* [#100](https://github.com/ruby-grape/grape-active_model_serializers/pull/100): Fix compatibility with Grape 3.0 and test in CI - [@samsonjs](https://github.com/samsonjs).
### v1.3.2 (February 27, 2015)
### 2.0.0 (2025/06/02)
* [#39](https://github.com/jrhe/grape-active_model_serializers/pull/39): Look for namespace and other options to configure serializers - [@jwkoelewijn](https://github.com/jwkoelewijn).
* [#40](https://github.com/jrhe/grape-active_model_serializers/pull/40): Use env to pass AMS meta around - [@dblock](https://github.com/dblock).
* [#96](https://github.com/ruby-grape/grape-active_model_serializers/pull/96): Add compatibility for Grape 2.3 - [@samsonjs](https://github.com/samsonjs).
* [#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).
### v1.3.1 (November 20, 2014)
### 1.5.2 (2018/03/14)
* [#30](https://github.com/jrhe/grape-active_model_serializers/pull/30): Read options from default_serializer_options - [@siong1987](https://github.com/siong1987).
* [#24](https://github.com/jrhe/grape-active_model_serializers/pull/24): Makes it possible to use `current_user` within serializers - [@sonxurxo](https://github.com/sonxurxo).
* [#76](https://github.com/ruby-grape/grape-active_model_serializers/pull/76): Add custom error formatter - [@xn](https://github.com/xn).
### v1.2.1 (July 23, 2014)
### 1.5.1 (2017/04/25)
* [#21](https://github.com/jrhe/grape-active_model_serializers/pull/21): Correctly fetch serialization scope - [@radanskoric](https://github.com/radanskoric).
* [#18](https://github.com/jrhe/grape-active_model_serializers/pull/18): Added support for active model serializer 0.9.x - [@sbounmy](https://github.com/sbounmy).
* [#74](https://github.com/ruby-grape/grape-active_model_serializers/pull/74): Add support for Sequel - [@drn](https://github.com/drn).
* [#73](https://github.com/ruby-grape/grape-active_model_serializers/pull/73): Ensure all AMS options are passed through - [@drn](https://github.com/drn).
* [#65](https://github.com/ruby-grape/grape-active_model_serializers/pull/65): Added Danger, PR linter - [@dblock](https://github.com/dblock).
* [#63](https://github.com/ruby-grape/grape-active_model_serializers/pull/63): Pass adapter options through render - [@drn](https://github.com/drn).
* [#15](https://github.com/jrhe/grape-active_model_serializers/pull/15): Added `render` syntactic sugar - [@zph](https://github.com/zph).
* [#14](https://github.com/jrhe/grape-active_model_serializers/pull/14): Fix: `default_root` method to support symbol route in Grape - [@wjp2013](https://github.com/wjp2013).
* [#12](https://github.com/jrhe/grape-active_model_serializers/pull/12): Added support for `current_user` - [@kpassapk](https://github.com/kpassapk).
* [#11](https://github.com/jrhe/grape-active_model_serializers/pull/11): Fixed require path - [@schickling](https://github.com/schickling).
### 1.5.0 (2016/08/24)
### v1.0.1 (September 9, 2013)
* [#61](https://github.com/ruby-grape/grape-active_model_serializers/pull/61): Adds support for collection serializers - [@drn](https://github.com/drn).
* [#60](https://github.com/ruby-grape/grape-active_model_serializers/pull/60): Namespace serializer inference - [@drn](https://github.com/drn).
* [#59](https://github.com/ruby-grape/grape-active_model_serializers/pull/59): Refactor option and serializer resolution - [@drn](https://github.com/drn).
* [#57](https://github.com/ruby-grape/grape-active_model_serializers/pull/57): Solve line length linter issues - [@drn](https://github.com/drn).
* [#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).
* [#6](https://github.com/jrhe/grape-active_model_serializers/pull/6): Conform to ActiveModel::Serializers way of determining array-ness - [@tfe](https://github.com/tfe).
* [#4](https://github.com/jrhe/grape-active_model_serializers/pull/4): Support for namespace options and rely more on active_model_serializers - [@johnallen3d](https://github.com/johnallen3d).
* [#1](https://github.com/jrhe/grape-active_model_serializers/pull/1): Fix: Grape::ActiveModelSerializers for models with compound names - [@george](https://github.com/george).
### 1.4.0 (2016/07/14)
### v1.0.0
* [#49](https://github.com/ruby-grape/grape-active_model_serializers/pull/49): Adds support for active model serializer namespace - [@syntaxTerr0r](https://github.com/syntaxTerr0r).
* [#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).
### 1.3.2 (2015/02/27)
* [#40](https://github.com/ruby-grape/grape-active_model_serializers/pull/40): Use env to pass AMS meta around - [@dblock](https://github.com/dblock).
* [#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).
### 1.3.1 (2014/11/20)
* [#30](https://github.com/ruby-grape/grape-active_model_serializers/pull/30): Read options from default_serializer_options - [@siong1987](https://github.com/siong1987).
* [#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).
### 1.2.1 (2014/07/23)
* [#21](https://github.com/ruby-grape/grape-active_model_serializers/pull/21): Correctly fetch serialization scope - [@radanskoric](https://github.com/radanskoric).
* [#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).
* [#15](https://github.com/ruby-grape/grape-active_model_serializers/pull/15): Added `render` syntactic sugar - [@zph](https://github.com/zph).
* [#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).
* [#12](https://github.com/ruby-grape/grape-active_model_serializers/pull/12): Added support for `current_user` - [@kpassapk](https://github.com/kpassapk).
* [#11](https://github.com/ruby-grape/grape-active_model_serializers/pull/11): Fixed require path - [@schickling](https://github.com/schickling).
### 1.0.1 (2013/09/09)
* [#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).
* [#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).
* [#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).
### 1.0.0 (2013/09/09)
* Initial public release - [@jrhe](https://github.com/jrhe).

1
Dangerfile Normal file
View file

@ -0,0 +1 @@
danger.import_dangerfile(gem: 'ruby-grape-danger')

17
Gemfile
View file

@ -2,9 +2,24 @@ source 'https://rubygems.org'
gemspec
case version = ENV['GRAPE_VERSION'] || '~> 0.10.0'
case version = ENV['GRAPE_VERSION'] || '~> 3.0.0'
when 'HEAD'
gem 'grape', github: 'intridea/grape'
else
gem 'grape', version
end
group :test do
gem 'rack-test'
gem 'ruby-grape-danger', '~> 0.2.0', require: false
gem 'sequel', '~> 5.91', require: false
gem 'sqlite3'
end
group :development, :test do
gem 'guard-rspec'
gem 'listen', '~> 3.9.0'
gem 'rake'
gem 'rspec'
gem 'rubocop', '1.75.7'
end

View file

@ -1,8 +1,29 @@
# Table of Contents
- [Grape::ActiveModelSerializers](#grapeactivemodelserializers)
- [Installation](#installation)
- [Dependencies](#dependencies)
- [Usage](#usage)
- [Require grape-active_model_serializers](#require-grape-active_model_serializers)
- [Tell your API to use Grape::Formatter::ActiveModelSerializers](#tell-your-api-to-use-grapeformatteractivemodelserializers)
- [Writing Serializers](#writing-serializers)
- [Serializers are inferred by active_record model names](#serializers-are-inferred-by-active_record-model-names)
- [Array Roots](#array-roots)
- [API Versioning](#api-versioning)
- [Manually specifying serializer / adapter options](#manually-specifying-serializer--adapter-options)
- [Custom Metadata](#custom-metadata)
- [Default Serializer Options](#default-serializer-options)
- [Current User](#current-user)
- [Full Example](#full-example)
- [Contributing](#contributing)
- [History](#history)
# Grape::ActiveModelSerializers
Use [active_model_serializers](https://github.com/rails-api/active_model_serializers) with [Grape](https://github.com/intridea/grape)!
[![Build Status](https://api.travis-ci.org/ruby-grape/grape-active_model_serializers.svg)](http://travis-ci.org/ruby-grape/grape-active_model_serializers) [![Dependency Status](https://gemnasium.com/ruby-grape/grape-active_model_serializers.svg)](https://gemnasium.com/ruby-grape/grape-active_model_serializers) [![Code Climate](https://codeclimate.com/github/jrhe/grape-active_model_serializers.svg)](https://codeclimate.com/github/jrhe/grape-active_model_serializers)
[![Gem Version](https://badge.fury.io/rb/grape-active_model_serializers.svg)](https://badge.fury.io/rb/grape-active_model_serializers)
[![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)
## Installation
@ -14,6 +35,12 @@ gem 'grape-active_model_serializers'
See [UPGRADING](UPGRADING.md) if you're upgrading from a previous version.
## Dependencies
* &gt;= Ruby v3.1
* &gt;= [grape](https://github.com/ruby-grape/grape) v2.3.0
* &gt;= [active_model_serializers](https://github.com/rails-api/active_model_serializers) v0.10.0
## Usage
### Require grape-active_model_serializers
@ -29,6 +56,12 @@ require 'grape-active_model_serializers'
class API < Grape::API
format :json
formatter :json, Grape::Formatter::ActiveModelSerializers
# Serializes errors with ActiveModel::Serializer::ErrorSerializer if an ActiveModel.
# Serializer conforms to the adapter, ex: json, jsonapi.
# So an error formatted with a jsonapi formatter would render as per:
# http://jsonapi.org/format/#error-objects
error_formatter :json, Grape::Formatter::ActiveModelSerializers
end
```
@ -143,10 +176,10 @@ Or as follows.
ActiveModelSerializer will fetch automatically the right serializer to render.
### Manually specifying serializer options
### Manually specifying serializer / adapter options
```ruby
# Serializer options can be specified on routes or namespaces.
# Serializer and adapter options can be specified on routes or namespaces.
namespace 'foo', serializer: BarSerializer do
get "/" do
# will use "bar" serializer
@ -164,6 +197,36 @@ namespace 'foo', serializer: BarSerializer do
end
```
```ruby
# Serializer and adapter options can also be specified in the body of the route
resource :users do
get '/:id' do
if conditional
# uses UserSerializer and configured default adapter automatically
current_user
else
# uses specified serializer and adapter
render current_user, serializer: ErrorSerializer, adapter: :attributes
end
end
end
```
```ruby
# Adhoc serializer options can be specified in the body of the route
resource :users do
get '/:id' do
render current_user, extra: { adhoc_name_option: 'value' }
end
end
class UserSerializer
def name
instance_options[:adhoc_name_option] # accessible in instance_options
end
end
```
### Custom Metadata
```ruby
@ -240,4 +303,4 @@ See [CONTRIBUTING](CONTRIBUTING.md).
## History
Structured and based upon [grape-rabl](https://github.com/LTe/grape-rabl).
Structured and based upon [grape-rabl](https://github.com/ruby-grape/grape-rabl).

70
RELEASING.md Normal file
View file

@ -0,0 +1,70 @@
# Releasing Grape::ActiveModelSerializers
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.
### Release
Run tests, check that all tests succeed locally.
```
bundle install
rake
```
Check that the last build succeeded in [GitHub Actions](https://github.com/ruby-grape/grape-active_model_serializers/actions/workflows/ci.yml) for all supported platforms.
Change "Next Release" in [CHANGELOG.md](CHANGELOG.md) to the new version.
```
### 0.7.2 (2014/02/06)
```
Remove the line with "Your contribution here.", since there will be no more contributions to this release.
Commit your changes.
```
git add CHANGELOG.md lib/grape-active_model_serializers/version.rb
git commit -m "Preparing for release, 0.7.2."
git push origin master
```
Release.
```
$ rake release
grape-active_model_serializers 0.7.2 built to pkg/grape-active_model_serializers-0.7.2.gem.
Tagged v0.7.2.
Pushed git commits and tags.
Pushed grape-active_model_serializers 0.7.2 to rubygems.org.
```
### Prepare for the Next Version
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`).
```ruby
module Grape
module ActiveModelSerializers
VERSION = '0.7.3'.freeze
end
end
```
Add the next release to [CHANGELOG.md](CHANGELOG.md).
```
#### 0.7.3 (Next)
* Your contribution here.
```
Commit your changes.
```
git add CHANGELOG.md lib/grape-active_model_serializers/version.rb
git commit -m "Preparing for next development iteration, 0.7.3."
git push origin master
```

3
Rakefile Normal file → Executable file
View file

@ -1,4 +1,5 @@
#!/usr/bin/env rake
require 'bundler/gem_tasks'
require 'rspec/core'
@ -11,4 +12,4 @@ end
require 'rubocop/rake_task'
RuboCop::RakeTask.new(:rubocop)
task default: [:rubocop, :spec]
task default: %i[rubocop spec]

View file

@ -1,12 +1,145 @@
### Upgrading to v.1.4.0
### Upgrading to v1.5.0
#### ASM v0.10.x support added, v0.9.x support dropped
[ASM](https://github.com/rails-api/active_model_serializers) introduced
breaking changes with ASM v0.10.x. Support has been introduced in v1.5.0.
If you require support for older version of ASM, please lock your Gemfile
to the relevant version.
#### Non-collection Serializer Resolution
Serializer resolution now uses the following strategy:
1. Defined by the resource
```
class UsersApi < Grape::Api
resource :users do
get '/:id', serializer: SpecifiedUserSerializer do
current_user
end
end
end
class User
def serializer_class
SpecifiedUserSerializer
end
end
```
2. Specified by options
```
class UsersApi < Grape::Api
resource :users do
get '/:id', serializer: SpecifiedUserSerializer do
current_user
end
end
end
```
2. Namespace inferred
```
class V1::UsersApi < Grape::Api
resource :users do
get '/:id' do
current_user
end
end
end
# 'V1::UsersApi'.deconstantize => 'V1'
# searches for serializers in the V1:: namespace with the name UserSerializer
# used
class V1::UserSerializer
...
end
# not used since V1::UserSerializer resolved first
class UserSerializer
...
end
```
3. Version inferred
```
class UsersApi < Grape::Api
version 'v2'
resource :users do
get '/:id' do
current_user
end
end
end
# 'v2'.classify => V2
# searches for serializers in the V2:: namespace with the name UserSerializer
# used
class V2::UserSerializer
...
end
# not used since V2::UserSerializer resolved first
class UserSerializer
...
end
```
4. Default ASM lookup
```
class V1::UsersApi < Grape::Api
version 'v2'
resource :users do
get '/:id' do
current_user
end
end
end
# searches for serializers in the V1:: namespace, none found
# searches for serializers in the V2:: namespace, none found
# used as no other serializers were found
class UserSerializer
...
end
```
#### Collection Serializer Resolution
Serializer resolution for collections also uses the above strategy, but has
the added option of overriding the member serializer if the `each_serializer`
options is specified.
```
class UsersApi < Grape::Api
resource :users do
get each_serializer: SpecifiedUserSerializer do
User.all
end
end
end
```
### Upgrading to v1.4.0
#### Changes in Serializer Namespacing
Version 1.4.0 introduced changes in serializer namespacing when using Grape API versioning.
Version 1.4.0 introduced changes in serializer namespacing when using Grape
API versioning.
If you haven't declared an API version in Grape, nothing changed.
If your Grape API is versioned, which means you have declared at least one version in Grape, you must namespace your serializers accordingly.
If your Grape API is versioned, which means you have declared at least one
version in Grape, you must namespace your serializers accordingly.
For example, given the following API.
@ -48,7 +181,8 @@ module Chocolate
end
```
This will allow you to keep your serializers easier to maintain and more organized.
This will allow you to keep your serializers easier to maintain and more
organized.
```
app
@ -73,13 +207,15 @@ or alternatively:
└── candy_bar_user_serializer.rb
```
Thus, ActiveModelSerializer will fetch automatically the right serializer to render.
Thus, ActiveModelSerializer will fetch automatically the right serializer to
render.
### Upgrading to v1.0.0
#### Changes to Array Roots
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.
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.
In the following example the array root is `users`.

View file

@ -16,13 +16,6 @@ Gem::Specification.new do |gem|
gem.version = Grape::ActiveModelSerializers::VERSION
gem.licenses = ['MIT']
gem.add_dependency 'grape'
gem.add_dependency 'active_model_serializers', '>= 0.9.0'
gem.add_development_dependency 'listen', '~> 3.0.7'
gem.add_development_dependency 'rspec'
gem.add_development_dependency 'rack-test'
gem.add_development_dependency 'rake'
gem.add_development_dependency 'guard-rspec'
gem.add_development_dependency 'rubocop', '0.28.0'
gem.add_dependency 'grape', '>= 2.3.0'
gem.add_dependency 'active_model_serializers', '>= 0.10.0'
end

View file

@ -1,5 +1,8 @@
require 'active_model_serializers'
require 'grape'
require 'grape-active_model_serializers/endpoint_extension'
require 'grape-active_model_serializers/error_formatter'
require 'grape-active_model_serializers/formatter'
require 'grape-active_model_serializers/serializer_resolver'
require 'grape-active_model_serializers/options_builder'
require 'grape-active_model_serializers/version'

View file

@ -9,7 +9,7 @@ module Grape
attr_accessor :controller_name
def namespace_options
if self.respond_to?(:inheritable_setting)
if respond_to?(:inheritable_setting)
inheritable_setting.namespace
else
settings[:namespace] ? settings[:namespace].options : {}
@ -17,7 +17,7 @@ module Grape
end
def route_options
if self.respond_to?(:inheritable_setting)
if respond_to?(:inheritable_setting)
inheritable_setting.route
else
options[:route_options]
@ -30,22 +30,31 @@ module Grape
base.class_eval do
def serialization_scope
send(_serialization_scope) if _serialization_scope && respond_to?(_serialization_scope, true)
return unless _serialization_scope
return unless respond_to?(_serialization_scope, true)
send(_serialization_scope)
end
end
end
def render(resources, meta = {})
env['ams_meta'] = meta
def render(resources, extra_options = {})
options = extra_options.symbolize_keys
env['ams_meta'] = options.slice(
:meta, :meta_key
)
env['ams_adapter'] = options.slice(
:adapter, :serializer, :each_serializer, :include,
:fields, :key_transform, :links, :namespace
)
env['ams_extra'] = options[:extra]
resources
end
def default_serializer_options
end
def default_serializer_options; end
def url_options
end
def url_options; end
end
Endpoint.send(:include, EndpointExtension)
Endpoint.include EndpointExtension
end

View file

@ -0,0 +1,51 @@
module Grape
module ErrorFormatter
class ActiveModelSerializers < Base
class << self
def call(message, backtrace, options = {}, env = nil, original_exception = nil)
message = present(message, env) if respond_to?(:present)
message = wrap_message(message)
rescue_options = options[:rescue_options] || {}
if rescue_options[:backtrace] && backtrace && !backtrace.empty?
message = message.merge(backtrace: backtrace)
end
if rescue_options[:original_exception] && original_exception
message = message
.merge(original_exception: original_exception.inspect)
end
if ::Grape.const_defined? :Json
::Grape::Json.dump(message)
else
::MultiJson.dump(message)
end
end
private
def wrap_message(message)
if active_model?(message)
::ActiveModelSerializers::SerializableResource.new(
message,
serializer: ActiveModel::Serializer::ErrorSerializer
).as_json
elsif ok_to_pass_through?(message)
message
else
{ error: message }
end
end
def active_model?(message)
message.respond_to?(:errors) &&
message.errors.is_a?(ActiveModel::Errors)
end
def ok_to_pass_through?(message)
message.is_a?(Exceptions::ValidationErrors) ||
message.is_a?(Hash)
end
end
end
end
end

View file

@ -1,62 +1,30 @@
module Grape
module Formatter
module ActiveModelSerializers
class ActiveModelSerializers
class << self
def call(resource, env)
serializer = fetch_serializer(resource, env)
options = build_options(resource, env)
serializer = fetch_serializer(resource, options)
if serializer
serializer.to_json
::ActiveModelSerializers::Adapter.create(
serializer, options
).to_json
else
Grape::Formatter::Json.call resource, env
Grape::Formatter::Json.call(resource, env)
end
end
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, ams_options))
return nil unless serializer
options[:scope] = endpoint unless options.key?(:scope)
# ensure we have an root to fallback on
options[:resource_name] = default_root(endpoint) if resource.respond_to?(:to_ary)
serializer.new(resource, options.merge(other_options(env)))
def build_options(resource, env)
Grape::ActiveModelSerializers::OptionsBuilder.new(
resource, env
).options
end
def other_options(env)
options = {}
ams_meta = env['ams_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
options
end
def build_options_from_endpoint(endpoint)
[endpoint.default_serializer_options || {}, endpoint.namespace_options, endpoint.route_options, endpoint.options, endpoint.options.fetch(:route_options)].reduce(:merge)
end
# array root is the innermost namespace name ('space') if there is one,
# otherwise the route name (e.g. get 'name')
def default_root(endpoint)
innermost_scope = if endpoint.respond_to?(:namespace_stackable)
endpoint.namespace_stackable(:namespace).last
else
endpoint.settings.peek[:namespace]
end
if innermost_scope
innermost_scope.space
else
endpoint.options[:path][0].to_s.split('/')[-1]
end
def fetch_serializer(resource, options)
Grape::ActiveModelSerializers::SerializerResolver.new(
resource, options
).serializer
end
end
end

View file

@ -0,0 +1,77 @@
module Grape
module ActiveModelSerializers
class OptionsBuilder
def initialize(resource, env)
self.resource = resource
self.env = env
end
def options
@options ||= begin
options = endpoint_options
options[:scope] = endpoint unless options.key?(:scope)
options.merge!(default_root_options) unless options.key?(:root)
options.merge!(meta_options)
options.merge!(adapter_options)
options.merge!(extra_options)
options
end
end
private
attr_accessor :resource, :env
def endpoint_options
[
endpoint.default_serializer_options || {},
endpoint.namespace_options,
endpoint.route_options,
endpoint.options,
endpoint.options.fetch(:route_options)
].reduce(:merge)
end
def endpoint
@endpoint ||= env['api.endpoint']
end
# array root is the innermost namespace name ('space') if there is one,
# otherwise the route name (e.g. get 'name')
def default_root_options
return {} unless resource.respond_to?(:to_ary)
if innermost_scope
{ root: innermost_scope.space }
else
{ root: endpoint.options[:path].first.to_s.split('/').last }
end
end
def innermost_scope
endpoint.inheritable_setting.namespace_stackable[:namespace]&.last
end
def meta_options
options = {}
meta_options = env['ams_meta'] || {}
meta = meta_options[:meta]
meta_key = meta_options[:meta_key]
options[:meta] = meta if meta
options[:meta_key] = meta_key if meta && meta_key
options
end
def adapter_options
env['ams_adapter'] || {}
end
def extra_options
options = env['ams_extra'] || {}
return options if options.is_a?(Hash)
raise 'Extra options must be a hash'
end
end
end
end

View file

@ -0,0 +1,107 @@
module Grape
module ActiveModelSerializers
class SerializerResolver
def initialize(resource, options)
self.resource = resource
self.options = options
end
def serializer
serializer_class&.new(resource, serializer_options)
end
private
attr_accessor :resource, :options
def serializer_class
return @serializer_class if defined?(@serializer_class)
return nil if options.key?(:serializer) && options[:serializer].nil?
@serializer_class = resource_defined_class ||
collection_class ||
options[:serializer] ||
namespace_inferred_class ||
version_inferred_class ||
resource_serializer_class
end
def serializer_options
if collection_serializer? && !options.key?(:serializer)
options.merge(each_serializer_option)
else
options
end
end
def collection_serializer?
serializer_class == ActiveModel::Serializer.config.collection_serializer
end
def each_serializer_option
serializer_class = options[:each_serializer]
serializer_class ||= namespace_inferred_class
serializer_class ||= version_inferred_class
serializer_class ? { serializer: serializer_class } : {}
end
def resource_defined_class
resource.serializer_class if resource.respond_to?(:serializer_class)
end
def collection_class
if resource.respond_to?(:to_ary) || resource.respond_to?(:all)
ActiveModel::Serializer.config.collection_serializer
end
end
def namespace_inferred_class
return nil unless options.key?(:for)
namespace = options[:for].to_s.deconstantize
"#{namespace}::#{resource_serializer_klass}".safe_constantize
end
def version_inferred_class
return nil unless options.key?(:version)
"#{version}::#{resource_serializer_klass}".safe_constantize
end
def version
options[:version].try(:classify)
end
def resource_serializer_klass
@resource_serializer_klass ||= [
resource_namespace,
"#{resource_klass}Serializer"
].compact.join('::')
end
def resource_klass
resource_class.name.demodulize
end
def resource_namespace
klass = resource_class.name.deconstantize
klass.empty? ? nil : klass
end
def resource_class
if resource.respond_to?(:klass)
resource.klass
elsif resource.respond_to?(:to_ary) || resource.respond_to?(:all)
resource.first.class
else
resource.class
end
end
def resource_serializer_class
ActiveModel::Serializer.serializer_for(resource)
end
end
end
end

View file

@ -1,5 +1,5 @@
module Grape
module ActiveModelSerializers
VERSION = '1.4.0'
VERSION = '2.0.2'.freeze
end
end

View file

@ -5,6 +5,7 @@ describe '#render' do
let(:app) { Class.new(Grape::API) }
before do
ActiveModelSerializers.config.adapter = :json
app.format :json
app.formatter :json, Grape::Formatter::ActiveModelSerializers
end

View file

@ -2,41 +2,140 @@ require 'spec_helper'
describe 'Grape::EndpointExtension' do
if Grape::Util.const_defined?('InheritableSetting')
subject { Grape::Endpoint.new(Grape::Util::InheritableSetting.new, path: '/', method: 'foo') }
subject do
Grape::Endpoint.new(
Grape::Util::InheritableSetting.new,
path: '/',
method: 'foo'
)
end
else
subject { Grape::Endpoint.new({}, path: '/', method: 'foo') }
subject do
Grape::Endpoint.new({}, path: '/', method: 'foo')
end
end
let(:serializer) { Grape::Formatter::ActiveModelSerializers }
let(:user) do
let(:user) {
Object.new do
def name
'sven'
end
end
end
}
let(:users) { [user, user] }
describe '#render' do
let(:env) { {} }
let(:env_key) { nil }
before do
allow(subject).to receive(:env).and_return({})
allow(subject).to receive(:env).and_return(env)
end
it { should respond_to(:render) }
let(:meta_content) { { total: 2 } }
let(:meta_full) { { meta: meta_content } }
context 'supplying meta' do
it 'passes through the Resource and uses given meta settings' do
allow(subject).to receive(:env).and_return(meta: meta_full)
expect(subject.render(users, meta_full)).to eq(users)
shared_examples_for 'option capture' do
it 'captures options' do
subject.render(users, options)
expect(env[env_key]).to eq(options)
end
end
context 'supplying meta and key' do
let(:meta_key) { { meta_key: :custom_key_name } }
it 'passes through the Resource and uses given meta settings' do
allow(subject).to receive(:env).and_return(meta: meta_full.merge(meta_key))
expect(subject.render(users, meta_full.merge(meta_key))).to eq(users)
shared_examples_for 'skipped option capture' do
it 'does not capture options' do
subject.render(users, options)
expect(env[env_key]).to eq({})
end
end
it { should respond_to(:render) }
context 'meta options' do
let(:env_key) { 'ams_meta' }
let(:meta_full) { { meta: meta_content } }
context 'meta' do
let(:options) { { meta: { total: 2 } } }
it_behaves_like 'option capture'
end
context 'meta_key' do
let(:options) { { meta_key: 'custom_meta' } }
it_behaves_like 'option capture'
end
context 'unknown option' do
let(:options) { { unknown: 'value' } }
it_behaves_like 'skipped option capture'
end
end
context 'adapter options' do
let(:options) { {} }
let(:env_key) { 'ams_adapter' }
context 'adapter' do
let(:options) { { adapter: :json } }
it_behaves_like 'option capture'
end
context 'include' do
let(:options) { { include: '*' } }
it_behaves_like 'option capture'
end
context 'fields' do
let(:options) { { fields: [:id] } }
it_behaves_like 'option capture'
end
context 'key_transform' do
let(:options) { { key_transform: :camel_lower } }
it_behaves_like 'option capture'
end
context 'links' do
let(:links_object) {
{
href: 'http://example.com/api/posts',
meta: {
count: 10
}
}
}
let(:options) { { links: links_object } }
it_behaves_like 'option capture'
end
context 'namespace' do
let(:options) { { namespace: V4 } }
it_behaves_like 'option capture'
end
context 'unknown option' do
let(:options) { { unknown: 'value' } }
it_behaves_like 'skipped option capture'
end
end
context 'extra options' do
let(:env_key) { 'ams_extra' }
context 'namespace' do
let(:options) { { extra: { option: 'value' } } }
it 'captures options' do
subject.render(users, options)
expect(env[env_key]).to eq(options[:extra])
end
end
context 'unknown option' do
let(:options) { { unknown: 'value' } }
it 'does not capture options' do
subject.render(users, options)
expect(env[env_key]).to eq(nil)
end
end
end
end

View file

@ -0,0 +1,92 @@
require 'spec_helper'
require 'grape-active_model_serializers/error_formatter'
describe Grape::ErrorFormatter::ActiveModelSerializers do
subject { Grape::ErrorFormatter::ActiveModelSerializers }
let(:backtrace) { ['Line:1'] }
let(:options) { {} }
let(:env) { { 'api.endpoint' => app.endpoints.first } }
let(:original_exception) { StandardError.new('oh noes!') }
let(:app) {
Class.new(Grape::API) do |app|
app.format :json
app.formatter :jsonapi, Grape::Formatter::ActiveModelSerializers
app.error_formatter :jsonapi, Grape::ErrorFormatter::ActiveModelSerializers
app.namespace('space') do |ns|
ns.get('/', root: false) do
error!(message)
end
end
end
}
let(:foo) {
Class.new {
include ActiveModel::Model
attr_accessor :name
def initialize(attributes = {})
super
errors.add(:name, 'We don\'t like bears')
end
}
}
before do
ActiveModel::Serializer.config.adapter = :json_api
end
after do
ActiveModel::Serializer.config.adapter = :json
end
describe '#call' do
context 'message is an activemodel' do
let(:message) {
foo.new(name: 'bar')
}
it 'formats the error' do
result = subject
.call(message, backtrace, options, env, original_exception)
json_hash = JSON.parse(result)
expected_result = {
'errors' => [
{
'source' => {
'pointer' => '/data/attributes/name'
},
'detail' => 'We don\'t like bears'
}
]
}
expect(json_hash == expected_result).to eq(true)
end
end
context 'message is hash like' do
let(:message) { { 'errors' => ['error'] } }
it 'passes the message through' do
result = subject
.call(message, backtrace, options, env, original_exception)
json_hash = JSON.parse(result)
expect(json_hash == message).to eq(true)
end
end
context 'message is text' do
let(:message) { 'error' }
it 'wraps the error' do
result = subject
.call(message, backtrace, options, env, original_exception)
json_hash = JSON.parse(result)
expect(json_hash == { 'error' => message }).to eq(true)
end
end
end
end

View file

@ -18,19 +18,24 @@ describe Grape::Formatter::ActiveModelSerializers do
end
end
let(:env) { { 'api.endpoint' => app.endpoints.first } }
let(:options) { described_class.build_options(nil, env) }
it 'should read serializer options like "root"' do
expect(described_class.build_options_from_endpoint(app.endpoints.first)).to include :root
expect(options).to include(:root)
end
end
describe '.fetch_serializer' do
let(:user) { User.new(first_name: 'John') }
let(:params) { { path: '/', method: 'foo', root: false } }
if Grape::Util.const_defined?('InheritableSetting')
let(:endpoint) { Grape::Endpoint.new(Grape::Util::InheritableSetting.new, path: '/', method: 'foo', root: false) }
let(:setting) { Grape::Util::InheritableSetting.new }
else
let(:endpoint) { Grape::Endpoint.new({}, path: '/', method: 'foo', root: false) }
let(:setting) { {} }
end
let(:endpoint) { Grape::Endpoint.new(setting, params) }
let(:env) { { 'api.endpoint' => endpoint } }
@ -44,7 +49,10 @@ describe Grape::Formatter::ActiveModelSerializers do
end
end
subject { described_class.fetch_serializer(user, env) }
let(:options) { described_class.build_options(user, env) }
subject { described_class.fetch_serializer(user, options) }
let(:instance_options) { subject.instance_variable_get(:@instance_options) }
it { should be_a UserSerializer }
@ -53,12 +61,12 @@ describe Grape::Formatter::ActiveModelSerializers do
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])
expect(instance_options[:only]).to eq(:only)
expect(instance_options[: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
expect(options).to include(:root)
end
end
end

View file

@ -15,25 +15,35 @@ describe Grape::Formatter::ActiveModelSerializers do
app.namespace('space') do |ns|
ns.get('/', root: false, apiver: 'v1') do
{ user: { first_name: 'JR', last_name: 'HE', email: 'jrhe@github.com' } }
{
user: {
first_name: 'JR',
last_name: 'HE',
email: 'jrhe@github.com'
}
}
end
end
end
let(:env) { { 'api.endpoint' => app.endpoints.first } }
let(:options) { described_class.build_options(nil, env) }
it 'should read serializer options like "root"' do
expect(described_class.build_options_from_endpoint(app.endpoints.first)).to include :root
expect(options).to include(:root)
end
end
describe '.fetch_serializer' do
let(:user) { User.new(first_name: 'John', email: 'j.doe@internet.com') }
let(:params) { { path: '/', method: 'foo', version: 'v1', root: false } }
if Grape::Util.const_defined?('InheritableSetting')
let(:endpoint) { Grape::Endpoint.new(Grape::Util::InheritableSetting.new, path: '/', method: 'foo', version: 'v1', root: false) }
let(:setting) { Grape::Util::InheritableSetting.new }
else
let(:endpoint) { Grape::Endpoint.new({}, path: '/', method: 'foo', version: 'v1', root: false) }
let(:setting) { {} }
end
let(:endpoint) { Grape::Endpoint.new(setting, params) }
let(:env) { { 'api.endpoint' => endpoint } }
before do
@ -46,7 +56,10 @@ describe Grape::Formatter::ActiveModelSerializers do
end
end
subject { described_class.fetch_serializer(user, env) }
let(:options) { described_class.build_options(user, env) }
subject { described_class.fetch_serializer(user, options) }
let(:instance_options) { subject.send(:instance_options) }
it { should be_a V1::UserSerializer }
@ -55,12 +68,12 @@ describe Grape::Formatter::ActiveModelSerializers do
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])
expect(instance_options[:only]).to eq(:only)
expect(instance_options[: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
expect(options).to include(:root)
end
end
end

View file

@ -1,4 +0,0 @@
require 'spec_helper'
describe 'grape-active_model_serializers' do
end

View file

@ -0,0 +1,129 @@
require 'spec_helper'
describe Grape::ActiveModelSerializers::OptionsBuilder do
let(:resolver) { described_class.new(resource, env) }
let(:resource) { User.new }
let(:env) { { 'api.endpoint' => UsersApi.endpoints.first } }
context '#options' do
let(:options) { resolver.options }
context 'meta options' do
let(:env) { super().merge('ams_meta' => meta_options) }
let(:meta_options) {
{
meta: meta,
meta_key: meta_key
}
}
let(:meta) { { sample: 'metadata' } }
let(:meta_key) { 'specified_key' }
context 'meta option set' do
context 'meta_key set' do
it 'includes meta' do
expect(options[:meta]).to eq(meta)
end
it 'includes meta_key' do
expect(options[:meta_key]).to eq(meta_key)
end
end
context 'meta_key unset' do
let(:meta_key) { nil }
it 'includes meta' do
expect(options[:meta]).to eq(meta)
end
it 'does not include meta_key' do
expect(options.keys).not_to include(:meta_key)
end
end
end
context 'meta option unset' do
let(:meta) { nil }
context 'meta_key set' do
it 'does not include meta' do
expect(options.keys).not_to include(:meta)
end
it 'does not include meta_key' do
expect(options.keys).not_to include(:meta_key)
end
end
context 'meta_key unset' do
let(:meta_key) { nil }
it 'does not include meta' do
expect(options.keys).not_to include(:meta)
end
it 'does not include meta_key' do
expect(options.keys).not_to include(:meta_key)
end
end
end
end
context 'adapter options' do
let(:env) { super().merge('ams_adapter' => adapter_options) }
let(:adapter_options) { {} }
context 'adapter' do
let(:adapter_options) { { adapter: adapter } }
let(:adapter) { :attributes }
it 'includes adapter as top-level option' do
expect(options[:adapter]).to eq(adapter)
end
end
context 'serializer' do
let(:adapter_options) { { serializer: serializer } }
let(:serializer) { V2::UserSerializer }
it 'includes serializer as top-level option' do
expect(options[:serializer]).to eq(serializer)
end
end
context 'each_serializer' do
let(:adapter_options) { { each_serializer: each_serializer } }
let(:each_serializer) { V2::UserSerializer }
it 'includes each_serializer as top-level option' do
expect(options[:each_serializer]).to eq(each_serializer)
end
end
end
context 'extra options' do
let(:env) { super().merge('ams_extra' => extra_options) }
context 'hash' do
let(:extra_options) { { extra: 'info' } }
it 'includes extra options in top-level options' do
expect(options.keys).to include(:extra)
end
end
context 'not a hash' do
let(:extra_options) { 'extra' }
it 'raises an exception' do
expect {
options
}.to raise_exception(
'Extra options must be a hash'
)
end
end
end
end
end

View file

@ -0,0 +1,157 @@
require 'spec_helper'
# asserts serializer resolution order:
# 1. resource_defined_class # V1::UserSerializer
# 2. collection_class # CollectionSerializer, V2::UserSerializer
# 3. options[:serializer] # V3::UserSerializer
# 4. namespace_inferred_class # V4::UserSerializer
# 5. version_inferred_class # V5::UserSerializer
# 6. resource_serializer_class # UserSerializer
# 7. missing resource # nil
describe Grape::ActiveModelSerializers::SerializerResolver do
let(:resolver) { described_class.new(resource, options) }
let(:resource) { User.new }
let(:options) {
{
serializer: options_serializer_class, # options defined
for: V4::UsersApi, # namespace inference
version: 'v5' # version inference
}
}
# resource defined
let(:resource_defined?) { true }
let(:defined_serializer_class) { V1::UserSerializer }
# options defined
let(:options_serializer_class) { V3::UserSerializer }
let(:serializer) { resolver.serializer }
before do
if resource_defined?
allow(resource).to receive(:respond_to?).and_call_original
allow(resource).to receive(:respond_to?).with(:to_ary) { true }
allow(resource).to receive(:serializer_class) { defined_serializer_class }
end
end
context 'resource defined' do
it 'returns serializer' do
expect(serializer).to be_kind_of(defined_serializer_class)
end
end
context 'not resource defined' do
let(:resource_defined?) { false }
context 'resource collection' do
let(:resource) { [User.new] }
let(:serializer_class) { ActiveModel::Serializer::CollectionSerializer }
it 'returns serializer' do
expect(serializer).to be_kind_of(serializer_class)
end
context 'specified nil by options' do
let(:options) {
super().merge(
serializer: nil
)
}
it 'returns nil' do
expect(serializer).to be_nil
end
end
context 'each serializer' do
let(:options) {
super().except(:serializer).merge(
each_serializer: V2::UserSerializer
)
}
let(:each_serializer) { serializer.send(:options)[:serializer] }
context 'each_serializer option' do
it 'returns expected serializer' do
expect(each_serializer).to eq(V2::UserSerializer)
end
end
context 'no each_serializer option' do
let(:options) { super().except(:each_serializer) }
context 'namespace inferred' do
it 'returns expected serializer' do
expect(each_serializer).to eq(V4::UserSerializer)
end
end
context 'not namespace inferred' do
let(:options) { super().except(:for) }
context 'version inferred' do
it 'returns expected serializer' do
expect(each_serializer).to eq(V5::UserSerializer)
end
end
context 'not version inferred' do
let(:options) { super().except(:version) }
it 'returns nil' do
expect(each_serializer).to eq(nil)
end
end
end
end
end
end
context 'not resource collection' do
context 'specified by options' do
it 'returns specified serializer' do
expect(serializer).to be_kind_of(V3::UserSerializer)
end
end
context 'not specified by options' do
let(:options) { super().except(:serializer) }
context 'namespace inferred' do
it 'returns inferred serializer' do
expect(serializer).to be_kind_of(V4::UserSerializer)
end
end
context 'not namespace inferred' do
let(:options) { super().except(:for) }
context 'version inferred' do
it 'returns inferred serializer' do
expect(serializer).to be_kind_of(V5::UserSerializer)
end
end
context 'not version inferred' do
let(:options) { super().except(:version) }
context 'ASM resolved' do
it 'returns serializer' do
expect(serializer).to be_kind_of(UserSerializer)
end
end
context 'not ASM resolved' do
let(:resource) { nil }
it 'returns nil' do
expect(serializer).to eq(nil)
end
end
end
end
end
end
end
end

View file

@ -0,0 +1,69 @@
require 'sequel'
require 'spec_helper'
describe 'Sequel Integration' do
before do
DB = Sequel.sqlite unless defined?(DB)
DB.create_table(:users) do
primary_key :id
String :name
end
ActiveModelSerializers.config.adapter = :json
app.format :json
app.formatter :json, Grape::Formatter::ActiveModelSerializers
end
after do
DB.drop_table(:users)
Object.send(:remove_const, :SequelUser)
Object.send(:remove_const, :SequelUserSerializer)
end
let!(:model) {
SequelUser = Class.new(Sequel::Model(:users)) do
include ActiveModel::Serialization
def self.model_name
'User'
end
end
}
let!(:serializer) {
SequelUserSerializer = Class.new(ActiveModel::Serializer) do
attributes :id, :name
end
}
let(:app) { Class.new(Grape::API) }
context 'collection' do
let!(:users) {
[
model.create(name: 'one'),
model.create(name: 'two')
]
}
it 'renders' do
app.get('/users') { render SequelUser.dataset }
response = get '/users'
expect(JSON.parse(response.body)).to eq(
'users' => [
{ 'id' => 1, 'name' => 'one' },
{ 'id' => 2, 'name' => 'two' }
]
)
end
end
context 'member' do
let!(:user) { model.create(name: 'name') }
it 'renders' do
app.get('/user/1') { render SequelUser.first }
response = get '/user/1'
expect(JSON.parse(response.body)).to eq(
'user' => { 'id' => 1, 'name' => 'name' }
)
end
end
end

View file

@ -10,6 +10,7 @@ describe Grape::ActiveModelSerializers do
subject { last_response.body }
before do
ActiveModelSerializers.config.adapter = :json
app.format :json
app.formatter :json, Grape::Formatter::ActiveModelSerializers
end
@ -30,31 +31,48 @@ describe Grape::ActiveModelSerializers do
end
it 'uses the built in grape serializer' do
get('/home')
expect(subject).to eql "{\"user\":{\"first_name\":\"JR\",\"last_name\":\"HE\"}}"
expect(subject).to eq(
'{"user":{"first_name":"JR","last_name":"HE"}}'
)
end
end
context "serializer isn't set" do
before do
app.get('/home') do
User.new(first_name: 'JR', last_name: 'HE', email: 'contact@jrhe.co.uk')
User.new(
first_name: 'JR',
last_name: 'HE',
email: 'contact@jrhe.co.uk'
)
end
end
it 'infers the serializer' do
get '/home'
expect(subject).to eql "{\"user\":{\"first_name\":\"JR\",\"last_name\":\"HE\"}}"
expect(subject).to eq(
'{"user":{"first_name":"JR","last_name":"HE"}}'
)
end
end
it 'serializes arrays of objects' do
app.get('/users') do
user = User.new(first_name: 'JR', last_name: 'HE', email: 'contact@jrhe.co.uk')
user = User.new(
first_name: 'JR',
last_name: 'HE',
email: 'contact@jrhe.co.uk'
)
[user, user]
end
get '/users'
expect(subject).to eql "{\"users\":[{\"first_name\":\"JR\",\"last_name\":\"HE\"},{\"first_name\":\"JR\",\"last_name\":\"HE\"}]}"
expect(subject).to eq(
'{"users":[' \
'{"first_name":"JR","last_name":"HE"},' \
'{"first_name":"JR","last_name":"HE"}' \
']}'
)
end
context 'models with compound names' do
@ -64,17 +82,29 @@ describe Grape::ActiveModelSerializers do
end
get '/home'
expect(subject).to eql "{\"blog_post\":{\"title\":\"Grape AM::S Rocks!\",\"body\":\"Really, it does.\"}}"
expect(subject).to eq(
'{"blog_post":' \
'{"title":"Grape AM::S Rocks!","body":"Really, it does."}' \
'}'
)
end
it "generates the proper 'root' node for serialized arrays" do
app.get('/blog_posts') do
blog_post = BlogPost.new(title: 'Grape AM::S Rocks!', body: 'Really, it does.')
blog_post = BlogPost.new(
title: 'Grape AM::S Rocks!',
body: 'Really, it does.'
)
[blog_post, blog_post]
end
get '/blog_posts'
expect(subject).to eql "{\"blog_posts\":[{\"title\":\"Grape AM::S Rocks!\",\"body\":\"Really, it does.\"},{\"title\":\"Grape AM::S Rocks!\",\"body\":\"Really, it does.\"}]}"
expect(subject).to eq(
'{"blog_posts":[' \
'{"title":"Grape AM::S Rocks!","body":"Really, it does."},' \
'{"title":"Grape AM::S Rocks!","body":"Really, it does."}' \
']}'
)
end
end
@ -86,7 +116,9 @@ describe Grape::ActiveModelSerializers do
end
get '/admin/jeff'
expect(subject).to eql "{\"user\":{\"first_name\":\"Jeff\",\"last_name\":null}}"
expect(subject).to eq(
'{"user":{"first_name":"Jeff","last_name":null}}'
)
end
context 'route is in a namespace' do
@ -99,7 +131,12 @@ describe Grape::ActiveModelSerializers do
end
get '/admin/jeff'
expect(subject).to eql "{\"admin\":[{\"first_name\":\"Jeff\",\"last_name\":null},{\"first_name\":\"Jeff\",\"last_name\":null}]}"
expect(subject).to eq(
'{"admin":[' \
'{"first_name":"Jeff","last_name":null},' \
'{"first_name":"Jeff","last_name":null}' \
']}'
)
end
end
@ -111,7 +148,12 @@ describe Grape::ActiveModelSerializers do
end
get '/people'
expect(subject).to eql "{\"people\":[{\"first_name\":\"Jeff\",\"last_name\":null},{\"first_name\":\"Jeff\",\"last_name\":null}]}"
expect(subject).to eq(
'{"people":[' \
'{"first_name":"Jeff","last_name":null},' \
'{"first_name":"Jeff","last_name":null}' \
']}'
)
end
end
end

View file

@ -15,4 +15,4 @@ RSpec.configure do |config|
config.include Rack::Test::Methods
end
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each { |f| require f }

View file

@ -0,0 +1,13 @@
class UsersApi < Grape::API
resource :users do
desc 'all users'
get do
[User.new]
end
desc 'specified user'
get '/:id' do
User.new
end
end
end

View file

@ -0,0 +1,15 @@
module V4
class UsersApi < Grape::API
resource :users do
desc 'all users'
get do
[User.new]
end
desc 'specified user'
get '/:id' do
User.new
end
end
end
end

View file

@ -1,7 +1,12 @@
class BlogPost
include ActiveModel::SerializerSupport
include ActiveModel::Serialization
attr_accessor :title, :body
def self.model_name
to_s
end
def initialize(params = {})
params.each do |k, v|
instance_variable_set("@#{k}", v) unless v.nil?

View file

@ -1,7 +1,12 @@
class User
include ActiveModel::SerializerSupport
include ActiveModel::Serialization
attr_accessor :first_name, :last_name, :password, :email
def self.model_name
to_s
end
def initialize(params = {})
params.each do |k, v|
instance_variable_set("@#{k}", v) unless v.nil?

View file

@ -0,0 +1,5 @@
module V2
class UserSerializer < ActiveModel::Serializer
attributes :first_name, :last_name, :email
end
end

View file

@ -0,0 +1,5 @@
module V3
class UserSerializer < ActiveModel::Serializer
attributes :first_name, :last_name, :email
end
end

View file

@ -0,0 +1,5 @@
module V4
class UserSerializer < ActiveModel::Serializer
attributes :first_name, :last_name, :email
end
end

View file

@ -0,0 +1,5 @@
module V5
class UserSerializer < ActiveModel::Serializer
attributes :first_name, :last_name, :email
end
end