Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
36f432d
Change to maintained fork of fasts_jsonapi
ekampp Jul 9, 2020
5e864a2
Switch to `jsonapi/serializer`.
stas Jul 9, 2020
750490a
Do not run rubocop on Rails 4.
stas Jul 9, 2020
45ccba6
Version bump.
stas Jul 9, 2020
01db1e9
Add Count of Records to Pagination (#29)
FinnLawrence Nov 26, 2020
c3255b8
Updated GH Actions config.
stas Nov 26, 2020
efa76c1
Updated rubocop bits.
stas Nov 26, 2020
5b0c51c
Loosen up jsonapi-serializer deps.
stas Nov 26, 2020
9dee9d8
Fixed failed tests.
stas Nov 26, 2020
3a31499
Use `#size` instead of `#count` for pagination
coffeejunk Nov 29, 2020
3a685a7
Total count and configurable default per_page (#31)
jkorz Dec 12, 2020
f107416
Add Rails 6.0 to test matrix
coffeejunk Feb 14, 2021
870a7f0
Handle Rails 6.1 ActiveModel::Error#detail
coffeejunk Feb 14, 2021
4f7d4b7
Expect different blank validation detail Rails 6.1
coffeejunk Feb 14, 2021
e62c5c3
Add Ruby 3.0 to test matrix (#39)
coffeejunk Feb 14, 2021
7cd3698
always call jsonapi_page_size method on pagination enabled requests (…
fluxsaas Feb 16, 2021
a28f6d4
Version bump.
stas Feb 17, 2021
edfb676
Fixed the readme example bug. Closes #48
stas Jun 15, 2021
fefefcf
Use base serializer to detect collections (#75)
dmolesUC Mar 15, 2022
b531b29
Make ransack a soft dependency. Closes #64
stas Mar 21, 2022
d701849
Make rubocop happy.
stas Mar 21, 2022
ae31acf
Allow running tests without code QA.
stas Mar 21, 2022
e0ab1cd
Use built-in version comparison API.
stas Mar 21, 2022
80a1e9b
Cleanup dependencies.
stas Mar 21, 2022
5f9def0
Updated the readme.
stas Mar 21, 2022
07aaf43
Added the sponsors. <3
stas Mar 21, 2022
cab94c7
Version bump.
stas Mar 21, 2022
ef685b1
Add Dependabot for GitHub Actions
petergoldstein Jun 28, 2022
c363207
Bump actions/checkout from 2 to 3
dependabot[bot] Sep 7, 2022
f9415aa
Fix deprecation warning with Content-Type header without modification
ydakuka Aug 9, 2021
5d50e67
Removing empty meta and link fields if data is empty
mylescc Apr 21, 2022
1f60bdf
Remove annoying ransack warning #84
gagalago Nov 14, 2022
a35c067
Cleanup supported CI envs.
stas Dec 5, 2022
4ea5624
Version bump.
stas Dec 5, 2022
c530c25
Provide example request for using sparse fields
xhs345 Dec 13, 2022
fd05f52
Adds Ruby 3.2 to the CI matrix.
petergoldstein Dec 26, 2022
07a96dc
Fix lints
petergoldstein Dec 26, 2022
c3b32db
Fix specs with Ransack 4.0
mamhoff Jul 21, 2023
886218e
Fix Rubocop offenses (#92)
mamhoff Oct 8, 2023
b1c2080
Bump actions/checkout from 3 to 4 (#96)
dependabot[bot] Oct 8, 2023
7a105a5
Added `code` to error attributes. Closes #95
stas Oct 8, 2023
796a362
Fix example code on filtering and sorting section
GabrielSandoval Mar 6, 2024
6ceac38
CI Matrix: Drop Rails 6, Ruby 2.7
mamhoff Jan 31, 2024
d2f94b5
Drop activerecord dependency (#102)
marclerodrigues Jun 23, 2024
52b4128
Refactor ActiveModel::ErrorSerializer (#98)
mamhoff Jun 23, 2024
5f52cdd
Allow decorating objects after pagination (#91)
mamhoff Jun 23, 2024
bac0727
Cleanup CI config.
stas Jun 23, 2024
87b5d00
Version bump.
stas Jun 23, 2024
e6f7501
Cleanup readme.
stas Jun 23, 2024
30f0149
updates specs to run on newer ruby versions
code-bunny Jan 20, 2025
0e25319
unescapes request.fullpath to match the remainder of the output when …
code-bunny Jan 20, 2025
260166c
Add meta field to error serializer
pelargir Jan 20, 2025
b5c6b60
Updated ci matrix.
stas Jan 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

26 changes: 13 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,26 @@ on: [push, pull_request]

jobs:
ruby_rails_test_matrix:
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.experimental }}

strategy:
matrix:
ruby: [2.4, 2.6]
rails: [4, 5, 6]
exclude:
- ruby: 2.4
rails: 6
ruby: ['3.3', '3.3', '3.4', '4']
rails: ['7.2', '8.0', '8']
experimental: [false]
include:
- rails: '7.1'
ruby: '3.3'
experimental: true

steps:
- uses: actions/checkout@master
- uses: actions/checkout@v6

- name: Sets up the environment
uses: actions/setup-ruby@v1
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true

- name: Runs code QA and tests
env:
Expand All @@ -29,8 +32,5 @@ jobs:
rm -rf Gemfile.lock
sudo apt-get update
sudo apt-get install libsqlite3-dev
gem uninstall bundler -a --force
gem install bundler -v '~> 1'
echo $RAILS_VERSION | grep -q '4' && export SQLITE3_VERSION='~> 1.3.6'
bundle
rake
bundle exec rake
9 changes: 9 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ inherit_gem:

require: rubocop-performance

AllCops:
NewCops: enable

Performance:
Enabled: true

Rails:
Enabled: true

Rails/Pluck:
Enabled: false

Rails/NegateInclude:
Enabled: false

Style/StringLiterals:
Enabled: true
EnforcedStyle: single_quotes
Expand Down
2 changes: 0 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,3 @@ source 'https://rubygems.org'

# Specify your gem's dependencies in jsonapi.gemspec
gemspec

gem 'jsonapi-rspec', git: 'https://github.com/jsonapi-rb/jsonapi-rspec.git'
39 changes: 34 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,33 @@ Main goals:

The available features include:

* object serialization (powered by Fast JSON API)
* object serialization (powered by JSON:API Serializer, was `fast_jsonapi`)
* [error handling](https://jsonapi.org/format/#errors) (parameters,
validation, generic errors)
* fetching of the data (support for
[includes](https://jsonapi.org/format/#fetching-includes) and
[sparse fields](https://jsonapi.org/format/#fetching-sparse-fieldsets))
* [filtering](https://jsonapi.org/format/#fetching-filtering) and
[sorting](https://jsonapi.org/format/#fetching-sorting) of the data
(powered by Ransack)
(powered by Ransack, soft-dependency)
* [pagination](https://jsonapi.org/format/#fetching-pagination) support

## But how?

Mainly by leveraging [Fast JSON API](https://github.com/Netflix/fast_jsonapi)
Mainly by leveraging [JSON:API Serializer](https://github.com/jsonapi-serializer/jsonapi-serializer)
and [Ransack](https://github.com/activerecord-hackery/ransack).

Thanks to everyone who worked on these amazing projects!

## Sponsors

I'm grateful for the following companies for supporting this project!

<p align="center">
<a href="https://www.luneteyewear.com"><img src="https://user-images.githubusercontent.com/112147/136836142-2bfba96e-447f-4eb6-b137-2445aee81b37.png"/></a>
</p>


## Installation

Add this line to your application's Gemfile:
Expand Down Expand Up @@ -100,7 +109,7 @@ The naming scheme follows the `ModuleName::ClassNameSerializer` for an instance
of the `ModuleName::ClassName`.

Please follow the
[Fast JSON API guide](https://github.com/Netflix/fast_jsonapi#serializer-definition)
[JSON:API Serializer guide](https://github.com/jsonapi-serializer/jsonapi-serializer#serializer-definition)
on how to define a serializer.

To provide a different naming scheme implement the `jsonapi_serializer_class`
Expand Down Expand Up @@ -223,6 +232,12 @@ class MyController < ActionController::Base
end
```

This allows you to run queries like:

```bash
$ curl -X GET /api/resources?fields[model]=model_attr,relationship
```

### Filtering and sorting

`JSONAPI::Filtering` uses the power of
Expand All @@ -231,6 +246,8 @@ to filter and sort over a collection of records.
The support is pretty extended and covers also relationships and composite
matchers.

Please add `ransack` to your `Gemfile` in order to benefit from this functionality!

Here's an example:

```ruby
Expand Down Expand Up @@ -264,7 +281,7 @@ grouping. To enable expressions along with filters, use the option flags:
```ruby
options = { sort_with_expressions: true }
jsonapi_filter(User.all, allowed_fields, options) do |filtered|
render jsonapi: result.group('id').to_a
render jsonapi: filtered.result.group('id').to_a
end
```

Expand All @@ -290,6 +307,7 @@ class MyController < ActionController::Base
render jsonapi: paginated
end
end

end
```

Expand All @@ -306,6 +324,17 @@ use the `jsonapi_pagination_meta` method:
end

```

If you want to change the default number of items per page or define a custom logic to handle page size, use the
`jsonapi_page_size` method:

```ruby
def jsonapi_page_size(pagination_params)
per_page = pagination_params[:size].to_f.to_i
per_page = 30 if per_page > 30 || per_page < 1
per_page
end
```
### Deserialization

`JSONAPI::Deserialization` provides a helper to transform a `JSONAPI` document
Expand Down
12 changes: 9 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ require 'rubocop/rake_task'
require 'yaml'
require 'yardstick'

# rubocop:disable Rails/RakeEnvironment
desc('Documentation stats and measurements')
task('qa:docs') do
yaml = YAML.load_file(File.expand_path('../.yardstick.yml', __FILE__))
Expand All @@ -13,6 +14,7 @@ task('qa:docs') do
coverage = Yardstick.round_percentage(measure.coverage * 100)
exit(1) if coverage < config.threshold
end
# rubocop:enable Rails/RakeEnvironment

desc('Codestyle check and linter')
RuboCop::RakeTask.new('qa:code') do |task|
Expand All @@ -24,7 +26,11 @@ RuboCop::RakeTask.new('qa:code') do |task|
end

desc('Run CI QA tasks')
task(qa: ['qa:docs', 'qa:code'])
if ENV['RAILS_VERSION'].to_s.include?('4')
task(qa: ['qa:docs'])
else
task(qa: ['qa:docs', 'qa:code'])
end

RSpec::Core::RakeTask.new(spec: :qa)
task(default: :spec)
RSpec::Core::RakeTask.new(:spec)
task(default: %w[qa spec])
20 changes: 12 additions & 8 deletions jsonapi.rb.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,29 @@ Gem::Specification.new do |spec|
spec.homepage = 'https://github.com/stas/jsonapi.rb'
spec.license = 'MIT'

spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
end
spec.files = Dir.glob('{lib,spec}/**/*', File::FNM_DOTMATCH)
spec.files += %w(LICENSE.txt README.md)
spec.require_paths = ['lib']

spec.add_dependency 'fast_jsonapi', '~> 1.5'
spec.add_dependency 'ransack'
spec.post_install_message = (
'Install manually `ransack` gem before using `JSONAPI::Filtering`!'
)

spec.add_dependency 'jsonapi-serializer'
spec.add_dependency 'rack'

spec.add_development_dependency 'bundler'
spec.add_development_dependency 'rails', ENV['RAILS_VERSION']
spec.add_development_dependency 'sqlite3', ENV['SQLITE3_VERSION']
spec.add_development_dependency 'ransack'
spec.add_development_dependency 'railties', ENV['RAILS_VERSION']
spec.add_development_dependency 'activerecord', ENV['RAILS_VERSION']
spec.add_development_dependency 'sqlite3', '~> 2.1'
spec.add_development_dependency 'ffaker'
spec.add_development_dependency 'rspec', '~> 3.0'
spec.add_development_dependency 'rspec-rails'
spec.add_development_dependency 'jsonapi-rspec'
spec.add_development_dependency 'yardstick'
spec.add_development_dependency 'rubocop-rails_config'
spec.add_development_dependency 'rubocop'
spec.add_development_dependency 'rubocop', ENV['RUBOCOP_VERSION']
spec.add_development_dependency 'simplecov'
spec.add_development_dependency 'rubocop-performance'
end
31 changes: 4 additions & 27 deletions lib/jsonapi/active_model_error_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
module JSONAPI
# [ActiveModel::Errors] serializer
class ActiveModelErrorSerializer < ErrorSerializer
set_id :object_id
set_type :error

attribute :status do
'422'
end
Expand All @@ -15,35 +12,15 @@ class ActiveModelErrorSerializer < ErrorSerializer
end

attribute :code do |object|
_, error_hash = object
code = error_hash[:error] unless error_hash[:error].is_a?(Hash)
code ||= error_hash[:message] || :invalid
# `parameterize` separator arguments are different on Rails 4 vs 5...
code.to_s.delete("''").parameterize.tr('-', '_')
object.type.to_s.delete("''").parameterize.tr('-', '_')
end

attribute :detail do |object, params|
error_key, error_hash = object
errors_object = params[:model].errors

# Rails 4 provides just the message.
if error_hash[:error].present? && error_hash[:error].is_a?(Hash)
message = errors_object.generate_message(
error_key, nil, error_hash[:error]
)
elsif error_hash[:error].present?
message = errors_object.generate_message(
error_key, error_hash[:error], error_hash
)
else
message = error_hash[:message]
end

errors_object.full_message(error_key, message)
attribute :detail do |object, _params|
object.full_message
end

attribute :source do |object, params|
error_key, _ = object
error_key = object.attribute
model_serializer = params[:model_serializer]
attrs = (model_serializer.attributes_to_serialize || {}).keys
rels = (model_serializer.relationships_to_serialize || {}).keys
Expand Down
2 changes: 1 addition & 1 deletion lib/jsonapi/deserialization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def jsonapi_deserialize(document, options = {})
rel_name = jsonapi_inflector.singularize(assoc_name)

if assoc_data.is_a?(Array)
parsed["#{rel_name}_ids"] = assoc_data.map { |ri| ri['id'] }.compact
parsed["#{rel_name}_ids"] = assoc_data.filter_map { |ri| ri['id'] }
next
end

Expand Down
13 changes: 9 additions & 4 deletions lib/jsonapi/error_serializer.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
require 'fast_jsonapi'
require 'jsonapi/serializer'

module JSONAPI
# A simple error serializer
class ErrorSerializer
include FastJsonapi::ObjectSerializer
include JSONAPI::Serializer

set_id :object_id
set_type :error

# Object/Hash attribute helpers.
[:status, :source, :title, :detail].each do |attr_name|
[:status, :source, :title, :detail, :code, :meta].each do |attr_name|
attribute attr_name do |object|
object.try(attr_name) || object.try(:fetch, attr_name, nil)
end
end

# Overwrite the ID extraction method, to skip validations
#
# @return [NilClass]
def self.id_from_record(_record, _params)
end

# Remap the root key to `errors`
#
# @return [Hash]
Expand Down
2 changes: 1 addition & 1 deletion lib/jsonapi/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def render_jsonapi_not_found(exception)
render jsonapi_errors: [error], status: :not_found
end

# Unprocessable entity (422) error handler callback
# Unprocessable Content (422) error handler callback
#
# @param exception [Exception] instance to handle
# @return [String] JSONAPI error response
Expand Down
4 changes: 2 additions & 2 deletions lib/jsonapi/fetching.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def jsonapi_fields
end

params[:fields].each do |k, v|
extracted[k] = v.to_s.split(',').map(&:strip).compact
extracted[k] = v.to_s.split(',').filter_map(&:strip)
end

extracted
Expand All @@ -29,7 +29,7 @@ def jsonapi_fields
#
# @return [Array]
def jsonapi_include
params['include'].to_s.split(',').map(&:strip).compact
params['include'].to_s.split(',').filter_map(&:strip)
end
end
end
7 changes: 5 additions & 2 deletions lib/jsonapi/filtering.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
require 'ransack/predicate'
require_relative 'patches'
begin
require 'ransack/predicate'
require_relative 'patches'
rescue LoadError
end

# Filtering and sorting support
module JSONAPI
Expand Down
Loading