diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..b02f6a0a1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,74 @@ +--- + +name: CI + +on: [push, pull_request] + +jobs: + test: + name: "Testing" + runs-on: ubuntu-18.04 + strategy: + fail-fast: false + matrix: + include: + # Recent Rubies and Rails + - ruby-version: '3.1' + rails-version: '7.0' + - ruby-version: '3.0' + rails-version: '7.0' + - ruby-version: '2.7' + rails-version: '7.0' + - ruby-version: '2.6' + rails-version: '6.1' + - ruby-version: '2.6' + rails-version: '6.0' + - ruby-version: '2.7' + rails-version: '6.0' + - ruby-version: '2.6' + rails-version: '5.2' + # Old Rubies and Rails + - ruby-version: '2.5' + rails-version: '5.1' + bundler: '1' + - ruby-version: '2.4' + rails-version: '5.0' + bundler: '1' + - ruby-version: '2.4' + rails-version: '4.2' + bundler: '1' + # Failing with a stack trace in active support + # - ruby-version: '2.4' + # rails-version: '4.1' + # bundler: '1' + + continue-on-error: "${{ endsWith(matrix.ruby-version, 'head') }}" + + env: + CI: "1" + + steps: + - name: "Checkout Code" + uses: "actions/checkout@v2" + timeout-minutes: 5 + with: + fetch-depth: 0 + + - name: Install required libs + run: | + sudo apt-get -yqq install libsqlite3-dev + + - name: "Build Ruby" + uses: ruby/setup-ruby@v1 + with: + ruby-version: "${{ matrix.ruby-version }}" + bundler: "${{ matrix.bundler || 2 }}" + bundler-cache: true + env: + RAILS_VERSION: ${{ matrix.rails-version }} + + - name: "Run tests" + run: | + bundle exec rake + env: + RAILS_VERSION: ${{ matrix.rails-version }} diff --git a/.rubocop.yml b/.rubocop.yml index 82f076564..6dff73574 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -59,6 +59,10 @@ Style/MissingElse: Style/EmptyElse: EnforcedStyle: empty +Style/FrozenStringLiteralComment: + Enabled: true + EnforcedStyle: always + Style/MultilineOperationIndentation: EnforcedStyle: indented diff --git a/.simplecov b/.simplecov deleted file mode 100644 index 955a60606..000000000 --- a/.simplecov +++ /dev/null @@ -1,110 +0,0 @@ -# https://github.com/colszowka/simplecov#using-simplecov-for-centralized-config -# see https://github.com/colszowka/simplecov/blob/master/lib/simplecov/defaults.rb -# vim: set ft=ruby - -## DEFINE VARIABLES -@minimum_coverage = ENV.fetch('COVERAGE_MINIMUM') { - case (defined?(RUBY_ENGINE) && RUBY_ENGINE) || "ruby" - when 'jruby', 'rbx' - 96.0 - else - 98.1 - end -}.to_f.round(2) -# rubocop:disable Style/DoubleNegation -ENV['FULL_BUILD'] ||= ENV['CI'] -@running_ci = !!(ENV['FULL_BUILD'] =~ /\Atrue\z/i) -@generate_report = @running_ci || !!(ENV['COVERAGE'] =~ /\Atrue\z/i) -@output = STDOUT -# rubocop:enable Style/DoubleNegation - -## CONFIGURE SIMPLECOV - -SimpleCov.profiles.define 'app' do - coverage_dir 'coverage' - load_profile 'test_frameworks' - - add_group 'Libraries', 'lib' - - add_group 'Long files' do |src_file| - src_file.lines.count > 100 - end - class MaxLinesFilter < SimpleCov::Filter - def matches?(source_file) - source_file.lines.count < filter_argument - end - end - add_group 'Short files', MaxLinesFilter.new(5) - - # Exclude these paths from analysis - add_filter '/config/' - add_filter '/db/' - add_filter 'tasks' - add_filter '/.bundle/' -end - -## START TRACKING COVERAGE (before activating SimpleCov) -require 'coverage' -Coverage.start - -## ADD SOME CUSTOM REPORTING AT EXIT -SimpleCov.at_exit do - next if $! and not ($!.kind_of? SystemExit and $!.success?) - - header = "#{'*' * 20} SimpleCov Results #{'*' * 20}" - results = SimpleCov.result.format!.join("\n") - exit_message = <<-EOF - -#{header} -{{RESULTS}} -{{FAILURE_MESSAGE}} - -#{'*' * header.size} - EOF - percent = Float(SimpleCov.result.covered_percent) - if percent < @minimum_coverage - failure_message = <<-EOF -Spec coverage was not high enough: #{percent.round(2)}% is < #{@minimum_coverage}% - EOF - exit_message.sub!('{{RESULTS}}', results).sub!('{{FAILURE_MESSAGE}}', failure_message) - @output.puts exit_message - abort(failure_message) if @generate_report - elsif @running_ci - exit_message.sub!('{{RESULTS}}', results).sub!('{{FAILURE_MESSAGE}}', <<-EOF) -Nice job! Spec coverage (#{percent.round(2)}%) is still at or above #{@minimum_coverage}% - EOF - @output.puts exit_message - end -end - -## CAPTURE CONFIG IN CLOSURE 'AppCoverage.start' -## to defer running until test/test_helper.rb is loaded. -# rubocop:disable Style/MultilineBlockChain -AppCoverage = Class.new do - def initialize(&block) - @block = block - end - - def start - @block.call - end -end.new do - SimpleCov.start 'app' - if @generate_report - if @running_ci - require 'codeclimate-test-reporter' - @output.puts '[COVERAGE] Running with SimpleCov Simple Formatter and CodeClimate Test Reporter' - formatters = [ - SimpleCov::Formatter::SimpleFormatter, - CodeClimate::TestReporter::Formatter - ] - else - @output.puts '[COVERAGE] Running with SimpleCov HTML Formatter' - formatters = [SimpleCov::Formatter::HTMLFormatter] - end - else - formatters = [] - end - SimpleCov.formatters = formatters -end -# rubocop:enable Style/MultilineBlockChain diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9aff1edcb..000000000 --- a/.travis.yml +++ /dev/null @@ -1,55 +0,0 @@ -language: ruby - -sudo: false - -rvm: - - 2.1 - - 2.2.6 - - 2.3.3 - - ruby-head - - jruby-9.1.5.0 # is precompiled per http://rubies.travis-ci.org/ - - jruby-head - -jdk: - - oraclejdk8 - -before_install: - - gem update --system - - rvm @global do gem uninstall bundler -a -x - - rvm @global do gem install bundler -v 1.13.7 -install: bundle install --path=vendor/bundle --retry=3 --jobs=3 -cache: - directories: - - vendor/bundle - -script: - - bundle exec rake ci -after_success: - - codeclimate-test-reporter -env: - global: - - "JRUBY_OPTS='--dev -J-Xmx1024M --debug'" - matrix: - - "RAILS_VERSION=4.1" - - "RAILS_VERSION=4.2" - - "RAILS_VERSION=5.0" - - "RAILS_VERSION=master" - -matrix: - exclude: - - rvm: 2.1 - env: RAILS_VERSION=master - - rvm: jruby-9.1.5.0 - env: RAILS_VERSION=master - - rvm: jruby-head - env: RAILS_VERSION=master - - rvm: 2.1 - env: RAILS_VERSION=5.0 - - rvm: jruby-9.1.5.0 - env: RAILS_VERSION=5.0 - - rvm: jruby-head - env: RAILS_VERSION=5.0 - allow_failures: - - rvm: ruby-head - - rvm: jruby-head - fast_finish: true diff --git a/CHANGELOG.md b/CHANGELOG.md index c975e4733..532a7ca7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.10.x -### [master (unreleased)](https://github.com/rails-api/active_model_serializers/compare/v0.10.6...master) +### [master (unreleased)](https://github.com/rails-api/active_model_serializers/compare/v0.10.13...0-10-stable) Breaking changes: @@ -10,6 +10,119 @@ Fixes: Misc: +### [v0.10.13 (2022-01013)](https://github.com/rails-api/active_model_serializers/compare/v0.10.12...v0.10.13) + +Fixes: + +- [#2399](https://github.com/rails-api/active_model_serializers/pull/2399) Handles edge case where requested current_page > total_pages (@f3z0) + +### [v0.10.12 (2020-12-10)](https://github.com/rails-api/active_model_serializers/compare/v0.10.11...v0.10.12) + +Fixes: + +- [#2398](https://github.com/rails-api/active_model_serializers/pull/2398) Update rails dependency to < 6.2 (@ritikesh) + +### [v0.10.11 (2020-12-04)](https://github.com/rails-api/active_model_serializers/compare/v0.10.10...v0.10.11) + +Features: + +- [#2361](https://github.com/rails-api/active_model_serializers/pull/2361) Add `ActiveModelSerializers.config.use_sha1_digests` to allow customization of the hashing algorithm used for serializer caching (@alexzherdev) + +Fixes: + +- [#2344](https://github.com/rails-api/active_model_serializers/pull/2344) Fixes #2341 introduced since #2223 (@wasifhossain) +- [#2395](https://github.com/rails-api/active_model_serializers/pull/2395) remove explicit require for thread_safe (@ritikesh) + +### [v0.10.10 (2019-07-13)](https://github.com/rails-api/active_model_serializers/compare/v0.10.9...v0.10.10) + +Fixes: + +- [#2319](https://github.com/rails-api/active_model_serializers/pull/2319) Fixes #2316. (@kylekeesling) + - Fix Rails 6.0 deprication warnings + - update test fixture schema to use `timestamps` instead of `timestamp` +- [#2223](https://github.com/rails-api/active_model_serializers/pull/2223) Support Fieldset in Attributes/JSON adapters documented in [docs/general/fields.md](https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/general/fields.md) that worked partially before (@bf4) +- [#2337](https://github.com/rails-api/active_model_serializers/pull/2337) fix incorrect belongs_to serialization when foreign_key on object and belongs_to is blank (@InteNs) + - Fixes incorrect json-api generation when `jsonapi_use_foreign_key_on_belongs_to_relationship` is `true` and the relationship is blank +- [#2172](https://github.com/rails-api/active_model_serializers/pull/2172) Preserve the namespace when falling back to a superclass serializer + +Misc: + +- [#2327](https://github.com/rails-api/active_model_serializers/pull/2327) Add support for Ruby 2.6 on Travis CI (@wasifhossain) +- [#2304](https://github.com/rails-api/active_model_serializers/pull/2304) Slim down bundled gem by excluding test files and docs (@greysteil) + +### [v0.10.9 (2019-02-08)](https://github.com/rails-api/active_model_serializers/compare/v0.10.8...v0.10.9) + +Fixes: + +- [#2288](https://github.com/rails-api/active_model_serializers/pull/2288) + Change the fetch method to deal with recyclable key cache strategy. + Fixes #2287. (@cintamani, @wasifhossain) +- [#2307](https://github.com/rails-api/active_model_serializers/pull/2307) Falsey attribute values should not be reevaluated. + +Misc: + +- [#2309](https://github.com/rails-api/active_model_serializers/pull/2309) Performance and memory usage fixes + +### [v0.10.8 (2018-11-01)](https://github.com/rails-api/active_model_serializers/compare/v0.10.7...v0.10.8) + +Features: +- [#2279](https://github.com/rails-api/active_model_serializers/pull/2279) Support condition options in serializer link statements + +Fixes: + +- [#2296](https://github.com/rails-api/active_model_serializers/pull/2296) Fixes #2295 (@Hirurg103) + - Fix finding of namespaced serializer and non-namespaced model. +- [#2289](https://github.com/rails-api/active_model_serializers/pull/2289) Fixes #2255 (@f-mer) + - Fix autoloading race condition, especially in Rails 5. +- [#2299](https://github.com/rails-api/active_model_serializers/pull/2299) Fixes #2270 (@chau-bao-long via #2276) + - Fix reflection thread-safety bug + +### [v0.10.7 (2017-11-14)](https://github.com/rails-api/active_model_serializers/compare/v0.10.6...v0.10.7) + +Regressions Fixed From v0.10.6: + +- [#2211](https://github.com/rails-api/active_model_serializers/pull/2211). Fixes #2125, #2160. (@bf4) + - Fix polymorphic belongs_to tests; passes on v0.10.5, fails on v0.10.6 + - Fix JSON:API polymorphic type regression from v0.10.5 + - Fix JSON:API: for_type_and_id should always inflect_type + ``` + Should Serializer._type ever be inflected? + Right now, it won't be, but association.serializer._type will be inflected. + + That's the current behavior. + ``` +- [#2216](https://github.com/rails-api/active_model_serializers/pull/2216). Fixes #2132, #2180. (@bf4) + - Fix JSON:API: Serialize resource type for unpersisted records (blank id) +- [#2218](https://github.com/rails-api/active_model_serializers/pull/2218). Fixes #2178. (@bf4) + - Fix JSON:API: Make using foreign key on belongs_to opt-in. No effect on polymorphic relationships. + ``` + # set to true to opt-in + ActiveModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship = true + ``` + +Features: + +- [#2136](https://github.com/rails-api/active_model_serializers/pull/2136) Enable inclusion of sideloaded relationship objects by `key`. (@caomania) +- [#2021](https://github.com/rails-api/active_model_serializers/pull/2021) ActiveModelSerializers::Model#attributes. Originally in [#1982](https://github.com/rails-api/active_model_serializers/pull/1982). (@bf4) +- [#2130](https://github.com/rails-api/active_model_serializers/pull/2130) Allow serialized ID to be overwritten for belongs-to relationships. (@greysteil) +- [#2189](https://github.com/rails-api/active_model_serializers/pull/2189) + Update version constraint for jsonapi-renderer to `['>= 0.1.1.beta1', '< 0.3']` + (@tagliala) + +Fixes: + +- [#2022](https://github.com/rails-api/active_model_serializers/pull/2022) Mutation of ActiveModelSerializers::Model now changes the attributes. Originally in [#1984](https://github.com/rails-api/active_model_serializers/pull/1984). (@bf4) +- [#2200](https://github.com/rails-api/active_model_serializers/pull/2200) Fix deserialization of polymorphic relationships. (@dennis95stumm) +- [#2214](https://github.com/rails-api/active_model_serializers/pull/2214) Fail if unable to infer collection type with json adapter. (@jmeredith16) +- [#2149](https://github.com/rails-api/active_model_serializers/pull/2149) Always include self, first, last pagination link. (@mecampbellsoup) +- [#2179](https://github.com/rails-api/active_model_serializers/pull/2179) Fixes #2173, Pass block to Enumerator.new. (@drn) + +Misc: + +- [#2176](https://github.com/rails-api/active_model_serializers/pull/2176) Documentation for global adapter config. (@mrpinsky) +- [#2215](https://github.com/rails-api/active_model_serializers/pull/2215) Update `serializers.md` documentation to denote alternate use cases for `scope`. (@stratigos) +- [#2212](https://github.com/rails-api/active_model_serializers/pull/2212) Remove legacy has_many_embed_ids test. (@bf4) + ### [v0.10.6 (2017-05-01)](https://github.com/rails-api/active_model_serializers/compare/v0.10.5...v0.10.6) Fixes: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4c006f456..0ebdf788f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,7 +51,7 @@ please adhere to these standards: - Provide a description of the changes contained in the pull request. - Note any specific areas that should be reviewed. - Include tests. -- The test suite must pass on [supported Ruby versions](.travis.yml) +- The test suite must pass on [supported Ruby versions](.github/workflows/ci.yml) - Include updates to the [documentation](https://github.com/rails-api/active_model_serializers/tree/master/docs) where applicable. - Update the @@ -79,7 +79,7 @@ and bundling gems. (save this script somewhere executable and run from top of A ```bash #!/usr/bin/env bash -rcommand='puts YAML.load_file("./.travis.yml")["env"]["matrix"].join(" ").gsub("RAILS_VERSION=", "")' +rcommand='puts YAML.load_file(".github/workflows/ci.yml").dig("jobs", "test", "strategy", "matrix", "include").map{|v| v["ruby-version"]}.join(" ")' versions=$(ruby -ryaml -e "$rcommand") for version in ${versions[@]}; do diff --git a/Gemfile b/Gemfile index e854a2048..94c0ca40c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,12 @@ +# frozen_string_literal: true + source 'https://rubygems.org' + +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?('/') + "https://github.com/#{repo_name}.git" +end + # # Add a Gemfile.local to locally bundle gems outside of version control local_gemfile = File.join(File.expand_path('..', __FILE__), 'Gemfile.local') @@ -7,11 +15,12 @@ eval_gemfile local_gemfile if File.readable?(local_gemfile) # Specify your gem's dependencies in active_model_serializers.gemspec gemspec -version = ENV['RAILS_VERSION'] || '4.2' +version = ENV['RAILS_VERSION'] || '6.1' if version == 'master' gem 'rack', github: 'rack/rack' gem 'arel', github: 'rails/arel' + gem 'rails', github: 'rails/rails' git 'https://github.com/rails/rails.git' do gem 'railties' gem 'activesupport' @@ -23,6 +32,7 @@ if version == 'master' end else gem_version = "~> #{version}.0" + gem 'rails', gem_version gem 'railties', gem_version gem 'activesupport', gem_version gem 'activemodel', gem_version @@ -36,18 +46,43 @@ end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: (@windows_platforms + [:jruby]) +if ENV['CI'] + if RUBY_VERSION < '2.4' + # Windows: An error occurred while installing nokogiri (1.8.0) + gem 'nokogiri', '< 1.7', platforms: @windows_platforms + end +end + group :bench do # https://github.com/rails-api/active_model_serializers/commit/cb4459580a6f4f37f629bf3185a5224c8624ca76 gem 'benchmark-ips', '>= 2.7.2', require: false, group: :development end group :test do - gem 'sqlite3', platform: (@windows_platforms + [:ruby]) - gem 'activerecord-jdbcsqlite3-adapter', platform: :jruby + platforms(*(@windows_platforms + [:ruby])) do + if version == 'master' || version >= '6' + gem 'sqlite3', '~> 1.4' + else + gem 'sqlite3', '~> 1.3.13' + end + end + platforms :jruby do + if version == 'master' || version >= '6.0' + gem 'activerecord-jdbcsqlite3-adapter', github: 'jruby/activerecord-jdbc-adapter' + elsif version == '5.2' + gem 'activerecord-jdbcsqlite3-adapter', '~> 52.0' + elsif version == '5.1' + gem 'activerecord-jdbcsqlite3-adapter', '~> 51.0' + elsif version == '5.0' + gem 'activerecord-jdbcsqlite3-adapter', '~> 50.0' + else + gem 'activerecord-jdbcsqlite3-adapter', '~> 1.3.0' + end + end gem 'codeclimate-test-reporter', require: false gem 'm', '~> 1.5' - gem 'pry', '~> 0.10' - gem 'pry-byebug', '~> 3.4', platform: :ruby + gem 'pry', '>= 0.10' + gem 'byebug', '~> 8.2' if RUBY_VERSION < '2.2' end group :development, :test do diff --git a/README.md b/README.md index 5bdcd20d8..5ac89f625 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ Build Status - Build Status - Build status + Build Status + Build status @@ -13,7 +13,6 @@ Code Quality codebeat - Test Coverage @@ -41,7 +40,7 @@ these methods to the adapter.) By default ActiveModelSerializers will use the **Attributes Adapter** (no JSON root). But we strongly advise you to use **JsonApi Adapter**, which -follows 1.0 of the format specified in [jsonapi.org/format](http://jsonapi.org/format). +follows 1.0 of the format specified in [jsonapi.org/format](https://jsonapi.org/format). Check how to change the adapter in the sections below. `0.10.x` is **not** backward compatible with `0.9.x` nor `0.8.x`. @@ -49,8 +48,6 @@ Check how to change the adapter in the sections below. `0.10.x` is based on the `0.8.0` code, but with a more flexible architecture. We'd love your help. [Learn how you can help here.](CONTRIBUTING.md) -It is generally safe and recommended to use the master branch. - ## Installation Add this line to your application's Gemfile: @@ -77,9 +74,9 @@ More information is available in the [Guides](docs) and If you find a bug, please report an [Issue](https://github.com/rails-api/active_model_serializers/issues/new) and see our [contributing guide](CONTRIBUTING.md). -If you have a question, please [post to Stack Overflow](http://stackoverflow.com/questions/tagged/active-model-serializers). +If you have a question, please [post to Stack Overflow](https://stackoverflow.com/questions/tagged/active-model-serializers). -If you'd like to chat, we have a [community slack](http://amserializers.herokuapp.com). +If you'd like to chat, we have a [community slack](https://amserializers.herokuapp.com). Thanks! @@ -89,14 +86,14 @@ If you're reading this at https://github.com/rails-api/active_model_serializers reading documentation for our `master`, which may include features that have not been released yet. Please see below for the documentation relevant to you. -- [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master) -- [0.10.6 (latest release) Documentation](https://github.com/rails-api/active_model_serializers/tree/v0.10.6) - - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/active_model_serializers/0.10.6) - - [Guides](docs) +- [0.10 (0-10-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-10-stable) +- [0.10.10 (latest release) Documentation](https://github.com/rails-api/active_model_serializers/tree/v0.10.10) + - [![API Docs](https://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/gems/active_model_serializers/0.10.10) + - [Guides](docs) - [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable) - - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-9-stable) + - [![API Docs](https://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/gems/active_model_serializers/0.9.7) - [0.8 (0-8-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-8-stable) - - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-8-stable) + - [![API Docs](https://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/gems/active_model_serializers/0.8.4) ## High-level behavior @@ -173,12 +170,12 @@ The original design is also available [here](https://github.com/rails-api/active ### ActiveModel::Serializer -An **`ActiveModel::Serializer`** wraps a [serializable resource](https://github.com/rails/rails/blob/4-2-stable/activemodel/lib/active_model/serialization.rb) +An **`ActiveModel::Serializer`** wraps a [serializable resource](https://github.com/rails/rails/blob/master/activemodel/lib/active_model/serialization.rb) and exposes an `attributes` method, among a few others. It allows you to specify which attributes and associations should be represented in the serializatation of the resource. It requires an adapter to transform its attributes into a JSON document; it cannot be serialized itself. It may be useful to think of it as a -[presenter](http://blog.steveklabnik.com/posts/2011-09-09-better-ruby-presenters). +[presenter](https://blog.steveklabnik.com/posts/2011-09-09-better-ruby-presenters). #### ActiveModel::CollectionSerializer @@ -187,10 +184,10 @@ and, if there is no serializer, primitives. ### ActiveModelSerializers::Adapter::Base -The **`ActiveModelSerializeres::Adapter::Base`** describes the structure of the JSON document generated from a +The **`ActiveModelSerializers::Adapter::Base`** describes the structure of the JSON document generated from a serializer. For example, the `Attributes` example represents each serializer as its unmodified attributes. The `JsonApi` adapter represents the serializer as a [JSON -API](http://jsonapi.org/) document. +API](https://jsonapi.org/) document. ### ActiveModelSerializers::SerializableResource @@ -231,7 +228,7 @@ High-level overview: - `:each_serializer` specifies the serializer for each resource in the collection. - For a **single resource**, the `:serializer` option is the resource serializer. - Options are partitioned in serializer options and adapter options. Keys for adapter options are specified by - [`ADAPTER_OPTION_KEYS`](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/serializable_resource.rb#L5). + [`ADAPTER_OPTION_KEYS`](lib/active_model_serializers/serializable_resource.rb#L5). The remaining options are serializer options. Details: @@ -256,7 +253,7 @@ Details: 2. `adapter_instance = ActiveModel::Serializer::Adapter.create(serializer_instance, adapter_opts)` 1. **ActiveModel::Serializer::CollectionSerializer#new** 1. If the `serializer_instance` was a `CollectionSerializer` and the `:serializer` serializer_opts - is present, then [that serializer is passed into each resource](https://github.com/rails-api/active_model_serializers/blob/a54d237e2828fe6bab1ea5dfe6360d4ecc8214cd/lib/active_model/serializer/array_serializer.rb#L14-L16). + is present, then [that serializer is passed into each resource](https://github.com/rails-api/active_model_serializers/blob/0-10-stable/lib/active_model/serializer/collection_serializer.rb#L77-L79). 1. **ActiveModel::Serializer#attributes** is used by the adapter to get the attributes for resource as defined by the serializer. @@ -268,11 +265,11 @@ to know about, but not part of ActiveModelSerializers.) - An `ActiveRecord::Base` object. - Any Ruby object that passes the - [Lint](http://www.rubydoc.info/github/rails-api/active_model_serializers/ActiveModel/Serializer/Lint/Tests) - [code](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer/lint.rb). + [Lint](https://www.rubydoc.info/gems/active_model_serializers/ActiveModel/Serializer/Lint/Tests) + [(code)](lib/active_model/serializer/lint.rb). ActiveModelSerializers provides a -[`ActiveModelSerializers::Model`](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/model.rb), +[`ActiveModelSerializers::Model`](lib/active_model_serializers/model.rb), which is a simple serializable PORO (Plain-Old Ruby Object). `ActiveModelSerializers::Model` may be used either as a reference implementation, or in production code. @@ -300,7 +297,7 @@ ActiveModelSerializers::SerializableResource.new(MyModel.new(level: 'awesome'), ## Semantic Versioning -This project adheres to [semver](http://semver.org/) +This project adheres to [semver](https://semver.org/) ## Contributing diff --git a/Rakefile b/Rakefile index 6ba0c2bc9..e7bebff77 100644 --- a/Rakefile +++ b/Rakefile @@ -1,12 +1,10 @@ +# frozen_string_literal: true + begin require 'bundler/setup' rescue LoadError puts 'You must `gem install bundler` and `bundle install` to run rake tasks' end -begin - require 'simplecov' -rescue LoadError # rubocop:disable Lint/HandleExceptions -end import('lib/tasks/rubocop.rake') Bundler::GemHelper.install_tasks @@ -54,13 +52,20 @@ namespace :test do # https://github.com/rails/rails/blob/3d590add45/railties/lib/rails/generators/app_base.rb#L345-L363 _bundle_command = Gem.bin_path('bundler', 'bundle') require 'bundler' - Bundler.with_clean_env do + with_clean_env = proc do isolated_test_files.all? do |test_file| command = "-w -I#{dir}/lib -I#{dir}/test #{Shellwords.shellescape(test_file)}" full_command = %("#{Gem.ruby}" #{command}) system(full_command) end or fail 'Failures' # rubocop:disable Style/AndOr end + bundler_method = + if Bundler.respond_to?(:with_unbundled_env) + :with_unbundled_env + else + :with_clean_env + end + Bundler.public_send(bundler_method, &with_clean_env) end end diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec index 805c99c8a..2d1855e8d 100644 --- a/active_model_serializers.gemspec +++ b/active_model_serializers.gemspec @@ -1,3 +1,4 @@ +# frozen_string_literal: true # coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) @@ -14,14 +15,13 @@ Gem::Specification.new do |spec| spec.homepage = 'https://github.com/rails-api/active_model_serializers' spec.license = 'MIT' - spec.files = `git ls-files -z`.split("\x0") - spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.md', 'lib/**/*'] spec.require_paths = ['lib'] spec.executables = [] spec.required_ruby_version = '>= 2.1' - rails_versions = ['>= 4.1', '< 6'] + rails_versions = ['>= 4.1', '< 7.1'] spec.add_runtime_dependency 'activemodel', rails_versions # 'activesupport', rails_versions # 'builder' @@ -39,10 +39,10 @@ Gem::Specification.new do |spec| # 'activesupport', rails_versions # 'i18n, # 'tzinfo' - # 'minitest' + spec.add_development_dependency 'minitest', ['~> 5.0', '< 5.11'] # 'thread_safe' - spec.add_runtime_dependency 'jsonapi-renderer', ['>= 0.1.1.beta1', '< 0.2'] + spec.add_runtime_dependency 'jsonapi-renderer', ['>= 0.1.1.beta1', '< 0.3'] spec.add_runtime_dependency 'case_transform', '>= 0.2' spec.add_development_dependency 'activerecord', rails_versions @@ -54,10 +54,9 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'kaminari', ' ~> 0.16.3' spec.add_development_dependency 'will_paginate', '~> 3.0', '>= 3.0.7' - spec.add_development_dependency 'bundler', '~> 1.6' - spec.add_development_dependency 'simplecov', '~> 0.11' + spec.add_development_dependency 'bundler' spec.add_development_dependency 'timecop', '~> 0.7' - spec.add_development_dependency 'grape', ['>= 0.13', '< 0.19.1'] + spec.add_development_dependency 'grape', '>= 0.13' spec.add_development_dependency 'json_schema' - spec.add_development_dependency 'rake', ['>= 10.0', '< 12.0'] + spec.add_development_dependency 'rake', '>= 10.0' end diff --git a/appveyor.yml b/appveyor.yml index 7ecfa13ad..0901a78d8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,20 +4,22 @@ skip_tags: true environment: JRUBY_OPTS: "--dev -J-Xmx1024M --debug" + RAILS_VERSION: 5.2 matrix: - - ruby_version: "Ruby21" - - ruby_version: "Ruby21-x64" + - ruby_version: "Ruby23" + - ruby_version: "Ruby23-x64" cache: - vendor/bundle install: - SET PATH=C:\%ruby_version%\bin;%PATH% - - gem update --system - - gem uninstall bundler -a -x - - gem install bundler -v 1.13.7 + - gem uninstall bundler -x + - # gem update --system 2.7.9 + - gem install bundler -v '1.17.3' - bundle env - - bundle install --path=vendor/bundle --retry=3 --jobs=3 + - bundle check || bundle install --path=vendor/bundle --retry=3 --jobs=3 + - bundle clean --force before_test: - ruby -v diff --git a/docs/STYLE.md b/docs/STYLE.md index ccd75dd40..236c5138f 100644 --- a/docs/STYLE.md +++ b/docs/STYLE.md @@ -18,8 +18,6 @@ - [Improve code quality](https://codeclimate.com/github/rails-api/active_model_serializers/code?sort=smell_count&sort_direction=desc). -- [Improve amount of code exercised by tests](https://codeclimate.com/github/rails-api/active_model_serializers/coverage?sort=covered_percent&sort_direction=asc). - - [Fix RuboCop (Style) TODOS](https://github.com/rails-api/active_model_serializers/blob/master/.rubocop_todo.yml). - Delete and offsense, run `rake rubocop` (or possibly `rake rubocop:auto_correct`), and [submit a PR](CONTRIBUTING.md#submitting-a-pull-request-pr). diff --git a/docs/general/adapters.md b/docs/general/adapters.md index 84fc4e627..39d85f6b4 100644 --- a/docs/general/adapters.md +++ b/docs/general/adapters.md @@ -36,6 +36,12 @@ The `Attributes` adapter does not include a root key. It is just the serialized Use either the `JSON` or `JSON API` adapters if you want the response document to have a root key. +***IMPORTANT***: Adapter configuration has *no effect* on a serializer instance +being used directly. That is, `UserSerializer.new(user).as_json` will *always* +behave as if the adapter were the 'Attributes' adapter. See [Outside Controller +Usage](../howto/outside_controller_use.md) for more details on recommended +usage. + ## Built in Adapters ### Attributes - Default diff --git a/docs/general/configuration_options.md b/docs/general/configuration_options.md index 83f8890d7..0f9b2aaee 100644 --- a/docs/general/configuration_options.md +++ b/docs/general/configuration_options.md @@ -57,6 +57,7 @@ still prefer the render option `:key_transform` over this setting. application, setting `config.key_transform` to `:unaltered` will provide a performance boost.* ##### default_includes + What relationships to serialize by default. Default: `'*'`, which includes one level of related objects. See [includes](adapters.md#included) for more info. @@ -110,6 +111,18 @@ ActiveModelSerializers.config.serializer_lookup_chain.unshift( See [lookup_chain.rb](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/lookup_chain.rb) for further explanations and examples. +#### use_sha1_digests + +Determines which hashing algorithm to use internally when caching serializers. + +Possible values: + +- `true` +- `false` (default) + +When `true`, ActiveModelSerializers will use SHA1. Otherwise, it will default to using MD5. +Be warned that changing this value may result in serializer caches being invalidated. + ## JSON API ##### jsonapi_resource_type @@ -162,6 +175,21 @@ Default: `{}`. *Used when `jsonapi_include_toplevel_object` is `true`* +##### jsonapi_use_foreign_key_on_belongs_to_relationship + +When true, the relationship will determine its resource object identifier +without calling the association or its serializer. This can be useful when calling +the association object is triggering unnecessary queries. + +For example, if a `comment` belongs to a `post`, and the comment +uses the foreign key `post_id`, we can determine the resource object +identifier `id` as `comment.post_id` and the `type` from the association options. +Or quite simply, it behaves as `belongs_to :post, type: :posts, foreign_key: :post_id`. + +Note: This option has *no effect* on polymorphic associations as we cannot reliably +determine the associated object's type without instantiating it. + +Default: `false`. ## Hooks diff --git a/docs/general/logging.md b/docs/general/logging.md index 321bf5d8b..f5c34dd17 100644 --- a/docs/general/logging.md +++ b/docs/general/logging.md @@ -16,6 +16,5 @@ ActiveModelSerializers.logger = Logger.new(STDOUT) You can also disable the logger, just put this in `config/initializers/active_model_serializers.rb`: ```ruby -require 'active_model_serializers' -ActiveSupport::Notifications.unsubscribe(ActiveModelSerializers::Logging::RENDER_EVENT) +ActiveModelSerializers.logger = Logger.new(IO::NULL) ``` diff --git a/docs/general/rendering.md b/docs/general/rendering.md index af2d886f5..2a7626fb5 100644 --- a/docs/general/rendering.md +++ b/docs/general/rendering.md @@ -290,4 +290,4 @@ See [Usage outside of a controller](../howto/outside_controller_use.md#serializi ## Pagination -See [How to add pagination links](https://github.com/rails-api/active_model_serializers/blob/master/docs/howto/add_pagination_links.md). +See [How to add pagination links](../howto/add_pagination_links.md). diff --git a/docs/general/serializers.md b/docs/general/serializers.md index 5b23ba0f4..f7f0ccc2f 100644 --- a/docs/general/serializers.md +++ b/docs/general/serializers.md @@ -65,7 +65,7 @@ Where: - `virtual_value:` - `polymorphic:` defines if polymorphic relation type should be nested in serialized association. - `type:` the resource type as used by JSON:API, especially on a `belongs_to` relationship. - - `class_name:` used to determine `type` when `type` not given + - `class_name:` the (String) model name used to determine `type`, when `type` is not given. e.g. `class_name: "Comment"` would imply the type `comments` - `foreign_key:` used by JSON:API on a `belongs_to` relationship to avoid unnecessarily loading the association object. - `namespace:` used when looking up the serializer and `serializer` is not given. Falls back to the parent serializer's `:namespace` instance options, which, when present, comes from the render options. See [Rendering#namespace](rendering.md#namespace] for more details. - optional: `&block` is a context that returns the association's attributes. @@ -81,6 +81,7 @@ e.g. ```ruby has_one :bio has_one :blog, key: :site +has_one :blog, class_name: "Blog" has_one :maker, virtual_value: { id: 1 } has_one :blog do |serializer| @@ -114,6 +115,7 @@ e.g. has_many :comments has_many :comments, key: :reviews has_many :comments, serializer: CommentPreviewSerializer +has_many :comments, class_name: "Comment" has_many :reviews, virtual_value: [{ id: 1 }, { id: 2 }] has_many :comments, key: :last_comments do last(1) @@ -127,6 +129,7 @@ e.g. ```ruby belongs_to :author, serializer: AuthorPreviewSerializer belongs_to :author, key: :writer +belongs_to :author, class_name: "Author" belongs_to :post belongs_to :blog def blog @@ -235,6 +238,15 @@ link :other, 'https://example.com/resource' link(:posts) { link_author_posts_url(object) } ``` +Just like attributes, links also support conditions in options +```ruby +link(:secret, if: :internal?) { object.secret_link } + +def internal? + instance_options[:context] == :internal +end +``` + #### #object The object being serialized. @@ -294,6 +306,8 @@ end Whether you write the method as above or as `object.comments.where(created_by: scope)` is a matter of preference (assuming `scope_name` has been set). +Keep in mind that the scope can be set to any available controller reference. This can be utilized to provide access to any other data scopes or presentation helpers. + ##### Controller Authorization Context In the controller, the scope/scope_name options are equal to @@ -311,7 +325,7 @@ current authorization scope when you call `render :json`. called on every request. This was [also a problem](https://github.com/rails-api/active_model_serializers/pull/1252#issuecomment-159810477) in [`0.9`](https://github.com/rails-api/active_model_serializers/tree/0-9-stable#customizing-scope). -We can change the scope from `current_user` to `view_context`. +We can change the scope from `current_user` to `view_context`, which is included in subclasses of `ActionController::Base`. ```diff class SomeController < ActionController::Base @@ -379,6 +393,7 @@ class PostsController < ActionController::Base end end ``` +Note that any controller reference which provides the desired scope is acceptable, such as another controller method for loading a different resource or reference to helpers. For example, `ActionController::API` does not include `ActionView::ViewContext`, and would need a different reference for passing any helpers into a serializer via `serialization_scope`. #### #read_attribute_for_serialization(key) @@ -437,7 +452,7 @@ serializer classes. ## More Info -For more information, see [the Serializer class on GitHub](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer.rb) +For more information, see [the Serializer class on GitHub](https://github.com/rails-api/active_model_serializers/blob/0-10-stable/lib/active_model/serializer.rb) ## Overriding association methods @@ -471,7 +486,7 @@ the `ActiveModel::Serializer.serializer_for` method to return a serializer class ```ruby class MySerializer < ActiveModel::Serializer def self.serializer_for(model, options) - return SparseAdminSerializer if model.class == 'Admin' + return SparseAdminSerializer if model.class.name == 'Admin' super end diff --git a/docs/howto/add_root_key.md b/docs/howto/add_root_key.md index 82a4ab6fc..6d8c3269c 100644 --- a/docs/howto/add_root_key.md +++ b/docs/howto/add_root_key.md @@ -18,6 +18,13 @@ In order to add the root key you need to use the ```JSON``` Adapter, you can cha ActiveModelSerializers.config.adapter = :json ``` +Note that adapter configuration has no effect on a serializer that is called +directly, e.g. in a serializer unit test. Instead, something like +`UserSerializer.new(user).as_json` will *always* behave as if the adapter were +the 'Attributes' adapter. See [Outside Controller +Usage](../howto/outside_controller_use.md) for more details on recommended +usage. + You can also specify a class as adapter, as long as it complies with the ActiveModelSerializers adapters interface. It will add the root key to all your serialized endpoints. diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index ea84c6743..904c758cc 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/core_ext/class/attribute' require 'active_model_serializers/serialization_context' @@ -21,7 +23,15 @@ def serialization_scope(scope) end def namespace_for_serializer - @namespace_for_serializer ||= self.class.parent unless self.class.parent == Object + @namespace_for_serializer ||= namespace_for_class(self.class) unless namespace_for_class(self.class) == Object + end + + def namespace_for_class(klass) + if Module.method_defined?(:module_parent) + klass.module_parent + else + klass.parent + end end def serialization_scope diff --git a/lib/active_model/serializable_resource.rb b/lib/active_model/serializable_resource.rb index 0e1c8e2d2..75a8d505b 100644 --- a/lib/active_model/serializable_resource.rb +++ b/lib/active_model/serializable_resource.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'set' module ActiveModel diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 9d00e6fbf..45e929ed5 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -1,4 +1,5 @@ -require 'thread_safe' +# frozen_string_literal: true + require 'jsonapi/include_directive' require 'active_model/serializer/collection_serializer' require 'active_model/serializer/array_serializer' @@ -18,16 +19,17 @@ class Serializer # @see #serializable_hash for more details on these valid keys. SERIALIZABLE_HASH_VALID_KEYS = [:only, :except, :methods, :include, :root].freeze extend ActiveSupport::Autoload - autoload :Adapter - autoload :Null - autoload :Attribute - autoload :Association - autoload :Reflection - autoload :SingularReflection - autoload :CollectionReflection - autoload :BelongsToReflection - autoload :HasOneReflection - autoload :HasManyReflection + eager_autoload do + autoload :Adapter + autoload :Null + autoload :Attribute + autoload :Link + autoload :Association + autoload :Reflection + autoload :BelongsToReflection + autoload :HasOneReflection + autoload :HasManyReflection + end include ActiveSupport::Configurable include Caching @@ -70,7 +72,7 @@ def self.serializer_lookup_chain_for(klass, namespace = nil) # Used to cache serializer name => serializer class # when looked up by Serializer.get_serializer_for. def self.serializers_cache - @serializers_cache ||= ThreadSafe::Cache.new + @serializers_cache ||= Concurrent::Map.new end # @api private @@ -91,7 +93,7 @@ def self.get_serializer_for(klass, namespace = nil) if serializer_class serializer_class elsif klass.superclass - get_serializer_for(klass.superclass) + get_serializer_for(klass.superclass, namespace) else nil # No serializer found end @@ -142,7 +144,10 @@ def config.array_serializer # Make JSON API top-level jsonapi member opt-in # ref: http://jsonapi.org/format/#document-top-level config.jsonapi_include_toplevel_object = false + config.jsonapi_use_foreign_key_on_belongs_to_relationship = false config.include_data_default = true + # Raise ActiveModel::Serializer::CollectionSerializer::CannotInferRootKeyError when cannot infer root key from collection type + config.raise_cannot_infer_root_key_error = true # For configuring how serializers are found. # This should be an array of procs. @@ -274,9 +279,14 @@ def self.associate(reflection) # link(:self) { "http://example.com/resource/#{object.id}" } # @example # link :resource, "http://example.com/resource" + # @example + # link(:callback, if: :internal?), { "http://example.com/callback" } # - def self.link(name, value = nil, &block) - _links[name] = block || value + def self.link(name, *args, &block) + options = args.extract_options! + # For compatibility with the use case of passing link directly as string argument + # without block, we are creating a wrapping block + _links[name] = Link.new(name, options, block || ->(_serializer) { args.first }) end # Set the JSON API meta attribute of a serializer. @@ -337,10 +347,10 @@ def attributes(requested_attrs = nil, reload = false) # @return [Enumerator] def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil) include_slice ||= include_directive - return Enumerator.new unless object + return Enumerator.new {} unless object Enumerator.new do |y| - self.class._reflections.each do |key, reflection| + (self.instance_reflections ||= self.class._reflections.deep_dup).each do |key, reflection| next if reflection.excluded?(self) next unless include_directive.key?(key) @@ -356,6 +366,10 @@ def associations(include_directive = ActiveModelSerializers.default_include_dire def serializable_hash(adapter_options = nil, options = {}, adapter_instance = self.class.serialization_adapter_instance) adapter_options ||= {} options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options) + + fieldset = adapter_options[:fieldset] + options[:fields] = fieldset.fields_for(json_key) if fieldset + resource = attributes_hash(adapter_options, options, adapter_instance) relationships = associations_hash(adapter_options, options, adapter_instance) resource.merge(relationships) @@ -370,7 +384,12 @@ def as_json(adapter_opts = nil) # Used by adapter as resource root. def json_key - root || _type || object.class.model_name.to_s.underscore + root || _type || + begin + object.class.model_name.to_s.underscore + rescue ArgumentError + 'anonymous_object' + end end def read_attribute_for_serialization(attr) @@ -404,6 +423,6 @@ def associations_hash(adapter_options, options, adapter_instance) protected - attr_accessor :instance_options + attr_accessor :instance_options, :instance_reflections end end diff --git a/lib/active_model/serializer/adapter.rb b/lib/active_model/serializer/adapter.rb index 6b5f30ca7..8a8ee4192 100644 --- a/lib/active_model/serializer/adapter.rb +++ b/lib/active_model/serializer/adapter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_model_serializers/adapter' require 'active_model_serializers/deprecate' diff --git a/lib/active_model/serializer/adapter/attributes.rb b/lib/active_model/serializer/adapter/attributes.rb index e04e5fd8c..6d7602a20 100644 --- a/lib/active_model/serializer/adapter/attributes.rb +++ b/lib/active_model/serializer/adapter/attributes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer module Adapter diff --git a/lib/active_model/serializer/adapter/base.rb b/lib/active_model/serializer/adapter/base.rb index 013a9705a..34c39540a 100644 --- a/lib/active_model/serializer/adapter/base.rb +++ b/lib/active_model/serializer/adapter/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer module Adapter diff --git a/lib/active_model/serializer/adapter/json.rb b/lib/active_model/serializer/adapter/json.rb index 1998a4c65..404b0d8d6 100644 --- a/lib/active_model/serializer/adapter/json.rb +++ b/lib/active_model/serializer/adapter/json.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer module Adapter diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb index 13777cdc7..2a1186b2e 100644 --- a/lib/active_model/serializer/adapter/json_api.rb +++ b/lib/active_model/serializer/adapter/json_api.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer module Adapter diff --git a/lib/active_model/serializer/adapter/null.rb b/lib/active_model/serializer/adapter/null.rb index 906953d16..12f3ee85e 100644 --- a/lib/active_model/serializer/adapter/null.rb +++ b/lib/active_model/serializer/adapter/null.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer module Adapter diff --git a/lib/active_model/serializer/array_serializer.rb b/lib/active_model/serializer/array_serializer.rb index 2e768deb4..93b3beb8f 100644 --- a/lib/active_model/serializer/array_serializer.rb +++ b/lib/active_model/serializer/array_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_model/serializer/collection_serializer' module ActiveModel diff --git a/lib/active_model/serializer/association.rb b/lib/active_model/serializer/association.rb index 7ce82316d..7aeee338a 100644 --- a/lib/active_model/serializer/association.rb +++ b/lib/active_model/serializer/association.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_model/serializer/lazy_association' module ActiveModel diff --git a/lib/active_model/serializer/attribute.rb b/lib/active_model/serializer/attribute.rb index d3e006faa..f405ceb55 100644 --- a/lib/active_model/serializer/attribute.rb +++ b/lib/active_model/serializer/attribute.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_model/serializer/field' module ActiveModel diff --git a/lib/active_model/serializer/belongs_to_reflection.rb b/lib/active_model/serializer/belongs_to_reflection.rb index 04bbc6fc5..b299e87b0 100644 --- a/lib/active_model/serializer/belongs_to_reflection.rb +++ b/lib/active_model/serializer/belongs_to_reflection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer # @api private diff --git a/lib/active_model/serializer/collection_serializer.rb b/lib/active_model/serializer/collection_serializer.rb index 44b806a17..d60d331a6 100644 --- a/lib/active_model/serializer/collection_serializer.rb +++ b/lib/active_model/serializer/collection_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer class CollectionSerializer @@ -19,11 +21,10 @@ def success? # @api private def serializable_hash(adapter_options, options, adapter_instance) - include_directive = ActiveModel::Serializer.include_directive_from_options(adapter_options) - adapter_options[:cached_attributes] ||= ActiveModel::Serializer.cache_read_multi(self, adapter_instance, include_directive) - adapter_opts = adapter_options.merge(include_directive: include_directive) + options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options) + options[:cached_attributes] ||= ActiveModel::Serializer.cache_read_multi(self, adapter_instance, options[:include_directive]) serializers.map do |serializer| - serializer.serializable_hash(adapter_opts, options, adapter_instance) + serializer.serializable_hash(adapter_options, options, adapter_instance) end end @@ -46,7 +47,12 @@ def json_key # 3. get from collection name, if a named collection key ||= object.respond_to?(:name) ? object.name && object.name.underscore : nil # 4. key may be nil for empty collection and no serializer option - key && key.pluralize + key &&= key.pluralize + if raise_cannot_infer_root_key_error? + # 5. fail if the key cannot be determined + key || fail(CannotInferRootKeyError, 'Cannot infer root key from collection type. Please specify the root or each_serializer option, or render a JSON String') + end + key end # rubocop:enable Metrics/CyclomaticComplexity @@ -57,12 +63,18 @@ def paginated? object.respond_to?(:size) end + class CannotInferRootKeyError < StandardError; end + protected attr_reader :serializers, :options private + def raise_cannot_infer_root_key_error? + ActiveModelSerializers.config.raise_cannot_infer_root_key_error + end + def serializers_from_resources serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer) object.map do |resource| diff --git a/lib/active_model/serializer/concerns/caching.rb b/lib/active_model/serializer/concerns/caching.rb index 2a030b682..cdcd56852 100644 --- a/lib/active_model/serializer/concerns/caching.rb +++ b/lib/active_model/serializer/concerns/caching.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer UndefinedCacheKey = Class.new(StandardError) @@ -54,7 +56,8 @@ def _cache_digest def digest_caller_file(caller_line) serializer_file_path = caller_line[CALLER_FILE] serializer_file_contents = IO.read(serializer_file_path) - Digest::MD5.hexdigest(serializer_file_contents) + algorithm = ActiveModelSerializers.config.use_sha1_digests ? Digest::SHA1 : Digest::MD5 + algorithm.hexdigest(serializer_file_contents) rescue TypeError, Errno::ENOENT warn <<-EOF.strip_heredoc Cannot digest non-existent file: '#{caller_line}'. @@ -281,7 +284,9 @@ def expand_cache_key(parts) # Use object's cache_key if available, else derive a key from the object # Pass the `key` option to the `cache` declaration or override this method to customize the cache key def object_cache_key - if object.respond_to?(:cache_key) + if object.respond_to?(:cache_key_with_version) + object.cache_key_with_version + elsif object.respond_to?(:cache_key) object.cache_key elsif (serializer_cache_key = (serializer_class._cache_key || serializer_class._cache_options[:key])) object_time_safe = object.updated_at diff --git a/lib/active_model/serializer/error_serializer.rb b/lib/active_model/serializer/error_serializer.rb index d0e708091..6c6a8cdbd 100644 --- a/lib/active_model/serializer/error_serializer.rb +++ b/lib/active_model/serializer/error_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer class ErrorSerializer < ActiveModel::Serializer diff --git a/lib/active_model/serializer/errors_serializer.rb b/lib/active_model/serializer/errors_serializer.rb index 1fd924d54..34fee1d22 100644 --- a/lib/active_model/serializer/errors_serializer.rb +++ b/lib/active_model/serializer/errors_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_model/serializer/error_serializer' module ActiveModel diff --git a/lib/active_model/serializer/field.rb b/lib/active_model/serializer/field.rb index 6299b0990..32973bbd4 100644 --- a/lib/active_model/serializer/field.rb +++ b/lib/active_model/serializer/field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer # Holds all the meta-data about a field (i.e. attribute or association) as it was diff --git a/lib/active_model/serializer/fieldset.rb b/lib/active_model/serializer/fieldset.rb index efa3187c7..78609a4e5 100644 --- a/lib/active_model/serializer/fieldset.rb +++ b/lib/active_model/serializer/fieldset.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer class Fieldset @@ -10,7 +12,7 @@ def fields end def fields_for(type) - fields[type.singularize.to_sym] || fields[type.pluralize.to_sym] + fields[type.to_s.singularize.to_sym] || fields[type.to_s.pluralize.to_sym] end protected diff --git a/lib/active_model/serializer/has_many_reflection.rb b/lib/active_model/serializer/has_many_reflection.rb index 99f6f63cc..33d48d280 100644 --- a/lib/active_model/serializer/has_many_reflection.rb +++ b/lib/active_model/serializer/has_many_reflection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer # @api private diff --git a/lib/active_model/serializer/has_one_reflection.rb b/lib/active_model/serializer/has_one_reflection.rb index a385009bc..51e1523f1 100644 --- a/lib/active_model/serializer/has_one_reflection.rb +++ b/lib/active_model/serializer/has_one_reflection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer # @api private diff --git a/lib/active_model/serializer/lazy_association.rb b/lib/active_model/serializer/lazy_association.rb index 8c4dad612..ed7bd65c2 100644 --- a/lib/active_model/serializer/lazy_association.rb +++ b/lib/active_model/serializer/lazy_association.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer # @api private @@ -7,11 +9,12 @@ class Serializer delegate :collection?, to: :reflection def reflection_options - @reflection_options ||= reflection.options.dup.reject { |k, _| !REFLECTION_OPTIONS.include?(k) } + @reflection_options ||= reflection.options.select { |k, _| REFLECTION_OPTIONS.include?(k) } end def object - @object ||= reflection.value( + return @object if defined?(@object) + @object = reflection.value( association_options.fetch(:parent_serializer), association_options.fetch(:include_slice) ) @@ -76,6 +79,7 @@ def instantiate_serializer(object) serializer_options[:serializer_context_class] = association_options.fetch(:parent_serializer).class serializer = reflection_options.fetch(:serializer, nil) serializer_options[:serializer] = serializer if serializer + serializer_options[:namespace] = reflection_options[:namespace] if reflection_options[:namespace] serializer_class.new(object, serializer_options) end diff --git a/lib/active_model/serializer/link.rb b/lib/active_model/serializer/link.rb new file mode 100644 index 000000000..c54eb9121 --- /dev/null +++ b/lib/active_model/serializer/link.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'active_model/serializer/field' + +module ActiveModel + class Serializer + # Holds all the data about a serializer link + # + # @example + # class PostSerializer < ActiveModel::Serializer + # link :callback, if: :internal? do + # object.callback_link + # end + # + # def internal? + # instance_options[:internal] == true + # end + # end + # + class Link < Field + end + end +end diff --git a/lib/active_model/serializer/lint.rb b/lib/active_model/serializer/lint.rb index c40cebeb1..8c4ef8fcc 100644 --- a/lib/active_model/serializer/lint.rb +++ b/lib/active_model/serializer/lint.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer module Lint diff --git a/lib/active_model/serializer/null.rb b/lib/active_model/serializer/null.rb index 818bbbfa2..d36fca6e7 100644 --- a/lib/active_model/serializer/null.rb +++ b/lib/active_model/serializer/null.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer class Null < Serializer diff --git a/lib/active_model/serializer/reflection.rb b/lib/active_model/serializer/reflection.rb index 2e5cc2a16..170ea7713 100644 --- a/lib/active_model/serializer/reflection.rb +++ b/lib/active_model/serializer/reflection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_model/serializer/field' require 'active_model/serializer/association' @@ -85,8 +87,8 @@ def initialize(*) # meta ids: ids # end # end - def link(name, value = nil) - options[:links][name] = block_given? ? Proc.new : value + def link(name, value = nil, &block) + options[:links][name] = block_given? ? block : value :nil end @@ -100,8 +102,8 @@ def link(name, value = nil) # href object.blog.id.to_s # meta(id: object.blog.id) # end - def meta(value = nil) - options[:meta] = block_given? ? Proc.new : value + def meta(value = nil, &block) + options[:meta] = block_given? ? block : value :nil end @@ -140,7 +142,7 @@ def collection? def include_data?(include_slice) include_data_setting = options[:include_data_setting] case include_data_setting - when :if_sideloaded then include_slice.key?(name) + when :if_sideloaded then include_slice.key?(options.fetch(:key, name)) when true then true when false then false else fail ArgumentError, "Unknown include_data_setting '#{include_data_setting.inspect}'" @@ -151,6 +153,9 @@ def include_data?(include_slice) # @yield [ActiveModel::Serializer] # @return [:nil, associated resource or resource collection] def value(serializer, include_slice) + # NOTE(BF): This method isn't thread-safe because the _reflections class attribute is not thread-safe + # Therefore, when we build associations from reflections, we dup the entire reflection instance. + # Better solutions much appreciated! @object = serializer.object @scope = serializer.scope diff --git a/lib/active_model/serializer/version.rb b/lib/active_model/serializer/version.rb index e692240a3..96ba7a425 100644 --- a/lib/active_model/serializer/version.rb +++ b/lib/active_model/serializer/version.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + module ActiveModel class Serializer - VERSION = '0.10.6'.freeze + VERSION = '0.10.13'.freeze end end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 18cdd9f70..da9f9a040 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_model' require 'active_support' require 'active_support/core_ext/object/with_options' @@ -5,16 +7,19 @@ require 'active_support/json' module ActiveModelSerializers extend ActiveSupport::Autoload - autoload :Model - autoload :Callbacks - autoload :Deserialization - autoload :SerializableResource - autoload :Logging - autoload :Test - autoload :Adapter - autoload :JsonPointer - autoload :Deprecate - autoload :LookupChain + eager_autoload do + autoload :Model + autoload :Callbacks + autoload :SerializableResource + autoload :SerializationContext + autoload :Logging + autoload :Test + autoload :Adapter + autoload :JsonPointer + autoload :Deprecate + autoload :LookupChain + autoload :Deserialization + end class << self; attr_accessor :logger; end self.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) @@ -46,8 +51,13 @@ def self.silence_warnings $VERBOSE = original_verbose end + def self.eager_load! + super + ActiveModel::Serializer.eager_load! + end + require 'active_model/serializer/version' require 'active_model/serializer' require 'active_model/serializable_resource' - require 'active_model_serializers/railtie' if defined?(::Rails) + require 'active_model_serializers/railtie' if defined?(::Rails::Railtie) end diff --git a/lib/active_model_serializers/adapter.rb b/lib/active_model_serializers/adapter.rb index 98caab44f..274e5fee7 100644 --- a/lib/active_model_serializers/adapter.rb +++ b/lib/active_model_serializers/adapter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Adapter UnknownAdapterError = Class.new(ArgumentError) @@ -35,7 +37,7 @@ def adapter_map # @return [Array] list of adapter names def adapters - adapter_map.keys.sort + adapter_map.keys.sort! end # Adds an adapter 'klass' with 'name' to the 'adapter_map' diff --git a/lib/active_model_serializers/adapter/attributes.rb b/lib/active_model_serializers/adapter/attributes.rb index 79ca7b5ff..ff9fedd64 100644 --- a/lib/active_model_serializers/adapter/attributes.rb +++ b/lib/active_model_serializers/adapter/attributes.rb @@ -1,6 +1,16 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Adapter class Attributes < Base + def initialize(*) + super + + fields = instance_options.delete(:fields) + fieldset = fields_to_fieldset(fields) + instance_options[:fieldset] = ActiveModel::Serializer::Fieldset.new(fieldset) if fieldset + end + def serializable_hash(options = nil) options = serialization_options(options) options[:fields] ||= instance_options[:fields] @@ -8,6 +18,25 @@ def serializable_hash(options = nil) self.class.transform_key_casing!(serialized_hash, instance_options) end + + private + + def fields_to_fieldset(fields) + return if fields.nil? + + resource_fields = [] + relationship_fields = {} + + fields.each do |field| + case field + when Symbol, String then resource_fields << field + when Hash then relationship_fields.merge!(field) + else fail ArgumentError, "Unknown conversion of fields to fieldset: '#{field.inspect}' in '#{fields.inspect}'" + end + end + + relationship_fields.merge!(serializer.json_key.to_sym => resource_fields) + end end end end diff --git a/lib/active_model_serializers/adapter/base.rb b/lib/active_model_serializers/adapter/base.rb index 851583285..9d5f4b6a1 100644 --- a/lib/active_model_serializers/adapter/base.rb +++ b/lib/active_model_serializers/adapter/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'case_transform' module ActiveModelSerializers diff --git a/lib/active_model_serializers/adapter/json.rb b/lib/active_model_serializers/adapter/json.rb index 423cfb9fb..78a4a5518 100644 --- a/lib/active_model_serializers/adapter/json.rb +++ b/lib/active_model_serializers/adapter/json.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Adapter class Json < Base diff --git a/lib/active_model_serializers/adapter/json_api.rb b/lib/active_model_serializers/adapter/json_api.rb index b225416be..187319ed5 100644 --- a/lib/active_model_serializers/adapter/json_api.rb +++ b/lib/active_model_serializers/adapter/json_api.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # {http://jsonapi.org/format/ JSON API specification} # rubocop:disable Style/AsciiComments # TODO: implement! @@ -22,14 +24,16 @@ module ActiveModelSerializers module Adapter class JsonApi < Base extend ActiveSupport::Autoload - autoload :Jsonapi - autoload :ResourceIdentifier - autoload :Relationship - autoload :Link - autoload :PaginationLinks - autoload :Meta - autoload :Error - autoload :Deserialization + eager_autoload do + autoload :Jsonapi + autoload :ResourceIdentifier + autoload :Link + autoload :PaginationLinks + autoload :Meta + autoload :Error + autoload :Deserialization + autoload :Relationship + end def self.default_key_transform :dash @@ -49,7 +53,8 @@ def self.fragment_cache(cached_hash, non_cached_hash, root = true) def initialize(serializer, options = {}) super @include_directive = JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true) - @fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields)) + option_fields = options.delete(:fields) + @fieldset = ActiveModel::Serializer::Fieldset.new(option_fields) if option_fields end # {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure} @@ -344,7 +349,8 @@ def data_for(serializer, include_slice) data.tap do |resource_object| next if resource_object.nil? # NOTE(BF): the attributes are cached above, separately from the relationships, below. - requested_associations = fieldset.fields_for(resource_object[:type]) || '*' + requested_fields = fieldset && fieldset.fields_for(resource_object[:type]) + requested_associations = requested_fields || '*' relationships = relationships_for(serializer, requested_associations, include_slice) resource_object[:relationships] = relationships if relationships.any? end @@ -480,7 +486,8 @@ def relationships_for(serializer, requested_associations, include_slice) # }.reject! {|_,v| v.nil? } def links_for(serializer) serializer._links.each_with_object({}) do |(name, value), hash| - result = Link.new(serializer, value).as_json + next if value.excluded?(serializer) + result = Link.new(serializer, value.block).as_json hash[name] = result if result end end diff --git a/lib/active_model_serializers/adapter/json_api/deserialization.rb b/lib/active_model_serializers/adapter/json_api/deserialization.rb index b79125ac4..1af20d94a 100644 --- a/lib/active_model_serializers/adapter/json_api/deserialization.rb +++ b/lib/active_model_serializers/adapter/json_api/deserialization.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Adapter class JsonApi @@ -189,7 +191,7 @@ def parse_relationship(assoc_name, assoc_data, options) polymorphic = (options[:polymorphic] || []).include?(assoc_name.to_sym) if polymorphic - hash["#{prefix_key}_type".to_sym] = assoc_data.present? ? assoc_data['type'] : nil + hash["#{prefix_key}_type".to_sym] = assoc_data.present? ? assoc_data['type'].classify : nil end hash diff --git a/lib/active_model_serializers/adapter/json_api/error.rb b/lib/active_model_serializers/adapter/json_api/error.rb index c7b18716c..8f1d32b5b 100644 --- a/lib/active_model_serializers/adapter/json_api/error.rb +++ b/lib/active_model_serializers/adapter/json_api/error.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Adapter class JsonApi < Base diff --git a/lib/active_model_serializers/adapter/json_api/jsonapi.rb b/lib/active_model_serializers/adapter/json_api/jsonapi.rb index e94578af6..cb24f0273 100644 --- a/lib/active_model_serializers/adapter/json_api/jsonapi.rb +++ b/lib/active_model_serializers/adapter/json_api/jsonapi.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Adapter class JsonApi < Base diff --git a/lib/active_model_serializers/adapter/json_api/link.rb b/lib/active_model_serializers/adapter/json_api/link.rb index 64e15071a..6fa9f5299 100644 --- a/lib/active_model_serializers/adapter/json_api/link.rb +++ b/lib/active_model_serializers/adapter/json_api/link.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Adapter class JsonApi diff --git a/lib/active_model_serializers/adapter/json_api/meta.rb b/lib/active_model_serializers/adapter/json_api/meta.rb index d889b3eb8..02cf20989 100644 --- a/lib/active_model_serializers/adapter/json_api/meta.rb +++ b/lib/active_model_serializers/adapter/json_api/meta.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Adapter class JsonApi diff --git a/lib/active_model_serializers/adapter/json_api/pagination_links.rb b/lib/active_model_serializers/adapter/json_api/pagination_links.rb index b15f5ba68..a41153a6e 100644 --- a/lib/active_model_serializers/adapter/json_api/pagination_links.rb +++ b/lib/active_model_serializers/adapter/json_api/pagination_links.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Adapter class JsonApi < Base @@ -15,17 +17,18 @@ def initialize(collection, adapter_options) JsonApi::PaginationLinks requires a ActiveModelSerializers::SerializationContext. Please pass a ':serialization_context' option or override CollectionSerializer#paginated? to return 'false'. - EOF + EOF end end def as_json - per_page = collection.try(:per_page) || collection.try(:limit_value) || collection.size - pages_from.each_with_object({}) do |(key, value), hash| - params = query_parameters.merge(page: { number: value, size: per_page }).to_query - - hash[key] = "#{url(adapter_options)}?#{params}" - end + { + self: location_url, + first: first_page_url, + prev: prev_page_url, + next: next_page_url, + last: last_page_url + } end protected @@ -34,25 +37,43 @@ def as_json private - def pages_from - return {} if collection.total_pages <= FIRST_PAGE + def location_url + url_for_page(collection.current_page) + end - {}.tap do |pages| - pages[:self] = collection.current_page + def first_page_url + url_for_page(1) + end - unless collection.current_page == FIRST_PAGE - pages[:first] = FIRST_PAGE - pages[:prev] = collection.current_page - FIRST_PAGE - end + def last_page_url + if collection.total_pages == 0 + url_for_page(FIRST_PAGE) + else + url_for_page(collection.total_pages) + end + end - unless collection.current_page == collection.total_pages - pages[:next] = collection.current_page + FIRST_PAGE - pages[:last] = collection.total_pages - end + def prev_page_url + return nil if collection.current_page == FIRST_PAGE + if collection.current_page > collection.total_pages + return url_for_page(collection.total_pages) end + url_for_page(collection.current_page - FIRST_PAGE) end - def url(options) + def next_page_url + return nil if collection.total_pages == 0 || + collection.current_page >= collection.total_pages + url_for_page(collection.next_page) + end + + def url_for_page(number) + params = query_parameters.dup + params[:page] = { size: per_page, number: number } + "#{url(adapter_options)}?#{params.to_query}" + end + + def url(options = {}) @url ||= options.fetch(:links, {}).fetch(:self, nil) || request_url end @@ -63,6 +84,10 @@ def request_url def query_parameters @query_parameters ||= context.query_parameters end + + def per_page + @per_page ||= collection.try(:per_page) || collection.try(:limit_value) || collection.size + end end end end diff --git a/lib/active_model_serializers/adapter/json_api/relationship.rb b/lib/active_model_serializers/adapter/json_api/relationship.rb index 5d7399a35..2879e3eb4 100644 --- a/lib/active_model_serializers/adapter/json_api/relationship.rb +++ b/lib/active_model_serializers/adapter/json_api/relationship.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Adapter class JsonApi @@ -43,10 +45,16 @@ def data_for(association) end def data_for_one(association) - if association.belongs_to? && - parent_serializer.object.respond_to?(association.reflection.foreign_key) - id = parent_serializer.object.send(association.reflection.foreign_key) - type = association.reflection.type.to_s + if belongs_to_id_on_self?(association) + id = parent_serializer.read_attribute_for_serialization(association.reflection.foreign_key) + type = + if association.polymorphic? + # We can't infer resource type for polymorphic relationships from the serializer. + # We can ONLY know a polymorphic resource type by inspecting each resource. + association.lazy_association.serializer.json_key + else + association.reflection.type.to_s + end ResourceIdentifier.for_type_with_id(type, id, serializable_resource_options) else # TODO(BF): Process relationship without evaluating lazy_association @@ -86,6 +94,12 @@ def meta_for(association) meta = association.meta meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta end + + def belongs_to_id_on_self?(association) + parent_serializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship && + association.belongs_to? && + parent_serializer.object.respond_to?(association.reflection.foreign_key) + end end end end diff --git a/lib/active_model_serializers/adapter/json_api/resource_identifier.rb b/lib/active_model_serializers/adapter/json_api/resource_identifier.rb index 3a235f2be..e321914ea 100644 --- a/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +++ b/lib/active_model_serializers/adapter/json_api/resource_identifier.rb @@ -1,33 +1,37 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Adapter class JsonApi class ResourceIdentifier - def self.type_for(class_name, serializer_type = nil, transform_options = {}) - if serializer_type - raw_type = serializer_type - else - inflection = - if ActiveModelSerializers.config.jsonapi_resource_type == :singular - :singularize - else - :pluralize - end - - raw_type = class_name.underscore - raw_type = ActiveSupport::Inflector.public_send(inflection, raw_type) - raw_type - .gsub!('/'.freeze, ActiveModelSerializers.config.jsonapi_namespace_separator) - raw_type - end + def self.type_for(serializer, serializer_type = nil, transform_options = {}) + raw_type = serializer_type ? serializer_type : raw_type_from_serializer_object(serializer.object) JsonApi.send(:transform_key_casing!, raw_type, transform_options) end def self.for_type_with_id(type, id, options) - return nil if id.blank? - { - id: id.to_s, - type: type_for(:no_class_needed, type, options) - } + type = inflect_type(type) + type = type_for(:no_class_needed, type, options) + if id.blank? + nil + else + { id: id.to_s, type: type } + end + end + + def self.raw_type_from_serializer_object(object) + class_name = object.class.name # should use model_name + raw_type = class_name.underscore + raw_type = inflect_type(raw_type) + raw_type + .gsub!('/'.freeze, ActiveModelSerializers.config.jsonapi_namespace_separator) + raw_type + end + + def self.inflect_type(type) + singularize = ActiveModelSerializers.config.jsonapi_resource_type == :singular + inflection = singularize ? :singularize : :pluralize + ActiveSupport::Inflector.public_send(inflection, type) end # {http://jsonapi.org/format/#document-resource-identifier-objects Resource Identifier Objects} @@ -37,8 +41,11 @@ def initialize(serializer, options) end def as_json - return nil if id.blank? - { id: id, type: type } + if id.blank? + { type: type } + else + { id: id.to_s, type: type } + end end protected @@ -48,7 +55,8 @@ def as_json private def type_for(serializer, transform_options) - self.class.type_for(serializer.object.class.name, serializer._type, transform_options) + serializer_type = serializer._type + self.class.type_for(serializer, serializer_type, transform_options) end def id_for(serializer) diff --git a/lib/active_model_serializers/adapter/null.rb b/lib/active_model_serializers/adapter/null.rb index 9e5faf5cb..307d0e157 100644 --- a/lib/active_model_serializers/adapter/null.rb +++ b/lib/active_model_serializers/adapter/null.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Adapter class Null < Base diff --git a/lib/active_model_serializers/callbacks.rb b/lib/active_model_serializers/callbacks.rb index 71237e4a6..a7c9b6676 100644 --- a/lib/active_model_serializers/callbacks.rb +++ b/lib/active_model_serializers/callbacks.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Adapted from # https://github.com/rails/rails/blob/7f18ea14c8/activejob/lib/active_job/callbacks.rb require 'active_support/callbacks' diff --git a/lib/active_model_serializers/deprecate.rb b/lib/active_model_serializers/deprecate.rb index e173321d3..012ef623f 100644 --- a/lib/active_model_serializers/deprecate.rb +++ b/lib/active_model_serializers/deprecate.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + ## # Provides a single method +deprecate+ to be used to declare when # something is going away. diff --git a/lib/active_model_serializers/deserialization.rb b/lib/active_model_serializers/deserialization.rb index 878dd98d1..0ac675364 100644 --- a/lib/active_model_serializers/deserialization.rb +++ b/lib/active_model_serializers/deserialization.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Deserialization module_function diff --git a/lib/active_model_serializers/json_pointer.rb b/lib/active_model_serializers/json_pointer.rb index a262f3b28..a72b9daf1 100644 --- a/lib/active_model_serializers/json_pointer.rb +++ b/lib/active_model_serializers/json_pointer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module JsonPointer module_function diff --git a/lib/active_model_serializers/logging.rb b/lib/active_model_serializers/logging.rb index 943e937e1..ad79743b4 100644 --- a/lib/active_model_serializers/logging.rb +++ b/lib/active_model_serializers/logging.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + ## # ActiveModelSerializers::Logging # diff --git a/lib/active_model_serializers/lookup_chain.rb b/lib/active_model_serializers/lookup_chain.rb index 25db8e138..0c99a4521 100644 --- a/lib/active_model_serializers/lookup_chain.rb +++ b/lib/active_model_serializers/lookup_chain.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module LookupChain # Standard appending of Serializer to the resource name. diff --git a/lib/active_model_serializers/model.rb b/lib/active_model_serializers/model.rb index 2ff3d60c5..646042792 100644 --- a/lib/active_model_serializers/model.rb +++ b/lib/active_model_serializers/model.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # ActiveModelSerializers::Model is a convenient superclass for making your models # from Plain-Old Ruby Objects (PORO). It also serves as a reference implementation # that satisfies ActiveModel::Serializer::Lint::Tests. diff --git a/lib/active_model_serializers/railtie.rb b/lib/active_model_serializers/railtie.rb index d6843c9c2..64a67c2d5 100644 --- a/lib/active_model_serializers/railtie.rb +++ b/lib/active_model_serializers/railtie.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails/railtie' require 'action_controller' require 'action_controller/railtie' @@ -5,6 +7,8 @@ module ActiveModelSerializers class Railtie < Rails::Railtie + config.eager_load_namespaces << ActiveModelSerializers + config.to_prepare do ActiveModel::Serializer.serializers_cache.clear end diff --git a/lib/active_model_serializers/register_jsonapi_renderer.rb b/lib/active_model_serializers/register_jsonapi_renderer.rb index 715c6ab3d..cd67e252c 100644 --- a/lib/active_model_serializers/register_jsonapi_renderer.rb +++ b/lib/active_model_serializers/register_jsonapi_renderer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Based on discussion in https://github.com/rails/rails/pull/23712#issuecomment-184977238, # the JSON API media type will have its own format/renderer. # diff --git a/lib/active_model_serializers/serializable_resource.rb b/lib/active_model_serializers/serializable_resource.rb index f67cf2385..eb9431d20 100644 --- a/lib/active_model_serializers/serializable_resource.rb +++ b/lib/active_model_serializers/serializable_resource.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'set' module ActiveModelSerializers @@ -14,8 +16,8 @@ class SerializableResource # @return the serializable_resource, ready for #as_json/#to_json/#serializable_hash. def initialize(resource, options = {}) @resource = resource - @adapter_opts, @serializer_opts = - options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] } + @adapter_opts = options.select { |k, _| ADAPTER_OPTION_KEYS.include? k } + @serializer_opts = options.reject { |k, _| ADAPTER_OPTION_KEYS.include? k } end def serialization_scope=(scope) diff --git a/lib/active_model_serializers/serialization_context.rb b/lib/active_model_serializers/serialization_context.rb index 9ef604f2e..a171e5f76 100644 --- a/lib/active_model_serializers/serialization_context.rb +++ b/lib/active_model_serializers/serialization_context.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/core_ext/array/extract_options' module ActiveModelSerializers class SerializationContext diff --git a/lib/active_model_serializers/test.rb b/lib/active_model_serializers/test.rb index bec452ec1..86d96aef7 100644 --- a/lib/active_model_serializers/test.rb +++ b/lib/active_model_serializers/test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Test extend ActiveSupport::Autoload diff --git a/lib/active_model_serializers/test/schema.rb b/lib/active_model_serializers/test/schema.rb index a00015869..a924277fa 100644 --- a/lib/active_model_serializers/test/schema.rb +++ b/lib/active_model_serializers/test/schema.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModelSerializers module Test module Schema diff --git a/lib/active_model_serializers/test/serializer.rb b/lib/active_model_serializers/test/serializer.rb index dc812c557..519720900 100644 --- a/lib/active_model_serializers/test/serializer.rb +++ b/lib/active_model_serializers/test/serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'set' module ActiveModelSerializers module Test diff --git a/lib/generators/rails/resource_override.rb b/lib/generators/rails/resource_override.rb index 5177a6369..8820624a7 100644 --- a/lib/generators/rails/resource_override.rb +++ b/lib/generators/rails/resource_override.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails/generators' require 'rails/generators/rails/resource/resource_generator' diff --git a/lib/generators/rails/serializer_generator.rb b/lib/generators/rails/serializer_generator.rb index e670d5cf6..7cb0d8c2a 100644 --- a/lib/generators/rails/serializer_generator.rb +++ b/lib/generators/rails/serializer_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators class SerializerGenerator < NamedBase diff --git a/lib/grape/active_model_serializers.rb b/lib/grape/active_model_serializers.rb index 8dc7a314a..0c0e69abe 100644 --- a/lib/grape/active_model_serializers.rb +++ b/lib/grape/active_model_serializers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # To add Grape support, require 'grape/active_model_serializers' in the base of your Grape endpoints # Then add 'include Grape::ActiveModelSerializers' to enable the formatter and helpers require 'active_model_serializers' diff --git a/lib/grape/formatters/active_model_serializers.rb b/lib/grape/formatters/active_model_serializers.rb index 534c5babf..98a1d7bee 100644 --- a/lib/grape/formatters/active_model_serializers.rb +++ b/lib/grape/formatters/active_model_serializers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # A Grape response formatter that can be used as 'formatter :json, Grape::Formatters::ActiveModelSerializers' # # Serializer options can be passed as a hash from your Grape endpoint using env[:active_model_serializer_options], diff --git a/lib/grape/helpers/active_model_serializers.rb b/lib/grape/helpers/active_model_serializers.rb index afbdab85a..fe100e528 100644 --- a/lib/grape/helpers/active_model_serializers.rb +++ b/lib/grape/helpers/active_model_serializers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Helpers can be included in your Grape endpoint as: helpers Grape::Helpers::ActiveModelSerializers module Grape diff --git a/lib/tasks/rubocop.rake b/lib/tasks/rubocop.rake index 5c9a1242f..396c83319 100644 --- a/lib/tasks/rubocop.rake +++ b/lib/tasks/rubocop.rake @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require 'rubocop' require 'rubocop/rake_task' diff --git a/test/action_controller/adapter_selector_test.rb b/test/action_controller/adapter_selector_test.rb index 3373de7c0..783e52ee3 100644 --- a/test/action_controller/adapter_selector_test.rb +++ b/test/action_controller/adapter_selector_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActionController diff --git a/test/action_controller/explicit_serializer_test.rb b/test/action_controller/explicit_serializer_test.rb index a23b6f6b9..2d39e7b68 100644 --- a/test/action_controller/explicit_serializer_test.rb +++ b/test/action_controller/explicit_serializer_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActionController @@ -69,13 +71,13 @@ def render_using_explicit_each_serializer def test_render_using_explicit_serializer get :render_using_explicit_serializer - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) assert_equal '{"name":"Name 1"}', @response.body end def test_render_array_using_explicit_serializer get :render_array_using_explicit_serializer - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) expected = [ { 'name' => 'Name 1' }, @@ -87,7 +89,7 @@ def test_render_array_using_explicit_serializer def test_render_array_using_implicit_serializer get :render_array_using_implicit_serializer - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) expected = [ { 'name' => 'Name 1' }, diff --git a/test/action_controller/json/include_test.rb b/test/action_controller/json/include_test.rb index 1fc8863e5..d4268721a 100644 --- a/test/action_controller/json/include_test.rb +++ b/test/action_controller/json/include_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActionController diff --git a/test/action_controller/json_api/deserialization_test.rb b/test/action_controller/json_api/deserialization_test.rb index 025f857b7..18f754dc4 100644 --- a/test/action_controller/json_api/deserialization_test.rb +++ b/test/action_controller/json_api/deserialization_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActionController @@ -45,7 +47,7 @@ def test_deserialization_of_relationship_only_object response = JSON.parse(@response.body) expected = { 'restriction_for_id' => '67', - 'restriction_for_type' => 'discounts', + 'restriction_for_type' => 'Discount', 'restricted_to_id' => nil, 'restricted_to_type' => nil } diff --git a/test/action_controller/json_api/errors_test.rb b/test/action_controller/json_api/errors_test.rb index 6da3c9ada..7c983ef51 100644 --- a/test/action_controller/json_api/errors_test.rb +++ b/test/action_controller/json_api/errors_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActionController diff --git a/test/action_controller/json_api/fields_test.rb b/test/action_controller/json_api/fields_test.rb index af87ad39a..ed20eed06 100644 --- a/test/action_controller/json_api/fields_test.rb +++ b/test/action_controller/json_api/fields_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActionController diff --git a/test/action_controller/json_api/linked_test.rb b/test/action_controller/json_api/linked_test.rb index 120197681..69d8974d7 100644 --- a/test/action_controller/json_api/linked_test.rb +++ b/test/action_controller/json_api/linked_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActionController @@ -80,7 +82,7 @@ def render_collection_without_include def render_collection_with_include setup_post - render json: [@post], adapter: :json_api, include: 'author, comments' + render json: [@post], adapter: :json_api, include: 'author,comments' end end diff --git a/test/action_controller/json_api/pagination_test.rb b/test/action_controller/json_api/pagination_test.rb index 0af086b7c..17f14bcdc 100644 --- a/test/action_controller/json_api/pagination_test.rb +++ b/test/action_controller/json_api/pagination_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'will_paginate/array' require 'kaminari' @@ -58,8 +60,10 @@ def test_render_pagination_links_with_will_paginate assert_equal expected_links, response['links'] end - def test_render_only_last_and_next_pagination_links + def test_render_only_first_last_and_next_pagination_links expected_links = { 'self' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=1&page%5Bsize%5D=2", + 'first' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=1&page%5Bsize%5D=2", + 'prev' => nil, 'next' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=2&page%5Bsize%5D=2", 'last' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=2&page%5Bsize%5D=2" } get :render_pagination_using_will_paginate, params: { page: { number: 1, size: 2 } } @@ -78,17 +82,21 @@ def test_render_pagination_links_with_kaminari assert_equal expected_links, response['links'] end - def test_render_only_prev_and_first_pagination_links + def test_render_only_prev_first_and_last_pagination_links expected_links = { 'self' => "#{KAMINARI_URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1", 'first' => "#{KAMINARI_URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1", - 'prev' => "#{KAMINARI_URI}?page%5Bnumber%5D=2&page%5Bsize%5D=1" } + 'prev' => "#{KAMINARI_URI}?page%5Bnumber%5D=2&page%5Bsize%5D=1", + 'next' => nil, + 'last' => "#{KAMINARI_URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1" } get :render_pagination_using_kaminari, params: { page: { number: 3, size: 1 } } response = JSON.parse(@response.body) assert_equal expected_links, response['links'] end - def test_render_only_last_and_next_pagination_links_with_additional_params + def test_render_only_first_last_and_next_pagination_links_with_additional_params expected_links = { 'self' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=1&page%5Bsize%5D=2&teste=additional", + 'first' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=1&page%5Bsize%5D=2&teste=additional", + 'prev' => nil, 'next' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=2&page%5Bsize%5D=2&teste=additional", 'last' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=2&page%5Bsize%5D=2&teste=additional" } get :render_pagination_using_will_paginate, params: { page: { number: 1, size: 2 }, teste: 'additional' } @@ -96,10 +104,12 @@ def test_render_only_last_and_next_pagination_links_with_additional_params assert_equal expected_links, response['links'] end - def test_render_only_prev_and_first_pagination_links_with_additional_params + def test_render_only_prev_first_and_last_pagination_links_with_additional_params expected_links = { 'self' => "#{KAMINARI_URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1&teste=additional", 'first' => "#{KAMINARI_URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1&teste=additional", - 'prev' => "#{KAMINARI_URI}?page%5Bnumber%5D=2&page%5Bsize%5D=1&teste=additional" } + 'prev' => "#{KAMINARI_URI}?page%5Bnumber%5D=2&page%5Bsize%5D=1&teste=additional", + 'next' => nil, + 'last' => "#{KAMINARI_URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1&teste=additional" } get :render_pagination_using_kaminari, params: { page: { number: 3, size: 1 }, teste: 'additional' } response = JSON.parse(@response.body) assert_equal expected_links, response['links'] diff --git a/test/action_controller/json_api/transform_test.rb b/test/action_controller/json_api/transform_test.rb index 69212f324..ffd1edc97 100644 --- a/test/action_controller/json_api/transform_test.rb +++ b/test/action_controller/json_api/transform_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActionController diff --git a/test/action_controller/lookup_proc_test.rb b/test/action_controller/lookup_proc_test.rb index 4d2ad0b10..1f7c7d864 100644 --- a/test/action_controller/lookup_proc_test.rb +++ b/test/action_controller/lookup_proc_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActionController diff --git a/test/action_controller/namespace_lookup_test.rb b/test/action_controller/namespace_lookup_test.rb index b5c8f496d..2fe961cfd 100644 --- a/test/action_controller/namespace_lookup_test.rb +++ b/test/action_controller/namespace_lookup_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActionController @@ -123,7 +125,12 @@ def namespace_set_by_request_headers tests Api::V3::LookupTestController setup do - @test_namespace = self.class.parent + @test_namespace = + if Module.method_defined?(:module_parent) + self.class.module_parent + else + self.class.parent + end end test 'uses request headers to determine the namespace' do diff --git a/test/action_controller/serialization_scope_name_test.rb b/test/action_controller/serialization_scope_name_test.rb index 3d767d049..32a04a58d 100644 --- a/test/action_controller/serialization_scope_name_test.rb +++ b/test/action_controller/serialization_scope_name_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module SerializationScopeTesting diff --git a/test/action_controller/serialization_test.rb b/test/action_controller/serialization_test.rb index dfd72b42e..2286309b4 100644 --- a/test/action_controller/serialization_test.rb +++ b/test/action_controller/serialization_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActionController @@ -5,6 +7,7 @@ module Serialization class ImplicitSerializerTest < ActionController::TestCase class ImplicitSerializationTestController < ActionController::Base include SerializationTesting + def render_using_implicit_serializer @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') render json: @profile @@ -73,8 +76,12 @@ def render_json_array_object_without_serializer render json: [{ error: 'Result is Invalid' }] end + # HACK: to prevent the resetting of instance variables after each request in Rails 7 + # see https://github.com/rails/rails/pull/43735 + def clear_instance_variables_between_requests; end + def update_and_render_object_with_cache_enabled - @post.updated_at = Time.zone.now + @post.updated_at = Time.zone.now # requires hack above to prevent `NoMethodError: undefined method `updated_at=' for nil:NilClass` generate_cached_serializer(@post) render json: @post @@ -153,7 +160,7 @@ def test_render_using_implicit_serializer description: 'Description 1' } - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) assert_equal expected.to_json, @response.body end @@ -172,7 +179,7 @@ def test_render_using_default_root } } - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) assert_equal expected.to_json, @response.body end @@ -181,7 +188,7 @@ def test_render_array_using_custom_root get :render_array_using_custom_root end expected = { custom_root: [{ name: 'Name 1', description: 'Description 1' }] } - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) assert_equal expected.to_json, @response.body end @@ -191,7 +198,7 @@ def test_render_array_that_is_empty_using_custom_root end expected = { custom_root: [] } - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) assert_equal expected.to_json, @response.body end @@ -201,14 +208,14 @@ def test_render_object_using_custom_root end expected = { custom_root: { name: 'Name 1', description: 'Description 1' } } - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) assert_equal expected.to_json, @response.body end def test_render_json_object_without_serializer get :render_json_object_without_serializer - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) expected_body = { error: 'Result is Invalid' } assert_equal expected_body.to_json, @response.body end @@ -216,14 +223,14 @@ def test_render_json_object_without_serializer def test_render_json_array_object_without_serializer get :render_json_array_object_without_serializer - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) expected_body = [{ error: 'Result is Invalid' }] assert_equal expected_body.to_json, @response.body end def test_render_array_using_implicit_serializer get :render_array_using_implicit_serializer - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) expected = [ { @@ -259,7 +266,7 @@ def test_render_array_using_implicit_serializer_and_meta } } - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) assert_equal expected.to_json, @response.body end @@ -282,7 +289,7 @@ def test_render_array_using_implicit_serializer_and_links } } - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) assert_equal expected.to_json, @response.body end @@ -311,7 +318,7 @@ def test_render_with_cache_enable Timecop.freeze(Time.zone.now) do get :render_object_with_cache_enabled - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) assert_equal expected.to_json, @response.body get :render_changed_object_with_cache_enabled @@ -347,7 +354,7 @@ def test_render_with_cache_enable_and_expired } } - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) actual = @response.body expected = expected.to_json if ENV['APPVEYOR'] && actual != expected @@ -362,7 +369,7 @@ def test_render_with_fragment_only_cache_enable get :render_fragment_changed_object_with_only_cache_enabled response = JSON.parse(@response.body) - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) assert_equal 'ZOMG A ROLE', response['name'] assert_equal 'HUEHUEBRBR', response['description'] end @@ -372,7 +379,7 @@ def test_render_with_fragment_except_cache_enable get :render_fragment_changed_object_with_except_cache_enabled response = JSON.parse(@response.body) - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) assert_equal 5, response['rating'] assert_equal 'lol', response['content'] end @@ -393,7 +400,7 @@ def test_render_fragment_changed_object_with_relationship } } - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) assert_equal expected_return, response end end @@ -424,7 +431,7 @@ def test_cache_expiration_on_update get :update_and_render_object_with_cache_enabled - assert_equal 'application/json', @response.content_type + assert_match(%r{\Aapplication/json}, @response.content_type) actual = @response.body expected = expected.to_json if ENV['APPVEYOR'] && actual != expected @@ -457,13 +464,19 @@ def use_adapter? end def test_render_event_is_emitted - subscriber = ::ActiveSupport::Notifications.subscribe('render.active_model_serializers') do |name| - @name = name + subscriber = ::ActiveSupport::Notifications.subscribe('render.active_model_serializers') do |subscribed_event| + @subscribed_event = subscribed_event end get :render_using_implicit_serializer - assert_equal 'render.active_model_serializers', @name + subscribed_event_name = + if @subscribed_event.is_a?(String) + @subscribed_event + else + @subscribed_event.name # is a ActiveSupport::Notifications::Event + end + assert_equal 'render.active_model_serializers', subscribed_event_name ensure ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber end diff --git a/test/active_model_serializers/adapter_for_test.rb b/test/active_model_serializers/adapter_for_test.rb index 1439b987c..19466dba5 100644 --- a/test/active_model_serializers/adapter_for_test.rb +++ b/test/active_model_serializers/adapter_for_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/active_model_serializers/json_pointer_test.rb b/test/active_model_serializers/json_pointer_test.rb index 60619ee6e..e31e5aac7 100644 --- a/test/active_model_serializers/json_pointer_test.rb +++ b/test/active_model_serializers/json_pointer_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/active_model_serializers/logging_test.rb b/test/active_model_serializers/logging_test.rb index 95e616827..ee4343934 100644 --- a/test/active_model_serializers/logging_test.rb +++ b/test/active_model_serializers/logging_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel diff --git a/test/active_model_serializers/model_test.rb b/test/active_model_serializers/model_test.rb index 6a8a29afb..a6723fa9e 100644 --- a/test/active_model_serializers/model_test.rb +++ b/test/active_model_serializers/model_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/active_model_serializers/railtie_test_isolated.rb b/test/active_model_serializers/railtie_test_isolated.rb index 1044fc8b9..5e4e2085a 100644 --- a/test/active_model_serializers/railtie_test_isolated.rb +++ b/test/active_model_serializers/railtie_test_isolated.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Execute this test in isolation require 'support/isolated_unit' diff --git a/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb b/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb index 30542408f..a5460aa38 100644 --- a/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +++ b/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'support/isolated_unit' require 'minitest/mock' require 'action_dispatch' diff --git a/test/active_model_serializers/serialization_context_test_isolated.rb b/test/active_model_serializers/serialization_context_test_isolated.rb index 5720e84a1..065d75eee 100644 --- a/test/active_model_serializers/serialization_context_test_isolated.rb +++ b/test/active_model_serializers/serialization_context_test_isolated.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Execute this test in isolation require 'support/isolated_unit' require 'minitest/mock' diff --git a/test/active_model_serializers/test/schema_test.rb b/test/active_model_serializers/test/schema_test.rb index 0fe497d78..4ff7ba5e3 100644 --- a/test/active_model_serializers/test/schema_test.rb +++ b/test/active_model_serializers/test/schema_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/active_model_serializers/test/serializer_test.rb b/test/active_model_serializers/test/serializer_test.rb index 38dc60ba1..d64eb7d38 100644 --- a/test/active_model_serializers/test/serializer_test.rb +++ b/test/active_model_serializers/test/serializer_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/active_record_test.rb b/test/active_record_test.rb index 5bb941a46..2538a5d9b 100644 --- a/test/active_record_test.rb +++ b/test/active_record_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ActiveRecordTest < ActiveSupport::TestCase diff --git a/test/adapter/attributes_test.rb b/test/adapter/attributes_test.rb index e60019f50..547f7791f 100644 --- a/test/adapter/attributes_test.rb +++ b/test/adapter/attributes_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/deprecation_test.rb b/test/adapter/deprecation_test.rb index ea858caa4..23adc0269 100644 --- a/test/adapter/deprecation_test.rb +++ b/test/adapter/deprecation_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel class Serializer diff --git a/test/adapter/json/belongs_to_test.rb b/test/adapter/json/belongs_to_test.rb index 0f096f0b3..83efa3442 100644 --- a/test/adapter/json/belongs_to_test.rb +++ b/test/adapter/json/belongs_to_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/json/collection_test.rb b/test/adapter/json/collection_test.rb index 8deb40500..cf488905d 100644 --- a/test/adapter/json/collection_test.rb +++ b/test/adapter/json/collection_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/json/fields_test.rb b/test/adapter/json/fields_test.rb new file mode 100644 index 000000000..5e1f570aa --- /dev/null +++ b/test/adapter/json/fields_test.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'test_helper' + +module ActiveModelSerializers + module Adapter + class Json + class FieldsTest < ActiveSupport::TestCase + class Post < ::Model + attributes :title, :body + associations :author, :comments + end + class Author < ::Model + attributes :name, :birthday + end + class Comment < ::Model + attributes :title, :body + associations :author, :post + end + + class PostSerializer < ActiveModel::Serializer + type 'post' + attributes :title, :body + belongs_to :author + has_many :comments + end + + class AuthorSerializer < ActiveModel::Serializer + attributes :name, :birthday + end + + class CommentSerializer < ActiveModel::Serializer + type 'comment' + attributes :title, :body + belongs_to :author + end + + def setup + @author = Author.new(id: 1, name: 'Lucas', birthday: '10.01.1990') + @comment1 = Comment.new(id: 7, body: 'cool', author: @author) + @comment2 = Comment.new(id: 12, body: 'awesome', author: @author) + @post = Post.new(id: 1337, title: 'Title 1', body: 'Body 1', + author: @author, comments: [@comment1, @comment2]) + @comment1.post = @post + @comment2.post = @post + end + + def test_fields_attributes + fields = [:title] + hash = serializable(@post, adapter: :json, fields: fields, include: []).serializable_hash + expected = { title: 'Title 1' } + assert_equal(expected, hash[:post]) + end + + def test_fields_included + fields = [:title, { comments: [:body] }] + hash = serializable(@post, adapter: :json, include: [:comments], fields: fields).serializable_hash + expected = [{ body: @comment1.body }, { body: @comment2.body }] + + assert_equal(expected, hash[:post][:comments]) + end + end + end + end +end diff --git a/test/adapter/json/has_many_test.rb b/test/adapter/json/has_many_test.rb index feeec93c3..0599b1397 100644 --- a/test/adapter/json/has_many_test.rb +++ b/test/adapter/json/has_many_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/json/transform_test.rb b/test/adapter/json/transform_test.rb index c102b5af1..5035c2d70 100644 --- a/test/adapter/json/transform_test.rb +++ b/test/adapter/json/transform_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers @@ -54,9 +56,8 @@ def test_transform_serialization_ctx_overrides_global_config def test_transform_undefined mock_request(:blam) - result = nil assert_raises NoMethodError do - result = @adapter.serializable_hash + @adapter.serializable_hash end end diff --git a/test/adapter/json_api/belongs_to_test.rb b/test/adapter/json_api/belongs_to_test.rb index ded83ab5c..aaed447b6 100644 --- a/test/adapter/json_api/belongs_to_test.rb +++ b/test/adapter/json_api/belongs_to_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/json_api/collection_test.rb b/test/adapter/json_api/collection_test.rb index e60a824e8..44c9b16fa 100644 --- a/test/adapter/json_api/collection_test.rb +++ b/test/adapter/json_api/collection_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/json_api/errors_test.rb b/test/adapter/json_api/errors_test.rb index cae7a5a6c..35ba855eb 100644 --- a/test/adapter/json_api/errors_test.rb +++ b/test/adapter/json_api/errors_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/json_api/fields_test.rb b/test/adapter/json_api/fields_test.rb index 852283187..3d3ff716d 100644 --- a/test/adapter/json_api/fields_test.rb +++ b/test/adapter/json_api/fields_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/json_api/has_many_embed_ids_test.rb b/test/adapter/json_api/has_many_embed_ids_test.rb deleted file mode 100644 index e016de284..000000000 --- a/test/adapter/json_api/has_many_embed_ids_test.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonApi - class HasManyEmbedIdsTest < ActiveSupport::TestCase - def setup - @author = Author.new(name: 'Steve K.') - @author.bio = nil - @author.roles = nil - @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') - @second_post = Post.new(id: 2, title: 'New Post', body: 'Body') - @author.posts = [@first_post, @second_post] - @first_post.author = @author - @second_post.author = @author - @first_post.comments = [] - @second_post.comments = [] - @blog = Blog.new(id: 23, name: 'AMS Blog') - @first_post.blog = @blog - @second_post.blog = nil - - @serializer = AuthorSerializer.new(@author) - @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer) - end - - def test_includes_comment_ids - expected = { - data: [ - { type: 'posts', id: '1' }, - { type: 'posts', id: '2' } - ] - } - - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:posts]) - end - - def test_no_includes_linked_comments - assert_nil @adapter.serializable_hash[:linked] - end - end - end - end -end diff --git a/test/adapter/json_api/has_many_explicit_serializer_test.rb b/test/adapter/json_api/has_many_explicit_serializer_test.rb index f598bc9b0..cb54756f2 100644 --- a/test/adapter/json_api/has_many_explicit_serializer_test.rb +++ b/test/adapter/json_api/has_many_explicit_serializer_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/json_api/has_many_test.rb b/test/adapter/json_api/has_many_test.rb index a9fa9ac92..54cdf2e01 100644 --- a/test/adapter/json_api/has_many_test.rb +++ b/test/adapter/json_api/has_many_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/json_api/has_one_test.rb b/test/adapter/json_api/has_one_test.rb index eb505a0de..ce82f7918 100644 --- a/test/adapter/json_api/has_one_test.rb +++ b/test/adapter/json_api/has_one_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/json_api/include_data_if_sideloaded_test.rb b/test/adapter/json_api/include_data_if_sideloaded_test.rb index c0da94886..7c9d4221a 100644 --- a/test/adapter/json_api/include_data_if_sideloaded_test.rb +++ b/test/adapter/json_api/include_data_if_sideloaded_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel @@ -6,7 +8,7 @@ module Adapter class JsonApi class IncludeParamTest < ActiveSupport::TestCase IncludeParamAuthor = Class.new(::Model) do - associations :tags, :posts + associations :tags, :posts, :roles end class CustomCommentLoader @@ -51,14 +53,19 @@ class IncludeParamAuthorSerializer < ActiveModel::Serializer include_data :if_sideloaded IncludeParamAuthorSerializer.comment_loader.all end + has_many :roles, key: :granted_roles do + include_data :if_sideloaded + end end def setup IncludeParamAuthorSerializer.comment_loader = Class.new(CustomCommentLoader).new @tag = Tag.new(id: 1337, name: 'mytag') + @role = Role.new(id: 1337, name: 'myrole') @author = IncludeParamAuthor.new( id: 1337, - tags: [@tag] + tags: [@tag], + roles: [@role] ) end @@ -105,6 +112,31 @@ def test_sideloads_included assert_equal(expected, hash[:included]) end + def test_sideloads_included_when_using_key + expected = [ + { + id: '1337', + type: 'roles', + attributes: { + name: 'myrole', + description: nil, + slug: 'myrole-1337' + }, + relationships: { + author: { data: nil } + } + } + ] + + hash = result(include: :granted_roles) + assert_equal(expected, hash[:included]) + end + + def test_sideloads_not_included_when_using_name_when_key_defined + hash = result(include: :roles) + assert_nil(hash[:included]) + end + def test_nested_relationship expected = { data: [ diff --git a/test/adapter/json_api/json_api_test.rb b/test/adapter/json_api/json_api_test.rb index cb2ce909a..4db3231fe 100644 --- a/test/adapter/json_api/json_api_test.rb +++ b/test/adapter/json_api/json_api_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/json_api/linked_test.rb b/test/adapter/json_api/linked_test.rb index 0d9c69b6b..d154cad47 100644 --- a/test/adapter/json_api/linked_test.rb +++ b/test/adapter/json_api/linked_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class NestedPost < ::Model; associations :nested_posts end diff --git a/test/adapter/json_api/links_test.rb b/test/adapter/json_api/links_test.rb index ffbfa303e..edf6cbc70 100644 --- a/test/adapter/json_api/links_test.rb +++ b/test/adapter/json_api/links_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers @@ -17,7 +19,21 @@ class LinkAuthorSerializer < ActiveModel::Serializer link :yet_another do "http://example.com/resource/#{object.id}" end + link :conditional1, if: -> { instance_truth } do + "http://example.com/conditional1/#{object.id}" + end + link :conditional2, if: :instance_falsey do + "http://example.com/conditional2/#{object.id}" + end link(:nil) { nil } + + def instance_truth + true + end + + def instance_falsey + false + end end def setup @@ -85,7 +101,8 @@ def test_resource_links :"link-authors" => 'http://example.com/link_authors', resource: 'http://example.com/resource', posts: 'http://example.com/link_authors/1337/posts', - :"yet-another" => 'http://example.com/resource/1337' + :"yet-another" => 'http://example.com/resource/1337', + conditional1: 'http://example.com/conditional1/1337' } assert_equal(expected, hash[:data][:links]) end diff --git a/test/adapter/json_api/pagination_links_test.rb b/test/adapter/json_api/pagination_links_test.rb index 736ea2fe2..be1f475a8 100644 --- a/test/adapter/json_api/pagination_links_test.rb +++ b/test/adapter/json_api/pagination_links_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'will_paginate/array' require 'kaminari' @@ -54,6 +56,16 @@ def data } end + def empty_collection_links + { + self: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=2", + first: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=2", + prev: nil, + next: nil, + last: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=2" + } + end + def links { links: { @@ -71,7 +83,21 @@ def last_page_links links: { self: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=2", first: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=2", - prev: "#{URI}?page%5Bnumber%5D=2&page%5Bsize%5D=2" + prev: "#{URI}?page%5Bnumber%5D=2&page%5Bsize%5D=2", + next: nil, + last: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=2" + } + } + end + + def greater_than_last_page_links + { + links: { + self: "#{URI}?page%5Bnumber%5D=4&page%5Bsize%5D=2", + first: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=2", + prev: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=2", + next: nil, + last: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=2" } } end @@ -108,10 +134,17 @@ def expected_response_with_last_page_pagination_links end end - def expected_response_with_no_data_pagination_links + def expected_response_with_greater_than_last_page_pagination_links {}.tap do |hash| hash[:data] = [] - hash[:links] = {} + hash.merge! greater_than_last_page_links + end + end + + def expected_response_with_empty_collection_pagination_links + {}.tap do |hash| + hash[:data] = [] + hash.merge! links: empty_collection_links end end @@ -127,6 +160,18 @@ def test_pagination_links_using_will_paginate assert_equal expected_response_with_pagination_links, adapter.serializable_hash end + def test_pagination_links_invalid_current_page_using_kaminari + adapter = load_adapter(using_kaminari(4), mock_request) + + assert_equal expected_response_with_greater_than_last_page_pagination_links, adapter.serializable_hash + end + + def test_pagination_links_invalid_current_page_using_will_paginate + adapter = load_adapter(using_will_paginate(4), mock_request) + + assert_equal expected_response_with_greater_than_last_page_pagination_links, adapter.serializable_hash + end + def test_pagination_links_with_additional_params adapter = load_adapter(using_will_paginate, mock_request(test: 'test')) @@ -139,7 +184,7 @@ def test_pagination_links_when_zero_results_kaminari adapter = load_adapter(using_kaminari(1), mock_request) - assert_equal expected_response_with_no_data_pagination_links, adapter.serializable_hash + assert_equal expected_response_with_empty_collection_pagination_links, adapter.serializable_hash end def test_pagination_links_when_zero_results_will_paginate @@ -147,7 +192,7 @@ def test_pagination_links_when_zero_results_will_paginate adapter = load_adapter(using_will_paginate(1), mock_request) - assert_equal expected_response_with_no_data_pagination_links, adapter.serializable_hash + assert_equal expected_response_with_empty_collection_pagination_links, adapter.serializable_hash end def test_last_page_pagination_links_using_kaminari @@ -171,10 +216,11 @@ def test_not_showing_pagination_links def test_raises_descriptive_error_when_serialization_context_unset render_options = { adapter: :json_api } adapter = serializable(using_kaminari, render_options) - exception = assert_raises do + exception_class = ActiveModelSerializers::Adapter::JsonApi::PaginationLinks::MissingSerializationContextError + + exception = assert_raises(exception_class) do adapter.as_json end - exception_class = ActiveModelSerializers::Adapter::JsonApi::PaginationLinks::MissingSerializationContextError assert_equal exception_class, exception.class assert_match(/CollectionSerializer#paginated\?/, exception.message) end diff --git a/test/adapter/json_api/parse_test.rb b/test/adapter/json_api/parse_test.rb index bee79c8c1..d2b562190 100644 --- a/test/adapter/json_api/parse_test.rb +++ b/test/adapter/json_api/parse_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers module Adapter @@ -125,7 +127,7 @@ def test_polymorphic src: 'http://example.com/images/productivity.png', author_id: nil, photographer_id: '9', - photographer_type: 'people', + photographer_type: 'Person', comment_ids: %w(1 2) } assert_equal(expected, parsed_hash) diff --git a/test/adapter/json_api/relationship_test.rb b/test/adapter/json_api/relationship_test.rb index cfd5be85e..38a67fe22 100644 --- a/test/adapter/json_api/relationship_test.rb +++ b/test/adapter/json_api/relationship_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers @@ -33,6 +35,24 @@ def test_relationship_with_nil_model assert_equal(expected, actual) end + def test_relationship_with_nil_model_and_belongs_to_id_on_self + original_config = ActiveModelSerializers.config.jsonapi_use_foreign_key_on_belongs_to_relationship + ActiveModelSerializers.config.jsonapi_use_foreign_key_on_belongs_to_relationship = true + + expected = { data: nil } + + model_attributes = { blog: nil } + relationship_name = :blog + model = new_model(model_attributes) + actual = build_serializer_and_serialize_relationship(model, relationship_name) do + belongs_to :blog + end + + assert_equal(expected, actual) + ensure + ActiveModelSerializers.config.jsonapi_use_foreign_key_on_belongs_to_relationship = original_config + end + def test_relationship_with_data_array expected = { data: [ diff --git a/test/adapter/json_api/resource_identifier_test.rb b/test/adapter/json_api/resource_identifier_test.rb deleted file mode 100644 index 62b7d93b3..000000000 --- a/test/adapter/json_api/resource_identifier_test.rb +++ /dev/null @@ -1,110 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonApi - class ResourceIdentifierTest < ActiveSupport::TestCase - class WithDefinedTypeSerializer < ActiveModel::Serializer - type 'with_defined_type' - end - - class WithDefinedIdSerializer < ActiveModel::Serializer - def id - 'special_id' - end - end - - class FragmentedSerializer < ActiveModel::Serializer - cache only: :id - - def id - 'special_id' - end - end - - setup do - @model = Author.new(id: 1, name: 'Steve K.') - ActionController::Base.cache_store.clear - end - - def test_defined_type - test_type(WithDefinedTypeSerializer, 'with-defined-type') - end - - def test_singular_type - test_type_inflection(AuthorSerializer, 'author', :singular) - end - - def test_plural_type - test_type_inflection(AuthorSerializer, 'authors', :plural) - end - - def test_type_with_namespace - Object.const_set(:Admin, Module.new) - model = Class.new(::Model) - Admin.const_set(:PowerUser, model) - serializer = Class.new(ActiveModel::Serializer) - Admin.const_set(:PowerUserSerializer, serializer) - with_namespace_separator '--' do - admin_user = Admin::PowerUser.new - serializer = Admin::PowerUserSerializer.new(admin_user) - expected = { - id: admin_user.id, - type: 'admin--power-users' - } - - identifier = ResourceIdentifier.new(serializer, {}) - actual = identifier.as_json - assert_equal(expected, actual) - end - end - - def test_id_defined_on_object - test_id(AuthorSerializer, @model.id.to_s) - end - - def test_id_defined_on_serializer - test_id(WithDefinedIdSerializer, 'special_id') - end - - def test_id_defined_on_fragmented - test_id(FragmentedSerializer, 'special_id') - end - - private - - def test_type_inflection(serializer_class, expected_type, inflection) - original_inflection = ActiveModelSerializers.config.jsonapi_resource_type - ActiveModelSerializers.config.jsonapi_resource_type = inflection - test_type(serializer_class, expected_type) - ensure - ActiveModelSerializers.config.jsonapi_resource_type = original_inflection - end - - def test_type(serializer_class, expected_type) - serializer = serializer_class.new(@model) - resource_identifier = ResourceIdentifier.new(serializer, nil) - expected = { - id: @model.id.to_s, - type: expected_type - } - - assert_equal(expected, resource_identifier.as_json) - end - - def test_id(serializer_class, id) - serializer = serializer_class.new(@model) - resource_identifier = ResourceIdentifier.new(serializer, nil) - inflection = ActiveModelSerializers.config.jsonapi_resource_type - type = @model.class.model_name.send(inflection) - expected = { - id: id, - type: type - } - - assert_equal(expected, resource_identifier.as_json) - end - end - end - end -end diff --git a/test/adapter/json_api/resource_meta_test.rb b/test/adapter/json_api/resource_meta_test.rb index fa281f30b..73f668f3a 100644 --- a/test/adapter/json_api/resource_meta_test.rb +++ b/test/adapter/json_api/resource_meta_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel diff --git a/test/adapter/json_api/toplevel_jsonapi_test.rb b/test/adapter/json_api/toplevel_jsonapi_test.rb index 7b0357e52..30e04b1eb 100644 --- a/test/adapter/json_api/toplevel_jsonapi_test.rb +++ b/test/adapter/json_api/toplevel_jsonapi_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/json_api/transform_test.rb b/test/adapter/json_api/transform_test.rb index 887ec835f..f121b217a 100644 --- a/test/adapter/json_api/transform_test.rb +++ b/test/adapter/json_api/transform_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/json_api/type_test.rb b/test/adapter/json_api/type_test.rb index 40b84cf2b..56f7efcbc 100644 --- a/test/adapter/json_api/type_test.rb +++ b/test/adapter/json_api/type_test.rb @@ -1,60 +1,193 @@ +# frozen_string_literal: true + require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class JsonApi - class TypeTest < ActiveSupport::TestCase - class StringTypeSerializer < ActiveModel::Serializer - attribute :name - type 'profile' +module ActiveModelSerializers + module Adapter + class JsonApi + class TypeTest < ActiveSupport::TestCase + class StringTypeSerializer < ActiveModel::Serializer + attribute :name + type 'profile' + end + + class SymbolTypeSerializer < ActiveModel::Serializer + attribute :name + type :profile + end + + setup do + @author = Author.new(id: 1, name: 'Steve K.') + end + + def test_config_plural + with_jsonapi_inflection :plural do + assert_type(@author, 'authors') end + end - class SymbolTypeSerializer < ActiveModel::Serializer - attribute :name - type :profile + def test_config_singular + with_jsonapi_inflection :singular do + assert_type(@author, 'author') end + end + + def test_explicit_string_type_value + assert_type(@author, 'profile', serializer: StringTypeSerializer) + end + + def test_explicit_symbol_type_value + assert_type(@author, 'profile', serializer: SymbolTypeSerializer) + end + + private + + def assert_type(resource, expected_type, opts = {}) + opts = opts.reverse_merge(adapter: :json_api) + hash = serializable(resource, opts).serializable_hash + assert_equal(expected_type, hash.fetch(:data).fetch(:type)) + end + end + class ResourceIdentifierTest < ActiveSupport::TestCase + class WithDefinedTypeSerializer < ActiveModel::Serializer + type 'with_defined_types' + end + + class WithDefinedIdSerializer < ActiveModel::Serializer + def id + 'special_id' + end + end + + class FragmentedSerializer < ActiveModel::Serializer + cache only: :id + + def id + 'special_id' + end + end - setup do - @author = Author.new(id: 1, name: 'Steve K.') + setup do + @model = Author.new(id: 1, name: 'Steve K.') + ActionController::Base.cache_store.clear + end + + def test_defined_type + actual = with_jsonapi_inflection :plural do + actual_resource_identifier_object(WithDefinedTypeSerializer, @model) end + expected = { id: expected_model_id(@model), type: 'with-defined-types' } + assert_equal actual, expected + end - def test_config_plural - with_jsonapi_resource_type :plural do - assert_type(@author, 'authors') - end + def test_defined_type_not_inflected + actual = with_jsonapi_inflection :singular do + actual_resource_identifier_object(WithDefinedTypeSerializer, @model) end + expected = { id: expected_model_id(@model), type: 'with-defined-types' } + assert_equal actual, expected + end - def test_config_singular - with_jsonapi_resource_type :singular do - assert_type(@author, 'author') - end + def test_singular_type + actual = with_jsonapi_inflection :singular do + actual_resource_identifier_object(AuthorSerializer, @model) end + expected = { id: expected_model_id(@model), type: 'author' } + assert_equal actual, expected + end - def test_explicit_string_type_value - assert_type(@author, 'profile', serializer: StringTypeSerializer) + def test_plural_type + actual = with_jsonapi_inflection :plural do + actual_resource_identifier_object(AuthorSerializer, @model) end + expected = { id: expected_model_id(@model), type: 'authors' } + assert_equal actual, expected + end + + def test_type_with_namespace + Object.const_set(:Admin, Module.new) + model = Class.new(::Model) + Admin.const_set(:PowerUser, model) + serializer = Class.new(ActiveModel::Serializer) + Admin.const_set(:PowerUserSerializer, serializer) + with_namespace_separator '--' do + admin_user = Admin::PowerUser.new + serializer = Admin::PowerUserSerializer.new(admin_user) + expected = { + id: admin_user.id, + type: 'admin--power-users' + } - def test_explicit_symbol_type_value - assert_type(@author, 'profile', serializer: SymbolTypeSerializer) + identifier = ResourceIdentifier.new(serializer, {}) + actual = identifier.as_json + assert_equal(expected, actual) end + end + + def test_id_defined_on_object + actual = actual_resource_identifier_object(AuthorSerializer, @model) + expected = { id: @model.id.to_s, type: expected_model_type(@model) } + assert_equal actual, expected + end - private + def test_blank_id + model = Author.new(id: nil, name: 'Steve K.') + actual = actual_resource_identifier_object(AuthorSerializer, model) + expected = { type: expected_model_type(model) } + assert_equal actual, expected + end + + def test_for_type_with_id + id = 1 + actual = ResourceIdentifier.for_type_with_id('admin_user', id, {}) + expected = { id: '1', type: 'admin-users' } + assert_equal actual, expected + end - def assert_type(resource, expected_type, opts = {}) - opts = opts.reverse_merge(adapter: :json_api) - hash = serializable(resource, opts).serializable_hash - assert_equal(expected_type, hash.fetch(:data).fetch(:type)) + def test_for_type_with_id_given_blank_id + id = '' + actual = ResourceIdentifier.for_type_with_id('admin_user', id, {}) + assert_nil actual + end + + def test_for_type_with_id_inflected + id = 2 + actual = with_jsonapi_inflection :singular do + ResourceIdentifier.for_type_with_id('admin_users', id, {}) end + expected = { id: '2', type: 'admin-user' } + assert_equal actual, expected + end + + def test_id_defined_on_serializer + actual = actual_resource_identifier_object(WithDefinedIdSerializer, @model) + expected = { id: 'special_id', type: expected_model_type(@model) } + assert_equal actual, expected + end + + def test_id_defined_on_fragmented + actual = actual_resource_identifier_object(FragmentedSerializer, @model) + expected = { id: 'special_id', type: expected_model_type(@model) } + assert_equal actual, expected + end + + private + + def actual_resource_identifier_object(serializer_class, model) + serializer = serializer_class.new(model) + resource_identifier = ResourceIdentifier.new(serializer, nil) + resource_identifier.as_json + end - def with_jsonapi_resource_type(inflection) - old_inflection = ActiveModelSerializers.config.jsonapi_resource_type - ActiveModelSerializers.config.jsonapi_resource_type = inflection - yield - ensure - ActiveModelSerializers.config.jsonapi_resource_type = old_inflection + def expected_model_type(model, inflection = ActiveModelSerializers.config.jsonapi_resource_type) + with_jsonapi_inflection inflection do + model.class.model_name.send(inflection) end end + + def expected_model_id(model) + model.id.to_s + end end end end diff --git a/test/adapter/json_test.rb b/test/adapter/json_test.rb index f7f178f88..ebd40e68d 100644 --- a/test/adapter/json_test.rb +++ b/test/adapter/json_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/null_test.rb b/test/adapter/null_test.rb index 4e701db10..139f17db8 100644 --- a/test/adapter/null_test.rb +++ b/test/adapter/null_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/adapter/polymorphic_test.rb b/test/adapter/polymorphic_test.rb index 87d5ff51f..48bcd979e 100644 --- a/test/adapter/polymorphic_test.rb +++ b/test/adapter/polymorphic_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel @@ -165,6 +167,53 @@ def test_json_api_serialization assert_equal(expected, serialization(@picture, :json_api)) end + + def test_json_api_serialization_with_polymorphic_belongs_to + expected = { + data: { + id: '1', + type: 'poly-tags', + attributes: { phrase: 'foo' }, + relationships: { + :"object-tags" => { + data: [ + { id: '1', type: 'object-tags' }, + { id: '5', type: 'object-tags' } + ] + } + } + }, + included: [ + { + id: '1', + type: 'object-tags', + relationships: { + taggable: { + data: { id: '42', type: 'employees' } + } + } + }, + { + id: '42', + type: 'employees' + }, + { + id: '5', + type: 'object-tags', + relationships: { + taggable: { + data: { id: '1', type: 'pictures' } + } + } + }, + { + id: '1', + type: 'pictures' + } + ] + } + assert_equal(expected, tag_serialization(:json_api)) + end end end end diff --git a/test/adapter_test.rb b/test/adapter_test.rb index c1b00d726..f88dc157a 100644 --- a/test/adapter_test.rb +++ b/test/adapter_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/array_serializer_test.rb b/test/array_serializer_test.rb index 2ad55324e..498737c6d 100644 --- a/test/array_serializer_test.rb +++ b/test/array_serializer_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require_relative 'collection_serializer_test' diff --git a/test/benchmark/app.rb b/test/benchmark/app.rb index c39e9b4e8..cd7a41ef7 100644 --- a/test/benchmark/app.rb +++ b/test/benchmark/app.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # https://github.com/rails-api/active_model_serializers/pull/872 # approx ref 792fb8a9053f8db3c562dae4f40907a582dd1720 to test against require 'bundler/setup' diff --git a/test/benchmark/benchmarking_support.rb b/test/benchmark/benchmarking_support.rb index dd27f6c5f..7e3adaadc 100644 --- a/test/benchmark/benchmarking_support.rb +++ b/test/benchmark/benchmarking_support.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'benchmark/ips' require 'json' diff --git a/test/benchmark/bm_active_record.rb b/test/benchmark/bm_active_record.rb index 0837e266d..5ee6fbfdf 100644 --- a/test/benchmark/bm_active_record.rb +++ b/test/benchmark/bm_active_record.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative './benchmarking_support' require_relative './app' diff --git a/test/benchmark/bm_adapter.rb b/test/benchmark/bm_adapter.rb index c8bae66a5..d81971078 100644 --- a/test/benchmark/bm_adapter.rb +++ b/test/benchmark/bm_adapter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative './benchmarking_support' require_relative './app' diff --git a/test/benchmark/bm_caching.rb b/test/benchmark/bm_caching.rb index ae3ad798c..52aa9de20 100644 --- a/test/benchmark/bm_caching.rb +++ b/test/benchmark/bm_caching.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative './benchmarking_support' require_relative './app' diff --git a/test/benchmark/bm_lookup_chain.rb b/test/benchmark/bm_lookup_chain.rb index 3b32727f5..813d606f8 100644 --- a/test/benchmark/bm_lookup_chain.rb +++ b/test/benchmark/bm_lookup_chain.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative './benchmarking_support' require_relative './app' diff --git a/test/benchmark/bm_transform.rb b/test/benchmark/bm_transform.rb index 97c655c01..b6046973c 100644 --- a/test/benchmark/bm_transform.rb +++ b/test/benchmark/bm_transform.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative './benchmarking_support' require_relative './app' diff --git a/test/benchmark/config.ru b/test/benchmark/config.ru index 908eb28c4..7b0654bbd 100644 --- a/test/benchmark/config.ru +++ b/test/benchmark/config.ru @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require File.expand_path(['..', 'app'].join(File::SEPARATOR), __FILE__) run Rails.application diff --git a/test/benchmark/controllers.rb b/test/benchmark/controllers.rb index 81108445b..9829fc267 100644 --- a/test/benchmark/controllers.rb +++ b/test/benchmark/controllers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class PrimaryResourceController < ActionController::Base PRIMARY_RESOURCE = begin diff --git a/test/benchmark/fixtures.rb b/test/benchmark/fixtures.rb index c91e102d4..9d5f6606d 100644 --- a/test/benchmark/fixtures.rb +++ b/test/benchmark/fixtures.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Rails.configuration.serializers = [] class HasOneRelationshipSerializer < ActiveModel::Serializer attributes :id, :first_name, :last_name diff --git a/test/cache_test.rb b/test/cache_test.rb index f09589314..112e366cb 100644 --- a/test/cache_test.rb +++ b/test/cache_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'tmpdir' require 'tempfile' @@ -53,6 +55,11 @@ class AuthorSerializer < ActiveModel::Serializer has_many :roles has_one :bio end + class AuthorSerializerWithCache < ActiveModel::Serializer + cache + + attributes :name + end class Blog < ::Model attributes :name @@ -144,6 +151,67 @@ class InheritedRoleSerializer < RoleSerializer @blog_serializer = BlogSerializer.new(@blog) end + def test_expiring_of_cache_at_update_of_record + original_cache_versioning = :none + + if ARModels::Author.respond_to?(:cache_versioning) + original_cache_versioning = ARModels::Author.cache_versioning + ARModels::Author.cache_versioning = true + end + + author = ARModels::Author.create(name: 'Foo') + author_json = AuthorSerializerWithCache.new(author).as_json + + assert_equal 'Foo', author_json[:name] + + author.update(name: 'Bar') + author_json = AuthorSerializerWithCache.new(author).as_json + + expected = 'Bar' + actual = author_json[:name] + if ENV['APPVEYOR'] && actual != expected + skip('Cache expiration tests sometimes fail on Appveyor. FIXME :)') + else + assert_equal expected, actual + end + ensure + ARModels::Author.cache_versioning = original_cache_versioning unless original_cache_versioning == :none + end + + def test_cache_expiration_in_collection_on_update_of_record + original_cache_versioning = :none + + if ARModels::Author.respond_to?(:cache_versioning) + original_cache_versioning = ARModels::Author.cache_versioning + ARModels::Author.cache_versioning = true + end + + foo = 'Foo' + foo2 = 'Foo2' + author = ARModels::Author.create(name: foo) + author2 = ARModels::Author.create(name: foo2) + author_collection = [author, author, author2] + + collection_json = render_object_with_cache(author_collection, each_serializer: AuthorSerializerWithCache) + actual = collection_json + expected = [{ name: foo }, { name: foo }, { name: foo2 }] + if ENV['APPVEYOR'] && actual != expected + skip('Cache expiration tests sometimes fail on Appveyor. FIXME :)') + else + assert_equal expected, actual + end + + bar = 'Bar' + Timecop.travel(10.seconds.from_now) do + author.update!(name: bar) + + collection_json = render_object_with_cache(author_collection, each_serializer: AuthorSerializerWithCache) + assert_equal [{ name: bar }, { name: bar }, { name: foo2 }], collection_json + end + ensure + ARModels::Author.cache_versioning = original_cache_versioning unless original_cache_versioning == :none + end + def test_explicit_cache_store default_store = Class.new(ActiveModel::Serializer) do cache @@ -378,17 +446,41 @@ def test_a_serializer_rendered_by_two_adapter_returns_differently_fetch_attribut # rubocop:enable Metrics/AbcSize def test_uses_file_digest_in_cache_key + reset_cache_digest(@blog_serializer) render_object_with_cache(@blog) file_digest = Digest::MD5.hexdigest(File.open(__FILE__).read) key = "#{@blog.cache_key}/#{adapter.cache_key}/#{file_digest}" assert_equal(@blog_serializer.attributes, cache_store.fetch(key)) end + def test_uses_sha1_digest_in_cache_key_when_configured + reset_cache_digest(@blog_serializer) + previous_use_sha1_digests = ActiveModelSerializers.config.use_sha1_digests + ActiveModelSerializers.config.use_sha1_digests = true + render_object_with_cache(@blog) + file_digest = Digest::SHA1.hexdigest(File.open(__FILE__).read) + key = "#{@blog.cache_key}/#{adapter.cache_key}/#{file_digest}" + assert_equal(@blog_serializer.attributes, cache_store.fetch(key)) + ensure + ActiveModelSerializers.config.use_sha1_digests = previous_use_sha1_digests + end + def test_cache_digest_definition + reset_cache_digest(@post_serializer) file_digest = Digest::MD5.hexdigest(File.open(__FILE__).read) assert_equal(file_digest, @post_serializer.class._cache_digest) end + def test_cache_sha1_digest_definition + reset_cache_digest(@post_serializer) + previous_use_sha1_digests = ActiveModelSerializers.config.use_sha1_digests + ActiveModelSerializers.config.use_sha1_digests = true + file_digest = Digest::SHA1.hexdigest(File.open(__FILE__).read) + assert_equal(file_digest, @post_serializer.class._cache_digest) + ensure + ActiveModelSerializers.config.use_sha1_digests = previous_use_sha1_digests + end + def test_object_cache_keys serializable = ActiveModelSerializers::SerializableResource.new([@comment, @comment]) include_directive = JSONAPI::IncludeDirective.new('*', allow_wildcard: true) @@ -415,7 +507,7 @@ def test_fetch_attributes_from_cache adapter_options = {} adapter_instance = ActiveModelSerializers::Adapter::Attributes.new(serializers, adapter_options) serializers.serializable_hash(adapter_options, options, adapter_instance) - cached_attributes = adapter_options.fetch(:cached_attributes).with_indifferent_access + cached_attributes = options.fetch(:cached_attributes).with_indifferent_access include_directive = ActiveModelSerializers.default_include_directive manual_cached_attributes = ActiveModel::Serializer.cache_read_multi(serializers, adapter_instance, include_directive).with_indifferent_access @@ -446,9 +538,9 @@ def test_cache_read_multi_with_fragment_cache_enabled serializers.serializable_hash(adapter_options, options, adapter_instance) # Should find something with read_multi now - adapter_options = {} + options = {} serializers.serializable_hash(adapter_options, options, adapter_instance) - cached_attributes = adapter_options.fetch(:cached_attributes) + cached_attributes = options.fetch(:cached_attributes) include_directive = ActiveModelSerializers.default_include_directive manual_cached_attributes = ActiveModel::Serializer.cache_read_multi(serializers, adapter_instance, include_directive) @@ -647,5 +739,10 @@ def render_object_with_cache(obj, options = {}) def adapter @serializable_resource.adapter end + + def reset_cache_digest(serializer) + return unless serializer.class.instance_variable_defined?(:@_cache_digest) + serializer.class.remove_instance_variable(:@_cache_digest) + end end end diff --git a/test/collection_serializer_test.rb b/test/collection_serializer_test.rb index cdbebb158..856cbed7a 100644 --- a/test/collection_serializer_test.rb +++ b/test/collection_serializer_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel @@ -20,6 +22,8 @@ class MessagesSerializer < ActiveModel::Serializer type 'messages' end + class NonTypeSerializer < ActiveModel::Serializer; end + def setup @singular_model = SingularModel.new @has_many_model = HasManyModel.new @@ -93,12 +97,34 @@ def test_json_key_with_resource_with_nil_name_and_no_serializers resource = [] resource.define_singleton_method(:name) { nil } serializer = collection_serializer.new(resource) - assert_nil serializer.json_key + assert_raise ActiveModel::Serializer::CollectionSerializer::CannotInferRootKeyError do + serializer.json_key + end end def test_json_key_with_resource_without_name_and_no_serializers serializer = collection_serializer.new([]) + assert_raise ActiveModel::Serializer::CollectionSerializer::CannotInferRootKeyError do + serializer.json_key + end + end + + def test_json_key_with_empty_resources_with_non_type_serializer + resource = [] + serializer = collection_serializer.new(resource, serializer: NonTypeSerializer) + assert_raise ActiveModel::Serializer::CollectionSerializer::CannotInferRootKeyError do + serializer.json_key + end + end + + def test_json_key_with_empty_resources_with_non_type_serializer_when_raise_cannot_infer_root_key_error_is_false + previous_raise_cannot_infer_root_key_error = ActiveModelSerializers.config.raise_cannot_infer_root_key_error + ActiveModelSerializers.config.raise_cannot_infer_root_key_error = false + resource = [] + serializer = collection_serializer.new(resource, serializer: NonTypeSerializer) assert_nil serializer.json_key + ensure + ActiveModelSerializers.config.raise_cannot_infer_root_key_error = previous_raise_cannot_infer_root_key_error end def test_json_key_with_empty_resources_with_serializer diff --git a/test/fixtures/active_record.rb b/test/fixtures/active_record.rb index 9dc3830da..132a43b16 100644 --- a/test/fixtures/active_record.rb +++ b/test/fixtures/active_record.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_record' ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') @@ -17,28 +19,28 @@ t.text :contents t.references :author t.references :post - t.timestamp null: false + t.timestamps null: false end create_table :employees, force: true do |t| t.string :name t.string :email - t.timestamp null: false + t.timestamps null: false end create_table :object_tags, force: true do |t| t.string :poly_tag_id t.string :taggable_type t.string :taggable_id - t.timestamp null: false + t.timestamps null: false end create_table :poly_tags, force: true do |t| t.string :phrase - t.timestamp null: false + t.timestamps null: false end create_table :pictures, force: true do |t| t.string :title t.string :imageable_type t.string :imageable_id - t.timestamp null: false + t.timestamps null: false end end @@ -89,7 +91,7 @@ class ObjectTag < ActiveRecord::Base end class PolymorphicObjectTagSerializer < ActiveModel::Serializer attributes :id - has_many :taggable, serializer: PolymorphicSimpleSerializer, polymorphic: true + belongs_to :taggable, serializer: PolymorphicSimpleSerializer, polymorphic: true end class PolyTag < ActiveRecord::Base @@ -109,5 +111,5 @@ class PolymorphicHasManySerializer < ActiveModel::Serializer end class PolymorphicBelongsToSerializer < ActiveModel::Serializer attributes :id, :title - has_one :imageable, serializer: PolymorphicHasManySerializer, polymorphic: true + belongs_to :imageable, serializer: PolymorphicHasManySerializer, polymorphic: true end diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index 6245ad23d..d45c257f7 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Model < ActiveModelSerializers::Model rand(2).zero? && derive_attributes_from_names_and_fix_accessors diff --git a/test/generators/scaffold_controller_generator_test.rb b/test/generators/scaffold_controller_generator_test.rb index 183bb4f6f..797cc5327 100644 --- a/test/generators/scaffold_controller_generator_test.rb +++ b/test/generators/scaffold_controller_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'generators/rails/resource_override' diff --git a/test/generators/serializer_generator_test.rb b/test/generators/serializer_generator_test.rb index eef4a41e1..5a966e0a7 100644 --- a/test/generators/serializer_generator_test.rb +++ b/test/generators/serializer_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'generators/rails/resource_override' require 'generators/rails/serializer_generator' diff --git a/test/grape_test.rb b/test/grape_test.rb deleted file mode 100644 index 4851e57a7..000000000 --- a/test/grape_test.rb +++ /dev/null @@ -1,196 +0,0 @@ -require 'test_helper' -TestHelper.silence_warnings do - require 'grape' -end -require 'grape/active_model_serializers' -require 'kaminari' -require 'kaminari/hooks' -::Kaminari::Hooks.init - -module ActiveModelSerializers - class GrapeTest < ActiveSupport::TestCase - include Rack::Test::Methods - module Models - def self.model1 - ARModels::Post.new(id: 1, title: 'Dummy Title', body: 'Lorem Ipsum') - end - - def self.model2 - ARModels::Post.new(id: 2, title: 'Second Dummy Title', body: 'Second Lorem Ipsum') - end - - def self.all - @all ||= - begin - model1.save! - model2.save! - ARModels::Post.all - end - end - - def self.reset_all - ARModels::Post.delete_all - @all = nil - end - - def self.collection_per - 2 - end - - def self.collection - @collection ||= - begin - Kaminari.paginate_array( - [ - Profile.new(id: 1, name: 'Name 1', description: 'Description 1', comments: 'Comments 1'), - Profile.new(id: 2, name: 'Name 2', description: 'Description 2', comments: 'Comments 2'), - Profile.new(id: 3, name: 'Name 3', description: 'Description 3', comments: 'Comments 3'), - Profile.new(id: 4, name: 'Name 4', description: 'Description 4', comments: 'Comments 4'), - Profile.new(id: 5, name: 'Name 5', description: 'Description 5', comments: 'Comments 5') - ] - ).page(1).per(collection_per) - end - end - end - - class GrapeTest < Grape::API - format :json - TestHelper.silence_warnings do - include Grape::ActiveModelSerializers - end - - def self.resources(*) - TestHelper.silence_warnings do - super - end - end - - resources :grape do - get '/render' do - render Models.model1 - end - - get '/render_with_json_api' do - post = Models.model1 - render post, meta: { page: 1, total_pages: 2 }, adapter: :json_api - end - - get '/render_array_with_json_api' do - posts = Models.all - render posts, adapter: :json_api - end - - get '/render_collection_with_json_api' do - posts = Models.collection - render posts, adapter: :json_api - end - - get '/render_with_implicit_formatter' do - Models.model1 - end - - get '/render_array_with_implicit_formatter' do - Models.all - end - - get '/render_collection_with_implicit_formatter' do - Models.collection - end - end - end - - def app - Grape::Middleware::Globals.new(GrapeTest.new) - end - - extend Minitest::Assertions - def self.run_one_method(*) - _, stderr = capture_io do - super - end - fail Minitest::Assertion, stderr if stderr !~ /grape/ - end - - def test_formatter_returns_json - get '/grape/render' - - post = Models.model1 - serializable_resource = serializable(post) - - assert last_response.ok? - assert_equal serializable_resource.to_json, last_response.body - end - - def test_render_helper_passes_through_options_correctly - get '/grape/render_with_json_api' - - post = Models.model1 - serializable_resource = serializable(post, serializer: ARModels::PostSerializer, adapter: :json_api, meta: { page: 1, total_pages: 2 }) - - assert last_response.ok? - assert_equal serializable_resource.to_json, last_response.body - end - - def test_formatter_handles_arrays - get '/grape/render_array_with_json_api' - - posts = Models.all - serializable_resource = serializable(posts, adapter: :json_api) - - assert last_response.ok? - assert_equal serializable_resource.to_json, last_response.body - ensure - Models.reset_all - end - - def test_formatter_handles_collections - get '/grape/render_collection_with_json_api' - assert last_response.ok? - - representation = JSON.parse(last_response.body) - assert representation.include?('data') - assert representation['data'].count == Models.collection_per - assert representation.include?('links') - assert representation['links'].count > 0 - end - - def test_implicit_formatter - post = Models.model1 - serializable_resource = serializable(post, adapter: :json_api) - - with_adapter :json_api do - get '/grape/render_with_implicit_formatter' - end - - assert last_response.ok? - assert_equal serializable_resource.to_json, last_response.body - end - - def test_implicit_formatter_handles_arrays - posts = Models.all - serializable_resource = serializable(posts, adapter: :json_api) - - with_adapter :json_api do - get '/grape/render_array_with_implicit_formatter' - end - - assert last_response.ok? - assert_equal serializable_resource.to_json, last_response.body - ensure - Models.reset_all - end - - def test_implicit_formatter_handles_collections - with_adapter :json_api do - get '/grape/render_collection_with_implicit_formatter' - end - - representation = JSON.parse(last_response.body) - assert last_response.ok? - assert representation.include?('data') - assert representation['data'].count == Models.collection_per - assert representation.include?('links') - assert representation['links'].count > 0 - end - end -end diff --git a/test/lint_test.rb b/test/lint_test.rb index d404ccec1..b1d328dc5 100644 --- a/test/lint_test.rb +++ b/test/lint_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel diff --git a/test/logger_test.rb b/test/logger_test.rb index a15227bb0..30272006e 100644 --- a/test/logger_test.rb +++ b/test/logger_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/poro_test.rb b/test/poro_test.rb index e5fba8587..f1a57b199 100644 --- a/test/poro_test.rb +++ b/test/poro_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class PoroTest < ActiveSupport::TestCase diff --git a/test/serializable_resource_test.rb b/test/serializable_resource_test.rb index ab12bc27b..f996048f3 100644 --- a/test/serializable_resource_test.rb +++ b/test/serializable_resource_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModelSerializers diff --git a/test/serializers/association_macros_test.rb b/test/serializers/association_macros_test.rb index 3d5f05c5f..b640997de 100644 --- a/test/serializers/association_macros_test.rb +++ b/test/serializers/association_macros_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel diff --git a/test/serializers/associations_test.rb b/test/serializers/associations_test.rb index c1b164b8d..a86b7a3f4 100644 --- a/test/serializers/associations_test.rb +++ b/test/serializers/associations_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel class Serializer @@ -159,12 +161,56 @@ def blog_id end end - actual = serializable(post, adapter: :json_api, serializer: BelongsToBlogModelSerializer).as_json + actual = + begin + original_option = BelongsToBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship + BelongsToBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship = true + serializable(post, adapter: :json_api, serializer: BelongsToBlogModelSerializer).as_json + ensure + BelongsToBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship = original_option + end expected = { data: { id: '1', type: 'posts', relationships: { blog: { data: { id: '5', type: 'blogs' } } } } } assert_equal expected, actual end + class ExternalBlog < Blog + attributes :external_id + end + class BelongsToExternalBlogModel < ::Model + attributes :id, :title, :external_blog_id + associations :external_blog + end + class BelongsToExternalBlogModelSerializer < ActiveModel::Serializer + type :posts + belongs_to :external_blog + + def external_blog_id + object.external_blog.external_id + end + end + + def test_belongs_to_allows_id_overwriting + attributes = { + id: 1, + title: 'Title', + external_blog: ExternalBlog.new(id: 5, external_id: 6) + } + post = BelongsToExternalBlogModel.new(attributes) + + actual = + begin + original_option = BelongsToExternalBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship + BelongsToExternalBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship = true + serializable(post, adapter: :json_api, serializer: BelongsToExternalBlogModelSerializer).as_json + ensure + BelongsToExternalBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship = original_option + end + expected = { data: { id: '1', type: 'posts', relationships: { :'external-blog' => { data: { id: '6', type: 'external-blogs' } } } } } + + assert_equal expected, actual + end + class InlineAssociationTestPostSerializer < ActiveModel::Serializer has_many :comments has_many :comments, key: :last_comments do @@ -241,6 +287,56 @@ def test_associations_namespaced_resources end end + class AssociationsNamespacedSerializersTest < ActiveSupport::TestCase + class Post < ::Model + associations :comments, :author, :description + + def latest_comments + comments[0..3] + end + end + class Comment < ::Model; end + class Author < ::Model; end + class Description < ::Model; end + + class ResourceNamespace + class PostSerializer < ActiveModel::Serializer + has_many :comments, namespace: ResourceNamespace + has_many :latest_comments, namespace: ResourceNamespace + belongs_to :author, namespace: ResourceNamespace + has_one :description, namespace: ResourceNamespace + end + class CommentSerializer < ActiveModel::Serializer; end + class AuthorSerializer < ActiveModel::Serializer; end + class DescriptionSerializer < ActiveModel::Serializer; end + end + + def setup + @comment = Comment.new + @author = Author.new + @description = Description.new + @post = Post.new(comments: [@comment], + author: @author, + description: @description) + @post_serializer = ResourceNamespace::PostSerializer.new(@post) + end + + def test_associations_namespaced_serializers + @post_serializer.associations.each do |association| + case association.key + when :comments, :latest_comments + assert_instance_of(ResourceNamespace::CommentSerializer, association.lazy_association.serializer.first) + when :author + assert_instance_of(ResourceNamespace::AuthorSerializer, association.lazy_association.serializer) + when :description + assert_instance_of(ResourceNamespace::DescriptionSerializer, association.lazy_association.serializer) + else + flunk "Unknown association: #{key}" + end + end + end + end + class NestedSerializersTest < ActiveSupport::TestCase class Post < ::Model associations :comments, :author, :description diff --git a/test/serializers/attribute_test.rb b/test/serializers/attribute_test.rb index 608898c3e..41386ea59 100644 --- a/test/serializers/attribute_test.rb +++ b/test/serializers/attribute_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel diff --git a/test/serializers/attributes_test.rb b/test/serializers/attributes_test.rb index fb792b26f..82ed7b1e6 100644 --- a/test/serializers/attributes_test.rb +++ b/test/serializers/attributes_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel diff --git a/test/serializers/caching_configuration_test_isolated.rb b/test/serializers/caching_configuration_test_isolated.rb index b5698dd1a..326225a4d 100644 --- a/test/serializers/caching_configuration_test_isolated.rb +++ b/test/serializers/caching_configuration_test_isolated.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Execute this test in isolation require 'support/isolated_unit' diff --git a/test/serializers/configuration_test.rb b/test/serializers/configuration_test.rb index 2c5f922f4..1ac70dbb2 100644 --- a/test/serializers/configuration_test.rb +++ b/test/serializers/configuration_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel diff --git a/test/serializers/fieldset_test.rb b/test/serializers/fieldset_test.rb index 5b99d57a6..781696340 100644 --- a/test/serializers/fieldset_test.rb +++ b/test/serializers/fieldset_test.rb @@ -1,13 +1,25 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel class Serializer class FieldsetTest < ActiveSupport::TestCase + def setup + @fieldset = ActiveModel::Serializer::Fieldset.new('post' => %w(id title), 'comment' => ['body']) + end + def test_fieldset_with_hash - fieldset = ActiveModel::Serializer::Fieldset.new('post' => %w(id title), 'comment' => ['body']) expected = { post: [:id, :title], comment: [:body] } - assert_equal(expected, fieldset.fields) + assert_equal(expected, @fieldset.fields) + end + + def test_fields_for_accepts_string_or_symbol + expected = [:id, :title] + + assert_equal(expected, @fieldset.fields_for(:post)) + assert_equal(expected, @fieldset.fields_for('post')) end end end diff --git a/test/serializers/meta_test.rb b/test/serializers/meta_test.rb index 642093215..1ece1db46 100644 --- a/test/serializers/meta_test.rb +++ b/test/serializers/meta_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel diff --git a/test/serializers/options_test.rb b/test/serializers/options_test.rb index 009388e35..6e1b34596 100644 --- a/test/serializers/options_test.rb +++ b/test/serializers/options_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel diff --git a/test/serializers/read_attribute_for_serialization_test.rb b/test/serializers/read_attribute_for_serialization_test.rb index 02911c0e6..6095bf256 100644 --- a/test/serializers/read_attribute_for_serialization_test.rb +++ b/test/serializers/read_attribute_for_serialization_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel diff --git a/test/serializers/reflection_test.rb b/test/serializers/reflection_test.rb index 11cb154be..bb90e81c8 100644 --- a/test/serializers/reflection_test.rb +++ b/test/serializers/reflection_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel class Serializer @@ -423,5 +425,57 @@ def test_mutating_reflection_block_is_not_thread_safe end # rubocop:enable Metrics/AbcSize end + class ThreadedReflectionTest < ActiveSupport::TestCase + class Post < ::Model + attributes :id, :title, :body + associations :comments + end + class Comment < ::Model + attributes :id, :body + associations :post + end + class CommentSerializer < ActiveModel::Serializer + type 'comment' + attributes :id, :body + has_one :post + end + class PostSerializer < ActiveModel::Serializer + type 'post' + attributes :id, :title, :body + has_many :comments, serializer: CommentSerializer do + sleep 0.1 + object.comments + end + end + + # per https://github.com/rails-api/active_model_serializers/issues/2270 + def test_concurrent_serialization + post1 = Post.new(id: 1, title: 'Post 1 Title', body: 'Post 1 Body') + post1.comments = [Comment.new(id: 1, body: 'Comment on Post 1', post: post1)] + post2 = Post.new(id: 2, title: 'Post 2 Title', body: 'Post 2 Body') + post2.comments = [Comment.new(id: 2, body: 'Comment on Post 2', post: post2)] + serialized_posts = { + first: Set.new, + second: Set.new + } + t1 = Thread.new do + 10.times do + serialized_posts[:first] << PostSerializer.new(post1, {}).to_json + end + end + t2 = Thread.new do + 10.times do + serialized_posts[:second] << PostSerializer.new(post2, {}).to_json + end + end + t1.join + t2.join + expected_first_post_serialization = '{"id":1,"title":"Post 1 Title","body":"Post 1 Body","comments":[{"id":1,"body":"Comment on Post 1"}]}' + expected_second_post_serialization = '{"id":2,"title":"Post 2 Title","body":"Post 2 Body","comments":[{"id":2,"body":"Comment on Post 2"}]}' + + assert_equal [expected_second_post_serialization], serialized_posts[:second].to_a + assert_equal [expected_first_post_serialization], serialized_posts[:first].to_a + end + end end end diff --git a/test/serializers/root_test.rb b/test/serializers/root_test.rb index 5bd4cdc3b..50c019758 100644 --- a/test/serializers/root_test.rb +++ b/test/serializers/root_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel diff --git a/test/serializers/serialization_test.rb b/test/serializers/serialization_test.rb index 3c1884e62..21ec0dcc8 100644 --- a/test/serializers/serialization_test.rb +++ b/test/serializers/serialization_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveModel class Serializer class SerializationTest < ActiveSupport::TestCase diff --git a/test/serializers/serializer_for_test.rb b/test/serializers/serializer_for_test.rb index 9f6917081..551fb8d7a 100644 --- a/test/serializers/serializer_for_test.rb +++ b/test/serializers/serializer_for_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel diff --git a/test/serializers/serializer_for_with_namespace_test.rb b/test/serializers/serializer_for_with_namespace_test.rb index 5c6e3e5e9..f0f84528d 100644 --- a/test/serializers/serializer_for_with_namespace_test.rb +++ b/test/serializers/serializer_for_with_namespace_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module ActiveModel @@ -7,6 +9,7 @@ class Book < ::Model attributes :title, :author_name associations :publisher, :pages end + class Ebook < Book; end class Page < ::Model; attributes :number, :text end class Publisher < ::Model; attributes :name end @@ -32,12 +35,11 @@ class PublisherSerializer < ActiveModel::Serializer class BookSerializer < ActiveModel::Serializer attributes :title, :author_name end + test 'resource without a namespace' do book = Book.new(title: 'A Post', author_name: 'hello') - # TODO: this should be able to pull up this serializer without explicitly specifying the serializer - # currently, with no options, it still uses the Api::V3 serializer - result = ActiveModelSerializers::SerializableResource.new(book, serializer: BookSerializer).serializable_hash + result = ActiveModelSerializers::SerializableResource.new(book).serializable_hash expected = { title: 'A Post', author_name: 'hello' } assert_equal expected, result @@ -83,6 +85,11 @@ class BookSerializer < ActiveModel::Serializer } assert_equal expected, result end + + test 'follows inheritance with a namespace' do + serializer = ActiveModel::Serializer.serializer_for(Ebook.new, namespace: Api::V3) + assert_equal Api::V3::BookSerializer, serializer + end end end end diff --git a/test/support/isolated_unit.rb b/test/support/isolated_unit.rb index 26948d4aa..b83e0b296 100644 --- a/test/support/isolated_unit.rb +++ b/test/support/isolated_unit.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # https://github.com/rails/rails/blob/v5.0.0.beta1/railties/test/isolation/abstract_unit.rb # Usage Example: @@ -54,7 +56,7 @@ def make_basic_app require 'rails' require 'action_controller/railtie' - @app = Class.new(Rails::Application) do + app = Class.new(Rails::Application) do config.eager_load = false config.session_store :cookie_store, key: '_myapp_session' config.active_support.deprecation = :log @@ -66,9 +68,12 @@ def make_basic_app fake_logger = Logger.new(nil) config.logger = fake_logger Rails.application.routes.default_url_options = { host: 'example.com' } + config.hosts << 'www.example.com' if Rails.version >= '6.0' end - @app.respond_to?(:secrets) && @app.secrets.secret_key_base = '3b7cd727ee24e8444053437c36cc66c4' + def app.name; 'IsolatedRailsApp'; end # rubocop:disable Style/SingleLineMethods + app.respond_to?(:secrets) && app.secrets.secret_key_base = '3b7cd727ee24e8444053437c36cc66c4' + @app = app yield @app if block_given? @app.initialize! end diff --git a/test/support/rails5_shims.rb b/test/support/rails5_shims.rb index 03a036da6..55de76e96 100644 --- a/test/support/rails5_shims.rb +++ b/test/support/rails5_shims.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails5Shims module ControllerTests # https://github.com/rails/rails/blob/b217354/actionpack/lib/action_controller/test_case.rb diff --git a/test/support/rails_app.rb b/test/support/rails_app.rb index 43324b78c..26cc770e0 100644 --- a/test/support/rails_app.rb +++ b/test/support/rails_app.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'support/isolated_unit' module ActiveModelSerializers RailsApplication = TestHelpers::Generation.make_basic_app do |app| @@ -7,7 +9,13 @@ module ActiveModelSerializers config.action_controller.perform_caching = true config.action_controller.cache_store = :memory_store + if Rails::VERSION::MAJOR >= 6 + config.action_dispatch.return_only_media_type_on_content_type = true + end + config.filter_parameters += [:password] + + config.hosts << 'www.example.com' if Rails.version >= '6.0' end app.routes.default_url_options = { host: 'example.com' } diff --git a/test/support/ruby_2_6_rails_4_2_patch.rb b/test/support/ruby_2_6_rails_4_2_patch.rb new file mode 100644 index 000000000..768e94305 --- /dev/null +++ b/test/support/ruby_2_6_rails_4_2_patch.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +if RUBY_VERSION >= '2.6.0' + if Rails::VERSION::MAJOR < 5 + module ActionController + class TestResponse < ActionDispatch::TestResponse + def recycle! + # HACK: to avoid MonitorMixin double-initialize error: + @mon_mutex_owner_object_id = nil + @mon_mutex = nil + initialize + end + end + end + else + msg = 'Monkeypatch for ActionController::TestResponse not needed for '\ + 'Rails 5+. We can drop this patch once we drop support for Rails < 5. '\ + "Current Rails version: #{ENV['RAILS_VERSION']}" + + puts + puts "\033[33m **** #{msg} **** \033[0m" + puts + end +end diff --git a/test/support/serialization_testing.rb b/test/support/serialization_testing.rb index 524a32976..ada7fa3bf 100644 --- a/test/support/serialization_testing.rb +++ b/test/support/serialization_testing.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SerializationTesting def config ActiveModelSerializers.config @@ -18,11 +20,11 @@ def with_namespace_separator(separator) end def with_prepended_lookup(lookup_proc) - original_lookup = ActiveModelSerializers.config.serializer_lookup_cahin + original_lookup = ActiveModelSerializers.config.serializer_lookup_chain ActiveModelSerializers.config.serializer_lookup_chain.unshift lookup_proc yield ensure - ActiveModelSerializers.config.serializer_lookup_cahin = original_lookup + ActiveModelSerializers.config.serializer_lookup_chain = original_lookup end # Aliased as :with_configured_adapter to clarify that @@ -47,6 +49,14 @@ def with_config(hash) ActiveModelSerializers.config.replace(old_config) end + def with_jsonapi_inflection(inflection) + original_inflection = ActiveModelSerializers.config.jsonapi_resource_type + ActiveModelSerializers.config.jsonapi_resource_type = inflection + yield + ensure + ActiveModelSerializers.config.jsonapi_resource_type = original_inflection + end + def with_serializer_lookup_disabled original_serializer_lookup = ActiveModelSerializers.config.serializer_lookup_enabled ActiveModelSerializers.config.serializer_lookup_enabled = false diff --git a/test/test_helper.rb b/test/test_helper.rb index 294fa33c4..7d8ca63e3 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,14 +1,9 @@ +# frozen_string_literal: true + # Configure Rails Environment ENV['RAILS_ENV'] = 'test' require 'bundler/setup' -begin - require 'simplecov' - AppCoverage.start -rescue LoadError - STDERR.puts 'Running without SimpleCov' -end - require 'pry' require 'timecop' require 'rails' @@ -54,6 +49,8 @@ def silence_warnings require 'support/rails_app' +require 'support/ruby_2_6_rails_4_2_patch' + # require "rails/test_help" require 'support/serialization_testing'