From 0ef6ac30fc7c6ed8613a880c61ffde6f40d41a83 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Tue, 2 May 2017 09:57:09 -0500 Subject: [PATCH] Clear out master --- .rubocop.yml | 105 --- .simplecov | 110 --- .travis.yml | 47 +- CHANGELOG-0-08.md | 92 +++ CHANGELOG-0-09.md | 74 ++ CHANGELOG-0-10.md | 466 +++++++++++++ CHANGELOG-prehistory.md | 15 + CHANGELOG.md | 643 +---------------- CONTRIBUTING.md | 17 +- Gemfile | 56 -- README.md | 268 +------ Rakefile | 69 -- active_model_serializers.gemspec | 45 +- appveyor.yml | 21 +- bin/bench | 171 ----- bin/bench_regression | 316 --------- bin/rubocop | 38 - bin/serve_benchmark | 39 -- docs/README.md | 41 -- docs/STYLE.md | 58 -- docs/general/adapters.md | 263 ------- docs/general/caching.md | 58 -- docs/general/configuration_options.md | 169 ----- docs/general/deserialization.md | 100 --- docs/general/fields.md | 31 - docs/general/getting_started.md | 133 ---- docs/general/instrumentation.md | 40 -- docs/general/key_transforms.md | 40 -- docs/general/logging.md | 21 - docs/general/rendering.md | 293 -------- docs/general/serializers.md | 480 ------------- docs/how-open-source-maintained.jpg | Bin 282768 -> 0 bytes docs/howto/add_pagination_links.md | 138 ---- docs/howto/add_relationship_links.md | 140 ---- docs/howto/add_root_key.md | 55 -- docs/howto/grape_integration.md | 42 -- docs/howto/outside_controller_use.md | 66 -- docs/howto/passing_arbitrary_options.md | 27 - docs/howto/serialize_poro.md | 73 -- docs/howto/test.md | 154 ----- docs/howto/upgrade_from_0_8_to_0_10.md | 265 ------- docs/integrations/ember-and-json-api.md | 147 ---- docs/integrations/grape.md | 19 - docs/jsonapi/errors.md | 56 -- docs/jsonapi/schema.md | 151 ---- docs/jsonapi/schema/schema.json | 366 ---------- docs/rfcs/0000-namespace.md | 106 --- lib/action_controller/serialization.rb | 66 -- lib/active_model/serializable_resource.rb | 11 - lib/active_model/serializer.rb | 409 ----------- lib/active_model/serializer/adapter.rb | 24 - .../serializer/adapter/attributes.rb | 15 - lib/active_model/serializer/adapter/base.rb | 18 - lib/active_model/serializer/adapter/json.rb | 15 - .../serializer/adapter/json_api.rb | 15 - lib/active_model/serializer/adapter/null.rb | 15 - .../serializer/array_serializer.rb | 12 - lib/active_model/serializer/association.rb | 71 -- lib/active_model/serializer/attribute.rb | 25 - .../serializer/belongs_to_reflection.rb | 11 - .../serializer/collection_serializer.rb | 87 --- .../serializer/concerns/caching.rb | 300 -------- .../serializer/error_serializer.rb | 14 - .../serializer/errors_serializer.rb | 32 - lib/active_model/serializer/field.rb | 90 --- lib/active_model/serializer/fieldset.rb | 31 - .../serializer/has_many_reflection.rb | 10 - .../serializer/has_one_reflection.rb | 7 - .../serializer/lazy_association.rb | 95 --- lib/active_model/serializer/lint.rb | 150 ---- lib/active_model/serializer/null.rb | 17 - lib/active_model/serializer/reflection.rb | 207 ------ lib/active_model/serializer/version.rb | 5 - lib/active_model_serializers.rb | 53 -- lib/active_model_serializers/adapter.rb | 98 --- .../adapter/attributes.rb | 13 - lib/active_model_serializers/adapter/base.rb | 83 --- lib/active_model_serializers/adapter/json.rb | 21 - .../adapter/json_api.rb | 530 -------------- .../adapter/json_api/deserialization.rb | 213 ------ .../adapter/json_api/error.rb | 96 --- .../adapter/json_api/jsonapi.rb | 49 -- .../adapter/json_api/link.rb | 83 --- .../adapter/json_api/meta.rb | 37 - .../adapter/json_api/pagination_links.rb | 69 -- .../adapter/json_api/relationship.rb | 92 --- .../adapter/json_api/resource_identifier.rb | 60 -- lib/active_model_serializers/adapter/null.rb | 9 - lib/active_model_serializers/callbacks.rb | 55 -- lib/active_model_serializers/deprecate.rb | 54 -- .../deserialization.rb | 15 - lib/active_model_serializers/json_pointer.rb | 14 - lib/active_model_serializers/logging.rb | 122 ---- lib/active_model_serializers/lookup_chain.rb | 80 --- lib/active_model_serializers/model.rb | 130 ---- lib/active_model_serializers/railtie.rb | 48 -- .../register_jsonapi_renderer.rb | 78 --- .../serializable_resource.rb | 82 --- .../serialization_context.rb | 39 -- lib/active_model_serializers/test.rb | 7 - lib/active_model_serializers/test/schema.rb | 138 ---- .../test/serializer.rb | 125 ---- lib/generators/rails/USAGE | 6 - lib/generators/rails/resource_override.rb | 10 - lib/generators/rails/serializer_generator.rb | 36 - .../rails/templates/serializer.rb.erb | 8 - lib/grape/active_model_serializers.rb | 16 - .../formatters/active_model_serializers.rb | 32 - lib/grape/helpers/active_model_serializers.rb | 17 - lib/tasks/rubocop.rake | 53 -- .../adapter_selector_test.rb | 62 -- .../explicit_serializer_test.rb | 135 ---- test/action_controller/json/include_test.rb | 246 ------- .../json_api/deserialization_test.rb | 112 --- .../action_controller/json_api/errors_test.rb | 40 -- .../action_controller/json_api/fields_test.rb | 66 -- .../action_controller/json_api/linked_test.rb | 202 ------ .../json_api/pagination_test.rb | 116 ---- .../json_api/transform_test.rb | 189 ----- test/action_controller/lookup_proc_test.rb | 49 -- .../namespace_lookup_test.rb | 232 ------- .../serialization_scope_name_test.rb | 235 ------- test/action_controller/serialization_test.rb | 472 ------------- .../adapter_for_test.rb | 208 ------ .../json_pointer_test.rb | 22 - test/active_model_serializers/logging_test.rb | 77 --- test/active_model_serializers/model_test.rb | 142 ---- .../railtie_test_isolated.rb | 68 -- ...register_jsonapi_renderer_test_isolated.rb | 161 ----- .../serialization_context_test_isolated.rb | 71 -- .../test/schema_test.rb | 131 ---- .../test/serializer_test.rb | 62 -- test/active_record_test.rb | 9 - test/adapter/attributes_test.rb | 40 -- test/adapter/deprecation_test.rb | 100 --- test/adapter/json/belongs_to_test.rb | 45 -- test/adapter/json/collection_test.rb | 104 --- test/adapter/json/has_many_test.rb | 53 -- test/adapter/json/transform_test.rb | 93 --- test/adapter/json_api/belongs_to_test.rb | 155 ----- test/adapter/json_api/collection_test.rb | 96 --- test/adapter/json_api/errors_test.rb | 76 -- test/adapter/json_api/fields_test.rb | 96 --- .../json_api/has_many_embed_ids_test.rb | 43 -- .../has_many_explicit_serializer_test.rb | 96 --- test/adapter/json_api/has_many_test.rb | 173 ----- test/adapter/json_api/has_one_test.rb | 80 --- .../include_data_if_sideloaded_test.rb | 183 ----- test/adapter/json_api/json_api_test.rb | 33 - test/adapter/json_api/linked_test.rb | 413 ----------- test/adapter/json_api/links_test.rb | 95 --- .../adapter/json_api/pagination_links_test.rb | 193 ------ test/adapter/json_api/parse_test.rb | 137 ---- test/adapter/json_api/relationship_test.rb | 397 ----------- .../json_api/resource_identifier_test.rb | 110 --- test/adapter/json_api/resource_meta_test.rb | 100 --- .../adapter/json_api/toplevel_jsonapi_test.rb | 82 --- test/adapter/json_api/transform_test.rb | 512 -------------- test/adapter/json_api/type_test.rb | 61 -- test/adapter/json_test.rb | 46 -- test/adapter/null_test.rb | 22 - test/adapter/polymorphic_test.rb | 171 ----- test/adapter_test.rb | 67 -- test/array_serializer_test.rb | 22 - test/benchmark/app.rb | 65 -- test/benchmark/benchmarking_support.rb | 67 -- test/benchmark/bm_active_record.rb | 81 --- test/benchmark/bm_adapter.rb | 38 - test/benchmark/bm_caching.rb | 119 ---- test/benchmark/bm_lookup_chain.rb | 83 --- test/benchmark/bm_transform.rb | 45 -- test/benchmark/config.ru | 3 - test/benchmark/controllers.rb | 83 --- test/benchmark/fixtures.rb | 219 ------ test/cache_test.rb | 651 ------------------ test/collection_serializer_test.rb | 123 ---- test/fixtures/active_record.rb | 113 --- test/fixtures/poro.rb | 225 ------ .../scaffold_controller_generator_test.rb | 24 - test/generators/serializer_generator_test.rb | 75 -- test/grape_test.rb | 196 ------ test/lint_test.rb | 49 -- test/logger_test.rb | 20 - test/poro_test.rb | 9 - test/serializable_resource_test.rb | 79 --- test/serializers/association_macros_test.rb | 37 - test/serializers/associations_test.rb | 424 ------------ test/serializers/attribute_test.rb | 153 ---- test/serializers/attributes_test.rb | 52 -- .../caching_configuration_test_isolated.rb | 170 ----- test/serializers/configuration_test.rb | 32 - test/serializers/fieldset_test.rb | 14 - test/serializers/meta_test.rb | 202 ------ test/serializers/options_test.rb | 32 - .../read_attribute_for_serialization_test.rb | 79 --- test/serializers/reflection_test.rb | 427 ------------ test/serializers/root_test.rb | 21 - test/serializers/serialization_test.rb | 55 -- test/serializers/serializer_for_test.rb | 136 ---- .../serializer_for_with_namespace_test.rb | 88 --- .../test/schema_test/my/index.json | 6 - test/support/isolated_unit.rb | 82 --- test/support/rails5_shims.rb | 53 -- test/support/rails_app.rb | 38 - .../test/schema_test/my/index.json | 6 - .../test/schema_test/my/show.json | 6 - test/support/schemas/custom/show.json | 7 - test/support/schemas/hyper_schema.json | 93 --- .../schemas/render_using_json_api.json | 43 -- .../support/schemas/simple_json_pointers.json | 10 - test/support/serialization_testing.rb | 71 -- test/test_helper.rb | 70 -- 212 files changed, 666 insertions(+), 21656 deletions(-) delete mode 100644 .rubocop.yml delete mode 100644 .simplecov create mode 100644 CHANGELOG-0-08.md create mode 100644 CHANGELOG-0-09.md create mode 100644 CHANGELOG-0-10.md create mode 100644 CHANGELOG-prehistory.md delete mode 100644 Gemfile delete mode 100755 bin/bench delete mode 100755 bin/bench_regression delete mode 100755 bin/rubocop delete mode 100755 bin/serve_benchmark delete mode 100644 docs/README.md delete mode 100644 docs/STYLE.md delete mode 100644 docs/general/adapters.md delete mode 100644 docs/general/caching.md delete mode 100644 docs/general/configuration_options.md delete mode 100644 docs/general/deserialization.md delete mode 100644 docs/general/fields.md delete mode 100644 docs/general/getting_started.md delete mode 100644 docs/general/instrumentation.md delete mode 100644 docs/general/key_transforms.md delete mode 100644 docs/general/logging.md delete mode 100644 docs/general/rendering.md delete mode 100644 docs/general/serializers.md delete mode 100644 docs/how-open-source-maintained.jpg delete mode 100644 docs/howto/add_pagination_links.md delete mode 100644 docs/howto/add_relationship_links.md delete mode 100644 docs/howto/add_root_key.md delete mode 100644 docs/howto/grape_integration.md delete mode 100644 docs/howto/outside_controller_use.md delete mode 100644 docs/howto/passing_arbitrary_options.md delete mode 100644 docs/howto/serialize_poro.md delete mode 100644 docs/howto/test.md delete mode 100644 docs/howto/upgrade_from_0_8_to_0_10.md delete mode 100644 docs/integrations/ember-and-json-api.md delete mode 100644 docs/integrations/grape.md delete mode 100644 docs/jsonapi/errors.md delete mode 100644 docs/jsonapi/schema.md delete mode 100644 docs/jsonapi/schema/schema.json delete mode 100644 docs/rfcs/0000-namespace.md delete mode 100644 lib/action_controller/serialization.rb delete mode 100644 lib/active_model/serializable_resource.rb delete mode 100644 lib/active_model/serializer.rb delete mode 100644 lib/active_model/serializer/adapter.rb delete mode 100644 lib/active_model/serializer/adapter/attributes.rb delete mode 100644 lib/active_model/serializer/adapter/base.rb delete mode 100644 lib/active_model/serializer/adapter/json.rb delete mode 100644 lib/active_model/serializer/adapter/json_api.rb delete mode 100644 lib/active_model/serializer/adapter/null.rb delete mode 100644 lib/active_model/serializer/array_serializer.rb delete mode 100644 lib/active_model/serializer/association.rb delete mode 100644 lib/active_model/serializer/attribute.rb delete mode 100644 lib/active_model/serializer/belongs_to_reflection.rb delete mode 100644 lib/active_model/serializer/collection_serializer.rb delete mode 100644 lib/active_model/serializer/concerns/caching.rb delete mode 100644 lib/active_model/serializer/error_serializer.rb delete mode 100644 lib/active_model/serializer/errors_serializer.rb delete mode 100644 lib/active_model/serializer/field.rb delete mode 100644 lib/active_model/serializer/fieldset.rb delete mode 100644 lib/active_model/serializer/has_many_reflection.rb delete mode 100644 lib/active_model/serializer/has_one_reflection.rb delete mode 100644 lib/active_model/serializer/lazy_association.rb delete mode 100644 lib/active_model/serializer/lint.rb delete mode 100644 lib/active_model/serializer/null.rb delete mode 100644 lib/active_model/serializer/reflection.rb delete mode 100644 lib/active_model/serializer/version.rb delete mode 100644 lib/active_model_serializers.rb delete mode 100644 lib/active_model_serializers/adapter.rb delete mode 100644 lib/active_model_serializers/adapter/attributes.rb delete mode 100644 lib/active_model_serializers/adapter/base.rb delete mode 100644 lib/active_model_serializers/adapter/json.rb delete mode 100644 lib/active_model_serializers/adapter/json_api.rb delete mode 100644 lib/active_model_serializers/adapter/json_api/deserialization.rb delete mode 100644 lib/active_model_serializers/adapter/json_api/error.rb delete mode 100644 lib/active_model_serializers/adapter/json_api/jsonapi.rb delete mode 100644 lib/active_model_serializers/adapter/json_api/link.rb delete mode 100644 lib/active_model_serializers/adapter/json_api/meta.rb delete mode 100644 lib/active_model_serializers/adapter/json_api/pagination_links.rb delete mode 100644 lib/active_model_serializers/adapter/json_api/relationship.rb delete mode 100644 lib/active_model_serializers/adapter/json_api/resource_identifier.rb delete mode 100644 lib/active_model_serializers/adapter/null.rb delete mode 100644 lib/active_model_serializers/callbacks.rb delete mode 100644 lib/active_model_serializers/deprecate.rb delete mode 100644 lib/active_model_serializers/deserialization.rb delete mode 100644 lib/active_model_serializers/json_pointer.rb delete mode 100644 lib/active_model_serializers/logging.rb delete mode 100644 lib/active_model_serializers/lookup_chain.rb delete mode 100644 lib/active_model_serializers/model.rb delete mode 100644 lib/active_model_serializers/railtie.rb delete mode 100644 lib/active_model_serializers/register_jsonapi_renderer.rb delete mode 100644 lib/active_model_serializers/serializable_resource.rb delete mode 100644 lib/active_model_serializers/serialization_context.rb delete mode 100644 lib/active_model_serializers/test.rb delete mode 100644 lib/active_model_serializers/test/schema.rb delete mode 100644 lib/active_model_serializers/test/serializer.rb delete mode 100644 lib/generators/rails/USAGE delete mode 100644 lib/generators/rails/resource_override.rb delete mode 100644 lib/generators/rails/serializer_generator.rb delete mode 100644 lib/generators/rails/templates/serializer.rb.erb delete mode 100644 lib/grape/active_model_serializers.rb delete mode 100644 lib/grape/formatters/active_model_serializers.rb delete mode 100644 lib/grape/helpers/active_model_serializers.rb delete mode 100644 lib/tasks/rubocop.rake delete mode 100644 test/action_controller/adapter_selector_test.rb delete mode 100644 test/action_controller/explicit_serializer_test.rb delete mode 100644 test/action_controller/json/include_test.rb delete mode 100644 test/action_controller/json_api/deserialization_test.rb delete mode 100644 test/action_controller/json_api/errors_test.rb delete mode 100644 test/action_controller/json_api/fields_test.rb delete mode 100644 test/action_controller/json_api/linked_test.rb delete mode 100644 test/action_controller/json_api/pagination_test.rb delete mode 100644 test/action_controller/json_api/transform_test.rb delete mode 100644 test/action_controller/lookup_proc_test.rb delete mode 100644 test/action_controller/namespace_lookup_test.rb delete mode 100644 test/action_controller/serialization_scope_name_test.rb delete mode 100644 test/action_controller/serialization_test.rb delete mode 100644 test/active_model_serializers/adapter_for_test.rb delete mode 100644 test/active_model_serializers/json_pointer_test.rb delete mode 100644 test/active_model_serializers/logging_test.rb delete mode 100644 test/active_model_serializers/model_test.rb delete mode 100644 test/active_model_serializers/railtie_test_isolated.rb delete mode 100644 test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb delete mode 100644 test/active_model_serializers/serialization_context_test_isolated.rb delete mode 100644 test/active_model_serializers/test/schema_test.rb delete mode 100644 test/active_model_serializers/test/serializer_test.rb delete mode 100644 test/active_record_test.rb delete mode 100644 test/adapter/attributes_test.rb delete mode 100644 test/adapter/deprecation_test.rb delete mode 100644 test/adapter/json/belongs_to_test.rb delete mode 100644 test/adapter/json/collection_test.rb delete mode 100644 test/adapter/json/has_many_test.rb delete mode 100644 test/adapter/json/transform_test.rb delete mode 100644 test/adapter/json_api/belongs_to_test.rb delete mode 100644 test/adapter/json_api/collection_test.rb delete mode 100644 test/adapter/json_api/errors_test.rb delete mode 100644 test/adapter/json_api/fields_test.rb delete mode 100644 test/adapter/json_api/has_many_embed_ids_test.rb delete mode 100644 test/adapter/json_api/has_many_explicit_serializer_test.rb delete mode 100644 test/adapter/json_api/has_many_test.rb delete mode 100644 test/adapter/json_api/has_one_test.rb delete mode 100644 test/adapter/json_api/include_data_if_sideloaded_test.rb delete mode 100644 test/adapter/json_api/json_api_test.rb delete mode 100644 test/adapter/json_api/linked_test.rb delete mode 100644 test/adapter/json_api/links_test.rb delete mode 100644 test/adapter/json_api/pagination_links_test.rb delete mode 100644 test/adapter/json_api/parse_test.rb delete mode 100644 test/adapter/json_api/relationship_test.rb delete mode 100644 test/adapter/json_api/resource_identifier_test.rb delete mode 100644 test/adapter/json_api/resource_meta_test.rb delete mode 100644 test/adapter/json_api/toplevel_jsonapi_test.rb delete mode 100644 test/adapter/json_api/transform_test.rb delete mode 100644 test/adapter/json_api/type_test.rb delete mode 100644 test/adapter/json_test.rb delete mode 100644 test/adapter/null_test.rb delete mode 100644 test/adapter/polymorphic_test.rb delete mode 100644 test/adapter_test.rb delete mode 100644 test/array_serializer_test.rb delete mode 100644 test/benchmark/app.rb delete mode 100644 test/benchmark/benchmarking_support.rb delete mode 100644 test/benchmark/bm_active_record.rb delete mode 100644 test/benchmark/bm_adapter.rb delete mode 100644 test/benchmark/bm_caching.rb delete mode 100644 test/benchmark/bm_lookup_chain.rb delete mode 100644 test/benchmark/bm_transform.rb delete mode 100644 test/benchmark/config.ru delete mode 100644 test/benchmark/controllers.rb delete mode 100644 test/benchmark/fixtures.rb delete mode 100644 test/cache_test.rb delete mode 100644 test/collection_serializer_test.rb delete mode 100644 test/fixtures/active_record.rb delete mode 100644 test/fixtures/poro.rb delete mode 100644 test/generators/scaffold_controller_generator_test.rb delete mode 100644 test/generators/serializer_generator_test.rb delete mode 100644 test/grape_test.rb delete mode 100644 test/lint_test.rb delete mode 100644 test/logger_test.rb delete mode 100644 test/poro_test.rb delete mode 100644 test/serializable_resource_test.rb delete mode 100644 test/serializers/association_macros_test.rb delete mode 100644 test/serializers/associations_test.rb delete mode 100644 test/serializers/attribute_test.rb delete mode 100644 test/serializers/attributes_test.rb delete mode 100644 test/serializers/caching_configuration_test_isolated.rb delete mode 100644 test/serializers/configuration_test.rb delete mode 100644 test/serializers/fieldset_test.rb delete mode 100644 test/serializers/meta_test.rb delete mode 100644 test/serializers/options_test.rb delete mode 100644 test/serializers/read_attribute_for_serialization_test.rb delete mode 100644 test/serializers/reflection_test.rb delete mode 100644 test/serializers/root_test.rb delete mode 100644 test/serializers/serialization_test.rb delete mode 100644 test/serializers/serializer_for_test.rb delete mode 100644 test/serializers/serializer_for_with_namespace_test.rb delete mode 100644 test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json delete mode 100644 test/support/isolated_unit.rb delete mode 100644 test/support/rails5_shims.rb delete mode 100644 test/support/rails_app.rb delete mode 100644 test/support/schemas/active_model_serializers/test/schema_test/my/index.json delete mode 100644 test/support/schemas/active_model_serializers/test/schema_test/my/show.json delete mode 100644 test/support/schemas/custom/show.json delete mode 100644 test/support/schemas/hyper_schema.json delete mode 100644 test/support/schemas/render_using_json_api.json delete mode 100644 test/support/schemas/simple_json_pointers.json delete mode 100644 test/support/serialization_testing.rb delete mode 100644 test/test_helper.rb diff --git a/.rubocop.yml b/.rubocop.yml deleted file mode 100644 index 82f07656..00000000 --- a/.rubocop.yml +++ /dev/null @@ -1,105 +0,0 @@ -AllCops: - TargetRubyVersion: 2.1 - Exclude: - - !ruby/regexp /(vendor|bundle|bin|db|tmp)\/.*/ - DisplayCopNames: true - DisplayStyleGuide: true - # https://github.com/bbatsov/rubocop/blob/master/manual/caching.md - # https://github.com/bbatsov/rubocop/blob/e8680418b351491e111a18cf5b453fc07a3c5239/config/default.yml#L60-L77 - UseCache: true - CacheRootDirectory: tmp - -Rails: - Enabled: true - -Lint/NestedMethodDefinition: - Enabled: false - Exclude: - - test/action_controller/serialization_test.rb - -Style/Alias: - EnforcedStyle: prefer_alias - -Style/StringLiterals: - EnforcedStyle: single_quotes - -Metrics/AbcSize: - Max: 35 # TODO: Lower to 15 - -Metrics/ClassLength: - Max: 261 # TODO: Lower to 100 - Exclude: - - test/**/*.rb - -Metrics/CyclomaticComplexity: - Max: 7 # TODO: Lower to 6 - -Metrics/LineLength: - Max: 251 # TODO: Lower to 80 - -Metrics/MethodLength: - Max: 106 # TODO: Lower to 10 - -Metrics/PerceivedComplexity: - Max: 9 # TODO: Lower to 7 - -Style/AlignParameters: - EnforcedStyle: with_fixed_indentation - -Style/ClassAndModuleChildren: - EnforcedStyle: nested - -Style/Documentation: - Enabled: false - -Style/MissingElse: - Enabled: true - EnforcedStyle: case - -Style/EmptyElse: - EnforcedStyle: empty - -Style/MultilineOperationIndentation: - EnforcedStyle: indented - -Style/BlockDelimiters: - Enabled: true - EnforcedStyle: line_count_based - -Style/SignalException: - EnforcedStyle: semantic - -Style/TrailingCommaInLiteral: - EnforcedStyleForMultiline: no_comma - -Style/ConditionalAssignment: - Enabled: false - -Style/DotPosition: - EnforcedStyle: leading - -########## test_helper.rb sanity -Style/EndBlock: - Exclude: - - test/test_helper.rb - -Style/SpecialGlobalVars: - Exclude: - - test/test_helper.rb - -Style/GlobalVars: - Exclude: - - test/test_helper.rb - -Style/AndOr: - Exclude: - - test/test_helper.rb - - 'lib/active_model/serializer/lint.rb' - -Style/Not: - Exclude: - - test/test_helper.rb - -Style/ClassCheck: - Exclude: - - test/test_helper.rb diff --git a/.simplecov b/.simplecov deleted file mode 100644 index 955a6060..00000000 --- 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 index 9aff1edc..b48fe610 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,54 +2,9 @@ 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 + - true diff --git a/CHANGELOG-0-08.md b/CHANGELOG-0-08.md new file mode 100644 index 00000000..eec4e286 --- /dev/null +++ b/CHANGELOG-0-08.md @@ -0,0 +1,92 @@ +## 0.08.x + +### v0.8.3 (2014/12/10 14:45 +00:00) +- [#753](https://github.com/rails-api/active_model_serializers/pull/753) Test against Ruby 2.2 on Travis CI (@tricknotes) +- [#745](https://github.com/rails-api/active_model_serializers/pull/745) Missing a word (@jockee) + +### v0.8.2 (2014/09/01 21:00 +00:00) +- [#612](https://github.com/rails-api/active_model_serializers/pull/612) Feature/adapter (@bolshakov) + * adds adapters pattern +- [#615](https://github.com/rails-api/active_model_serializers/pull/615) Rails does not support const_defined? in development mode (@tpitale) +- [#613](https://github.com/rails-api/active_model_serializers/pull/613) README: typo fix on attributes (@spk) +- [#614](https://github.com/rails-api/active_model_serializers/pull/614) Fix rails 4.0.x build. (@arthurnn) +- [#610](https://github.com/rails-api/active_model_serializers/pull/610) ArraySerializer (@bolshakov) +- [#607](https://github.com/rails-api/active_model_serializers/pull/607) ruby syntax highlights (@zigomir) +- [#602](https://github.com/rails-api/active_model_serializers/pull/602) Add DSL for associations (@JordanFaust) + +### 0.8.1 (May 6, 2013) + +* Fix bug whereby a serializer using 'options' would blow up. + +### 0.8.0 (May 5, 2013) + +* Attributes can now have optional types. + +* A new DefaultSerializer ensures that POROs behave the same way as ActiveModels. + +* If you wish to override ActiveRecord::Base#to_Json, you can now require + 'active_record/serializer_override'. We don't recommend you do this, but + many users do, so we've left it optional. + +* Fixed a bug where ActionController wouldn't always have MimeResponds. + +* An optinal caching feature allows you to cache JSON & hashes that AMS uses. + Adding 'cached true' to your Serializers will turn on this cache. + +* URL helpers used inside of Engines now work properly. + +* Serializers now can filter attributes with `only` and `except`: + + ``` + UserSerializer.new(user, only: [:first_name, :last_name]) + UserSerializer.new(user, except: :first_name) + ``` + +* Basic Mongoid support. We now include our mixins in the right place. + +* On Ruby 1.8, we now generate an `id` method that properly serializes `id` + columns. See issue #127 for more. + +* Add an alias for `scope` method to be the name of the context. By default + this is `current_user`. The name is automatically set when using + `serialization_scope` in the controller. + +* Pass through serialization options (such as `:include`) when a model + has no serializer defined. + +## [0.7.0 (March 6, 2013)](https://github.com/rails-api/active_model_serializers/commit/fabdc621ff97fbeca317f6301973dd4564b9e695) + +* ```embed_key``` option to allow embedding by attributes other than IDs +* Fix rendering nil with custom serializer +* Fix global ```self.root = false``` +* Add support for specifying the serializer for an association as a String +* Able to specify keys on the attributes method +* Serializer Reloading via ActiveSupport::DescendantsTracker +* Reduce double map to once; Fixes datamapper eager loading. + +## 0.6.0 (October 22, 2012) + +* Serialize sets properly +* Add root option to ArraySerializer +* Support polymorphic associations +* Support :each_serializer in ArraySerializer +* Add `scope` method to easily access the scope in the serializer +* Fix regression with Rails 3.2.6; add Rails 4 support +* Allow serialization_scope to be disabled with serialization_scope nil +* Array serializer should support pure ruby objects besides serializers + +## 0.05.x + +### [0.5.2 (June 5, 2012)](https://github.com/rails-api/active_model_serializers/commit/615afd125c260432d456dc8be845867cf87ea118#diff-0c5c12f311d3b54734fff06069efd2ac) + +### [0.5.1 (May 23, 2012)](https://github.com/rails-api/active_model_serializers/commit/00194ec0e41831802fcbf893a34c0bb0853ebe14#diff-0c5c12f311d3b54734fff06069efd2ac) + +### [0.5.0 (May 16, 2012)](https://github.com/rails-api/active_model_serializers/commit/33d4842dcd35c7167b0b33fc0abcf00fb2c92286) + +* First tagged version +* Changes generators to always generate an ApplicationSerializer + +## [0.1.0 (December 21, 2011)](https://github.com/rails-api/active_model_serializers/commit/1e0c9ef93b96c640381575dcd30be07ac946818b) + +## First Commit as [Rails Serializers 0.0.1](https://github.com/rails-api/active_model_serializers/commit/d72b66d4c5355b0ff0a75a04895fcc4ea5b0c65e) + (December 1, 2011). diff --git a/CHANGELOG-0-09.md b/CHANGELOG-0-09.md new file mode 100644 index 00000000..a36e8e9b --- /dev/null +++ b/CHANGELOG-0-09.md @@ -0,0 +1,74 @@ +## 0.09.x + +### v0.9.3 (2015/01/21 20:29 +00:00) + +Features: +- [#774](https://github.com/rails-api/active_model_serializers/pull/774) Fix nested include attributes (@nhocki) +- [#771](https://github.com/rails-api/active_model_serializers/pull/771) Make linked resource type names consistent with root names (@sweatypitts) +- [#696](https://github.com/rails-api/active_model_serializers/pull/696) Explicitly set serializer for associations (@ggordon) +- [#700](https://github.com/rails-api/active_model_serializers/pull/700) sparse fieldsets (@arenoir) +- [#768](https://github.com/rails-api/active_model_serializers/pull/768) Adds support for `meta` and `meta_key` attribute (@kurko) + +### v0.9.1 (2014/12/04 11:54 +00:00) +- [#707](https://github.com/rails-api/active_model_serializers/pull/707) A Friendly Note on Which AMS Version to Use (@jherdman) +- [#730](https://github.com/rails-api/active_model_serializers/pull/730) Fixes nested has_many links in JSONAPI (@kurko) +- [#718](https://github.com/rails-api/active_model_serializers/pull/718) Allow overriding the adapter with render option (@ggordon) +- [#720](https://github.com/rails-api/active_model_serializers/pull/720) Rename attribute with :key (0.8.x compatibility) (@ggordon) +- [#728](https://github.com/rails-api/active_model_serializers/pull/728) Use type as key for linked resources (@kurko) +- [#729](https://github.com/rails-api/active_model_serializers/pull/729) Use the new beta build env on Travis (@joshk) +- [#703](https://github.com/rails-api/active_model_serializers/pull/703) Support serializer and each_serializer options in renderer (@ggordon, @mieko) +- [#727](https://github.com/rails-api/active_model_serializers/pull/727) Includes links inside of linked resources (@kurko) +- [#726](https://github.com/rails-api/active_model_serializers/pull/726) Bugfix: include nested has_many associations (@kurko) +- [#722](https://github.com/rails-api/active_model_serializers/pull/722) Fix infinite recursion (@ggordon) +- [#1](https://github.com/rails-api/active_model_serializers/pull/1) Allow for the implicit use of ArraySerializer when :each_serializer is specified (@mieko) +- [#692](https://github.com/rails-api/active_model_serializers/pull/692) Include 'linked' member for json-api collections (@ggordon) +- [#714](https://github.com/rails-api/active_model_serializers/pull/714) Define as_json instead of to_json (@guilleiguaran) +- [#710](https://github.com/rails-api/active_model_serializers/pull/710) JSON-API: Don't include linked section if associations are empty (@guilleiguaran) +- [#711](https://github.com/rails-api/active_model_serializers/pull/711) Fixes rbx gems bundling on TravisCI (@kurko) +- [#709](https://github.com/rails-api/active_model_serializers/pull/709) Add type key when association name is different than object type (@guilleiguaran) +- [#708](https://github.com/rails-api/active_model_serializers/pull/708) Handle correctly null associations (@guilleiguaran) +- [#691](https://github.com/rails-api/active_model_serializers/pull/691) Fix embed option for associations (@jacob-s-son) +- [#689](https://github.com/rails-api/active_model_serializers/pull/689) Fix support for custom root in JSON-API adapter (@guilleiguaran) +- [#685](https://github.com/rails-api/active_model_serializers/pull/685) Serialize ids as strings in JSON-API adapter (@guilleiguaran) +- [#684](https://github.com/rails-api/active_model_serializers/pull/684) Refactor adapters to implement support for array serialization (@guilleiguaran) +- [#682](https://github.com/rails-api/active_model_serializers/pull/682) Include root by default in JSON-API serializers (@guilleiguaran) +- [#625](https://github.com/rails-api/active_model_serializers/pull/625) Add DSL for urls (@JordanFaust) +- [#677](https://github.com/rails-api/active_model_serializers/pull/677) Add support for embed: :ids option for in associations (@guilleiguaran) +- [#681](https://github.com/rails-api/active_model_serializers/pull/681) Check superclasses for Serializers (@quainjn) +- [#680](https://github.com/rails-api/active_model_serializers/pull/680) Add support for root keys (@NullVoxPopuli) +- [#675](https://github.com/rails-api/active_model_serializers/pull/675) Support Rails 4.2.0 (@tricknotes) +- [#667](https://github.com/rails-api/active_model_serializers/pull/667) Require only activemodel instead of full rails (@guilleiguaran) +- [#653](https://github.com/rails-api/active_model_serializers/pull/653) Add "_test" suffix to JsonApi::HasManyTest filename. (@alexgenco) +- [#631](https://github.com/rails-api/active_model_serializers/pull/631) Update build badge URL (@craiglittle) + +### 0.9.0.alpha1 - January 7, 2014 + +### 0.9.0.pre + +* The following methods were removed + - Model#active\_model\_serializer + - Serializer#include! + - Serializer#include? + - Serializer#attr\_disabled= + - Serializer#cache + - Serializer#perform\_caching + - Serializer#schema (needs more discussion) + - Serializer#attribute + - Serializer#include\_#{name}? (filter method added) + - Serializer#attributes (took a hash) + +* The following things were added + - Serializer#filter method + - CONFIG object + +* Remove support for ruby 1.8 versions. + +* Require rails >= 3.2. + +* Serializers for associations are being looked up in a parent serializer's namespace first. Same with controllers' namespaces. + +* Added a "prefix" option in case you want to use a different version of serializer. + +* Serializers default namespace can be set in `default_serializer_options` and inherited by associations. + +* [Beginning of rewrite: c65d387705ec534db171712671ba7fcda4f49f68](https://github.com/rails-api/active_model_serializers/commit/c65d387705ec534db171712671ba7fcda4f49f68) diff --git a/CHANGELOG-0-10.md b/CHANGELOG-0-10.md new file mode 100644 index 00000000..fbe0bd21 --- /dev/null +++ b/CHANGELOG-0-10.md @@ -0,0 +1,466 @@ +## 0.10.x + +### [0-10-stable (unreleased)](https://github.com/rails-api/active_model_serializers/compare/v0.10.6...0-10-stable) + +Breaking changes: + +Features: + +Fixes: + +Misc: + +### [v0.10.6 (2017-05-01)](https://github.com/rails-api/active_model_serializers/compare/v0.10.5...v0.10.6) + +Fixes: + +- [#1857](https://github.com/rails-api/active_model_serializers/pull/1857) JSON:API does not load belongs_to relation to get identifier id. (@bf4) +- [#2119](https://github.com/rails-api/active_model_serializers/pull/2119) JSON:API returns null resource object identifier when 'id' is null. (@bf4) +- [#2093](https://github.com/rails-api/active_model_serializers/pull/2093) undef problematic Serializer methods: display, select. (@bf4) + +Misc: + +- [#2104](https://github.com/rails-api/active_model_serializers/pull/2104) Documentation for serializers and rendering. (@cassidycodes) +- [#2081](https://github.com/rails-api/active_model_serializers/pull/2081) Documentation for `include` option in adapters. (@charlie-wasp) +- [#2120](https://github.com/rails-api/active_model_serializers/pull/2120) Documentation for association options: foreign_key, type, class_name, namespace. (@bf4) + +### [v0.10.5 (2017-03-07)](https://github.com/rails-api/active_model_serializers/compare/v0.10.4...v0.10.5) + +Breaking changes: + +Features: + +- [#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) +- [#2057](https://github.com/rails-api/active_model_serializers/pull/2057) + Update version constraint for jsonapi-renderer to `['>= 0.1.1.beta1', '< 0.2']` + (@jaredbeck) + +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) + +Misc: + +- [#2055](https://github.com/rails-api/active_model_serializers/pull/2055) + Replace deprecated dependency jsonapi with jsonapi-renderer. (@jaredbeck) +- [#2021](https://github.com/rails-api/active_model_serializers/pull/2021) Make test attributes explicit. Tests have Model#associations. (@bf4) +- [#1981](https://github.com/rails-api/active_model_serializers/pull/1981) Fix relationship link documentation. (@groyoh) +- [#2035](https://github.com/rails-api/active_model_serializers/pull/2035) Document how to disable the logger. (@MSathieu) +- [#2039](https://github.com/rails-api/active_model_serializers/pull/2039) Documentation fixes. (@biow0lf) + +### [v0.10.4 (2017-01-06)](https://github.com/rails-api/active_model_serializers/compare/v0.10.3...v0.10.4) + +Misc: + +- [#2005](https://github.com/rails-api/active_model_serializers/pull/2005) Update jsonapi runtime dependency to 0.1.1.beta6, support Ruby 2.4. (@kofronpi) +- [#1993](https://github.com/rails-api/active_model_serializers/pull/1993) Swap out KeyTransform for CaseTransform gem for the possibility of native extension use. (@NullVoxPopuli) + +### [v0.10.3 (2016-11-21)](https://github.com/rails-api/active_model_serializers/compare/v0.10.2...v0.10.3) + +Fixes: + +- [#1973](https://github.com/rails-api/active_model_serializers/pull/1973) Fix namespace lookup for collections and has_many relationships (@groyoh) +- [#1887](https://github.com/rails-api/active_model_serializers/pull/1887) Make the comment reflect what the function does (@johnnymo87) +- [#1890](https://github.com/rails-api/active_model_serializers/issues/1890) Ensure generator inherits from ApplicationSerializer when available (@richmolj) +- [#1922](https://github.com/rails-api/active_model_serializers/pull/1922) Make railtie an optional dependency in runtime (@ggpasqualino) +- [#1930](https://github.com/rails-api/active_model_serializers/pull/1930) Ensure valid jsonapi when relationship has no links or data (@richmolj) + +Features: + +- [#1757](https://github.com/rails-api/active_model_serializers/pull/1757) Make serializer lookup chain configurable. (@NullVoxPopuli) +- [#1968](https://github.com/rails-api/active_model_serializers/pull/1968) (@NullVoxPopuli) + - Add controller namespace to default controller lookup + - Provide a `namespace` render option + - document how set the namespace in the controller for implicit lookup. +- [#1791](https://github.com/rails-api/active_model_serializers/pull/1791) (@bf4, @youroff, @NullVoxPopuli) + - Added `jsonapi_namespace_separator` config option. +- [#1889](https://github.com/rails-api/active_model_serializers/pull/1889) Support key transformation for Attributes adapter (@iancanderson, @danbee) +- [#1917](https://github.com/rails-api/active_model_serializers/pull/1917) Add `jsonapi_pagination_links_enabled` configuration option (@richmolj) +- [#1797](https://github.com/rails-api/active_model_serializers/pull/1797) Only include 'relationships' when sideloading (@richmolj) + +Fixes: + +- [#1833](https://github.com/rails-api/active_model_serializers/pull/1833) Remove relationship links if they are null (@groyoh) +- [#1881](https://github.com/rails-api/active_model_serializers/pull/1881) ActiveModelSerializers::Model correctly works with string keys (@yevhene) + +Misc: +- [#1767](https://github.com/rails-api/active_model_serializers/pull/1767) Replace raising/rescuing `CollectionSerializer::NoSerializerError`, + throw/catch `:no_serializer`. (@bf4) +- [#1839](https://github.com/rails-api/active_model_serializers/pull/1839) `fields` tests demonstrating usage for both attributes and relationships. (@NullVoxPopuli) +- [#1812](https://github.com/rails-api/active_model_serializers/pull/1812) add a code of conduct (@corainchicago) + +- [#1878](https://github.com/rails-api/active_model_serializers/pull/1878) Cache key generation for serializers now uses `ActiveSupport::Cache.expand_cache_key` instead of `Array#join` by default and is also overridable. This change should be backward-compatible. (@markiz) + +- [#1799](https://github.com/rails-api/active_model_serializers/pull/1799) Add documentation for setting the adapter. (@cassidycodes) +- [#1909](https://github.com/rails-api/active_model_serializers/pull/1909) Add documentation for relationship links. (@vasilakisfil, @NullVoxPopuli) +- [#1959](https://github.com/rails-api/active_model_serializers/pull/1959) Add documentation for root. (@shunsuke227ono) +- [#1967](https://github.com/rails-api/active_model_serializers/pull/1967) Improve type method documentation. (@yukideluxe) + +### [v0.10.2 (2016-07-05)](https://github.com/rails-api/active_model_serializers/compare/v0.10.1...v0.10.2) + +Fixes: +- [#1814](https://github.com/rails-api/active_model_serializers/pull/1814) Ensuring read_multi works with fragment cache +- [#1848](https://github.com/rails-api/active_model_serializers/pull/1848) Redefine associations on inherited serializers. (@EhsanYousefi) + +Misc: +- [#1808](https://github.com/rails-api/active_model_serializers/pull/1808) Adds documentation for `fields` option. (@luizkowalski) + +### [v0.10.1 (2016-06-16)](https://github.com/rails-api/active_model_serializers/compare/v0.10.0...v0.10.1) + +Features: +- [#1668](https://github.com/rails-api/active_model_serializers/pull/1668) Exclude nil and empty links. (@sigmike) +- [#1426](https://github.com/rails-api/active_model_serializers/pull/1426) Add ActiveModelSerializers.config.default_includes (@empact) + +Fixes: +- [#1754](https://github.com/rails-api/active_model_serializers/pull/1754) Fixes #1759, Grape integration, improves serialization_context + missing error message on pagination. Document overriding CollectionSerializer#paginated?. (@bf4) + Moved serialization_context creation to Grape formatter, so resource serialization works without explicit calls to the `render` helper method. + Added Grape collection tests. (@onomated) +- [#1287](https://github.com/rails-api/active_model_serializers/pull/1287) Pass `fields` options from adapter to serializer. (@vasilakisfil) +- [#1710](https://github.com/rails-api/active_model_serializers/pull/1710) Prevent association loading when `include_data` option + is set to `false`. (@groyoh) +- [#1747](https://github.com/rails-api/active_model_serializers/pull/1747) Improve jsonapi mime type registration for Rails 5 (@remear) + +Misc: +- [#1734](https://github.com/rails-api/active_model_serializers/pull/1734) Adds documentation for conditional attribute (@lambda2) +- [#1685](https://github.com/rails-api/active_model_serializers/pull/1685) Replace `IncludeTree` with `IncludeDirective` from the jsonapi gem. + +### [v0.10.0 (2016-05-17)](https://github.com/rails-api/active_model_serializers/compare/4a2d9853ba7...v0.10.0) + +Breaking changes: +- [#1662](https://github.com/rails-api/active_model_serializers/pull/1662) Drop support for Rails 4.0 and Ruby 2.0.0. (@remear) + +Features: +- [#1677](https://github.com/rails-api/active_model_serializers/pull/1677) Add `assert_schema`, `assert_request_schema`, `assert_request_response_schema`. (@bf4) +- [#1697](https://github.com/rails-api/active_model_serializers/pull/1697) Include actual exception message with custom exceptions; + `Test::Schema` exceptions are now `Minitest::Assertion`s. (@bf4) +- [#1699](https://github.com/rails-api/active_model_serializers/pull/1699) String/Lambda support for conditional attributes/associations (@mtsmfm) +- [#1687](https://github.com/rails-api/active_model_serializers/pull/1687) Only calculate `_cache_digest` (in `cache_key`) when `skip_digest` is false. (@bf4) +- [#1647](https://github.com/rails-api/active_model_serializers/pull/1647) Restrict usage of `serializable_hash` options + to the ActiveModel::Serialization and ActiveModel::Serializers::JSON interface. (@bf4) + +Fixes: +- [#1700](https://github.com/rails-api/active_model_serializers/pull/1700) Support pagination link for Kaminari when no data is returned. (@iamnader) +- [#1726](https://github.com/rails-api/active_model_serializers/pull/1726) Adds polymorphic option to association definition which includes association type/nesting in serializer (@cgmckeever) + +Misc: +- [#1673](https://github.com/rails-api/active_model_serializers/pull/1673) Adds "How to" guide on using AMS with POROs (@DrSayre) +- [#1730](https://github.com/rails-api/active_model_serializers/pull/1730) Adds documentation for overriding default serializer based on conditions (@groyoh/@cgmckeever) + +### [v0.10.0.rc5 (2016-04-04)](https://github.com/rails-api/active_model_serializers/compare/v0.10.0.rc4...v0.10.0.rc5) + +Breaking changes: + +- [#1645](https://github.com/rails-api/active_model_serializers/pull/1645) Changed :dashed key transform to :dash. (@remear) +- [#1574](https://github.com/rails-api/active_model_serializers/pull/1574) Default key case for the JsonApi adapter changed to dashed. (@remear) + +Features: +- [#1645](https://github.com/rails-api/active_model_serializers/pull/1645) Transform keys referenced in values. (@remear) +- [#1650](https://github.com/rails-api/active_model_serializers/pull/1650) Fix serialization scope options `scope`, `scope_name` + take precedence over `serialization_scope` in the controller. + Fix tests that required tearing down dynamic methods. (@bf4) +- [#1644](https://github.com/rails-api/active_model_serializers/pull/1644) Include adapter name in cache key so + that the same serializer can be cached per adapter. (@bf4 via #1346 by @kevintyll) +- [#1642](https://github.com/rails-api/active_model_serializers/pull/1642) Prefer object.cache_key over the generated + cache key. (@bf4 via #1346 by @kevintyll) +- [#1637](https://github.com/rails-api/active_model_serializers/pull/1637) Make references to 'ActionController::Base.cache_store' explicit + in order to avoid issues when application controllers inherit from 'ActionController::API'. (@ncuesta) +- [#1633](https://github.com/rails-api/active_model_serializers/pull/1633) Yield 'serializer' to serializer association blocks. (@bf4) +- [#1616](https://github.com/rails-api/active_model_serializers/pull/1616) SerializableResource handles no serializer like controller. (@bf4) +- [#1618](https://github.com/rails-api/active_model_serializers/issues/1618) Get collection root key for + empty collection from explicit serializer option, when possible. (@bf4) +- [#1574](https://github.com/rails-api/active_model_serializers/pull/1574) Provide key translation. (@remear) +- [#1494](https://github.com/rails-api/active_model_serializers/pull/1494) Make serializers serializalbe + (using the Attributes adapter by default). (@bf4) +- [#1550](https://github.com/rails-api/active_model_serializers/pull/1550) Add + Rails url_helpers to `SerializationContext` for use in links. (@remear, @bf4) +- [#1004](https://github.com/rails-api/active_model_serializers/pull/1004) JSON API errors object implementation. + - Only implements `detail` and `source` as derived from `ActiveModel::Error` + - Provides checklist of remaining questions and remaining parts of the spec. +- [#1515](https://github.com/rails-api/active_model_serializers/pull/1515) Adds support for symbols to the + `ActiveModel::Serializer.type` method. (@groyoh) +- [#1504](https://github.com/rails-api/active_model_serializers/pull/1504) Adds the changes missing from #1454 + and add more tests for resource identifier and relationship objects. Fix association block with link + returning `data: nil`.(@groyoh) +- [#1372](https://github.com/rails-api/active_model_serializers/pull/1372) Support + cache_store.read_multi. (@LcpMarvel) +- [#1018](https://github.com/rails-api/active_model_serializers/pull/1018) Add more tests and docs for top-level links. (@leandrocp) +- [#1454](https://github.com/rails-api/active_model_serializers/pull/1454) Add support for + relationship-level links and meta attributes. (@beauby) +- [#1340](https://github.com/rails-api/active_model_serializers/pull/1340) Add support for resource-level meta. (@beauby) + +Fixes: +- [#1657](https://github.com/rails-api/active_model_serializers/pull/1657) Add missing missing require "active_support/json". (@andreaseger) +- [#1661](https://github.com/rails-api/active_model_serializers/pull/1661) Fixes `read_attribute_for_serialization` not + seeing methods defined in serialization superclass (#1653, #1658, #1660), introduced in #1650. (@bf4) +- [#1651](https://github.com/rails-api/active_model_serializers/pull/1651) Fix deserialization of nil relationships. (@NullVoxPopuli) +- [#1480](https://github.com/rails-api/active_model_serializers/pull/1480) Fix setting of cache_store from Rails configuration. (@bf4) + Fix unintentional mutating of value in memory cache store. (@groyoh) +- [#1622](https://github.com/rails-api/active_model_serializers/pull/1622) Fragment cache changed from per-record to per-serializer. + Now, two serializers that use the same model may be separately cached. (@lserman) +- [#1478](https://github.com/rails-api/active_model_serializers/pull/1478) Cache store will now be correctly set when serializers are + loaded *before* Rails initializes. (@bf4) +- [#1570](https://github.com/rails-api/active_model_serializers/pull/1570) Fixed pagination issue with last page size. (@bmorrall) +- [#1516](https://github.com/rails-api/active_model_serializers/pull/1516) No longer return a nil href when only + adding meta to a relationship link. (@groyoh) +- [#1458](https://github.com/rails-api/active_model_serializers/pull/1458) Preserve the serializer + type when fragment caching. (@bdmac) +- [#1477](https://github.com/rails-api/active_model_serializers/pull/1477) Fix `fragment_cached?` + method to check if caching. (@bdmac) +- [#1501](https://github.com/rails-api/active_model_serializers/pull/1501) Adds tests for SerializableResource::use_adapter?,doc typos (@domitian) +- [#1488](https://github.com/rails-api/active_model_serializers/pull/1488) Require ActiveSupport's string inflections (@nate00) + +Misc: +- [#1608](https://github.com/rails-api/active_model_serializers/pull/1608) Move SerializableResource to ActiveModelSerializers (@groyoh) +- [#1602](https://github.com/rails-api/active_model_serializers/pull/1602) Add output examples to Adapters docs (@remear) +- [#1557](https://github.com/rails-api/active_model_serializers/pull/1557) Update docs regarding overriding the root key (@Jwan622) +- [#1471](https://github.com/rails-api/active_model_serializers/pull/1471) [Cleanup] Serializer caching is its own concern. (@bf4) +- [#1482](https://github.com/rails-api/active_model_serializers/pull/1482) Document JSON API implementation defs and progress in class. (@bf4) +- [#1551](https://github.com/rails-api/active_model_serializers/pull/1551) Added codebeat badge (@korzonek) +- [#1527](https://github.com/rails-api/active_model_serializers/pull/1527) Refactor fragment cache class. (@groyoh) +- [#1560](https://github.com/rails-api/active_model_serializers/pull/1560) Update rubocop and address its warnings. (@bf4 @groyoh) +- [#1545](https://github.com/rails-api/active_model_serializers/pull/1545) Document how to pass arbitrary options to the + serializer (@CodedBeardedSignedTaylor) +- [#1496](https://github.com/rails-api/active_model_serializers/pull/1496) Run all branches against JRuby on CI (@nadavshatz) +- [#1559](https://github.com/rails-api/active_model_serializers/pull/1559) Add a deprecation DSL. (@bf4 @groyoh) +- [#1543](https://github.com/rails-api/active_model_serializers/pull/1543) Add the changes missing from #1535. (@groyoh) +- [#1535](https://github.com/rails-api/active_model_serializers/pull/1535) Move the adapter and adapter folder to + active_model_serializers folder and changes the module namespace. (@domitian @bf4) +- [#1497](https://github.com/rails-api/active_model_serializers/pull/1497) Add JRuby-9000 to appveyor.yml(@corainchicago) +- [#1420](https://github.com/rails-api/active_model_serializers/pull/1420) Adds tests and documentation for polymorphism(@marcgarreau) + + +### [v0.10.0.rc4 (2016-01-27)](https://github.com/rails-api/active_model_serializers/compare/v0.10.0.rc3...v0.10.0.rc4) +Breaking changes: + +- [#1360](https://github.com/rails-api/active_model_serializers/pull/1360) + [#1369](https://github.com/rails-api/active_model_serializers/pull/1369) Drop support for Ruby 1.9.3 (@karaAJC, @maurogeorge) +- [#1131](https://github.com/rails-api/active_model_serializers/pull/1131) Remove Serializer#root_name (@beauby) +- [#1138](https://github.com/rails-api/active_model_serializers/pull/1138) Introduce Adapter::Base (@bf4) + * Adapters now inherit Adapter::Base. 'Adapter' is now a module, no longer a class. + * using a class as a namespace that you also inherit from is complicated and circular at times i.e. + buggy (see https://github.com/rails-api/active_model_serializers/pull/1177) + * The class methods on Adapter aren't necessarily related to the instance methods, they're more + Adapter functions. + * named `Base` because it's a Rails-ism. + * It helps to isolate and highlight what the Adapter interface actually is. +- [#1418](https://github.com/rails-api/active_model_serializers/pull/1418) + serialized collections now use the root option as is; now, only the + root derived from the serializer or object is always pluralized. + +Features: + +- [#1406](https://github.com/rails-api/active_model_serializers/pull/1406) Allow for custom dynamic values in JSON API links (@beauby) +- [#1270](https://github.com/rails-api/active_model_serializers/pull/1270) Adds `assert_response_schema` test helper (@maurogeorge) +- [#1099](https://github.com/rails-api/active_model_serializers/pull/1099) Adds `assert_serializer` test helper (@maurogeorge) +- [#1403](https://github.com/rails-api/active_model_serializers/pull/1403) Add support for if/unless on attributes/associations (@beauby) +- [#1248](https://github.com/rails-api/active_model_serializers/pull/1248) Experimental: Add support for JSON API deserialization (@beauby) +- [#1378](https://github.com/rails-api/active_model_serializers/pull/1378) Change association blocks + to be evaluated in *serializer* scope, rather than *association* scope. (@bf4) + * Syntax changes from e.g. + `has_many :titles do customers.pluck(:title) end` (in #1356) to + `has_many :titles do object.customers.pluck(:title) end` +- [#1356](https://github.com/rails-api/active_model_serializers/pull/1356) Add inline syntax for + attributes and associations (@bf4 @beauby @noahsilas) + * Allows defining attributes so that they don't conflict with existing methods. e.g. `attribute + :title do 'Mr. Topum Hat' end` + * Allows defining associations so that they don't conflict with existing methods. e.g. `has_many + :titles do customers.pluck(:title) end` + * Allows dynamic associations, as compared to compare to using + [`virtual_value`](https://github.com/rails-api/active_model_serializers/pull/1356#discussion_r47146466). + e.g. `has_many :reviews, virtual_value: [{ id: 1 }, { id: 2 }]` + * Removes dynamically defined methods on the serializer +- [#1336](https://github.com/rails-api/active_model_serializers/pull/1336) Added support for Grape >= 0.13, < 1.0 (@johnhamelink) +- [#1322](https://github.com/rails-api/active_model_serializers/pull/1322) Instrumenting rendering of resources (@bf4, @maurogeorge) +- [#1291](https://github.com/rails-api/active_model_serializers/pull/1291) Add logging (@maurogeorge) +- [#1272](https://github.com/rails-api/active_model_serializers/pull/1272) Add PORO serializable base class: ActiveModelSerializers::Model (@bf4) +- [#1255](https://github.com/rails-api/active_model_serializers/pull/1255) Make more class attributes inheritable (@bf4) +- [#1249](https://github.com/rails-api/active_model_serializers/pull/1249) Inheritance of serializer inheriting the cache configuration(@Rodrigora) +- [#1247](https://github.com/rails-api/active_model_serializers/pull/1247) Add support for toplevel JSON API links (@beauby) +- [#1246](https://github.com/rails-api/active_model_serializers/pull/1246) Add support for resource-level JSON API links (@beauby) +- [#1225](https://github.com/rails-api/active_model_serializers/pull/1225) Better serializer lookup, use nested serializer when it exists (@beauby) +- [#1213](https://github.com/rails-api/active_model_serializers/pull/1213) `type` directive for serializer to control type field with json-api adapter (@youroff) +- [#1172](https://github.com/rails-api/active_model_serializers/pull/1172) Better serializer registration, get more than just the first module (@bf4) +- [#1158](https://github.com/rails-api/active_model_serializers/pull/1158) Add support for wildcards in `include` option (@beauby) +- [#1127](https://github.com/rails-api/active_model_serializers/pull/1127) Add support for nested + associations for JSON and Attributes adapters via the `include` option (@NullVoxPopuli, @beauby). +- [#1050](https://github.com/rails-api/active_model_serializers/pull/1050) Add support for toplevel jsonapi member (@beauby, @bf4) +- [#1251](https://github.com/rails-api/active_model_serializers/pull/1251) Rename ArraySerializer to + CollectionSerializer for clarity, add ActiveModelSerializers.config.collection_serializer (@bf4) +- [#1295](https://github.com/rails-api/active_model_serializers/pull/1295) Add config `serializer_lookup_enabled` that, + when disabled, requires serializers to explicitly specified. (@trek) + +Fixes: + +- [#1352](https://github.com/rails-api/active_model_serializers/pull/1352) Fix generators; Isolate Rails-specifc code in Railties (@dgynn, @bf4) +- [#1384](https://github.com/rails-api/active_model_serializers/pull/1384)Fix database state leaking across tests (@bf4) +- [#1297](https://github.com/rails-api/active_model_serializers/pull/1297) Fix `fields` option to restrict relationships as well (@beauby) +- [#1239](https://github.com/rails-api/active_model_serializers/pull/1239) Fix duplicates in JSON API compound documents (@beauby) +- [#1214](https://github.com/rails-api/active_model_serializers/pull/1214) retrieve the key from the reflection options when building associations (@NullVoxPopuli, @hut8) +- [#1358](https://github.com/rails-api/active_model_serializers/pull/1358) Handle serializer file paths with spaces (@rwstauner, @bf4) +- [#1195](https://github.com/rails-api/active_model_serializers/pull/1195) Fix id override (@beauby) +- [#1185](https://github.com/rails-api/active_model_serializers/pull/1185) Fix options passing in Json and Attributes adapters (@beauby) + +Misc: + +- [#1383](https://github.com/rails-api/active_model_serializers/pull/1383) Simplify reflections handling (@beauby) +- [#1370](https://github.com/rails-api/active_model_serializers/pull/1370) Simplify attributes handling via a mixin (@beauby) +- [#1301](https://github.com/rails-api/active_model_serializers/pull/1301) Mapping JSON API spec / schema to AMS (@bf4) +- [#1271](https://github.com/rails-api/active_model_serializers/pull/1271) Handle no serializer source file to digest (@bf4) +- [#1260](https://github.com/rails-api/active_model_serializers/pull/1260) Serialization and Cache Documentation (@bf4) +- [#1259](https://github.com/rails-api/active_model_serializers/pull/1259) Add more info to CONTRIBUTING (@bf4) +- [#1233](https://github.com/rails-api/active_model_serializers/pull/1233) Top-level meta and meta_key options no longer handled at serializer level (@beauby) +- [#1232](https://github.com/rails-api/active_model_serializers/pull/1232) fields option no longer handled at serializer level (@beauby) +- [#1220](https://github.com/rails-api/active_model_serializers/pull/1220) Remove empty rubocop.rake (@maurogeorge) +- [#1178](https://github.com/rails-api/active_model_serializers/pull/1178) env CAPTURE_STDERR=false lets devs see hard failures (@bf4) +- [#1177](https://github.com/rails-api/active_model_serializers/pull/1177) Remove Adapter autoloads in favor of require (@bf4) +- [#1117](https://github.com/rails-api/active_model_serializers/pull/1117) FlattenJson adapter no longer inherits Json adapter, renamed to Attributes (@bf4) +- [#1171](https://github.com/rails-api/active_model_serializers/pull/1171) add require statements to top of file (@shicholas) +- [#1167](https://github.com/rails-api/active_model_serializers/pull/1167) Delegate Serializer.attributes to Serializer.attribute (@bf4) +- [#1174](https://github.com/rails-api/active_model_serializers/pull/1174) Consistently refer to the 'JSON API' and the 'JsonApi' adapter (@bf4) +- [#1173](https://github.com/rails-api/active_model_serializers/pull/1173) Comment private accessor warnings (@bf4) +- [#1166](https://github.com/rails-api/active_model_serializers/pull/1166) Prefer methods over instance variables (@bf4) +- [#1168](https://github.com/rails-api/active_model_serializers/pull/1168) Fix appveyor failure cache not being expired (@bf4) +- [#1161](https://github.com/rails-api/active_model_serializers/pull/1161) Remove duplicate test helper (@bf4) +- [#1360](https://github.com/rails-api/active_model_serializers/pull/1360) Update CI to test 2.2.2 -> 2.2.3 (@karaAJC) +- [#1371](https://github.com/rails-api/active_model_serializers/pull/1371) Refactor, update, create documentation (@bf4) + +### [v0.10.0.rc3 (2015-09-16)](https://github.com/rails-api/active_model_serializers/compare/v0.10.0.rc2...v0.10.0.rc3) +- [#1129](https://github.com/rails-api/active_model_serializers/pull/1129) Remove SerializableResource.serialize in favor of `.new` (@bf4) +- [#1155](https://github.com/rails-api/active_model_serializers/pull/1155) Outside controller use tutorial (@CodedBeardedSignedTaylor) +- [#1154](https://github.com/rails-api/active_model_serializers/pull/1154) Rubocop fixes for issues introduced by #1089 (@NullVoxPopuli) +- [#1089](https://github.com/rails-api/active_model_serializers/pull/1089) Add ActiveModelSerializers.logger with default null device (@bf4) +- [#1109](https://github.com/rails-api/active_model_serializers/pull/1109) Make better use of Minitest's lifecycle (@bf4) +- [#1144](https://github.com/rails-api/active_model_serializers/pull/1144) Fix Markdown to adapters documentation (@bacarini) +- [#1121](https://github.com/rails-api/active_model_serializers/pull/1121) Refactor `add_links` in JSONAPI adapter. (@beauby) +- [#1150](https://github.com/rails-api/active_model_serializers/pull/1150) Remove legacy method accidentally reintroduced in #1017 (@beauby) +- [#1149](https://github.com/rails-api/active_model_serializers/pull/1149) Update README with nested included association example. (@mattmueller) +- [#1110](https://github.com/rails-api/active_model_serializers/pull/1110) Add lint tests for AR models (@beauby) +- [#1131](https://github.com/rails-api/active_model_serializers/pull/1131) Extended format for JSONAPI `include` option (@beauby) + * adds extended format for `include` option to JsonApi adapter +- [#1142](https://github.com/rails-api/active_model_serializers/pull/1142) Updating wording on cache expiry in README (@leighhalliday) +- [#1140](https://github.com/rails-api/active_model_serializers/pull/1140) Fix typo in fieldset exception (@lautis) +- [#1132](https://github.com/rails-api/active_model_serializers/pull/1132) Get rid of unnecessary instance variables, and implied dependencies. (@beauby) +- [#1139](https://github.com/rails-api/active_model_serializers/pull/1139) Documentation for serializing resources without render (@PericlesTheo) +- [#1017](https://github.com/rails-api/active_model_serializers/pull/1017) Make Adapters registerable so they are not namespace-constrained (@bf4) +- [#1120](https://github.com/rails-api/active_model_serializers/pull/1120) Add windows platform to loading sqlite3 (@Eric-Guo) +- [#1123](https://github.com/rails-api/active_model_serializers/pull/1123) Remove url options (@bacarini) +- [#1093](https://github.com/rails-api/active_model_serializers/pull/1093) Factor `with_adapter` + force cache clear before each test. (@beauby) +- [#1095](https://github.com/rails-api/active_model_serializers/pull/1095) Add documentation about configuration options. (@beauby) +- [#1069](https://github.com/rails-api/active_model_serializers/pull/1069) Add test coverage; account for no artifacts on CI (@bf4) +- [#1103](https://github.com/rails-api/active_model_serializers/pull/1103) Move `id` and `json_api_type` methods from `Serializer` to `JsonApi`. (@beauby) +- [#1106](https://github.com/rails-api/active_model_serializers/pull/1106) Add Style enforcer (via Rubocop) (@bf4) +- [#1079](https://github.com/rails-api/active_model_serializers/pull/1079) Add ArraySerializer#object like Serializer (@bf4) +- [#1096](https://github.com/rails-api/active_model_serializers/pull/1096) Fix definition of serializer attributes with multiple calls to `attri… (@beauby) +- [#1105](https://github.com/rails-api/active_model_serializers/pull/1105) Add ActiveRecord-backed fixtures. (@beauby) +- [#1108](https://github.com/rails-api/active_model_serializers/pull/1108) Better lint (@bf4) +- [#1102](https://github.com/rails-api/active_model_serializers/pull/1102) Remove remains of `embed` option. (@beauby) +- [#1090](https://github.com/rails-api/active_model_serializers/pull/1090) Clarify AMS dependencies (@bf4) +- [#1081](https://github.com/rails-api/active_model_serializers/pull/1081) Add configuration option to set resource type to singular/plural (@beauby) +- [#1067](https://github.com/rails-api/active_model_serializers/pull/1067) Fix warnings (@bf4) +- [#1066](https://github.com/rails-api/active_model_serializers/pull/1066) Adding appveyor to the project (@joaomdmoura, @Eric-Guo, @bf4) +- [#1071](https://github.com/rails-api/active_model_serializers/pull/1071) Make testing suite running and pass in Windows (@Eric-Guo, @bf4) +- [#1041](https://github.com/rails-api/active_model_serializers/pull/1041) Adding pagination links (@bacarini) + * adds support for `pagination links` at top level of JsonApi adapter +- [#1063](https://github.com/rails-api/active_model_serializers/pull/1063) Lead by example: lint PORO model (@bf4) +- [#1](https://github.com/rails-api/active_model_serializers/pull/1) Test caller line parsing and digesting (@bf4) +- [#1048](https://github.com/rails-api/active_model_serializers/pull/1048) Let FlattenJson adapter decide it doesn't include meta (@bf4) +- [#1060](https://github.com/rails-api/active_model_serializers/pull/1060) Update fragment cache to support namespaced objects (@aaronlerch) +- [#1052](https://github.com/rails-api/active_model_serializers/pull/1052) Use underscored json_root when serializing a collection (@whatthewhat) +- [#1051](https://github.com/rails-api/active_model_serializers/pull/1051) Fix some invalid JSON in docs (@tjschuck) +- [#1049](https://github.com/rails-api/active_model_serializers/pull/1049) Fix incorrect s/options = {}/options ||= {} (@bf4) +- [#1037](https://github.com/rails-api/active_model_serializers/pull/1037) allow for type attribute (@lanej) +- [#1034](https://github.com/rails-api/active_model_serializers/pull/1034) allow id attribute to be overriden (@lanej) +- [#1035](https://github.com/rails-api/active_model_serializers/pull/1035) Fixed Comments highlight (@artLopez) +- [#1031](https://github.com/rails-api/active_model_serializers/pull/1031) Disallow to define multiple associations at once (@bolshakov) +- [#1032](https://github.com/rails-api/active_model_serializers/pull/1032) Wrap railtie requirement with rescue (@elliotlarson) +- [#1026](https://github.com/rails-api/active_model_serializers/pull/1026) Bump Version Number to 0.10.0.rc2 (@jfelchner) +- [#985](https://github.com/rails-api/active_model_serializers/pull/985) Associations implementation refactoring (@bolshakov) +- [#954](https://github.com/rails-api/active_model_serializers/pull/954) Encapsulate serialization in ActiveModel::SerializableResource (@bf4) +- [#972](https://github.com/rails-api/active_model_serializers/pull/972) Capture app warnings on test run (@bf4) +- [#1019](https://github.com/rails-api/active_model_serializers/pull/1019) Improve README.md (@baojjeu) +- [#998](https://github.com/rails-api/active_model_serializers/pull/998) Changing root to model class name (@joaomdmoura) +- [#1006](https://github.com/rails-api/active_model_serializers/pull/1006) Fix adapter inflection bug for api -> API (@bf4) +- [#1016](https://github.com/rails-api/active_model_serializers/pull/1016) require rails/railtie before subclassing Rails::Railtie (@bf4) +- [#1013](https://github.com/rails-api/active_model_serializers/pull/1013) Root option with empty array support (@vyrak, @mareczek) +- [#994](https://github.com/rails-api/active_model_serializers/pull/994) Starting Docs structure (@joaomdmoura) +- [#1007](https://github.com/rails-api/active_model_serializers/pull/1007) Bug fix for ArraySerializer json_key (@jiajiawang) +- [#1003](https://github.com/rails-api/active_model_serializers/pull/1003) Fix transient test failures (@Rodrigora) +- [#996](https://github.com/rails-api/active_model_serializers/pull/996) Add linter for serializable resource (@bf4) +- [#990](https://github.com/rails-api/active_model_serializers/pull/990) Adding json-api meta test (@joaomdmoura) +- [#984](https://github.com/rails-api/active_model_serializers/pull/984) Add option "key" to serializer associations (@Rodrigora) +- [#982](https://github.com/rails-api/active_model_serializers/pull/982) Fix typo (@bf4) +- [#981](https://github.com/rails-api/active_model_serializers/pull/981) Remove unused PORO#to_param (@bf4) +- [#978](https://github.com/rails-api/active_model_serializers/pull/978) fix generators template bug (@regonn) +- [#975](https://github.com/rails-api/active_model_serializers/pull/975) Fixes virtual value not being used (@GriffinHeart) +- [#970](https://github.com/rails-api/active_model_serializers/pull/970) Fix transient tests failures (@Rodrigora) +- [#962](https://github.com/rails-api/active_model_serializers/pull/962) Rendering objects that doesn't have serializers (@bf4, @joaomdmoura, @JustinAiken) +- [#939](https://github.com/rails-api/active_model_serializers/pull/939) Use a more precise generated cache key (@aaronlerch) +- [#971](https://github.com/rails-api/active_model_serializers/pull/971) Restore has_one to generator (@bf4) +- [#965](https://github.com/rails-api/active_model_serializers/pull/965) options fedault valueserializable_hash and as_json (@bf4) +- [#959](https://github.com/rails-api/active_model_serializers/pull/959) TYPO on README.md (@kangkyu) + +### [v0.10.0.rc2 (2015-06-16)](https://github.com/rails-api/active_model_serializers/compare/v0.10.0.rc1...v0.10.0.rc2) +- [#958](https://github.com/rails-api/active_model_serializers/pull/958) Splitting json adapter into two (@joaomdmoura) + * adds FlattenJSON as default adapter +- [#953](https://github.com/rails-api/active_model_serializers/pull/953) use model name to determine the type (@lsylvester) + * uses model name to determine the type +- [#949](https://github.com/rails-api/active_model_serializers/pull/949) Don't pass serializer option to associated serializers (@bf4, @edwardloveall) +- [#902](https://github.com/rails-api/active_model_serializers/pull/902) Added serializer file digest to the cache_key (@cristianbica) +- [#948](https://github.com/rails-api/active_model_serializers/pull/948) AMS supports JSONAPI 1.0 instead of RC4 (@SeyZ) +- [#936](https://github.com/rails-api/active_model_serializers/pull/936) Include meta when using json adapter with custom root (@chrisbranson) +- [#942](https://github.com/rails-api/active_model_serializers/pull/942) Small code styling issue (@thiagofm) +- [#930](https://github.com/rails-api/active_model_serializers/pull/930) Reverting PR #909 (@joaomdmoura) +- [#924](https://github.com/rails-api/active_model_serializers/pull/924) Avoid unecessary calls to attribute methods when fragment caching (@navinpeiris) +- [#925](https://github.com/rails-api/active_model_serializers/pull/925) Updates JSON API Adapter to generate RC4 schema (@benedikt) + * adds JSON API support 1.0 +- [#918](https://github.com/rails-api/active_model_serializers/pull/918) Adding rescue_with_handler to clear state (@ryansch) +- [#909](https://github.com/rails-api/active_model_serializers/pull/909) Defining Json-API Adapter as Default (@joaomdmoura) + * remove root key option and split JSON adapter +- [#914](https://github.com/rails-api/active_model_serializers/pull/914) Prevent possible duplicated attributes in serializer (@groyoh) +- [#880](https://github.com/rails-api/active_model_serializers/pull/880) Inabling subclasses serializers to inherit attributes (@groyoh) +- [#913](https://github.com/rails-api/active_model_serializers/pull/913) Avoiding the serializer option when instantiating a new one for ArraySerializer Fixed #911 (@groyoh) +- [#897](https://github.com/rails-api/active_model_serializers/pull/897) Allow to define custom serializer for given class (@imanel) +- [#892](https://github.com/rails-api/active_model_serializers/pull/892) Fixed a bug that appeared when json adapter serialize a nil association (@groyoh) +- [#895](https://github.com/rails-api/active_model_serializers/pull/895) Adding a test to cover 'meta' and 'meta_key' attr_readers (@adomokos) +- [#894](https://github.com/rails-api/active_model_serializers/pull/894) Fixing typos in README.md (@adomokos) +- [#888](https://github.com/rails-api/active_model_serializers/pull/888) Changed duplicated test name in action controller test (@groyoh) +- [#890](https://github.com/rails-api/active_model_serializers/pull/890) Remove unused method `def_serializer` (@JustinAiken) +- [#887](https://github.com/rails-api/active_model_serializers/pull/887) Fixing tests on JRuby (@joaomdmoura) +- [#885](https://github.com/rails-api/active_model_serializers/pull/885) Updates rails versions for test and dev (@tonyta) + +### [v0.10.0.rc1 (2015-04-22)](https://github.com/rails-api/active_model_serializers/compare/86fc7d7227f3ce538fcb28c1e8c7069ce311f0e1...v0.10.0.rc1) +- [#810](https://github.com/rails-api/active_model_serializers/pull/810) Adding Fragment Cache to AMS (@joaomdmoura) + * adds fragment cache support +- [#868](https://github.com/rails-api/active_model_serializers/pull/868) Fixed a bug that appears when a nil association is included (@groyoh) +- [#861](https://github.com/rails-api/active_model_serializers/pull/861) README: Add emphasis to single-word difference (@machty) +- [#858](https://github.com/rails-api/active_model_serializers/pull/858) Included resource fixes (@mateomurphy) +- [#853](https://github.com/rails-api/active_model_serializers/pull/853) RC3 Updates for JSON API (@mateomurphy) +- [#852](https://github.com/rails-api/active_model_serializers/pull/852) Fix options merge order in `each_association` (@mateomurphy) +- [#850](https://github.com/rails-api/active_model_serializers/pull/850) Use association value for determining serializer used (@mateomurphy) +- [#843](https://github.com/rails-api/active_model_serializers/pull/843) Remove the mailing list from the README (@JoshSmith) +- [#842](https://github.com/rails-api/active_model_serializers/pull/842) Add notes on how you can help to contributing documentation (@JoshSmith) +- [#833](https://github.com/rails-api/active_model_serializers/pull/833) Cache serializers for class (@lsylvester) +- [#837](https://github.com/rails-api/active_model_serializers/pull/837) Store options in array serializers (@kurko) +- [#836](https://github.com/rails-api/active_model_serializers/pull/836) Makes passed in options accessible inside serializers (@kurko) +- [#773](https://github.com/rails-api/active_model_serializers/pull/773) Make json api adapter 'include' option accept an array (@sweatypitts) +- [#830](https://github.com/rails-api/active_model_serializers/pull/830) Add contributing readme (@JoshSmith) +- [#811](https://github.com/rails-api/active_model_serializers/pull/811) Reimplement serialization scope and scope_name (@mateomurphy) +- [#725](https://github.com/rails-api/active_model_serializers/pull/725) Support has_one to be compatible with 0.8.x (@ggordon) + * adds `has_one` attribute for backwards compatibility +- [#822](https://github.com/rails-api/active_model_serializers/pull/822) Replace has_one with attribute in template (@bf4) +- [#821](https://github.com/rails-api/active_model_serializers/pull/821) Fix explicit serializer for associations (@wjordan) +- [#798](https://github.com/rails-api/active_model_serializers/pull/798) Fix lost test `test_include_multiple_posts_and_linked` (@donbobka) +- [#807](https://github.com/rails-api/active_model_serializers/pull/807) Add Overriding attribute methods section to README. (@alexstophel) +- [#693](https://github.com/rails-api/active_model_serializers/pull/693) Cache Support at AMS 0.10.0 (@joaomdmoura) + * adds cache support to attributes and associations. +- [#792](https://github.com/rails-api/active_model_serializers/pull/792) Association overrides (@kurko) + * adds method to override association +- [#794](https://github.com/rails-api/active_model_serializers/pull/794) add to_param for correct URL generation (@carlesjove) + +### v0.10.0-pre + +- [Introduce Adapter](https://github.com/rails-api/active_model_serializers/commit/f00fe5595ddf741dc26127ed8fe81adad833ead5) +- Prefer `ActiveModel::Serializer` to `ActiveModelSerializers`: + - [Namespace](https://github.com/rails-api/active_model_serializers/commit/729a823868e8c7ac86c653fcc7100ee511e08cb6#diff-fe7aa2941c19a41ccea6e52940d84016). + - [README](https://github.com/rails-api/active_model_serializers/commit/4a2d9853ba7486acc1747752982aa5650e7fd6e9). diff --git a/CHANGELOG-prehistory.md b/CHANGELOG-prehistory.md new file mode 100644 index 00000000..a2758830 --- /dev/null +++ b/CHANGELOG-prehistory.md @@ -0,0 +1,15 @@ +## Prehistory + +- [Changing Serialization/Serializers namespace to `Serializable` (November 30, 2011)](https://github.com/rails/rails/commit/8896b4fdc8a543157cdf4dfc378607ebf6c10ab0) + - [Merge branch 'serializers'. This implements the ActiveModel::Serializer object. Includes code, tests, generators and guides. From José and Yehuda with love.](https://github.com/rails/rails/commit/fcacc6986ab60f1fb2e423a73bf47c7abd7b191d) + - But [was reverted](https://github.com/rails/rails/commit/5b2eb64ceb08cd005dc06b721935de5853971473). + '[Revert the serializers API as other alternatives are now also under discussion](https://github.com/rails/rails/commit/0a4035b12a6c59253cb60f9e3456513c6a6a9d33)'. +- [Proposed Implementation to Rails 3.2 by @wycats and @josevalim (November 25, 2011)](https://github.com/rails/rails/pull/3753) + - [Creation of `ActionController::Serialization`, initial serializer + support (September, 26 2011)](https://github.com/rails/rails/commit/8ff7693a8dc61f43fc4eaf72ed24d3b8699191fe). + - [Docs and CHANGELOG](https://github.com/rails/rails/commit/696d01f7f4a8ed787924a41cce6df836cd73c46f) + - [Deprecation of ActiveModel::Serialization to ActiveModel::Serializable](https://github.com/rails/rails/blob/696d01f7f4a8ed787924a41cce6df836cd73c46f/activemodel/lib/active_model/serialization.rb) +- [Creation of `ActiveModel::Serialization` from `ActiveModel::Serializer` in Rails (2009)](https://github.com/rails/rails/commit/c6bc8e662614be711f45a8d4b231d5f993b024a7#diff-d029b9768d8df0407a35804a468e3ae5) +- [Integration of `ActiveModel::Serializer` into `ActiveRecord::Serialization`](https://github.com/rails/rails/commit/783db25e0c640c1588732967a87d65c10fddc08e) +- [Creation of `ActiveModel::Serializer` in Rails (2009)](https://github.com/rails/rails/commit/d2b78b3594b9cc9870e6a6ebfeb2e56d00e6ddb8#diff-80d5beeced9bdc24ca2b04a201543bdd) +- [Creation of `ActiveModel::Serializers::JSON` in Rails (2009)](https://github.com/rails/rails/commit/fbdf706fffbfb17731a1f459203d242414ef5086) diff --git a/CHANGELOG.md b/CHANGELOG.md index c975e473..11070ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ -## 0.10.x +## Dev -### [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/master..dev) Breaking changes: @@ -10,641 +10,10 @@ Fixes: Misc: -### [v0.10.6 (2017-05-01)](https://github.com/rails-api/active_model_serializers/compare/v0.10.5...v0.10.6) +## [0.10.x](CHANGELOG-0-10.md) -Fixes: +## [0.09.x](CHANGELOG-0-09.md) -- [#1857](https://github.com/rails-api/active_model_serializers/pull/1857) JSON:API does not load belongs_to relation to get identifier id. (@bf4) -- [#2119](https://github.com/rails-api/active_model_serializers/pull/2119) JSON:API returns null resource object identifier when 'id' is null. (@bf4) -- [#2093](https://github.com/rails-api/active_model_serializers/pull/2093) undef problematic Serializer methods: display, select. (@bf4) +## [0.08.x](CHANGELOG-0-08.md) -Misc: - -- [#2104](https://github.com/rails-api/active_model_serializers/pull/2104) Documentation for serializers and rendering. (@cassidycodes) -- [#2081](https://github.com/rails-api/active_model_serializers/pull/2081) Documentation for `include` option in adapters. (@charlie-wasp) -- [#2120](https://github.com/rails-api/active_model_serializers/pull/2120) Documentation for association options: foreign_key, type, class_name, namespace. (@bf4) - -### [v0.10.5 (2017-03-07)](https://github.com/rails-api/active_model_serializers/compare/v0.10.4...v0.10.5) - -Breaking changes: - -Features: - -- [#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) -- [#2057](https://github.com/rails-api/active_model_serializers/pull/2057) - Update version constraint for jsonapi-renderer to `['>= 0.1.1.beta1', '< 0.2']` - (@jaredbeck) - -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) - -Misc: - -- [#2055](https://github.com/rails-api/active_model_serializers/pull/2055) - Replace deprecated dependency jsonapi with jsonapi-renderer. (@jaredbeck) -- [#2021](https://github.com/rails-api/active_model_serializers/pull/2021) Make test attributes explicit. Tests have Model#associations. (@bf4) -- [#1981](https://github.com/rails-api/active_model_serializers/pull/1981) Fix relationship link documentation. (@groyoh) -- [#2035](https://github.com/rails-api/active_model_serializers/pull/2035) Document how to disable the logger. (@MSathieu) -- [#2039](https://github.com/rails-api/active_model_serializers/pull/2039) Documentation fixes. (@biow0lf) - -### [v0.10.4 (2017-01-06)](https://github.com/rails-api/active_model_serializers/compare/v0.10.3...v0.10.4) - -Misc: - -- [#2005](https://github.com/rails-api/active_model_serializers/pull/2005) Update jsonapi runtime dependency to 0.1.1.beta6, support Ruby 2.4. (@kofronpi) -- [#1993](https://github.com/rails-api/active_model_serializers/pull/1993) Swap out KeyTransform for CaseTransform gem for the possibility of native extension use. (@NullVoxPopuli) - -### [v0.10.3 (2016-11-21)](https://github.com/rails-api/active_model_serializers/compare/v0.10.2...v0.10.3) - -Fixes: - -- [#1973](https://github.com/rails-api/active_model_serializers/pull/1973) Fix namespace lookup for collections and has_many relationships (@groyoh) -- [#1887](https://github.com/rails-api/active_model_serializers/pull/1887) Make the comment reflect what the function does (@johnnymo87) -- [#1890](https://github.com/rails-api/active_model_serializers/issues/1890) Ensure generator inherits from ApplicationSerializer when available (@richmolj) -- [#1922](https://github.com/rails-api/active_model_serializers/pull/1922) Make railtie an optional dependency in runtime (@ggpasqualino) -- [#1930](https://github.com/rails-api/active_model_serializers/pull/1930) Ensure valid jsonapi when relationship has no links or data (@richmolj) - -Features: - -- [#1757](https://github.com/rails-api/active_model_serializers/pull/1757) Make serializer lookup chain configurable. (@NullVoxPopuli) -- [#1968](https://github.com/rails-api/active_model_serializers/pull/1968) (@NullVoxPopuli) - - Add controller namespace to default controller lookup - - Provide a `namespace` render option - - document how set the namespace in the controller for implicit lookup. -- [#1791](https://github.com/rails-api/active_model_serializers/pull/1791) (@bf4, @youroff, @NullVoxPopuli) - - Added `jsonapi_namespace_separator` config option. -- [#1889](https://github.com/rails-api/active_model_serializers/pull/1889) Support key transformation for Attributes adapter (@iancanderson, @danbee) -- [#1917](https://github.com/rails-api/active_model_serializers/pull/1917) Add `jsonapi_pagination_links_enabled` configuration option (@richmolj) -- [#1797](https://github.com/rails-api/active_model_serializers/pull/1797) Only include 'relationships' when sideloading (@richmolj) - -Fixes: - -- [#1833](https://github.com/rails-api/active_model_serializers/pull/1833) Remove relationship links if they are null (@groyoh) -- [#1881](https://github.com/rails-api/active_model_serializers/pull/1881) ActiveModelSerializers::Model correctly works with string keys (@yevhene) - -Misc: -- [#1767](https://github.com/rails-api/active_model_serializers/pull/1767) Replace raising/rescuing `CollectionSerializer::NoSerializerError`, - throw/catch `:no_serializer`. (@bf4) -- [#1839](https://github.com/rails-api/active_model_serializers/pull/1839) `fields` tests demonstrating usage for both attributes and relationships. (@NullVoxPopuli) -- [#1812](https://github.com/rails-api/active_model_serializers/pull/1812) add a code of conduct (@corainchicago) - -- [#1878](https://github.com/rails-api/active_model_serializers/pull/1878) Cache key generation for serializers now uses `ActiveSupport::Cache.expand_cache_key` instead of `Array#join` by default and is also overridable. This change should be backward-compatible. (@markiz) - -- [#1799](https://github.com/rails-api/active_model_serializers/pull/1799) Add documentation for setting the adapter. (@cassidycodes) -- [#1909](https://github.com/rails-api/active_model_serializers/pull/1909) Add documentation for relationship links. (@vasilakisfil, @NullVoxPopuli) -- [#1959](https://github.com/rails-api/active_model_serializers/pull/1959) Add documentation for root. (@shunsuke227ono) -- [#1967](https://github.com/rails-api/active_model_serializers/pull/1967) Improve type method documentation. (@yukideluxe) - -### [v0.10.2 (2016-07-05)](https://github.com/rails-api/active_model_serializers/compare/v0.10.1...v0.10.2) - -Fixes: -- [#1814](https://github.com/rails-api/active_model_serializers/pull/1814) Ensuring read_multi works with fragment cache -- [#1848](https://github.com/rails-api/active_model_serializers/pull/1848) Redefine associations on inherited serializers. (@EhsanYousefi) - -Misc: -- [#1808](https://github.com/rails-api/active_model_serializers/pull/1808) Adds documentation for `fields` option. (@luizkowalski) - -### [v0.10.1 (2016-06-16)](https://github.com/rails-api/active_model_serializers/compare/v0.10.0...v0.10.1) - -Features: -- [#1668](https://github.com/rails-api/active_model_serializers/pull/1668) Exclude nil and empty links. (@sigmike) -- [#1426](https://github.com/rails-api/active_model_serializers/pull/1426) Add ActiveModelSerializers.config.default_includes (@empact) - -Fixes: -- [#1754](https://github.com/rails-api/active_model_serializers/pull/1754) Fixes #1759, Grape integration, improves serialization_context - missing error message on pagination. Document overriding CollectionSerializer#paginated?. (@bf4) - Moved serialization_context creation to Grape formatter, so resource serialization works without explicit calls to the `render` helper method. - Added Grape collection tests. (@onomated) -- [#1287](https://github.com/rails-api/active_model_serializers/pull/1287) Pass `fields` options from adapter to serializer. (@vasilakisfil) -- [#1710](https://github.com/rails-api/active_model_serializers/pull/1710) Prevent association loading when `include_data` option - is set to `false`. (@groyoh) -- [#1747](https://github.com/rails-api/active_model_serializers/pull/1747) Improve jsonapi mime type registration for Rails 5 (@remear) - -Misc: -- [#1734](https://github.com/rails-api/active_model_serializers/pull/1734) Adds documentation for conditional attribute (@lambda2) -- [#1685](https://github.com/rails-api/active_model_serializers/pull/1685) Replace `IncludeTree` with `IncludeDirective` from the jsonapi gem. - -### [v0.10.0 (2016-05-17)](https://github.com/rails-api/active_model_serializers/compare/4a2d9853ba7...v0.10.0) - -Breaking changes: -- [#1662](https://github.com/rails-api/active_model_serializers/pull/1662) Drop support for Rails 4.0 and Ruby 2.0.0. (@remear) - -Features: -- [#1677](https://github.com/rails-api/active_model_serializers/pull/1677) Add `assert_schema`, `assert_request_schema`, `assert_request_response_schema`. (@bf4) -- [#1697](https://github.com/rails-api/active_model_serializers/pull/1697) Include actual exception message with custom exceptions; - `Test::Schema` exceptions are now `Minitest::Assertion`s. (@bf4) -- [#1699](https://github.com/rails-api/active_model_serializers/pull/1699) String/Lambda support for conditional attributes/associations (@mtsmfm) -- [#1687](https://github.com/rails-api/active_model_serializers/pull/1687) Only calculate `_cache_digest` (in `cache_key`) when `skip_digest` is false. (@bf4) -- [#1647](https://github.com/rails-api/active_model_serializers/pull/1647) Restrict usage of `serializable_hash` options - to the ActiveModel::Serialization and ActiveModel::Serializers::JSON interface. (@bf4) - -Fixes: -- [#1700](https://github.com/rails-api/active_model_serializers/pull/1700) Support pagination link for Kaminari when no data is returned. (@iamnader) -- [#1726](https://github.com/rails-api/active_model_serializers/pull/1726) Adds polymorphic option to association definition which includes association type/nesting in serializer (@cgmckeever) - -Misc: -- [#1673](https://github.com/rails-api/active_model_serializers/pull/1673) Adds "How to" guide on using AMS with POROs (@DrSayre) -- [#1730](https://github.com/rails-api/active_model_serializers/pull/1730) Adds documentation for overriding default serializer based on conditions (@groyoh/@cgmckeever) - -### [v0.10.0.rc5 (2016-04-04)](https://github.com/rails-api/active_model_serializers/compare/v0.10.0.rc4...v0.10.0.rc5) - -Breaking changes: - -- [#1645](https://github.com/rails-api/active_model_serializers/pull/1645) Changed :dashed key transform to :dash. (@remear) -- [#1574](https://github.com/rails-api/active_model_serializers/pull/1574) Default key case for the JsonApi adapter changed to dashed. (@remear) - -Features: -- [#1645](https://github.com/rails-api/active_model_serializers/pull/1645) Transform keys referenced in values. (@remear) -- [#1650](https://github.com/rails-api/active_model_serializers/pull/1650) Fix serialization scope options `scope`, `scope_name` - take precedence over `serialization_scope` in the controller. - Fix tests that required tearing down dynamic methods. (@bf4) -- [#1644](https://github.com/rails-api/active_model_serializers/pull/1644) Include adapter name in cache key so - that the same serializer can be cached per adapter. (@bf4 via #1346 by @kevintyll) -- [#1642](https://github.com/rails-api/active_model_serializers/pull/1642) Prefer object.cache_key over the generated - cache key. (@bf4 via #1346 by @kevintyll) -- [#1637](https://github.com/rails-api/active_model_serializers/pull/1637) Make references to 'ActionController::Base.cache_store' explicit - in order to avoid issues when application controllers inherit from 'ActionController::API'. (@ncuesta) -- [#1633](https://github.com/rails-api/active_model_serializers/pull/1633) Yield 'serializer' to serializer association blocks. (@bf4) -- [#1616](https://github.com/rails-api/active_model_serializers/pull/1616) SerializableResource handles no serializer like controller. (@bf4) -- [#1618](https://github.com/rails-api/active_model_serializers/issues/1618) Get collection root key for - empty collection from explicit serializer option, when possible. (@bf4) -- [#1574](https://github.com/rails-api/active_model_serializers/pull/1574) Provide key translation. (@remear) -- [#1494](https://github.com/rails-api/active_model_serializers/pull/1494) Make serializers serializalbe - (using the Attributes adapter by default). (@bf4) -- [#1550](https://github.com/rails-api/active_model_serializers/pull/1550) Add - Rails url_helpers to `SerializationContext` for use in links. (@remear, @bf4) -- [#1004](https://github.com/rails-api/active_model_serializers/pull/1004) JSON API errors object implementation. - - Only implements `detail` and `source` as derived from `ActiveModel::Error` - - Provides checklist of remaining questions and remaining parts of the spec. -- [#1515](https://github.com/rails-api/active_model_serializers/pull/1515) Adds support for symbols to the - `ActiveModel::Serializer.type` method. (@groyoh) -- [#1504](https://github.com/rails-api/active_model_serializers/pull/1504) Adds the changes missing from #1454 - and add more tests for resource identifier and relationship objects. Fix association block with link - returning `data: nil`.(@groyoh) -- [#1372](https://github.com/rails-api/active_model_serializers/pull/1372) Support - cache_store.read_multi. (@LcpMarvel) -- [#1018](https://github.com/rails-api/active_model_serializers/pull/1018) Add more tests and docs for top-level links. (@leandrocp) -- [#1454](https://github.com/rails-api/active_model_serializers/pull/1454) Add support for - relationship-level links and meta attributes. (@beauby) -- [#1340](https://github.com/rails-api/active_model_serializers/pull/1340) Add support for resource-level meta. (@beauby) - -Fixes: -- [#1657](https://github.com/rails-api/active_model_serializers/pull/1657) Add missing missing require "active_support/json". (@andreaseger) -- [#1661](https://github.com/rails-api/active_model_serializers/pull/1661) Fixes `read_attribute_for_serialization` not - seeing methods defined in serialization superclass (#1653, #1658, #1660), introduced in #1650. (@bf4) -- [#1651](https://github.com/rails-api/active_model_serializers/pull/1651) Fix deserialization of nil relationships. (@NullVoxPopuli) -- [#1480](https://github.com/rails-api/active_model_serializers/pull/1480) Fix setting of cache_store from Rails configuration. (@bf4) - Fix unintentional mutating of value in memory cache store. (@groyoh) -- [#1622](https://github.com/rails-api/active_model_serializers/pull/1622) Fragment cache changed from per-record to per-serializer. - Now, two serializers that use the same model may be separately cached. (@lserman) -- [#1478](https://github.com/rails-api/active_model_serializers/pull/1478) Cache store will now be correctly set when serializers are - loaded *before* Rails initializes. (@bf4) -- [#1570](https://github.com/rails-api/active_model_serializers/pull/1570) Fixed pagination issue with last page size. (@bmorrall) -- [#1516](https://github.com/rails-api/active_model_serializers/pull/1516) No longer return a nil href when only - adding meta to a relationship link. (@groyoh) -- [#1458](https://github.com/rails-api/active_model_serializers/pull/1458) Preserve the serializer - type when fragment caching. (@bdmac) -- [#1477](https://github.com/rails-api/active_model_serializers/pull/1477) Fix `fragment_cached?` - method to check if caching. (@bdmac) -- [#1501](https://github.com/rails-api/active_model_serializers/pull/1501) Adds tests for SerializableResource::use_adapter?,doc typos (@domitian) -- [#1488](https://github.com/rails-api/active_model_serializers/pull/1488) Require ActiveSupport's string inflections (@nate00) - -Misc: -- [#1608](https://github.com/rails-api/active_model_serializers/pull/1608) Move SerializableResource to ActiveModelSerializers (@groyoh) -- [#1602](https://github.com/rails-api/active_model_serializers/pull/1602) Add output examples to Adapters docs (@remear) -- [#1557](https://github.com/rails-api/active_model_serializers/pull/1557) Update docs regarding overriding the root key (@Jwan622) -- [#1471](https://github.com/rails-api/active_model_serializers/pull/1471) [Cleanup] Serializer caching is its own concern. (@bf4) -- [#1482](https://github.com/rails-api/active_model_serializers/pull/1482) Document JSON API implementation defs and progress in class. (@bf4) -- [#1551](https://github.com/rails-api/active_model_serializers/pull/1551) Added codebeat badge (@korzonek) -- [#1527](https://github.com/rails-api/active_model_serializers/pull/1527) Refactor fragment cache class. (@groyoh) -- [#1560](https://github.com/rails-api/active_model_serializers/pull/1560) Update rubocop and address its warnings. (@bf4 @groyoh) -- [#1545](https://github.com/rails-api/active_model_serializers/pull/1545) Document how to pass arbitrary options to the - serializer (@CodedBeardedSignedTaylor) -- [#1496](https://github.com/rails-api/active_model_serializers/pull/1496) Run all branches against JRuby on CI (@nadavshatz) -- [#1559](https://github.com/rails-api/active_model_serializers/pull/1559) Add a deprecation DSL. (@bf4 @groyoh) -- [#1543](https://github.com/rails-api/active_model_serializers/pull/1543) Add the changes missing from #1535. (@groyoh) -- [#1535](https://github.com/rails-api/active_model_serializers/pull/1535) Move the adapter and adapter folder to - active_model_serializers folder and changes the module namespace. (@domitian @bf4) -- [#1497](https://github.com/rails-api/active_model_serializers/pull/1497) Add JRuby-9000 to appveyor.yml(@corainchicago) -- [#1420](https://github.com/rails-api/active_model_serializers/pull/1420) Adds tests and documentation for polymorphism(@marcgarreau) - - -### [v0.10.0.rc4 (2016-01-27)](https://github.com/rails-api/active_model_serializers/compare/v0.10.0.rc3...v0.10.0.rc4) -Breaking changes: - -- [#1360](https://github.com/rails-api/active_model_serializers/pull/1360) - [#1369](https://github.com/rails-api/active_model_serializers/pull/1369) Drop support for Ruby 1.9.3 (@karaAJC, @maurogeorge) -- [#1131](https://github.com/rails-api/active_model_serializers/pull/1131) Remove Serializer#root_name (@beauby) -- [#1138](https://github.com/rails-api/active_model_serializers/pull/1138) Introduce Adapter::Base (@bf4) - * Adapters now inherit Adapter::Base. 'Adapter' is now a module, no longer a class. - * using a class as a namespace that you also inherit from is complicated and circular at times i.e. - buggy (see https://github.com/rails-api/active_model_serializers/pull/1177) - * The class methods on Adapter aren't necessarily related to the instance methods, they're more - Adapter functions. - * named `Base` because it's a Rails-ism. - * It helps to isolate and highlight what the Adapter interface actually is. -- [#1418](https://github.com/rails-api/active_model_serializers/pull/1418) - serialized collections now use the root option as is; now, only the - root derived from the serializer or object is always pluralized. - -Features: - -- [#1406](https://github.com/rails-api/active_model_serializers/pull/1406) Allow for custom dynamic values in JSON API links (@beauby) -- [#1270](https://github.com/rails-api/active_model_serializers/pull/1270) Adds `assert_response_schema` test helper (@maurogeorge) -- [#1099](https://github.com/rails-api/active_model_serializers/pull/1099) Adds `assert_serializer` test helper (@maurogeorge) -- [#1403](https://github.com/rails-api/active_model_serializers/pull/1403) Add support for if/unless on attributes/associations (@beauby) -- [#1248](https://github.com/rails-api/active_model_serializers/pull/1248) Experimental: Add support for JSON API deserialization (@beauby) -- [#1378](https://github.com/rails-api/active_model_serializers/pull/1378) Change association blocks - to be evaluated in *serializer* scope, rather than *association* scope. (@bf4) - * Syntax changes from e.g. - `has_many :titles do customers.pluck(:title) end` (in #1356) to - `has_many :titles do object.customers.pluck(:title) end` -- [#1356](https://github.com/rails-api/active_model_serializers/pull/1356) Add inline syntax for - attributes and associations (@bf4 @beauby @noahsilas) - * Allows defining attributes so that they don't conflict with existing methods. e.g. `attribute - :title do 'Mr. Topum Hat' end` - * Allows defining associations so that they don't conflict with existing methods. e.g. `has_many - :titles do customers.pluck(:title) end` - * Allows dynamic associations, as compared to compare to using - [`virtual_value`](https://github.com/rails-api/active_model_serializers/pull/1356#discussion_r47146466). - e.g. `has_many :reviews, virtual_value: [{ id: 1 }, { id: 2 }]` - * Removes dynamically defined methods on the serializer -- [#1336](https://github.com/rails-api/active_model_serializers/pull/1336) Added support for Grape >= 0.13, < 1.0 (@johnhamelink) -- [#1322](https://github.com/rails-api/active_model_serializers/pull/1322) Instrumenting rendering of resources (@bf4, @maurogeorge) -- [#1291](https://github.com/rails-api/active_model_serializers/pull/1291) Add logging (@maurogeorge) -- [#1272](https://github.com/rails-api/active_model_serializers/pull/1272) Add PORO serializable base class: ActiveModelSerializers::Model (@bf4) -- [#1255](https://github.com/rails-api/active_model_serializers/pull/1255) Make more class attributes inheritable (@bf4) -- [#1249](https://github.com/rails-api/active_model_serializers/pull/1249) Inheritance of serializer inheriting the cache configuration(@Rodrigora) -- [#1247](https://github.com/rails-api/active_model_serializers/pull/1247) Add support for toplevel JSON API links (@beauby) -- [#1246](https://github.com/rails-api/active_model_serializers/pull/1246) Add support for resource-level JSON API links (@beauby) -- [#1225](https://github.com/rails-api/active_model_serializers/pull/1225) Better serializer lookup, use nested serializer when it exists (@beauby) -- [#1213](https://github.com/rails-api/active_model_serializers/pull/1213) `type` directive for serializer to control type field with json-api adapter (@youroff) -- [#1172](https://github.com/rails-api/active_model_serializers/pull/1172) Better serializer registration, get more than just the first module (@bf4) -- [#1158](https://github.com/rails-api/active_model_serializers/pull/1158) Add support for wildcards in `include` option (@beauby) -- [#1127](https://github.com/rails-api/active_model_serializers/pull/1127) Add support for nested - associations for JSON and Attributes adapters via the `include` option (@NullVoxPopuli, @beauby). -- [#1050](https://github.com/rails-api/active_model_serializers/pull/1050) Add support for toplevel jsonapi member (@beauby, @bf4) -- [#1251](https://github.com/rails-api/active_model_serializers/pull/1251) Rename ArraySerializer to - CollectionSerializer for clarity, add ActiveModelSerializers.config.collection_serializer (@bf4) -- [#1295](https://github.com/rails-api/active_model_serializers/pull/1295) Add config `serializer_lookup_enabled` that, - when disabled, requires serializers to explicitly specified. (@trek) - -Fixes: - -- [#1352](https://github.com/rails-api/active_model_serializers/pull/1352) Fix generators; Isolate Rails-specifc code in Railties (@dgynn, @bf4) -- [#1384](https://github.com/rails-api/active_model_serializers/pull/1384)Fix database state leaking across tests (@bf4) -- [#1297](https://github.com/rails-api/active_model_serializers/pull/1297) Fix `fields` option to restrict relationships as well (@beauby) -- [#1239](https://github.com/rails-api/active_model_serializers/pull/1239) Fix duplicates in JSON API compound documents (@beauby) -- [#1214](https://github.com/rails-api/active_model_serializers/pull/1214) retrieve the key from the reflection options when building associations (@NullVoxPopuli, @hut8) -- [#1358](https://github.com/rails-api/active_model_serializers/pull/1358) Handle serializer file paths with spaces (@rwstauner, @bf4) -- [#1195](https://github.com/rails-api/active_model_serializers/pull/1195) Fix id override (@beauby) -- [#1185](https://github.com/rails-api/active_model_serializers/pull/1185) Fix options passing in Json and Attributes adapters (@beauby) - -Misc: - -- [#1383](https://github.com/rails-api/active_model_serializers/pull/1383) Simplify reflections handling (@beauby) -- [#1370](https://github.com/rails-api/active_model_serializers/pull/1370) Simplify attributes handling via a mixin (@beauby) -- [#1301](https://github.com/rails-api/active_model_serializers/pull/1301) Mapping JSON API spec / schema to AMS (@bf4) -- [#1271](https://github.com/rails-api/active_model_serializers/pull/1271) Handle no serializer source file to digest (@bf4) -- [#1260](https://github.com/rails-api/active_model_serializers/pull/1260) Serialization and Cache Documentation (@bf4) -- [#1259](https://github.com/rails-api/active_model_serializers/pull/1259) Add more info to CONTRIBUTING (@bf4) -- [#1233](https://github.com/rails-api/active_model_serializers/pull/1233) Top-level meta and meta_key options no longer handled at serializer level (@beauby) -- [#1232](https://github.com/rails-api/active_model_serializers/pull/1232) fields option no longer handled at serializer level (@beauby) -- [#1220](https://github.com/rails-api/active_model_serializers/pull/1220) Remove empty rubocop.rake (@maurogeorge) -- [#1178](https://github.com/rails-api/active_model_serializers/pull/1178) env CAPTURE_STDERR=false lets devs see hard failures (@bf4) -- [#1177](https://github.com/rails-api/active_model_serializers/pull/1177) Remove Adapter autoloads in favor of require (@bf4) -- [#1117](https://github.com/rails-api/active_model_serializers/pull/1117) FlattenJson adapter no longer inherits Json adapter, renamed to Attributes (@bf4) -- [#1171](https://github.com/rails-api/active_model_serializers/pull/1171) add require statements to top of file (@shicholas) -- [#1167](https://github.com/rails-api/active_model_serializers/pull/1167) Delegate Serializer.attributes to Serializer.attribute (@bf4) -- [#1174](https://github.com/rails-api/active_model_serializers/pull/1174) Consistently refer to the 'JSON API' and the 'JsonApi' adapter (@bf4) -- [#1173](https://github.com/rails-api/active_model_serializers/pull/1173) Comment private accessor warnings (@bf4) -- [#1166](https://github.com/rails-api/active_model_serializers/pull/1166) Prefer methods over instance variables (@bf4) -- [#1168](https://github.com/rails-api/active_model_serializers/pull/1168) Fix appveyor failure cache not being expired (@bf4) -- [#1161](https://github.com/rails-api/active_model_serializers/pull/1161) Remove duplicate test helper (@bf4) -- [#1360](https://github.com/rails-api/active_model_serializers/pull/1360) Update CI to test 2.2.2 -> 2.2.3 (@karaAJC) -- [#1371](https://github.com/rails-api/active_model_serializers/pull/1371) Refactor, update, create documentation (@bf4) - -### [v0.10.0.rc3 (2015-09-16)](https://github.com/rails-api/active_model_serializers/compare/v0.10.0.rc2...v0.10.0.rc3) -- [#1129](https://github.com/rails-api/active_model_serializers/pull/1129) Remove SerializableResource.serialize in favor of `.new` (@bf4) -- [#1155](https://github.com/rails-api/active_model_serializers/pull/1155) Outside controller use tutorial (@CodedBeardedSignedTaylor) -- [#1154](https://github.com/rails-api/active_model_serializers/pull/1154) Rubocop fixes for issues introduced by #1089 (@NullVoxPopuli) -- [#1089](https://github.com/rails-api/active_model_serializers/pull/1089) Add ActiveModelSerializers.logger with default null device (@bf4) -- [#1109](https://github.com/rails-api/active_model_serializers/pull/1109) Make better use of Minitest's lifecycle (@bf4) -- [#1144](https://github.com/rails-api/active_model_serializers/pull/1144) Fix Markdown to adapters documentation (@bacarini) -- [#1121](https://github.com/rails-api/active_model_serializers/pull/1121) Refactor `add_links` in JSONAPI adapter. (@beauby) -- [#1150](https://github.com/rails-api/active_model_serializers/pull/1150) Remove legacy method accidentally reintroduced in #1017 (@beauby) -- [#1149](https://github.com/rails-api/active_model_serializers/pull/1149) Update README with nested included association example. (@mattmueller) -- [#1110](https://github.com/rails-api/active_model_serializers/pull/1110) Add lint tests for AR models (@beauby) -- [#1131](https://github.com/rails-api/active_model_serializers/pull/1131) Extended format for JSONAPI `include` option (@beauby) - * adds extended format for `include` option to JsonApi adapter -- [#1142](https://github.com/rails-api/active_model_serializers/pull/1142) Updating wording on cache expiry in README (@leighhalliday) -- [#1140](https://github.com/rails-api/active_model_serializers/pull/1140) Fix typo in fieldset exception (@lautis) -- [#1132](https://github.com/rails-api/active_model_serializers/pull/1132) Get rid of unnecessary instance variables, and implied dependencies. (@beauby) -- [#1139](https://github.com/rails-api/active_model_serializers/pull/1139) Documentation for serializing resources without render (@PericlesTheo) -- [#1017](https://github.com/rails-api/active_model_serializers/pull/1017) Make Adapters registerable so they are not namespace-constrained (@bf4) -- [#1120](https://github.com/rails-api/active_model_serializers/pull/1120) Add windows platform to loading sqlite3 (@Eric-Guo) -- [#1123](https://github.com/rails-api/active_model_serializers/pull/1123) Remove url options (@bacarini) -- [#1093](https://github.com/rails-api/active_model_serializers/pull/1093) Factor `with_adapter` + force cache clear before each test. (@beauby) -- [#1095](https://github.com/rails-api/active_model_serializers/pull/1095) Add documentation about configuration options. (@beauby) -- [#1069](https://github.com/rails-api/active_model_serializers/pull/1069) Add test coverage; account for no artifacts on CI (@bf4) -- [#1103](https://github.com/rails-api/active_model_serializers/pull/1103) Move `id` and `json_api_type` methods from `Serializer` to `JsonApi`. (@beauby) -- [#1106](https://github.com/rails-api/active_model_serializers/pull/1106) Add Style enforcer (via Rubocop) (@bf4) -- [#1079](https://github.com/rails-api/active_model_serializers/pull/1079) Add ArraySerializer#object like Serializer (@bf4) -- [#1096](https://github.com/rails-api/active_model_serializers/pull/1096) Fix definition of serializer attributes with multiple calls to `attri… (@beauby) -- [#1105](https://github.com/rails-api/active_model_serializers/pull/1105) Add ActiveRecord-backed fixtures. (@beauby) -- [#1108](https://github.com/rails-api/active_model_serializers/pull/1108) Better lint (@bf4) -- [#1102](https://github.com/rails-api/active_model_serializers/pull/1102) Remove remains of `embed` option. (@beauby) -- [#1090](https://github.com/rails-api/active_model_serializers/pull/1090) Clarify AMS dependencies (@bf4) -- [#1081](https://github.com/rails-api/active_model_serializers/pull/1081) Add configuration option to set resource type to singular/plural (@beauby) -- [#1067](https://github.com/rails-api/active_model_serializers/pull/1067) Fix warnings (@bf4) -- [#1066](https://github.com/rails-api/active_model_serializers/pull/1066) Adding appveyor to the project (@joaomdmoura, @Eric-Guo, @bf4) -- [#1071](https://github.com/rails-api/active_model_serializers/pull/1071) Make testing suite running and pass in Windows (@Eric-Guo, @bf4) -- [#1041](https://github.com/rails-api/active_model_serializers/pull/1041) Adding pagination links (@bacarini) - * adds support for `pagination links` at top level of JsonApi adapter -- [#1063](https://github.com/rails-api/active_model_serializers/pull/1063) Lead by example: lint PORO model (@bf4) -- [#1](https://github.com/rails-api/active_model_serializers/pull/1) Test caller line parsing and digesting (@bf4) -- [#1048](https://github.com/rails-api/active_model_serializers/pull/1048) Let FlattenJson adapter decide it doesn't include meta (@bf4) -- [#1060](https://github.com/rails-api/active_model_serializers/pull/1060) Update fragment cache to support namespaced objects (@aaronlerch) -- [#1052](https://github.com/rails-api/active_model_serializers/pull/1052) Use underscored json_root when serializing a collection (@whatthewhat) -- [#1051](https://github.com/rails-api/active_model_serializers/pull/1051) Fix some invalid JSON in docs (@tjschuck) -- [#1049](https://github.com/rails-api/active_model_serializers/pull/1049) Fix incorrect s/options = {}/options ||= {} (@bf4) -- [#1037](https://github.com/rails-api/active_model_serializers/pull/1037) allow for type attribute (@lanej) -- [#1034](https://github.com/rails-api/active_model_serializers/pull/1034) allow id attribute to be overriden (@lanej) -- [#1035](https://github.com/rails-api/active_model_serializers/pull/1035) Fixed Comments highlight (@artLopez) -- [#1031](https://github.com/rails-api/active_model_serializers/pull/1031) Disallow to define multiple associations at once (@bolshakov) -- [#1032](https://github.com/rails-api/active_model_serializers/pull/1032) Wrap railtie requirement with rescue (@elliotlarson) -- [#1026](https://github.com/rails-api/active_model_serializers/pull/1026) Bump Version Number to 0.10.0.rc2 (@jfelchner) -- [#985](https://github.com/rails-api/active_model_serializers/pull/985) Associations implementation refactoring (@bolshakov) -- [#954](https://github.com/rails-api/active_model_serializers/pull/954) Encapsulate serialization in ActiveModel::SerializableResource (@bf4) -- [#972](https://github.com/rails-api/active_model_serializers/pull/972) Capture app warnings on test run (@bf4) -- [#1019](https://github.com/rails-api/active_model_serializers/pull/1019) Improve README.md (@baojjeu) -- [#998](https://github.com/rails-api/active_model_serializers/pull/998) Changing root to model class name (@joaomdmoura) -- [#1006](https://github.com/rails-api/active_model_serializers/pull/1006) Fix adapter inflection bug for api -> API (@bf4) -- [#1016](https://github.com/rails-api/active_model_serializers/pull/1016) require rails/railtie before subclassing Rails::Railtie (@bf4) -- [#1013](https://github.com/rails-api/active_model_serializers/pull/1013) Root option with empty array support (@vyrak, @mareczek) -- [#994](https://github.com/rails-api/active_model_serializers/pull/994) Starting Docs structure (@joaomdmoura) -- [#1007](https://github.com/rails-api/active_model_serializers/pull/1007) Bug fix for ArraySerializer json_key (@jiajiawang) -- [#1003](https://github.com/rails-api/active_model_serializers/pull/1003) Fix transient test failures (@Rodrigora) -- [#996](https://github.com/rails-api/active_model_serializers/pull/996) Add linter for serializable resource (@bf4) -- [#990](https://github.com/rails-api/active_model_serializers/pull/990) Adding json-api meta test (@joaomdmoura) -- [#984](https://github.com/rails-api/active_model_serializers/pull/984) Add option "key" to serializer associations (@Rodrigora) -- [#982](https://github.com/rails-api/active_model_serializers/pull/982) Fix typo (@bf4) -- [#981](https://github.com/rails-api/active_model_serializers/pull/981) Remove unused PORO#to_param (@bf4) -- [#978](https://github.com/rails-api/active_model_serializers/pull/978) fix generators template bug (@regonn) -- [#975](https://github.com/rails-api/active_model_serializers/pull/975) Fixes virtual value not being used (@GriffinHeart) -- [#970](https://github.com/rails-api/active_model_serializers/pull/970) Fix transient tests failures (@Rodrigora) -- [#962](https://github.com/rails-api/active_model_serializers/pull/962) Rendering objects that doesn't have serializers (@bf4, @joaomdmoura, @JustinAiken) -- [#939](https://github.com/rails-api/active_model_serializers/pull/939) Use a more precise generated cache key (@aaronlerch) -- [#971](https://github.com/rails-api/active_model_serializers/pull/971) Restore has_one to generator (@bf4) -- [#965](https://github.com/rails-api/active_model_serializers/pull/965) options fedault valueserializable_hash and as_json (@bf4) -- [#959](https://github.com/rails-api/active_model_serializers/pull/959) TYPO on README.md (@kangkyu) - -### [v0.10.0.rc2 (2015-06-16)](https://github.com/rails-api/active_model_serializers/compare/v0.10.0.rc1...v0.10.0.rc2) -- [#958](https://github.com/rails-api/active_model_serializers/pull/958) Splitting json adapter into two (@joaomdmoura) - * adds FlattenJSON as default adapter -- [#953](https://github.com/rails-api/active_model_serializers/pull/953) use model name to determine the type (@lsylvester) - * uses model name to determine the type -- [#949](https://github.com/rails-api/active_model_serializers/pull/949) Don't pass serializer option to associated serializers (@bf4, @edwardloveall) -- [#902](https://github.com/rails-api/active_model_serializers/pull/902) Added serializer file digest to the cache_key (@cristianbica) -- [#948](https://github.com/rails-api/active_model_serializers/pull/948) AMS supports JSONAPI 1.0 instead of RC4 (@SeyZ) -- [#936](https://github.com/rails-api/active_model_serializers/pull/936) Include meta when using json adapter with custom root (@chrisbranson) -- [#942](https://github.com/rails-api/active_model_serializers/pull/942) Small code styling issue (@thiagofm) -- [#930](https://github.com/rails-api/active_model_serializers/pull/930) Reverting PR #909 (@joaomdmoura) -- [#924](https://github.com/rails-api/active_model_serializers/pull/924) Avoid unecessary calls to attribute methods when fragment caching (@navinpeiris) -- [#925](https://github.com/rails-api/active_model_serializers/pull/925) Updates JSON API Adapter to generate RC4 schema (@benedikt) - * adds JSON API support 1.0 -- [#918](https://github.com/rails-api/active_model_serializers/pull/918) Adding rescue_with_handler to clear state (@ryansch) -- [#909](https://github.com/rails-api/active_model_serializers/pull/909) Defining Json-API Adapter as Default (@joaomdmoura) - * remove root key option and split JSON adapter -- [#914](https://github.com/rails-api/active_model_serializers/pull/914) Prevent possible duplicated attributes in serializer (@groyoh) -- [#880](https://github.com/rails-api/active_model_serializers/pull/880) Inabling subclasses serializers to inherit attributes (@groyoh) -- [#913](https://github.com/rails-api/active_model_serializers/pull/913) Avoiding the serializer option when instantiating a new one for ArraySerializer Fixed #911 (@groyoh) -- [#897](https://github.com/rails-api/active_model_serializers/pull/897) Allow to define custom serializer for given class (@imanel) -- [#892](https://github.com/rails-api/active_model_serializers/pull/892) Fixed a bug that appeared when json adapter serialize a nil association (@groyoh) -- [#895](https://github.com/rails-api/active_model_serializers/pull/895) Adding a test to cover 'meta' and 'meta_key' attr_readers (@adomokos) -- [#894](https://github.com/rails-api/active_model_serializers/pull/894) Fixing typos in README.md (@adomokos) -- [#888](https://github.com/rails-api/active_model_serializers/pull/888) Changed duplicated test name in action controller test (@groyoh) -- [#890](https://github.com/rails-api/active_model_serializers/pull/890) Remove unused method `def_serializer` (@JustinAiken) -- [#887](https://github.com/rails-api/active_model_serializers/pull/887) Fixing tests on JRuby (@joaomdmoura) -- [#885](https://github.com/rails-api/active_model_serializers/pull/885) Updates rails versions for test and dev (@tonyta) - -### [v0.10.0.rc1 (2015-04-22)](https://github.com/rails-api/active_model_serializers/compare/86fc7d7227f3ce538fcb28c1e8c7069ce311f0e1...v0.10.0.rc1) -- [#810](https://github.com/rails-api/active_model_serializers/pull/810) Adding Fragment Cache to AMS (@joaomdmoura) - * adds fragment cache support -- [#868](https://github.com/rails-api/active_model_serializers/pull/868) Fixed a bug that appears when a nil association is included (@groyoh) -- [#861](https://github.com/rails-api/active_model_serializers/pull/861) README: Add emphasis to single-word difference (@machty) -- [#858](https://github.com/rails-api/active_model_serializers/pull/858) Included resource fixes (@mateomurphy) -- [#853](https://github.com/rails-api/active_model_serializers/pull/853) RC3 Updates for JSON API (@mateomurphy) -- [#852](https://github.com/rails-api/active_model_serializers/pull/852) Fix options merge order in `each_association` (@mateomurphy) -- [#850](https://github.com/rails-api/active_model_serializers/pull/850) Use association value for determining serializer used (@mateomurphy) -- [#843](https://github.com/rails-api/active_model_serializers/pull/843) Remove the mailing list from the README (@JoshSmith) -- [#842](https://github.com/rails-api/active_model_serializers/pull/842) Add notes on how you can help to contributing documentation (@JoshSmith) -- [#833](https://github.com/rails-api/active_model_serializers/pull/833) Cache serializers for class (@lsylvester) -- [#837](https://github.com/rails-api/active_model_serializers/pull/837) Store options in array serializers (@kurko) -- [#836](https://github.com/rails-api/active_model_serializers/pull/836) Makes passed in options accessible inside serializers (@kurko) -- [#773](https://github.com/rails-api/active_model_serializers/pull/773) Make json api adapter 'include' option accept an array (@sweatypitts) -- [#830](https://github.com/rails-api/active_model_serializers/pull/830) Add contributing readme (@JoshSmith) -- [#811](https://github.com/rails-api/active_model_serializers/pull/811) Reimplement serialization scope and scope_name (@mateomurphy) -- [#725](https://github.com/rails-api/active_model_serializers/pull/725) Support has_one to be compatible with 0.8.x (@ggordon) - * adds `has_one` attribute for backwards compatibility -- [#822](https://github.com/rails-api/active_model_serializers/pull/822) Replace has_one with attribute in template (@bf4) -- [#821](https://github.com/rails-api/active_model_serializers/pull/821) Fix explicit serializer for associations (@wjordan) -- [#798](https://github.com/rails-api/active_model_serializers/pull/798) Fix lost test `test_include_multiple_posts_and_linked` (@donbobka) -- [#807](https://github.com/rails-api/active_model_serializers/pull/807) Add Overriding attribute methods section to README. (@alexstophel) -- [#693](https://github.com/rails-api/active_model_serializers/pull/693) Cache Support at AMS 0.10.0 (@joaomdmoura) - * adds cache support to attributes and associations. -- [#792](https://github.com/rails-api/active_model_serializers/pull/792) Association overrides (@kurko) - * adds method to override association -- [#794](https://github.com/rails-api/active_model_serializers/pull/794) add to_param for correct URL generation (@carlesjove) - -### v0.10.0-pre - -- [Introduce Adapter](https://github.com/rails-api/active_model_serializers/commit/f00fe5595ddf741dc26127ed8fe81adad833ead5) -- Prefer `ActiveModel::Serializer` to `ActiveModelSerializers`: - - [Namespace](https://github.com/rails-api/active_model_serializers/commit/729a823868e8c7ac86c653fcc7100ee511e08cb6#diff-fe7aa2941c19a41ccea6e52940d84016). - - [README](https://github.com/rails-api/active_model_serializers/commit/4a2d9853ba7486acc1747752982aa5650e7fd6e9). - -## 0.09.x - -### v0.9.3 (2015/01/21 20:29 +00:00) - -Features: -- [#774](https://github.com/rails-api/active_model_serializers/pull/774) Fix nested include attributes (@nhocki) -- [#771](https://github.com/rails-api/active_model_serializers/pull/771) Make linked resource type names consistent with root names (@sweatypitts) -- [#696](https://github.com/rails-api/active_model_serializers/pull/696) Explicitly set serializer for associations (@ggordon) -- [#700](https://github.com/rails-api/active_model_serializers/pull/700) sparse fieldsets (@arenoir) -- [#768](https://github.com/rails-api/active_model_serializers/pull/768) Adds support for `meta` and `meta_key` attribute (@kurko) - -### v0.9.1 (2014/12/04 11:54 +00:00) -- [#707](https://github.com/rails-api/active_model_serializers/pull/707) A Friendly Note on Which AMS Version to Use (@jherdman) -- [#730](https://github.com/rails-api/active_model_serializers/pull/730) Fixes nested has_many links in JSONAPI (@kurko) -- [#718](https://github.com/rails-api/active_model_serializers/pull/718) Allow overriding the adapter with render option (@ggordon) -- [#720](https://github.com/rails-api/active_model_serializers/pull/720) Rename attribute with :key (0.8.x compatibility) (@ggordon) -- [#728](https://github.com/rails-api/active_model_serializers/pull/728) Use type as key for linked resources (@kurko) -- [#729](https://github.com/rails-api/active_model_serializers/pull/729) Use the new beta build env on Travis (@joshk) -- [#703](https://github.com/rails-api/active_model_serializers/pull/703) Support serializer and each_serializer options in renderer (@ggordon, @mieko) -- [#727](https://github.com/rails-api/active_model_serializers/pull/727) Includes links inside of linked resources (@kurko) -- [#726](https://github.com/rails-api/active_model_serializers/pull/726) Bugfix: include nested has_many associations (@kurko) -- [#722](https://github.com/rails-api/active_model_serializers/pull/722) Fix infinite recursion (@ggordon) -- [#1](https://github.com/rails-api/active_model_serializers/pull/1) Allow for the implicit use of ArraySerializer when :each_serializer is specified (@mieko) -- [#692](https://github.com/rails-api/active_model_serializers/pull/692) Include 'linked' member for json-api collections (@ggordon) -- [#714](https://github.com/rails-api/active_model_serializers/pull/714) Define as_json instead of to_json (@guilleiguaran) -- [#710](https://github.com/rails-api/active_model_serializers/pull/710) JSON-API: Don't include linked section if associations are empty (@guilleiguaran) -- [#711](https://github.com/rails-api/active_model_serializers/pull/711) Fixes rbx gems bundling on TravisCI (@kurko) -- [#709](https://github.com/rails-api/active_model_serializers/pull/709) Add type key when association name is different than object type (@guilleiguaran) -- [#708](https://github.com/rails-api/active_model_serializers/pull/708) Handle correctly null associations (@guilleiguaran) -- [#691](https://github.com/rails-api/active_model_serializers/pull/691) Fix embed option for associations (@jacob-s-son) -- [#689](https://github.com/rails-api/active_model_serializers/pull/689) Fix support for custom root in JSON-API adapter (@guilleiguaran) -- [#685](https://github.com/rails-api/active_model_serializers/pull/685) Serialize ids as strings in JSON-API adapter (@guilleiguaran) -- [#684](https://github.com/rails-api/active_model_serializers/pull/684) Refactor adapters to implement support for array serialization (@guilleiguaran) -- [#682](https://github.com/rails-api/active_model_serializers/pull/682) Include root by default in JSON-API serializers (@guilleiguaran) -- [#625](https://github.com/rails-api/active_model_serializers/pull/625) Add DSL for urls (@JordanFaust) -- [#677](https://github.com/rails-api/active_model_serializers/pull/677) Add support for embed: :ids option for in associations (@guilleiguaran) -- [#681](https://github.com/rails-api/active_model_serializers/pull/681) Check superclasses for Serializers (@quainjn) -- [#680](https://github.com/rails-api/active_model_serializers/pull/680) Add support for root keys (@NullVoxPopuli) -- [#675](https://github.com/rails-api/active_model_serializers/pull/675) Support Rails 4.2.0 (@tricknotes) -- [#667](https://github.com/rails-api/active_model_serializers/pull/667) Require only activemodel instead of full rails (@guilleiguaran) -- [#653](https://github.com/rails-api/active_model_serializers/pull/653) Add "_test" suffix to JsonApi::HasManyTest filename. (@alexgenco) -- [#631](https://github.com/rails-api/active_model_serializers/pull/631) Update build badge URL (@craiglittle) - -### 0.9.0.alpha1 - January 7, 2014 - -### 0.9.0.pre - -* The following methods were removed - - Model#active\_model\_serializer - - Serializer#include! - - Serializer#include? - - Serializer#attr\_disabled= - - Serializer#cache - - Serializer#perform\_caching - - Serializer#schema (needs more discussion) - - Serializer#attribute - - Serializer#include\_#{name}? (filter method added) - - Serializer#attributes (took a hash) - -* The following things were added - - Serializer#filter method - - CONFIG object - -* Remove support for ruby 1.8 versions. - -* Require rails >= 3.2. - -* Serializers for associations are being looked up in a parent serializer's namespace first. Same with controllers' namespaces. - -* Added a "prefix" option in case you want to use a different version of serializer. - -* Serializers default namespace can be set in `default_serializer_options` and inherited by associations. - -* [Beginning of rewrite: c65d387705ec534db171712671ba7fcda4f49f68](https://github.com/rails-api/active_model_serializers/commit/c65d387705ec534db171712671ba7fcda4f49f68) - -## 0.08.x - -### v0.8.3 (2014/12/10 14:45 +00:00) -- [#753](https://github.com/rails-api/active_model_serializers/pull/753) Test against Ruby 2.2 on Travis CI (@tricknotes) -- [#745](https://github.com/rails-api/active_model_serializers/pull/745) Missing a word (@jockee) - -### v0.8.2 (2014/09/01 21:00 +00:00) -- [#612](https://github.com/rails-api/active_model_serializers/pull/612) Feature/adapter (@bolshakov) - * adds adapters pattern -- [#615](https://github.com/rails-api/active_model_serializers/pull/615) Rails does not support const_defined? in development mode (@tpitale) -- [#613](https://github.com/rails-api/active_model_serializers/pull/613) README: typo fix on attributes (@spk) -- [#614](https://github.com/rails-api/active_model_serializers/pull/614) Fix rails 4.0.x build. (@arthurnn) -- [#610](https://github.com/rails-api/active_model_serializers/pull/610) ArraySerializer (@bolshakov) -- [#607](https://github.com/rails-api/active_model_serializers/pull/607) ruby syntax highlights (@zigomir) -- [#602](https://github.com/rails-api/active_model_serializers/pull/602) Add DSL for associations (@JordanFaust) - -### 0.8.1 (May 6, 2013) - -* Fix bug whereby a serializer using 'options' would blow up. - -### 0.8.0 (May 5, 2013) - -* Attributes can now have optional types. - -* A new DefaultSerializer ensures that POROs behave the same way as ActiveModels. - -* If you wish to override ActiveRecord::Base#to_Json, you can now require - 'active_record/serializer_override'. We don't recommend you do this, but - many users do, so we've left it optional. - -* Fixed a bug where ActionController wouldn't always have MimeResponds. - -* An optinal caching feature allows you to cache JSON & hashes that AMS uses. - Adding 'cached true' to your Serializers will turn on this cache. - -* URL helpers used inside of Engines now work properly. - -* Serializers now can filter attributes with `only` and `except`: - - ``` - UserSerializer.new(user, only: [:first_name, :last_name]) - UserSerializer.new(user, except: :first_name) - ``` - -* Basic Mongoid support. We now include our mixins in the right place. - -* On Ruby 1.8, we now generate an `id` method that properly serializes `id` - columns. See issue #127 for more. - -* Add an alias for `scope` method to be the name of the context. By default - this is `current_user`. The name is automatically set when using - `serialization_scope` in the controller. - -* Pass through serialization options (such as `:include`) when a model - has no serializer defined. - -## [0.7.0 (March 6, 2013)](https://github.com/rails-api/active_model_serializers/commit/fabdc621ff97fbeca317f6301973dd4564b9e695) - -* ```embed_key``` option to allow embedding by attributes other than IDs -* Fix rendering nil with custom serializer -* Fix global ```self.root = false``` -* Add support for specifying the serializer for an association as a String -* Able to specify keys on the attributes method -* Serializer Reloading via ActiveSupport::DescendantsTracker -* Reduce double map to once; Fixes datamapper eager loading. - -## 0.6.0 (October 22, 2012) - -* Serialize sets properly -* Add root option to ArraySerializer -* Support polymorphic associations -* Support :each_serializer in ArraySerializer -* Add `scope` method to easily access the scope in the serializer -* Fix regression with Rails 3.2.6; add Rails 4 support -* Allow serialization_scope to be disabled with serialization_scope nil -* Array serializer should support pure ruby objects besides serializers - -## 0.05.x - -### [0.5.2 (June 5, 2012)](https://github.com/rails-api/active_model_serializers/commit/615afd125c260432d456dc8be845867cf87ea118#diff-0c5c12f311d3b54734fff06069efd2ac) - -### [0.5.1 (May 23, 2012)](https://github.com/rails-api/active_model_serializers/commit/00194ec0e41831802fcbf893a34c0bb0853ebe14#diff-0c5c12f311d3b54734fff06069efd2ac) - -### [0.5.0 (May 16, 2012)](https://github.com/rails-api/active_model_serializers/commit/33d4842dcd35c7167b0b33fc0abcf00fb2c92286) - -* First tagged version -* Changes generators to always generate an ApplicationSerializer - -## [0.1.0 (December 21, 2011)](https://github.com/rails-api/active_model_serializers/commit/1e0c9ef93b96c640381575dcd30be07ac946818b) - -## First Commit as [Rails Serializers 0.0.1](https://github.com/rails-api/active_model_serializers/commit/d72b66d4c5355b0ff0a75a04895fcc4ea5b0c65e) - (December 1, 2011). - -## Prehistory - -- [Changing Serialization/Serializers namespace to `Serializable` (November 30, 2011)](https://github.com/rails/rails/commit/8896b4fdc8a543157cdf4dfc378607ebf6c10ab0) - - [Merge branch 'serializers'. This implements the ActiveModel::Serializer object. Includes code, tests, generators and guides. From José and Yehuda with love.](https://github.com/rails/rails/commit/fcacc6986ab60f1fb2e423a73bf47c7abd7b191d) - - But [was reverted](https://github.com/rails/rails/commit/5b2eb64ceb08cd005dc06b721935de5853971473). - '[Revert the serializers API as other alternatives are now also under discussion](https://github.com/rails/rails/commit/0a4035b12a6c59253cb60f9e3456513c6a6a9d33)'. -- [Proposed Implementation to Rails 3.2 by @wycats and @josevalim (November 25, 2011)](https://github.com/rails/rails/pull/3753) - - [Creation of `ActionController::Serialization`, initial serializer - support (September, 26 2011)](https://github.com/rails/rails/commit/8ff7693a8dc61f43fc4eaf72ed24d3b8699191fe). - - [Docs and CHANGELOG](https://github.com/rails/rails/commit/696d01f7f4a8ed787924a41cce6df836cd73c46f) - - [Deprecation of ActiveModel::Serialization to ActiveModel::Serializable](https://github.com/rails/rails/blob/696d01f7f4a8ed787924a41cce6df836cd73c46f/activemodel/lib/active_model/serialization.rb) -- [Creation of `ActiveModel::Serialization` from `ActiveModel::Serializer` in Rails (2009)](https://github.com/rails/rails/commit/c6bc8e662614be711f45a8d4b231d5f993b024a7#diff-d029b9768d8df0407a35804a468e3ae5) -- [Integration of `ActiveModel::Serializer` into `ActiveRecord::Serialization`](https://github.com/rails/rails/commit/783db25e0c640c1588732967a87d65c10fddc08e) -- [Creation of `ActiveModel::Serializer` in Rails (2009)](https://github.com/rails/rails/commit/d2b78b3594b9cc9870e6a6ebfeb2e56d00e6ddb8#diff-80d5beeced9bdc24ca2b04a201543bdd) -- [Creation of `ActiveModel::Serializers::JSON` in Rails (2009)](https://github.com/rails/rails/commit/fbdf706fffbfb17731a1f459203d242414ef5086) +## [Prehistory](CHANGELOG-prehistory.md) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4c006f45..3ea519c4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,13 +4,7 @@ Before opening an issue, try the following: ##### Consult the documentation -See if your issue can be resolved by information in the documentation. - -- [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master/docs) - - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/v0.10.0) - - [Guides](docs) -- [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable) -- [0.8 (0-8-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-8-stable) +See if your issue can be resolved by information in the [documentation](README.md). ##### Check for an existing issue @@ -43,7 +37,9 @@ for discussion or add your comments to existing ones. We also gladly welcome pull requests. When preparing to work on pull request, please adhere to these standards: -- Base work on the master branch unless fixing an issue with +- Base work on the relevant branch: + [0.10-stable](https://github.com/rails-api/active_model_serializers/tree/0-10-stable) + or [0.9-stable](https://github.com/rails-api/active_model_serializers/tree/0-9-stable) or [0.8-stable](https://github.com/rails-api/active_model_serializers/tree/0-8-stable) @@ -52,10 +48,10 @@ please adhere to these standards: - Note any specific areas that should be reviewed. - Include tests. - The test suite must pass on [supported Ruby versions](.travis.yml) -- Include updates to the [documentation](https://github.com/rails-api/active_model_serializers/tree/master/docs) +- Include updates to the [documentation](docs) where applicable. - Update the - [CHANGELOG](https://github.com/rails-api/active_model_serializers/blob/master/CHANGELOG.md) + [CHANGELOG](CHANGELOG.md) to the appropriate sections with a brief description of the changes. - Do not change the VERSION file. @@ -102,4 +98,3 @@ fi unset RAILS_VERSION done ``` - diff --git a/Gemfile b/Gemfile deleted file mode 100644 index e854a204..00000000 --- a/Gemfile +++ /dev/null @@ -1,56 +0,0 @@ -source 'https://rubygems.org' -# -# Add a Gemfile.local to locally bundle gems outside of version control -local_gemfile = File.join(File.expand_path('..', __FILE__), 'Gemfile.local') -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' - -if version == 'master' - gem 'rack', github: 'rack/rack' - gem 'arel', github: 'rails/arel' - git 'https://github.com/rails/rails.git' do - gem 'railties' - gem 'activesupport' - gem 'activemodel' - gem 'actionpack' - gem 'activerecord', group: :test - # Rails 5 - gem 'actionview' - end -else - gem_version = "~> #{version}.0" - gem 'railties', gem_version - gem 'activesupport', gem_version - gem 'activemodel', gem_version - gem 'actionpack', gem_version - gem 'activerecord', gem_version, group: :test -end - -# https://github.com/bundler/bundler/blob/89a8778c19269561926cea172acdcda241d26d23/lib/bundler/dependency.rb#L30-L54 -@windows_platforms = [:mswin, :mingw, :x64_mingw] - -# Windows does not include zoneinfo files, so bundle the tzinfo-data gem -gem 'tzinfo-data', platforms: (@windows_platforms + [:jruby]) - -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 - gem 'codeclimate-test-reporter', require: false - gem 'm', '~> 1.5' - gem 'pry', '~> 0.10' - gem 'pry-byebug', '~> 3.4', platform: :ruby -end - -group :development, :test do - gem 'rubocop', '~> 0.40.0', require: false - gem 'yard', require: false -end diff --git a/README.md b/README.md index 5bdcd20d..714c69ed 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,11 @@ # ActiveModelSerializers - - - - - - - - - - - - - -
Build Status - Build Status - Build status -
Code Quality - Code Quality - codebeat - Test Coverage -
Issue Stats - Pulse -
- ## About -ActiveModelSerializers brings convention over configuration to your JSON generation. - -ActiveModelSerializers works through two components: **serializers** and **adapters**. - -Serializers describe _which_ attributes and relationships should be serialized. - -Adapters describe _how_ attributes and relationships should be serialized. - -SerializableResource co-ordinates the resource, Adapter and Serializer to produce the -resource serialization. The serialization has the `#as_json`, `#to_json` and `#serializable_hash` -methods used by the Rails JSON Renderer. (SerializableResource actually delegates -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). -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`. - -`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: - -``` -gem 'active_model_serializers', '~> 0.10.0' -``` - -And then execute: - -``` -$ bundle -``` - ## Getting Started -See [Getting Started](docs/general/getting_started.md) for the nuts and bolts. - -More information is available in the [Guides](docs) and -[High-level behavior](README.md#high-level-behavior). - ## Getting Help If you find a bug, please report an [Issue](https://github.com/rails-api/active_model_serializers/issues/new) @@ -86,10 +20,11 @@ Thanks! ## Documentation If you're reading this at https://github.com/rails-api/active_model_serializers you are -reading documentation for our `master`, which may include features that have not -been released yet. Please see below for the documentation relevant to you. +reading documentation for our `master`, which is not yet released. -- [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master) +Please see below for the documentation relevant to you. + +- [0.10 (0-10-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-10-stable) - [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) @@ -101,203 +36,8 @@ been released yet. Please see below for the documentation relevant to you. ## High-level behavior -Choose an adapter from [adapters](lib/active_model_serializers/adapter): - -``` ruby -ActiveModelSerializers.config.adapter = :json_api # Default: `:attributes` -``` - -Given a [serializable model](lib/active_model/serializer/lint.rb): - -```ruby -# either -class SomeResource < ActiveRecord::Base - # columns: title, body -end -# or -class SomeResource < ActiveModelSerializers::Model - attributes :title, :body -end -``` - -And initialized as: - -```ruby -resource = SomeResource.new(title: 'ActiveModelSerializers', body: 'Convention over configuration') -``` - -Given a serializer for the serializable model: - -```ruby -class SomeSerializer < ActiveModel::Serializer - attribute :title, key: :name - attributes :body -end -``` - -The model can be serialized as: - -```ruby -options = {} -serialization = ActiveModelSerializers::SerializableResource.new(resource, options) -serialization.to_json -serialization.as_json -``` - -SerializableResource delegates to the adapter, which it builds as: - -```ruby -adapter_options = {} -adapter = ActiveModelSerializers::Adapter.create(serializer, adapter_options) -adapter.to_json -adapter.as_json -adapter.serializable_hash -``` - -The adapter formats the serializer's attributes and associations (a.k.a. includes): - -```ruby -serializer_options = {} -serializer = SomeSerializer.new(resource, serializer_options) -serializer.attributes -serializer.associations -``` - ## Architecture -This section focuses on architecture the 0.10.x version of ActiveModelSerializers. If you are interested in the architecture of the 0.8 or 0.9 versions, -please refer to the [0.8 README](https://github.com/rails-api/active_model_serializers/blob/0-8-stable/README.md) or -[0.9 README](https://github.com/rails-api/active_model_serializers/blob/0-9-stable/README.md). - -The original design is also available [here](https://github.com/rails-api/active_model_serializers/blob/d72b66d4c5355b0ff0a75a04895fcc4ea5b0c65e/README.textile). - -### ActiveModel::Serializer - -An **`ActiveModel::Serializer`** wraps a [serializable resource](https://github.com/rails/rails/blob/4-2-stable/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). - -#### ActiveModel::CollectionSerializer - -The **`ActiveModel::CollectionSerializer`** represents a collection of resources as serializers -and, if there is no serializer, primitives. - -### ActiveModelSerializers::Adapter::Base - -The **`ActiveModelSerializeres::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. - -### ActiveModelSerializers::SerializableResource - -The **`ActiveModelSerializers::SerializableResource`** acts to coordinate the serializer(s) and adapter -to an object that responds to `to_json`, and `as_json`. It is used in the controller to -encapsulate the serialization resource when rendered. However, it can also be used on its own -to serialize a resource outside of a controller, as well. - -### Primitive handling - -Definitions: A primitive is usually a String or Array. There is no serializer -defined for them; they will be serialized when the resource is converted to JSON (`as_json` or -`to_json`). (The below also applies for any object with no serializer.) - -- ActiveModelSerializers doesn't handle primitives passed to `render json:` at all. - -Internally, if no serializer can be found in the controller, the resource is not decorated by -ActiveModelSerializers. - -- However, when a primitive value is an attribute or in a collection, it is not modified. - -When serializing a collection and the collection serializer (CollectionSerializer) cannot -identify a serializer for a resource in its collection, it throws [`:no_serializer`](https://github.com/rails-api/active_model_serializers/issues/1191#issuecomment-142327128). -For example, when caught by `Reflection#build_association`, and the association value is set directly: - -```ruby -reflection_options[:virtual_value] = association_value.try(:as_json) || association_value -``` - -(which is called by the adapter as `serializer.associations(*)`.) - -### How options are parsed - -High-level overview: - -- For a **collection** - - `:serializer` specifies the collection serializer and - - `: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). - The remaining options are serializer options. - -Details: - -1. **ActionController::Serialization** - 1. `serializable_resource = ActiveModelSerializers::SerializableResource.new(resource, options)` - 1. `options` are partitioned into `adapter_opts` and everything else (`serializer_opts`). - The `adapter_opts` keys are defined in [`ActiveModelSerializers::SerializableResource::ADAPTER_OPTION_KEYS`](lib/active_model_serializers/serializable_resource.rb#L5). -1. **ActiveModelSerializers::SerializableResource** - 1. `if serializable_resource.serializer?` (there is a serializer for the resource, and an adapter is used.) - - Where `serializer?` is `use_adapter? && !!(serializer)` - - Where `use_adapter?`: 'True when no explicit adapter given, or explicit value is truthy (non-nil); - False when explicit adapter is falsy (nil or false)' - - Where `serializer`: - 1. from explicit `:serializer` option, else - 2. implicitly from resource `ActiveModel::Serializer.serializer_for(resource)` - 1. A side-effect of checking `serializer` is: - - The `:serializer` option is removed from the serializer_opts hash - - If the `:each_serializer` option is present, it is removed from the serializer_opts hash and set as the `:serializer` option - 1. The serializer and adapter are created as - 1. `serializer_instance = serializer.new(resource, serializer_opts)` - 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). -1. **ActiveModel::Serializer#attributes** is used by the adapter to get the attributes for - resource as defined by the serializer. - -(In Rails, the `options` are also passed to the `as_json(options)` or `to_json(options)` -methods on the resource serialization by the Rails JSON renderer. They are, therefore, important -to know about, but not part of ActiveModelSerializers.) - -### What does a 'serializable resource' look like? - -- 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). - -ActiveModelSerializers provides a -[`ActiveModelSerializers::Model`](https://github.com/rails-api/active_model_serializers/blob/master/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. - -```ruby -class MyModel < ActiveModelSerializers::Model - attributes :id, :name, :level -end -``` - -The default serializer for `MyModel` would be `MyModelSerializer` whether MyModel is an -ActiveRecord::Base object or not. - -Outside of the controller the rules are **exactly** the same as for records. For example: - -```ruby -render json: MyModel.new(level: 'awesome'), adapter: :json -``` - -would be serialized the same as - -```ruby -ActiveModelSerializers::SerializableResource.new(MyModel.new(level: 'awesome'), adapter: :json).as_json -``` - ## Semantic Versioning This project adheres to [semver](http://semver.org/) diff --git a/Rakefile b/Rakefile index 6ba0c2bc..fbfba082 100644 --- a/Rakefile +++ b/Rakefile @@ -3,72 +3,3 @@ begin 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 - -require 'yard' - -namespace :yard do - YARD::Rake::YardocTask.new(:doc) do |t| - t.stats_options = ['--list-undoc'] - end - - desc 'start a gem server' - task :server do - sh 'bundle exec yard server --gems' - end - - desc 'use Graphviz to generate dot graph' - task :graph do - output_file = 'doc/erd.dot' - sh "bundle exec yard graph --protected --full --dependencies > #{output_file}" - puts 'open doc/erd.dot if you have graphviz installed' - end -end - -require 'rake/testtask' - -Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.libs << 'test' - t.pattern = 'test/**/*_test.rb' - t.ruby_opts = ['-r./test/test_helper.rb'] - t.ruby_opts << ' -w' unless ENV['NO_WARN'] == 'true' - t.verbose = true -end - -desc 'Run isolated tests' -task isolated: ['test:isolated'] -namespace :test do - task :isolated do - desc 'Run isolated tests for Railtie' - require 'shellwords' - dir = File.dirname(__FILE__) - dir = Shellwords.shellescape(dir) - isolated_test_files = FileList['test/**/*_test_isolated.rb'] - # 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 - 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 - end -end - -if ENV['RAILS_VERSION'].to_s > '4.0' && RUBY_ENGINE == 'ruby' - task default: [:isolated, :test, :rubocop] -else - task default: [:test, :rubocop] -end - -desc 'CI test task' -task ci: [:default] diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec index 805c99c8..02578620 100644 --- a/active_model_serializers.gemspec +++ b/active_model_serializers.gemspec @@ -1,11 +1,8 @@ # coding: utf-8 -lib = File.expand_path('../lib', __FILE__) -$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'active_model/serializer/version' Gem::Specification.new do |spec| spec.name = 'active_model_serializers' - spec.version = ActiveModel::Serializer::VERSION + spec.version = "1.0.0-dev" spec.platform = Gem::Platform::RUBY spec.authors = ['Steve Klabnik'] spec.email = ['steve@steveklabnik.com'] @@ -20,44 +17,4 @@ Gem::Specification.new do |spec| spec.executables = [] spec.required_ruby_version = '>= 2.1' - - rails_versions = ['>= 4.1', '< 6'] - spec.add_runtime_dependency 'activemodel', rails_versions - # 'activesupport', rails_versions - # 'builder' - - spec.add_runtime_dependency 'actionpack', rails_versions - # 'activesupport', rails_versions - # 'rack' - # 'rack-test', '~> 0.6.2' - - spec.add_development_dependency 'railties', rails_versions - # 'activesupport', rails_versions - # 'actionpack', rails_versions - # 'rake', '>= 0.8.7' - - # 'activesupport', rails_versions - # 'i18n, - # 'tzinfo' - # 'minitest' - # 'thread_safe' - - spec.add_runtime_dependency 'jsonapi-renderer', ['>= 0.1.1.beta1', '< 0.2'] - spec.add_runtime_dependency 'case_transform', '>= 0.2' - - spec.add_development_dependency 'activerecord', rails_versions - # arel - # activesupport - # activemodel - - # Soft dependency for pagination - 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 'timecop', '~> 0.7' - spec.add_development_dependency 'grape', ['>= 0.13', '< 0.19.1'] - spec.add_development_dependency 'json_schema' - spec.add_development_dependency 'rake', ['>= 10.0', '< 12.0'] end diff --git a/appveyor.yml b/appveyor.yml index 7ecfa13a..aabf26a6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,29 +2,10 @@ version: 1.0.{build}-{branch} skip_tags: true -environment: - JRUBY_OPTS: "--dev -J-Xmx1024M --debug" - matrix: - - ruby_version: "Ruby21" - - ruby_version: "Ruby21-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 - - bundle env - - bundle install --path=vendor/bundle --retry=3 --jobs=3 - -before_test: - - ruby -v - - gem -v - - bundle -v - test_script: - - bundle exec rake ci + - true build: off diff --git a/bin/bench b/bin/bench deleted file mode 100755 index 8e8d5a49..00000000 --- a/bin/bench +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env ruby -# ActiveModelSerializers Benchmark driver -# Adapted from -# https://github.com/ruby-bench/ruby-bench-suite/blob/8ad567f7e43a044ae48c36833218423bb1e2bd9d/rails/benchmarks/driver.rb -require 'bundler' -Bundler.setup -require 'json' -require 'pathname' -require 'optparse' -require 'digest' -require 'pathname' -require 'shellwords' -require 'logger' -require 'English' - -class BenchmarkDriver - ROOT = Pathname File.expand_path(File.join('..', '..'), __FILE__) - BASE = ENV.fetch('BASE') { ROOT.join('test', 'benchmark') } - ESCAPED_BASE = Shellwords.shellescape(BASE) - - def self.benchmark(options) - new(options).run - end - - def self.parse_argv_and_run(argv = ARGV, options = {}) - options = { - repeat_count: 1, - pattern: [], - env: 'CACHE_ON=on' - }.merge!(options) - - OptionParser.new do |opts| - opts.banner = 'Usage: bin/bench [options]' - - opts.on('-r', '--repeat-count [NUM]', 'Run benchmarks [NUM] times taking the best result') do |value| - options[:repeat_count] = value.to_i - end - - opts.on('-p', '--pattern ', 'Benchmark name pattern') do |value| - options[:pattern] = value.split(',') - end - - opts.on('-e', '--env ', 'ENV variables to pass in') do |value| - options[:env] = value.split(',') - end - end.parse!(argv) - - benchmark(options) - end - - attr_reader :commit_hash, :base - - # Based on logfmt: - # https://www.brandur.org/logfmt - # For more complete implementation see: - # see https://github.com/arachnid-cb/logfmtr/blob/master/lib/logfmtr/base.rb - # For usage see: - # https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write/ - # https://engineering.heroku.com/blogs/2014-09-05-hutils-explore-your-structured-data-logs/ - # For Ruby parser see: - # https://github.com/cyberdelia/logfmt-ruby - def self.summary_logger(device = 'output.txt') - require 'time' - logger = Logger.new(device) - logger.level = Logger::INFO - logger.formatter = proc { |severity, datetime, progname, msg| - msg = "'#{msg}'" - "level=#{severity} time=#{datetime.utc.iso8601(6)} pid=#{Process.pid} progname=#{progname} msg=#{msg}#{$INPUT_RECORD_SEPARATOR}" - } - logger - end - - def self.stdout_logger - logger = Logger.new(STDOUT) - logger.level = Logger::INFO - logger.formatter = proc { |_, _, _, msg| "#{msg}#{$INPUT_RECORD_SEPARATOR}" } - logger - end - - def initialize(options) - @writer = ENV['SUMMARIZE'] ? self.class.summary_logger : self.class.stdout_logger - @repeat_count = options[:repeat_count] - @pattern = options[:pattern] - @commit_hash = options.fetch(:commit_hash) { `git rev-parse --short HEAD`.chomp } - @base = options.fetch(:base) { ESCAPED_BASE } - @env = Array(options[:env]).join(' ') - @rubyopt = options[:rubyopt] # TODO: rename - end - - def run - files.each do |path| - next if !@pattern.empty? && /#{@pattern.join('|')}/ !~ File.basename(path) - run_single(Shellwords.shellescape(path)) - end - end - - private - - def files - Dir[File.join(base, 'bm_*')] - end - - def run_single(path) - script = "RAILS_ENV=production #{@env} ruby #{@rubyopt} #{path}" - environment = `ruby -v`.chomp.strip[/\d+\.\d+\.\d+\w+/] - - runs_output = measure(script) - if runs_output.empty? - results = { error: :no_results } - return - end - - results = {} - results['commit_hash'] = commit_hash - results['version'] = runs_output.first['version'] - results['rails_version'] = runs_output.first['rails_version'] - results['benchmark_run[environment]'] = environment - results['runs'] = [] - - runs_output.each do |output| - results['runs'] << { - 'benchmark_type[category]' => output['label'], - 'benchmark_run[result][iterations_per_second]' => output['iterations_per_second'].round(3), - 'benchmark_run[result][total_allocated_objects_per_iteration]' => output['total_allocated_objects_per_iteration'] - } - end - ensure - results && report(results) - end - - def report(results) - @writer.info { 'Benchmark results:' } - @writer.info { JSON.pretty_generate(results) } - end - - def summarize(result) - puts "#{result['label']} #{result['iterations_per_second']}/ips; #{result['total_allocated_objects_per_iteration']} objects" - end - - # FIXME: ` provides the full output but it'll return failed output as well. - def measure(script) - results = Hash.new { |h, k| h[k] = [] } - - @repeat_count.times do - output = sh(script) - output.each_line do |line| - next if line.nil? - begin - result = JSON.parse(line) - rescue JSON::ParserError - result = { error: line } # rubocop:disable Lint/UselessAssignment - else - summarize(result) - results[result['label']] << result - end - end - end - - results.map do |_, bm_runs| - bm_runs.sort_by do |run| - run['iterations_per_second'] - end.last - end - end - - def sh(cmd) - `#{cmd}` - end -end - -BenchmarkDriver.parse_argv_and_run if $PROGRAM_NAME == __FILE__ diff --git a/bin/bench_regression b/bin/bench_regression deleted file mode 100755 index c4f00cba..00000000 --- a/bin/bench_regression +++ /dev/null @@ -1,316 +0,0 @@ -#!/usr/bin/env ruby -require 'fileutils' -require 'pathname' -require 'shellwords' -require 'English' - -############################ -# USAGE -# -# bundle exec bin/bench_regression -# defaults to the current branch -# defaults to the master branch -# bundle exec bin/bench_regression current # will run on the current branch -# bundle exec bin/bench_regression revisions 792fb8a90 master # every revision inclusive -# bundle exec bin/bench_regression 792fb8a90 master --repeat-count 2 --env CACHE_ON=off -# bundle exec bin/bench_regression vendor -########################### - -class BenchRegression - ROOT = Pathname File.expand_path(File.join(*['..', '..']), __FILE__) - TMP_DIR_NAME = File.join('tmp', 'bench') - TMP_DIR = File.join(ROOT, TMP_DIR_NAME) - E_TMP_DIR = Shellwords.shellescape(TMP_DIR) - load ROOT.join('bin', 'bench') - - attr_reader :source_stasher - - def initialize - @source_stasher = SourceStasher.new - end - - class SourceStasher - attr_reader :gem_require_paths, :gem_paths - attr_writer :vendor - - def initialize - @gem_require_paths = [] - @gem_paths = [] - refresh_temp_dir - @vendor = false - end - - def temp_dir_empty? - File.directory?(TMP_DIR) && - Dir[File.join(TMP_DIR, '*')].none? - end - - def empty_temp_dir - return if @vendor - return if temp_dir_empty? - FileUtils.mkdir_p(TMP_DIR) - Dir[File.join(TMP_DIR, '*')].each do |file| - if File.directory?(file) - FileUtils.rm_rf(file) - else - FileUtils.rm(file) - end - end - end - - def fill_temp_dir - vendor_files(Dir[File.join(ROOT, 'test', 'benchmark', '*.{rb,ru}')]) - # vendor_file(File.join('bin', 'bench')) - housekeeping { empty_temp_dir } - vendor_gem('benchmark-ips') - end - - def vendor_files(files) - files.each do |file| - vendor_file(file) - end - end - - def vendor_file(file) - FileUtils.cp(file, File.join(TMP_DIR, File.basename(file))) - end - - def vendor_gem(gem_name) - directory_name = `bundle exec gem unpack benchmark-ips --target=#{E_TMP_DIR}`[/benchmark-ips.+\d/] - gem_paths << File.join(TMP_DIR, directory_name) - gem_require_paths << File.join(TMP_DIR_NAME, directory_name, 'lib') - housekeeping { remove_vendored_gems } - end - - def remove_vendored_gems - return if @vendor - FileUtils.rm_rf(*gem_paths) - end - - def refresh_temp_dir - empty_temp_dir - fill_temp_dir - end - - def housekeeping - at_exit { yield } - end - end - - module RevisionMethods - module_function - def current_branch - @current_branch ||= `cat .git/HEAD | cut -d/ -f3,4,5`.chomp - end - - def current_revision - `git rev-parse --short HEAD`.chomp - end - - def revision_description(rev) - `git log --oneline -1 #{rev}`.chomp - end - - def revisions(start_ref, end_ref) - cmd = "git rev-list --reverse #{start_ref}..#{end_ref}" - `#{cmd}`.chomp.split("\n") - end - - def checkout_ref(ref) - `git checkout #{ref}`.chomp - if $CHILD_STATUS - STDERR.puts "Checkout failed: #{ref}, #{$CHILD_STATUS.exitstatus}" unless $CHILD_STATUS.success? - $CHILD_STATUS.success? - else - true - end - end - - def clean_head - system('git reset --hard --quiet') - end - end - module ShellMethods - - def sh(cmd) - puts cmd - # system(cmd) - run(cmd) - # env = {} - # # out = STDOUT - # pid = spawn(env, cmd) - # Process.wait(pid) - # pid = fork do - # exec cmd - # end - # Process.waitpid2(pid) - # puts $CHILD_STATUS.exitstatus - end - - require 'pty' - # should consider trapping SIGINT in here - def run(cmd) - puts cmd - child_process = '' - result = '' - # http://stackoverflow.com/a/1162850 - # stream output of subprocess - begin - PTY.spawn(cmd) do |stdin, _stdout, pid| - begin - # Do stuff with the output here. Just printing to show it works - stdin.each do |line| - print line - result << line - end - child_process = PTY.check(pid) - rescue Errno::EIO - puts 'Errno:EIO error, but this probably just means ' \ - 'that the process has finished giving output' - end - end - rescue PTY::ChildExited - puts 'The child process exited!' - end - unless (child_process && child_process.success?) - exitstatus = child_process.exitstatus - puts "FAILED: #{child_process.pid} exited with status #{exitstatus.inspect} due to failed command #{cmd}" - exit exitstatus || 1 - end - result - end - - def bundle(ref) - system("rm -f Gemfile.lock") - # This is absolutely critical for bundling to work - Bundler.with_clean_env do - system("bundle check || - bundle install --local || - bundle install || - bundle update") - end - - # if $CHILD_STATUS - # STDERR.puts "Bundle failed at: #{ref}, #{$CHILD_STATUS.exitstatus}" unless $CHILD_STATUS.success? - # $CHILD_STATUS.success? - # else - # false - # end - end - end - include ShellMethods - include RevisionMethods - - def benchmark_refs(ref1: nil, ref2: nil, cmd:) - checking_out = false - ref0 = current_branch - ref1 ||= current_branch - ref2 ||= 'master' - p [ref0, ref1, ref2, current_revision] - - run_benchmark_at_ref(cmd, ref1) - p [ref0, ref1, ref2, current_revision] - run_benchmark_at_ref(cmd, ref2) - p [ref0, ref1, ref2, current_revision] - - checking_out = true - checkout_ref(ref0) - rescue Exception # rubocop:disable Lint/RescueException - STDERR.puts "[ERROR] #{$!.message}" - checkout_ref(ref0) unless checking_out - raise - end - - def benchmark_revisions(ref1: nil, ref2: nil, cmd:) - checking_out = false - ref0 = current_branch - ref1 ||= current_branch - ref2 ||= 'master' - - revisions(ref1, ref2).each do |rev| - STDERR.puts "Checking out: #{revision_description(rev)}" - - run_benchmark_at_ref(cmd, rev) - clean_head - end - checking_out = true - checkout_ref(ref0) - rescue Exception # rubocop:disable Lint/RescueException - STDERR.puts "[ERROR]: #{$!.message}" - checkout_ref(ref0) unless checking_out - raise - end - - def run_benchmark_at_ref(cmd, ref) - checkout_ref(ref) - run_benchmark(cmd, ref) - end - - def run_benchmark(cmd, ref = nil) - ref ||= current_revision - bundle(ref) && - benchmark_tests(cmd, ref) - end - - def benchmark_tests(cmd, ref) - base = E_TMP_DIR - # cmd.sub('bin/bench', 'tmp/revision_runner/bench') - # bundle = Gem.bin('bunle' - # Bundler.with_clean_env(&block) - - # cmd = Shellwords.shelljoin(cmd) - # cmd = "COMMIT_HASH=#{ref} BASE=#{base} bundle exec ruby -rbenchmark/ips #{cmd}" - # Add vendoring benchmark/ips to load path - - # CURRENT THINKING: IMPORTANT - # Pass into require statement as RUBYOPTS i.e. via env rather than command line argument - # otherwise, have a 'fast ams benchmarking' module that extends benchmarkings to add the 'ams' - # method but doesn't depend on benchmark-ips - options = { - commit_hash: ref, - base: base, - rubyopt: Shellwords.shellescape("-Ilib:#{source_stasher.gem_require_paths.join(':')}") - } - BenchmarkDriver.parse_argv_and_run(ARGV.dup, options) - end -end - -if $PROGRAM_NAME == __FILE__ - benchmarking = BenchRegression.new - - case ARGV[0] - when 'current' - # Run current branch only - - # super simple command line parsing - args = ARGV.dup - _ = args.shift # remove 'current' from args - cmd = args - benchmarking.run_benchmark(cmd) - when 'revisions' - # Runs on every revision - - # super simple command line parsing - args = ARGV.dup - _ = args.shift - ref1 = args.shift # remove 'revisions' from args - ref2 = args.shift - cmd = args - benchmarking.benchmark_revisions(ref1: ref1, ref2: ref2, cmd: cmd) - when 'vendor' - # Just prevents vendored files from being cleaned up - # at exit. (They are vendored at initialize.) - benchmarking.source_stasher.vendor = true - else - # Default: Compare current_branch to master - # Optionally: pass in two refs as args to `bin/bench_regression` - # TODO: Consider checking across more revisions, to automatically find problems. - - # super simple command line parsing - args = ARGV.dup - ref1 = args.shift - ref2 = args.shift - cmd = args - benchmarking.benchmark_refs(ref1: ref1, ref2: ref2, cmd: cmd) - end -end diff --git a/bin/rubocop b/bin/rubocop deleted file mode 100755 index 269f8954..00000000 --- a/bin/rubocop +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -# -# Usage: -# bin/rubocop [-A|-t|-h] -# bin/rubocop [file or path] [cli options] -# -# Options: -# Autocorrect -A -# AutoGenConfig -t -# Usage -h,--help,help - -set -e - -case $1 in - -A) - echo "Rubocop autocorrect is ON" >&2 - bundle exec rake -f lib/tasks/rubocop.rake rubocop:auto_correct - ;; - - -t) - echo "Rubocop is generating a new TODO" >&2 - bundle exec rake -f lib/tasks/rubocop.rake rubocop:auto_gen_config - ;; - - -h|--help|help) - sed -ne '/^#/!q;s/.\{1,2\}//;1d;p' < "$0" - ;; - - *) - # with no args, run vanilla rubocop - # else assume we're passing in arbitrary arguments - if [ -z "$1" ]; then - bundle exec rake -f lib/tasks/rubocop.rake rubocop - else - bundle exec rubocop "$@" - fi - ;; -esac diff --git a/bin/serve_benchmark b/bin/serve_benchmark deleted file mode 100755 index 3f292d18..00000000 --- a/bin/serve_benchmark +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash -set -e - -case "$1" in - - start) - config="${CONFIG_RU:-test/benchmark/config.ru}" - bundle exec ruby -Ilib -S rackup "$config" --daemonize --pid tmp/benchmark_app.pid --warn --server webrick - until [ -f 'tmp/benchmark_app.pid' ]; do - sleep 0.1 # give it time to start.. I don't know a better way - done - cat tmp/benchmark_app.pid - true - ;; - - stop) - if [ -f 'tmp/benchmark_app.pid' ]; then - kill -TERM $(cat tmp/benchmark_app.pid) - else - echo 'No pidfile' - false - fi - ;; - - status) - if [ -f 'tmp/benchmark_app.pid' ]; then - kill -0 $(cat tmp/benchmark_app.pid) - [ "$?" -eq 0 ] - else - echo 'No pidfile' - false - fi - ;; - - *) - echo "Usage: $0 [start|stop|status]" - ;; - -esac diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 94460ec1..00000000 --- a/docs/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Docs - ActiveModel::Serializer 0.10.x - -This is the documentation of ActiveModelSerializers, it's focused on the **0.10.x version.** - ------ - -## General - -- [Getting Started](general/getting_started.md) -- [Configuration Options](general/configuration_options.md) -- [Serializers](general/serializers.md) -- [Adapters](general/adapters.md) -- [Rendering](general/rendering.md) -- [Caching](general/caching.md) -- [Logging](general/logging.md) -- [Deserialization](general/deserialization.md) -- [Instrumentation](general/instrumentation.md) -- JSON API - - [Schema](jsonapi/schema.md) - - [Errors](jsonapi/errors.md) - -## How to - -- [How to add root key](howto/add_root_key.md) -- [How to add pagination links](howto/add_pagination_links.md) -- [How to add relationship links](howto/add_relationship_links.md) -- [Using ActiveModelSerializers Outside Of Controllers](howto/outside_controller_use.md) -- [Testing ActiveModelSerializers](howto/test.md) -- [Passing Arbitrary Options](howto/passing_arbitrary_options.md) -- [How to serialize a Plain-Old Ruby Object (PORO)](howto/serialize_poro.md) -- [How to upgrade from `0.8` to `0.10` safely](howto/upgrade_from_0_8_to_0_10.md) - -## Integrations - -| Integration | Supported ActiveModelSerializers versions | Gem name and/or link -|----|-----|---- -| Ember.js | 0.9.x | [active-model-adapter](https://github.com/ember-data/active-model-adapter) -| Ember.js | 0.10.x + | [docs/integrations/ember-and-json-api.md](integrations/ember-and-json-api.md) -| Grape | 0.10.x + | [docs/integrations/grape.md](integrations/grape.md) | -| Grape | 0.9.x | https://github.com/jrhe/grape-active_model_serializers/ | -| Sinatra | 0.9.x | https://github.com/SauloSilva/sinatra-active-model-serializers/ diff --git a/docs/STYLE.md b/docs/STYLE.md deleted file mode 100644 index ccd75dd4..00000000 --- a/docs/STYLE.md +++ /dev/null @@ -1,58 +0,0 @@ -# STYLE - -## Code and comments - -- We are actively working to identify tasks under the label [**Good for New - Contributors**](https://github.com/rails-api/active_model_serializers/labels/Good%20for%20New%20Contributors). - - [Changelog - Missing](https://github.com/rails-api/active_model_serializers/issues?q=label%3A%22Changelog+Missing%22+is%3Aclosed) is - an easy way to help out. - -- [Fix a bug](https://github.com/rails-api/active_model_serializers/labels/Ready%20for%20PR). - - Ready for PR - A well defined bug, needs someone to PR a fix. - - Bug - Anything that is broken. - - Regression - A bug that did not exist in previous versions and isn't a new feature (applied in tandem with Bug). - - Performance - A performance related issue. We could track this as a bug, but usually these would have slightly lower priority than standard bugs. - -- [Develop new features](https://github.com/rails-api/active_model_serializers/labels/Feature). - -- [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). - -- We are also encouraging comments to substantial changes (larger than bugfixes and simple features) under an - "RFC" (Request for Comments) process before we start active development. - Look for the [**RFC**](https://github.com/rails-api/active_model_serializers/labels/RFC) label. - - -## Pull requests - -- If the tests pass and the pull request looks good, a maintainer will merge it. -- If the pull request needs to be changed, - - you can change it by updating the branch you generated the pull request from - - either by adding more commits, or - - by force pushing to it - - A maintainer can make any changes themselves and manually merge the code in. - -## Commit messages - -- [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) -- [http://stopwritingramblingcommitmessages.com/](http://stopwritingramblingcommitmessages.com/) -- [ThoughtBot style guide](https://github.com/thoughtbot/guides/tree/master/style#git) - -#### About Pull Requests (PR's) - -- [Using Pull Requests](https://help.github.com/articles/using-pull-requests) -- [Github pull requests made easy](http://www.element84.com/github-pull-requests-made-easy.html) -- [Exercism Git Workflow](http://help.exercism.io/git-workflow.html). -- [Level up your Git](http://rakeroutes.com/blog/deliberate-git/) -- [All Your Open Source Code Are Belong To Us](http://www.benjaminfleischer.com/2013/07/30/all-your-open-source-code-are-belong-to-us/) - -## Issue Labeling - -ActiveModelSerializers uses a subset of [StandardIssueLabels](https://github.com/wagenet/StandardIssueLabels) for Github Issues. You can [see our labels here](https://github.com/rails-api/active_model_serializers/labels). - diff --git a/docs/general/adapters.md b/docs/general/adapters.md deleted file mode 100644 index 84fc4e62..00000000 --- a/docs/general/adapters.md +++ /dev/null @@ -1,263 +0,0 @@ -[Back to Guides](../README.md) - -# Adapters - -ActiveModelSerializers offers the ability to configure which adapter -to use both globally and/or when serializing (usually when rendering). - -The global adapter configuration is set on [`ActiveModelSerializers.config`](configuration_options.md). -It should be set only once, preferably at initialization. - -For example: - -```ruby -ActiveModelSerializers.config.adapter = ActiveModelSerializers::Adapter::JsonApi -``` - -or - -```ruby -ActiveModelSerializers.config.adapter = :json_api -``` - -or - -```ruby -ActiveModelSerializers.config.adapter = :json -``` - -The local adapter option is in the format `adapter: adapter`, where `adapter` is -any of the same values as set globally. - -The configured adapter can be set as a symbol, class, or class name, as described in -[Advanced adapter configuration](adapters.md#advanced-adapter-configuration). - -The `Attributes` adapter does not include a root key. It is just the serialized attributes. - -Use either the `JSON` or `JSON API` adapters if you want the response document to have a root key. - -## Built in Adapters - -### Attributes - Default - -It's the default adapter, it generates a json response without a root key. -Doesn't follow any specific convention. - -##### Example output - -```json -{ - "title": "Title 1", - "body": "Body 1", - "publish_at": "2020-03-16T03:55:25.291Z", - "author": { - "first_name": "Bob", - "last_name": "Jones" - }, - "comments": [ - { - "body": "cool" - }, - { - "body": "awesome" - } - ] -} -``` - -### JSON - -The json response is always rendered with a root key. - -The root key can be overridden by: -* passing the `root` option in the render call. See details in the [Rendering Guides](rendering.md#overriding-the-root-key). -* setting the `type` of the serializer. See details in the [Serializers Guide](serializers.md#type). - -Doesn't follow any specific convention. - -##### Example output - -```json -{ - "post": { - "title": "Title 1", - "body": "Body 1", - "publish_at": "2020-03-16T03:55:25.291Z", - "author": { - "first_name": "Bob", - "last_name": "Jones" - }, - "comments": [{ - "body": "cool" - }, { - "body": "awesome" - }] - } -} -``` - -### JSON API - -This adapter follows **version 1.0** of the [format specified](../jsonapi/schema.md) in -[jsonapi.org/format](http://jsonapi.org/format). - -##### Example output - -```json -{ - "data": { - "id": "1337", - "type": "posts", - "attributes": { - "title": "Title 1", - "body": "Body 1", - "publish-at": "2020-03-16T03:55:25.291Z" - }, - "relationships": { - "author": { - "data": { - "id": "1", - "type": "authors" - } - }, - "comments": { - "data": [{ - "id": "7", - "type": "comments" - }, { - "id": "12", - "type": "comments" - }] - } - }, - "links": { - "post-authors": "https://example.com/post_authors" - }, - "meta": { - "rating": 5, - "favorite-count": 10 - } - } -} -``` - -### Include option - -Which [serializer associations](https://github.com/rails-api/active_model_serializers/blob/master/docs/general/serializers.md#associations) are rendered can be specified using the `include` option. The option usage is consistent with [the include option in the JSON API spec](http://jsonapi.org/format/#fetching-includes), and is available in all adapters. - -Example of the usage: -```ruby - render json: @posts, include: ['author', 'comments', 'comments.author'] - # or - render json: @posts, include: 'author,comments,comments.author' -``` - -The format of the `include` option can be either: - -- a String composed of a comma-separated list of [relationship paths](http://jsonapi.org/format/#fetching-includes). -- an Array of Symbols and Hashes. -- a mix of both. - -An empty string or an empty array will prevent rendering of any associations. - -In addition, two types of wildcards may be used: - -- `*` includes one level of associations. -- `**` includes all recursively. - -These can be combined with other paths. - -```ruby - render json: @posts, include: '**' # or '*' for a single layer -``` - - -The following would render posts and include: - -- the author -- the author's comments, and -- every resource referenced by the author's comments (recursively). - -It could be combined, like above, with other paths in any combination desired. - -```ruby - render json: @posts, include: 'author.comments.**' -``` - -**Note:** Wildcards are ActiveModelSerializers-specific, they are not part of the JSON API spec. - -The default include for the JSON API adapter is no associations. The default for the JSON and Attributes adapters is all associations. - -For the JSON API adapter associated resources will be gathered in the `"included"` member. For the JSON and Attributes -adapters associated resources will be rendered among the other attributes. - -Only for the JSON API adapter you can specify, which attributes of associated resources will be rendered. This feature -is called [sparse fieldset](http://jsonapi.org/format/#fetching-sparse-fieldsets): - -```ruby - render json: @posts, include: 'comments', fields: { comments: ['content', 'created_at'] } -``` - -##### Security Considerations - -Since the included options may come from the query params (i.e. user-controller): - -```ruby - render json: @posts, include: params[:include] -``` - -The user could pass in `include=**`. - -We recommend filtering any user-supplied includes appropriately. - -## Advanced adapter configuration - -### Registering an adapter - -The default adapter can be configured, as above, to use any class given to it. - -An adapter may also be specified, e.g. when rendering, as a class or as a symbol. -If a symbol, then the adapter must be, e.g. `:great_example`, -`ActiveModelSerializers::Adapter::GreatExample`, or registered. - -There are two ways to register an adapter: - -1) The simplest, is to subclass `ActiveModelSerializers::Adapter::Base`, e.g. the below will -register the `Example::UsefulAdapter` as `"example/useful_adapter"`. - -```ruby -module Example - class UsefulAdapter < ActiveModelSerializers::Adapter::Base - end -end -``` - -You'll notice that the name it registers is the underscored namespace and class. - -Under the covers, when the `ActiveModelSerializers::Adapter::Base` is subclassed, it registers -the subclass as `register("example/useful_adapter", Example::UsefulAdapter)` - -2) Any class can be registered as an adapter by calling `register` directly on the -`ActiveModelSerializers::Adapter` class. e.g., the below registers `MyAdapter` as -`:special_adapter`. - -```ruby -class MyAdapter; end -ActiveModelSerializers::Adapter.register(:special_adapter, MyAdapter) -``` - -### Looking up an adapter - -| Method | Return value | -| :------------ |:---------------| -| `ActiveModelSerializers::Adapter.adapter_map` | A Hash of all known adapters `{ adapter_name => adapter_class }` | -| `ActiveModelSerializers::Adapter.adapters` | A (sorted) Array of all known `adapter_names` | -| `ActiveModelSerializers::Adapter.lookup(name_or_klass)` | The `adapter_class`, else raises an `ActiveModelSerializers::Adapter::UnknownAdapter` error | -| `ActiveModelSerializers::Adapter.adapter_class(adapter)` | Delegates to `ActiveModelSerializers::Adapter.lookup(adapter)` | -| `ActiveModelSerializers::Adapter.configured_adapter` | A convenience method for `ActiveModelSerializers::Adapter.lookup(config.adapter)` | - -The registered adapter name is always a String, but may be looked up as a Symbol or String. -Helpfully, the Symbol or String is underscored, so that `get(:my_adapter)` and `get("MyAdapter")` -may both be used. - -For more information, see [the Adapter class on GitHub](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/adapter.rb) diff --git a/docs/general/caching.md b/docs/general/caching.md deleted file mode 100644 index 9ab9d71a..00000000 --- a/docs/general/caching.md +++ /dev/null @@ -1,58 +0,0 @@ -[Back to Guides](../README.md) - -# Caching - -## Warning - -There is currently a problem with caching in AMS [Caching doesn't improve performance](https://github.com/rails-api/active_model_serializers/issues/1586). Adding caching _may_ slow down your application, rather than speeding it up. We suggest you benchmark any caching you implement before using in a production enviroment - -___ - -To cache a serializer, call ```cache``` and pass its options. -The options are the same options of ```ActiveSupport::Cache::Store```, plus -a ```key``` option that will be the prefix of the object cache -on a pattern ```"#{key}/#{object.id}-#{object.updated_at}"```. - -The cache support is optimized to use the cached object in multiple request. An object cached on a ```show``` request will be reused at the ```index```. If there is a relationship with another cached serializer it will also be created and reused automatically. - -**[NOTE] Every object is individually cached.** - -**[NOTE] The cache is automatically expired after an object is updated, but it's not deleted.** - -```ruby -cache(options = nil) # options: ```{key, expires_in, compress, force, race_condition_ttl}``` -``` - -Take the example below: - -```ruby -class PostSerializer < ActiveModel::Serializer - cache key: 'post', expires_in: 3.hours - attributes :title, :body - - has_many :comments -end -``` - -On this example every ```Post``` object will be cached with -the key ```"post/#{post.id}-#{post.updated_at}"```. You can use this key to expire it as you want, -but in this case it will be automatically expired after 3 hours. - -## Fragment Caching - -If there is some API endpoint that shouldn't be fully cached, you can still optimise it, using Fragment Cache on the attributes and relationships that you want to cache. - -You can define the attribute by using ```only``` or ```except``` option on cache method. - -**[NOTE] Cache serializers will be used at their relationships** - -Example: - -```ruby -class PostSerializer < ActiveModel::Serializer - cache key: 'post', expires_in: 3.hours, only: [:title] - attributes :title, :body - - has_many :comments -end -``` diff --git a/docs/general/configuration_options.md b/docs/general/configuration_options.md deleted file mode 100644 index 83f8890d..00000000 --- a/docs/general/configuration_options.md +++ /dev/null @@ -1,169 +0,0 @@ -[Back to Guides](../README.md) - -# Configuration Options - -The following configuration options can be set on -`ActiveModelSerializers.config`, preferably inside an initializer. - -## General - -##### adapter - -The [adapter](adapters.md) to use. - -Possible values: - -- `:attributes` (default) -- `:json` -- `:json_api` - -##### serializer_lookup_enabled - -Enable automatic serializer lookup. - -Possible values: - -- `true` (default) -- `false` - -When `false`, serializers must be explicitly specified. - -##### key_transform - -The [key transform](key_transforms.md) to use. - - -| Option | Result | -|----|----| -| `:camel` | ExampleKey | -| `:camel_lower` | exampleKey | -| `:dash` | example-key | -| `:unaltered` | the original, unaltered key | -| `:underscore` | example_key | -| `nil` | use the adapter default | - -Each adapter has a default key transform configured: - -| Adapter | Default Key Transform | -|----|----| -| `Attributes` | `:unaltered` | -| `Json` | `:unaltered` | -| `JsonApi` | `:dash` | - -`config.key_transform` is a global override of the adapter default. Adapters -still prefer the render option `:key_transform` over this setting. - -*NOTE: Key transforms can be expensive operations. If key transforms are unnecessary for the -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. - - -##### serializer_lookup_chain - -Configures how serializers are searched for. By default, the lookup chain is - -```ruby -ActiveModelSerializers::LookupChain::DEFAULT -``` - -which is shorthand for - -```ruby -[ - ActiveModelSerializers::LookupChain::BY_PARENT_SERIALIZER, - ActiveModelSerializers::LookupChain::BY_NAMESPACE, - ActiveModelSerializers::LookupChain::BY_RESOURCE_NAMESPACE, - ActiveModelSerializers::LookupChain::BY_RESOURCE -] -``` - -Each of the array entries represent a proc. A serializer lookup proc will be yielded 3 arguments. `resource_class`, `serializer_class`, and `namespace`. - -Note that: - - `resource_class` is the class of the resource being rendered - - by default `serializer_class` is `ActiveModel::Serializer` - - for association lookup it's the "parent" serializer - - `namespace` correspond to either the controller namespace or the [optionally] specified [namespace render option](./rendering.md#namespace) - -An example config could be: - -```ruby -ActiveModelSerializers.config.serializer_lookup_chain = [ - lambda do |resource_class, serializer_class, namespace| - "API::#{namespace}::#{resource_class}" - end -] -``` - -If you simply want to add to the existing lookup_chain. Use `unshift`. - -```ruby -ActiveModelSerializers.config.serializer_lookup_chain.unshift( - lambda do |resource_class, serializer_class, namespace| - # ... - end -) -``` - -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. - -## JSON API - -##### jsonapi_resource_type - -Sets whether the [type](http://jsonapi.org/format/#document-resource-identifier-objects) -of the resource should be `singularized` or `pluralized` when it is not -[explicitly specified by the serializer](https://github.com/rails-api/active_model_serializers/blob/master/docs/general/serializers.md#type) - -Possible values: - -- `:singular` -- `:plural` (default) - -##### jsonapi_namespace_separator - -Sets separator string for namespaced models to render `type` attribute. - - -| Separator | Example: Admin::User | -|----|----| -| `'-'` (default) | 'admin-users' -| `'--'` (recommended) | 'admin--users' - -See [Recommendation for dasherizing (kebab-case-ing) namespaced object, such as `Admin::User`](https://github.com/json-api/json-api/issues/850) -for more discussion. - -##### jsonapi_include_toplevel_object - -Include a [top level jsonapi member](http://jsonapi.org/format/#document-jsonapi-object) -in the response document. - -Possible values: - -- `true` -- `false` (default) - -##### jsonapi_version - -The latest version of the spec to which the API conforms. - -Default: `'1.0'`. - -*Used when `jsonapi_include_toplevel_object` is `true`* - -##### jsonapi_toplevel_meta - -Optional top-level metadata. Not included if empty. - -Default: `{}`. - -*Used when `jsonapi_include_toplevel_object` is `true`* - - -## Hooks - -To run a hook when ActiveModelSerializers is loaded, use -`ActiveSupport.on_load(:action_controller) do end` diff --git a/docs/general/deserialization.md b/docs/general/deserialization.md deleted file mode 100644 index 995abea9..00000000 --- a/docs/general/deserialization.md +++ /dev/null @@ -1,100 +0,0 @@ -[Back to Guides](../README.md) - -# Deserialization - -This is currently an *experimental* feature. The interface may change. - -## JSON API - -The `ActiveModelSerializers::Deserialization` defines two methods (namely `jsonapi_parse` and `jsonapi_parse!`), which take a `Hash` or an instance of `ActionController::Parameters` representing a JSON API payload, and return a hash that can directly be used to create/update models. The bang version throws an `InvalidDocument` exception when parsing fails, whereas the "safe" version simply returns an empty hash. - -- Parameters - - document: `Hash` or `ActionController::Parameters` instance - - options: - - only: `Array` of whitelisted fields - - except: `Array` of blacklisted fields - - keys: `Hash` of fields the name of which needs to be modified (e.g. `{ :author => :user, :date => :created_at }`) - -Examples: - -```ruby -class PostsController < ActionController::Base - def create - Post.create(create_params) - end - - def create_params - ActiveModelSerializers::Deserialization.jsonapi_parse(params, only: [:title, :content, :author]) - end -end -``` - - - -Given a JSON API document, - -``` -document = { - 'data' => { - 'id' => 1, - 'type' => 'post', - 'attributes' => { - 'title' => 'Title 1', - 'date' => '2015-12-20' - }, - 'relationships' => { - 'author' => { - 'data' => { - 'type' => 'user', - 'id' => '2' - } - }, - 'second_author' => { - 'data' => nil - }, - 'comments' => { - 'data' => [{ - 'type' => 'comment', - 'id' => '3' - },{ - 'type' => 'comment', - 'id' => '4' - }] - } - } - } -} -``` - -The entire document can be parsed without specifying any options: -```ruby -ActiveModelSerializers::Deserialization.jsonapi_parse(document) -#=> -# { -# title: 'Title 1', -# date: '2015-12-20', -# author_id: 2, -# second_author_id: nil -# comment_ids: [3, 4] -# } -``` - -and fields, relationships, and polymorphic relationships can be specified via the options: - -```ruby -ActiveModelSerializers::Deserialization - .jsonapi_parse(document, only: [:title, :date, :author], - keys: { date: :published_at }, - polymorphic: [:author]) -#=> -# { -# title: 'Title 1', -# published_at: '2015-12-20', -# author_id: '2', -# author_type: 'user' -# } -``` - -## Attributes/Json - -There is currently no deserialization for those adapters. diff --git a/docs/general/fields.md b/docs/general/fields.md deleted file mode 100644 index a1a12be6..00000000 --- a/docs/general/fields.md +++ /dev/null @@ -1,31 +0,0 @@ -[Back to Guides](../README.md) - -# Fields - -If for any reason, you need to restrict the fields returned, you should use `fields` option. - -For example, if you have a serializer like this - -```ruby -class UserSerializer < ActiveModel::Serializer - attributes :access_token, :first_name, :last_name -end -``` - -and in a specific controller, you want to return `access_token` only, `fields` will help you: - -```ruby -class AnonymousController < ApplicationController - def create - render json: User.create(activation_state: 'anonymous'), fields: [:access_token], status: 201 - end -end -``` - -Note that this is only valid for the `json` and `attributes` adapter. For the `json_api` adapter, you would use - -```ruby -render json: @user, fields: { users: [:access_token] } -``` - -Where `users` is the JSONAPI type. diff --git a/docs/general/getting_started.md b/docs/general/getting_started.md deleted file mode 100644 index b39cd283..00000000 --- a/docs/general/getting_started.md +++ /dev/null @@ -1,133 +0,0 @@ -[Back to Guides](../README.md) - -# Getting Started - -## Creating a Serializer - -The easiest way to create a new serializer is to generate a new resource, which -will generate a serializer at the same time: - -``` -$ rails g resource post title:string body:string -``` - -This will generate a serializer in `app/serializers/post_serializer.rb` for -your new model. You can also generate a serializer for an existing model with -the serializer generator: - -``` -$ rails g serializer post -``` - -The generated serializer will contain basic `attributes` and -`has_many`/`has_one`/`belongs_to` declarations, based on the model. For example: - -```ruby -class PostSerializer < ActiveModel::Serializer - attributes :title, :body - - has_many :comments - has_one :author -end -``` - -and - -```ruby -class CommentSerializer < ActiveModel::Serializer - attributes :name, :body - - belongs_to :post -end -``` - -The attribute names are a **whitelist** of attributes to be serialized. - -The `has_many`, `has_one`, and `belongs_to` declarations describe relationships between -resources. By default, when you serialize a `Post`, you will get its `Comments` -as well. - -For more information, see [Serializers](/docs/general/serializers.md). - -### Namespaced Models - -When serializing a model inside a namespace, such as `Api::V1::Post`, ActiveModelSerializers will expect the corresponding serializer to be inside the same namespace (namely `Api::V1::PostSerializer`). - -### Model Associations and Nested Serializers - -When declaring a serializer for a model with associations, such as: -```ruby -class PostSerializer < ActiveModel::Serializer - has_many :comments -end -``` -ActiveModelSerializers will look for `PostSerializer::CommentSerializer` in priority, and fall back to `::CommentSerializer` in case the former does not exist. This allows for more control over the way a model gets serialized as an association of an other model. - -For example, in the following situation: - -```ruby -class CommentSerializer < ActiveModel::Serializer - attributes :body, :date, :nb_likes -end - -class PostSerializer < ActiveModel::Serializer - has_many :comments - class CommentSerializer < ActiveModel::Serializer - attributes :body_short - end -end -``` - -ActiveModelSerializers will use `PostSerializer::CommentSerializer` (thus including only the `:body_short` attribute) when serializing a `Comment` as part of a `Post`, but use `::CommentSerializer` when serializing a `Comment` directly (thus including `:body, :date, :nb_likes`). - -### Extending a Base `ApplicationSerializer` - -By default, new serializers descend from `ActiveModel::Serializer`. However, if -you wish to share behavior across your serializers, you can create an -`ApplicationSerializer` at `app/serializers/application_serializer.rb`: - -```ruby -class ApplicationSerializer < ActiveModel::Serializer -end -``` - -Then any newly-generated serializers will automatically descend from -`ApplicationSerializer`. - -``` -$ rails g serializer post -``` - -Now generates: - -```ruby -class PostSerializer < ApplicationSerializer - attributes :id -end -```` - -## Rails Integration - -ActiveModelSerializers will automatically integrate with your Rails app, -so you won't need to update your controller. -This is a example of how the controller will look: - -```ruby -class PostsController < ApplicationController - - def show - @post = Post.find(params[:id]) - render json: @post - end - -end -``` - -If you wish to use Rails url helpers for link generation, e.g., `link(:resources) { resources_url }`, ensure your application sets -`Rails.application.routes.default_url_options`. - -```ruby -Rails.application.routes.default_url_options = { - host: 'example.com' -} -``` diff --git a/docs/general/instrumentation.md b/docs/general/instrumentation.md deleted file mode 100644 index 560494ac..00000000 --- a/docs/general/instrumentation.md +++ /dev/null @@ -1,40 +0,0 @@ -[Back to Guides](../README.md) - -# Instrumentation - -ActiveModelSerializers uses the -[ActiveSupport::Notification API](http://guides.rubyonrails.org/active_support_instrumentation.html#subscribing-to-an-event), -which allows for subscribing to events, such as for logging. - -## Events - -Name: - -`render.active_model_serializers` - -Payload (example): - -```ruby -{ - serializer: PostSerializer, - adapter: ActiveModelSerializers::Adapter::Attributes -} -``` - -Subscribing: - -```ruby -ActiveSupport::Notifications.subscribe 'render.active_model_serializers' do |name, started, finished, unique_id, data| - # whatever -end -ActiveSupport::Notifications.subscribe 'render.active_model_serializers' do |*args| - event = ActiveSupport::Notifications::Event.new(*args) - # event.payload - # whatever -end -``` - -## [LogSubscriber](http://api.rubyonrails.org/classes/ActiveSupport/LogSubscriber.html) - -ActiveModelSerializers includes an `ActiveModelSerializers::LogSubscriber` that attaches to -`render.active_model_serializers`. diff --git a/docs/general/key_transforms.md b/docs/general/key_transforms.md deleted file mode 100644 index fd1be2d7..00000000 --- a/docs/general/key_transforms.md +++ /dev/null @@ -1,40 +0,0 @@ -[Back to Guides](../README.md) - -# Key Transforms - -Key Transforms modify the casing of keys and keys referenced in values in -serialized responses. - -Provided key transforms: - -| Option | Result | -|----|----| -| `:camel` | ExampleKey | -| `:camel_lower` | exampleKey | -| `:dash` | example-key | -| `:unaltered` | the original, unaltered key | -| `:underscore` | example_key | -| `nil` | use the adapter default | - -Key translation precedence is as follows: - -##### Adapter option - -`key_transform` is provided as an option via render. - -```render json: posts, each_serializer: PostSerializer, key_transform: :camel_lower``` - -##### Configuration option - -`key_transform` is set in `ActiveModelSerializers.config.key_transform`. - -```ActiveModelSerializers.config.key_transform = :camel_lower``` - -##### Adapter default - -Each adapter has a default transform configured: - -| Adapter | Default Key Transform | -|----|----| -| `Json` | `:unaltered` | -| `JsonApi` | `:dash` | diff --git a/docs/general/logging.md b/docs/general/logging.md deleted file mode 100644 index 321bf5d8..00000000 --- a/docs/general/logging.md +++ /dev/null @@ -1,21 +0,0 @@ -[Back to Guides](../README.md) - -# Logging - -The default logger in a Rails application will be `Rails.logger`. - -When there is no `Rails.logger`, the default logger is an instance of -`ActiveSupport::TaggedLogging` logging to STDOUT. - -You may customize the logger in an initializer, for example: - -```ruby -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) -``` diff --git a/docs/general/rendering.md b/docs/general/rendering.md deleted file mode 100644 index af2d886f..00000000 --- a/docs/general/rendering.md +++ /dev/null @@ -1,293 +0,0 @@ -[Back to Guides](../README.md) - -# Rendering - -### Implicit Serializer - -In your controllers, when you use `render :json`, Rails will now first search -for a serializer for the object and use it if available. - -```ruby -class PostsController < ApplicationController - def show - @post = Post.find(params[:id]) - - render json: @post - end -end -``` - -In this case, Rails will look for a serializer named `PostSerializer`, and if -it exists, use it to serialize the `Post`. - -### Explicit Serializer - -If you wish to use a serializer other than the default, you can explicitly pass it to the renderer. - -#### 1. For a resource: - -```ruby - render json: @post, serializer: PostPreviewSerializer -``` - -#### 2. For a resource collection: - -Specify the serializer for each resource with `each_serializer` - -```ruby -render json: @posts, each_serializer: PostPreviewSerializer -``` - -The default serializer for collections is `CollectionSerializer`. - -Specify the collection serializer with the `serializer` option. - -```ruby -render json: @posts, serializer: CollectionSerializer, each_serializer: PostPreviewSerializer -``` - -## Serializing non-ActiveRecord objects - -See [README](../../README.md#what-does-a-serializable-resource-look-like) - -## SerializableResource options - -See [README](../../README.md#activemodelserializersserializableresource) - -### adapter_opts - -#### fields - -If you are using `json` or `attributes` adapter -```ruby -render json: @user, fields: [:access_token] -``` - -See [Fields](fields.md) for more information. - -#### adapter - -This option lets you explicitly set the adapter to be used by passing a registered adapter. Your options are `:attributes`, `:json`, and `:json_api`. - -``` -ActiveModel::Serializer.config.adapter = :json_api -``` - -#### key_transform - -```render json: posts, each_serializer: PostSerializer, key_transform: :camel_lower``` - -See [Key Transforms](key_transforms.md) for more information. - -#### meta - -A `meta` member can be used to include non-standard meta-information. `meta` can -be utilized in several levels in a response. - -##### Top-level - -To set top-level `meta` in a response, specify it in the `render` call. - -```ruby -render json: @post, meta: { total: 10 } -``` - -The key can be customized using `meta_key` option. - -```ruby -render json: @post, meta: { total: 10 }, meta_key: "custom_meta" -``` - -`meta` will only be included in your response if you are using an Adapter that -supports `root`, e.g., `JsonApi` and `Json` adapters. The default adapter, -`Attributes` does not have `root`. - - -##### Resource-level - -To set resource-level `meta` in a response, define meta in a serializer with one -of the following methods: - -As a single, static string. - -```ruby -meta stuff: 'value' -``` - -As a block containing a Hash. - -```ruby -meta do - { - rating: 4, - comments_count: object.comments.count - } -end -``` - - -#### links - -If you wish to use Rails url helpers for link generation, e.g., `link(:resources) { resources_url }`, ensure your application sets -`Rails.application.routes.default_url_options`. - -##### Top-level - -JsonApi supports a [links object](http://jsonapi.org/format/#document-links) to be specified at top-level, that you can specify in the `render`: - -```ruby - links_object = { - href: "http://example.com/api/posts", - meta: { - count: 10 - } - } - render json: @posts, links: links_object -``` - -That's the result: - -```json -{ - "data": [ - { - "type": "posts", - "id": "1", - "attributes": { - "title": "JSON API is awesome!", - "body": "You should be using JSON API", - "created": "2015-05-22T14:56:29.000Z", - "updated": "2015-05-22T14:56:28.000Z" - } - } - ], - "links": { - "href": "http://example.com/api/posts", - "meta": { - "count": 10 - } - } -} -``` - -This feature is specific to JsonApi, so you have to use the use the [JsonApi Adapter](adapters.md#jsonapi) - - -##### Resource-level - -In your serializer, define each link in one of the following methods: - -As a static string - -```ruby -link :link_name, 'https://example.com/resource' -``` - -As a block to be evaluated. When using Rails, URL helpers are available. -Ensure your application sets `Rails.application.routes.default_url_options`. - -```ruby -link :link_name_ do - "https://example.com/resource/#{object.id}" -end - -link(:link_name) { "https://example.com/resource/#{object.id}" } - -link(:link_name) { resource_url(object) } - -link(:link_name) { url_for(controller: 'controller_name', action: 'index', only_path: false) } - -``` - -### serializer_opts - -#### include - -See [Adapters: Include Option](/docs/general/adapters.md#include-option). - -#### Overriding the root key - -Overriding the resource root only applies when using the JSON adapter. - -Normally, the resource root is derived from the class name of the resource being serialized. -e.g. `UserPostSerializer.new(UserPost.new)` will be serialized with the root `user_post` or `user_posts` according the adapter collection pluralization rules. - -When using the JSON adapter in your initializer (ActiveModelSerializers.config.adapter = :json), or passing in the adapter in your render call, you can specify the root by passing it as an argument to `render`. For example: - -```ruby - render json: @user_post, root: "admin_post", adapter: :json -``` - -This will be rendered as: -```json - { - "admin_post": { - "title": "how to do open source" - } - } -``` -Note: the `Attributes` adapter (default) does not include a resource root. You also will not be able to create a single top-level root if you are using the :json_api adapter. - -#### namespace - -The namespace for serializer lookup is based on the controller. - -To configure the implicit namespace, in your controller, create a before filter - -```ruby -before_action do - self.namespace_for_serializer = Api::V2 -end -``` - -`namespace` can also be passed in as a render option: - - -```ruby -@post = Post.first -render json: @post, namespace: Api::V2 -``` - -This tells the serializer lookup to check for the existence of `Api::V2::PostSerializer`, and if any relations are rendered with `@post`, they will also utilize the `Api::V2` namespace. - -The `namespace` can be any object whose namespace can be represented by string interpolation (i.e. by calling to_s) -- Module `Api::V2` -- String `'Api::V2'` -- Symbol `:'Api::V2'` - -Note that by using a string and symbol, Ruby will assume the namespace is defined at the top level. - - -#### serializer - -Specify which serializer to use if you want to use a serializer other than the default. - -For a single resource: - -```ruby -@post = Post.first -render json: @post, serializer: SpecialPostSerializer -``` - -To specify which serializer to use on individual items in a collection (i.e., an `index` action), use `each_serializer`: - -```ruby -@posts = Post.all -render json: @posts, each_serializer: SpecialPostSerializer -``` - -#### scope - -See [Serializers: Scope](/docs/general/serializers.md#scope). - -#### scope_name - -See [Serializers: Scope](/docs/general/serializers.md#scope). - -## Using a serializer without `render` - -See [Usage outside of a controller](../howto/outside_controller_use.md#serializing-before-controller-render). - -## Pagination - -See [How to add pagination links](https://github.com/rails-api/active_model_serializers/blob/master/docs/howto/add_pagination_links.md). diff --git a/docs/general/serializers.md b/docs/general/serializers.md deleted file mode 100644 index 5b23ba0f..00000000 --- a/docs/general/serializers.md +++ /dev/null @@ -1,480 +0,0 @@ -[Back to Guides](../README.md) - -# Serializers - -Given a serializer class: - -```ruby -class SomeSerializer < ActiveModel::Serializer -end -``` - -The following methods may be defined in it: - -### Attributes - -#### ::attributes - -Serialization of the resource `title` and `body` - -| In Serializer | #attributes | -|---------------------------- |-------------| -| `attributes :title, :body` | `{ title: 'Some Title', body: 'Some Body' }` -| `attributes :title, :body`
`def body "Special #{object.body}" end` | `{ title: 'Some Title', body: 'Special Some Body' }` - - -#### ::attribute - -Serialization of the resource `title` - -| In Serializer | #attributes | -|---------------------------- |-------------| -| `attribute :title` | `{ title: 'Some Title' } ` -| `attribute :title, key: :name` | `{ name: 'Some Title' } ` -| `attribute(:title) { 'A Different Title'}` | `{ title: 'A Different Title' } ` -| `attribute :title`
`def title 'A Different Title' end` | `{ title: 'A Different Title' }` - -An `if` or `unless` option can make an attribute conditional. It takes a symbol of a method name on the serializer, or a lambda literal. - -e.g. - -```ruby -attribute :private_data, if: :is_current_user? -attribute :another_private_data, if: -> { scope.admin? } - -def is_current_user? - object.id == current_user.id -end -``` - -### Associations - -The interface for associations is, generically: - -> `association_type(association_name, options, &block)` - -Where: - -- `association_type` may be `has_one`, `has_many`, `belongs_to`. -- `association_name` is a method name the serializer calls. -- optional: `options` may be: - - `key:` The name used for the serialized association. - - `serializer:` - - `if:` - - `unless:` - - `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 - - `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. - - prevents `association_name` method from being called. - - return value of block is used as the association value. - - yields the `serializer` to the block. - - `include_data false` prevents the `data` key from being rendered in the JSON API relationship. - -#### ::has_one - -e.g. - -```ruby -has_one :bio -has_one :blog, key: :site -has_one :maker, virtual_value: { id: 1 } - -has_one :blog do |serializer| - serializer.cached_blog -end - -def cached_blog - cache_store.fetch("cached_blog:#{object.updated_at}") do - Blog.find(object.blog_id) - end -end -``` - -```ruby -has_one :blog, if: :show_blog? -# you can also use a string or lambda -# has_one :blog, if: 'scope.admin?' -# has_one :blog, if: -> (serializer) { serializer.scope.admin? } -# has_one :blog, if: -> { scope.admin? } - -def show_blog? - scope.admin? -end -``` - -#### ::has_many - -e.g. - -```ruby -has_many :comments -has_many :comments, key: :reviews -has_many :comments, serializer: CommentPreviewSerializer -has_many :reviews, virtual_value: [{ id: 1 }, { id: 2 }] -has_many :comments, key: :last_comments do - last(1) -end -``` - -#### ::belongs_to - -e.g. - -```ruby -belongs_to :author, serializer: AuthorPreviewSerializer -belongs_to :author, key: :writer -belongs_to :post -belongs_to :blog -def blog - Blog.new(id: 999, name: 'Custom blog') -end -``` - -### Polymorphic Relationships - -Polymorphic relationships are serialized by specifying the relationship, like any other association. For example: - -```ruby -class PictureSerializer < ActiveModel::Serializer - has_one :imageable -end -``` - -You can specify the serializers by [overriding serializer_for](serializers.md#overriding-association-serializer-lookup). For more context about polymorphic relationships, see the [tests](../../test/adapter/polymorphic_test.rb) for each adapter. - -### Caching - -#### ::cache - -e.g. - -```ruby -cache key: 'post', expires_in: 0.1, skip_digest: true -cache expires_in: 1.day, skip_digest: true -cache key: 'writer', skip_digest: true -cache only: [:name], skip_digest: true -cache except: [:content], skip_digest: true -cache key: 'blog' -cache only: [:id] -``` - -#### #cache_key - -e.g. - -```ruby -# Uses a custom non-time-based cache key -def cache_key - "#{self.class.name.downcase}/#{self.id}" -end -``` - -### Other - -#### ::type - -When using the `:json_api` adapter, the `::type` method defines the JSONAPI [type](http://jsonapi.org/format/#document-resource-object-identification) that will be rendered for this serializer. - -When using the `:json` adapter, the `::type` method defines the name of the root element. - -It either takes a `String` or `Symbol` as parameter. - -Note: This method is useful only when using the `:json_api` or `:json` adapter. - -Examples: -```ruby -class UserProfileSerializer < ActiveModel::Serializer - type 'profile' - - attribute :name -end -class AuthorProfileSerializer < ActiveModel::Serializer - type :profile - - attribute :name -end -``` - -With the `:json_api` adapter, the previous serializers would be rendered as: - -``` json -{ - "data": { - "id": "1", - "type": "profile", - "attributes": { - "name": "Julia" - } - } -} -``` - -With the `:json` adapter, the previous serializer would be rendered as: - -``` json -{ - "profile": { - "name": "Julia" - } -} -``` - -#### ::link - -```ruby -link :self do - href "https://example.com/link_author/#{object.id}" -end -link(:author) { link_author_url(object) } -link(:link_authors) { link_authors_url } -link :other, 'https://example.com/resource' -link(:posts) { link_author_posts_url(object) } -``` - -#### #object - -The object being serialized. - -#### #root - -Resource root which is included in `JSON` adapter. As you can see at [Adapters Document](adapters.md), `Attribute` adapter (default) and `JSON API` adapter does not include root at top level. -By default, the resource root comes from the `model_name` of the serialized object's class. - -There are several ways to specify root: -* [Overriding the root key](rendering.md#overriding-the-root-key) -* [Setting `type`](serializers.md#type) -* Specifying the `root` option, e.g. `root: 'specific_name'`, during the serializer's initialization: - -```ruby -ActiveModelSerializers::SerializableResource.new(foo, root: 'bar') -``` - -#### #scope - -Allows you to include in the serializer access to an external method. - -It's intended to provide an authorization context to the serializer, so that -you may e.g. show an admin all comments on a post, else only published comments. - -- `scope` is a method on the serializer instance that comes from `options[:scope]`. It may be nil. -- `scope_name` is an option passed to the new serializer (`options[:scope_name]`). The serializer - defines a method with that name that calls the `scope`, e.g. `def current_user; scope; end`. - Note: it does not define the method if the serializer instance responds to it. - -That's a lot of words, so here's some examples: - -First, let's assume the serializer is instantiated in the controller, since that's the usual scenario. -We'll refer to the serialization context as `controller`. - -| options | `Serializer#scope` | method definition | -|-------- | ------------------|--------------------| -| `scope: current_user, scope_name: :current_user` | `current_user` | `Serializer#current_user` calls `controller.current_user` -| `scope: view_context, scope_name: :view_context` | `view_context` | `Serializer#view_context` calls `controller.view_context` - -We can take advantage of the scope to customize the objects returned based -on the current user (scope). - -For example, we can limit the posts the current user sees to those they created: - -```ruby -class PostSerializer < ActiveModel::Serializer - attributes :id, :title, :body - - # scope comments to those created_by the current user - has_many :comments do - object.comments.where(created_by: current_user) - end -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). - -##### Controller Authorization Context - -In the controller, the scope/scope_name options are equal to -the [`serialization_scope`method](https://github.com/rails-api/active_model_serializers/blob/d02cd30fe55a3ea85e1d351b6e039620903c1871/lib/action_controller/serialization.rb#L13-L20), -which is `:current_user`, by default. - -Specifically, the `scope_name` is defaulted to `:current_user`, and may be set as -`serialization_scope :view_context`. The `scope` is set to `send(scope_name)` when `scope_name` is -present and the controller responds to `scope_name`. - -Thus, in a serializer, the controller provides `current_user` as the -current authorization scope when you call `render :json`. - -**IMPORTANT**: Since the scope is set at render, you may want to customize it so that `current_user` isn't -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`. - -```diff -class SomeController < ActionController::Base -+ serialization_scope :view_context - - def current_user - User.new(id: 2, name: 'Bob', admin: true) - end - - def edit - user = User.new(id: 1, name: 'Pete') - render json: user, serializer: AdminUserSerializer, adapter: :json_api - end -end -``` - -We could then use the controller method `view_context` in our serializer, like so: - -```diff -class AdminUserSerializer < ActiveModel::Serializer - attributes :id, :name, :can_edit - - def can_edit? -+ view_context.current_user.admin? - end -end -``` - -So that when we render the `#edit` action, we'll get - -```json -{"data":{"id":"1","type":"users","attributes":{"name":"Pete","can_edit":true}}} -``` - -Where `can_edit` is `view_context.current_user.admin?` (true). - -You can also tell what to set as `serialization_scope` for specific actions. - -For example, use `admin_user` only for `Admin::PostSerializer` and `current_user` for rest. - -```ruby -class PostsController < ActionController::Base - - before_action only: :edit do - self.class.serialization_scope :admin_user - end - - def show - render json: @post, serializer: PostSerializer - end - - def edit - @post.save - render json: @post, serializer: Admin::PostSerializer - end - - private - - def admin_user - User.new(id: 2, name: 'Bob', admin: true) - end - - def current_user - User.new(id: 2, name: 'Bob', admin: false) - end -end -``` - -#### #read_attribute_for_serialization(key) - -The serialized value for a given key. e.g. `read_attribute_for_serialization(:title) #=> 'Hello World'` - -#### #links - -Allows you to modify the `links` node. By default, this node will be populated with the attributes set using the [::link](#link) method. Using `links: nil` will remove the `links` node. - -```ruby -ActiveModelSerializers::SerializableResource.new( - @post, - adapter: :json_api, - links: { - self: { - href: 'http://example.com/posts', - meta: { - stuff: 'value' - } - } - } -) -``` - -#### #json_key - -Returns the key used by the adapter as the resource root. See [root](#root) for more information. - -## Examples - -Given two models, a `Post(title: string, body: text)` and a -`Comment(name: string, body: text, post_id: integer)`, you will have two -serializers: - -```ruby -class PostSerializer < ActiveModel::Serializer - cache key: 'posts', expires_in: 3.hours - attributes :title, :body - - has_many :comments -end -``` - -and - -```ruby -class CommentSerializer < ActiveModel::Serializer - attributes :name, :body - - belongs_to :post -end -``` - -Generally speaking, you, as a user of ActiveModelSerializers, will write (or generate) these -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) - -## Overriding association methods - -To override an association, call `has_many`, `has_one` or `belongs_to` with a block: - -```ruby -class PostSerializer < ActiveModel::Serializer - has_many :comments do - object.comments.active - end -end -``` - -## Overriding attribute methods - -To override an attribute, call `attribute` with a block: - -```ruby -class PostSerializer < ActiveModel::Serializer - attribute :body do - object.body.downcase - end -end -``` - -## Overriding association serializer lookup - -If you want to define a specific serializer lookup for your associations, you can override -the `ActiveModel::Serializer.serializer_for` method to return a serializer class based on defined conditions. - -```ruby -class MySerializer < ActiveModel::Serializer - def self.serializer_for(model, options) - return SparseAdminSerializer if model.class == 'Admin' - super - end - - # the rest of the serializer -end -``` diff --git a/docs/how-open-source-maintained.jpg b/docs/how-open-source-maintained.jpg deleted file mode 100644 index 7e4fa53a74f6e5e217737aa9d54bc8c23fceb709..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282768 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 z 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%0AWTQ`iir}1ut_{fmX70 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%0Dt#Mm$3)jeHq;qd!$T2%s6^!r!FGgZn+ zJu;DKaCEs67oo1|@ZBzgxw{)_^=XUh7P^w`$R0QczI{dx=hUNZS6 zC4DC!-FuN-=Rea6l4IE-u&XmLvT|U^5Bso{xU(stQNE#$FI0CcwsL=%=*q8Y zuzO1R@W$@;OnC6%Ubw47wE}y>V^?{-*^jT%p2vcZ6eGrrJnTA&vfxtt_f%=qr1=E? zp7trfB2Vr>Sy*D2a@s@ZME6Mm4-M1M(2#W?Z27YTjqh%X zkH)iQC&X7ObnAFa_^`6gXNJ$wABHusg!F5c8~p;(1Leu-&*q; zLre$T(;eZIba00AfvliP)Ohm#2^uo?nn?ppb6I=bfC1Hhoi@4}4sKWBlA%(hG7c-+ zTJ~ma)|{1uE0t+-tGda(U%QcCf!9712@ifGEPoUi@>Rr!7QxpA^T5uD16 zLN;2~@V7>x(_e>`L0wWOAN(+KN1woIQLaH|rs$8!o!dP^y6Xa9rgzkSj$W9!vucB2 z=Nh5ihSl*`aP!En2>PS$(5OJC+PzNWHtQ#e8J#AR{OA+3zK($>Ac-yg>GlMy2G!|0 zlSrl}*oiw!h8ZMeYX#S`p!>j#h2mT)5`KWpG3H=oa@Ys%PpIJU#t9y$zyQbU2IvFc z{KMOma&o_tJKr1h;#9JWTI(dVDj%7@5-JxZKWBMl4obp8jZOCxd&;Lt@PcRj$e7`J zu^G1fe7{?0<|=IDby5O5(L??;NY^1BM`gCDQhE6-C;NpqhWSRvdIW6#aaFkX|B7FvZYc*C+|CG*rbi>=MJ8UxZiuFym5y$D^h%XCHgL4i0?3#cW zu4@o%TmH26W@th*ktPm4&S|>)o(nVY;SjgfLIdl_fycC6mlOt%0Qf2vY)xbAObqw4zwjY>;&sk}^+jdo?B`h@!xOy?(dqaI11iXkbpI&lwCu^-6 zAmx+08T=UZjZ}n7K`c+g<1C!+0vEW>lXkrU-W$((T!;@>c`Q7voXdRv)7{d)sVb^0 zuF_9?-0j3DBr-O8o+u10{%%gV=!G(K=61z=QHEF05IV8|GabXj(?}qqK>q{qsMr*G zKcbXhA;K(?@+ALN@bQ106u_Wqd7O}*yE za*xwrzW)Gt=GGeiFYa*DsT1T?HRYc=M(M8U9If{-dQj1{QrqB?ZN47k!=*8Mei?WN z%7tDbe+6n@Q~s`8nQU%M%);<`i)MpW+VrQ5nLZg7#hsm95<2_Gyl#Umm4F->(|Rkd zDij1>#=e}H7!JwwD%2{M3A5PJC?>EW#@~)tu!?mAzQxI8X{|89+mV3goM3q}%}hUV zeW=AUX^?z~G_{$_HF*{qmXpuDX$A&@TASIU#9q@zjjeFJSK)xVm;oO2qfxZ|ttqby z0m9zvNjLLk2RwtFi2Urs&|;Ow?Dm~7;hufbj4;G7q7`Poa==quQN>4J&WS-CINzp8Q=8Sv>QY+I zmH#fD(Xfpx=fo$-G@9J|ew$5=OL;;jH|_t~V4dpJc;I|EYQTpqric+Tu44&y+8JDA zM|7Czl>Cv)?JzEu`lMZF=kwqZ$ct;0cfAOUqmbi$r)E#4iG7Se*@mf<7iC0tYDK08 zsIPq@Q3FCsBfotd{L!op-@op0vXHFk-B_O-0X%@;JYJlG0?pYr`+8DKCwGp%1Wso|oRj1R_)WcNdcbT%vNs z1{&f+cYGoHwas-(`C)}2;tdR@CFT+Q3him)k>0DS^7Q$SS5s2D3`ku+jI!tiQph6v z!{aL3m({B;B@jOvD5XxXFyfvQUP2{iv8;>vy(?(GYt|H<(uA4+*r~AZIi*8%J?S z>5~}HZs0?vqmOvCf(vcx?Mer0mqL?TnME{v5vR}MbAyRs>eM?ZXJcgWv7iW{7QjRq ze50lYHnS^rEYM2cZ%rC%&sDIS3Qs{hhvfOZ zVWk)>5yCh|j^-$E*=1LFx};8mn|!}t=8Do!WCqrG-YAi~sln{vDmt0kDWZNhK1vTw zY=T3E!skI1JXU0?LuTesvp8A%N}k-ua8&w?TzBEyX2!EN6W+6y9P|0@C*fWwNKV>j zqEtKe`a{*A8K>3K(GDS1dtrMH9oOFHNpt6+k#q9=+~0qGYKSKm5b?5GxB5%RrMUhM zem3b+=~usNXhP3_;=%zc+^g#Y`W?56pAd1oROnrtYnw#Ov4@p7&+PQV8hHfo?*qn= z`p5RO))%^xymk&XCsROWHW@L7XFmQ&a)KQ1HDPbjv)eyY(YV99mnw*}+byow%-H@- z7;2rb)%RNGg~y3nBLZj=02Rs5tfW3tFVcgzUZ zmk%s$^UyA}sTaRjo_WdL3FGXE1gUDMkjkOdAIX+Z3W{Xd!#?u}uwI_Y6foAYUad~T zGs>Y}Hp?Jx^2XD+e1vYHoP}Rln7wYCiUEBf^Vy<}OdJqyUJxmv3(`l!mw|Bkbp-k+ zHf1HK5B>Dt@Q*hd24eh(au-?tEy2zwL#~JW;0otA3G8CTp9EnSzMq<2{k3HcA=`JL*!MY_TzT+M9mALt>~OMBrY z{+FUF71=s`Z!9(fBss{j#o;fnkux-UI0nDsLJJr>tMI^Wtl@nT5ShpYONnB~NT^x6 ziiwZZ$A&{XV5rBYi2(r%V3tHvLJ4W1p^$aw7-WiFZFKpY0GO?qv%e7B? zC9q!gt@E-%?6oF68%;5f_XkXL&>%cn;r6b2{Gow{V24-6Z>c3$6=_fI3L~I1TW5wA z%Q~hq-5G#|m>vM=`TMtM;l5X5u!7=My#{VcV-SSjS@@dqiZJs}+G!qc3JrWJkR{3} zIuT?hpjkjLym6^Ndt_bi^U{^y z1-)cHcJXa0b%M~R_+eOH3-g2vZ9j;(?rdUzd)(FB=zvHZAIvlldQTel(~+^dF1r|! zEmI6+iV=*qqGYI9z&$G-#rDXhGUF{LOsJmhntyW0Ov+7S(Yv*y@4swTnT4q5>1%k) z@UYHg$xrZQR&x=z6`aLD{&3zxdtwhBg$&yHj-v8|q>)Q0qxIMIab(J`^rj{Pi0)$p zJGN=P%IpSoQLlhSA;~~6d7(<%E8ytwP|SED!JU5$aSr8atkEsuJPTt``;-ywn#P}? zdX=RGBD`EA+dio-4Wom~GvfViUJT{w6&Pd;&-AYWaB_?snvc9{6w8`lbq0F?m7c=8 z`m3~IWAsV${`_q`xf$KaQL~^0h=I_l7PZC0XM!sp-WRu+yp#1ay?YrSVj)eVJl01N zs_9)WpNzE@{@PY|IE&1|rEb@~CNi)g^SxeS3?6d%w>Ou=^x}>slvLaI*qq{qV$Jx{ zh98PR#d5w1mvSyb9=9kPzc{4Id@>Z3m|2&34 zYmo@sfO|eC(O7DQ=7do@9Yt?Bt(|%0=33x0VcJE0K@)kM`bBFqPiIh-O5*>5r zk4(`zW^aSK;&^x_o;4tHn$E>s1mgJ}o*Qk?H3psHa5p~P;%bZgk{14d)xTD!GP7>< zBD5(O=~@OX#$hh%Dd*1 z_RLr%3yHI;TgFOZbYkv9B_&2AkZE=Jo$WG40?P3UO8glDv#&~n3EexlU12lsiBcbm z<-`bUT4=2tpLahp}{O+mJ&vlec(U6400)m0f;ciTX&5s^FE zdb;PoKDg@!PQ%nLR8si>zJ{6SWjp@p?5W@9h|UK* zPZfa$xh_eE>;{(N_PcX4(+#e~WKGF~K6zCeKx!CcOp<=o)TL&NN&EVyYhsghEF2s# zp>VYitvg$2&-4qwB4pgbi_V+mF_O7cD~~8A-=TrM6WP9p3lxDpzlrIM9OZy2K#1hD zX$F5%_doYzk)b~QBL?kEygUce9YzhW4wmxMr~dA4cq_|#{sq%O?iF+^ugs&k^y z6Osc-?Hx|Vs7DsQD?{{kKd8A#P8M2jMYcOFjByc+jyfgC*gcIl;R|?~Kb=ZE(tsP6)m#V4U(&0@(kN`PJgLZSN#lJgw{- zg=~wUTuX06tQ(%DTQ4qbw?;x00y?Dm>?Lp(N9DGNh$HOWQ?Ux zcE}ksJP|l1=0)n*WS;A^MRSN>Ax--#f&Zv`?Sl?KaoX@V1sLJCxYx+$h-R7hI)KL< zq~|#1-uiq}KXqq3+><71@1jjI;hZS%sx8y@2e8um%%|6p+fc4c4@;=E==wv!W8Q(m zW)_3@zWtZtU?A`3O_xY$r5(ZX->d>B6tA7?2Fx1$^lV!N`4J+s)^m4EpnbL*W!1+( zC1&`3v^01RTF+lu`wHUzHl2Q;C!6DfX(Mq^mimd-$gjjK`!VBwrpei(y^u6evc+jH zvEix5MsLS*YHZWU)V%!I6q)!Wd&N}q{E;3G4w9s?m%B-*wey$nkPVC6DeWC%ic%E) z!yR~)iQ!ZLzixhJzR}d+4x0yg^_jJGVdttoPubZ-xL|Syk2BRAh72c7#){#BY@g1_ zl`b)JthfC%$?-d+_gOuM{+g4O6p4k-Xx*zE%ZwFfoNTi%^l@2_r$AU)4FbsEzp}ORNMj{Qz?>Y6Agv{p@vdy59fA`d z#2D2hy>5-Z5NevO;Wli2?hihSI(!$C%FcAI!>O3#9>#Ukg&to5f1hz`9(lO-FgT?; zkJ8aTyjNSRGDfp*KcUZY(T@r)D#jAz6#(@uxpk?QXKD*NZ*=?B%lAhRYGoE*hz0qh_ZVTCRxW)gUC* zEi8J5GokxVhi7Z1R))2<5OB()4zS*FNF(nB6%vX&YDWO1Y*uml;or5E=!`V*DM z@-|^aV_m@CURMx6=C;B=+4jhV`Ok7Bkpm}T+v~^PSSO}Kkve}q6iOo+C}BRvVjfod)D=Nu7rZ`zn@wb=1bTDQRbuJff#A3=@S zna$e>8;#1Ubh1A4XkwKK;JQqnBCty5>xKnU0(w0^5G+jCFNxvlJndC~Ng*~aM4W$C zDU>eArhNQ;YgyUF-@J+Q%zNJ)ZrFXQBQ7O{;1@WNfRr>uE>46?=oHbUZ|J;6Yngvz zq;9*q56foVR!;!BEHhf#=qASlWKiK>_hMrQ(hfhH{ehUOXLKkyk*CDrYt0dS8SW=9ykwHij6*)>|TELi}E55*w9S(PEI!npUQ7%-b2-X{cI^7%UkNc zSXM|F9}hS4{Ut)q)8lu{FOr7Nb-fZw4i*{9PJ=CO4I)F*e$?Fcn>Kb`065=Kul59{ zE@Au`-pOH}7`5IK-r9 zECQsdnH|t1IJeRD6CAqR=%7*TA`mL+S?nwJn32uArBIz)+Q)7tRspT^B;f@mXM0eoF7S6@1Bp)4lka! z7a=*10Id}j+R5v0jsS6{RNFk+v;P2Y(7b9-YyCecYGeYp?irE47{o);d6lVTNj5eT zfE&ZrCG#~5*AeI_ci3s=1x_wrbI!N0Z;sZ=Wdc*UJ)7`CZdROct3oT@a*dv5Y0HXa z$Sb-U%3?P%a?=Et>*W{!?XzsHSR_N1oW!~gjk2!DC+VGg&VGd$2lW~UJFJQtwt6Q( zIu5Sc4M@;#6nsp^yg`iqU$yV_uM#%!aS+Up2PHM+WO}>197CoQ5u~eB=GUY(SWMsT zJPmE3rN^KKN%|bCwzy=cz30L2z}!7z4$NEejdk z?G>G2Uy?S;nWdrx*jW3l{ElkJ=$BKD(ym#tUFQVkhZNbbULL4?AOyx#CgNbRVtETx zpZz2%a0i9L_Im{G07PU6Q88ZhhS7Rhe>NVc6cz@2r<>Y5@qIyW^0yl3C-4vS0!yN%}n2jx<5?Gv!uPg ziI~Z#iX0^`vV05vTIp38_7%0W`!ji&)_0#&xm;8G=tz*^q&WhX_p|eX4a7~-p;l|V z+A}YT&kTQQZ{(E!ho(r?z9UV(;f_H2T(@B-kJPTx3doL z3EP|Jmcq3qkCfDZzF!pmtq`ij4=r|j7aq9W(?93euPM0mE5s~uiJSnsrbedzs61la zK2a#MPl0i2Wr5rRb&pJT5l!lrHB5U`n|lUltKwiK4swh-AS1AzUc-J(uxZ*%Rm9z# z&*k$2@Z~GhSfuIgl=E;twnbrtUq`YF9dm>KsfQ7sT563~hFxvFWN$d;;e?58Wob8S zEOpYM&hGA}acq?1#7d0je!cXtK(U8{WZPLuy z7d%#N1tzE&b9vNNM3}}j$;!T&8e3_iV&0ZA$GEVb2O@lMeI?VH9|p+L(p>gsM;Q+E z-4|6%!Z95w2#aUTb?zcu(`?=woY!jZ>&?i)9mmT+WoT|_!CptpCZ25rfA+#RFi8Wn zKQ6Q~k!do{Moa`e7*uH5CdLyenVn$qW#Kr_PxP56L$mFlT>NS_!uvVM|mNGIE zyMq^?d*9F^)6WR1ea2Rzr0Jmb-*gyP|aUP*H#64-jLSs`)4s+fCmTp_IyD zCJ{FaA2VR@&)Y_q59i1x1i;;Ohv0sXp5mdmQw|mMqhNcHeI6(dh9;3TjpqGK>(%w& z*C)!NB0XqJMjCHThOw^PwS~-grZxr{(M)mE;PO{zgFoX;^j&b(?&G02xs2}`J!K?1 zbgnc8dd!?P_r|noXjC(XlZ?&(j5HUD)jm|+mbCEt0etypnu{VlE63J5{yC;!WZ$C9 zv7h)PmzC0kww-y>-Li&kgB#I|K2Znq7g$6xy{;@n2^TJQwl()*c&nb`}7smO+w(2s?Y(vmcHO=Rv{Fj5-e^VAdhv<|5!Yxtv<`vXa`xQaw z2gn^ZC8B-+;rGcpm+zuUumS`sCFxzwrbLx+0P-*Z?gijRy|v*gq4>|#=(I6VFsPq( z`~JZTmIU%0&|dZ6qO{_v!Uw`QMX*a};!(p?ZU=R7m#U|OmC--5wfz2rEwRgx4pTl9 zas23kB_5sh!L{TF)Xzpm+tI&nmOqohKR}rY{pS$;#(i;FAca#nd|c1bOut-f4x0@P ztlxEYKejz0wHXNo#jtTqGdYA<8M_#e72Ys+*YYX^!9(?B1jqKgm_#=?C@B|%_Fx+H z9BIQRH_HMGqdn}?6HKS9QC^5OcDI#^(olqKAU_O%+IU{VV%4qaIH<{zqr}=a&vw6P3`x@NM z!nwbYCG@Qn#!e(2<3iJDU0$CIuxLI&0l;yFW#;5`wO_T0K;r1k)e;D>=p5iCwv5U^ zJ5@tz<(9jkpICXD*!ePw$oYMW84%@>On+1TV}s{OZ=dZrVV={Q-yJc$V2g2H<2khU zmf8un_stE>7TZtqF4<1m$-XpC{TS_MXBfARYoEn-23rj{(z*{V_G~QQX6+86_*W$3&+TO zmTN$Q2#R##g8y#;nY*?rT)4Pzy^d?~wM&V6_w0gd$UKdjS|`fA)VV71{d-*ob`)Sj z9?n@1+424A8rOsxMbQ5HFhriW7g?$Q!~s@V?a6)=S}L0m+nPN0m(h`lp!Y<`F5EDe zin|Xxs?qgxWA1z%b0M?ef1)^1$~h7j&$c|-W0>{tP3Gv_aKLlCwsmYk!d{84Ak@<| z;hRjnemBidVSgSc(}#5@55;u0rLL#NyNH3N(Z(rrSHkp$-xP={D#oh54hWgVL{vd( zoB%eN-#Do1Joeb)Eow8gG4(f8WD8?i^i|~NQD~>F5z^%6;Td?ab>QHzBN1u^`aU+A zU;Gd@IKIycJcWs$+)F@Cu{A^QGvezFs@Zy`O`5sPjqu0pT!Fk=43vg9t4|=@v+&e9 z-V}GLd@!dBH@%+PPPaCU!J(k|r!0%5V#L5>vFiE;NVIT=3By#aA@?}hmCUDi*O|>! zD5Py!uTG9r{7ey8gpvD_Y`ljhCfyKZJPClf70 z*ALW!ElU#$y6X_&B3YLK0lvCe65OUfWkbW&Lm(0N#DL<#j{eWatW%L)Ki3jIKKTl^ zboVAbS7TLMf9AwAMoN+3?Xn@)DUpLw+9*U~u^jZ@pnBAD>vy>|9=#_TjXru-E`f|v z0c|palWSqyc-I@Xowe~R`dcRufyoDjc)yfBzwq1}EtNU2uGM%^c2LG}l_y=@1e*HW zwUVv8mV)C~o>w5vbK0qaZa=ZwTL3t4n{BqS9n2ixGSexE^0OGaxe_};+nYe@o>^n0ON5U*mL0N%T-f@K2W|X=J?XX-pBgwYwxqHEXh5G z&Oq`60vEBcQvaB3VZg5IOY1}srN@sMJ_QDpRvJGN&F_>(de>HY%KrA5pVzZo^UVm*4M%n;2m20sT;HyP2|ITa zc0NAxy751&`LXgVnBRsU-z-YG*bh<{Kh z4@cpdke`i!Bb;aY)t_}Mc=QrmZO*LEEPxRL(#M6)CUjxH42OFz3t8x|V!c1prC{0C zAoUk)2GzW&64b(=X;>b~?@$968^4bp)Ap^Vc3ga-lT<3y2V`7TGQi7`CpUJoh58^C zgQ-8#Zmq4<4Q-!L?=JtX%Dn|Ngj}0SaVZ`|Bf|xn-@P`{p{AYklG1JxFnC$F4Ybm zoOA5OnFJxy(Ol5&1en~zY3$oESH4S>jj6M^RuRKG?Yj5vzWy3%LZ=VV0AQVC|vGDdn2J_;obUSSO z%W3+4>HmkYvj~c_i~e-u?hcK+ySvjk1b2698VwTM8+Qxt?jF4H;O-hU!2-m-;XgI2 zshXPAt#|h>?&6$tf9HArb(CM}?Kn8WAyy+4jn0IlQ(&L-IJNu0l;&0U9zzz3`9AU^ zZGMRNh6FN0{~%4}X0Xw9kzrhIOrgge+35D$P#T!R(hM z*B7g7jhbjbU#=q#V&wh(iN-&@Ab-e=lsPcVh+W2}5EW`(OvHb?1`3}zS7Dwd}pQP`wD$|%)5IySOS9xgWxKN*9)tK?D`MxwM z6j*JTKhNY>pzn*ioil0XOhoF6cVkQ#Pj)ipM|Z32kxD{YOP4-FtV(R=GK5^h&~1IMXke?+J4(QCSYWzI%8`6s zs>_ZdmWG!z`+I!6N`-L2kXV&Ef5lGKPEu0FBuM*^W5$R^{pqWPa1!VTu7UPo=@5s6 zaQ)TFY_$@>T)nwxo#c4iP zP)VFv$m_4g1+eMElrimqorTOop3(=lEtkb~hl1D&rsF}=8iUJ5$E3th%>&W(dRvh% zPyoe2Vxe1)I2W+qrC`HJ6?H@ts6T#(|$EI8LycJ{w0D1F=WS za}B9yt0FoKb9B|zOhwU9U*leum%nZ)Dk`>vX_;A>Zu?Qconzti-5p2=-zwy{J8a>j zt?I}VzLS^Y!kp@Csp+G@5f?r04mprG0Kr*DQ$Cl-Sx^yEJgIHppZf=g-u8)Vmxvz9 zlb(AzpL(?F7oOd*8v`B&d>&ucB=V%bb)Q)}Q!qcmFwuAgYThlGFN5?(deQb~{oDuA`RZwhoW6HI1oY<~4H( z>ytTQy0z0+A(DyFj@lREVq0$VFm=TXgv`Tnj#dMNHmIWeoGm#WmK_}`!QSQ!%0dPo z%dt8I8IrYGbs`2|H{$=~p3F;in@x_%oSxG-q7V*2_1;BrNhIa(&NFRm0+yX?wfoyNu9fiCXWCRDbVzfQaYF^%Akk4(FY_r zh`k_gWK8rkcRi`H>Bl)BApyle7_J3#I89xy%NQk#{RDD~+WSw)JGg7k8V<{`-#{0p zlN`|xXRCjOORz$W>MF3yu6kP6x**AXS>&V8xQ2VMBDB3q&Eacomn8P6<%ZuA(?1So zfR;9LICBBBFz9}tV;5Oj68_9-?6;NWfROWuqj?2|0gubmEV!iBi*{}W?=<$RorORM zlT5P&BPX-C<}3lnEpL9;TU2eGkq#;*-b58scxWKR9hw568lpt+pC4g{17103MnAF= z8yk1+`%>3IDy3iw0FkKC-e@&Gh#~8tc`j6HRRpGuCz;&c5{Wzx+L`gwE9$HgAS{7% z@TtSo&C(mk%&%mt%|-Fza{&Nj;9kNLtSH@Q3z{3>cYGtbg?bz>>I3{!)1UyuMv&C| zMhjZT@h`@bnaxWaAS#O9$xYhVyiDcIK%3Yv2378+7K*)I(fgp&Ht@&^&m}3w~=LZrpI#jMU=K z>;=1}`W@OmHP<_}UN<`D^kS5tsM$Q-<}I zR@>Lz)@Z18kU0Csii}*z$_ymwGkl0dB7c-jvMd}Lx5uAE%`*@txT`FvG`;ro5+wQ5>Hr<)k9gh}iDpUOv;IWCR5jEgHU z^5h$`j9d5~*9gIPwJ^EW1@rBTe~QFY;&?clB1drYOX_C{eE03`bBJb8&R^pPi;g9l z^85#&=rxQ`Ki+K8ttMN??=sA_G5c%P%rR->ivi7UM8lDaw~21!rov5zON+MDI@&RB zGHO@HyLv$0n@9LDitH=|s=53{zx5 zmj651QN-DYa;lgrln9>V+oiHbr(0plY{sA*x_bHN8NdtkVtn|ZELBl|oNG$99c@V` zEYs~foRz+g9$G`dF_~HtrPY&>5_7;W^S~SL-(=JL_7qn`S?M8U!YC*VSopGx#A1`` zDpqhpC-p0BhteZKj{`U5V-uPHJ$&2`9uv#Li4SRyg5pgUZX+VEyKhqqG8fyAj-agk z6xTO3j;)JtGj-|J&zjC`b#6Ht{HS#-q@{No0&fN2`(R7S0fhk*Sxm%)R7{<4W*OJn zaB;T2@2E2F9i-+oSUydVH}>4ZLHiQCxi)LcRHOBp=ZVS<3xklSPYs#gt_cs=(S{g# zjC(Ahk3urj@uBp{$CT&@O=jQJ)BCybBD=kJA|F}|NmzJO-cHOr9$an{4rwYJ3%QjG z(_Mo+uNjjWIG)IIS#~7ZTA5giFvPFq(N0r1`jwIC4Uzen0X(E5gI7Sf+RI&L9K3xz z3QKWPTKd=0bis-A0e1&~A*eGEO{AGqiTi8B_JyHe0ft%_6gK_~X^x(s+)tcEsuelx zXrZ|+I4pK{&5xmeYD9gqIPe67b~Tso-Qu}tScwDtDVaEq-49`<;0j;yM$(r-A^^bc zgt%;&NS^1gmkmPiHA%6RlbYdf=ax;tWce;^@L4er7+tZ$=R*?X4$d{A8mK7NrNfOl z)Js>&_xuz!U<5{Ct2u*gW&p2hgMPKpsqtp?)Q2>FIN+odNbZih%xmMDLoy6jVZvv| zK->~Jr{G@q{`yfoEs}6QH&HBJQ)N(LF!V*$7I#7vQPqYN^8-pp70Vqwbta;K;^^yZ zzwT>w#+&4%=n|tPm1$=8VrI3My)|I@wRqw#U%V$;zGiIB_39CQJ zt@>wSU!Se~v<%SrQh%u&issAMP65Oh`XLgFZa?~AHdH@HiZ4WFGfRf z9+_u-U}ggV=gzkn;o_p9gkUq4LMKh6_-{U=&ewv%O2y@$B`F&^;5Is5keUyIhl?Hs z^WA7$V$pMsFVJsFMmn!0jx4_m?q<3w%$o(f>P#ri|3rd7clgA|@5mb&8rJ#71G<)Q zd;>0-;`;dz?H1fu!-14U+A20gI&zc(3py!BYL--2frb1E

un5P$n-ldo^3@0QK- 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Ξ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

ag6mPO8>yTtPJt&#GLO~_Vn2@%Y9no#N}%3xGl zJz{K@2y|v2^@CuVZu1jwd zE7!ys%t$z%~5;KA9_Yc@j==w znO~gm7Lh%&=By6_G$4-aT;>W6u#1k0CFa(cfVpF1Rdq-Y2q~-$y419`&8qS1F10#& zIhvURjxWEaV1U}aeA-i5I@tAwI8No6tvXQ`X12!tn+mo089f#j#UxF~xv~kKosfsF zb8e5Ii`>W16$*)ytH|>txXZ-Z)Zw*pvx6v=&Z648ufys2)eluh&c*))1WY{)rmfmd zw%NC?spnO7MiuawCu|U+im8QN{Jx7#-J+udZdtLg z3WGHMQI#qz*j3R4(-Ac_$z2|M>pUOqB_r0g%uaVxN1Ng92UB1s8e6D8@OxE5Ca9qQ zvvRNHQPBvp##Q>XAMNfkXtnk<7tctU?S!=2I_BR(VgF*Q3<-}WH! z%Dwz?O76P~{&sOwyk}?SCa9W1QEnuyJS`<5j_Mx(iN((lPNv5PAWCa8%=__k9GYiu zl2WGU`kY2fFlSn9y26yqq?}Tb#-(s$3hE&aW%LEWv)RBRNk+PCpXB7Yvu~cJl=$J(I^`eY zT1DQm(?u)wGWMGkka-^mluF1 z%ER#>Y6|rII%y9##+NZ~fc)LL)4cp!bK)r*Ma<{71Njp;_g2By;s0a9`)7{xe}4Y| z?<4Jy=;X~CYIl|3t?#@PKSl%Wfy=6MyWQo}Wo&(Wz2qLf_I&1F{@btqe~i6VP+VcR zuG_e~)3|$ZcZcB6I0R@QSmW;Q?jGFTHNoB8-8Ddf03kX2YwuO7&c&&9=4Ds)?7JS{ zH^wub_kTW=IZ{5v9sb)*a&W!de4&E_zu)i_{?(&x|LF>1+aveR9dP0mojou5{tuuz zgS%hcqCt=%ZC5yvwGuw5W`lDa4d&m`C^xYZciEs9FOj8`0dxncuI>PcAA-K6zaFsZ z_Fs9N>TX$1tV>aB(+bI*Xv!I}x|Ei5iSNTD@-@1GqysohIK&#a>m>@SNMvw*1iwn3 z%7|mN?-`5;{XT8~q2s z*%BRC_6*)bZ9rSg?~P$0N$%&pxP|t@@~Izei!EPe*Z!PkFC{8NWKmR9t>Hkfl+-kk z*{R@=!Jm1eR>j(pS&DsK3zq#6Z%o$`t%D;}2L`fNIu`tCW7X!^1k%pJ>XaT9V<=Ggtp~&QxU` z{tn}ki%jabEoY1)p`~kJi~o(b$NAyJC?xeH-x4)$(l7z`7!I+h4k$JABXSh(T{kj7 zx)R*cl7#ji1%NEJ8v$lq?O7N4A7yIXYecxw18nn;)2>wdJKz&k(@eVX)lw207OfGH zUDUCX&m-NSXmapxWBxAt`Ng$kHJT-*rr+b)-h&hJ8o{so{xtJmT%JkvYhi9tltqC4 z_pR$Jo~ZXliI3vp{>j!`_3J59VW_?P9|ZfIy|&k`n*U3dGJ{g=$Qh>j=|7aRk?B9K<7>uk;Fc z=tV`qIbp;RCYdAeNT!8ZvC=O>y8~~{TY$Et!!jrh7*(}(7Uyw4)Y;?T-BuvrpS4+J zKj$}Fp6dvFJ|%dtZ00_wM9+D!eaiFN^Bf(t|9onB3C+d-hPkn|#K(?%d4vL^vE;O< z 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∋537s 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

0a}50e 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 }, does not work in 0.10 -``` - -- No default serializer when serializer doesn't exist -- `@options` changed to `instance_options` -- Nested relationships are no longer walked by default. Use the `:include` option at **controller `render`** level to specify what relationships to walk. E.g. `render json: @post, include: {comments: :author}` if you want the `author` relationship walked, otherwise the json would only include the post with comments. See: https://github.com/rails-api/active_model_serializers/pull/1127 -- To emulate `0.8`'s walking of arbitrarily deep relationships use: `include: '**'`. E.g. `render json: @post, include: '**'` - -## Steps to migrate - -### 1. Upgrade the `active_model_serializer` gem in you `Gemfile` -Change to `gem 'active_model_serializers', '~> 0.10'` and run `bundle install` - -### 2. Add `ActiveModel::V08::Serializer` - -```ruby -module ActiveModel - module V08 - class Serializer < ActiveModel::Serializer - include Rails.application.routes.url_helpers - - # AMS 0.8 would delegate method calls from within the serializer to the - # object. - def method_missing(*args) - method = args.first - read_attribute_for_serialization(method) - end - - alias_method :options, :instance_options - - # Since attributes could be read from the `object` via `method_missing`, - # the `try` method did not behave as before. This patches `try` with the - # original implementation plus the addition of - # ` || object.respond_to?(a.first, true)` to check if the object responded to - # the given method. - def try(*a, &b) - if a.empty? || respond_to?(a.first, true) || object.respond_to?(a.first, true) - try!(*a, &b) - end - end - - # AMS 0.8 would return nil if the serializer was initialized with a nil - # resource. - def serializable_hash(adapter_options = nil, - options = {}, - adapter_instance = - self.class.serialization_adapter_instance) - object.nil? ? nil : super - end - end - end -end - -``` -Add this class to your app however you see fit. This is the class that your existing serializers -that inherit from `ActiveModel::Serializer` should inherit from. - -### 3. Add `ActiveModel::V08::CollectionSerializer` -```ruby -module ActiveModel - module V08 - class CollectionSerializer < ActiveModel::Serializer::CollectionSerializer - # In AMS 0.8, passing an ArraySerializer instance with a `root` option - # properly nested the serialized resources within the given root. - # Ex. - # - # class MyController < ActionController::Base - # def index - # render json: ActiveModel::Serializer::ArraySerializer - # .new(resources, root: "resources") - # end - # end - # - # Produced - # - # { - # "resources": [ - # , - # ... - # ] - # } - def as_json(options = {}) - if root - { - root => super - } - else - super - end - end - - # AMS 0.8 used `DefaultSerializer` if it couldn't find a serializer for - # the given resource. When not using an adapter, this is not true in - # `0.10` - def serializer_from_resource(resource, serializer_context_class, options) - serializer_class = - options.fetch(:serializer) { serializer_context_class.serializer_for(resource) } - - if serializer_class.nil? # rubocop:disable Style/GuardClause - DefaultSerializer.new(resource, options) - else - serializer_class.new(resource, options.except(:serializer)) - end - end - - class DefaultSerializer - attr_reader :object, :options - - def initialize(object, options={}) - @object, @options = object, options - end - - def serializable_hash - @object.as_json(@options) - end - end - end - end -end -``` -Add this class to your app however you see fit. This is the class that existing uses of -`ActiveModel::ArraySerializer` should be changed to use. - -### 4. Add `ActiveModelSerializers::Adapter::V08Adapter` -```ruby -module ActiveModelSerializers - module Adapter - class V08Adapter < ActiveModelSerializers::Adapter::Base - def serializable_hash(options = nil) - options ||= {} - - if serializer.respond_to?(:each) - if serializer.root - delegate_to_json_adapter(options) - else - serializable_hash_for_collection(options) - end - else - serializable_hash_for_single_resource(options) - end - end - - def serializable_hash_for_collection(options) - serializer.map do |s| - V08Adapter.new(s, instance_options) - .serializable_hash(options) - end - end - - def serializable_hash_for_single_resource(options) - if serializer.object.is_a?(ActiveModel::Serializer) - # It is recommended that you add some logging here to indicate - # places that should get converted to eventually allow for this - # adapter to get removed. - @serializer = serializer.object - end - - if serializer.root - delegate_to_json_adapter(options) - else - options = serialization_options(options) - serializer.serializable_hash(instance_options, options, self) - end - end - - def delegate_to_json_adapter(options) - ActiveModelSerializers::Adapter::Json - .new(serializer, instance_options) - .serializable_hash(options) - end - end - end -end -``` -Add this class to your app however you see fit. - -Add -```ruby -ActiveModelSerializers.config.adapter = - ActiveModelSerializers::Adapter::V08Adapter -``` -to `config/active_model_serializer.rb` to configure AMS to use this -class as the default adapter. - -### 5. Change inheritors of `ActiveModel::Serializer` to inherit from `ActiveModel::V08::Serializer` -Simple find/replace - -### 6. Remove `private` keyword from serializers -Simple find/replace. This is required to allow the `ActiveModel::V08::Serializer` -to have proper access to the methods defined in the serializer. - -You may be able to change the `private` to `protected`, but this is hasn't been tested yet. - -### 7. Remove references to `ActiveRecord::Base#active_model_serializer` -This method is no longer supported in `0.10`. - -`0.10` does a good job of discovering serializers for `ActiveRecord` objects. - -### 8. Rename `ActiveModel::ArraySerializer` to `ActiveModel::V08::CollectionSerializer` -Find/replace uses of `ActiveModel::ArraySerializer` with `ActiveModel::V08::CollectionSerializer`. - -Also, be sure to change the `each_serializer` keyword to `serializer` when calling making the replacement. - -### 9. Replace uses of `@options` to `instance_options` in serializers -Simple find/replace - -## Conclusion -After you've done the steps above, you should test your app to ensure that everything is still working properly. - -If you run into issues, please contribute back to this document so others can benefit from your knowledge. - diff --git a/docs/integrations/ember-and-json-api.md b/docs/integrations/ember-and-json-api.md deleted file mode 100644 index eb7f1ade..00000000 --- a/docs/integrations/ember-and-json-api.md +++ /dev/null @@ -1,147 +0,0 @@ -[Back to Guides](../README.md) - -# Integrating with Ember and JSON API - - - [Preparation](./ember-and-json-api.md#preparation) - - [Server-Side Changes](./ember-and-json-api.md#server-side-changes) - - [Adapter Changes](./ember-and-json-api.md#adapter-changes) - - [Serializer Changes](./ember-and-json-api.md#serializer-changes) - - [Including Nested Resources](./ember-and-json-api.md#including-nested-resources) - -## Preparation - -Note: This guide assumes that `ember-cli` is used for your ember app. - -The JSON API specification calls for hyphens for multi-word separators. ActiveModelSerializers uses underscores. -To solve this, in Ember, both the adapter and the serializer will need some modifications: - -### Server-Side Changes - -First, set the adapter type in an initializer file: - -```ruby -# config/initializers/active_model_serializers.rb -ActiveModelSerializers.config.adapter = :json_api -``` - -or: - -```ruby -# config/initializers/active_model_serializers.rb -ActiveModelSerializers.config.adapter = ActiveModelSerializers::Adapter::JsonApi -``` - -You will also want to set the `key_transform` to `:unaltered` since you will adjust the attributes in your Ember serializer to use underscores instead of dashes later. You could also use `:underscore`, but `:unaltered` is better for performance. - -```ruby -# config/initializers/active_model_serializers.rb -ActiveModelSerializers.config.key_transform = :unaltered -``` - -In order to properly handle JSON API responses, we need to register a JSON API renderer, like so: - -```ruby -# config/initializers/active_model_serializers.rb -ActiveSupport.on_load(:action_controller) do - require 'active_model_serializers/register_jsonapi_renderer' -end -``` -Rails also requires your controller to tell it that you accept and generate JSONAPI data. To do that, you use `respond_to` in your controller handlers to tell rails you are consuming and returning jsonapi format data. Without this, Rails will refuse to parse the request body into params. You can add `ActionController::MimeResponds` to your application controller to enable this: - -```ruby -class ApplicationController < ActionController::API - include ActionController::MimeResponds -end -``` -Then, in your controller you can tell rails you're accepting and rendering the jsonapi format: -```ruby - # POST /post - def create - @post = Post.new(post_params) - respond_to do |format| - if @post.save - format.jsonapi { render jsonapi: @post, status: :created, location: @post } - else - format.jsonapi { render jsonapi: @post.errors, status: :unprocessable_entity } - end - end - end - - # Only allow a trusted parameter "white list" through. - def post_params - ActiveModelSerializers::Deserialization.jsonapi_parse!(params, only: [:title, :body] ) - end -end -``` - -#### Note: -In Rails 5, the "unsafe" method ( `jsonapi_parse!` vs the safe `jsonapi_parse`) throws an `InvalidDocument` exception when the payload does not meet basic criteria for JSON API deserialization. - - -### Adapter Changes - -```javascript -// app/adapters/application.js -import Ember from 'ember'; -import DS from 'ember-data'; -import ENV from "../config/environment"; -const { underscore, pluralize } = Ember.String; - -export default DS.JSONAPIAdapter.extend({ - namespace: 'api', - // if your rails app is on a different port from your ember app - // this can be helpful for development. - // in production, the host for both rails and ember should be the same. - host: ENV.host, - - // allows the multiword paths in urls to be underscored - pathForType: function(type) { - let underscored = underscore(type); - return pluralize(underscored); - }, - -}); -``` - -### Serializer Changes - -```javascript -// app/serializers/application.js -import Ember from 'ember'; -import DS from 'ember-data'; -var underscore = Ember.String.underscore; - -export default DS.JSONAPISerializer.extend({ - keyForAttribute: function(attr) { - return underscore(attr); - }, - - keyForRelationship: function(rawKey) { - return underscore(rawKey); - } -}); - -``` - - -## Including Nested Resources - -Ember Data can request related records by using `include`. Below are some examples of how to make Ember Data request the inclusion of related records. For more on `include` usage, see: [The JSON API include examples](./../general/adapters.md#JSON-API) - -```javascript -store.findRecord('post', postId, { include: 'comments' } ); -``` -which will generate the path /posts/{postId}?include='comments' - -So then in your controller, you'll want to be sure to have something like: -```ruby -render jsonapi: @post, include: params[:include] -``` - -If you want to use `include` on a collection, you'd write something like this: - -```javascript -store.query('post', { include: 'comments' }); -``` - -which will generate the path `/posts?include='comments'` diff --git a/docs/integrations/grape.md b/docs/integrations/grape.md deleted file mode 100644 index 7c855ebf..00000000 --- a/docs/integrations/grape.md +++ /dev/null @@ -1,19 +0,0 @@ -# Integration with Grape - -[Grape](https://github.com/ruby-grape/grape) is an opinionated micro-framework for creating REST-like APIs in ruby. - -ActiveModelSerializers currently supports Grape >= 0.13, < 1.0 - -To add [Grape](https://github.com/ruby-grape/grape) support, enable the formatter and helper functions by including `Grape::ActiveModelSerializers` in your base endpoint. For example: - -```ruby -module Example - class Dummy < Grape::API - require 'grape/active_model_serializers' - include Grape::ActiveModelSerializers - mount Example::V1::Base - end -end -``` - -Aside from this, [configuration](../general/configuration_options.md) of ActiveModelSerializers is exactly the same. diff --git a/docs/jsonapi/errors.md b/docs/jsonapi/errors.md deleted file mode 100644 index d19e2f9c..00000000 --- a/docs/jsonapi/errors.md +++ /dev/null @@ -1,56 +0,0 @@ -[Back to Guides](../README.md) - -# [JSON API Errors](http://jsonapi.org/format/#errors) - -Rendering error documents requires specifying the error serializer(s): - -- Serializer: - - For a single resource: `serializer: ActiveModel::Serializer::ErrorSerializer`. - - For a collection: `serializer: ActiveModel::Serializer::ErrorsSerializer`, `each_serializer: ActiveModel::Serializer::ErrorSerializer`. - -The resource **MUST** have a non-empty associated `#errors` object. -The `errors` object must have a `#messages` method that returns a hash of error name to array of -descriptions. - -## Use in controllers - -```ruby -resource = Profile.new(name: 'Name 1', - description: 'Description 1', - comments: 'Comments 1') -resource.errors.add(:name, 'cannot be nil') -resource.errors.add(:name, 'must be longer') -resource.errors.add(:id, 'must be a uuid') - -render json: resource, status: 422, adapter: :json_api, serializer: ActiveModel::Serializer::ErrorSerializer -# #=> -# { :errors => -# [ -# { :source => { :pointer => '/data/attributes/name' }, :detail => 'cannot be nil' }, -# { :source => { :pointer => '/data/attributes/name' }, :detail => 'must be longer' }, -# { :source => { :pointer => '/data/attributes/id' }, :detail => 'must be a uuid' } -# ] -# }.to_json -``` - -## Direct error document generation - -```ruby -options = nil -resource = ModelWithErrors.new -resource.errors.add(:name, 'must be awesome') - -serializable_resource = ActiveModelSerializers::SerializableResource.new( - resource, { - serializer: ActiveModel::Serializer::ErrorSerializer, - adapter: :json_api - }) -serializable_resource.as_json(options) -# #=> -# { -# :errors => -# [ -# { :source => { :pointer => '/data/attributes/name' }, :detail => 'must be awesome' } -# ] -# } -``` diff --git a/docs/jsonapi/schema.md b/docs/jsonapi/schema.md deleted file mode 100644 index baffe358..00000000 --- a/docs/jsonapi/schema.md +++ /dev/null @@ -1,151 +0,0 @@ -[Back to Guides](../README.md) - -[![JSON API 1.0](https://img.shields.io/badge/JSON%20API-1.0-lightgrey.svg)](http://jsonapi.org/) - -## JSON API Requests - -- [Query Parameters Spec](http://jsonapi.org/format/#query-parameters) - -Headers: - -- Request: `Accept: application/vnd.api+json` -- Response: `Content-Type: application/vnd.api+json` - -### [Fetching Data](http://jsonapi.org/format/#fetching) - -A server MUST support fetching resource data for every URL provided as: - -- a `self` link as part of the top-level links object -- a `self` link as part of a resource-level links object -- a `related` link as part of a relationship-level links object - -Example supported requests - -- Individual resource or collection - - GET /articles - - GET /articles/1 - - GET /articles/1/author -- Relationships - - GET /articles/1/relationships/comments - - GET /articles/1/relationships/author -- Optional: [Inclusion of related resources](http://jsonapi.org/format/#fetching-includes) `JSONAPI::IncludeDirective` - - GET /articles/1?`include`=comments - - GET /articles/1?`include`=comments.author - - GET /articles/1?`include`=author,comments.author - - GET /articles/1/relationships/comments?`include`=comments.author -- Optional: [Sparse Fieldsets](http://jsonapi.org/format/#fetching-sparse-fieldsets) `ActiveModel::Serializer::Fieldset` - - GET /articles?`include`=author&`fields`[articles]=title,body&`fields`[people]=name -- Optional: [Sorting](http://jsonapi.org/format/#fetching-sorting) - - GET /people?`sort`=age - - GET /people?`sort`=age,author.name - - GET /articles?`sort`=-created,title -- Optional: [Pagination](http://jsonapi.org/format/#fetching-pagination) - - GET /articles?`page`[number]=3&`page`[size]=1 -- Optional: [Filtering](http://jsonapi.org/format/#fetching-filtering) - - GET /comments?`filter`[post]=1 - - GET /comments?`filter`[post]=1,2 - - GET /comments?`filter`[post]=1,2 - -### [CRUD Actions](http://jsonapi.org/format/#crud) - -### [Asynchronous Processing](http://jsonapi.org/recommendations/#asynchronous-processing) - -### [Bulk Operations Extension](http://jsonapi.org/extensions/bulk/) - -## JSON API Document Schema - -| JSON API object | JSON API properties | Required | ActiveModelSerializers representation | -|-----------------------|----------------------------------------------------------------------------------------------------|----------|---------------------------------------| -| schema | oneOf (success, failure, info) | | -| success | data, included, meta, links, jsonapi | | AM::SerializableResource -| success.meta | meta | | AMS::Adapter::Base#meta -| success.included | UniqueArray(resource) | | AMS::Adapter::JsonApi#serializable_hash_for_collection -| success.data | data | | -| success.links | allOf (links, pagination) | | AMS::Adapter::JsonApi#links_for -| success.jsonapi | jsonapi | | -| failure | errors, meta, jsonapi | errors | AMS::Adapter::JsonApi#failure_document, #1004 -| failure.errors | UniqueArray(error) | | AM::S::ErrorSerializer, #1004 -| meta | Object | | -| data | oneOf (resource, UniqueArray(resource)) | | AMS::Adapter::JsonApi#serializable_hash_for_collection,#serializable_hash_for_single_resource -| resource | String(type), String(id),
attributes, relationships,
links, meta | type, id | AM::S::Adapter::JsonApi#primary_data_for -| links | Uri(self), Link(related) | | #1028, #1246, #1282 -| link | oneOf (linkString, linkObject) | | -| link.linkString | Uri | | -| link.linkObject | Uri(href), meta | href | -| attributes | patternProperties(
`"^(?!relationships$|links$)\\w[-\\w_]*$"`),
any valid JSON | | AM::Serializer#attributes, AMS::Adapter::JsonApi#resource_object_for -| relationships | patternProperties(
`"^\\w[-\\w_]*$"`);
links, relationships.data, meta | | AMS::Adapter::JsonApi#relationships_for -| relationships.data | oneOf (relationshipToOne, relationshipToMany) | | AMS::Adapter::JsonApi#resource_identifier_for -| relationshipToOne | anyOf(empty, linkage) | | -| relationshipToMany | UniqueArray(linkage) | | -| empty | null | | -| linkage | String(type), String(id), meta | type, id | AMS::Adapter::JsonApi#primary_data_for -| pagination | pageObject(first), pageObject(last),
pageObject(prev), pageObject(next) | | AMS::Adapter::JsonApi::PaginationLinks#serializable_hash -| pagination.pageObject | oneOf(Uri, null) | | -| jsonapi | String(version), meta | | AMS::Adapter::JsonApi::Jsonapi#as_json -| error | String(id), links, String(status),
String(code), String(title),
String(detail), error.source, meta | | AM::S::ErrorSerializer, AMS::Adapter::JsonApi::Error.resource_errors -| error.source | String(pointer), String(parameter) | | AMS::Adapter::JsonApi::Error.error_source -| pointer | [JSON Pointer RFC6901](https://tools.ietf.org/html/rfc6901) | | AMS::JsonPointer - - -The [http://jsonapi.org/schema](schema/schema.json) makes a nice roadmap. - -### Success Document -- [ ] success - - [ ] data: `"$ref": "#/definitions/data"` - - [ ] included: array of unique items of type `"$ref": "#/definitions/resource"` - - [ ] meta: `"$ref": "#/definitions/meta"` - - [ ] links: - - [ ] link: `"$ref": "#/definitions/links"` - - [ ] pagination: ` "$ref": "#/definitions/pagination"` - - [ ] jsonapi: ` "$ref": "#/definitions/jsonapi"` - -### Failure Document - -- [ ] failure - - [x] errors: array of unique items of type ` "$ref": "#/definitions/error"` - - [ ] meta: `"$ref": "#/definitions/meta"` - - [ ] jsonapi: `"$ref": "#/definitions/jsonapi"` - -### Info Document - -- [ ] info - - [ ] meta: `"$ref": "#/definitions/meta"` - - [ ] links: `"$ref": "#/definitions/links"` - - [ ] jsonapi: ` "$ref": "#/definitions/jsonapi"` - -### Definitions - -- [ ] definitions: - - [ ] meta - - [ ] data: oneOf (resource, array of unique resources) - - [ ] resource - - [ ] attributes - - [ ] relationships - - [ ] relationshipToOne - - [ ] empty - - [ ] linkage - - [ ] meta - - [ ] relationshipToMany - - [ ] linkage - - [ ] meta - - [ ] links - - [ ] meta - - [ ] links - - [ ] link - - [ ] uri - - [ ] href, meta - - [ ] pagination - - [ ] jsonapi - - [ ] meta - - [ ] error - - [ ] id: a unique identifier for this particular occurrence of the problem. - - [ ] links: a links object containing the following members: - - [ ] about: a link that leads to further details about this particular occurrence of the problem. - - [ ] status: the HTTP status code applicable to this problem, expressed as a string value. - - [ ] code: an application-specific error code, expressed as a string value. - - [ ] title: a short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization. - - [x] detail: a human-readable explanation specific to this occurrence of the problem. - - [x] source: an object containing references to the source of the error, optionally including any of the following members: - - [x] pointer: a JSON Pointer [RFC6901](https://tools.ietf.org/html/rfc6901) to the associated entity in the request document [e.g. "/data" for a primary data object, or "/data/attributes/title" for a specific attribute]. - - [x] parameter: a string indicating which query parameter caused the error. - - [ ] meta: a meta object containing non-standard meta-information about the error. diff --git a/docs/jsonapi/schema/schema.json b/docs/jsonapi/schema/schema.json deleted file mode 100644 index ef3ea351..00000000 --- a/docs/jsonapi/schema/schema.json +++ /dev/null @@ -1,366 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "JSON API Schema", - "description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org", - "oneOf": [ - { - "$ref": "#/definitions/success" - }, - { - "$ref": "#/definitions/failure" - }, - { - "$ref": "#/definitions/info" - } - ], - - "definitions": { - "success": { - "type": "object", - "required": [ - "data" - ], - "properties": { - "data": { - "$ref": "#/definitions/data" - }, - "included": { - "description": "To reduce the number of HTTP requests, servers **MAY** allow responses that include related resources along with the requested primary resources. Such responses are called \"compound documents\".", - "type": "array", - "items": { - "$ref": "#/definitions/resource" - }, - "uniqueItems": true - }, - "meta": { - "$ref": "#/definitions/meta" - }, - "links": { - "description": "Link members related to the primary data.", - "allOf": [ - { - "$ref": "#/definitions/links" - }, - { - "$ref": "#/definitions/pagination" - } - ] - }, - "jsonapi": { - "$ref": "#/definitions/jsonapi" - } - }, - "additionalProperties": false - }, - "failure": { - "type": "object", - "required": [ - "errors" - ], - "properties": { - "errors": { - "type": "array", - "items": { - "$ref": "#/definitions/error" - }, - "uniqueItems": true - }, - "meta": { - "$ref": "#/definitions/meta" - }, - "jsonapi": { - "$ref": "#/definitions/jsonapi" - } - }, - "additionalProperties": false - }, - "info": { - "type": "object", - "required": [ - "meta" - ], - "properties": { - "meta": { - "$ref": "#/definitions/meta" - }, - "links": { - "$ref": "#/definitions/links" - }, - "jsonapi": { - "$ref": "#/definitions/jsonapi" - } - }, - "additionalProperties": false - }, - - "meta": { - "description": "Non-standard meta-information that can not be represented as an attribute or relationship.", - "type": "object", - "additionalProperties": true - }, - "data": { - "description": "The document's \"primary data\" is a representation of the resource or collection of resources targeted by a request.", - "oneOf": [ - { - "$ref": "#/definitions/resource" - }, - { - "description": "An array of resource objects, an array of resource identifier objects, or an empty array ([]), for requests that target resource collections.", - "type": "array", - "items": { - "$ref": "#/definitions/resource" - }, - "uniqueItems": true - } - ] - }, - "resource": { - "description": "\"Resource objects\" appear in a JSON API document to represent resources.", - "type": "object", - "required": [ - "type", - "id" - ], - "properties": { - "type": { - "type": "string" - }, - "id": { - "type": "string" - }, - "attributes": { - "$ref": "#/definitions/attributes" - }, - "relationships": { - "$ref": "#/definitions/relationships" - }, - "links": { - "$ref": "#/definitions/links" - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "additionalProperties": false - }, - - "links": { - "description": "A resource object **MAY** contain references to other resource objects (\"relationships\"). Relationships may be to-one or to-many. Relationships can be specified by including a member in a resource's links object.", - "type": "object", - "properties": { - "self": { - "description": "A `self` member, whose value is a URL for the relationship itself (a \"relationship URL\"). This URL allows the client to directly manipulate the relationship. For example, it would allow a client to remove an `author` from an `article` without deleting the people resource itself.", - "type": "string", - "format": "uri" - }, - "related": { - "$ref": "#/definitions/link" - } - }, - "additionalProperties": true - }, - "link": { - "description": "A link **MUST** be represented as either: a string containing the link's URL or a link object.", - "oneOf": [ - { - "description": "A string containing the link's URL.", - "type": "string", - "format": "uri" - }, - { - "type": "object", - "required": [ - "href" - ], - "properties": { - "href": { - "description": "A string containing the link's URL.", - "type": "string", - "format": "uri" - }, - "meta": { - "$ref": "#/definitions/meta" - } - } - } - ] - }, - - "attributes": { - "description": "Members of the attributes object (\"attributes\") represent information about the resource object in which it's defined.", - "type": "object", - "patternProperties": { - "^(?!relationships$|links$)\\w[-\\w_]*$": { - "description": "Attributes may contain any valid JSON value." - } - }, - "additionalProperties": false - }, - - "relationships": { - "description": "Members of the relationships object (\"relationships\") represent references from the resource object in which it's defined to other resource objects.", - "type": "object", - "patternProperties": { - "^\\w[-\\w_]*$": { - "properties": { - "links": { - "$ref": "#/definitions/links" - }, - "data": { - "description": "Member, whose value represents \"resource linkage\".", - "oneOf": [ - { - "$ref": "#/definitions/relationshipToOne" - }, - { - "$ref": "#/definitions/relationshipToMany" - } - ] - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "relationshipToOne": { - "description": "References to other resource objects in a to-one (\"relationship\"). Relationships can be specified by including a member in a resource's links object.", - "anyOf": [ - { - "$ref": "#/definitions/empty" - }, - { - "$ref": "#/definitions/linkage" - } - ] - }, - "relationshipToMany": { - "description": "An array of objects each containing \"type\" and \"id\" members for to-many relationships.", - "type": "array", - "items": { - "$ref": "#/definitions/linkage" - }, - "uniqueItems": true - }, - "empty": { - "description": "Describes an empty to-one relationship.", - "type": "null" - }, - "linkage": { - "description": "The \"type\" and \"id\" to non-empty members.", - "type": "object", - "required": [ - "type", - "id" - ], - "properties": { - "type": { - "type": "string" - }, - "id": { - "type": "string" - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "additionalProperties": false - }, - "pagination": { - "type": "object", - "properties": { - "first": { - "description": "The first page of data", - "oneOf": [ - { "type": "string", "format": "uri" }, - { "type": "null" } - ] - }, - "last": { - "description": "The last page of data", - "oneOf": [ - { "type": "string", "format": "uri" }, - { "type": "null" } - ] - }, - "prev": { - "description": "The previous page of data", - "oneOf": [ - { "type": "string", "format": "uri" }, - { "type": "null" } - ] - }, - "next": { - "description": "The next page of data", - "oneOf": [ - { "type": "string", "format": "uri" }, - { "type": "null" } - ] - } - } - }, - - "jsonapi": { - "description": "An object describing the server's implementation", - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "additionalProperties": false - }, - - "error": { - "type": "object", - "properties": { - "id": { - "description": "A unique identifier for this particular occurrence of the problem.", - "type": "string" - }, - "links": { - "$ref": "#/definitions/links" - }, - "status": { - "description": "The HTTP status code applicable to this problem, expressed as a string value.", - "type": "string" - }, - "code": { - "description": "An application-specific error code, expressed as a string value.", - "type": "string" - }, - "title": { - "description": "A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.", - "type": "string" - }, - "detail": { - "description": "A human-readable explanation specific to this occurrence of the problem.", - "type": "string" - }, - "source": { - "type": "object", - "properties": { - "pointer": { - "description": "A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute].", - "type": "string" - }, - "parameter": { - "description": "A string indicating which query parameter caused the error.", - "type": "string" - } - } - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "additionalProperties": false - } - } -} diff --git a/docs/rfcs/0000-namespace.md b/docs/rfcs/0000-namespace.md deleted file mode 100644 index da07c4c1..00000000 --- a/docs/rfcs/0000-namespace.md +++ /dev/null @@ -1,106 +0,0 @@ -- Start Date: (2015-10-29) -- RFC PR: https://github.com/rails-api/active_model_serializers/pull/1310 -- ActiveModelSerializers Issue: https://github.com/rails-api/active_model_serializers/issues/1298 - -# Summary - -Provide a consistent API for the user of the AMS. - -# Motivation - -The actual public API is defined under `ActiveModelSerializers`, -`ActiveModel::Serializer` and `ActiveModel`. - -At the `ActiveModel::Serializer` we have: - -- `ActiveModel::Serializer.config` -- `ActiveModel::Serializer` - -At the `ActiveModelSerializers` we have: - -- `ActiveModelSerializers::Model` -- `ActiveModelSerializers.logger` - -At `ActiveModel` we have: - -- `ActiveModel::SerializableResource` - -The idea here is to provide a single namespace `ActiveModelSerializers` to the user. -Following the same idea we have on other gems like -[Devise](https://github.com/plataformatec/devise/blob/e9c82472ffe7c43a448945f77e034a0e47dde0bb/lib/devise.rb), -[Refile](https://github.com/refile/refile/blob/6b24c293d044862dafbf1bfa4606672a64903aa2/lib/refile.rb) and -[Active Job](https://github.com/rails/rails/blob/30bacc26f8f258b39e12f63fe52389a968d9c1ea/activejob/lib/active_job.rb) -for example. - -This way we are clarifing the boundaries of -[ActiveModelSerializers and Rails](https://github.com/rails-api/active_model_serializers/blob/master/CHANGELOG.md#prehistory) -and make clear that the `ActiveModel::Serializer` class is no longer the primary -behavior of the ActiveModelSerializers. - -# Detailed design - -## New classes and modules organization - -Since this will be a big change we can do this on baby steps, read small pull requests. A -possible approach is: - -- All new code will be in `lib/active_model_serializers/` using - the module namespace `ActiveModelSerializers`. -- Move all content under `ActiveModel::Serializer` to be under - `ActiveModelSerializers`, the adapter is on this steps; -- Move all content under `ActiveModel` to be under `ActiveModelSerializers`, - the `SerializableResource` is on this step; -- Change all public API that doesn't make sense, keeping in mind only to keep - this in the same namespace -- Update the README; -- Update the docs; - -The following table represents the current and the desired classes and modules -at the first moment. - -| Current | Desired | Notes | -|--------------------------------------------------------|--------------------------------------------------|--------------------| -| `ActiveModelSerializers` and `ActiveModel::Serializer` | `ActiveModelSerializers` | The main namespace | -| `ActiveModelSerializers.logger` | `ActiveModelSerializers.logger` || -| `ActiveModelSerializers::Model` | `ActiveModelSerializers::Model` || -| `ActiveModel::SerializableResource` | `ActiveModelSerializers::SerializableResource` || -| `ActiveModel::Serializer` | `ActiveModelSerializers::Serializer` | The name can be discussed in a future pull request. For example, we can rename this to `Resource` [following this idea](https://github.com/rails-api/active_model_serializers/pull/1301/files#r42963185) more info about naming in the next section| -| `ActiveModel::Serializer.config` | `ActiveModelSerializers.config` || - -## Renaming of class and modules - -When moving some content to the new namespace we can find some names that does -not make much sense like `ActiveModel::Serializer::Adapter::JsonApi`. -Discussion of renaming existing classes / modules and JsonApi objects will -happen in separate pull requests, and issues, and in the google doc -https://docs.google.com/document/d/1rcrJr0sVcazY2Opd_6Kmv1iIwuHbI84s1P_NzFn-05c/edit?usp=sharing - -Some of names already have a definition. - -- Adapters get their own namespace under ActiveModelSerializers. E.g - `ActiveModelSerializers::Adapter` -- Serializers get their own namespace under ActiveModelSerializers. E.g - `ActiveModelSerializers::Serializer` - -## Keeping compatibility - -All moved classes or modules be aliased to their old name and location with -deprecation warnings, such as -[was done for CollectionSerializer](https://github.com/rails-api/active_model_serializers/pull/1251). - -# Drawbacks - -This will be a breaking change, so all users serializers will be broken after a -major bump. -All pull requests will need to rebase since the architeture will change a lot. - -# Alternatives - -We can keep the way it is, and keep in mind to not add another namespace as a -public API. - -# Unresolved questions - -What is the better class name to be used to the class that will be inherited at -the creation of a serializer. This can be discussed in other RFC or directly via -pull request. diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb deleted file mode 100644 index ea84c674..00000000 --- a/lib/action_controller/serialization.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'active_support/core_ext/class/attribute' -require 'active_model_serializers/serialization_context' - -module ActionController - module Serialization - extend ActiveSupport::Concern - - include ActionController::Renderers - - module ClassMethods - def serialization_scope(scope) - self._serialization_scope = scope - end - end - - included do - class_attribute :_serialization_scope - self._serialization_scope = :current_user - - attr_writer :namespace_for_serializer - end - - def namespace_for_serializer - @namespace_for_serializer ||= self.class.parent unless self.class.parent == Object - end - - def serialization_scope - return unless _serialization_scope && respond_to?(_serialization_scope, true) - - send(_serialization_scope) - end - - def get_serializer(resource, options = {}) - unless use_adapter? - warn 'ActionController::Serialization#use_adapter? has been removed. '\ - "Please pass 'adapter: false' or see ActiveSupport::SerializableResource.new" - options[:adapter] = false - end - - options.fetch(:namespace) { options[:namespace] = namespace_for_serializer } - - serializable_resource = ActiveModelSerializers::SerializableResource.new(resource, options) - serializable_resource.serialization_scope ||= options.fetch(:scope) { serialization_scope } - serializable_resource.serialization_scope_name = options.fetch(:scope_name) { _serialization_scope } - # For compatibility with the JSON renderer: `json.to_json(options) if json.is_a?(String)`. - # Otherwise, since `serializable_resource` is not a string, the renderer would call - # `to_json` on a String and given odd results, such as `"".to_json #=> '""'` - serializable_resource.adapter.is_a?(String) ? serializable_resource.adapter : serializable_resource - end - - # Deprecated - def use_adapter? - true - end - - [:_render_option_json, :_render_with_renderer_json].each do |renderer_method| - define_method renderer_method do |resource, options| - options.fetch(:serialization_context) do - options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request, options) - end - serializable_resource = get_serializer(resource, options) - super(serializable_resource, options) - end - end - end -end diff --git a/lib/active_model/serializable_resource.rb b/lib/active_model/serializable_resource.rb deleted file mode 100644 index 0e1c8e2d..00000000 --- a/lib/active_model/serializable_resource.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'set' - -module ActiveModel - class SerializableResource - class << self - extend ActiveModelSerializers::Deprecate - - delegate_and_deprecate :new, ActiveModelSerializers::SerializableResource - end - end -end diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb deleted file mode 100644 index 9d00e6fb..00000000 --- a/lib/active_model/serializer.rb +++ /dev/null @@ -1,409 +0,0 @@ -require 'thread_safe' -require 'jsonapi/include_directive' -require 'active_model/serializer/collection_serializer' -require 'active_model/serializer/array_serializer' -require 'active_model/serializer/error_serializer' -require 'active_model/serializer/errors_serializer' -require 'active_model/serializer/concerns/caching' -require 'active_model/serializer/fieldset' -require 'active_model/serializer/lint' - -# ActiveModel::Serializer is an abstract class that is -# reified when subclassed to decorate a resource. -module ActiveModel - class Serializer - undef_method :select, :display # These IO methods, which are mixed into Kernel, - # sometimes conflict with attribute names. We don't need these IO methods. - - # @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 - include ActiveSupport::Configurable - include Caching - - # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model] - # @return [ActiveModel::Serializer] - # Preferentially returns - # 1. resource.serializer_class - # 2. ArraySerializer when resource is a collection - # 3. options[:serializer] - # 4. lookup serializer when resource is a Class - def self.serializer_for(resource_or_class, options = {}) - if resource_or_class.respond_to?(:serializer_class) - resource_or_class.serializer_class - elsif resource_or_class.respond_to?(:to_ary) - config.collection_serializer - else - resource_class = resource_or_class.class == Class ? resource_or_class : resource_or_class.class - options.fetch(:serializer) { get_serializer_for(resource_class, options[:namespace]) } - end - end - - # @see ActiveModelSerializers::Adapter.lookup - # Deprecated - def self.adapter - ActiveModelSerializers::Adapter.lookup(config.adapter) - end - class << self - extend ActiveModelSerializers::Deprecate - deprecate :adapter, 'ActiveModelSerializers::Adapter.configured_adapter' - end - - # @api private - def self.serializer_lookup_chain_for(klass, namespace = nil) - lookups = ActiveModelSerializers.config.serializer_lookup_chain - Array[*lookups].flat_map do |lookup| - lookup.call(klass, self, namespace) - end.compact - end - - # Used to cache serializer name => serializer class - # when looked up by Serializer.get_serializer_for. - def self.serializers_cache - @serializers_cache ||= ThreadSafe::Cache.new - end - - # @api private - # Find a serializer from a class and caches the lookup. - # Preferentially returns: - # 1. class name appended with "Serializer" - # 2. try again with superclass, if present - # 3. nil - def self.get_serializer_for(klass, namespace = nil) - return nil unless config.serializer_lookup_enabled - - cache_key = ActiveSupport::Cache.expand_cache_key(klass, namespace) - serializers_cache.fetch_or_store(cache_key) do - # NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs. - lookup_chain = serializer_lookup_chain_for(klass, namespace) - serializer_class = lookup_chain.map(&:safe_constantize).find { |x| x && x < ActiveModel::Serializer } - - if serializer_class - serializer_class - elsif klass.superclass - get_serializer_for(klass.superclass) - else - nil # No serializer found - end - end - end - - # @api private - def self.include_directive_from_options(options) - if options[:include_directive] - options[:include_directive] - elsif options[:include] - JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true) - else - ActiveModelSerializers.default_include_directive - end - end - - # @api private - def self.serialization_adapter_instance - @serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes - end - - # Preferred interface is ActiveModelSerializers.config - # BEGIN DEFAULT CONFIGURATION - config.collection_serializer = ActiveModel::Serializer::CollectionSerializer - config.serializer_lookup_enabled = true - - # @deprecated Use {#config.collection_serializer=} instead of this. Is - # compatibility layer for ArraySerializer. - def config.array_serializer=(collection_serializer) - self.collection_serializer = collection_serializer - end - - # @deprecated Use {#config.collection_serializer} instead of this. Is - # compatibility layer for ArraySerializer. - def config.array_serializer - collection_serializer - end - - config.default_includes = '*' - config.adapter = :attributes - config.key_transform = nil - config.jsonapi_pagination_links_enabled = true - config.jsonapi_resource_type = :plural - config.jsonapi_namespace_separator = '-'.freeze - config.jsonapi_version = '1.0' - config.jsonapi_toplevel_meta = {} - # Make JSON API top-level jsonapi member opt-in - # ref: http://jsonapi.org/format/#document-top-level - config.jsonapi_include_toplevel_object = false - config.include_data_default = true - - # For configuring how serializers are found. - # This should be an array of procs. - # - # The priority of the output is that the first item - # in the evaluated result array will take precedence - # over other possible serializer paths. - # - # i.e.: First match wins. - # - # @example output - # => [ - # "CustomNamespace::ResourceSerializer", - # "ParentSerializer::ResourceSerializer", - # "ResourceNamespace::ResourceSerializer" , - # "ResourceSerializer"] - # - # If CustomNamespace::ResourceSerializer exists, it will be used - # for serialization - config.serializer_lookup_chain = ActiveModelSerializers::LookupChain::DEFAULT.dup - - config.schema_path = 'test/support/schemas' - # END DEFAULT CONFIGURATION - - with_options instance_writer: false, instance_reader: false do |serializer| - serializer.class_attribute :_attributes_data # @api private - self._attributes_data ||= {} - end - with_options instance_writer: false, instance_reader: true do |serializer| - serializer.class_attribute :_reflections - self._reflections ||= {} - serializer.class_attribute :_links # @api private - self._links ||= {} - serializer.class_attribute :_meta # @api private - serializer.class_attribute :_type # @api private - end - - def self.inherited(base) - super - base._attributes_data = _attributes_data.dup - base._reflections = _reflections.dup - base._links = _links.dup - end - - # @return [Array] Key names of declared attributes - # @see Serializer::attribute - def self._attributes - _attributes_data.keys - end - - # BEGIN SERIALIZER MACROS - - # @example - # class AdminAuthorSerializer < ActiveModel::Serializer - # attributes :id, :name, :recent_edits - def self.attributes(*attrs) - attrs = attrs.first if attrs.first.class == Array - - attrs.each do |attr| - attribute(attr) - end - end - - # @example - # class AdminAuthorSerializer < ActiveModel::Serializer - # attributes :id, :recent_edits - # attribute :name, key: :title - # - # attribute :full_name do - # "#{object.first_name} #{object.last_name}" - # end - # - # def recent_edits - # object.edits.last(5) - # end - def self.attribute(attr, options = {}, &block) - key = options.fetch(:key, attr) - _attributes_data[key] = Attribute.new(attr, options, block) - end - - # @param [Symbol] name of the association - # @param [Hash any>] options for the reflection - # @return [void] - # - # @example - # has_many :comments, serializer: CommentSummarySerializer - # - def self.has_many(name, options = {}, &block) # rubocop:disable Style/PredicateName - associate(HasManyReflection.new(name, options, block)) - end - - # @param [Symbol] name of the association - # @param [Hash any>] options for the reflection - # @return [void] - # - # @example - # belongs_to :author, serializer: AuthorSerializer - # - def self.belongs_to(name, options = {}, &block) - associate(BelongsToReflection.new(name, options, block)) - end - - # @param [Symbol] name of the association - # @param [Hash any>] options for the reflection - # @return [void] - # - # @example - # has_one :author, serializer: AuthorSerializer - # - def self.has_one(name, options = {}, &block) # rubocop:disable Style/PredicateName - associate(HasOneReflection.new(name, options, block)) - end - - # Add reflection and define {name} accessor. - # @param [ActiveModel::Serializer::Reflection] reflection - # @return [void] - # - # @api private - def self.associate(reflection) - key = reflection.options[:key] || reflection.name - self._reflections[key] = reflection - end - private_class_method :associate - - # Define a link on a serializer. - # @example - # link(:self) { resource_url(object) } - # @example - # link(:self) { "http://example.com/resource/#{object.id}" } - # @example - # link :resource, "http://example.com/resource" - # - def self.link(name, value = nil, &block) - _links[name] = block || value - end - - # Set the JSON API meta attribute of a serializer. - # @example - # class AdminAuthorSerializer < ActiveModel::Serializer - # meta { stuff: 'value' } - # @example - # meta do - # { comment_count: object.comments.count } - # end - def self.meta(value = nil, &block) - self._meta = block || value - end - - # Set the JSON API type of a serializer. - # @example - # class AdminAuthorSerializer < ActiveModel::Serializer - # type 'authors' - def self.type(type) - self._type = type && type.to_s - end - - # END SERIALIZER MACROS - - attr_accessor :object, :root, :scope - - # `scope_name` is set as :current_user by default in the controller. - # If the instance does not have a method named `scope_name`, it - # defines the method so that it calls the +scope+. - def initialize(object, options = {}) - self.object = object - self.instance_options = options - self.root = instance_options[:root] - self.scope = instance_options[:scope] - - return if !(scope_name = instance_options[:scope_name]) || respond_to?(scope_name) - - define_singleton_method scope_name, -> { scope } - end - - def success? - true - end - - # Return the +attributes+ of +object+ as presented - # by the serializer. - def attributes(requested_attrs = nil, reload = false) - @attributes = nil if reload - @attributes ||= self.class._attributes_data.each_with_object({}) do |(key, attr), hash| - next if attr.excluded?(self) - next unless requested_attrs.nil? || requested_attrs.include?(key) - hash[key] = attr.value(self) - end - end - - # @param [JSONAPI::IncludeDirective] include_directive (defaults to the - # +default_include_directive+ config value when not provided) - # @return [Enumerator] - def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil) - include_slice ||= include_directive - return Enumerator.new unless object - - Enumerator.new do |y| - self.class._reflections.each do |key, reflection| - next if reflection.excluded?(self) - next unless include_directive.key?(key) - - association = reflection.build_association(self, instance_options, include_slice) - y.yield association - end - end - end - - # @return [Hash] containing the attributes and first level - # associations, similar to how ActiveModel::Serializers::JSON is used - # in ActiveRecord::Base. - 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) - resource = attributes_hash(adapter_options, options, adapter_instance) - relationships = associations_hash(adapter_options, options, adapter_instance) - resource.merge(relationships) - end - alias to_hash serializable_hash - alias to_h serializable_hash - - # @see #serializable_hash - def as_json(adapter_opts = nil) - serializable_hash(adapter_opts) - end - - # Used by adapter as resource root. - def json_key - root || _type || object.class.model_name.to_s.underscore - end - - def read_attribute_for_serialization(attr) - if respond_to?(attr) - send(attr) - else - object.read_attribute_for_serialization(attr) - end - end - - # @api private - def attributes_hash(_adapter_options, options, adapter_instance) - if self.class.cache_enabled? - fetch_attributes(options[:fields], options[:cached_attributes] || {}, adapter_instance) - elsif self.class.fragment_cache_enabled? - fetch_attributes_fragment(adapter_instance, options[:cached_attributes] || {}) - else - attributes(options[:fields], true) - end - end - - # @api private - def associations_hash(adapter_options, options, adapter_instance) - include_directive = options.fetch(:include_directive) - include_slice = options[:include_slice] - associations(include_directive, include_slice).each_with_object({}) do |association, relationships| - adapter_opts = adapter_options.merge(include_directive: include_directive[association.key], adapter_instance: adapter_instance) - relationships[association.key] = association.serializable_hash(adapter_opts, adapter_instance) - end - end - - protected - - attr_accessor :instance_options - end -end diff --git a/lib/active_model/serializer/adapter.rb b/lib/active_model/serializer/adapter.rb deleted file mode 100644 index 6b5f30ca..00000000 --- a/lib/active_model/serializer/adapter.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'active_model_serializers/adapter' -require 'active_model_serializers/deprecate' - -module ActiveModel - class Serializer - # @deprecated Use ActiveModelSerializers::Adapter instead - module Adapter - class << self - extend ActiveModelSerializers::Deprecate - - DEPRECATED_METHODS = [:create, :adapter_class, :adapter_map, :adapters, :register, :lookup].freeze - DEPRECATED_METHODS.each do |method| - delegate_and_deprecate method, ActiveModelSerializers::Adapter - end - end - end - end -end - -require 'active_model/serializer/adapter/base' -require 'active_model/serializer/adapter/null' -require 'active_model/serializer/adapter/attributes' -require 'active_model/serializer/adapter/json' -require 'active_model/serializer/adapter/json_api' diff --git a/lib/active_model/serializer/adapter/attributes.rb b/lib/active_model/serializer/adapter/attributes.rb deleted file mode 100644 index e04e5fd8..00000000 --- a/lib/active_model/serializer/adapter/attributes.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class Attributes < DelegateClass(ActiveModelSerializers::Adapter::Attributes) - def initialize(serializer, options = {}) - super(ActiveModelSerializers::Adapter::Attributes.new(serializer, options)) - end - class << self - extend ActiveModelSerializers::Deprecate - deprecate :new, 'ActiveModelSerializers::Adapter::Json.' - end - end - end - end -end diff --git a/lib/active_model/serializer/adapter/base.rb b/lib/active_model/serializer/adapter/base.rb deleted file mode 100644 index 013a9705..00000000 --- a/lib/active_model/serializer/adapter/base.rb +++ /dev/null @@ -1,18 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class Base < DelegateClass(ActiveModelSerializers::Adapter::Base) - class << self - extend ActiveModelSerializers::Deprecate - deprecate :inherited, 'ActiveModelSerializers::Adapter::Base.' - end - - # :nocov: - def initialize(serializer, options = {}) - super(ActiveModelSerializers::Adapter::Base.new(serializer, options)) - end - # :nocov: - end - end - end -end diff --git a/lib/active_model/serializer/adapter/json.rb b/lib/active_model/serializer/adapter/json.rb deleted file mode 100644 index 1998a4c6..00000000 --- a/lib/active_model/serializer/adapter/json.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class Json < DelegateClass(ActiveModelSerializers::Adapter::Json) - def initialize(serializer, options = {}) - super(ActiveModelSerializers::Adapter::Json.new(serializer, options)) - end - class << self - extend ActiveModelSerializers::Deprecate - deprecate :new, 'ActiveModelSerializers::Adapter::Json.new' - end - end - end - end -end diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb deleted file mode 100644 index 13777cdc..00000000 --- a/lib/active_model/serializer/adapter/json_api.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class JsonApi < DelegateClass(ActiveModelSerializers::Adapter::JsonApi) - def initialize(serializer, options = {}) - super(ActiveModelSerializers::Adapter::JsonApi.new(serializer, options)) - end - class << self - extend ActiveModelSerializers::Deprecate - deprecate :new, 'ActiveModelSerializers::Adapter::JsonApi.new' - end - end - end - end -end diff --git a/lib/active_model/serializer/adapter/null.rb b/lib/active_model/serializer/adapter/null.rb deleted file mode 100644 index 906953d1..00000000 --- a/lib/active_model/serializer/adapter/null.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class Null < DelegateClass(ActiveModelSerializers::Adapter::Null) - def initialize(serializer, options = {}) - super(ActiveModelSerializers::Adapter::Null.new(serializer, options)) - end - class << self - extend ActiveModelSerializers::Deprecate - deprecate :new, 'ActiveModelSerializers::Adapter::Null.new' - end - end - end - end -end diff --git a/lib/active_model/serializer/array_serializer.rb b/lib/active_model/serializer/array_serializer.rb deleted file mode 100644 index 2e768deb..00000000 --- a/lib/active_model/serializer/array_serializer.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'active_model/serializer/collection_serializer' - -module ActiveModel - class Serializer - class ArraySerializer < CollectionSerializer - class << self - extend ActiveModelSerializers::Deprecate - deprecate :new, 'ActiveModel::Serializer::CollectionSerializer.' - end - end - end -end diff --git a/lib/active_model/serializer/association.rb b/lib/active_model/serializer/association.rb deleted file mode 100644 index 7ce82316..00000000 --- a/lib/active_model/serializer/association.rb +++ /dev/null @@ -1,71 +0,0 @@ -require 'active_model/serializer/lazy_association' - -module ActiveModel - class Serializer - # This class holds all information about serializer's association. - # - # @api private - Association = Struct.new(:reflection, :association_options) do - attr_reader :lazy_association - delegate :object, :include_data?, :virtual_value, :collection?, to: :lazy_association - - def initialize(*) - super - @lazy_association = LazyAssociation.new(reflection, association_options) - end - - # @return [Symbol] - delegate :name, to: :reflection - - # @return [Symbol] - def key - reflection_options.fetch(:key, name) - end - - # @return [True,False] - def key? - reflection_options.key?(:key) - end - - # @return [Hash] - def links - reflection_options.fetch(:links) || {} - end - - # @return [Hash, nil] - # This gets mutated, so cannot use the cached reflection_options - def meta - reflection.options[:meta] - end - - def belongs_to? - reflection.foreign_key_on == :self - end - - def polymorphic? - true == reflection_options[:polymorphic] - end - - # @api private - def serializable_hash(adapter_options, adapter_instance) - association_serializer = lazy_association.serializer - return virtual_value if virtual_value - association_object = association_serializer && association_serializer.object - return unless association_object - - serialization = association_serializer.serializable_hash(adapter_options, {}, adapter_instance) - - if polymorphic? && serialization - polymorphic_type = association_object.class.name.underscore - serialization = { type: polymorphic_type, polymorphic_type.to_sym => serialization } - end - - serialization - end - - private - - delegate :reflection_options, to: :lazy_association - end - end -end diff --git a/lib/active_model/serializer/attribute.rb b/lib/active_model/serializer/attribute.rb deleted file mode 100644 index d3e006fa..00000000 --- a/lib/active_model/serializer/attribute.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'active_model/serializer/field' - -module ActiveModel - class Serializer - # Holds all the meta-data about an attribute as it was specified in the - # ActiveModel::Serializer class. - # - # @example - # class PostSerializer < ActiveModel::Serializer - # attribute :content - # attribute :name, key: :title - # attribute :email, key: :author_email, if: :user_logged_in? - # attribute :preview do - # truncate(object.content) - # end - # - # def user_logged_in? - # current_user.logged_in? - # end - # end - # - class Attribute < Field - end - end -end diff --git a/lib/active_model/serializer/belongs_to_reflection.rb b/lib/active_model/serializer/belongs_to_reflection.rb deleted file mode 100644 index 04bbc6fc..00000000 --- a/lib/active_model/serializer/belongs_to_reflection.rb +++ /dev/null @@ -1,11 +0,0 @@ -module ActiveModel - class Serializer - # @api private - class BelongsToReflection < Reflection - # @api private - def foreign_key_on - :self - end - end - end -end diff --git a/lib/active_model/serializer/collection_serializer.rb b/lib/active_model/serializer/collection_serializer.rb deleted file mode 100644 index 44b806a1..00000000 --- a/lib/active_model/serializer/collection_serializer.rb +++ /dev/null @@ -1,87 +0,0 @@ -module ActiveModel - class Serializer - class CollectionSerializer - include Enumerable - delegate :each, to: :@serializers - - attr_reader :object, :root - - def initialize(resources, options = {}) - @object = resources - @options = options - @root = options[:root] - @serializers = serializers_from_resources - end - - def success? - true - end - - # @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) - serializers.map do |serializer| - serializer.serializable_hash(adapter_opts, options, adapter_instance) - end - end - - # TODO: unify naming of root, json_key, and _type. Right now, a serializer's - # json_key comes from the root option or the object's model name, by default. - # But, if a dev defines a custom `json_key` method with an explicit value, - # we have no simple way to know that it is safe to call that instance method. - # (which is really a class property at this point, anyhow). - # rubocop:disable Metrics/CyclomaticComplexity - # Disabling cop since it's good to highlight the complexity of this method by - # including all the logic right here. - def json_key - return root if root - # 1. get from options[:serializer] for empty resource collection - key = object.empty? && - (explicit_serializer_class = options[:serializer]) && - explicit_serializer_class._type - # 2. get from first serializer instance in collection - key ||= (serializer = serializers.first) && serializer.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 - end - # rubocop:enable Metrics/CyclomaticComplexity - - def paginated? - ActiveModelSerializers.config.jsonapi_pagination_links_enabled && - object.respond_to?(:current_page) && - object.respond_to?(:total_pages) && - object.respond_to?(:size) - end - - protected - - attr_reader :serializers, :options - - private - - def serializers_from_resources - serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer) - object.map do |resource| - serializer_from_resource(resource, serializer_context_class, options) - end - end - - def serializer_from_resource(resource, serializer_context_class, options) - serializer_class = options.fetch(:serializer) do - serializer_context_class.serializer_for(resource, namespace: options[:namespace]) - end - - if serializer_class.nil? - ActiveModelSerializers.logger.debug "No serializer found for resource: #{resource.inspect}" - throw :no_serializer - else - serializer_class.new(resource, options.except(:serializer)) - end - end - end - end -end diff --git a/lib/active_model/serializer/concerns/caching.rb b/lib/active_model/serializer/concerns/caching.rb deleted file mode 100644 index 2a030b68..00000000 --- a/lib/active_model/serializer/concerns/caching.rb +++ /dev/null @@ -1,300 +0,0 @@ -module ActiveModel - class Serializer - UndefinedCacheKey = Class.new(StandardError) - module Caching - extend ActiveSupport::Concern - - included do - with_options instance_writer: false, instance_reader: false do |serializer| - serializer.class_attribute :_cache # @api private : the cache store - serializer.class_attribute :_cache_key # @api private : when present, is first item in cache_key. Ignored if the serializable object defines #cache_key. - serializer.class_attribute :_cache_only # @api private : when fragment caching, whitelists fetch_attributes. Cannot combine with except - serializer.class_attribute :_cache_except # @api private : when fragment caching, blacklists fetch_attributes. Cannot combine with only - serializer.class_attribute :_cache_options # @api private : used by CachedSerializer, passed to _cache.fetch - # _cache_options include: - # expires_in - # compress - # force - # race_condition_ttl - # Passed to ::_cache as - # serializer.cache_store.fetch(cache_key, @klass._cache_options) - # Passed as second argument to serializer.cache_store.fetch(cache_key, serializer_class._cache_options) - serializer.class_attribute :_cache_digest_file_path # @api private : Derived at inheritance - end - end - - # Matches - # "c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `'" - # AND - # "/c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `'" - # AS - # c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb - CALLER_FILE = / - \A # start of string - .+ # file path (one or more characters) - (?= # stop previous match when - :\d+ # a colon is followed by one or more digits - :in # followed by a colon followed by in - ) - /x - - module ClassMethods - def inherited(base) - caller_line = caller[1] - base._cache_digest_file_path = caller_line - super - end - - def _cache_digest - return @_cache_digest if defined?(@_cache_digest) - @_cache_digest = digest_caller_file(_cache_digest_file_path) - end - - # Hashes contents of file for +_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) - rescue TypeError, Errno::ENOENT - warn <<-EOF.strip_heredoc - Cannot digest non-existent file: '#{caller_line}'. - Please set `::_cache_digest` of the serializer - if you'd like to cache it. - EOF - ''.freeze - end - - def _skip_digest? - _cache_options && _cache_options[:skip_digest] - end - - # @api private - # maps attribute value to explicit key name - # @see Serializer::attribute - # @see Serializer::fragmented_attributes - def _attributes_keys - _attributes_data - .each_with_object({}) do |(key, attr), hash| - next if key == attr.name - hash[attr.name] = { key: key } - end - end - - def fragmented_attributes - cached = _cache_only ? _cache_only : _attributes - _cache_except - cached = cached.map! { |field| _attributes_keys.fetch(field, field) } - non_cached = _attributes - cached - non_cached = non_cached.map! { |field| _attributes_keys.fetch(field, field) } - { - cached: cached, - non_cached: non_cached - } - end - - # Enables a serializer to be automatically cached - # - # Sets +::_cache+ object to ActionController::Base.cache_store - # when Rails.configuration.action_controller.perform_caching - # - # @param options [Hash] with valid keys: - # cache_store : @see ::_cache - # key : @see ::_cache_key - # only : @see ::_cache_only - # except : @see ::_cache_except - # skip_digest : does not include digest in cache_key - # all else : @see ::_cache_options - # - # @example - # class PostSerializer < ActiveModel::Serializer - # cache key: 'post', expires_in: 3.hours - # attributes :title, :body - # - # has_many :comments - # end - # - # @todo require less code comments. See - # https://github.com/rails-api/active_model_serializers/pull/1249#issuecomment-146567837 - def cache(options = {}) - self._cache = - options.delete(:cache_store) || - ActiveModelSerializers.config.cache_store || - ActiveSupport::Cache.lookup_store(:null_store) - self._cache_key = options.delete(:key) - self._cache_only = options.delete(:only) - self._cache_except = options.delete(:except) - self._cache_options = options.empty? ? nil : options - end - - # Value is from ActiveModelSerializers.config.perform_caching. Is used to - # globally enable or disable all serializer caching, just like - # Rails.configuration.action_controller.perform_caching, which is its - # default value in a Rails application. - # @return [true, false] - # Memoizes value of config first time it is called with a non-nil value. - # rubocop:disable Style/ClassVars - def perform_caching - return @@perform_caching if defined?(@@perform_caching) && !@@perform_caching.nil? - @@perform_caching = ActiveModelSerializers.config.perform_caching - end - alias perform_caching? perform_caching - # rubocop:enable Style/ClassVars - - # The canonical method for getting the cache store for the serializer. - # - # @return [nil] when _cache is not set (i.e. when `cache` has not been called) - # @return [._cache] when _cache is not the NullStore - # @return [ActiveModelSerializers.config.cache_store] when _cache is the NullStore. - # This is so we can use `cache` being called to mean the serializer should be cached - # even if ActiveModelSerializers.config.cache_store has not yet been set. - # That means that when _cache is the NullStore and ActiveModelSerializers.config.cache_store - # is configured, `cache_store` becomes `ActiveModelSerializers.config.cache_store`. - # @return [nil] when _cache is the NullStore and ActiveModelSerializers.config.cache_store is nil. - def cache_store - return nil if _cache.nil? - return _cache if _cache.class != ActiveSupport::Cache::NullStore - if ActiveModelSerializers.config.cache_store - self._cache = ActiveModelSerializers.config.cache_store - else - nil - end - end - - def cache_enabled? - perform_caching? && cache_store && !_cache_only && !_cache_except - end - - def fragment_cache_enabled? - perform_caching? && cache_store && - (_cache_only && !_cache_except || !_cache_only && _cache_except) - end - - # Read cache from cache_store - # @return [Hash] - # Used in CollectionSerializer to set :cached_attributes - def cache_read_multi(collection_serializer, adapter_instance, include_directive) - return {} if ActiveModelSerializers.config.cache_store.blank? - - keys = object_cache_keys(collection_serializer, adapter_instance, include_directive) - - return {} if keys.blank? - - ActiveModelSerializers.config.cache_store.read_multi(*keys) - end - - # Find all cache_key for the collection_serializer - # @param serializers [ActiveModel::Serializer::CollectionSerializer] - # @param adapter_instance [ActiveModelSerializers::Adapter::Base] - # @param include_directive [JSONAPI::IncludeDirective] - # @return [Array] all cache_key of collection_serializer - def object_cache_keys(collection_serializer, adapter_instance, include_directive) - cache_keys = [] - - collection_serializer.each do |serializer| - cache_keys << object_cache_key(serializer, adapter_instance) - - serializer.associations(include_directive).each do |association| - # TODO(BF): Process relationship without evaluating lazy_association - association_serializer = association.lazy_association.serializer - if association_serializer.respond_to?(:each) - association_serializer.each do |sub_serializer| - cache_keys << object_cache_key(sub_serializer, adapter_instance) - end - else - cache_keys << object_cache_key(association_serializer, adapter_instance) - end - end - end - - cache_keys.compact.uniq - end - - # @return [String, nil] the cache_key of the serializer or nil - def object_cache_key(serializer, adapter_instance) - return unless serializer.present? && serializer.object.present? - - (serializer.class.cache_enabled? || serializer.class.fragment_cache_enabled?) ? serializer.cache_key(adapter_instance) : nil - end - end - - ### INSTANCE METHODS - def fetch_attributes(fields, cached_attributes, adapter_instance) - key = cache_key(adapter_instance) - cached_attributes.fetch(key) do - fetch(adapter_instance, serializer_class._cache_options, key) do - attributes(fields, true) - end - end - end - - def fetch(adapter_instance, cache_options = serializer_class._cache_options, key = nil) - if serializer_class.cache_store - key ||= cache_key(adapter_instance) - serializer_class.cache_store.fetch(key, cache_options) do - yield - end - else - yield - end - end - - # 1. Determine cached fields from serializer class options - # 2. Get non_cached_fields and fetch cache_fields - # 3. Merge the two hashes using adapter_instance#fragment_cache - def fetch_attributes_fragment(adapter_instance, cached_attributes = {}) - serializer_class._cache_options ||= {} - serializer_class._cache_options[:key] = serializer_class._cache_key if serializer_class._cache_key - fields = serializer_class.fragmented_attributes - - non_cached_fields = fields[:non_cached].dup - non_cached_hash = attributes(non_cached_fields, true) - include_directive = JSONAPI::IncludeDirective.new(non_cached_fields - non_cached_hash.keys) - non_cached_hash.merge! associations_hash({}, { include_directive: include_directive }, adapter_instance) - - cached_fields = fields[:cached].dup - key = cache_key(adapter_instance) - cached_hash = - cached_attributes.fetch(key) do - fetch(adapter_instance, serializer_class._cache_options, key) do - hash = attributes(cached_fields, true) - include_directive = JSONAPI::IncludeDirective.new(cached_fields - hash.keys) - hash.merge! associations_hash({}, { include_directive: include_directive }, adapter_instance) - end - end - # Merge both results - adapter_instance.fragment_cache(cached_hash, non_cached_hash) - end - - def cache_key(adapter_instance) - return @cache_key if defined?(@cache_key) - - parts = [] - parts << object_cache_key - parts << adapter_instance.cache_key - parts << serializer_class._cache_digest unless serializer_class._skip_digest? - @cache_key = expand_cache_key(parts) - end - - def expand_cache_key(parts) - ActiveSupport::Cache.expand_cache_key(parts) - end - - # 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) - object.cache_key - elsif (serializer_cache_key = (serializer_class._cache_key || serializer_class._cache_options[:key])) - object_time_safe = object.updated_at - object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime) - "#{serializer_cache_key}/#{object.id}-#{object_time_safe}" - else - fail UndefinedCacheKey, "#{object.class} must define #cache_key, or the 'key:' option must be passed into '#{serializer_class}.cache'" - end - end - - def serializer_class - @serializer_class ||= self.class - end - end - end -end diff --git a/lib/active_model/serializer/error_serializer.rb b/lib/active_model/serializer/error_serializer.rb deleted file mode 100644 index d0e70809..00000000 --- a/lib/active_model/serializer/error_serializer.rb +++ /dev/null @@ -1,14 +0,0 @@ -module ActiveModel - class Serializer - class ErrorSerializer < ActiveModel::Serializer - # @return [Hash>] - def as_json - object.errors.messages - end - - def success? - false - end - end - end -end diff --git a/lib/active_model/serializer/errors_serializer.rb b/lib/active_model/serializer/errors_serializer.rb deleted file mode 100644 index 1fd924d5..00000000 --- a/lib/active_model/serializer/errors_serializer.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'active_model/serializer/error_serializer' - -module ActiveModel - class Serializer - class ErrorsSerializer - include Enumerable - delegate :each, to: :@serializers - attr_reader :object, :root - - def initialize(resources, options = {}) - @root = options[:root] - @object = resources - @serializers = resources.map do |resource| - serializer_class = options.fetch(:serializer) { ActiveModel::Serializer::ErrorSerializer } - serializer_class.new(resource, options.except(:serializer)) - end - end - - def success? - false - end - - def json_key - nil - end - - protected - - attr_reader :serializers - end - end -end diff --git a/lib/active_model/serializer/field.rb b/lib/active_model/serializer/field.rb deleted file mode 100644 index 6299b099..00000000 --- a/lib/active_model/serializer/field.rb +++ /dev/null @@ -1,90 +0,0 @@ -module ActiveModel - class Serializer - # Holds all the meta-data about a field (i.e. attribute or association) as it was - # specified in the ActiveModel::Serializer class. - # Notice that the field block is evaluated in the context of the serializer. - Field = Struct.new(:name, :options, :block) do - def initialize(*) - super - - validate_condition! - end - - # Compute the actual value of a field for a given serializer instance. - # @param [Serializer] The serializer instance for which the value is computed. - # @return [Object] value - # - # @api private - # - def value(serializer) - if block - serializer.instance_eval(&block) - else - serializer.read_attribute_for_serialization(name) - end - end - - # Decide whether the field should be serialized by the given serializer instance. - # @param [Serializer] The serializer instance - # @return [Bool] - # - # @api private - # - def excluded?(serializer) - case condition_type - when :if - !evaluate_condition(serializer) - when :unless - evaluate_condition(serializer) - else - false - end - end - - private - - def validate_condition! - return if condition_type == :none - - case condition - when Symbol, String, Proc - # noop - else - fail TypeError, "#{condition_type.inspect} should be a Symbol, String or Proc" - end - end - - def evaluate_condition(serializer) - case condition - when Symbol - serializer.public_send(condition) - when String - serializer.instance_eval(condition) - when Proc - if condition.arity.zero? - serializer.instance_exec(&condition) - else - serializer.instance_exec(serializer, &condition) - end - else - nil - end - end - - def condition_type - @condition_type ||= - if options.key?(:if) - :if - elsif options.key?(:unless) - :unless - else - :none - end - end - - def condition - options[condition_type] - end - end - end -end diff --git a/lib/active_model/serializer/fieldset.rb b/lib/active_model/serializer/fieldset.rb deleted file mode 100644 index efa3187c..00000000 --- a/lib/active_model/serializer/fieldset.rb +++ /dev/null @@ -1,31 +0,0 @@ -module ActiveModel - class Serializer - class Fieldset - def initialize(fields) - @raw_fields = fields || {} - end - - def fields - @fields ||= parsed_fields - end - - def fields_for(type) - fields[type.singularize.to_sym] || fields[type.pluralize.to_sym] - end - - protected - - attr_reader :raw_fields - - private - - def parsed_fields - if raw_fields.is_a?(Hash) - raw_fields.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.map(&:to_sym) } - else - {} - end - end - end - end -end diff --git a/lib/active_model/serializer/has_many_reflection.rb b/lib/active_model/serializer/has_many_reflection.rb deleted file mode 100644 index 99f6f63c..00000000 --- a/lib/active_model/serializer/has_many_reflection.rb +++ /dev/null @@ -1,10 +0,0 @@ -module ActiveModel - class Serializer - # @api private - class HasManyReflection < Reflection - def collection? - true - end - end - end -end diff --git a/lib/active_model/serializer/has_one_reflection.rb b/lib/active_model/serializer/has_one_reflection.rb deleted file mode 100644 index a385009b..00000000 --- a/lib/active_model/serializer/has_one_reflection.rb +++ /dev/null @@ -1,7 +0,0 @@ -module ActiveModel - class Serializer - # @api private - class HasOneReflection < Reflection - end - end -end diff --git a/lib/active_model/serializer/lazy_association.rb b/lib/active_model/serializer/lazy_association.rb deleted file mode 100644 index 8c4dad61..00000000 --- a/lib/active_model/serializer/lazy_association.rb +++ /dev/null @@ -1,95 +0,0 @@ -module ActiveModel - class Serializer - # @api private - LazyAssociation = Struct.new(:reflection, :association_options) do - REFLECTION_OPTIONS = %i(key links polymorphic meta serializer virtual_value namespace).freeze - - delegate :collection?, to: :reflection - - def reflection_options - @reflection_options ||= reflection.options.dup.reject { |k, _| !REFLECTION_OPTIONS.include?(k) } - end - - def object - @object ||= reflection.value( - association_options.fetch(:parent_serializer), - association_options.fetch(:include_slice) - ) - end - alias_method :eval_reflection_block, :object - - def include_data? - eval_reflection_block if reflection.block - reflection.include_data?( - association_options.fetch(:include_slice) - ) - end - - # @return [ActiveModel::Serializer, nil] - def serializer - return @serializer if defined?(@serializer) - if serializer_class - serialize_object!(object) - elsif !object.nil? && !object.instance_of?(Object) - cached_result[:virtual_value] = object - end - @serializer = cached_result[:serializer] - end - - def virtual_value - cached_result[:virtual_value] || reflection_options[:virtual_value] - end - - def serializer_class - return @serializer_class if defined?(@serializer_class) - serializer_for_options = { namespace: namespace } - serializer_for_options[:serializer] = reflection_options[:serializer] if reflection_options.key?(:serializer) - @serializer_class = association_options.fetch(:parent_serializer).class.serializer_for(object, serializer_for_options) - end - - private - - def cached_result - @cached_result ||= {} - end - - def serialize_object!(object) - if collection? - if (serializer = instantiate_collection_serializer(object)).nil? - # BUG: per #2027, JSON API resource relationships are only id and type, and hence either - # *require* a serializer or we need to be a little clever about figuring out the id/type. - # In either case, returning the raw virtual value will almost always be incorrect. - # - # Should be reflection_options[:virtual_value] or adapter needs to figure out what to do - # with an object that is non-nil and has no defined serializer. - cached_result[:virtual_value] = object.try(:as_json) || object - else - cached_result[:serializer] = serializer - end - else - cached_result[:serializer] = instantiate_serializer(object) - end - end - - def instantiate_serializer(object) - serializer_options = association_options.fetch(:parent_serializer_options).except(:serializer) - serializer_options[:serializer_context_class] = association_options.fetch(:parent_serializer).class - serializer = reflection_options.fetch(:serializer, nil) - serializer_options[:serializer] = serializer if serializer - serializer_class.new(object, serializer_options) - end - - def instantiate_collection_serializer(object) - serializer = catch(:no_serializer) do - instantiate_serializer(object) - end - serializer - end - - def namespace - reflection_options[:namespace] || - association_options.fetch(:parent_serializer_options)[:namespace] - end - end - end -end diff --git a/lib/active_model/serializer/lint.rb b/lib/active_model/serializer/lint.rb deleted file mode 100644 index c40cebeb..00000000 --- a/lib/active_model/serializer/lint.rb +++ /dev/null @@ -1,150 +0,0 @@ -module ActiveModel - class Serializer - module Lint - # == Active \Model \Serializer \Lint \Tests - # - # You can test whether an object is compliant with the Active \Model \Serializers - # API by including ActiveModel::Serializer::Lint::Tests in your TestCase. - # It will include tests that tell you whether your object is fully compliant, - # or if not, which aspects of the API are not implemented. - # - # Note an object is not required to implement all APIs in order to work - # with Active \Model \Serializers. This module only intends to provide guidance in case - # you want all features out of the box. - # - # These tests do not attempt to determine the semantic correctness of the - # returned values. For instance, you could implement serializable_hash to - # always return +{}+, and the tests would pass. It is up to you to ensure - # that the values are semantically meaningful. - module Tests - # Passes if the object responds to serializable_hash and if it takes - # zero or one arguments. - # Fails otherwise. - # - # serializable_hash returns a hash representation of a object's attributes. - # Typically, it is implemented by including ActiveModel::Serialization. - def test_serializable_hash - assert_respond_to resource, :serializable_hash, 'The resource should respond to serializable_hash' - resource.serializable_hash - resource.serializable_hash(nil) - end - - # Passes if the object responds to read_attribute_for_serialization - # and if it requires one argument (the attribute to be read). - # Fails otherwise. - # - # read_attribute_for_serialization gets the attribute value for serialization - # Typically, it is implemented by including ActiveModel::Serialization. - def test_read_attribute_for_serialization - assert_respond_to resource, :read_attribute_for_serialization, 'The resource should respond to read_attribute_for_serialization' - actual_arity = resource.method(:read_attribute_for_serialization).arity - # using absolute value since arity is: - # 1 for def read_attribute_for_serialization(name); end - # -1 for alias :read_attribute_for_serialization :send - assert_equal 1, actual_arity.abs, "expected #{actual_arity.inspect}.abs to be 1 or -1" - end - - # Passes if the object responds to as_json and if it takes - # zero or one arguments. - # Fails otherwise. - # - # as_json returns a hash representation of a serialized object. - # It may delegate to serializable_hash - # Typically, it is implemented either by including ActiveModel::Serialization - # which includes ActiveModel::Serializers::JSON. - # or by the JSON gem when required. - def test_as_json - assert_respond_to resource, :as_json - resource.as_json - resource.as_json(nil) - end - - # Passes if the object responds to to_json and if it takes - # zero or one arguments. - # Fails otherwise. - # - # to_json returns a string representation (JSON) of a serialized object. - # It may be called on the result of as_json. - # Typically, it is implemented on all objects when the JSON gem is required. - def test_to_json - assert_respond_to resource, :to_json - resource.to_json - resource.to_json(nil) - end - - # Passes if the object responds to cache_key - # Fails otherwise. - # - # cache_key returns a (self-expiring) unique key for the object, - # and is part of the (self-expiring) cache_key, which is used by the - # adapter. It is not required unless caching is enabled. - def test_cache_key - assert_respond_to resource, :cache_key - actual_arity = resource.method(:cache_key).arity - assert_includes [-1, 0], actual_arity, "expected #{actual_arity.inspect} to be 0 or -1" - end - - # Passes if the object responds to updated_at and if it takes no - # arguments. - # Fails otherwise. - # - # updated_at returns a Time object or iso8601 string and - # is part of the (self-expiring) cache_key, which is used by the adapter. - # It is not required unless caching is enabled. - def test_updated_at - assert_respond_to resource, :updated_at - actual_arity = resource.method(:updated_at).arity - assert_equal 0, actual_arity - end - - # Passes if the object responds to id and if it takes no - # arguments. - # Fails otherwise. - # - # id returns a unique identifier for the object. - # It is not required unless caching is enabled. - def test_id - assert_respond_to resource, :id - assert_equal 0, resource.method(:id).arity - end - - # Passes if the object's class responds to model_name and if it - # is in an instance of +ActiveModel::Name+. - # Fails otherwise. - # - # model_name returns an ActiveModel::Name instance. - # It is used by the serializer to identify the object's type. - # It is not required unless caching is enabled. - def test_model_name - resource_class = resource.class - assert_respond_to resource_class, :model_name - assert_instance_of resource_class.model_name, ActiveModel::Name - end - - def test_active_model_errors - assert_respond_to resource, :errors - end - - def test_active_model_errors_human_attribute_name - assert_respond_to resource.class, :human_attribute_name - assert_equal(-2, resource.class.method(:human_attribute_name).arity) - end - - def test_active_model_errors_lookup_ancestors - assert_respond_to resource.class, :lookup_ancestors - assert_equal 0, resource.class.method(:lookup_ancestors).arity - end - - private - - def resource - @resource or fail "'@resource' must be set as the linted object" - end - - def assert_instance_of(result, name) - assert result.instance_of?(name), "#{result} should be an instance of #{name}" - end - end - end - end -end diff --git a/lib/active_model/serializer/null.rb b/lib/active_model/serializer/null.rb deleted file mode 100644 index 818bbbfa..00000000 --- a/lib/active_model/serializer/null.rb +++ /dev/null @@ -1,17 +0,0 @@ -module ActiveModel - class Serializer - class Null < Serializer - def attributes(*) - {} - end - - def associations(*) - {} - end - - def serializable_hash(*) - {} - end - end - end -end diff --git a/lib/active_model/serializer/reflection.rb b/lib/active_model/serializer/reflection.rb deleted file mode 100644 index 2e5cc2a1..00000000 --- a/lib/active_model/serializer/reflection.rb +++ /dev/null @@ -1,207 +0,0 @@ -require 'active_model/serializer/field' -require 'active_model/serializer/association' - -module ActiveModel - class Serializer - # Holds all the meta-data about an association as it was specified in the - # ActiveModel::Serializer class. - # - # @example - # class PostSerializer < ActiveModel::Serializer - # has_one :author, serializer: AuthorSerializer - # belongs_to :boss, type: :users, foreign_key: :boss_id - # has_many :comments - # has_many :comments, key: :last_comments do - # object.comments.last(1) - # end - # has_many :secret_meta_data, if: :is_admin? - # - # has_one :blog do |serializer| - # meta count: object.roles.count - # serializer.cached_blog - # end - # - # private - # - # def cached_blog - # cache_store.fetch("cached_blog:#{object.updated_at}") do - # Blog.find(object.blog_id) - # end - # end - # - # def is_admin? - # current_user.admin? - # end - # end - # - # Specifically, the association 'comments' is evaluated two different ways: - # 1) as 'comments' and named 'comments'. - # 2) as 'object.comments.last(1)' and named 'last_comments'. - # - # PostSerializer._reflections # => - # # { - # # author: HasOneReflection.new(:author, serializer: AuthorSerializer), - # # comments: HasManyReflection.new(:comments) - # # last_comments: HasManyReflection.new(:comments, { key: :last_comments }, #) - # # secret_meta_data: HasManyReflection.new(:secret_meta_data, { if: :is_admin? }) - # # } - # - # So you can inspect reflections in your Adapters. - class Reflection < Field - attr_reader :foreign_key, :type - - def initialize(*) - super - options[:links] = {} - options[:include_data_setting] = Serializer.config.include_data_default - options[:meta] = nil - @type = options.fetch(:type) do - class_name = options.fetch(:class_name, name.to_s.camelize.singularize) - class_name.underscore.pluralize.to_sym - end - @foreign_key = options.fetch(:foreign_key) do - if collection? - "#{name.to_s.singularize}_ids".to_sym - else - "#{name}_id".to_sym - end - end - end - - # @api public - # @example - # has_one :blog do - # include_data false - # link :self, 'a link' - # link :related, 'another link' - # link :self, '//example.com/link_author/relationships/bio' - # id = object.profile.id - # link :related do - # "//example.com/profiles/#{id}" if id != 123 - # end - # link :related do - # ids = object.likes.map(&:id).join(',') - # href "//example.com/likes/#{ids}" - # meta ids: ids - # end - # end - def link(name, value = nil) - options[:links][name] = block_given? ? Proc.new : value - :nil - end - - # @api public - # @example - # has_one :blog do - # include_data false - # meta(id: object.blog.id) - # meta liked: object.likes.any? - # link :self do - # href object.blog.id.to_s - # meta(id: object.blog.id) - # end - def meta(value = nil) - options[:meta] = block_given? ? Proc.new : value - :nil - end - - # @api public - # @example - # has_one :blog do - # include_data false - # link :self, 'a link' - # link :related, 'another link' - # end - # - # has_one :blog do - # include_data false - # link :self, 'a link' - # link :related, 'another link' - # end - # - # belongs_to :reviewer do - # meta name: 'Dan Brown' - # include_data true - # end - # - # has_many :tags, serializer: TagSerializer do - # link :self, '//example.com/link_author/relationships/tags' - # include_data :if_sideloaded - # end - def include_data(value = true) - options[:include_data_setting] = value - :nil - end - - def collection? - false - end - - 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 true then true - when false then false - else fail ArgumentError, "Unknown include_data_setting '#{include_data_setting.inspect}'" - end - end - - # @param serializer [ActiveModel::Serializer] - # @yield [ActiveModel::Serializer] - # @return [:nil, associated resource or resource collection] - def value(serializer, include_slice) - @object = serializer.object - @scope = serializer.scope - - block_value = instance_exec(serializer, &block) if block - return unless include_data?(include_slice) - - if block && block_value != :nil - block_value - else - serializer.read_attribute_for_serialization(name) - end - end - - # @api private - def foreign_key_on - :related - end - - # Build association. This method is used internally to - # build serializer's association by its reflection. - # - # @param [Serializer] parent_serializer for given association - # @param [Hash{Symbol => Object}] parent_serializer_options - # - # @example - # # Given the following serializer defined: - # class PostSerializer < ActiveModel::Serializer - # has_many :comments, serializer: CommentSummarySerializer - # end - # - # # Then you instantiate your serializer - # post_serializer = PostSerializer.new(post, foo: 'bar') # - # # to build association for comments you need to get reflection - # comments_reflection = PostSerializer._reflections.detect { |r| r.name == :comments } - # # and #build_association - # comments_reflection.build_association(post_serializer, foo: 'bar') - # - # @api private - def build_association(parent_serializer, parent_serializer_options, include_slice = {}) - association_options = { - parent_serializer: parent_serializer, - parent_serializer_options: parent_serializer_options, - include_slice: include_slice - } - Association.new(self, association_options) - end - - protected - - # used in instance exec - attr_accessor :object, :scope - end - end -end diff --git a/lib/active_model/serializer/version.rb b/lib/active_model/serializer/version.rb deleted file mode 100644 index e692240a..00000000 --- a/lib/active_model/serializer/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -module ActiveModel - class Serializer - VERSION = '0.10.6'.freeze - end -end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb deleted file mode 100644 index 18cdd9f7..00000000 --- a/lib/active_model_serializers.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'active_model' -require 'active_support' -require 'active_support/core_ext/object/with_options' -require 'active_support/core_ext/string/inflections' -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 - - class << self; attr_accessor :logger; end - self.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) - - def self.config - ActiveModel::Serializer.config - end - - # The file name and line number of the caller of the caller of this method. - def self.location_of_caller - caller[1] =~ /(.*?):(\d+).*?$/i - file = Regexp.last_match(1) - lineno = Regexp.last_match(2).to_i - - [file, lineno] - end - - # Memoized default include directive - # @return [JSONAPI::IncludeDirective] - def self.default_include_directive - @default_include_directive ||= JSONAPI::IncludeDirective.new(config.default_includes, allow_wildcard: true) - end - - def self.silence_warnings - original_verbose = $VERBOSE - $VERBOSE = nil - yield - ensure - $VERBOSE = original_verbose - end - - require 'active_model/serializer/version' - require 'active_model/serializer' - require 'active_model/serializable_resource' - require 'active_model_serializers/railtie' if defined?(::Rails) -end diff --git a/lib/active_model_serializers/adapter.rb b/lib/active_model_serializers/adapter.rb deleted file mode 100644 index 98caab44..00000000 --- a/lib/active_model_serializers/adapter.rb +++ /dev/null @@ -1,98 +0,0 @@ -module ActiveModelSerializers - module Adapter - UnknownAdapterError = Class.new(ArgumentError) - ADAPTER_MAP = {} # rubocop:disable Style/MutableConstant - private_constant :ADAPTER_MAP if defined?(private_constant) - - class << self # All methods are class functions - # :nocov: - def new(*args) - fail ArgumentError, 'Adapters inherit from Adapter::Base.' \ - "Adapter.new called with args: '#{args.inspect}', from" \ - "'caller[0]'." - end - # :nocov: - - def configured_adapter - lookup(ActiveModelSerializers.config.adapter) - end - - def create(resource, options = {}) - override = options.delete(:adapter) - klass = override ? adapter_class(override) : configured_adapter - klass.new(resource, options) - end - - # @see ActiveModelSerializers::Adapter.lookup - def adapter_class(adapter) - ActiveModelSerializers::Adapter.lookup(adapter) - end - - # @return [Hash] - def adapter_map - ADAPTER_MAP - end - - # @return [Array] list of adapter names - def adapters - adapter_map.keys.sort - end - - # Adds an adapter 'klass' with 'name' to the 'adapter_map' - # Names are stringified and underscored - # @param name [Symbol, String, Class] name of the registered adapter - # @param klass [Class] adapter class itself, optional if name is the class - # @example - # AMS::Adapter.register(:my_adapter, MyAdapter) - # @note The registered name strips out 'ActiveModelSerializers::Adapter::' - # so that registering 'ActiveModelSerializers::Adapter::Json' and - # 'Json' will both register as 'json'. - def register(name, klass = name) - name = name.to_s.gsub(/\AActiveModelSerializers::Adapter::/, ''.freeze) - adapter_map[name.underscore] = klass - self - end - - def registered_name(adapter_class) - ADAPTER_MAP.key adapter_class - end - - # @param adapter [String, Symbol, Class] name to fetch adapter by - # @return [ActiveModelSerializers::Adapter] subclass of Adapter - # @raise [UnknownAdapterError] - def lookup(adapter) - # 1. return if is a class - return adapter if adapter.is_a?(Class) - adapter_name = adapter.to_s.underscore - # 2. return if registered - adapter_map.fetch(adapter_name) do - # 3. try to find adapter class from environment - adapter_class = find_by_name(adapter_name) - register(adapter_name, adapter_class) - adapter_class - end - rescue NameError, ArgumentError => e - failure_message = - "NameError: #{e.message}. Unknown adapter: #{adapter.inspect}. Valid adapters are: #{adapters}" - raise UnknownAdapterError, failure_message, e.backtrace - end - - # @api private - def find_by_name(adapter_name) - adapter_name = adapter_name.to_s.classify.tr('API', 'Api') - "ActiveModelSerializers::Adapter::#{adapter_name}".safe_constantize || - "ActiveModelSerializers::Adapter::#{adapter_name.pluralize}".safe_constantize or # rubocop:disable Style/AndOr - fail UnknownAdapterError - end - private :find_by_name - end - - # Gotta be at the bottom to use the code above it :( - extend ActiveSupport::Autoload - autoload :Base - autoload :Null - autoload :Attributes - autoload :Json - autoload :JsonApi - end -end diff --git a/lib/active_model_serializers/adapter/attributes.rb b/lib/active_model_serializers/adapter/attributes.rb deleted file mode 100644 index 79ca7b5f..00000000 --- a/lib/active_model_serializers/adapter/attributes.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ActiveModelSerializers - module Adapter - class Attributes < Base - def serializable_hash(options = nil) - options = serialization_options(options) - options[:fields] ||= instance_options[:fields] - serialized_hash = serializer.serializable_hash(instance_options, options, self) - - self.class.transform_key_casing!(serialized_hash, instance_options) - end - end - end -end diff --git a/lib/active_model_serializers/adapter/base.rb b/lib/active_model_serializers/adapter/base.rb deleted file mode 100644 index 85158328..00000000 --- a/lib/active_model_serializers/adapter/base.rb +++ /dev/null @@ -1,83 +0,0 @@ -require 'case_transform' - -module ActiveModelSerializers - module Adapter - class Base - # Automatically register adapters when subclassing - def self.inherited(subclass) - ActiveModelSerializers::Adapter.register(subclass) - end - - # Sets the default transform for the adapter. - # - # @return [Symbol] the default transform for the adapter - def self.default_key_transform - :unaltered - end - - # Determines the transform to use in order of precedence: - # adapter option, global config, adapter default. - # - # @param options [Object] - # @return [Symbol] the transform to use - def self.transform(options) - return options[:key_transform] if options && options[:key_transform] - ActiveModelSerializers.config.key_transform || default_key_transform - end - - # Transforms the casing of the supplied value. - # - # @param value [Object] the value to be transformed - # @param options [Object] serializable resource options - # @return [Symbol] the default transform for the adapter - def self.transform_key_casing!(value, options) - CaseTransform.send(transform(options), value) - end - - def self.cache_key - @cache_key ||= ActiveModelSerializers::Adapter.registered_name(self) - end - - def self.fragment_cache(cached_hash, non_cached_hash) - non_cached_hash.merge cached_hash - end - - attr_reader :serializer, :instance_options - - def initialize(serializer, options = {}) - @serializer = serializer - @instance_options = options - end - - # Subclasses that implement this method must first call - # options = serialization_options(options) - def serializable_hash(_options = nil) - fail NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.' - end - - def as_json(options = nil) - serializable_hash(options) - end - - def cache_key - self.class.cache_key - end - - def fragment_cache(cached_hash, non_cached_hash) - self.class.fragment_cache(cached_hash, non_cached_hash) - end - - private - - # see https://github.com/rails-api/active_model_serializers/pull/965 - # When options is +nil+, sets it to +{}+ - def serialization_options(options) - options ||= {} # rubocop:disable Lint/UselessAssignment - end - - def root - serializer.json_key.to_sym if serializer.json_key - end - end - end -end diff --git a/lib/active_model_serializers/adapter/json.rb b/lib/active_model_serializers/adapter/json.rb deleted file mode 100644 index 423cfb9f..00000000 --- a/lib/active_model_serializers/adapter/json.rb +++ /dev/null @@ -1,21 +0,0 @@ -module ActiveModelSerializers - module Adapter - class Json < Base - def serializable_hash(options = nil) - options = serialization_options(options) - serialized_hash = { root => Attributes.new(serializer, instance_options).serializable_hash(options) } - serialized_hash[meta_key] = meta unless meta.blank? - - self.class.transform_key_casing!(serialized_hash, instance_options) - end - - def meta - instance_options.fetch(:meta, nil) - end - - def meta_key - instance_options.fetch(:meta_key, 'meta'.freeze) - end - end - end -end diff --git a/lib/active_model_serializers/adapter/json_api.rb b/lib/active_model_serializers/adapter/json_api.rb deleted file mode 100644 index b225416b..00000000 --- a/lib/active_model_serializers/adapter/json_api.rb +++ /dev/null @@ -1,530 +0,0 @@ -# {http://jsonapi.org/format/ JSON API specification} -# rubocop:disable Style/AsciiComments -# TODO: implement! -# ☐ https://github.com/rails-api/active_model_serializers/issues/1235 -# TODO: use uri_template in link generation? -# ☐ https://github.com/rails-api/active_model_serializers/pull/1282#discussion_r42528812 -# see gem https://github.com/hannesg/uri_template -# spec http://tools.ietf.org/html/rfc6570 -# impl https://developer.github.com/v3/#schema https://api.github.com/ -# TODO: validate against a JSON schema document? -# ☐ https://github.com/rails-api/active_model_serializers/issues/1162 -# ☑ https://github.com/rails-api/active_model_serializers/pull/1270 -# TODO: Routing -# ☐ https://github.com/rails-api/active_model_serializers/pull/1476 -# TODO: Query Params -# ☑ `include` https://github.com/rails-api/active_model_serializers/pull/1131 -# ☑ `fields` https://github.com/rails-api/active_model_serializers/pull/700 -# ☑ `page[number]=3&page[size]=1` https://github.com/rails-api/active_model_serializers/pull/1041 -# ☐ `filter` -# ☐ `sort` -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 - - def self.default_key_transform - :dash - end - - def self.fragment_cache(cached_hash, non_cached_hash, root = true) - core_cached = cached_hash.first - core_non_cached = non_cached_hash.first - no_root_cache = cached_hash.delete_if { |key, _value| key == core_cached[0] } - no_root_non_cache = non_cached_hash.delete_if { |key, _value| key == core_non_cached[0] } - cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1] - hash = root ? { root => cached_resource } : cached_resource - - hash.deep_merge no_root_non_cache.deep_merge no_root_cache - end - - 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)) - end - - # {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure} - # {http://jsonapi.org/format/#document-top-level data and errors MUST NOT coexist in the same document.} - def serializable_hash(*) - document = if serializer.success? - success_document - else - failure_document - end - self.class.transform_key_casing!(document, instance_options) - end - - def fragment_cache(cached_hash, non_cached_hash) - root = !instance_options.include?(:include) - self.class.fragment_cache(cached_hash, non_cached_hash, root) - end - - # {http://jsonapi.org/format/#document-top-level Primary data} - # definition: - # ☐ toplevel_data (required) - # ☐ toplevel_included - # ☑ toplevel_meta - # ☑ toplevel_links - # ☑ toplevel_jsonapi - # structure: - # { - # data: toplevel_data, - # included: toplevel_included, - # meta: toplevel_meta, - # links: toplevel_links, - # jsonapi: toplevel_jsonapi - # }.reject! {|_,v| v.nil? } - # rubocop:disable Metrics/CyclomaticComplexity - def success_document - is_collection = serializer.respond_to?(:each) - serializers = is_collection ? serializer : [serializer] - primary_data, included = resource_objects_for(serializers) - - hash = {} - # toplevel_data - # definition: - # oneOf - # resource - # array of unique items of type 'resource' - # null - # - # description: - # The document's "primary data" is a representation of the resource or collection of resources - # targeted by a request. - # - # Singular: the resource object. - # - # Collection: one of an array of resource objects, an array of resource identifier objects, or - # an empty array ([]), for requests that target resource collections. - # - # None: null if the request is one that might correspond to a single resource, but doesn't currently. - # structure: - # if serializable_resource.resource? - # resource - # elsif serializable_resource.collection? - # [ - # resource, - # resource - # ] - # else - # nil - # end - hash[:data] = is_collection ? primary_data : primary_data[0] - # toplevel_included - # alias included - # definition: - # array of unique items of type 'resource' - # - # description: - # To reduce the number of HTTP requests, servers **MAY** allow - # responses that include related resources along with the requested primary - # resources. Such responses are called "compound documents". - # structure: - # [ - # resource, - # resource - # ] - hash[:included] = included if included.any? - - Jsonapi.add!(hash) - - if instance_options[:links] - hash[:links] ||= {} - hash[:links].update(instance_options[:links]) - end - - if is_collection && serializer.paginated? - hash[:links] ||= {} - hash[:links].update(pagination_links_for(serializer)) - end - - hash[:meta] = instance_options[:meta] unless instance_options[:meta].blank? - - hash - end - # rubocop:enable Metrics/CyclomaticComplexity - - # {http://jsonapi.org/format/#errors JSON API Errors} - # TODO: look into caching - # definition: - # ☑ toplevel_errors array (required) - # ☐ toplevel_meta - # ☐ toplevel_jsonapi - # structure: - # { - # errors: toplevel_errors, - # meta: toplevel_meta, - # jsonapi: toplevel_jsonapi - # }.reject! {|_,v| v.nil? } - # prs: - # https://github.com/rails-api/active_model_serializers/pull/1004 - def failure_document - hash = {} - # PR Please :) - # Jsonapi.add!(hash) - - # toplevel_errors - # definition: - # array of unique items of type 'error' - # structure: - # [ - # error, - # error - # ] - if serializer.respond_to?(:each) - hash[:errors] = serializer.flat_map do |error_serializer| - Error.resource_errors(error_serializer, instance_options) - end - else - hash[:errors] = Error.resource_errors(serializer, instance_options) - end - hash - end - - protected - - attr_reader :fieldset - - private - - # {http://jsonapi.org/format/#document-resource-objects Primary data} - # resource - # definition: - # JSON Object - # - # properties: - # type (required) : String - # id (required) : String - # attributes - # relationships - # links - # meta - # - # description: - # "Resource objects" appear in a JSON API document to represent resources - # structure: - # { - # type: 'admin--some-user', - # id: '1336', - # attributes: attributes, - # relationships: relationships, - # links: links, - # meta: meta, - # }.reject! {|_,v| v.nil? } - # prs: - # type - # https://github.com/rails-api/active_model_serializers/pull/1122 - # [x] https://github.com/rails-api/active_model_serializers/pull/1213 - # https://github.com/rails-api/active_model_serializers/pull/1216 - # https://github.com/rails-api/active_model_serializers/pull/1029 - # links - # [x] https://github.com/rails-api/active_model_serializers/pull/1246 - # [x] url helpers https://github.com/rails-api/active_model_serializers/issues/1269 - # meta - # [x] https://github.com/rails-api/active_model_serializers/pull/1340 - def resource_objects_for(serializers) - @primary = [] - @included = [] - @resource_identifiers = Set.new - serializers.each { |serializer| process_resource(serializer, true, @include_directive) } - serializers.each { |serializer| process_relationships(serializer, @include_directive) } - - [@primary, @included] - end - - def process_resource(serializer, primary, include_slice = {}) - resource_identifier = ResourceIdentifier.new(serializer, instance_options).as_json - return false unless @resource_identifiers.add?(resource_identifier) - - resource_object = resource_object_for(serializer, include_slice) - if primary - @primary << resource_object - else - @included << resource_object - end - - true - end - - def process_relationships(serializer, include_slice) - serializer.associations(include_slice).each do |association| - # TODO(BF): Process relationship without evaluating lazy_association - process_relationship(association.lazy_association.serializer, include_slice[association.key]) - end - end - - def process_relationship(serializer, include_slice) - if serializer.respond_to?(:each) - serializer.each { |s| process_relationship(s, include_slice) } - return - end - return unless serializer && serializer.object - return unless process_resource(serializer, false, include_slice) - - process_relationships(serializer, include_slice) - end - - # {http://jsonapi.org/format/#document-resource-object-attributes Document Resource Object Attributes} - # attributes - # definition: - # JSON Object - # - # patternProperties: - # ^(?!relationships$|links$)\\w[-\\w_]*$ - # - # description: - # Members of the attributes object ("attributes") represent information about the resource - # object in which it's defined. - # Attributes may contain any valid JSON value - # structure: - # { - # foo: 'bar' - # } - def attributes_for(serializer, fields) - serializer.attributes(fields).except(:id) - end - - # {http://jsonapi.org/format/#document-resource-objects Document Resource Objects} - def resource_object_for(serializer, include_slice = {}) - resource_object = data_for(serializer, include_slice) - - # toplevel_links - # definition: - # allOf - # ☐ links - # ☐ pagination - # - # description: - # Link members related to the primary data. - # structure: - # links.merge!(pagination) - # prs: - # https://github.com/rails-api/active_model_serializers/pull/1247 - # https://github.com/rails-api/active_model_serializers/pull/1018 - if (links = links_for(serializer)).any? - resource_object ||= {} - resource_object[:links] = links - end - - # toplevel_meta - # alias meta - # definition: - # meta - # structure - # { - # :'git-ref' => 'abc123' - # } - if (meta = meta_for(serializer)).present? - resource_object ||= {} - resource_object[:meta] = meta - end - - resource_object - end - - def data_for(serializer, include_slice) - data = serializer.fetch(self) do - resource_object = ResourceIdentifier.new(serializer, instance_options).as_json - break nil if resource_object.nil? - - requested_fields = fieldset && fieldset.fields_for(resource_object[:type]) - attributes = attributes_for(serializer, requested_fields) - resource_object[:attributes] = attributes if attributes.any? - resource_object - end - 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]) || '*' - relationships = relationships_for(serializer, requested_associations, include_slice) - resource_object[:relationships] = relationships if relationships.any? - end - end - - # {http://jsonapi.org/format/#document-resource-object-relationships Document Resource Object Relationship} - # relationships - # definition: - # JSON Object - # - # patternProperties: - # ^\\w[-\\w_]*$" - # - # properties: - # data : relationshipsData - # links - # meta - # - # description: - # - # Members of the relationships object ("relationships") represent references from the - # resource object in which it's defined to other resource objects." - # structure: - # { - # links: links, - # meta: meta, - # data: relationshipsData - # }.reject! {|_,v| v.nil? } - # - # prs: - # links - # [x] https://github.com/rails-api/active_model_serializers/pull/1454 - # meta - # [x] https://github.com/rails-api/active_model_serializers/pull/1454 - # polymorphic - # [ ] https://github.com/rails-api/active_model_serializers/pull/1420 - # - # relationshipsData - # definition: - # oneOf - # relationshipToOne - # relationshipToMany - # - # description: - # Member, whose value represents "resource linkage" - # structure: - # if has_one? - # relationshipToOne - # else - # relationshipToMany - # end - # - # definition: - # anyOf - # null - # linkage - # - # relationshipToOne - # description: - # - # References to other resource objects in a to-one ("relationship"). Relationships can be - # specified by including a member in a resource's links object. - # - # None: Describes an empty to-one relationship. - # structure: - # if has_related? - # linkage - # else - # nil - # end - # - # relationshipToMany - # definition: - # array of unique items of type 'linkage' - # - # description: - # An array of objects each containing "type" and "id" members for to-many relationships - # structure: - # [ - # linkage, - # linkage - # ] - # prs: - # polymorphic - # [ ] https://github.com/rails-api/active_model_serializers/pull/1282 - # - # linkage - # definition: - # type (required) : String - # id (required) : String - # meta - # - # description: - # The "type" and "id" to non-empty members. - # structure: - # { - # type: 'required-type', - # id: 'required-id', - # meta: meta - # }.reject! {|_,v| v.nil? } - def relationships_for(serializer, requested_associations, include_slice) - include_directive = JSONAPI::IncludeDirective.new( - requested_associations, - allow_wildcard: true - ) - serializer.associations(include_directive, include_slice).each_with_object({}) do |association, hash| - hash[association.key] = Relationship.new(serializer, instance_options, association).as_json - end - end - - # {http://jsonapi.org/format/#document-links Document Links} - # links - # definition: - # JSON Object - # - # properties: - # self : URI - # related : link - # - # description: - # A resource object **MAY** contain references to other resource objects ("relationships"). - # Relationships may be to-one or to-many. Relationships can be specified by including a member - # in a resource's links object. - # - # A `self` member’s value is a URL for the relationship itself (a "relationship URL"). This - # URL allows the client to directly manipulate the relationship. For example, it would allow - # a client to remove an `author` from an `article` without deleting the people resource - # itself. - # structure: - # { - # self: 'http://example.com/etc', - # related: link - # }.reject! {|_,v| v.nil? } - def links_for(serializer) - serializer._links.each_with_object({}) do |(name, value), hash| - result = Link.new(serializer, value).as_json - hash[name] = result if result - end - end - - # {http://jsonapi.org/format/#fetching-pagination Pagination Links} - # pagination - # definition: - # first : pageObject - # last : pageObject - # prev : pageObject - # next : pageObject - # structure: - # { - # first: pageObject, - # last: pageObject, - # prev: pageObject, - # next: pageObject - # } - # - # pageObject - # definition: - # oneOf - # URI - # null - # - # description: - # The page of data - # structure: - # if has_page? - # 'http://example.com/some-page?page[number][x]' - # else - # nil - # end - # prs: - # https://github.com/rails-api/active_model_serializers/pull/1041 - def pagination_links_for(serializer) - PaginationLinks.new(serializer.object, instance_options).as_json - end - - # {http://jsonapi.org/format/#document-meta Docment Meta} - def meta_for(serializer) - Meta.new(serializer).as_json - end - end - end -end -# rubocop:enable Style/AsciiComments diff --git a/lib/active_model_serializers/adapter/json_api/deserialization.rb b/lib/active_model_serializers/adapter/json_api/deserialization.rb deleted file mode 100644 index b79125ac..00000000 --- a/lib/active_model_serializers/adapter/json_api/deserialization.rb +++ /dev/null @@ -1,213 +0,0 @@ -module ActiveModelSerializers - module Adapter - class JsonApi - # NOTE(Experimental): - # This is an experimental feature. Both the interface and internals could be subject - # to changes. - module Deserialization - InvalidDocument = Class.new(ArgumentError) - - module_function - - # Transform a JSON API document, containing a single data object, - # into a hash that is ready for ActiveRecord::Base.new() and such. - # Raises InvalidDocument if the payload is not properly formatted. - # - # @param [Hash|ActionController::Parameters] document - # @param [Hash] options - # only: Array of symbols of whitelisted fields. - # except: Array of symbols of blacklisted fields. - # keys: Hash of translated keys (e.g. :author => :user). - # polymorphic: Array of symbols of polymorphic fields. - # @return [Hash] - # - # @example - # document = { - # data: { - # id: 1, - # type: 'post', - # attributes: { - # title: 'Title 1', - # date: '2015-12-20' - # }, - # associations: { - # author: { - # data: { - # type: 'user', - # id: 2 - # } - # }, - # second_author: { - # data: nil - # }, - # comments: { - # data: [{ - # type: 'comment', - # id: 3 - # },{ - # type: 'comment', - # id: 4 - # }] - # } - # } - # } - # } - # - # parse(document) #=> - # # { - # # title: 'Title 1', - # # date: '2015-12-20', - # # author_id: 2, - # # second_author_id: nil - # # comment_ids: [3, 4] - # # } - # - # parse(document, only: [:title, :date, :author], - # keys: { date: :published_at }, - # polymorphic: [:author]) #=> - # # { - # # title: 'Title 1', - # # published_at: '2015-12-20', - # # author_id: '2', - # # author_type: 'people' - # # } - # - def parse!(document, options = {}) - parse(document, options) do |invalid_payload, reason| - fail InvalidDocument, "Invalid payload (#{reason}): #{invalid_payload}" - end - end - - # Same as parse!, but returns an empty hash instead of raising InvalidDocument - # on invalid payloads. - def parse(document, options = {}) - document = document.dup.permit!.to_h if document.is_a?(ActionController::Parameters) - - validate_payload(document) do |invalid_document, reason| - yield invalid_document, reason if block_given? - return {} - end - - primary_data = document['data'] - attributes = primary_data['attributes'] || {} - attributes['id'] = primary_data['id'] if primary_data['id'] - relationships = primary_data['relationships'] || {} - - filter_fields(attributes, options) - filter_fields(relationships, options) - - hash = {} - hash.merge!(parse_attributes(attributes, options)) - hash.merge!(parse_relationships(relationships, options)) - - hash - end - - # Checks whether a payload is compliant with the JSON API spec. - # - # @api private - # rubocop:disable Metrics/CyclomaticComplexity - def validate_payload(payload) - unless payload.is_a?(Hash) - yield payload, 'Expected hash' - return - end - - primary_data = payload['data'] - unless primary_data.is_a?(Hash) - yield payload, { data: 'Expected hash' } - return - end - - attributes = primary_data['attributes'] || {} - unless attributes.is_a?(Hash) - yield payload, { data: { attributes: 'Expected hash or nil' } } - return - end - - relationships = primary_data['relationships'] || {} - unless relationships.is_a?(Hash) - yield payload, { data: { relationships: 'Expected hash or nil' } } - return - end - - relationships.each do |(key, value)| - unless value.is_a?(Hash) && value.key?('data') - yield payload, { data: { relationships: { key => 'Expected hash with :data key' } } } - end - end - end - # rubocop:enable Metrics/CyclomaticComplexity - - # @api private - def filter_fields(fields, options) - if (only = options[:only]) - fields.slice!(*Array(only).map(&:to_s)) - elsif (except = options[:except]) - fields.except!(*Array(except).map(&:to_s)) - end - end - - # @api private - def field_key(field, options) - (options[:keys] || {}).fetch(field.to_sym, field).to_sym - end - - # @api private - def parse_attributes(attributes, options) - transform_keys(attributes, options) - .map { |(k, v)| { field_key(k, options) => v } } - .reduce({}, :merge) - end - - # Given an association name, and a relationship data attribute, build a hash - # mapping the corresponding ActiveRecord attribute to the corresponding value. - # - # @example - # parse_relationship(:comments, [{ 'id' => '1', 'type' => 'comments' }, - # { 'id' => '2', 'type' => 'comments' }], - # {}) - # # => { :comment_ids => ['1', '2'] } - # parse_relationship(:author, { 'id' => '1', 'type' => 'users' }, {}) - # # => { :author_id => '1' } - # parse_relationship(:author, nil, {}) - # # => { :author_id => nil } - # @param [Symbol] assoc_name - # @param [Hash] assoc_data - # @param [Hash] options - # @return [Hash{Symbol, Object}] - # - # @api private - def parse_relationship(assoc_name, assoc_data, options) - prefix_key = field_key(assoc_name, options).to_s.singularize - hash = - if assoc_data.is_a?(Array) - { "#{prefix_key}_ids".to_sym => assoc_data.map { |ri| ri['id'] } } - else - { "#{prefix_key}_id".to_sym => assoc_data ? assoc_data['id'] : nil } - end - - polymorphic = (options[:polymorphic] || []).include?(assoc_name.to_sym) - if polymorphic - hash["#{prefix_key}_type".to_sym] = assoc_data.present? ? assoc_data['type'] : nil - end - - hash - end - - # @api private - def parse_relationships(relationships, options) - transform_keys(relationships, options) - .map { |(k, v)| parse_relationship(k, v['data'], options) } - .reduce({}, :merge) - end - - # @api private - def transform_keys(hash, options) - transform = options[:key_transform] || :underscore - CaseTransform.send(transform, hash) - end - end - end - end -end diff --git a/lib/active_model_serializers/adapter/json_api/error.rb b/lib/active_model_serializers/adapter/json_api/error.rb deleted file mode 100644 index c7b18716..00000000 --- a/lib/active_model_serializers/adapter/json_api/error.rb +++ /dev/null @@ -1,96 +0,0 @@ -module ActiveModelSerializers - module Adapter - class JsonApi < Base - module Error - # rubocop:disable Style/AsciiComments - UnknownSourceTypeError = Class.new(ArgumentError) - - # Builds a JSON API Errors Object - # {http://jsonapi.org/format/#errors JSON API Errors} - # - # @param [ActiveModel::Serializer::ErrorSerializer] error_serializer - # @return [Array>] i.e. attribute_name, [attribute_errors] - def self.resource_errors(error_serializer, options) - error_serializer.as_json.flat_map do |attribute_name, attribute_errors| - attribute_name = JsonApi.send(:transform_key_casing!, attribute_name, - options) - attribute_error_objects(attribute_name, attribute_errors) - end - end - - # definition: - # JSON Object - # - # properties: - # ☐ id : String - # ☐ status : String - # ☐ code : String - # ☐ title : String - # ☑ detail : String - # ☐ links - # ☐ meta - # ☑ error_source - # - # description: - # id : A unique identifier for this particular occurrence of the problem. - # status : The HTTP status code applicable to this problem, expressed as a string value - # code : An application-specific error code, expressed as a string value. - # title : A short, human-readable summary of the problem. It **SHOULD NOT** change from - # occurrence to occurrence of the problem, except for purposes of localization. - # detail : A human-readable explanation specific to this occurrence of the problem. - # structure: - # { - # title: 'SystemFailure', - # detail: 'something went terribly wrong', - # status: '500' - # }.merge!(errorSource) - def self.attribute_error_objects(attribute_name, attribute_errors) - attribute_errors.map do |attribute_error| - { - source: error_source(:pointer, attribute_name), - detail: attribute_error - } - end - end - - # errorSource - # description: - # oneOf - # ☑ pointer : String - # ☑ parameter : String - # - # description: - # pointer: A JSON Pointer RFC6901 to the associated entity in the request document e.g. "/data" - # for a primary data object, or "/data/attributes/title" for a specific attribute. - # https://tools.ietf.org/html/rfc6901 - # - # parameter: A string indicating which query parameter caused the error - # structure: - # if is_attribute? - # { - # pointer: '/data/attributes/red-button' - # } - # else - # { - # parameter: 'pres' - # } - # end - def self.error_source(source_type, attribute_name) - case source_type - when :pointer - { - pointer: ActiveModelSerializers::JsonPointer.new(:attribute, attribute_name) - } - when :parameter - { - parameter: attribute_name - } - else - fail UnknownSourceTypeError, "Unknown source type '#{source_type}' for attribute_name '#{attribute_name}'" - end - end - # rubocop:enable Style/AsciiComments - end - end - end -end diff --git a/lib/active_model_serializers/adapter/json_api/jsonapi.rb b/lib/active_model_serializers/adapter/json_api/jsonapi.rb deleted file mode 100644 index e94578af..00000000 --- a/lib/active_model_serializers/adapter/json_api/jsonapi.rb +++ /dev/null @@ -1,49 +0,0 @@ -module ActiveModelSerializers - module Adapter - class JsonApi < Base - # {http://jsonapi.org/format/#document-jsonapi-object Jsonapi Object} - - # toplevel_jsonapi - # definition: - # JSON Object - # - # properties: - # version : String - # meta - # - # description: - # An object describing the server's implementation - # structure: - # { - # version: ActiveModelSerializers.config.jsonapi_version, - # meta: ActiveModelSerializers.config.jsonapi_toplevel_meta - # }.reject! { |_, v| v.blank? } - # prs: - # https://github.com/rails-api/active_model_serializers/pull/1050 - module Jsonapi - module_function - - def add!(hash) - hash.merge!(object) if include_object? - end - - def include_object? - ActiveModelSerializers.config.jsonapi_include_toplevel_object - end - - # TODO: see if we can cache this - def object - object = { - jsonapi: { - version: ActiveModelSerializers.config.jsonapi_version, - meta: ActiveModelSerializers.config.jsonapi_toplevel_meta - } - } - object[:jsonapi].reject! { |_, v| v.blank? } - - object - end - end - end - end -end diff --git a/lib/active_model_serializers/adapter/json_api/link.rb b/lib/active_model_serializers/adapter/json_api/link.rb deleted file mode 100644 index 64e15071..00000000 --- a/lib/active_model_serializers/adapter/json_api/link.rb +++ /dev/null @@ -1,83 +0,0 @@ -module ActiveModelSerializers - module Adapter - class JsonApi - # link - # definition: - # oneOf - # linkString - # linkObject - # - # description: - # A link **MUST** be represented as either: a string containing the link's URL or a link - # object." - # structure: - # if href? - # linkString - # else - # linkObject - # end - # - # linkString - # definition: - # URI - # - # description: - # A string containing the link's URL. - # structure: - # 'http://example.com/link-string' - # - # linkObject - # definition: - # JSON Object - # - # properties: - # href (required) : URI - # meta - # structure: - # { - # href: 'http://example.com/link-object', - # meta: meta, - # }.reject! {|_,v| v.nil? } - class Link - include SerializationContext::UrlHelpers - - def initialize(serializer, value) - @_routes ||= nil # handles warning - # actionpack-4.0.13/lib/action_dispatch/routing/route_set.rb:417: warning: instance variable @_routes not initialized - @object = serializer.object - @scope = serializer.scope - # Use the return value of the block unless it is nil. - if value.respond_to?(:call) - @value = instance_eval(&value) - else - @value = value - end - end - - def href(value) - @href = value - nil - end - - def meta(value) - @meta = value - nil - end - - def as_json - return @value if @value - - hash = {} - hash[:href] = @href if defined?(@href) - hash[:meta] = @meta if defined?(@meta) - - hash.any? ? hash : nil - end - - protected - - attr_reader :object, :scope - end - end - end -end diff --git a/lib/active_model_serializers/adapter/json_api/meta.rb b/lib/active_model_serializers/adapter/json_api/meta.rb deleted file mode 100644 index d889b3eb..00000000 --- a/lib/active_model_serializers/adapter/json_api/meta.rb +++ /dev/null @@ -1,37 +0,0 @@ -module ActiveModelSerializers - module Adapter - class JsonApi - # meta - # definition: - # JSON Object - # - # description: - # Non-standard meta-information that can not be represented as an attribute or relationship. - # structure: - # { - # attitude: 'adjustable' - # } - class Meta - def initialize(serializer) - @object = serializer.object - @scope = serializer.scope - - # Use the return value of the block unless it is nil. - if serializer._meta.respond_to?(:call) - @value = instance_eval(&serializer._meta) - else - @value = serializer._meta - end - end - - def as_json - @value - end - - protected - - attr_reader :object, :scope - end - end - end -end diff --git a/lib/active_model_serializers/adapter/json_api/pagination_links.rb b/lib/active_model_serializers/adapter/json_api/pagination_links.rb deleted file mode 100644 index b15f5ba6..00000000 --- a/lib/active_model_serializers/adapter/json_api/pagination_links.rb +++ /dev/null @@ -1,69 +0,0 @@ -module ActiveModelSerializers - module Adapter - class JsonApi < Base - class PaginationLinks - MissingSerializationContextError = Class.new(KeyError) - FIRST_PAGE = 1 - - attr_reader :collection, :context - - def initialize(collection, adapter_options) - @collection = collection - @adapter_options = adapter_options - @context = adapter_options.fetch(:serialization_context) do - fail MissingSerializationContextError, <<-EOF.freeze - JsonApi::PaginationLinks requires a ActiveModelSerializers::SerializationContext. - Please pass a ':serialization_context' option or - override CollectionSerializer#paginated? to return 'false'. - 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 - end - - protected - - attr_reader :adapter_options - - private - - def pages_from - return {} if collection.total_pages <= FIRST_PAGE - - {}.tap do |pages| - pages[:self] = collection.current_page - - unless collection.current_page == FIRST_PAGE - pages[:first] = FIRST_PAGE - pages[:prev] = collection.current_page - FIRST_PAGE - end - - unless collection.current_page == collection.total_pages - pages[:next] = collection.current_page + FIRST_PAGE - pages[:last] = collection.total_pages - end - end - end - - def url(options) - @url ||= options.fetch(:links, {}).fetch(:self, nil) || request_url - end - - def request_url - @request_url ||= context.request_url - end - - def query_parameters - @query_parameters ||= context.query_parameters - end - 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 deleted file mode 100644 index 5d7399a3..00000000 --- a/lib/active_model_serializers/adapter/json_api/relationship.rb +++ /dev/null @@ -1,92 +0,0 @@ -module ActiveModelSerializers - module Adapter - class JsonApi - class Relationship - # {http://jsonapi.org/format/#document-resource-object-related-resource-links Document Resource Object Related Resource Links} - # {http://jsonapi.org/format/#document-links Document Links} - # {http://jsonapi.org/format/#document-resource-object-linkage Document Resource Relationship Linkage} - # {http://jsonapi.org/format/#document-meta Document Meta} - def initialize(parent_serializer, serializable_resource_options, association) - @parent_serializer = parent_serializer - @association = association - @serializable_resource_options = serializable_resource_options - end - - def as_json - hash = {} - - hash[:data] = data_for(association) if association.include_data? - - links = links_for(association) - hash[:links] = links if links.any? - - meta = meta_for(association) - hash[:meta] = meta if meta - hash[:meta] = {} if hash.empty? - - hash - end - - protected - - attr_reader :parent_serializer, :serializable_resource_options, :association - - private - - # TODO(BF): Avoid db hit on belong_to_ releationship by using foreign_key on self - def data_for(association) - if association.collection? - data_for_many(association) - else - data_for_one(association) - end - 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 - ResourceIdentifier.for_type_with_id(type, id, serializable_resource_options) - else - # TODO(BF): Process relationship without evaluating lazy_association - serializer = association.lazy_association.serializer - if (virtual_value = association.virtual_value) - virtual_value - elsif serializer && association.object - ResourceIdentifier.new(serializer, serializable_resource_options).as_json - else - nil - end - end - end - - def data_for_many(association) - # TODO(BF): Process relationship without evaluating lazy_association - collection_serializer = association.lazy_association.serializer - if collection_serializer.respond_to?(:each) - collection_serializer.map do |serializer| - ResourceIdentifier.new(serializer, serializable_resource_options).as_json - end - elsif (virtual_value = association.virtual_value) - virtual_value - else - [] - end - end - - def links_for(association) - association.links.each_with_object({}) do |(key, value), hash| - result = Link.new(parent_serializer, value).as_json - hash[key] = result if result - end - end - - def meta_for(association) - meta = association.meta - meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta - end - 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 deleted file mode 100644 index 3a235f2b..00000000 --- a/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +++ /dev/null @@ -1,60 +0,0 @@ -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 - 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) - } - end - - # {http://jsonapi.org/format/#document-resource-identifier-objects Resource Identifier Objects} - def initialize(serializer, options) - @id = id_for(serializer) - @type = type_for(serializer, options) - end - - def as_json - return nil if id.blank? - { id: id, type: type } - end - - protected - - attr_reader :id, :type - - private - - def type_for(serializer, transform_options) - self.class.type_for(serializer.object.class.name, serializer._type, transform_options) - end - - def id_for(serializer) - serializer.read_attribute_for_serialization(:id).to_s - end - end - end - end -end diff --git a/lib/active_model_serializers/adapter/null.rb b/lib/active_model_serializers/adapter/null.rb deleted file mode 100644 index 9e5faf5c..00000000 --- a/lib/active_model_serializers/adapter/null.rb +++ /dev/null @@ -1,9 +0,0 @@ -module ActiveModelSerializers - module Adapter - class Null < Base - def serializable_hash(*) - {} - end - end - end -end diff --git a/lib/active_model_serializers/callbacks.rb b/lib/active_model_serializers/callbacks.rb deleted file mode 100644 index 71237e4a..00000000 --- a/lib/active_model_serializers/callbacks.rb +++ /dev/null @@ -1,55 +0,0 @@ -# Adapted from -# https://github.com/rails/rails/blob/7f18ea14c8/activejob/lib/active_job/callbacks.rb -require 'active_support/callbacks' - -module ActiveModelSerializers - # = ActiveModelSerializers Callbacks - # - # ActiveModelSerializers provides hooks during the life cycle of serialization and - # allow you to trigger logic. Available callbacks are: - # - # * around_render - # - module Callbacks - extend ActiveSupport::Concern - include ActiveSupport::Callbacks - - included do - define_callbacks :render - end - - # These methods will be included into any ActiveModelSerializers object, adding - # callbacks for +render+. - module ClassMethods - # Defines a callback that will get called around the render method, - # whether it is as_json, to_json, or serializable_hash - # - # class ActiveModelSerializers::SerializableResource - # include ActiveModelSerializers::Callbacks - # - # around_render do |args, block| - # tag_logger do - # notify_render do - # block.call(args) - # end - # end - # end - # - # def as_json - # run_callbacks :render do - # adapter.as_json - # end - # end - # # Note: So that we can re-use the instrumenter for as_json, to_json, and - # # serializable_hash, we aren't using the usual format, which would be: - # # def render(args) - # # adapter.as_json - # # end - # end - # - def around_render(*filters, &blk) - set_callback(:render, :around, *filters, &blk) - end - end - end -end diff --git a/lib/active_model_serializers/deprecate.rb b/lib/active_model_serializers/deprecate.rb deleted file mode 100644 index e173321d..00000000 --- a/lib/active_model_serializers/deprecate.rb +++ /dev/null @@ -1,54 +0,0 @@ -## -# Provides a single method +deprecate+ to be used to declare when -# something is going away. -# -# class Legacy -# def self.klass_method -# # ... -# end -# -# def instance_method -# # ... -# end -# -# extend ActiveModelSerializers::Deprecate -# deprecate :instance_method, "ActiveModelSerializers::NewPlace#new_method" -# -# class << self -# extend ActiveModelSerializers::Deprecate -# deprecate :klass_method, :none -# end -# end -# -# Adapted from https://github.com/rubygems/rubygems/blob/1591331/lib/rubygems/deprecate.rb -module ActiveModelSerializers - module Deprecate - ## - # Simple deprecation method that deprecates +name+ by wrapping it up - # in a dummy method. It warns on each call to the dummy method - # telling the user of +replacement+ (unless +replacement+ is :none) that it is planned to go away. - - def deprecate(name, replacement) - old = "_deprecated_#{name}" - alias_method old, name - class_eval do - define_method(name) do |*args, &block| - target = is_a?(Module) ? "#{self}." : "#{self.class}#" - msg = ["NOTE: #{target}#{name} is deprecated", - replacement == :none ? ' with no replacement' : "; use #{replacement} instead", - "\n#{target}#{name} called from #{ActiveModelSerializers.location_of_caller.join(':')}"] - warn "#{msg.join}." - send old, *args, &block - end - end - end - - def delegate_and_deprecate(method, delegee) - delegate method, to: delegee - deprecate method, "#{delegee.name}." - end - - module_function :deprecate - module_function :delegate_and_deprecate - end -end diff --git a/lib/active_model_serializers/deserialization.rb b/lib/active_model_serializers/deserialization.rb deleted file mode 100644 index 878dd98d..00000000 --- a/lib/active_model_serializers/deserialization.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ActiveModelSerializers - module Deserialization - module_function - - def jsonapi_parse(*args) - Adapter::JsonApi::Deserialization.parse(*args) - end - - # :nocov: - def jsonapi_parse!(*args) - Adapter::JsonApi::Deserialization.parse!(*args) - end - # :nocov: - end -end diff --git a/lib/active_model_serializers/json_pointer.rb b/lib/active_model_serializers/json_pointer.rb deleted file mode 100644 index a262f3b2..00000000 --- a/lib/active_model_serializers/json_pointer.rb +++ /dev/null @@ -1,14 +0,0 @@ -module ActiveModelSerializers - module JsonPointer - module_function - - POINTERS = { - attribute: '/data/attributes/%s'.freeze, - primary_data: '/data%s'.freeze - }.freeze - - def new(pointer_type, value = nil) - format(POINTERS[pointer_type], value) - end - end -end diff --git a/lib/active_model_serializers/logging.rb b/lib/active_model_serializers/logging.rb deleted file mode 100644 index 943e937e..00000000 --- a/lib/active_model_serializers/logging.rb +++ /dev/null @@ -1,122 +0,0 @@ -## -# ActiveModelSerializers::Logging -# -# https://github.com/rails/rails/blob/280654ef88/activejob/lib/active_job/logging.rb -# -module ActiveModelSerializers - module Logging - RENDER_EVENT = 'render.active_model_serializers'.freeze - extend ActiveSupport::Concern - - included do - include ActiveModelSerializers::Callbacks - extend Macros - instrument_rendering - end - - module ClassMethods - def instrument_rendering - around_render do |args, block| - tag_logger do - notify_render do - block.call(args) - end - end - end - end - end - - # Macros that can be used to customize the logging of class or instance methods, - # by extending the class or its singleton. - # - # Adapted from: - # https://github.com/rubygems/rubygems/blob/cb28f5e991/lib/rubygems/deprecate.rb - # - # Provides a single method +notify+ to be used to declare when - # something a method notifies, with the argument +callback_name+ of the notification callback. - # - # class Adapter - # def self.klass_method - # # ... - # end - # - # def instance_method - # # ... - # end - # - # include ActiveModelSerializers::Logging::Macros - # notify :instance_method, :render - # - # class << self - # extend ActiveModelSerializers::Logging::Macros - # notify :klass_method, :render - # end - # end - module Macros - ## - # Simple notify method that wraps up +name+ - # in a dummy method. It notifies on with the +callback_name+ notifier on - # each call to the dummy method, telling what the current serializer and adapter - # are being rendered. - # Adapted from: - # https://github.com/rubygems/rubygems/blob/cb28f5e991/lib/rubygems/deprecate.rb - def notify(name, callback_name) - class_eval do - old = "_notifying_#{callback_name}_#{name}" - alias_method old, name - define_method name do |*args, &block| - run_callbacks callback_name do - send old, *args, &block - end - end - end - end - end - - def notify_render(*) - event_name = RENDER_EVENT - ActiveSupport::Notifications.instrument(event_name, notify_render_payload) do - yield - end - end - - def notify_render_payload - { - serializer: serializer || ActiveModel::Serializer::Null, - adapter: adapter || ActiveModelSerializers::Adapter::Null - } - end - - private - - def tag_logger(*tags) - if ActiveModelSerializers.logger.respond_to?(:tagged) - tags.unshift 'active_model_serializers'.freeze unless logger_tagged_by_active_model_serializers? - ActiveModelSerializers.logger.tagged(*tags) { yield } - else - yield - end - end - - def logger_tagged_by_active_model_serializers? - ActiveModelSerializers.logger.formatter.current_tags.include?('active_model_serializers'.freeze) - end - - class LogSubscriber < ActiveSupport::LogSubscriber - def render(event) - info do - serializer = event.payload[:serializer] - adapter = event.payload[:adapter] - duration = event.duration.round(2) - "Rendered #{serializer.name} with #{adapter.class} (#{duration}ms)" - end - end - - def logger - ActiveModelSerializers.logger - end - end - end -end - -ActiveModelSerializers::Logging::LogSubscriber.attach_to :active_model_serializers diff --git a/lib/active_model_serializers/lookup_chain.rb b/lib/active_model_serializers/lookup_chain.rb deleted file mode 100644 index 25db8e13..00000000 --- a/lib/active_model_serializers/lookup_chain.rb +++ /dev/null @@ -1,80 +0,0 @@ -module ActiveModelSerializers - module LookupChain - # Standard appending of Serializer to the resource name. - # - # Example: - # Author => AuthorSerializer - BY_RESOURCE = lambda do |resource_class, _serializer_class, _namespace| - serializer_from(resource_class) - end - - # Uses the namespace of the resource to find the serializer - # - # Example: - # British::Author => British::AuthorSerializer - BY_RESOURCE_NAMESPACE = lambda do |resource_class, _serializer_class, _namespace| - resource_namespace = namespace_for(resource_class) - serializer_name = serializer_from(resource_class) - - "#{resource_namespace}::#{serializer_name}" - end - - # Uses the controller namespace of the resource to find the serializer - # - # Example: - # Api::V3::AuthorsController => Api::V3::AuthorSerializer - BY_NAMESPACE = lambda do |resource_class, _serializer_class, namespace| - resource_name = resource_class_name(resource_class) - namespace ? "#{namespace}::#{resource_name}Serializer" : nil - end - - # Allows for serializers to be defined in parent serializers - # - useful if a relationship only needs a different set of attributes - # than if it were rendered independently. - # - # Example: - # class BlogSerializer < ActiveModel::Serializer - # class AuthorSerialier < ActiveModel::Serializer - # ... - # end - # - # belongs_to :author - # ... - # end - # - # The belongs_to relationship would be rendered with - # BlogSerializer::AuthorSerialier - BY_PARENT_SERIALIZER = lambda do |resource_class, serializer_class, _namespace| - return if serializer_class == ActiveModel::Serializer - - serializer_name = serializer_from(resource_class) - "#{serializer_class}::#{serializer_name}" - end - - DEFAULT = [ - BY_PARENT_SERIALIZER, - BY_NAMESPACE, - BY_RESOURCE_NAMESPACE, - BY_RESOURCE - ].freeze - - module_function - - def namespace_for(klass) - klass.name.deconstantize - end - - def resource_class_name(klass) - klass.name.demodulize - end - - def serializer_from_resource_name(name) - "#{name}Serializer" - end - - def serializer_from(klass) - name = resource_class_name(klass) - serializer_from_resource_name(name) - end - end -end diff --git a/lib/active_model_serializers/model.rb b/lib/active_model_serializers/model.rb deleted file mode 100644 index 2ff3d60c..00000000 --- a/lib/active_model_serializers/model.rb +++ /dev/null @@ -1,130 +0,0 @@ -# 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. -require 'active_support/core_ext/hash' -module ActiveModelSerializers - class Model - include ActiveModel::Serializers::JSON - include ActiveModel::Model - - # Declare names of attributes to be included in +attributes+ hash. - # Is only available as a class-method since the ActiveModel::Serialization mixin in Rails - # uses an +attribute_names+ local variable, which may conflict if we were to add instance methods here. - # - # @overload attribute_names - # @return [Array] - class_attribute :attribute_names, instance_writer: false, instance_reader: false - # Initialize +attribute_names+ for all subclasses. The array is usually - # mutated in the +attributes+ method, but can be set directly, as well. - self.attribute_names = [] - - # Easily declare instance attributes with setters and getters for each. - # - # To initialize an instance, all attributes must have setters. - # However, the hash returned by +attributes+ instance method will ALWAYS - # be the value of the initial attributes, regardless of what accessors are defined. - # The only way to change the change the attributes after initialization is - # to mutate the +attributes+ directly. - # Accessor methods do NOT mutate the attributes. (This is a bug). - # - # @note For now, the Model only supports the notion of 'attributes'. - # In the tests, there is a special Model that also supports 'associations'. This is - # important so that we can add accessors for values that should not appear in the - # attributes hash when modeling associations. It is not yet clear if it - # makes sense for a PORO to have associations outside of the tests. - # - # @overload attributes(names) - # @param names [Array] - # @param name [String, Symbol] - def self.attributes(*names) - self.attribute_names |= names.map(&:to_sym) - # Silence redefinition of methods warnings - ActiveModelSerializers.silence_warnings do - attr_accessor(*names) - end - end - - # Opt-in to breaking change - def self.derive_attributes_from_names_and_fix_accessors - unless included_modules.include?(DeriveAttributesFromNamesAndFixAccessors) - prepend(DeriveAttributesFromNamesAndFixAccessors) - end - end - - module DeriveAttributesFromNamesAndFixAccessors - def self.included(base) - # NOTE that +id+ will always be in +attributes+. - base.attributes :id - end - - # Override the +attributes+ method so that the hash is derived from +attribute_names+. - # - # The fields in +attribute_names+ determines the returned hash. - # +attributes+ are returned frozen to prevent any expectations that mutation affects - # the actual values in the model. - def attributes - self.class.attribute_names.each_with_object({}) do |attribute_name, result| - result[attribute_name] = public_send(attribute_name).freeze - end.with_indifferent_access.freeze - end - end - - # Support for validation and other ActiveModel::Errors - # @return [ActiveModel::Errors] - attr_reader :errors - - # (see #updated_at) - attr_writer :updated_at - - # The only way to change the attributes of an instance is to directly mutate the attributes. - # @example - # - # model.attributes[:foo] = :bar - # @return [Hash] - attr_reader :attributes - - # @param attributes [Hash] - def initialize(attributes = {}) - attributes ||= {} # protect against nil - @attributes = attributes.symbolize_keys.with_indifferent_access - @errors = ActiveModel::Errors.new(self) - super - end - - # Defaults to the downcased model name. - # This probably isn't a good default, since it's not a unique instance identifier, - # but that's what is currently implemented \_('-')_/. - # - # @note Though +id+ is defined, it will only show up - # in +attributes+ when it is passed in to the initializer or added to +attributes+, - # such as attributes[:id] = 5. - # @return [String, Numeric, Symbol] - def id - attributes.fetch(:id) do - defined?(@id) ? @id : self.class.model_name.name && self.class.model_name.name.downcase - end - end - - # When not set, defaults to the time the file was modified. - # - # @note Though +updated_at+ and +updated_at=+ are defined, it will only show up - # in +attributes+ when it is passed in to the initializer or added to +attributes+, - # such as attributes[:updated_at] = Time.current. - # @return [String, Numeric, Time] - def updated_at - attributes.fetch(:updated_at) do - defined?(@updated_at) ? @updated_at : File.mtime(__FILE__) - end - end - - # To customize model behavior, this method must be redefined. However, - # there are other ways of setting the +cache_key+ a serializer uses. - # @return [String] - def cache_key - ActiveSupport::Cache.expand_cache_key([ - self.class.model_name.name.downcase, - "#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}" - ].compact) - end - end -end diff --git a/lib/active_model_serializers/railtie.rb b/lib/active_model_serializers/railtie.rb deleted file mode 100644 index d6843c9c..00000000 --- a/lib/active_model_serializers/railtie.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'rails/railtie' -require 'action_controller' -require 'action_controller/railtie' -require 'action_controller/serialization' - -module ActiveModelSerializers - class Railtie < Rails::Railtie - config.to_prepare do - ActiveModel::Serializer.serializers_cache.clear - end - - initializer 'active_model_serializers.action_controller' do - ActiveSupport.on_load(:action_controller) do - include(::ActionController::Serialization) - end - end - - initializer 'active_model_serializers.prepare_serialization_context' do - SerializationContext.url_helpers = Rails.application.routes.url_helpers - SerializationContext.default_url_options = Rails.application.routes.default_url_options - end - - # This hook is run after the action_controller railtie has set the configuration - # based on the *environment* configuration and before any config/initializers are run - # and also before eager_loading (if enabled). - initializer 'active_model_serializers.set_configs', after: 'action_controller.set_configs' do - ActiveModelSerializers.logger = Rails.configuration.action_controller.logger - ActiveModelSerializers.config.perform_caching = Rails.configuration.action_controller.perform_caching - # We want this hook to run after the config has been set, even if ActionController has already loaded. - ActiveSupport.on_load(:action_controller) do - ActiveModelSerializers.config.cache_store = ActionController::Base.cache_store - end - end - - # :nocov: - generators do |app| - Rails::Generators.configure!(app.config.generators) - Rails::Generators.hidden_namespaces.uniq! - require 'generators/rails/resource_override' - end - # :nocov: - - if Rails.env.test? - ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Schema) - ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Serializer) - end - end -end diff --git a/lib/active_model_serializers/register_jsonapi_renderer.rb b/lib/active_model_serializers/register_jsonapi_renderer.rb deleted file mode 100644 index 715c6ab3..00000000 --- a/lib/active_model_serializers/register_jsonapi_renderer.rb +++ /dev/null @@ -1,78 +0,0 @@ -# Based on discussion in https://github.com/rails/rails/pull/23712#issuecomment-184977238, -# the JSON API media type will have its own format/renderer. -# -# > We recommend the media type be registered on its own as jsonapi -# when a jsonapi Renderer and deserializer (Http::Parameters::DEFAULT_PARSERS) are added. -# -# Usage: -# -# ActiveSupport.on_load(:action_controller) do -# require 'active_model_serializers/register_jsonapi_renderer' -# end -# -# And then in controllers, use `render jsonapi: model` rather than `render json: model, adapter: :json_api`. -# -# For example, in a controller action, we can: -# respond_to do |format| -# format.jsonapi { render jsonapi: model } -# end -# -# or -# -# render jsonapi: model -# -# No wrapper format needed as it does not apply (i.e. no `wrap_parameters format: [jsonapi]`) -module ActiveModelSerializers - module Jsonapi - MEDIA_TYPE = 'application/vnd.api+json'.freeze - HEADERS = { - response: { 'CONTENT_TYPE'.freeze => MEDIA_TYPE }, - request: { 'ACCEPT'.freeze => MEDIA_TYPE } - }.freeze - - def self.install - # actionpack/lib/action_dispatch/http/mime_types.rb - Mime::Type.register MEDIA_TYPE, :jsonapi - - if Rails::VERSION::MAJOR >= 5 - ActionDispatch::Request.parameter_parsers[:jsonapi] = parser - else - ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime[:jsonapi]] = parser - end - - # ref https://github.com/rails/rails/pull/21496 - ActionController::Renderers.add :jsonapi do |json, options| - json = serialize_jsonapi(json, options).to_json(options) unless json.is_a?(String) - self.content_type ||= Mime[:jsonapi] - self.response_body = json - end - end - - # Proposal: should actually deserialize the JSON API params - # to the hash format expected by `ActiveModel::Serializers::JSON` - # actionpack/lib/action_dispatch/http/parameters.rb - def self.parser - lambda do |body| - data = JSON.parse(body) - data = { _json: data } unless data.is_a?(Hash) - data.with_indifferent_access - end - end - - module ControllerSupport - def serialize_jsonapi(json, options) - options[:adapter] = :json_api - options.fetch(:serialization_context) do - options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request) - end - get_serializer(json, options) - end - end - end -end - -ActiveModelSerializers::Jsonapi.install - -ActiveSupport.on_load(:action_controller) do - include ActiveModelSerializers::Jsonapi::ControllerSupport -end diff --git a/lib/active_model_serializers/serializable_resource.rb b/lib/active_model_serializers/serializable_resource.rb deleted file mode 100644 index f67cf238..00000000 --- a/lib/active_model_serializers/serializable_resource.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'set' - -module ActiveModelSerializers - class SerializableResource - ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter, :meta, :meta_key, :links, :serialization_context, :key_transform]) - include ActiveModelSerializers::Logging - - delegate :serializable_hash, :as_json, :to_json, to: :adapter - notify :serializable_hash, :render - notify :as_json, :render - notify :to_json, :render - - # Primary interface to composing a resource with a serializer and adapter. - # @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] } - end - - def serialization_scope=(scope) - serializer_opts[:scope] = scope - end - - def serialization_scope - serializer_opts[:scope] - end - - def serialization_scope_name=(scope_name) - serializer_opts[:scope_name] = scope_name - end - - # NOTE: if no adapter is available, returns the resource itself. (i.e. adapter is a no-op) - def adapter - @adapter ||= find_adapter - end - alias adapter_instance adapter - - def find_adapter - return resource unless serializer? - adapter = catch :no_serializer do - ActiveModelSerializers::Adapter.create(serializer_instance, adapter_opts) - end - adapter || resource - end - - def serializer_instance - @serializer_instance ||= serializer.new(resource, serializer_opts) - end - - # Get serializer either explicitly :serializer or implicitly from resource - # Remove :serializer key from serializer_opts - # Remove :each_serializer if present and set as :serializer key - def serializer - @serializer ||= - begin - @serializer = serializer_opts.delete(:serializer) - @serializer ||= ActiveModel::Serializer.serializer_for(resource, serializer_opts) - - if serializer_opts.key?(:each_serializer) - serializer_opts[:serializer] = serializer_opts.delete(:each_serializer) - end - @serializer - end - end - alias serializer_class serializer - - # True when no explicit adapter given, or explicit appear is truthy (non-nil) - # False when explicit adapter is falsy (nil or false) - def use_adapter? - !(adapter_opts.key?(:adapter) && !adapter_opts[:adapter]) - end - - def serializer? - use_adapter? && !serializer.nil? - end - - protected - - attr_reader :resource, :adapter_opts, :serializer_opts - end -end diff --git a/lib/active_model_serializers/serialization_context.rb b/lib/active_model_serializers/serialization_context.rb deleted file mode 100644 index 9ef604f2..00000000 --- a/lib/active_model_serializers/serialization_context.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'active_support/core_ext/array/extract_options' -module ActiveModelSerializers - class SerializationContext - class << self - attr_writer :url_helpers, :default_url_options - def url_helpers - @url_helpers ||= Module.new - end - - def default_url_options - @default_url_options ||= {} - end - end - module UrlHelpers - def self.included(base) - base.send(:include, SerializationContext.url_helpers) - end - - def default_url_options - SerializationContext.default_url_options - end - end - - attr_reader :request_url, :query_parameters, :key_transform - - def initialize(*args) - options = args.extract_options! - if args.size == 1 - request = args.pop - options[:request_url] = request.original_url[/\A[^?]+/] - options[:query_parameters] = request.query_parameters - end - @request_url = options.delete(:request_url) - @query_parameters = options.delete(:query_parameters) - @url_helpers = options.delete(:url_helpers) || self.class.url_helpers - @default_url_options = options.delete(:default_url_options) || self.class.default_url_options - end - end -end diff --git a/lib/active_model_serializers/test.rb b/lib/active_model_serializers/test.rb deleted file mode 100644 index bec452ec..00000000 --- a/lib/active_model_serializers/test.rb +++ /dev/null @@ -1,7 +0,0 @@ -module ActiveModelSerializers - module Test - extend ActiveSupport::Autoload - autoload :Serializer - autoload :Schema - end -end diff --git a/lib/active_model_serializers/test/schema.rb b/lib/active_model_serializers/test/schema.rb deleted file mode 100644 index a0001586..00000000 --- a/lib/active_model_serializers/test/schema.rb +++ /dev/null @@ -1,138 +0,0 @@ -module ActiveModelSerializers - module Test - module Schema - # A Minitest Assertion that test the response is valid against a schema. - # @param schema_path [String] a custom schema path - # @param message [String] a custom error message - # @return [Boolean] true when the response is valid - # @return [Minitest::Assertion] when the response is invalid - # @example - # get :index - # assert_response_schema - def assert_response_schema(schema_path = nil, message = nil) - matcher = AssertResponseSchema.new(schema_path, request, response, message) - assert(matcher.call, matcher.message) - end - - def assert_request_schema(schema_path = nil, message = nil) - matcher = AssertRequestSchema.new(schema_path, request, response, message) - assert(matcher.call, matcher.message) - end - - # May be renamed - def assert_request_response_schema(schema_path = nil, message = nil) - assert_request_schema(schema_path, message) - assert_response_schema(schema_path, message) - end - - def assert_schema(payload, schema_path = nil, message = nil) - matcher = AssertSchema.new(schema_path, request, response, message, payload) - assert(matcher.call, matcher.message) - end - - MissingSchema = Class.new(Minitest::Assertion) - InvalidSchemaError = Class.new(Minitest::Assertion) - - class AssertSchema - attr_reader :schema_path, :request, :response, :message, :payload - - # Interface may change. - def initialize(schema_path, request, response, message, payload = nil) - require_json_schema! - @request = request - @response = response - @payload = payload - @schema_path = schema_path || schema_path_default - @message = message - @document_store = JsonSchema::DocumentStore.new - add_schema_to_document_store - end - - def call - json_schema.expand_references!(store: document_store) - status, errors = json_schema.validate(response_body) - @message = [message, errors.map(&:to_s).to_sentence].compact.join(': ') - status - end - - protected - - attr_reader :document_store - - def controller_path - request.filtered_parameters.with_indifferent_access[:controller] - end - - def action - request.filtered_parameters.with_indifferent_access[:action] - end - - def schema_directory - ActiveModelSerializers.config.schema_path - end - - def schema_full_path - "#{schema_directory}/#{schema_path}" - end - - def schema_path_default - "#{controller_path}/#{action}.json" - end - - def schema_data - load_json_file(schema_full_path) - end - - def response_body - load_json(response.body) - end - - def request_params - request.env['action_dispatch.request.request_parameters'] - end - - def json_schema - @json_schema ||= JsonSchema.parse!(schema_data) - end - - def add_schema_to_document_store - Dir.glob("#{schema_directory}/**/*.json").each do |path| - schema_data = load_json_file(path) - extra_schema = JsonSchema.parse!(schema_data) - document_store.add_schema(extra_schema) - end - end - - def load_json(json) - JSON.parse(json) - rescue JSON::ParserError => ex - raise InvalidSchemaError, ex.message - end - - def load_json_file(path) - load_json(File.read(path)) - rescue Errno::ENOENT - raise MissingSchema, "No Schema file at #{schema_full_path}" - end - - def require_json_schema! - require 'json_schema' - rescue LoadError - raise LoadError, "You don't have json_schema installed in your application. Please add it to your Gemfile and run bundle install" - end - end - class AssertResponseSchema < AssertSchema - def initialize(*) - super - @payload = response_body - end - end - class AssertRequestSchema < AssertSchema - def initialize(*) - super - @payload = request_params - end - end - end - end -end diff --git a/lib/active_model_serializers/test/serializer.rb b/lib/active_model_serializers/test/serializer.rb deleted file mode 100644 index dc812c55..00000000 --- a/lib/active_model_serializers/test/serializer.rb +++ /dev/null @@ -1,125 +0,0 @@ -require 'set' -module ActiveModelSerializers - module Test - module Serializer - extend ActiveSupport::Concern - - included do - setup :setup_serialization_subscriptions - teardown :teardown_serialization_subscriptions - end - - # Asserts that the request was rendered with the appropriate serializers. - # - # # assert that the "PostSerializer" serializer was rendered - # assert_serializer "PostSerializer" - # - # # return a custom error message - # assert_serializer "PostSerializer", "PostSerializer not rendered" - # - # # assert that the instance of PostSerializer was rendered - # assert_serializer PostSerializer - # - # # assert that the "PostSerializer" serializer was rendered - # assert_serializer :post_serializer - # - # # assert that the rendered serializer starts with "Post" - # assert_serializer %r{\APost.+\Z} - # - # # assert that no serializer was rendered - # assert_serializer nil - # - def assert_serializer(expectation, message = nil) - @assert_serializer.expectation = expectation - @assert_serializer.message = message - @assert_serializer.response = response - assert(@assert_serializer.matches?, @assert_serializer.message) - end - - class AssertSerializer - attr_reader :serializers, :message - attr_accessor :response, :expectation - - def initialize - @serializers = Set.new - @_subscribers = [] - end - - def message=(message) - @message = message || "expecting <#{expectation.inspect}> but rendering with <#{serializers.to_a}>" - end - - def matches? - # Force body to be read in case the template is being streamed. - response.body - - case expectation - when a_serializer? then matches_class? - when Symbol then matches_symbol? - when String then matches_string? - when Regexp then matches_regexp? - when NilClass then matches_nil? - else fail ArgumentError, 'assert_serializer only accepts a String, Symbol, Regexp, ActiveModel::Serializer, or nil' - end - end - - def subscribe - @_subscribers << ActiveSupport::Notifications.subscribe(event_name) do |_name, _start, _finish, _id, payload| - serializer = payload[:serializer].name - serializers << serializer - end - end - - def unsubscribe - @_subscribers.each do |subscriber| - ActiveSupport::Notifications.unsubscribe(subscriber) - end - end - - private - - def matches_class? - serializers.include?(expectation.name) - end - - def matches_symbol? - camelize_expectation = expectation.to_s.camelize - serializers.include?(camelize_expectation) - end - - def matches_string? - !expectation.empty? && serializers.include?(expectation) - end - - def matches_regexp? - serializers.any? do |serializer| - serializer.match(expectation) - end - end - - def matches_nil? - serializers.empty? - end - - def a_serializer? - ->(exp) { exp.is_a?(Class) && exp < ActiveModel::Serializer } - end - - def event_name - ::ActiveModelSerializers::Logging::RENDER_EVENT - end - end - - private - - def setup_serialization_subscriptions - @assert_serializer = AssertSerializer.new - @assert_serializer.subscribe - end - - def teardown_serialization_subscriptions - @assert_serializer.unsubscribe - end - end - end -end diff --git a/lib/generators/rails/USAGE b/lib/generators/rails/USAGE deleted file mode 100644 index 85de6ad3..00000000 --- a/lib/generators/rails/USAGE +++ /dev/null @@ -1,6 +0,0 @@ -Description: - Generates a serializer for the given resource. - -Example: - `rails generate serializer Account name created_at` - diff --git a/lib/generators/rails/resource_override.rb b/lib/generators/rails/resource_override.rb deleted file mode 100644 index 5177a636..00000000 --- a/lib/generators/rails/resource_override.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'rails/generators' -require 'rails/generators/rails/resource/resource_generator' - -module Rails - module Generators - class ResourceGenerator - hook_for :serializer, default: true, type: :boolean - end - end -end diff --git a/lib/generators/rails/serializer_generator.rb b/lib/generators/rails/serializer_generator.rb deleted file mode 100644 index e670d5cf..00000000 --- a/lib/generators/rails/serializer_generator.rb +++ /dev/null @@ -1,36 +0,0 @@ -module Rails - module Generators - class SerializerGenerator < NamedBase - source_root File.expand_path('../templates', __FILE__) - check_class_collision suffix: 'Serializer' - - argument :attributes, type: :array, default: [], banner: 'field:type field:type' - - class_option :parent, type: :string, desc: 'The parent class for the generated serializer' - - def create_serializer_file - template 'serializer.rb.erb', File.join('app/serializers', class_path, "#{file_name}_serializer.rb") - end - - private - - def attributes_names - [:id] + attributes.reject(&:reference?).map! { |a| a.name.to_sym } - end - - def association_names - attributes.select(&:reference?).map! { |a| a.name.to_sym } - end - - def parent_class_name - if options[:parent] - options[:parent] - elsif 'ApplicationSerializer'.safe_constantize - 'ApplicationSerializer' - else - 'ActiveModel::Serializer' - end - end - end - end -end diff --git a/lib/generators/rails/templates/serializer.rb.erb b/lib/generators/rails/templates/serializer.rb.erb deleted file mode 100644 index 4ebb004e..00000000 --- a/lib/generators/rails/templates/serializer.rb.erb +++ /dev/null @@ -1,8 +0,0 @@ -<% module_namespacing do -%> -class <%= class_name %>Serializer < <%= parent_class_name %> - attributes <%= attributes_names.map(&:inspect).join(", ") %> -<% association_names.each do |attribute| -%> - has_one :<%= attribute %> -<% end -%> -end -<% end -%> diff --git a/lib/grape/active_model_serializers.rb b/lib/grape/active_model_serializers.rb deleted file mode 100644 index 8dc7a314..00000000 --- a/lib/grape/active_model_serializers.rb +++ /dev/null @@ -1,16 +0,0 @@ -# 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' -require 'grape/formatters/active_model_serializers' -require 'grape/helpers/active_model_serializers' - -module Grape - module ActiveModelSerializers - extend ActiveSupport::Concern - - included do - formatter :json, Grape::Formatters::ActiveModelSerializers - helpers Grape::Helpers::ActiveModelSerializers - end - end -end diff --git a/lib/grape/formatters/active_model_serializers.rb b/lib/grape/formatters/active_model_serializers.rb deleted file mode 100644 index 534c5bab..00000000 --- a/lib/grape/formatters/active_model_serializers.rb +++ /dev/null @@ -1,32 +0,0 @@ -# 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], -# or better yet user the render helper in Grape::Helpers::ActiveModelSerializers - -require 'active_model_serializers/serialization_context' - -module Grape - module Formatters - module ActiveModelSerializers - def self.call(resource, env) - serializer_options = build_serializer_options(env) - ::ActiveModelSerializers::SerializableResource.new(resource, serializer_options).to_json - end - - def self.build_serializer_options(env) - ams_options = env[:active_model_serializer_options] || {} - - # Add serialization context - ams_options.fetch(:serialization_context) do - request = env['grape.request'] - ams_options[:serialization_context] = ::ActiveModelSerializers::SerializationContext.new( - request_url: request.url[/\A[^?]+/], - query_parameters: request.params - ) - end - - ams_options - end - end - end -end diff --git a/lib/grape/helpers/active_model_serializers.rb b/lib/grape/helpers/active_model_serializers.rb deleted file mode 100644 index afbdab85..00000000 --- a/lib/grape/helpers/active_model_serializers.rb +++ /dev/null @@ -1,17 +0,0 @@ -# Helpers can be included in your Grape endpoint as: helpers Grape::Helpers::ActiveModelSerializers - -module Grape - module Helpers - module ActiveModelSerializers - # A convenience method for passing ActiveModelSerializers serializer options - # - # Example: To include relationships in the response: render(post, include: ['comments']) - # - # Example: To include pagination meta data: render(posts, meta: { page: posts.page, total_pages: posts.total_pages }) - def render(resource, active_model_serializer_options = {}) - env[:active_model_serializer_options] = active_model_serializer_options - resource - end - end - end -end diff --git a/lib/tasks/rubocop.rake b/lib/tasks/rubocop.rake deleted file mode 100644 index 5c9a1242..00000000 --- a/lib/tasks/rubocop.rake +++ /dev/null @@ -1,53 +0,0 @@ -begin - require 'rubocop' - require 'rubocop/rake_task' -rescue LoadError # rubocop:disable Lint/HandleExceptions -else - require 'rbconfig' - # https://github.com/bundler/bundler/blob/1b3eb2465a/lib/bundler/constants.rb#L2 - windows_platforms = /(msdos|mswin|djgpp|mingw)/ - if RbConfig::CONFIG['host_os'] =~ windows_platforms - desc 'No-op rubocop on Windows-- unsupported platform' - task :rubocop do - puts 'Skipping rubocop on Windows' - end - elsif defined?(::Rubinius) - desc 'No-op rubocop to avoid rbx segfault' - task :rubocop do - puts 'Skipping rubocop on rbx due to segfault' - puts 'https://github.com/rubinius/rubinius/issues/3499' - end - else - Rake::Task[:rubocop].clear if Rake::Task.task_defined?(:rubocop) - patterns = [ - 'Gemfile', - 'Rakefile', - 'lib/**/*.{rb,rake}', - 'config/**/*.rb', - 'app/**/*.rb', - 'test/**/*.rb' - ] - desc 'Execute rubocop' - RuboCop::RakeTask.new(:rubocop) do |task| - task.options = ['--rails', '--display-cop-names', '--display-style-guide'] - task.formatters = ['progress'] - task.patterns = patterns - task.fail_on_error = true - end - - namespace :rubocop do - desc 'Auto-gen rubocop config' - task :auto_gen_config do - options = ['--auto-gen-config'].concat patterns - require 'benchmark' - result = 0 - cli = RuboCop::CLI.new - time = Benchmark.realtime do - result = cli.run(options) - end - puts "Finished in #{time} seconds" if cli.options[:debug] - abort('RuboCop failed!') if result.nonzero? - end - end - end -end diff --git a/test/action_controller/adapter_selector_test.rb b/test/action_controller/adapter_selector_test.rb deleted file mode 100644 index 3373de7c..00000000 --- a/test/action_controller/adapter_selector_test.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'test_helper' - -module ActionController - module Serialization - class AdapterSelectorTest < ActionController::TestCase - class Profile < Model - attributes :id, :name, :description - associations :comments - end - class ProfileSerializer < ActiveModel::Serializer - type 'profiles' - attributes :name, :description - end - - class AdapterSelectorTestController < ActionController::Base - def render_using_default_adapter - @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') - render json: @profile - end - - def render_using_adapter_override - @profile = Profile.new(id: 'render_using_adapter_override', name: 'Name 1', description: 'Description 1', comments: 'Comments 1') - render json: @profile, adapter: :json_api - end - - def render_skipping_adapter - @profile = Profile.new(id: 'render_skipping_adapter_id', name: 'Name 1', description: 'Description 1', comments: 'Comments 1') - render json: @profile, adapter: false - end - end - - tests AdapterSelectorTestController - - def test_render_using_default_adapter - get :render_using_default_adapter - assert_equal '{"name":"Name 1","description":"Description 1"}', response.body - end - - def test_render_using_adapter_override - get :render_using_adapter_override - - expected = { - data: { - id: 'render_using_adapter_override', - type: 'profiles', - attributes: { - name: 'Name 1', - description: 'Description 1' - } - } - } - - assert_equal expected.to_json, response.body - end - - def test_render_skipping_adapter - get :render_skipping_adapter - assert_equal '{"id":"render_skipping_adapter_id","name":"Name 1","description":"Description 1"}', response.body - end - end - end -end diff --git a/test/action_controller/explicit_serializer_test.rb b/test/action_controller/explicit_serializer_test.rb deleted file mode 100644 index a23b6f6b..00000000 --- a/test/action_controller/explicit_serializer_test.rb +++ /dev/null @@ -1,135 +0,0 @@ -require 'test_helper' - -module ActionController - module Serialization - class ExplicitSerializerTest < ActionController::TestCase - class ExplicitSerializerTestController < ActionController::Base - def render_using_explicit_serializer - @profile = Profile.new(name: 'Name 1', - description: 'Description 1', - comments: 'Comments 1') - render json: @profile, serializer: ProfilePreviewSerializer - end - - def render_array_using_explicit_serializer - array = [ - Profile.new(name: 'Name 1', - description: 'Description 1', - comments: 'Comments 1'), - Profile.new(name: 'Name 2', - description: 'Description 2', - comments: 'Comments 2') - ] - render json: array, - serializer: PaginatedSerializer, - each_serializer: ProfilePreviewSerializer - end - - def render_array_using_implicit_serializer - array = [ - Profile.new(name: 'Name 1', - description: 'Description 1', - comments: 'Comments 1'), - Profile.new(name: 'Name 2', - description: 'Description 2', - comments: 'Comments 2') - ] - render json: array, - each_serializer: ProfilePreviewSerializer - end - - def render_array_using_explicit_serializer_and_custom_serializers - @post = Post.new(title: 'New Post', body: 'Body') - @author = Author.new(name: 'Jane Blogger') - @author.posts = [@post] - @post.author = @author - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - @post.comments = [@first_comment, @second_comment] - @first_comment.post = @post - @first_comment.author = nil - @second_comment.post = @post - @second_comment.author = nil - @blog = Blog.new(id: 23, name: 'AMS Blog') - @post.blog = @blog - - render json: [@post], each_serializer: PostPreviewSerializer - end - - def render_using_explicit_each_serializer - location = Location.new(id: 42, lat: '-23.550520', lng: '-46.633309') - place = Place.new(id: 1337, name: 'Amazing Place', locations: [location]) - - render json: place, each_serializer: PlaceSerializer - end - end - - tests ExplicitSerializerTestController - - def test_render_using_explicit_serializer - get :render_using_explicit_serializer - - assert_equal 'application/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 - - expected = [ - { 'name' => 'Name 1' }, - { 'name' => 'Name 2' } - ] - - assert_equal expected.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 - - expected = [ - { 'name' => 'Name 1' }, - { 'name' => 'Name 2' } - ] - assert_equal expected.to_json, @response.body - end - - def test_render_array_using_explicit_serializer_and_custom_serializers - get :render_array_using_explicit_serializer_and_custom_serializers - - expected = [ - { - 'title' => 'New Post', - 'body' => 'Body', - 'id' => @controller.instance_variable_get(:@post).id, - 'comments' => [{ 'id' => 1 }, { 'id' => 2 }], - 'author' => { 'id' => @controller.instance_variable_get(:@author).id } - } - ] - - assert_equal expected.to_json, @response.body - end - - def test_render_using_explicit_each_serializer - get :render_using_explicit_each_serializer - - expected = { - id: 1337, - name: 'Amazing Place', - locations: [ - { - id: 42, - lat: '-23.550520', - lng: '-46.633309', - address: 'Nowhere' # is a virtual attribute on LocationSerializer - } - ] - } - - assert_equal expected.to_json, response.body - end - end - end -end diff --git a/test/action_controller/json/include_test.rb b/test/action_controller/json/include_test.rb deleted file mode 100644 index 1fc8863e..00000000 --- a/test/action_controller/json/include_test.rb +++ /dev/null @@ -1,246 +0,0 @@ -require 'test_helper' - -module ActionController - module Serialization - class Json - class IncludeTest < ActionController::TestCase - INCLUDE_STRING = 'posts.comments'.freeze - INCLUDE_HASH = { posts: :comments }.freeze - DEEP_INCLUDE = 'posts.comments.author'.freeze - - class IncludeTestController < ActionController::Base - def setup_data - ActionController::Base.cache_store.clear - - @author = Author.new(id: 1, name: 'Steve K.') - - @post = Post.new(id: 42, title: 'New Post', body: 'Body') - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - - @post.comments = [@first_comment, @second_comment] - @post.author = @author - - @first_comment.post = @post - @second_comment.post = @post - - @blog = Blog.new(id: 1, name: 'My Blog!!') - @post.blog = @blog - @author.posts = [@post] - - @first_comment.author = @author - @second_comment.author = @author - @author.comments = [@first_comment, @second_comment] - @author.roles = [] - @author.bio = {} - end - - def render_without_include - setup_data - render json: @author, adapter: :json - end - - def render_resource_with_include_hash - setup_data - render json: @author, include: INCLUDE_HASH, adapter: :json - end - - def render_resource_with_include_string - setup_data - render json: @author, include: INCLUDE_STRING, adapter: :json - end - - def render_resource_with_deep_include - setup_data - render json: @author, include: DEEP_INCLUDE, adapter: :json - end - - def render_without_recursive_relationships - # testing recursive includes ('**') can't have any cycles in the - # relationships, or we enter an infinite loop. - author = Author.new(id: 11, name: 'Jane Doe') - post = Post.new(id: 12, title: 'Hello World', body: 'My first post') - comment = Comment.new(id: 13, body: 'Commentary') - author.posts = [post] - post.comments = [comment] - render json: author - end - end - - tests IncludeTestController - - def test_render_without_include - get :render_without_include - response = JSON.parse(@response.body) - expected = { - 'author' => { - 'id' => 1, - 'name' => 'Steve K.', - 'posts' => [ - { - 'id' => 42, 'title' => 'New Post', 'body' => 'Body' - } - ], - 'roles' => [], - 'bio' => {} - } - } - - assert_equal(expected, response) - end - - def test_render_resource_with_include_hash - get :render_resource_with_include_hash - response = JSON.parse(@response.body) - - assert_equal(expected_include_response, response) - end - - def test_render_resource_with_include_string - get :render_resource_with_include_string - - response = JSON.parse(@response.body) - - assert_equal(expected_include_response, response) - end - - def test_render_resource_with_deep_include - get :render_resource_with_deep_include - - response = JSON.parse(@response.body) - - assert_equal(expected_deep_include_response, response) - end - - def test_render_with_empty_default_includes - with_default_includes '' do - get :render_without_include - response = JSON.parse(@response.body) - expected = { - 'author' => { - 'id' => 1, - 'name' => 'Steve K.' - } - } - assert_equal(expected, response) - end - end - - def test_render_with_recursive_default_includes - with_default_includes '**' do - get :render_without_recursive_relationships - response = JSON.parse(@response.body) - - expected = { - 'id' => 11, - 'name' => 'Jane Doe', - 'roles' => nil, - 'bio' => nil, - 'posts' => [ - { - 'id' => 12, - 'title' => 'Hello World', - 'body' => 'My first post', - 'comments' => [ - { - 'id' => 13, - 'body' => 'Commentary', - 'post' => nil, # not set to avoid infinite recursion - 'author' => nil, # not set to avoid infinite recursion - } - ], - 'blog' => { - 'id' => 999, - 'name' => 'Custom blog', - 'writer' => nil, - 'articles' => nil - }, - 'author' => nil # not set to avoid infinite recursion - } - ] - } - assert_equal(expected, response) - end - end - - def test_render_with_includes_overrides_default_includes - with_default_includes '' do - get :render_resource_with_include_hash - response = JSON.parse(@response.body) - - assert_equal(expected_include_response, response) - end - end - - private - - def expected_include_response - { - 'author' => { - 'id' => 1, - 'name' => 'Steve K.', - 'posts' => [ - { - 'id' => 42, 'title' => 'New Post', 'body' => 'Body', - 'comments' => [ - { - 'id' => 1, 'body' => 'ZOMG A COMMENT' - }, - { - 'id' => 2, 'body' => 'ZOMG ANOTHER COMMENT' - } - ] - } - ] - } - } - end - - def expected_deep_include_response - { - 'author' => { - 'id' => 1, - 'name' => 'Steve K.', - 'posts' => [ - { - 'id' => 42, 'title' => 'New Post', 'body' => 'Body', - 'comments' => [ - { - 'id' => 1, 'body' => 'ZOMG A COMMENT', - 'author' => { - 'id' => 1, - 'name' => 'Steve K.' - } - }, - { - 'id' => 2, 'body' => 'ZOMG ANOTHER COMMENT', - 'author' => { - 'id' => 1, - 'name' => 'Steve K.' - } - } - ] - } - ] - } - } - end - - def with_default_includes(include_directive) - original = ActiveModelSerializers.config.default_includes - ActiveModelSerializers.config.default_includes = include_directive - clear_include_directive_cache - yield - ensure - ActiveModelSerializers.config.default_includes = original - clear_include_directive_cache - end - - def clear_include_directive_cache - ActiveModelSerializers - .instance_variable_set(:@default_include_directive, nil) - end - end - end - end -end diff --git a/test/action_controller/json_api/deserialization_test.rb b/test/action_controller/json_api/deserialization_test.rb deleted file mode 100644 index 025f857b..00000000 --- a/test/action_controller/json_api/deserialization_test.rb +++ /dev/null @@ -1,112 +0,0 @@ -require 'test_helper' - -module ActionController - module Serialization - class JsonApi - class DeserializationTest < ActionController::TestCase - class DeserializationTestController < ActionController::Base - def render_parsed_payload - parsed_hash = ActiveModelSerializers::Deserialization.jsonapi_parse(params) - render json: parsed_hash - end - - def render_polymorphic_parsed_payload - parsed_hash = ActiveModelSerializers::Deserialization.jsonapi_parse( - params, - polymorphic: [:restriction_for, :restricted_to] - ) - render json: parsed_hash - end - end - - tests DeserializationTestController - - def test_deserialization_of_relationship_only_object - hash = { - 'data' => { - 'type' => 'restraints', - 'relationships' => { - 'restriction_for' => { - 'data' => { - 'type' => 'discounts', - 'id' => '67' - } - }, - 'restricted_to' => { - 'data' => nil - } - } - }, - 'restraint' => {} - } - - post :render_polymorphic_parsed_payload, params: hash - - response = JSON.parse(@response.body) - expected = { - 'restriction_for_id' => '67', - 'restriction_for_type' => 'discounts', - 'restricted_to_id' => nil, - 'restricted_to_type' => nil - } - - assert_equal(expected, response) - end - - def test_deserialization - hash = { - 'data' => { - 'type' => 'photos', - 'id' => 'zorglub', - 'attributes' => { - 'title' => 'Ember Hamster', - 'src' => 'http://example.com/images/productivity.png', - 'image-width' => '200', - 'imageHeight' => '200', - 'ImageSize' => '1024' - }, - 'relationships' => { - 'author' => { - 'data' => nil - }, - 'photographer' => { - 'data' => { 'type' => 'people', 'id' => '9' } - }, - 'comments' => { - 'data' => [ - { 'type' => 'comments', 'id' => '1' }, - { 'type' => 'comments', 'id' => '2' } - ] - }, - 'related-images' => { - 'data' => [ - { 'type' => 'image', 'id' => '7' }, - { 'type' => 'image', 'id' => '8' } - ] - } - } - } - } - - post :render_parsed_payload, params: hash - - response = JSON.parse(@response.body) - expected = { - 'id' => 'zorglub', - 'title' => 'Ember Hamster', - 'src' => 'http://example.com/images/productivity.png', - 'image_width' => '200', - 'image_height' => '200', - 'image_size' => '1024', - 'author_id' => nil, - 'photographer_id' => '9', - 'comment_ids' => %w(1 2), - 'related_image_ids' => %w(7 8) - } - - assert_equal(expected, response) - end - end - end - end -end diff --git a/test/action_controller/json_api/errors_test.rb b/test/action_controller/json_api/errors_test.rb deleted file mode 100644 index 6da3c9ad..00000000 --- a/test/action_controller/json_api/errors_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'test_helper' - -module ActionController - module Serialization - class JsonApi - class ErrorsTest < ActionController::TestCase - def test_active_model_with_multiple_errors - get :render_resource_with_errors - - expected_errors_object = { - errors: [ - { source: { pointer: '/data/attributes/name' }, detail: 'cannot be nil' }, - { source: { pointer: '/data/attributes/name' }, detail: 'must be longer' }, - { source: { pointer: '/data/attributes/id' }, detail: 'must be a uuid' } - ] - }.to_json - assert_equal json_response_body.to_json, expected_errors_object - end - - def json_response_body - JSON.load(@response.body) - end - - class ErrorsTestController < ActionController::Base - def render_resource_with_errors - resource = Profile.new(name: 'Name 1', - description: 'Description 1', - comments: 'Comments 1') - resource.errors.add(:name, 'cannot be nil') - resource.errors.add(:name, 'must be longer') - resource.errors.add(:id, 'must be a uuid') - render json: resource, adapter: :json_api, serializer: ActiveModel::Serializer::ErrorSerializer - end - end - - tests ErrorsTestController - end - end - end -end diff --git a/test/action_controller/json_api/fields_test.rb b/test/action_controller/json_api/fields_test.rb deleted file mode 100644 index af87ad39..00000000 --- a/test/action_controller/json_api/fields_test.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'test_helper' - -module ActionController - module Serialization - class JsonApi - class FieldsTest < ActionController::TestCase - class FieldsTestController < ActionController::Base - class AuthorWithName < Author - attributes :first_name, :last_name - end - class AuthorWithNameSerializer < AuthorSerializer - type 'authors' - end - class PostWithPublishAt < Post - attributes :publish_at - end - class PostWithPublishAtSerializer < ActiveModel::Serializer - type 'posts' - attributes :title, :body, :publish_at - belongs_to :author - has_many :comments - end - - def setup_post - ActionController::Base.cache_store.clear - @author = AuthorWithName.new(id: 1, first_name: 'Bob', last_name: 'Jones') - @comment1 = Comment.new(id: 7, body: 'cool', author: @author) - @comment2 = Comment.new(id: 12, body: 'awesome', author: @author) - @post = PostWithPublishAt.new(id: 1337, title: 'Title 1', body: 'Body 1', - author: @author, comments: [@comment1, @comment2], - publish_at: '2020-03-16T03:55:25.291Z') - @comment1.post = @post - @comment2.post = @post - end - - def render_fields_works_on_relationships - setup_post - render json: @post, serializer: PostWithPublishAtSerializer, adapter: :json_api, fields: { posts: [:author] } - end - end - - tests FieldsTestController - - test 'fields works on relationships' do - get :render_fields_works_on_relationships - response = JSON.parse(@response.body) - expected = { - 'data' => { - 'id' => '1337', - 'type' => 'posts', - 'relationships' => { - 'author' => { - 'data' => { - 'id' => '1', - 'type' => 'authors' - } - } - } - } - } - assert_equal expected, response - end - end - end - end -end diff --git a/test/action_controller/json_api/linked_test.rb b/test/action_controller/json_api/linked_test.rb deleted file mode 100644 index 12019768..00000000 --- a/test/action_controller/json_api/linked_test.rb +++ /dev/null @@ -1,202 +0,0 @@ -require 'test_helper' - -module ActionController - module Serialization - class JsonApi - class LinkedTest < ActionDispatch::IntegrationTest - class LinkedTestController < ActionController::Base - def setup_post - ActionController::Base.cache_store.clear - @role1 = Role.new(id: 1, name: 'admin') - @role2 = Role.new(id: 2, name: 'colab') - @author = Author.new(id: 1, name: 'Steve K.') - @author.posts = [] - @author.bio = nil - @author.roles = [@role1, @role2] - @role1.author = @author - @role2.author = @author - @author2 = Author.new(id: 2, name: 'Anonymous') - @author2.posts = [] - @author2.bio = nil - @author2.roles = [] - @post = Post.new(id: 1, title: 'New Post', body: 'Body') - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - @post.comments = [@first_comment, @second_comment] - @post.author = @author - @first_comment.post = @post - @first_comment.author = @author2 - @second_comment.post = @post - @second_comment.author = nil - @post2 = Post.new(id: 2, title: 'Another Post', body: 'Body') - @post2.author = @author - @post2.comments = [] - @blog = Blog.new(id: 1, name: 'My Blog!!') - @post.blog = @blog - @post2.blog = @blog - end - - def render_resource_without_include - setup_post - render json: @post - end - - def render_resource_with_include - setup_post - render json: @post, adapter: :json_api, include: [:author] - end - - def render_resource_with_include_of_custom_key_by_original - setup_post - render json: @post, adapter: :json_api, include: [:reviews], serializer: PostWithCustomKeysSerializer - end - - def render_resource_with_nested_include - setup_post - render json: @post, adapter: :json_api, include: [comments: [:author]] - end - - def render_resource_with_nested_has_many_include_wildcard - setup_post - render json: @post, adapter: :json_api, include: 'author.*' - end - - def render_resource_with_missing_nested_has_many_include - setup_post - @post.author = @author2 # author2 has no roles. - render json: @post, adapter: :json_api, include: [author: [:roles]] - end - - def render_collection_with_missing_nested_has_many_include - setup_post - @post.author = @author2 - render json: [@post, @post2], adapter: :json_api, include: [author: [:roles]] - end - - def render_collection_without_include - setup_post - render json: [@post], adapter: :json_api - end - - def render_collection_with_include - setup_post - render json: [@post], adapter: :json_api, include: 'author, comments' - end - end - - setup do - @routes = Rails.application.routes.draw do - ActiveSupport::Deprecation.silence do - match ':action', to: LinkedTestController, via: [:get, :post] - end - end - end - - def test_render_resource_without_include - get '/render_resource_without_include' - response = JSON.parse(@response.body) - refute response.key? 'included' - end - - def test_render_resource_with_include - get '/render_resource_with_include' - response = JSON.parse(@response.body) - assert response.key? 'included' - assert_equal 1, response['included'].size - assert_equal 'Steve K.', response['included'].first['attributes']['name'] - end - - def test_render_resource_with_nested_has_many_include - get '/render_resource_with_nested_has_many_include_wildcard' - response = JSON.parse(@response.body) - expected_linked = [ - { - 'id' => '1', - 'type' => 'authors', - 'attributes' => { - 'name' => 'Steve K.' - }, - 'relationships' => { - 'posts' => { 'data' => [] }, - 'roles' => { 'data' => [{ 'type' => 'roles', 'id' => '1' }, { 'type' => 'roles', 'id' => '2' }] }, - 'bio' => { 'data' => nil } - } - }, { - 'id' => '1', - 'type' => 'roles', - 'attributes' => { - 'name' => 'admin', - 'description' => nil, - 'slug' => 'admin-1' - }, - 'relationships' => { - 'author' => { 'data' => { 'type' => 'authors', 'id' => '1' } } - } - }, { - 'id' => '2', - 'type' => 'roles', - 'attributes' => { - 'name' => 'colab', - 'description' => nil, - 'slug' => 'colab-2' - }, - 'relationships' => { - 'author' => { 'data' => { 'type' => 'authors', 'id' => '1' } } - } - } - ] - assert_equal expected_linked, response['included'] - end - - def test_render_resource_with_include_of_custom_key_by_original - get '/render_resource_with_include_of_custom_key_by_original' - response = JSON.parse(@response.body) - assert response.key? 'included' - - relationships = response['data']['relationships'] - - assert_includes relationships, 'reviews' - assert_includes relationships, 'writer' - assert_includes relationships, 'site' - end - - def test_render_resource_with_nested_include - get '/render_resource_with_nested_include' - response = JSON.parse(@response.body) - assert response.key? 'included' - assert_equal 3, response['included'].size - end - - def test_render_collection_without_include - get '/render_collection_without_include' - response = JSON.parse(@response.body) - refute response.key? 'included' - end - - def test_render_collection_with_include - get '/render_collection_with_include' - response = JSON.parse(@response.body) - assert response.key? 'included' - end - - def test_render_resource_with_nested_attributes_even_when_missing_associations - get '/render_resource_with_missing_nested_has_many_include' - response = JSON.parse(@response.body) - assert response.key? 'included' - refute include_type?(response['included'], 'roles') - end - - def test_render_collection_with_missing_nested_has_many_include - get '/render_collection_with_missing_nested_has_many_include' - response = JSON.parse(@response.body) - assert response.key? 'included' - assert include_type?(response['included'], 'roles') - end - - def include_type?(collection, value) - collection.detect { |i| i['type'] == value } - end - end - end - end -end diff --git a/test/action_controller/json_api/pagination_test.rb b/test/action_controller/json_api/pagination_test.rb deleted file mode 100644 index 0af086b7..00000000 --- a/test/action_controller/json_api/pagination_test.rb +++ /dev/null @@ -1,116 +0,0 @@ -require 'test_helper' -require 'will_paginate/array' -require 'kaminari' -require 'kaminari/hooks' -::Kaminari::Hooks.init - -module ActionController - module Serialization - class JsonApi - class PaginationTest < ActionController::TestCase - KAMINARI_URI = 'http://test.host/action_controller/serialization/json_api/pagination_test/pagination_test/render_pagination_using_kaminari'.freeze - WILL_PAGINATE_URI = 'http://test.host/action_controller/serialization/json_api/pagination_test/pagination_test/render_pagination_using_will_paginate'.freeze - - class PaginationTestController < ActionController::Base - def setup - @array = [ - Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1'), - Profile.new(name: 'Name 2', description: 'Description 2', comments: 'Comments 2'), - Profile.new(name: 'Name 3', description: 'Description 3', comments: 'Comments 3') - ] - end - - def using_kaminari - setup - Kaminari.paginate_array(@array).page(params[:page][:number]).per(params[:page][:size]) - end - - def using_will_paginate - setup - @array.paginate(page: params[:page][:number], per_page: params[:page][:size]) - end - - def render_pagination_using_kaminari - render json: using_kaminari, adapter: :json_api - end - - def render_pagination_using_will_paginate - render json: using_will_paginate, adapter: :json_api - end - - def render_array_without_pagination_links - setup - render json: @array, adapter: :json_api - end - end - - tests PaginationTestController - - def test_render_pagination_links_with_will_paginate - expected_links = { 'self' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=2&page%5Bsize%5D=1", - 'first' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1", - 'prev' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1", - 'next' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1", - 'last' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1" } - - get :render_pagination_using_will_paginate, params: { page: { number: 2, size: 1 } } - response = JSON.parse(@response.body) - assert_equal expected_links, response['links'] - end - - def test_render_only_last_and_next_pagination_links - expected_links = { 'self' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=1&page%5Bsize%5D=2", - '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 } } - response = JSON.parse(@response.body) - assert_equal expected_links, response['links'] - end - - def test_render_pagination_links_with_kaminari - expected_links = { 'self' => "#{KAMINARI_URI}?page%5Bnumber%5D=2&page%5Bsize%5D=1", - 'first' => "#{KAMINARI_URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1", - 'prev' => "#{KAMINARI_URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1", - 'next' => "#{KAMINARI_URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1", - 'last' => "#{KAMINARI_URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1" } - get :render_pagination_using_kaminari, params: { page: { number: 2, size: 1 } } - response = JSON.parse(@response.body) - assert_equal expected_links, response['links'] - end - - def test_render_only_prev_and_first_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" } - 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 - expected_links = { 'self' => "#{WILL_PAGINATE_URI}?page%5Bnumber%5D=1&page%5Bsize%5D=2&teste=additional", - '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' } - response = JSON.parse(@response.body) - assert_equal expected_links, response['links'] - end - - def test_render_only_prev_and_first_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" } - get :render_pagination_using_kaminari, params: { page: { number: 3, size: 1 }, teste: 'additional' } - response = JSON.parse(@response.body) - assert_equal expected_links, response['links'] - end - - def test_array_without_pagination_links - get :render_array_without_pagination_links, params: { page: { number: 2, size: 1 } } - response = JSON.parse(@response.body) - refute response.key? 'links' - end - end - end - end -end diff --git a/test/action_controller/json_api/transform_test.rb b/test/action_controller/json_api/transform_test.rb deleted file mode 100644 index 69212f32..00000000 --- a/test/action_controller/json_api/transform_test.rb +++ /dev/null @@ -1,189 +0,0 @@ -require 'test_helper' - -module ActionController - module Serialization - class JsonApi - class KeyTransformTest < ActionController::TestCase - class KeyTransformTestController < ActionController::Base - class Post < ::Model - attributes :title, :body, :publish_at - associations :author, :top_comments - end - class Author < ::Model - attributes :first_name, :last_name - end - class TopComment < ::Model - attributes :body - associations :author, :post - end - class PostSerializer < ActiveModel::Serializer - type 'posts' - attributes :title, :body, :publish_at - belongs_to :author - has_many :top_comments - - link(:post_authors) { 'https://example.com/post_authors' } - - meta do - { - rating: 5, - favorite_count: 10 - } - end - end - - class AuthorSerializer < ActiveModel::Serializer - type 'authors' - attributes :first_name, :last_name - end - - class TopCommentSerializer < ActiveModel::Serializer - type 'top_comments' - attributes :body - belongs_to :author - end - - def setup_post - ActionController::Base.cache_store.clear - @author = Author.new(id: 1, first_name: 'Bob', last_name: 'Jones') - @comment1 = TopComment.new(id: 7, body: 'cool', author: @author) - @comment2 = TopComment.new(id: 12, body: 'awesome', author: @author) - @post = Post.new(id: 1337, title: 'Title 1', body: 'Body 1', - author: @author, top_comments: [@comment1, @comment2], - publish_at: '2020-03-16T03:55:25.291Z') - @comment1.post = @post - @comment2.post = @post - end - - def render_resource_with_transform - setup_post - render json: @post, serializer: PostSerializer, adapter: :json_api, - key_transform: :camel - end - - def render_resource_with_transform_nil - setup_post - render json: @post, serializer: PostSerializer, adapter: :json_api, - key_transform: nil - end - - def render_resource_with_transform_with_global_config - old_transform = ActiveModelSerializers.config.key_transform - setup_post - ActiveModelSerializers.config.key_transform = :camel_lower - render json: @post, serializer: PostSerializer, adapter: :json_api - ensure - ActiveModelSerializers.config.key_transform = old_transform - end - end - - tests KeyTransformTestController - - def test_render_resource_with_transform - get :render_resource_with_transform - response = JSON.parse(@response.body) - expected = { - 'Data' => { - 'Id' => '1337', - 'Type' => 'Posts', - 'Attributes' => { - 'Title' => 'Title 1', - 'Body' => 'Body 1', - 'PublishAt' => '2020-03-16T03:55:25.291Z' - }, - 'Relationships' => { - 'Author' => { - 'Data' => { - 'Id' => '1', - 'Type' => 'Authors' - } - }, - 'TopComments' => { - 'Data' => [ - { 'Id' => '7', 'Type' => 'TopComments' }, - { 'Id' => '12', 'Type' => 'TopComments' } - ] - } - }, - 'Links' => { - 'PostAuthors' => 'https://example.com/post_authors' - }, - 'Meta' => { 'Rating' => 5, 'FavoriteCount' => 10 } - } - } - assert_equal expected, response - end - - def test_render_resource_with_transform_nil - get :render_resource_with_transform_nil - response = JSON.parse(@response.body) - expected = { - 'data' => { - 'id' => '1337', - 'type' => 'posts', - 'attributes' => { - 'title' => 'Title 1', - 'body' => 'Body 1', - 'publish-at' => '2020-03-16T03:55:25.291Z' - }, - 'relationships' => { - 'author' => { - 'data' => { - 'id' => '1', - 'type' => 'authors' - } - }, - 'top-comments' => { - 'data' => [ - { 'id' => '7', 'type' => 'top-comments' }, - { 'id' => '12', 'type' => 'top-comments' } - ] - } - }, - 'links' => { - 'post-authors' => 'https://example.com/post_authors' - }, - 'meta' => { 'rating' => 5, 'favorite-count' => 10 } - } - } - assert_equal expected, response - end - - def test_render_resource_with_transform_with_global_config - get :render_resource_with_transform_with_global_config - response = JSON.parse(@response.body) - expected = { - 'data' => { - 'id' => '1337', - 'type' => 'posts', - 'attributes' => { - 'title' => 'Title 1', - 'body' => 'Body 1', - 'publishAt' => '2020-03-16T03:55:25.291Z' - }, - 'relationships' => { - 'author' => { - 'data' => { - 'id' => '1', - 'type' => 'authors' - } - }, - 'topComments' => { - 'data' => [ - { 'id' => '7', 'type' => 'topComments' }, - { 'id' => '12', 'type' => 'topComments' } - ] - } - }, - 'links' => { - 'postAuthors' => 'https://example.com/post_authors' - }, - 'meta' => { 'rating' => 5, 'favoriteCount' => 10 } - } - } - assert_equal expected, response - end - end - end - end -end diff --git a/test/action_controller/lookup_proc_test.rb b/test/action_controller/lookup_proc_test.rb deleted file mode 100644 index 4d2ad0b1..00000000 --- a/test/action_controller/lookup_proc_test.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'test_helper' - -module ActionController - module Serialization - class LookupProcTest < ActionController::TestCase - module Api - module V3 - class PostCustomSerializer < ActiveModel::Serializer - attributes :title, :body - - belongs_to :author - end - - class AuthorCustomSerializer < ActiveModel::Serializer - attributes :name - end - - class LookupProcTestController < ActionController::Base - def implicit_namespaced_serializer - author = Author.new(name: 'Bob') - post = Post.new(title: 'New Post', body: 'Body', author: author) - - render json: post - end - end - end - end - - tests Api::V3::LookupProcTestController - - test 'implicitly uses namespaced serializer' do - controller_namespace = lambda do |resource_class, _parent_serializer_class, namespace| - "#{namespace}::#{resource_class}CustomSerializer" if namespace - end - - with_prepended_lookup(controller_namespace) do - get :implicit_namespaced_serializer - - assert_serializer Api::V3::PostCustomSerializer - - expected = { 'title' => 'New Post', 'body' => 'Body', 'author' => { 'name' => 'Bob' } } - actual = JSON.parse(@response.body) - - assert_equal expected, actual - end - end - end - end -end diff --git a/test/action_controller/namespace_lookup_test.rb b/test/action_controller/namespace_lookup_test.rb deleted file mode 100644 index b5c8f496..00000000 --- a/test/action_controller/namespace_lookup_test.rb +++ /dev/null @@ -1,232 +0,0 @@ -require 'test_helper' - -module ActionController - module Serialization - class NamespaceLookupTest < ActionController::TestCase - class Book < ::Model - attributes :id, :title, :body - associations :writer, :chapters - end - class Chapter < ::Model - attributes :title - end - class Writer < ::Model - attributes :name - end - - module Api - module V2 - class BookSerializer < ActiveModel::Serializer - attributes :title - end - end - - module VHeader - class BookSerializer < ActiveModel::Serializer - attributes :title, :body - - def body - 'header' - end - end - end - - module V3 - class BookSerializer < ActiveModel::Serializer - attributes :title, :body - - belongs_to :writer - has_many :chapters - end - - class ChapterSerializer < ActiveModel::Serializer - attribute :title do - "Chapter - #{object.title}" - end - end - - class WriterSerializer < ActiveModel::Serializer - attributes :name - end - - class LookupTestController < ActionController::Base - before_action only: [:namespace_set_in_before_filter] do - self.namespace_for_serializer = Api::V2 - end - - def implicit_namespaced_serializer - writer = Writer.new(name: 'Bob') - book = Book.new(title: 'New Post', body: 'Body', writer: writer, chapters: []) - - render json: book - end - - def implicit_namespaced_collection_serializer - chapter1 = Chapter.new(title: 'Oh') - chapter2 = Chapter.new(title: 'Oh my') - - render json: [chapter1, chapter2] - end - - def implicit_has_many_namespaced_serializer - chapter1 = Chapter.new(title: 'Odd World') - chapter2 = Chapter.new(title: 'New World') - book = Book.new(title: 'New Post', body: 'Body', chapters: [chapter1, chapter2]) - - render json: book - end - - def explicit_namespace_as_module - book = Book.new(title: 'New Post', body: 'Body') - - render json: book, namespace: Api::V2 - end - - def explicit_namespace_as_string - book = Book.new(title: 'New Post', body: 'Body') - - # because this is a string, ruby can't auto-lookup the constant, so otherwise - # the lookup thinks we mean ::Api::V2 - render json: book, namespace: 'ActionController::Serialization::NamespaceLookupTest::Api::V2' - end - - def explicit_namespace_as_symbol - book = Book.new(title: 'New Post', body: 'Body') - - # because this is a string, ruby can't auto-lookup the constant, so otherwise - # the lookup thinks we mean ::Api::V2 - render json: book, namespace: :'ActionController::Serialization::NamespaceLookupTest::Api::V2' - end - - def invalid_namespace - book = Book.new(id: 'invalid_namespace_book_id', title: 'New Post', body: 'Body') - - render json: book, namespace: :api_v2 - end - - def namespace_set_in_before_filter - book = Book.new(title: 'New Post', body: 'Body') - render json: book - end - - def namespace_set_by_request_headers - book = Book.new(title: 'New Post', body: 'Body') - version_from_header = request.headers['X-API_VERSION'] - namespace = "ActionController::Serialization::NamespaceLookupTest::#{version_from_header}" - - render json: book, namespace: namespace - end - end - end - end - - tests Api::V3::LookupTestController - - setup do - @test_namespace = self.class.parent - end - - test 'uses request headers to determine the namespace' do - request.env['X-API_VERSION'] = 'Api::VHeader' - get :namespace_set_by_request_headers - - assert_serializer Api::VHeader::BookSerializer - end - - test 'implicitly uses namespaced serializer' do - get :implicit_namespaced_serializer - - assert_serializer Api::V3::BookSerializer - - expected = { 'title' => 'New Post', 'body' => 'Body', 'writer' => { 'name' => 'Bob' }, 'chapters' => [] } - actual = JSON.parse(@response.body) - - assert_equal expected, actual - end - - test 'implicitly uses namespaced serializer for collection' do - get :implicit_namespaced_collection_serializer - - assert_serializer 'ActiveModel::Serializer::CollectionSerializer' - - expected = [{ 'title' => 'Chapter - Oh' }, { 'title' => 'Chapter - Oh my' }] - actual = JSON.parse(@response.body) - - assert_equal expected, actual - end - - test 'implicitly uses namespaced serializer for has_many' do - get :implicit_has_many_namespaced_serializer - - assert_serializer Api::V3::BookSerializer - - expected = { - 'title' => 'New Post', - 'body' => 'Body', 'writer' => nil, - 'chapters' => [ - { 'title' => 'Chapter - Odd World' }, - { 'title' => 'Chapter - New World' } - ] - } - actual = JSON.parse(@response.body) - - assert_equal expected, actual - end - - test 'explicit namespace as module' do - get :explicit_namespace_as_module - - assert_serializer Api::V2::BookSerializer - - expected = { 'title' => 'New Post' } - actual = JSON.parse(@response.body) - - assert_equal expected, actual - end - - test 'explicit namespace as string' do - get :explicit_namespace_as_string - - assert_serializer Api::V2::BookSerializer - - expected = { 'title' => 'New Post' } - actual = JSON.parse(@response.body) - - assert_equal expected, actual - end - - test 'explicit namespace as symbol' do - get :explicit_namespace_as_symbol - - assert_serializer Api::V2::BookSerializer - - expected = { 'title' => 'New Post' } - actual = JSON.parse(@response.body) - - assert_equal expected, actual - end - - test 'invalid namespace' do - get :invalid_namespace - - assert_serializer ActiveModel::Serializer::Null - - expected = { 'id' => 'invalid_namespace_book_id', 'title' => 'New Post', 'body' => 'Body' } - actual = JSON.parse(@response.body) - - assert_equal expected, actual - end - - test 'namespace set in before filter' do - get :namespace_set_in_before_filter - - assert_serializer Api::V2::BookSerializer - - expected = { 'title' => 'New Post' } - actual = JSON.parse(@response.body) - - assert_equal expected, actual - end - end - end -end diff --git a/test/action_controller/serialization_scope_name_test.rb b/test/action_controller/serialization_scope_name_test.rb deleted file mode 100644 index 3d767d04..00000000 --- a/test/action_controller/serialization_scope_name_test.rb +++ /dev/null @@ -1,235 +0,0 @@ -require 'test_helper' - -module SerializationScopeTesting - class User < ActiveModelSerializers::Model - attributes :id, :name, :admin - def admin? - admin - end - end - class Comment < ActiveModelSerializers::Model - attributes :id, :body - end - class Post < ActiveModelSerializers::Model - attributes :id, :title, :body, :comments - end - class PostSerializer < ActiveModel::Serializer - attributes :id, :title, :body, :comments - - def body - "The 'scope' is the 'current_user': #{scope == current_user}" - end - - def comments - if current_user.admin? - [Comment.new(id: 1, body: 'Admin')] - else - [Comment.new(id: 2, body: 'Scoped')] - end - end - - def json_key - 'post' - end - end - class PostTestController < ActionController::Base - attr_writer :current_user - - def render_post_by_non_admin - self.current_user = User.new(id: 3, name: 'Pete', admin: false) - render json: new_post, serializer: serializer, adapter: :json - end - - def render_post_by_admin - self.current_user = User.new(id: 3, name: 'Pete', admin: true) - render json: new_post, serializer: serializer, adapter: :json - end - - def current_user - defined?(@current_user) ? @current_user : :current_user_not_set - end - - private - - def new_post - Post.new(id: 4, title: 'Title') - end - - def serializer - PostSerializer - end - end - class PostViewContextSerializer < PostSerializer - def body - "The 'scope' is the 'view_context': #{scope == view_context}" - end - - def comments - if view_context.controller.current_user.admin? - [Comment.new(id: 1, body: 'Admin')] - else - [Comment.new(id: 2, body: 'Scoped')] - end - end - end - class DefaultScopeTest < ActionController::TestCase - tests PostTestController - - def test_default_serialization_scope - assert_equal :current_user, @controller._serialization_scope - end - - def test_default_serialization_scope_object - assert_equal :current_user_not_set, @controller.current_user - assert_equal :current_user_not_set, @controller.serialization_scope - end - - def test_default_scope_non_admin - get :render_post_by_non_admin - expected_json = { - post: { - id: 4, - title: 'Title', - body: "The 'scope' is the 'current_user': true", - comments: [ - { id: 2, body: 'Scoped' } - ] - } - }.to_json - assert_equal expected_json, @response.body - end - - def test_default_scope_admin - get :render_post_by_admin - expected_json = { - post: { - id: 4, - title: 'Title', - body: "The 'scope' is the 'current_user': true", - comments: [ - { id: 1, body: 'Admin' } - ] - } - }.to_json - assert_equal expected_json, @response.body - end - end - class SerializationScopeTest < ActionController::TestCase - class PostViewContextTestController < PostTestController - serialization_scope :view_context - - private - - def serializer - PostViewContextSerializer - end - end - tests PostViewContextTestController - - def test_defined_serialization_scope - assert_equal :view_context, @controller._serialization_scope - end - - def test_defined_serialization_scope_object - assert_equal @controller.view_context.controller, @controller.serialization_scope.controller - end - - def test_serialization_scope_non_admin - get :render_post_by_non_admin - expected_json = { - post: { - id: 4, - title: 'Title', - body: "The 'scope' is the 'view_context': true", - comments: [ - { id: 2, body: 'Scoped' } - ] - } - }.to_json - assert_equal expected_json, @response.body - end - - def test_serialization_scope_admin - get :render_post_by_admin - expected_json = { - post: { - id: 4, - title: 'Title', - body: "The 'scope' is the 'view_context': true", - comments: [ - { id: 1, body: 'Admin' } - ] - } - }.to_json - assert_equal expected_json, @response.body - end - end - class NilSerializationScopeTest < ActionController::TestCase - class PostViewContextTestController < ActionController::Base - serialization_scope nil - - attr_accessor :current_user - - def render_post_with_no_scope - self.current_user = User.new(id: 3, name: 'Pete', admin: false) - render json: new_post, serializer: PostSerializer, adapter: :json - end - - def render_post_with_passed_in_scope - self.current_user = User.new(id: 3, name: 'Pete', admin: false) - render json: new_post, serializer: PostSerializer, adapter: :json, scope: current_user, scope_name: :current_user - end - - def render_post_with_passed_in_scope_without_scope_name - self.current_user = User.new(id: 3, name: 'Pete', admin: false) - render json: new_post, serializer: PostSerializer, adapter: :json, scope: current_user - end - - private - - def new_post - Post.new(id: 4, title: 'Title') - end - end - tests PostViewContextTestController - - def test_nil_serialization_scope - assert_nil @controller._serialization_scope - end - - def test_nil_serialization_scope_object - assert_nil @controller.serialization_scope - end - - def test_nil_scope - exception_matcher = /current_user/ - exception = assert_raises(NameError) do - get :render_post_with_no_scope - end - assert_match exception_matcher, exception.message - end - - def test_serialization_scope_is_and_nil_scope_passed_in_current_user - get :render_post_with_passed_in_scope - expected_json = { - post: { - id: 4, - title: 'Title', - body: "The 'scope' is the 'current_user': true", - comments: [ - { id: 2, body: 'Scoped' } - ] - } - }.to_json - assert_equal expected_json, @response.body - end - - def test_serialization_scope_is_nil_and_scope_passed_in_current_user_without_scope_name - exception_matcher = /current_user/ - exception = assert_raises(NameError) do - get :render_post_with_passed_in_scope_without_scope_name - end - assert_match exception_matcher, exception.message - end - end -end diff --git a/test/action_controller/serialization_test.rb b/test/action_controller/serialization_test.rb deleted file mode 100644 index dfd72b42..00000000 --- a/test/action_controller/serialization_test.rb +++ /dev/null @@ -1,472 +0,0 @@ -require 'test_helper' - -module ActionController - 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 - end - - def render_using_default_adapter_root - @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') - render json: @profile - end - - def render_array_using_custom_root - @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') - render json: [@profile], root: 'custom_root' - end - - def render_array_that_is_empty_using_custom_root - render json: [], root: 'custom_root' - end - - def render_object_using_custom_root - @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') - render json: @profile, root: 'custom_root' - end - - def render_array_using_implicit_serializer - array = [ - Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1'), - Profile.new(name: 'Name 2', description: 'Description 2', comments: 'Comments 2') - ] - render json: array - end - - def render_array_using_implicit_serializer_and_meta - @profiles = [ - Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') - ] - render json: @profiles, meta: { total: 10 } - end - - def render_array_using_implicit_serializer_and_links - with_adapter ActiveModelSerializers::Adapter::JsonApi do - @profiles = [ - Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') - ] - - render json: @profiles, links: { self: 'http://example.com/api/profiles/1' } - end - end - - def render_object_with_cache_enabled - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @author = Author.new(id: 1, name: 'Joao Moura.') - @post = Post.new(id: 1, title: 'New Post', body: 'Body', comments: [@comment], author: @author) - - generate_cached_serializer(@post) - - @post.title = 'ZOMG a New Post' - render json: @post - end - - def render_json_object_without_serializer - render json: { error: 'Result is Invalid' } - end - - def render_json_array_object_without_serializer - render json: [{ error: 'Result is Invalid' }] - end - - def update_and_render_object_with_cache_enabled - @post.updated_at = Time.zone.now - - generate_cached_serializer(@post) - render json: @post - end - - def render_object_expired_with_cache_enabled - comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - author = Author.new(id: 1, name: 'Joao Moura.') - post = Post.new(id: 1, title: 'New Post', body: 'Body', comments: [comment], author: author) - - generate_cached_serializer(post) - - post.title = 'ZOMG a New Post' - - expires_in = [ - PostSerializer._cache_options[:expires_in], - CommentSerializer._cache_options[:expires_in] - ].max + 200 - - Timecop.travel(Time.zone.now + expires_in) do - render json: post - end - end - - def render_changed_object_with_cache_enabled - comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - author = Author.new(id: 1, name: 'Joao Moura.') - post = Post.new(id: 1, title: 'ZOMG a New Post', body: 'Body', comments: [comment], author: author) - - render json: post - end - - def render_fragment_changed_object_with_only_cache_enabled - author = Author.new(id: 1, name: 'Joao Moura.') - role = Role.new(id: 42, name: 'ZOMG A ROLE', description: 'DESCRIPTION HERE', author: author) - - generate_cached_serializer(role) - role.name = 'lol' - role.description = 'HUEHUEBRBR' - - render json: role - end - - def render_fragment_changed_object_with_except_cache_enabled - author = Author.new(id: 1, name: 'Joao Moura.') - bio = Bio.new(id: 42, content: 'ZOMG A ROLE', rating: 5, author: author) - - generate_cached_serializer(bio) - bio.content = 'lol' - bio.rating = 0 - - render json: bio - end - - def render_fragment_changed_object_with_relationship - comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - comment2 = Comment.new(id: 1, body: 'ZOMG AN UPDATED-BUT-NOT-CACHE-EXPIRED COMMENT') - like = Like.new(id: 1, likeable: comment, time: 3.days.ago) - - generate_cached_serializer(like) - like.likeable = comment2 - like.time = Time.zone.now.to_s - - render json: like - end - end - - tests ImplicitSerializationTestController - - # We just have Null for now, this will change - def test_render_using_implicit_serializer - get :render_using_implicit_serializer - - expected = { - name: 'Name 1', - description: 'Description 1' - } - - assert_equal 'application/json', @response.content_type - assert_equal expected.to_json, @response.body - end - - def test_render_using_default_root - with_adapter :json_api do - get :render_using_default_adapter_root - end - expected = { - data: { - id: @controller.instance_variable_get(:@profile).id.to_s, - type: 'profiles', - attributes: { - name: 'Name 1', - description: 'Description 1' - } - } - } - - assert_equal 'application/json', @response.content_type - assert_equal expected.to_json, @response.body - end - - def test_render_array_using_custom_root - with_adapter :json do - get :render_array_using_custom_root - end - expected = { custom_root: [{ name: 'Name 1', description: 'Description 1' }] } - assert_equal 'application/json', @response.content_type - assert_equal expected.to_json, @response.body - end - - def test_render_array_that_is_empty_using_custom_root - with_adapter :json do - get :render_array_that_is_empty_using_custom_root - end - - expected = { custom_root: [] } - assert_equal 'application/json', @response.content_type - assert_equal expected.to_json, @response.body - end - - def test_render_object_using_custom_root - with_adapter :json do - get :render_object_using_custom_root - end - - expected = { custom_root: { name: 'Name 1', description: 'Description 1' } } - assert_equal 'application/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 - expected_body = { error: 'Result is Invalid' } - assert_equal expected_body.to_json, @response.body - end - - def test_render_json_array_object_without_serializer - get :render_json_array_object_without_serializer - - assert_equal 'application/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 - - expected = [ - { - name: 'Name 1', - description: 'Description 1' - }, - { - name: 'Name 2', - description: 'Description 2' - } - ] - - assert_equal expected.to_json, @response.body - end - - def test_render_array_using_implicit_serializer_and_meta - with_adapter :json_api do - get :render_array_using_implicit_serializer_and_meta - end - expected = { - data: [ - { - id: @controller.instance_variable_get(:@profiles).first.id.to_s, - type: 'profiles', - attributes: { - name: 'Name 1', - description: 'Description 1' - } - } - ], - meta: { - total: 10 - } - } - - assert_equal 'application/json', @response.content_type - assert_equal expected.to_json, @response.body - end - - def test_render_array_using_implicit_serializer_and_links - get :render_array_using_implicit_serializer_and_links - - expected = { - data: [ - { - id: @controller.instance_variable_get(:@profiles).first.id.to_s, - type: 'profiles', - attributes: { - name: 'Name 1', - description: 'Description 1' - } - } - ], - links: { - self: 'http://example.com/api/profiles/1' - } - } - - assert_equal 'application/json', @response.content_type - assert_equal expected.to_json, @response.body - end - - def test_render_with_cache_enable - expected = { - id: 1, - title: 'New Post', - body: 'Body', - comments: [ - { - id: 1, - body: 'ZOMG A COMMENT' - } - ], - blog: { - id: 999, - name: 'Custom blog' - }, - author: { - id: 1, - name: 'Joao Moura.' - } - } - - ActionController::Base.cache_store.clear - Timecop.freeze(Time.zone.now) do - get :render_object_with_cache_enabled - - assert_equal 'application/json', @response.content_type - assert_equal expected.to_json, @response.body - - get :render_changed_object_with_cache_enabled - assert_equal expected.to_json, @response.body - end - - ActionController::Base.cache_store.clear - get :render_changed_object_with_cache_enabled - assert_not_equal expected.to_json, @response.body - end - - def test_render_with_cache_enable_and_expired - ActionController::Base.cache_store.clear - get :render_object_expired_with_cache_enabled - - expected = { - id: 1, - title: 'ZOMG a New Post', - body: 'Body', - comments: [ - { - id: 1, - body: 'ZOMG A COMMENT' - } - ], - blog: { - id: 999, - name: 'Custom blog' - }, - author: { - id: 1, - name: 'Joao Moura.' - } - } - - assert_equal 'application/json', @response.content_type - actual = @response.body - expected = expected.to_json - if ENV['APPVEYOR'] && actual != expected - skip('Cache expiration tests sometimes fail on Appveyor. FIXME :)') - else - assert_equal actual, expected - end - end - - def test_render_with_fragment_only_cache_enable - ActionController::Base.cache_store.clear - get :render_fragment_changed_object_with_only_cache_enabled - response = JSON.parse(@response.body) - - assert_equal 'application/json', @response.content_type - assert_equal 'ZOMG A ROLE', response['name'] - assert_equal 'HUEHUEBRBR', response['description'] - end - - def test_render_with_fragment_except_cache_enable - ActionController::Base.cache_store.clear - get :render_fragment_changed_object_with_except_cache_enabled - response = JSON.parse(@response.body) - - assert_equal 'application/json', @response.content_type - assert_equal 5, response['rating'] - assert_equal 'lol', response['content'] - end - - def test_render_fragment_changed_object_with_relationship - ActionController::Base.cache_store.clear - - Timecop.freeze(Time.zone.now) do - get :render_fragment_changed_object_with_relationship - response = JSON.parse(@response.body) - - expected_return = { - 'id' => 1, - 'time' => Time.zone.now.to_s, - 'likeable' => { - 'id' => 1, - 'body' => 'ZOMG A COMMENT' - } - } - - assert_equal 'application/json', @response.content_type - assert_equal expected_return, response - end - end - - def test_cache_expiration_on_update - ActionController::Base.cache_store.clear - get :render_object_with_cache_enabled - - expected = { - id: 1, - title: 'ZOMG a New Post', - body: 'Body', - comments: [ - { - id: 1, - body: 'ZOMG A COMMENT' - } - ], - blog: { - id: 999, - name: 'Custom blog' - }, - author: { - id: 1, - name: 'Joao Moura.' - } - } - - get :update_and_render_object_with_cache_enabled - - assert_equal 'application/json', @response.content_type - actual = @response.body - expected = expected.to_json - if ENV['APPVEYOR'] && actual != expected - skip('Cache expiration tests sometimes fail on Appveyor. FIXME :)') - else - assert_equal actual, expected - end - end - - def test_warn_overridding_use_adapter_as_falsy_on_controller_instance - controller = Class.new(ImplicitSerializationTestController) do - def use_adapter? - false - end - end.new - assert_output(nil, /adapter: false/) do - controller.get_serializer(Profile.new) - end - end - - def test_dont_warn_overridding_use_adapter_as_truthy_on_controller_instance - controller = Class.new(ImplicitSerializationTestController) do - def use_adapter? - true - end - end.new - assert_output(nil, '') do - controller.get_serializer(Profile.new) - end - end - - def test_render_event_is_emitted - subscriber = ::ActiveSupport::Notifications.subscribe('render.active_model_serializers') do |name| - @name = name - end - - get :render_using_implicit_serializer - - assert_equal 'render.active_model_serializers', @name - ensure - ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber - end - end - end -end diff --git a/test/active_model_serializers/adapter_for_test.rb b/test/active_model_serializers/adapter_for_test.rb deleted file mode 100644 index 1439b987..00000000 --- a/test/active_model_serializers/adapter_for_test.rb +++ /dev/null @@ -1,208 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - class AdapterForTest < ::ActiveSupport::TestCase - UnknownAdapterError = ::ActiveModelSerializers::Adapter::UnknownAdapterError - - def test_serializer_adapter_returns_configured_adapter - assert_output(nil, /ActiveModelSerializers::Adapter.configured_adapter/) do - assert_equal ActiveModelSerializers::Adapter.configured_adapter, ActiveModel::Serializer.adapter - end - end - - def test_returns_default_adapter - with_adapter_config_setup do - adapter = ActiveModelSerializers::Adapter.configured_adapter - assert_equal ActiveModelSerializers::Adapter::Attributes, adapter - end - end - - def test_overwrite_adapter_with_symbol - with_adapter_config_setup do - ActiveModelSerializers.config.adapter = :null - - adapter = ActiveModelSerializers::Adapter.configured_adapter - assert_equal ActiveModelSerializers::Adapter::Null, adapter - end - end - - def test_overwrite_adapter_with_camelcased_symbol - with_adapter_config_setup do - ActiveModelSerializers.config.adapter = :JsonApi - - adapter = ActiveModelSerializers::Adapter.configured_adapter - assert_equal ActiveModelSerializers::Adapter::JsonApi, adapter - end - end - - def test_overwrite_adapter_with_string - with_adapter_config_setup do - ActiveModelSerializers.config.adapter = 'json_api' - - adapter = ActiveModelSerializers::Adapter.configured_adapter - assert_equal ActiveModelSerializers::Adapter::JsonApi, adapter - end - end - - def test_overwrite_adapter_with_a_camelcased_string - with_adapter_config_setup do - ActiveModelSerializers.config.adapter = 'JsonApi' - - adapter = ActiveModelSerializers::Adapter.configured_adapter - assert_equal ActiveModelSerializers::Adapter::JsonApi, adapter - end - end - - def test_overwrite_adapter_with_class - with_adapter_config_setup do - ActiveModelSerializers.config.adapter = ActiveModelSerializers::Adapter::Null - - adapter = ActiveModelSerializers::Adapter.configured_adapter - assert_equal ActiveModelSerializers::Adapter::Null, adapter - end - end - - def test_raises_exception_if_invalid_symbol_given - with_adapter_config_setup do - ActiveModelSerializers.config.adapter = :unknown - - assert_raises UnknownAdapterError do - ActiveModelSerializers::Adapter.configured_adapter - end - end - end - - def test_raises_exception_if_it_does_not_know_hot_to_infer_adapter - with_adapter_config_setup do - ActiveModelSerializers.config.adapter = 42 - - assert_raises UnknownAdapterError do - ActiveModelSerializers::Adapter.configured_adapter - end - end - end - - def test_adapter_class_for_known_adapter - klass = ActiveModelSerializers::Adapter.adapter_class(:json_api) - assert_equal ActiveModelSerializers::Adapter::JsonApi, klass - end - - def test_adapter_class_for_unknown_adapter - assert_raises UnknownAdapterError do - ActiveModelSerializers::Adapter.adapter_class(:json_simple) - end - end - - def test_adapter_map - expected_adapter_map = { - 'null'.freeze => ActiveModelSerializers::Adapter::Null, - 'json'.freeze => ActiveModelSerializers::Adapter::Json, - 'attributes'.freeze => ActiveModelSerializers::Adapter::Attributes, - 'json_api'.freeze => ActiveModelSerializers::Adapter::JsonApi - } - actual = ActiveModelSerializers::Adapter.adapter_map - assert_equal actual, expected_adapter_map - end - - def test_adapters - assert_equal ActiveModelSerializers::Adapter.adapters.sort, [ - 'attributes'.freeze, - 'json'.freeze, - 'json_api'.freeze, - 'null'.freeze - ] - end - - def test_lookup_adapter_by_string_name - assert_equal ActiveModelSerializers::Adapter.lookup('json'.freeze), ActiveModelSerializers::Adapter::Json - end - - def test_lookup_adapter_by_symbol_name - assert_equal ActiveModelSerializers::Adapter.lookup(:json), ActiveModelSerializers::Adapter::Json - end - - def test_lookup_adapter_by_class - klass = ActiveModelSerializers::Adapter::Json - assert_equal ActiveModelSerializers::Adapter.lookup(klass), klass - end - - def test_lookup_adapter_from_environment_registers_adapter - ActiveModelSerializers::Adapter.const_set(:AdapterFromEnvironment, Class.new) - klass = ::ActiveModelSerializers::Adapter::AdapterFromEnvironment - name = 'adapter_from_environment'.freeze - assert_equal ActiveModelSerializers::Adapter.lookup(name), klass - assert ActiveModelSerializers::Adapter.adapters.include?(name) - ensure - ActiveModelSerializers::Adapter.adapter_map.delete(name) - ActiveModelSerializers::Adapter.send(:remove_const, :AdapterFromEnvironment) - end - - def test_lookup_adapter_for_unknown_name - assert_raises UnknownAdapterError do - ActiveModelSerializers::Adapter.lookup(:json_simple) - end - end - - def test_adapter - assert_equal ActiveModelSerializers.config.adapter, :attributes - assert_equal ActiveModelSerializers::Adapter.configured_adapter, ActiveModelSerializers::Adapter::Attributes - end - - def test_register_adapter - new_adapter_name = :foo - new_adapter_klass = Class.new - ActiveModelSerializers::Adapter.register(new_adapter_name, new_adapter_klass) - assert ActiveModelSerializers::Adapter.adapters.include?('foo'.freeze) - assert ActiveModelSerializers::Adapter.lookup(:foo), new_adapter_klass - ensure - ActiveModelSerializers::Adapter.adapter_map.delete(new_adapter_name.to_s) - end - - def test_inherited_adapter_hooks_register_adapter - Object.const_set(:MyAdapter, Class.new) - my_adapter = MyAdapter - ActiveModelSerializers::Adapter::Base.inherited(my_adapter) - assert_equal ActiveModelSerializers::Adapter.lookup(:my_adapter), my_adapter - ensure - ActiveModelSerializers::Adapter.adapter_map.delete('my_adapter'.freeze) - Object.send(:remove_const, :MyAdapter) - end - - def test_inherited_adapter_hooks_register_namespaced_adapter - Object.const_set(:MyNamespace, Module.new) - MyNamespace.const_set(:MyAdapter, Class.new) - my_adapter = MyNamespace::MyAdapter - ActiveModelSerializers::Adapter::Base.inherited(my_adapter) - assert_equal ActiveModelSerializers::Adapter.lookup(:'my_namespace/my_adapter'), my_adapter - ensure - ActiveModelSerializers::Adapter.adapter_map.delete('my_namespace/my_adapter'.freeze) - MyNamespace.send(:remove_const, :MyAdapter) - Object.send(:remove_const, :MyNamespace) - end - - def test_inherited_adapter_hooks_register_subclass_of_registered_adapter - Object.const_set(:MyAdapter, Class.new) - my_adapter = MyAdapter - Object.const_set(:MySubclassedAdapter, Class.new(MyAdapter)) - my_subclassed_adapter = MySubclassedAdapter - ActiveModelSerializers::Adapter::Base.inherited(my_adapter) - ActiveModelSerializers::Adapter::Base.inherited(my_subclassed_adapter) - assert_equal ActiveModelSerializers::Adapter.lookup(:my_adapter), my_adapter - assert_equal ActiveModelSerializers::Adapter.lookup(:my_subclassed_adapter), my_subclassed_adapter - ensure - ActiveModelSerializers::Adapter.adapter_map.delete('my_adapter'.freeze) - ActiveModelSerializers::Adapter.adapter_map.delete('my_subclassed_adapter'.freeze) - Object.send(:remove_const, :MyAdapter) - Object.send(:remove_const, :MySubclassedAdapter) - end - - private - - def with_adapter_config_setup - previous_adapter = ActiveModelSerializers.config.adapter - yield - ensure - ActiveModelSerializers.config.adapter = previous_adapter - end - end -end diff --git a/test/active_model_serializers/json_pointer_test.rb b/test/active_model_serializers/json_pointer_test.rb deleted file mode 100644 index 60619ee6..00000000 --- a/test/active_model_serializers/json_pointer_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - class JsonPointerTest < ActiveSupport::TestCase - def test_attribute_pointer - attribute_name = 'title' - pointer = ActiveModelSerializers::JsonPointer.new(:attribute, attribute_name) - assert_equal '/data/attributes/title', pointer - end - - def test_primary_data_pointer - pointer = ActiveModelSerializers::JsonPointer.new(:primary_data) - assert_equal '/data', pointer - end - - def test_unknown_data_pointer - assert_raises(TypeError) do - ActiveModelSerializers::JsonPointer.new(:unknown) - end - end - end -end diff --git a/test/active_model_serializers/logging_test.rb b/test/active_model_serializers/logging_test.rb deleted file mode 100644 index 95e61682..00000000 --- a/test/active_model_serializers/logging_test.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class LoggingTest < ActiveSupport::TestCase - class TestLogger < ActiveSupport::Logger - def initialize - @file = StringIO.new - super(@file) - end - - def messages - @file.rewind - @file.read - end - end - - def setup - @author = Author.new(name: 'Steve K.') - @post = Post.new(title: 'New Post', body: 'Body') - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post.comments = [@comment] - @comment.post = @post - @post.author = @author - @author.posts = [@post] - @post_serializer = PostSerializer.new(@post, custom_options: true) - - @old_logger = ActiveModelSerializers.logger - @logger = ActiveSupport::TaggedLogging.new(TestLogger.new) - logger @logger - end - - def teardown - logger @old_logger - end - - def logger(logger) - ActiveModelSerializers.logger = logger - end - - def test_uses_ams_as_tag - ActiveModelSerializers::SerializableResource.new(@post).serializable_hash - assert_match(/\[active_model_serializers\]/, @logger.messages) - end - - def test_logs_when_call_serializable_hash - ActiveModelSerializers::SerializableResource.new(@post).serializable_hash - assert_match(/Rendered/, @logger.messages) - end - - def test_logs_when_call_as_json - ActiveModelSerializers::SerializableResource.new(@post).as_json - assert_match(/Rendered/, @logger.messages) - end - - def test_logs_when_call_to_json - ActiveModelSerializers::SerializableResource.new(@post).to_json - assert_match(/Rendered/, @logger.messages) - end - - def test_logs_correct_serializer - ActiveModelSerializers::SerializableResource.new(@post).serializable_hash - assert_match(/PostSerializer/, @logger.messages) - end - - def test_logs_correct_adapter - ActiveModelSerializers::SerializableResource.new(@post).serializable_hash - assert_match(/ActiveModelSerializers::Adapter::Attributes/, @logger.messages) - end - - def test_logs_the_duration - ActiveModelSerializers::SerializableResource.new(@post).serializable_hash - assert_match(/\(\d+\.\d+ms\)/, @logger.messages) - end - end - end -end diff --git a/test/active_model_serializers/model_test.rb b/test/active_model_serializers/model_test.rb deleted file mode 100644 index 6a8a29af..00000000 --- a/test/active_model_serializers/model_test.rb +++ /dev/null @@ -1,142 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - class ModelTest < ActiveSupport::TestCase - include ActiveModel::Serializer::Lint::Tests - - setup do - @resource = ActiveModelSerializers::Model.new - end - - def test_initialization_with_string_keys - klass = Class.new(ActiveModelSerializers::Model) do - attributes :key - end - value = 'value' - - model_instance = klass.new('key' => value) - - assert_equal model_instance.read_attribute_for_serialization(:key), value - end - - def test_attributes_can_be_read_for_serialization - klass = Class.new(ActiveModelSerializers::Model) do - attributes :one, :two, :three - end - original_attributes = { one: 1, two: 2, three: 3 } - original_instance = klass.new(original_attributes) - - # Initial value - instance = original_instance - expected_attributes = { one: 1, two: 2, three: 3 }.with_indifferent_access - assert_equal expected_attributes, instance.attributes - assert_equal 1, instance.one - assert_equal 1, instance.read_attribute_for_serialization(:one) - - # FIXME: Change via accessor has no effect on attributes. - instance = original_instance.dup - instance.one = :not_one - assert_equal expected_attributes, instance.attributes - assert_equal :not_one, instance.one - assert_equal :not_one, instance.read_attribute_for_serialization(:one) - - # FIXME: Change via mutating attributes - instance = original_instance.dup - instance.attributes[:one] = :not_one - expected_attributes = { one: :not_one, two: 2, three: 3 }.with_indifferent_access - assert_equal expected_attributes, instance.attributes - assert_equal 1, instance.one - assert_equal 1, instance.read_attribute_for_serialization(:one) - end - - def test_attributes_can_be_read_for_serialization_with_attributes_accessors_fix - klass = Class.new(ActiveModelSerializers::Model) do - derive_attributes_from_names_and_fix_accessors - attributes :one, :two, :three - end - original_attributes = { one: 1, two: 2, three: 3 } - original_instance = klass.new(original_attributes) - - # Initial value - instance = original_instance - expected_attributes = { one: 1, two: 2, three: 3 }.with_indifferent_access - assert_equal expected_attributes, instance.attributes - assert_equal 1, instance.one - assert_equal 1, instance.read_attribute_for_serialization(:one) - - expected_attributes = { one: :not_one, two: 2, three: 3 }.with_indifferent_access - # Change via accessor - instance = original_instance.dup - instance.one = :not_one - assert_equal expected_attributes, instance.attributes - assert_equal :not_one, instance.one - assert_equal :not_one, instance.read_attribute_for_serialization(:one) - - # Attributes frozen - assert instance.attributes.frozen? - end - - def test_id_attribute_can_be_read_for_serialization - klass = Class.new(ActiveModelSerializers::Model) do - attributes :id, :one, :two, :three - end - self.class.const_set(:SomeTestModel, klass) - original_attributes = { id: :ego, one: 1, two: 2, three: 3 } - original_instance = klass.new(original_attributes) - - # Initial value - instance = original_instance.dup - expected_attributes = { id: :ego, one: 1, two: 2, three: 3 }.with_indifferent_access - assert_equal expected_attributes, instance.attributes - assert_equal :ego, instance.id - assert_equal :ego, instance.read_attribute_for_serialization(:id) - - # FIXME: Change via accessor has no effect on attributes. - instance = original_instance.dup - instance.id = :superego - assert_equal expected_attributes, instance.attributes - assert_equal :superego, instance.id - assert_equal :superego, instance.read_attribute_for_serialization(:id) - - # FIXME: Change via mutating attributes - instance = original_instance.dup - instance.attributes[:id] = :superego - expected_attributes = { id: :superego, one: 1, two: 2, three: 3 }.with_indifferent_access - assert_equal expected_attributes, instance.attributes - assert_equal :ego, instance.id - assert_equal :ego, instance.read_attribute_for_serialization(:id) - ensure - self.class.send(:remove_const, :SomeTestModel) - end - - def test_id_attribute_can_be_read_for_serialization_with_attributes_accessors_fix - klass = Class.new(ActiveModelSerializers::Model) do - derive_attributes_from_names_and_fix_accessors - attributes :id, :one, :two, :three - end - self.class.const_set(:SomeTestModel, klass) - original_attributes = { id: :ego, one: 1, two: 2, three: 3 } - original_instance = klass.new(original_attributes) - - # Initial value - instance = original_instance.dup - expected_attributes = { id: :ego, one: 1, two: 2, three: 3 }.with_indifferent_access - assert_equal expected_attributes, instance.attributes - assert_equal :ego, instance.id - assert_equal :ego, instance.read_attribute_for_serialization(:id) - - expected_attributes = { id: :superego, one: 1, two: 2, three: 3 }.with_indifferent_access - # Change via accessor - instance = original_instance.dup - instance.id = :superego - assert_equal expected_attributes, instance.attributes - assert_equal :superego, instance.id - assert_equal :superego, instance.read_attribute_for_serialization(:id) - - # Attributes frozen - assert instance.attributes.frozen? - ensure - self.class.send(:remove_const, :SomeTestModel) - end - end -end diff --git a/test/active_model_serializers/railtie_test_isolated.rb b/test/active_model_serializers/railtie_test_isolated.rb deleted file mode 100644 index 1044fc8b..00000000 --- a/test/active_model_serializers/railtie_test_isolated.rb +++ /dev/null @@ -1,68 +0,0 @@ -# Execute this test in isolation -require 'support/isolated_unit' - -class RailtieTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation - - class WithRailsRequiredFirst < RailtieTest - setup do - require 'rails' - require 'active_model_serializers' - make_basic_app do |app| - app.config.action_controller.perform_caching = true - end - end - - test 'mixes ActionController::Serialization into ActionController::Base' do - assert ActionController.const_defined?(:Serialization), - "ActionController::Serialization should be defined, but isn't" - assert ::ActionController::Base.included_modules.include?(::ActionController::Serialization), - "ActionController::Serialization should be included in ActionController::Base, but isn't" - end - - test 'prepares url_helpers for SerializationContext' do - assert ActiveModelSerializers::SerializationContext.url_helpers.respond_to? :url_for - assert_equal Rails.application.routes.default_url_options, - ActiveModelSerializers::SerializationContext.default_url_options - end - - test 'sets the ActiveModelSerializers.logger to Rails.logger' do - refute_nil Rails.logger - refute_nil ActiveModelSerializers.logger - assert_equal Rails.logger, ActiveModelSerializers.logger - end - - test 'it is configured for caching' do - assert_equal ActionController::Base.cache_store, ActiveModelSerializers.config.cache_store - assert_equal true, Rails.configuration.action_controller.perform_caching - assert_equal true, ActiveModelSerializers.config.perform_caching - end - end - - class WithoutRailsRequiredFirst < RailtieTest - setup do - require 'active_model_serializers' - make_basic_app do |app| - app.config.action_controller.perform_caching = true - end - end - - test 'does not mix ActionController::Serialization into ActionController::Base' do - refute ActionController.const_defined?(:Serialization), - 'ActionController::Serialization should not be defined, but is' - end - - test 'has its own logger at ActiveModelSerializers.logger' do - refute_nil Rails.logger - refute_nil ActiveModelSerializers.logger - refute_equal Rails.logger, ActiveModelSerializers.logger - end - - test 'it is not configured for caching' do - refute_nil ActionController::Base.cache_store - assert_nil ActiveModelSerializers.config.cache_store - assert_equal true, Rails.configuration.action_controller.perform_caching - assert_nil ActiveModelSerializers.config.perform_caching - end - end -end diff --git a/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb b/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb deleted file mode 100644 index 30542408..00000000 --- a/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +++ /dev/null @@ -1,161 +0,0 @@ -require 'support/isolated_unit' -require 'minitest/mock' -require 'action_dispatch' -require 'action_controller' - -class JsonApiRendererTest < ActionDispatch::IntegrationTest - include ActiveSupport::Testing::Isolation - - class TestController < ActionController::Base - class << self - attr_accessor :last_request_parameters - end - - def render_with_jsonapi_renderer - permitted_params = params.permit(data: [:id, :type, attributes: [:name]]) - permitted_params = permitted_params.to_h.with_indifferent_access - attributes = - if permitted_params[:data] - permitted_params[:data][:attributes].merge(id: permitted_params[:data][:id]) - else - # Rails returns empty params when no mime type can be negotiated. - # (Until https://github.com/rails/rails/pull/26632 is reviewed.) - permitted_params - end - author = Author.new(attributes) - render jsonapi: author - end - - def parse - self.class.last_request_parameters = request.request_parameters - head :ok - end - end - - def teardown - TestController.last_request_parameters = nil - end - - def assert_parses(expected, actual, headers = {}) - post '/parse', params: actual, headers: headers - assert_response :ok - assert_equal(expected, TestController.last_request_parameters) - end - - def define_author_model_and_serializer - TestController.const_set(:Author, Class.new(ActiveModelSerializers::Model) do - attributes :id, :name - end) - TestController.const_set(:AuthorSerializer, Class.new(ActiveModel::Serializer) do - type 'users' - attribute :id - attribute :name - end) - end - - class WithoutRenderer < JsonApiRendererTest - setup do - require 'rails' - require 'active_record' - require 'support/rails5_shims' - require 'active_model_serializers' - require 'fixtures/poro' - - make_basic_app - - Rails.application.routes.draw do - ActiveSupport::Deprecation.silence do - match ':action', to: TestController, via: [:get, :post] - end - end - define_author_model_and_serializer - end - - def test_jsonapi_parser_not_registered - parsers = if Rails::VERSION::MAJOR >= 5 - ActionDispatch::Request.parameter_parsers - else - ActionDispatch::ParamsParser::DEFAULT_PARSERS - end - assert_nil parsers[Mime[:jsonapi]] - end - - def test_jsonapi_renderer_not_registered - payload = '{"data": {"attributes": {"name": "Johnny Rico"}, "type": "users", "id": "36c9c04e-86b1-4636-a5b0-8616672d1765"}}' - headers = { 'CONTENT_TYPE' => 'application/vnd.api+json' } - post '/render_with_jsonapi_renderer', params: payload, headers: headers - assert_equal '', response.body - assert_equal 500, response.status - assert_equal ActionView::MissingTemplate, request.env['action_dispatch.exception'].class - end - - def test_jsonapi_parser - assert_parses( - {}, - '', - 'CONTENT_TYPE' => 'application/vnd.api+json' - ) - end - end - - class WithRenderer < JsonApiRendererTest - setup do - require 'rails' - require 'active_record' - require 'support/rails5_shims' - require 'active_model_serializers' - require 'fixtures/poro' - require 'active_model_serializers/register_jsonapi_renderer' - - make_basic_app - - Rails.application.routes.draw do - ActiveSupport::Deprecation.silence do - match ':action', to: TestController, via: [:get, :post] - end - end - define_author_model_and_serializer - end - - def test_jsonapi_parser_registered - if Rails::VERSION::MAJOR >= 5 - parsers = ActionDispatch::Request.parameter_parsers - assert_equal Proc, parsers[:jsonapi].class - else - parsers = ActionDispatch::ParamsParser::DEFAULT_PARSERS - assert_equal Proc, parsers[Mime[:jsonapi]].class - end - end - - def test_jsonapi_renderer_registered - expected = { - 'data' => { - 'id' => '36c9c04e-86b1-4636-a5b0-8616672d1765', - 'type' => 'users', - 'attributes' => { 'name' => 'Johnny Rico' } - } - } - - payload = '{"data": {"attributes": {"name": "Johnny Rico"}, "type": "users", "id": "36c9c04e-86b1-4636-a5b0-8616672d1765"}}' - headers = { 'CONTENT_TYPE' => 'application/vnd.api+json' } - post '/render_with_jsonapi_renderer', params: payload, headers: headers - assert_equal expected.to_json, response.body - end - - def test_jsonapi_parser - assert_parses( - { - 'data' => { - 'attributes' => { - 'name' => 'John Doe' - }, - 'type' => 'users', - 'id' => '36c9c04e-86b1-4636-a5b0-8616672d1765' - } - }, - '{"data": {"attributes": {"name": "John Doe"}, "type": "users", "id": "36c9c04e-86b1-4636-a5b0-8616672d1765"}}', - 'CONTENT_TYPE' => 'application/vnd.api+json' - ) - end - end -end diff --git a/test/active_model_serializers/serialization_context_test_isolated.rb b/test/active_model_serializers/serialization_context_test_isolated.rb deleted file mode 100644 index 5720e84a..00000000 --- a/test/active_model_serializers/serialization_context_test_isolated.rb +++ /dev/null @@ -1,71 +0,0 @@ -# Execute this test in isolation -require 'support/isolated_unit' -require 'minitest/mock' - -class SerializationContextTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation - - class WithRails < SerializationContextTest - def create_request - request = ActionDispatch::Request.new({}) - def request.original_url - 'http://example.com/articles?page=2' - end - - def request.query_parameters - { 'page' => 2 } - end - request - end - - setup do - require 'rails' - require 'active_model_serializers' - make_basic_app - @context = ActiveModelSerializers::SerializationContext.new(create_request) - end - - test 'create context with request url and query parameters' do - assert_equal @context.request_url, 'http://example.com/articles' - assert_equal @context.query_parameters, 'page' => 2 - end - - test 'url_helpers is set up for Rails url_helpers' do - assert_equal Module, ActiveModelSerializers::SerializationContext.url_helpers.class - assert ActiveModelSerializers::SerializationContext.url_helpers.respond_to? :url_for - end - - test 'default_url_options returns Rails.application.routes.default_url_options' do - assert_equal Rails.application.routes.default_url_options, - ActiveModelSerializers::SerializationContext.default_url_options - end - end - - class WithoutRails < SerializationContextTest - def create_request - { - request_url: 'http://example.com/articles', - query_parameters: { 'page' => 2 } - } - end - - setup do - require 'active_model_serializers/serialization_context' - @context = ActiveModelSerializers::SerializationContext.new(create_request) - end - - test 'create context with request url and query parameters' do - assert_equal @context.request_url, 'http://example.com/articles' - assert_equal @context.query_parameters, 'page' => 2 - end - - test 'url_helpers is a module when Rails is not present' do - assert_equal Module, ActiveModelSerializers::SerializationContext.url_helpers.class - refute ActiveModelSerializers::SerializationContext.url_helpers.respond_to? :url_for - end - - test 'default_url_options return a Hash' do - assert Hash, ActiveModelSerializers::SerializationContext.default_url_options.class - end - end -end diff --git a/test/active_model_serializers/test/schema_test.rb b/test/active_model_serializers/test/schema_test.rb deleted file mode 100644 index 0fe497d7..00000000 --- a/test/active_model_serializers/test/schema_test.rb +++ /dev/null @@ -1,131 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Test - class SchemaTest < ActionController::TestCase - include ActiveModelSerializers::Test::Schema - - class MyController < ActionController::Base - def index - render json: profile - end - - def show - index - end - - def name_as_a_integer - profile.name = 1 - index - end - - def render_using_json_api - render json: profile, adapter: :json_api - end - - def invalid_json_body - render json: '' - end - - private - - def profile - @profile ||= Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') - end - end - - tests MyController - - def test_that_assert_with_a_valid_schema - get :index - assert_response_schema - end - - def test_that_raises_a_minitest_error_with_a_invalid_schema - message = "#/name: failed schema #/properties/name: For 'properties/name', \"Name 1\" is not an integer. and #/description: failed schema #/properties/description: For 'properties/description', \"Description 1\" is not a boolean." - - get :show - - error = assert_raises Minitest::Assertion do - assert_response_schema - end - assert_equal(message, error.message) - end - - def test_that_raises_error_with_a_custom_message_with_a_invalid_schema - message = 'oh boy the show is broken' - exception_message = "#/name: failed schema #/properties/name: For 'properties/name', \"Name 1\" is not an integer. and #/description: failed schema #/properties/description: For 'properties/description', \"Description 1\" is not a boolean." - expected_message = "#{message}: #{exception_message}" - - get :show - - error = assert_raises Minitest::Assertion do - assert_response_schema(nil, message) - end - assert_equal(expected_message, error.message) - end - - def test_that_assert_with_a_custom_schema - get :show - assert_response_schema('custom/show.json') - end - - def test_that_assert_with_a_hyper_schema - get :show - assert_response_schema('hyper_schema.json') - end - - def test_simple_json_pointers - get :show - assert_response_schema('simple_json_pointers.json') - end - - def test_simple_json_pointers_that_doesnt_match - get :name_as_a_integer - - assert_raises Minitest::Assertion do - assert_response_schema('simple_json_pointers.json') - end - end - - def test_json_api_schema - get :render_using_json_api - assert_response_schema('render_using_json_api.json') - end - - def test_that_assert_with_a_custom_schema_directory - original_schema_path = ActiveModelSerializers.config.schema_path - ActiveModelSerializers.config.schema_path = 'test/support/custom_schemas' - - get :index - assert_response_schema - - ActiveModelSerializers.config.schema_path = original_schema_path - end - - def test_with_a_non_existent_file - message = 'No Schema file at test/support/schemas/non-existent.json' - - get :show - - error = assert_raises ActiveModelSerializers::Test::Schema::MissingSchema do - assert_response_schema('non-existent.json') - end - assert_equal(message, error.message) - end - - def test_that_raises_with_a_invalid_json_body - # message changes from JSON gem 2.0.2 to 2.2.0 - message = /A JSON text must at least contain two octets!|unexpected token at ''/ - - get :invalid_json_body - - error = assert_raises ActiveModelSerializers::Test::Schema::InvalidSchemaError do - assert_response_schema('custom/show.json') - end - - assert_match(message, error.message) - end - end - end -end diff --git a/test/active_model_serializers/test/serializer_test.rb b/test/active_model_serializers/test/serializer_test.rb deleted file mode 100644 index 38dc60ba..00000000 --- a/test/active_model_serializers/test/serializer_test.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Test - class SerializerTest < ActionController::TestCase - include ActiveModelSerializers::Test::Serializer - - class MyController < ActionController::Base - def render_using_serializer - render json: Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') - end - - def render_some_text - render(plain: 'ok') - end - end - - tests MyController - - def test_supports_specifying_serializers_with_a_serializer_class - get :render_using_serializer - assert_serializer ProfileSerializer - end - - def test_supports_specifying_serializers_with_a_regexp - get :render_using_serializer - assert_serializer(/\AProfile.+\Z/) - end - - def test_supports_specifying_serializers_with_a_string - get :render_using_serializer - assert_serializer 'ProfileSerializer' - end - - def test_supports_specifying_serializers_with_a_symbol - get :render_using_serializer - assert_serializer :profile_serializer - end - - def test_supports_specifying_serializers_with_a_nil - get :render_some_text - assert_serializer nil - end - - def test_raises_descriptive_error_message_when_serializer_was_not_rendered - get :render_using_serializer - e = assert_raise ActiveSupport::TestCase::Assertion do - assert_serializer 'PostSerializer' - end - assert_match 'expecting <"PostSerializer"> but rendering with <["ProfileSerializer"]>', e.message - end - - def test_raises_argument_error_when_asserting_with_invalid_object - get :render_using_serializer - e = assert_raise ArgumentError do - assert_serializer Hash - end - assert_match 'assert_serializer only accepts a String, Symbol, Regexp, ActiveModel::Serializer, or nil', e.message - end - end - end -end diff --git a/test/active_record_test.rb b/test/active_record_test.rb deleted file mode 100644 index 5bb941a4..00000000 --- a/test/active_record_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -class ActiveRecordTest < ActiveSupport::TestCase - include ActiveModel::Serializer::Lint::Tests - - def setup - @resource = ARModels::Post.new - end -end diff --git a/test/adapter/attributes_test.rb b/test/adapter/attributes_test.rb deleted file mode 100644 index e60019f5..00000000 --- a/test/adapter/attributes_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class AttributesTest < ActiveSupport::TestCase - class Person < ActiveModelSerializers::Model - attributes :first_name, :last_name - end - - class PersonSerializer < ActiveModel::Serializer - attributes :first_name, :last_name - end - - def setup - ActionController::Base.cache_store.clear - end - - def test_serializable_hash - person = Person.new(first_name: 'Arthur', last_name: 'Dent') - serializer = PersonSerializer.new(person) - adapter = ActiveModelSerializers::Adapter::Attributes.new(serializer) - - assert_equal({ first_name: 'Arthur', last_name: 'Dent' }, - adapter.serializable_hash) - end - - def test_serializable_hash_with_transform_key_casing - person = Person.new(first_name: 'Arthur', last_name: 'Dent') - serializer = PersonSerializer.new(person) - adapter = ActiveModelSerializers::Adapter::Attributes.new( - serializer, - key_transform: :camel_lower - ) - - assert_equal({ firstName: 'Arthur', lastName: 'Dent' }, - adapter.serializable_hash) - end - end - end -end diff --git a/test/adapter/deprecation_test.rb b/test/adapter/deprecation_test.rb deleted file mode 100644 index ea858caa..00000000 --- a/test/adapter/deprecation_test.rb +++ /dev/null @@ -1,100 +0,0 @@ -require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class DeprecationTest < ActiveSupport::TestCase - class PostSerializer < ActiveModel::Serializer - attribute :body - end - setup do - post = Post.new(id: 1, body: 'Hello') - @serializer = PostSerializer.new(post) - end - - def test_null_adapter_serialization_deprecation - expected = {} - assert_deprecated do - assert_equal(expected, Null.new(@serializer).as_json) - end - end - - def test_json_adapter_serialization_deprecation - expected = { post: { body: 'Hello' } } - assert_deprecated do - assert_equal(expected, Json.new(@serializer).as_json) - end - end - - def test_jsonapi_adapter_serialization_deprecation - expected = { - data: { - id: '1', - type: 'posts', - attributes: { - body: 'Hello' - } - } - } - assert_deprecated do - assert_equal(expected, JsonApi.new(@serializer).as_json) - end - end - - def test_attributes_adapter_serialization_deprecation - expected = { body: 'Hello' } - assert_deprecated do - assert_equal(expected, Attributes.new(@serializer).as_json) - end - end - - def test_adapter_create_deprecation - assert_deprecated do - Adapter.create(@serializer) - end - end - - def test_adapter_adapter_map_deprecation - assert_deprecated do - Adapter.adapter_map - end - end - - def test_adapter_adapters_deprecation - assert_deprecated do - Adapter.adapters - end - end - - def test_adapter_adapter_class_deprecation - assert_deprecated do - Adapter.adapter_class(:json_api) - end - end - - def test_adapter_register_deprecation - assert_deprecated do - begin - Adapter.register(:test, Class.new) - ensure - Adapter.adapter_map.delete('test') - end - end - end - - def test_adapter_lookup_deprecation - assert_deprecated do - Adapter.lookup(:json_api) - end - end - - private - - def assert_deprecated - assert_output(nil, /deprecated/) do - yield - end - end - end - end - end -end diff --git a/test/adapter/json/belongs_to_test.rb b/test/adapter/json/belongs_to_test.rb deleted file mode 100644 index 0f096f0b..00000000 --- a/test/adapter/json/belongs_to_test.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class Json - class BelongsToTest < ActiveSupport::TestCase - def setup - @post = Post.new(id: 42, title: 'New Post', body: 'Body') - @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post.comments = [@comment] - @anonymous_post.comments = [] - @comment.post = @post - @comment.author = nil - @anonymous_post.author = nil - @blog = Blog.new(id: 1, name: 'My Blog!!') - @post.blog = @blog - @anonymous_post.blog = nil - - @serializer = CommentSerializer.new(@comment) - @adapter = ActiveModelSerializers::Adapter::Json.new(@serializer) - ActionController::Base.cache_store.clear - end - - def test_includes_post - assert_equal({ id: 42, title: 'New Post', body: 'Body' }, @adapter.serializable_hash[:comment][:post]) - end - - def test_include_nil_author - serializer = PostSerializer.new(@anonymous_post) - adapter = ActiveModelSerializers::Adapter::Json.new(serializer) - - assert_equal({ post: { title: 'Hello!!', body: 'Hello, world!!', id: 43, comments: [], blog: { id: 999, name: 'Custom blog' }, author: nil } }, adapter.serializable_hash) - end - - def test_include_nil_author_with_specified_serializer - serializer = PostPreviewSerializer.new(@anonymous_post) - adapter = ActiveModelSerializers::Adapter::Json.new(serializer) - - assert_equal({ post: { title: 'Hello!!', body: 'Hello, world!!', id: 43, comments: [], author: nil } }, adapter.serializable_hash) - end - end - end - end -end diff --git a/test/adapter/json/collection_test.rb b/test/adapter/json/collection_test.rb deleted file mode 100644 index 8deb4050..00000000 --- a/test/adapter/json/collection_test.rb +++ /dev/null @@ -1,104 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class Json - class Collection < ActiveSupport::TestCase - def setup - @author = Author.new(id: 1, name: 'Steve K.') - @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') - @second_post = Post.new(id: 2, title: 'New Post', body: 'Body') - @first_post.comments = [] - @second_post.comments = [] - @first_post.author = @author - @second_post.author = @author - @blog = Blog.new(id: 1, name: 'My Blog!!') - @first_post.blog = @blog - @second_post.blog = nil - - ActionController::Base.cache_store.clear - end - - def test_with_serializer_option - @blog.special_attribute = 'Special' - @blog.articles = [@first_post, @second_post] - serializer = ActiveModel::Serializer::CollectionSerializer.new([@blog], serializer: CustomBlogSerializer) - adapter = ActiveModelSerializers::Adapter::Json.new(serializer) - - expected = { blogs: [{ - id: 1, - special_attribute: 'Special', - articles: [{ id: 1, title: 'Hello!!', body: 'Hello, world!!' }, { id: 2, title: 'New Post', body: 'Body' }] - }] } - assert_equal expected, adapter.serializable_hash - end - - def test_include_multiple_posts - serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post]) - adapter = ActiveModelSerializers::Adapter::Json.new(serializer) - - expected = { posts: [{ - title: 'Hello!!', - body: 'Hello, world!!', - id: 1, - comments: [], - author: { - id: 1, - name: 'Steve K.' - }, - blog: { - id: 999, - name: 'Custom blog' - } - }, { - title: 'New Post', - body: 'Body', - id: 2, - comments: [], - author: { - id: 1, - name: 'Steve K.' - }, - blog: { - id: 999, - name: 'Custom blog' - } - }] } - assert_equal expected, adapter.serializable_hash - end - - def test_root_is_underscored - virtual_value = VirtualValue.new(id: 1) - serializer = ActiveModel::Serializer::CollectionSerializer.new([virtual_value]) - adapter = ActiveModelSerializers::Adapter::Json.new(serializer) - - assert_equal 1, adapter.serializable_hash[:virtual_values].length - end - - def test_include_option - serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post]) - adapter = ActiveModelSerializers::Adapter::Json.new(serializer, include: '') - actual = adapter.serializable_hash - expected = { posts: [{ id: 1, title: 'Hello!!', body: 'Hello, world!!' }, - { id: 2, title: 'New Post', body: 'Body' }] } - - assert_equal(expected, actual) - end - - def test_fields_with_no_associations_include_option - actual = ActiveModelSerializers::SerializableResource.new( - [@first_post, @second_post], adapter: :json, fields: [:id], include: [] - ).as_json - - expected = { posts: [{ - id: 1 - }, { - id: 2 - }] } - - assert_equal(expected, actual) - end - end - end - end -end diff --git a/test/adapter/json/has_many_test.rb b/test/adapter/json/has_many_test.rb deleted file mode 100644 index feeec93c..00000000 --- a/test/adapter/json/has_many_test.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class Json - class HasManyTestTest < ActiveSupport::TestCase - class ModelWithoutSerializer < ::Model - attributes :id, :name - end - - def setup - ActionController::Base.cache_store.clear - @author = Author.new(id: 1, name: 'Steve K.') - @post = Post.new(id: 42, title: 'New Post', body: 'Body') - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - @post.comments = [@first_comment, @second_comment] - @post.author = @author - @first_comment.post = @post - @second_comment.post = @post - @blog = Blog.new(id: 1, name: 'My Blog!!') - @post.blog = @blog - @tag = ModelWithoutSerializer.new(id: 1, name: '#hash_tag') - @post.tags = [@tag] - end - - def test_has_many - serializer = PostSerializer.new(@post) - adapter = ActiveModelSerializers::Adapter::Json.new(serializer) - assert_equal([ - { id: 1, body: 'ZOMG A COMMENT' }, - { id: 2, body: 'ZOMG ANOTHER COMMENT' } - ], adapter.serializable_hash[:post][:comments]) - end - - def test_has_many_with_no_serializer - post_serializer_class = Class.new(ActiveModel::Serializer) do - attributes :id - has_many :tags - end - serializer = post_serializer_class.new(@post) - adapter = ActiveModelSerializers::Adapter::Json.new(serializer) - assert_equal({ - id: 42, - tags: [ - { 'id' => 1, 'name' => '#hash_tag' } - ] - }.to_json, adapter.serializable_hash[:post].to_json) - end - end - end - end -end diff --git a/test/adapter/json/transform_test.rb b/test/adapter/json/transform_test.rb deleted file mode 100644 index c102b5af..00000000 --- a/test/adapter/json/transform_test.rb +++ /dev/null @@ -1,93 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class Json - class KeyCaseTest < ActiveSupport::TestCase - def mock_request(key_transform = nil) - context = Minitest::Mock.new - context.expect(:request_url, URI) - context.expect(:query_parameters, {}) - options = {} - options[:key_transform] = key_transform if key_transform - options[:serialization_context] = context - serializer = CustomBlogSerializer.new(@blog) - @adapter = ActiveModelSerializers::Adapter::Json.new(serializer, options) - end - - class Post < ::Model; end - class PostSerializer < ActiveModel::Serializer - attributes :id, :title, :body, :publish_at - end - - setup do - ActionController::Base.cache_store.clear - @blog = Blog.new(id: 1, name: 'My Blog!!', special_attribute: 'neat') - end - - def test_transform_default - mock_request - assert_equal({ - blog: { id: 1, special_attribute: 'neat', articles: nil } - }, @adapter.serializable_hash) - end - - def test_transform_global_config - mock_request - result = with_config(key_transform: :camel_lower) do - @adapter.serializable_hash - end - assert_equal({ - blog: { id: 1, specialAttribute: 'neat', articles: nil } - }, result) - end - - def test_transform_serialization_ctx_overrides_global_config - mock_request(:camel) - result = with_config(key_transform: :camel_lower) do - @adapter.serializable_hash - end - assert_equal({ - Blog: { Id: 1, SpecialAttribute: 'neat', Articles: nil } - }, result) - end - - def test_transform_undefined - mock_request(:blam) - result = nil - assert_raises NoMethodError do - result = @adapter.serializable_hash - end - end - - def test_transform_dash - mock_request(:dash) - assert_equal({ - blog: { id: 1, :"special-attribute" => 'neat', articles: nil } - }, @adapter.serializable_hash) - end - - def test_transform_unaltered - mock_request(:unaltered) - assert_equal({ - blog: { id: 1, special_attribute: 'neat', articles: nil } - }, @adapter.serializable_hash) - end - - def test_transform_camel - mock_request(:camel) - assert_equal({ - Blog: { Id: 1, SpecialAttribute: 'neat', Articles: nil } - }, @adapter.serializable_hash) - end - - def test_transform_camel_lower - mock_request(:camel_lower) - assert_equal({ - blog: { id: 1, specialAttribute: 'neat', articles: nil } - }, @adapter.serializable_hash) - end - end - end - end -end diff --git a/test/adapter/json_api/belongs_to_test.rb b/test/adapter/json_api/belongs_to_test.rb deleted file mode 100644 index ded83ab5..00000000 --- a/test/adapter/json_api/belongs_to_test.rb +++ /dev/null @@ -1,155 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonApi - class BelongsToTest < ActiveSupport::TestCase - def setup - @author = Author.new(id: 1, name: 'Steve K.') - @author.bio = nil - @author.roles = [] - @blog = Blog.new(id: 23, name: 'AMS Blog') - @post = Post.new(id: 42, title: 'New Post', body: 'Body') - @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post.comments = [@comment] - @post.blog = @blog - @anonymous_post.comments = [] - @anonymous_post.blog = nil - @comment.post = @post - @comment.author = nil - @post.author = @author - @anonymous_post.author = nil - @blog = Blog.new(id: 1, name: 'My Blog!!') - @blog.writer = @author - @blog.articles = [@post, @anonymous_post] - @author.posts = [] - - @serializer = CommentSerializer.new(@comment) - @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer) - ActionController::Base.cache_store.clear - end - - def test_includes_post_id - expected = { data: { type: 'posts', id: '42' } } - - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:post]) - end - - def test_includes_linked_post - @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:post]) - expected = [{ - id: '42', - type: 'posts', - attributes: { - title: 'New Post', - body: 'Body' - }, - relationships: { - comments: { data: [{ type: 'comments', id: '1' }] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } - }] - assert_equal expected, @adapter.serializable_hash[:included] - end - - def test_limiting_linked_post_fields - @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:post], fields: { post: [:title, :comments, :blog, :author] }) - expected = [{ - id: '42', - type: 'posts', - attributes: { - title: 'New Post' - }, - relationships: { - comments: { data: [{ type: 'comments', id: '1' }] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } - }] - assert_equal expected, @adapter.serializable_hash[:included] - end - - def test_include_nil_author - serializer = PostSerializer.new(@anonymous_post) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) - - assert_equal({ comments: { data: [] }, blog: { data: { type: 'blogs', id: '999' } }, author: { data: nil } }, adapter.serializable_hash[:data][:relationships]) - end - - def test_include_type_for_association_when_different_than_name - serializer = BlogSerializer.new(@blog) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) - relationships = adapter.serializable_hash[:data][:relationships] - expected = { - writer: { - data: { - type: 'authors', - id: '1' - } - }, - articles: { - data: [ - { - type: 'posts', - id: '42' - }, - { - type: 'posts', - id: '43' - } - ] - } - } - assert_equal expected, relationships - end - - def test_include_linked_resources_with_type_name - serializer = BlogSerializer.new(@blog) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, include: [:writer, :articles]) - linked = adapter.serializable_hash[:included] - expected = [ - { - id: '1', - type: 'authors', - attributes: { - name: 'Steve K.' - }, - relationships: { - posts: { data: [] }, - roles: { data: [] }, - bio: { data: nil } - } - }, { - id: '42', - type: 'posts', - attributes: { - title: 'New Post', - body: 'Body' - }, - relationships: { - comments: { data: [{ type: 'comments', id: '1' }] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } - }, { - id: '43', - type: 'posts', - attributes: { - title: 'Hello!!', - body: 'Hello, world!!' - }, - relationships: { - comments: { data: [] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: nil } - } - } - ] - assert_equal expected, linked - end - end - end - end -end diff --git a/test/adapter/json_api/collection_test.rb b/test/adapter/json_api/collection_test.rb deleted file mode 100644 index e60a824e..00000000 --- a/test/adapter/json_api/collection_test.rb +++ /dev/null @@ -1,96 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonApi - class CollectionTest < ActiveSupport::TestCase - def setup - @author = Author.new(id: 1, name: 'Steve K.') - @author.bio = nil - @blog = Blog.new(id: 23, name: 'AMS Blog') - @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') - @second_post = Post.new(id: 2, title: 'New Post', body: 'Body') - @first_post.comments = [] - @second_post.comments = [] - @first_post.blog = @blog - @second_post.blog = nil - @first_post.author = @author - @second_post.author = @author - @author.posts = [@first_post, @second_post] - - @serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post]) - @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer) - ActionController::Base.cache_store.clear - end - - def test_include_multiple_posts - expected = [ - { - id: '1', - type: 'posts', - attributes: { - title: 'Hello!!', - body: 'Hello, world!!' - }, - relationships: { - comments: { data: [] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } - }, - { - id: '2', - type: 'posts', - attributes: { - title: 'New Post', - body: 'Body' - }, - relationships: { - comments: { data: [] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } - } - ] - - assert_equal(expected, @adapter.serializable_hash[:data]) - end - - def test_limiting_fields - actual = ActiveModelSerializers::SerializableResource.new( - [@first_post, @second_post], - adapter: :json_api, - fields: { posts: %w(title comments blog author) } - ).serializable_hash - expected = [ - { - id: '1', - type: 'posts', - attributes: { - title: 'Hello!!' - }, - relationships: { - comments: { data: [] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } - }, - { - id: '2', - type: 'posts', - attributes: { - title: 'New Post' - }, - relationships: { - comments: { data: [] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } - } - ] - assert_equal(expected, actual[:data]) - end - end - end - end -end diff --git a/test/adapter/json_api/errors_test.rb b/test/adapter/json_api/errors_test.rb deleted file mode 100644 index cae7a5a6..00000000 --- a/test/adapter/json_api/errors_test.rb +++ /dev/null @@ -1,76 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonApi < Base - class ErrorsTest < Minitest::Test - include ActiveModel::Serializer::Lint::Tests - - def setup - @resource = ModelWithErrors.new - end - - def test_active_model_with_error - options = { - serializer: ActiveModel::Serializer::ErrorSerializer, - adapter: :json_api - } - - @resource.errors.add(:name, 'cannot be nil') - - serializable_resource = ActiveModelSerializers::SerializableResource.new(@resource, options) - assert_equal serializable_resource.serializer_instance.attributes, {} - assert_equal serializable_resource.serializer_instance.object, @resource - - expected_errors_object = { - errors: [ - { - source: { pointer: '/data/attributes/name' }, - detail: 'cannot be nil' - } - ] - } - assert_equal serializable_resource.as_json, expected_errors_object - end - - def test_active_model_with_multiple_errors - options = { - serializer: ActiveModel::Serializer::ErrorSerializer, - adapter: :json_api - } - - @resource.errors.add(:name, 'cannot be nil') - @resource.errors.add(:name, 'must be longer') - @resource.errors.add(:id, 'must be a uuid') - - serializable_resource = ActiveModelSerializers::SerializableResource.new(@resource, options) - assert_equal serializable_resource.serializer_instance.attributes, {} - assert_equal serializable_resource.serializer_instance.object, @resource - - expected_errors_object = { - errors: [ - { source: { pointer: '/data/attributes/name' }, detail: 'cannot be nil' }, - { source: { pointer: '/data/attributes/name' }, detail: 'must be longer' }, - { source: { pointer: '/data/attributes/id' }, detail: 'must be a uuid' } - ] - } - assert_equal serializable_resource.as_json, expected_errors_object - end - - # see http://jsonapi.org/examples/ - def test_parameter_source_type_error - parameter = 'auther' - error_source = ActiveModelSerializers::Adapter::JsonApi::Error.error_source(:parameter, parameter) - assert_equal({ parameter: parameter }, error_source) - end - - def test_unknown_source_type_error - value = 'auther' - assert_raises(ActiveModelSerializers::Adapter::JsonApi::Error::UnknownSourceTypeError) do - ActiveModelSerializers::Adapter::JsonApi::Error.error_source(:hyper, value) - end - end - end - end - end -end diff --git a/test/adapter/json_api/fields_test.rb b/test/adapter/json_api/fields_test.rb deleted file mode 100644 index 85228318..00000000 --- a/test/adapter/json_api/fields_test.rb +++ /dev/null @@ -1,96 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonApi - 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 :body - associations :author, :post - end - - class PostSerializer < ActiveModel::Serializer - type 'posts' - attributes :title, :body - belongs_to :author - has_many :comments - end - - class AuthorSerializer < ActiveModel::Serializer - type 'authors' - attributes :name, :birthday - end - - class CommentSerializer < ActiveModel::Serializer - type 'comments' - attributes :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 = { posts: [:title] } - hash = serializable(@post, adapter: :json_api, fields: fields).serializable_hash - expected = { - title: 'Title 1' - } - - assert_equal(expected, hash[:data][:attributes]) - end - - def test_fields_relationships - fields = { posts: [:author] } - hash = serializable(@post, adapter: :json_api, fields: fields).serializable_hash - expected = { - author: { - data: { - type: 'authors', - id: '1' - } - } - } - - assert_equal(expected, hash[:data][:relationships]) - end - - def test_fields_included - fields = { posts: [:author], comments: [:body] } - hash = serializable(@post, adapter: :json_api, fields: fields, include: 'comments').serializable_hash - expected = [ - { - type: 'comments', - id: '7', - attributes: { - body: 'cool' - } - }, { - type: 'comments', - id: '12', - attributes: { - body: 'awesome' - } - } - ] - - assert_equal(expected, hash[:included]) - end - end - end - end -end 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 e016de28..00000000 --- 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 deleted file mode 100644 index f598bc9b..00000000 --- a/test/adapter/json_api/has_many_explicit_serializer_test.rb +++ /dev/null @@ -1,96 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonApi - # Test 'has_many :assocs, serializer: AssocXSerializer' - class HasManyExplicitSerializerTest < ActiveSupport::TestCase - def setup - @post = Post.new(title: 'New Post', body: 'Body') - @author = Author.new(name: 'Jane Blogger') - @author.posts = [@post] - @post.author = @author - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - @post.comments = [@first_comment, @second_comment] - @first_comment.post = @post - @first_comment.author = nil - @second_comment.post = @post - @second_comment.author = nil - @blog = Blog.new(id: 23, name: 'AMS Blog') - @post.blog = @blog - - @serializer = PostPreviewSerializer.new(@post) - @adapter = ActiveModelSerializers::Adapter::JsonApi.new( - @serializer, - include: [:comments, :author] - ) - end - - def test_includes_comment_ids - expected = { - data: [ - { type: 'comments', id: '1' }, - { type: 'comments', id: '2' } - ] - } - - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments]) - end - - def test_includes_linked_data - # If CommentPreviewSerializer is applied correctly the body text will not be present in the output - expected = [ - { - id: '1', - type: 'comments', - relationships: { - post: { data: { type: 'posts', id: @post.id.to_s } } - } - }, - { - id: '2', - type: 'comments', - relationships: { - post: { data: { type: 'posts', id: @post.id.to_s } } - } - }, - { - id: @author.id.to_s, - type: 'authors', - relationships: { - posts: { data: [{ type: 'posts', id: @post.id.to_s }] } - } - } - ] - - assert_equal(expected, @adapter.serializable_hash[:included]) - end - - def test_includes_author_id - expected = { - data: { type: 'authors', id: @author.id.to_s } - } - - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:author]) - end - - def test_explicit_serializer_with_null_resource - @post.author = nil - - expected = { data: nil } - - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:author]) - end - - def test_explicit_serializer_with_null_collection - @post.comments = [] - - expected = { data: [] } - - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments]) - end - end - end - end -end diff --git a/test/adapter/json_api/has_many_test.rb b/test/adapter/json_api/has_many_test.rb deleted file mode 100644 index a9fa9ac9..00000000 --- a/test/adapter/json_api/has_many_test.rb +++ /dev/null @@ -1,173 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonApi - class HasManyTest < ActiveSupport::TestCase - class ModelWithoutSerializer < ::Model - attributes :id, :name - end - - def setup - ActionController::Base.cache_store.clear - @author = Author.new(id: 1, name: 'Steve K.') - @author.posts = [] - @author.bio = nil - @post = Post.new(id: 1, title: 'New Post', body: 'Body') - @post_without_comments = Post.new(id: 2, title: 'Second Post', body: 'Second') - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @first_comment.author = nil - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - @second_comment.author = nil - @post.comments = [@first_comment, @second_comment] - @post_without_comments.comments = [] - @first_comment.post = @post - @second_comment.post = @post - @post.author = @author - @post_without_comments.author = nil - @blog = Blog.new(id: 1, name: 'My Blog!!') - @blog.writer = @author - @blog.articles = [@post] - @post.blog = @blog - @post_without_comments.blog = nil - @tag = ModelWithoutSerializer.new(id: 1, name: '#hash_tag') - @post.tags = [@tag] - @serializer = PostSerializer.new(@post) - @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer) - - @virtual_value = VirtualValue.new(id: 1) - end - - def test_includes_comment_ids - expected = { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] } - - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments]) - end - - test 'relationships can be whitelisted via fields' do - @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, fields: { posts: [:author] }) - result = @adapter.serializable_hash - expected = { - data: { - id: '1', - type: 'posts', - relationships: { - author: { - data: { - id: '1', - type: 'authors' - } - } - } - } - } - - assert_equal expected, result - end - - def test_includes_linked_comments - @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:comments]) - expected = [{ - id: '1', - type: 'comments', - attributes: { - body: 'ZOMG A COMMENT' - }, - relationships: { - post: { data: { type: 'posts', id: '1' } }, - author: { data: nil } - } - }, { - id: '2', - type: 'comments', - attributes: { - body: 'ZOMG ANOTHER COMMENT' - }, - relationships: { - post: { data: { type: 'posts', id: '1' } }, - author: { data: nil } - } - }] - assert_equal expected, @adapter.serializable_hash[:included] - end - - def test_limit_fields_of_linked_comments - @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:comments], fields: { comment: [:id, :post, :author] }) - expected = [{ - id: '1', - type: 'comments', - relationships: { - post: { data: { type: 'posts', id: '1' } }, - author: { data: nil } - } - }, { - id: '2', - type: 'comments', - relationships: { - post: { data: { type: 'posts', id: '1' } }, - author: { data: nil } - } - }] - assert_equal expected, @adapter.serializable_hash[:included] - end - - def test_no_include_linked_if_comments_is_empty - serializer = PostSerializer.new(@post_without_comments) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) - - assert_nil adapter.serializable_hash[:linked] - end - - def test_include_type_for_association_when_different_than_name - serializer = BlogSerializer.new(@blog) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) - actual = adapter.serializable_hash[:data][:relationships][:articles] - - expected = { - data: [{ - type: 'posts', - id: '1' - }] - } - assert_equal expected, actual - end - - def test_has_many_with_no_serializer - post_serializer_class = Class.new(ActiveModel::Serializer) do - attributes :id - has_many :tags - end - serializer = post_serializer_class.new(@post) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) - - assert_equal({ - data: { - id: '1', - type: 'posts', - relationships: { - tags: { data: [@tag.as_json] } - } - } - }, adapter.serializable_hash) - end - - def test_has_many_with_virtual_value - serializer = VirtualValueSerializer.new(@virtual_value) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) - - assert_equal({ - data: { - id: '1', - type: 'virtual-values', - relationships: { - maker: { data: { type: 'makers', id: '1' } }, - reviews: { data: [{ type: 'reviews', id: '1' }, - { type: 'reviews', id: '2' }] } - } - } - }, adapter.serializable_hash) - end - end - end - end -end diff --git a/test/adapter/json_api/has_one_test.rb b/test/adapter/json_api/has_one_test.rb deleted file mode 100644 index eb505a0d..00000000 --- a/test/adapter/json_api/has_one_test.rb +++ /dev/null @@ -1,80 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonApi - class HasOneTest < ActiveSupport::TestCase - def setup - @author = Author.new(id: 1, name: 'Steve K.') - @bio = Bio.new(id: 43, content: 'AMS Contributor') - @author.bio = @bio - @bio.author = @author - @post = Post.new(id: 42, title: 'New Post', body: 'Body') - @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post.comments = [@comment] - @anonymous_post.comments = [] - @comment.post = @post - @comment.author = nil - @post.author = @author - @anonymous_post.author = nil - @blog = Blog.new(id: 1, name: 'My Blog!!') - @blog.writer = @author - @blog.articles = [@post, @anonymous_post] - @author.posts = [] - @author.roles = [] - - @virtual_value = VirtualValue.new(id: 1) - - @serializer = AuthorSerializer.new(@author) - @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:bio, :posts]) - end - - def test_includes_bio_id - expected = { data: { type: 'bios', id: '43' } } - - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:bio]) - end - - def test_includes_linked_bio - @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:bio]) - - expected = [ - { - id: '43', - type: 'bios', - attributes: { - content: 'AMS Contributor', - rating: nil - }, - relationships: { - author: { data: { type: 'authors', id: '1' } } - } - } - ] - - assert_equal(expected, @adapter.serializable_hash[:included]) - end - - def test_has_one_with_virtual_value - serializer = VirtualValueSerializer.new(@virtual_value) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) - - expected = { - data: { - id: '1', - type: 'virtual-values', - relationships: { - maker: { data: { type: 'makers', id: '1' } }, - reviews: { data: [{ type: 'reviews', id: '1' }, - { type: 'reviews', id: '2' }] } - } - } - } - - assert_equal(expected, adapter.serializable_hash) - end - end - end - end -end diff --git a/test/adapter/json_api/include_data_if_sideloaded_test.rb b/test/adapter/json_api/include_data_if_sideloaded_test.rb deleted file mode 100644 index c0da9488..00000000 --- a/test/adapter/json_api/include_data_if_sideloaded_test.rb +++ /dev/null @@ -1,183 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - module Adapter - class JsonApi - class IncludeParamTest < ActiveSupport::TestCase - IncludeParamAuthor = Class.new(::Model) do - associations :tags, :posts - end - - class CustomCommentLoader - def all - [{ foo: 'bar' }] - end - end - class Tag < ::Model - attributes :id, :name - end - - class TagSerializer < ActiveModel::Serializer - type 'tags' - attributes :id, :name - end - - class PostWithTagsSerializer < ActiveModel::Serializer - type 'posts' - attributes :id - has_many :tags - end - - class IncludeParamAuthorSerializer < ActiveModel::Serializer - class_attribute :comment_loader - - has_many :tags, serializer: TagSerializer do - link :self, '//example.com/link_author/relationships/tags' - include_data :if_sideloaded - end - - has_many :unlinked_tags, serializer: TagSerializer do - include_data :if_sideloaded - end - - has_many :posts, serializer: PostWithTagsSerializer do - include_data :if_sideloaded - end - has_many :locations do - include_data :if_sideloaded - end - has_many :comments do - include_data :if_sideloaded - IncludeParamAuthorSerializer.comment_loader.all - end - end - - def setup - IncludeParamAuthorSerializer.comment_loader = Class.new(CustomCommentLoader).new - @tag = Tag.new(id: 1337, name: 'mytag') - @author = IncludeParamAuthor.new( - id: 1337, - tags: [@tag] - ) - end - - def test_relationship_not_loaded_when_not_included - expected = { - links: { - self: '//example.com/link_author/relationships/tags' - } - } - - @author.define_singleton_method(:read_attribute_for_serialization) do |attr| - fail 'should not be called' if attr == :tags - super(attr) - end - - assert_relationship(:tags, expected) - end - - def test_relationship_included - expected = { - data: [ - { - id: '1337', - type: 'tags' - } - ], - links: { - self: '//example.com/link_author/relationships/tags' - } - } - - assert_relationship(:tags, expected, include: :tags) - end - - def test_sideloads_included - expected = [ - { - id: '1337', - type: 'tags', - attributes: { name: 'mytag' } - } - ] - hash = result(include: :tags) - assert_equal(expected, hash[:included]) - end - - def test_nested_relationship - expected = { - data: [ - { - id: '1337', - type: 'tags' - } - ], - links: { - self: '//example.com/link_author/relationships/tags' - } - } - - expected_no_data = { - links: { - self: '//example.com/link_author/relationships/tags' - } - } - - assert_relationship(:tags, expected, include: [:tags, { posts: :tags }]) - - @author.define_singleton_method(:read_attribute_for_serialization) do |attr| - fail 'should not be called' if attr == :tags - super(attr) - end - - assert_relationship(:tags, expected_no_data, include: { posts: :tags }) - end - - def test_include_params_with_no_block - @author.define_singleton_method(:read_attribute_for_serialization) do |attr| - fail 'should not be called' if attr == :locations - super(attr) - end - - expected = { meta: {} } - - assert_relationship(:locations, expected) - end - - def test_block_relationship - expected = { - data: [ - { 'foo' => 'bar' } - ] - } - - assert_relationship(:comments, expected, include: [:comments]) - end - - def test_node_not_included_when_no_link - expected = { meta: {} } - assert_relationship(:unlinked_tags, expected, key_transform: :unaltered) - end - - private - - def assert_relationship(relationship_name, expected, opts = {}) - actual = relationship_data(relationship_name, opts) - assert_equal(expected, actual) - end - - def result(opts) - opts = { adapter: :json_api }.merge(opts) - serializable(@author, opts).serializable_hash - end - - def relationship_data(relationship_name, opts = {}) - hash = result(opts) - hash[:data][:relationships][relationship_name] - end - end - end - end - end -end diff --git a/test/adapter/json_api/json_api_test.rb b/test/adapter/json_api/json_api_test.rb deleted file mode 100644 index cb2ce909..00000000 --- a/test/adapter/json_api/json_api_test.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonApiTest < ActiveSupport::TestCase - def setup - ActionController::Base.cache_store.clear - @author = Author.new(id: 1, name: 'Steve K.') - @post = Post.new(id: 1, title: 'New Post', body: 'Body') - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - @post.comments = [@first_comment, @second_comment] - @first_comment.post = @post - @second_comment.post = @post - @post.author = @author - @blog = Blog.new(id: 1, name: 'My Blog!!') - @post.blog = @blog - end - - def test_custom_keys - serializer = PostWithCustomKeysSerializer.new(@post) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) - - assert_equal({ - reviews: { data: [{ type: 'comments', id: '1' }, - { type: 'comments', id: '2' }] }, - writer: { data: { type: 'authors', id: '1' } }, - site: { data: { type: 'blogs', id: '1' } } - }, adapter.serializable_hash[:data][:relationships]) - end - end - end -end diff --git a/test/adapter/json_api/linked_test.rb b/test/adapter/json_api/linked_test.rb deleted file mode 100644 index 0d9c69b6..00000000 --- a/test/adapter/json_api/linked_test.rb +++ /dev/null @@ -1,413 +0,0 @@ -require 'test_helper' - -class NestedPost < ::Model; associations :nested_posts end -class NestedPostSerializer < ActiveModel::Serializer - has_many :nested_posts -end -module ActiveModelSerializers - module Adapter - class JsonApi - class LinkedTest < ActiveSupport::TestCase - def setup - @author1 = Author.new(id: 1, name: 'Steve K.') - @author2 = Author.new(id: 2, name: 'Tenderlove') - @bio1 = Bio.new(id: 1, content: 'AMS Contributor') - @bio2 = Bio.new(id: 2, content: 'Rails Contributor') - @first_post = Post.new(id: 10, title: 'Hello!!', body: 'Hello, world!!') - @second_post = Post.new(id: 20, title: 'New Post', body: 'Body') - @third_post = Post.new(id: 30, title: 'Yet Another Post', body: 'Body') - @blog = Blog.new(name: 'AMS Blog') - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - @first_post.blog = @blog - @second_post.blog = @blog - @third_post.blog = nil - @first_post.comments = [@first_comment, @second_comment] - @second_post.comments = [] - @third_post.comments = [] - @first_post.author = @author1 - @second_post.author = @author2 - @third_post.author = @author1 - @first_comment.post = @first_post - @first_comment.author = nil - @second_comment.post = @first_post - @second_comment.author = nil - @author1.posts = [@first_post, @third_post] - @author1.bio = @bio1 - @author1.roles = [] - @author2.posts = [@second_post] - @author2.bio = @bio2 - @author2.roles = [] - @bio1.author = @author1 - @bio2.author = @author2 - end - - def test_include_multiple_posts_and_linked_array - serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post]) - adapter = ActiveModelSerializers::Adapter::JsonApi.new( - serializer, - include: [:comments, author: [:bio]] - ) - alt_adapter = ActiveModelSerializers::Adapter::JsonApi.new( - serializer, - include: [:comments, author: [:bio]] - ) - - expected = { - data: [ - { - id: '10', - type: 'posts', - attributes: { - title: 'Hello!!', - body: 'Hello, world!!' - }, - relationships: { - comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } - }, - { - id: '20', - type: 'posts', - attributes: { - title: 'New Post', - body: 'Body' - }, - relationships: { - comments: { data: [] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '2' } } - } - } - ], - included: [ - { - id: '1', - type: 'comments', - attributes: { - body: 'ZOMG A COMMENT' - }, - relationships: { - post: { data: { type: 'posts', id: '10' } }, - author: { data: nil } - } - }, { - id: '2', - type: 'comments', - attributes: { - body: 'ZOMG ANOTHER COMMENT' - }, - relationships: { - post: { data: { type: 'posts', id: '10' } }, - author: { data: nil } - } - }, { - id: '1', - type: 'authors', - attributes: { - name: 'Steve K.' - }, - relationships: { - posts: { data: [{ type: 'posts', id: '10' }, { type: 'posts', id: '30' }] }, - roles: { data: [] }, - bio: { data: { type: 'bios', id: '1' } } - } - }, { - id: '1', - type: 'bios', - attributes: { - content: 'AMS Contributor', - rating: nil - }, - relationships: { - author: { data: { type: 'authors', id: '1' } } - } - }, { - id: '2', - type: 'authors', - attributes: { - name: 'Tenderlove' - }, - relationships: { - posts: { data: [{ type: 'posts', id: '20' }] }, - roles: { data: [] }, - bio: { data: { type: 'bios', id: '2' } } - } - }, { - id: '2', - type: 'bios', - attributes: { - rating: nil, - content: 'Rails Contributor' - }, - relationships: { - author: { data: { type: 'authors', id: '2' } } - } - } - ] - } - assert_equal expected, adapter.serializable_hash - assert_equal expected, alt_adapter.serializable_hash - end - - def test_include_multiple_posts_and_linked - serializer = BioSerializer.new @bio1 - adapter = ActiveModelSerializers::Adapter::JsonApi.new( - serializer, - include: [author: [:posts]] - ) - alt_adapter = ActiveModelSerializers::Adapter::JsonApi.new( - serializer, - include: [author: [:posts]] - ) - - expected = [ - { - id: '1', - type: 'authors', - attributes: { - name: 'Steve K.' - }, - relationships: { - posts: { data: [{ type: 'posts', id: '10' }, { type: 'posts', id: '30' }] }, - roles: { data: [] }, - bio: { data: { type: 'bios', id: '1' } } - } - }, { - id: '10', - type: 'posts', - attributes: { - title: 'Hello!!', - body: 'Hello, world!!' - }, - relationships: { - comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } - }, { - id: '30', - type: 'posts', - attributes: { - title: 'Yet Another Post', - body: 'Body' - }, - relationships: { - comments: { data: [] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } - } - ] - - assert_equal expected, adapter.serializable_hash[:included] - assert_equal expected, alt_adapter.serializable_hash[:included] - end - - def test_underscore_model_namespace_for_linked_resource_type - spammy_post = Post.new(id: 123) - spammy_post.related = [Spam::UnrelatedLink.new(id: 456)] - serializer = SpammyPostSerializer.new(spammy_post) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) - relationships = adapter.serializable_hash[:data][:relationships] - expected = { - related: { - data: [{ - type: 'spam-unrelated-links', - id: '456' - }] - } - } - assert_equal expected, relationships - end - - def test_underscore_model_namespace_with_namespace_separator_for_linked_resource_type - spammy_post = Post.new(id: 123) - spammy_post.related = [Spam::UnrelatedLink.new(id: 456)] - serializer = SpammyPostSerializer.new(spammy_post) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) - relationships = with_namespace_separator '--' do - adapter.serializable_hash[:data][:relationships] - end - expected = { - related: { - data: [{ - type: 'spam--unrelated-links', - id: '456' - }] - } - } - assert_equal expected, relationships - end - - def test_multiple_references_to_same_resource - serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_comment, @second_comment]) - adapter = ActiveModelSerializers::Adapter::JsonApi.new( - serializer, - include: [:post] - ) - - expected = [ - { - id: '10', - type: 'posts', - attributes: { - title: 'Hello!!', - body: 'Hello, world!!' - }, - relationships: { - comments: { - data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] - }, - blog: { - data: { type: 'blogs', id: '999' } - }, - author: { - data: { type: 'authors', id: '1' } - } - } - } - ] - - assert_equal expected, adapter.serializable_hash[:included] - end - - def test_nil_link_with_specified_serializer - @first_post.author = nil - serializer = PostPreviewSerializer.new(@first_post) - adapter = ActiveModelSerializers::Adapter::JsonApi.new( - serializer, - include: [:author] - ) - - expected = { - data: { - id: '10', - type: 'posts', - attributes: { - title: 'Hello!!', - body: 'Hello, world!!' - }, - relationships: { - comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] }, - author: { data: nil } - } - } - } - assert_equal expected, adapter.serializable_hash - end - end - - class NoDuplicatesTest < ActiveSupport::TestCase - class Post < ::Model; associations :author end - class Author < ::Model; associations :posts, :roles, :bio end - - class PostSerializer < ActiveModel::Serializer - type 'posts' - belongs_to :author - end - - class AuthorSerializer < ActiveModel::Serializer - type 'authors' - has_many :posts - end - - def setup - @author = Author.new(id: 1, posts: [], roles: [], bio: nil) - @post1 = Post.new(id: 1, author: @author) - @post2 = Post.new(id: 2, author: @author) - @author.posts << @post1 - @author.posts << @post2 - - @nestedpost1 = NestedPost.new(id: 1, nested_posts: []) - @nestedpost2 = NestedPost.new(id: 2, nested_posts: []) - @nestedpost1.nested_posts << @nestedpost1 - @nestedpost1.nested_posts << @nestedpost2 - @nestedpost2.nested_posts << @nestedpost1 - @nestedpost2.nested_posts << @nestedpost2 - end - - def test_no_duplicates - hash = ActiveModelSerializers::SerializableResource.new(@post1, adapter: :json_api, - include: '*.*') - .serializable_hash - expected = [ - { - type: 'authors', id: '1', - relationships: { - posts: { - data: [ - { type: 'posts', id: '1' }, - { type: 'posts', id: '2' } - ] - } - } - }, - { - type: 'posts', id: '2', - relationships: { - author: { - data: { type: 'authors', id: '1' } - } - } - } - ] - assert_equal(expected, hash[:included]) - end - - def test_no_duplicates_collection - hash = ActiveModelSerializers::SerializableResource.new( - [@post1, @post2], - adapter: :json_api, - include: '*.*' - ).serializable_hash - expected = [ - { - type: 'authors', id: '1', - relationships: { - posts: { - data: [ - { type: 'posts', id: '1' }, - { type: 'posts', id: '2' } - ] - } - } - } - ] - assert_equal(expected, hash[:included]) - end - - def test_no_duplicates_global - hash = ActiveModelSerializers::SerializableResource.new( - @nestedpost1, - adapter: :json_api, - include: '*' - ).serializable_hash - expected = [ - type: 'nested-posts', id: '2', - relationships: { - :"nested-posts" => { - data: [ - { type: 'nested-posts', id: '1' }, - { type: 'nested-posts', id: '2' } - ] - } - } - ] - assert_equal(expected, hash[:included]) - end - - def test_no_duplicates_collection_global - hash = ActiveModelSerializers::SerializableResource.new( - [@nestedpost1, @nestedpost2], - adapter: :json_api, - include: '*' - ).serializable_hash - assert_nil(hash[:included]) - end - end - end - end -end diff --git a/test/adapter/json_api/links_test.rb b/test/adapter/json_api/links_test.rb deleted file mode 100644 index ffbfa303..00000000 --- a/test/adapter/json_api/links_test.rb +++ /dev/null @@ -1,95 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonApi - class LinksTest < ActiveSupport::TestCase - class LinkAuthor < ::Model; associations :posts end - class LinkAuthorSerializer < ActiveModel::Serializer - link :self do - href "http://example.com/link_author/#{object.id}" - meta stuff: 'value' - end - link(:author) { link_author_url(object.id) } - link(:link_authors) { url_for(controller: 'link_authors', action: 'index', only_path: false) } - link(:posts) { link_author_posts_url(object.id) } - link :resource, 'http://example.com/resource' - link :yet_another do - "http://example.com/resource/#{object.id}" - end - link(:nil) { nil } - end - - def setup - Rails.application.routes.draw do - resources :link_authors do - resources :posts - end - end - @post = Post.new(id: 1337, comments: [], author: nil) - @author = LinkAuthor.new(id: 1337, posts: [@post]) - end - - def test_toplevel_links - hash = ActiveModelSerializers::SerializableResource.new( - @post, - adapter: :json_api, - links: { - self: { - href: 'http://example.com/posts', - meta: { - stuff: 'value' - } - } - } - ).serializable_hash - expected = { - self: { - href: 'http://example.com/posts', - meta: { - stuff: 'value' - } - } - } - assert_equal(expected, hash[:links]) - end - - def test_nil_toplevel_links - hash = ActiveModelSerializers::SerializableResource.new( - @post, - adapter: :json_api, - links: nil - ).serializable_hash - refute hash.key?(:links), 'No links key to be output' - end - - def test_nil_toplevel_links_json_adapter - hash = ActiveModelSerializers::SerializableResource.new( - @post, - adapter: :json, - links: nil - ).serializable_hash - refute hash.key?(:links), 'No links key to be output' - end - - def test_resource_links - hash = serializable(@author, adapter: :json_api).serializable_hash - expected = { - self: { - href: 'http://example.com/link_author/1337', - meta: { - stuff: 'value' - } - }, - author: 'http://example.com/link_authors/1337', - :"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' - } - assert_equal(expected, hash[:data][:links]) - end - end - end - end -end diff --git a/test/adapter/json_api/pagination_links_test.rb b/test/adapter/json_api/pagination_links_test.rb deleted file mode 100644 index 736ea2fe..00000000 --- a/test/adapter/json_api/pagination_links_test.rb +++ /dev/null @@ -1,193 +0,0 @@ -require 'test_helper' -require 'will_paginate/array' -require 'kaminari' -require 'kaminari/hooks' -::Kaminari::Hooks.init - -module ActiveModelSerializers - module Adapter - class JsonApi - class PaginationLinksTest < ActiveSupport::TestCase - URI = 'http://example.com'.freeze - - def setup - ActionController::Base.cache_store.clear - @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') - ] - end - - def mock_request(query_parameters = {}, original_url = URI) - context = Minitest::Mock.new - context.expect(:request_url, original_url) - context.expect(:query_parameters, query_parameters) - context.expect(:key_transform, nil) - end - - def load_adapter(paginated_collection, mock_request = nil) - render_options = { adapter: :json_api } - render_options[:serialization_context] = mock_request if mock_request - serializable(paginated_collection, render_options) - end - - def using_kaminari(page = 2) - Kaminari.paginate_array(@array).page(page).per(2) - end - - def using_will_paginate(page = 2) - @array.paginate(page: page, per_page: 2) - end - - def data - { - data: [ - { id: '1', type: 'profiles', attributes: { name: 'Name 1', description: 'Description 1' } }, - { id: '2', type: 'profiles', attributes: { name: 'Name 2', description: 'Description 2' } }, - { id: '3', type: 'profiles', attributes: { name: 'Name 3', description: 'Description 3' } }, - { id: '4', type: 'profiles', attributes: { name: 'Name 4', description: 'Description 4' } }, - { id: '5', type: 'profiles', attributes: { name: 'Name 5', description: 'Description 5' } } - ] - } - end - - def links - { - links: { - self: "#{URI}?page%5Bnumber%5D=2&page%5Bsize%5D=2", - first: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=2", - prev: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=2", - next: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=2", - last: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=2" - } - } - end - - 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" - } - } - end - - def expected_response_when_unpaginatable - data - end - - def expected_response_with_pagination_links - {}.tap do |hash| - hash[:data] = data.values.flatten[2..3] - hash.merge! links - end - end - - def expected_response_without_pagination_links - {}.tap do |hash| - hash[:data] = data.values.flatten[2..3] - end - end - - def expected_response_with_pagination_links_and_additional_params - new_links = links[:links].each_with_object({}) { |(key, value), hash| hash[key] = "#{value}&test=test" } - {}.tap do |hash| - hash[:data] = data.values.flatten[2..3] - hash.merge! links: new_links - end - end - - def expected_response_with_last_page_pagination_links - {}.tap do |hash| - hash[:data] = [data.values.flatten.last] - hash.merge! last_page_links - end - end - - def expected_response_with_no_data_pagination_links - {}.tap do |hash| - hash[:data] = [] - hash[:links] = {} - end - end - - def test_pagination_links_using_kaminari - adapter = load_adapter(using_kaminari, mock_request) - - assert_equal expected_response_with_pagination_links, adapter.serializable_hash - end - - def test_pagination_links_using_will_paginate - adapter = load_adapter(using_will_paginate, mock_request) - - assert_equal expected_response_with_pagination_links, adapter.serializable_hash - end - - def test_pagination_links_with_additional_params - adapter = load_adapter(using_will_paginate, mock_request(test: 'test')) - - assert_equal expected_response_with_pagination_links_and_additional_params, - adapter.serializable_hash - end - - def test_pagination_links_when_zero_results_kaminari - @array = [] - - adapter = load_adapter(using_kaminari(1), mock_request) - - assert_equal expected_response_with_no_data_pagination_links, adapter.serializable_hash - end - - def test_pagination_links_when_zero_results_will_paginate - @array = [] - - adapter = load_adapter(using_will_paginate(1), mock_request) - - assert_equal expected_response_with_no_data_pagination_links, adapter.serializable_hash - end - - def test_last_page_pagination_links_using_kaminari - adapter = load_adapter(using_kaminari(3), mock_request) - - assert_equal expected_response_with_last_page_pagination_links, adapter.serializable_hash - end - - def test_last_page_pagination_links_using_will_paginate - adapter = load_adapter(using_will_paginate(3), mock_request) - - assert_equal expected_response_with_last_page_pagination_links, adapter.serializable_hash - end - - def test_not_showing_pagination_links - adapter = load_adapter(@array, mock_request) - - assert_equal expected_response_when_unpaginatable, adapter.serializable_hash - end - - def test_raises_descriptive_error_when_serialization_context_unset - render_options = { adapter: :json_api } - adapter = serializable(using_kaminari, render_options) - exception = assert_raises 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 - - def test_pagination_links_not_present_when_disabled - ActiveModel::Serializer.config.jsonapi_pagination_links_enabled = false - adapter = load_adapter(using_kaminari, mock_request) - - assert_equal expected_response_without_pagination_links, adapter.serializable_hash - ensure - ActiveModel::Serializer.config.jsonapi_pagination_links_enabled = true - end - end - end - end -end diff --git a/test/adapter/json_api/parse_test.rb b/test/adapter/json_api/parse_test.rb deleted file mode 100644 index bee79c8c..00000000 --- a/test/adapter/json_api/parse_test.rb +++ /dev/null @@ -1,137 +0,0 @@ -require 'test_helper' -module ActiveModelSerializers - module Adapter - class JsonApi - module Deserialization - class ParseTest < Minitest::Test - def setup - @hash = { - 'data' => { - 'type' => 'photos', - 'id' => 'zorglub', - 'attributes' => { - 'title' => 'Ember Hamster', - 'src' => 'http://example.com/images/productivity.png' - }, - 'relationships' => { - 'author' => { - 'data' => nil - }, - 'photographer' => { - 'data' => { 'type' => 'people', 'id' => '9' } - }, - 'comments' => { - 'data' => [ - { 'type' => 'comments', 'id' => '1' }, - { 'type' => 'comments', 'id' => '2' } - ] - } - } - } - } - @params = ActionController::Parameters.new(@hash) - @expected = { - id: 'zorglub', - title: 'Ember Hamster', - src: 'http://example.com/images/productivity.png', - author_id: nil, - photographer_id: '9', - comment_ids: %w(1 2) - } - - @illformed_payloads = [nil, - {}, - { - 'data' => nil - }, { - 'data' => { 'attributes' => [] } - }, { - 'data' => { 'relationships' => [] } - }, { - 'data' => { - 'relationships' => { 'rel' => nil } - } - }, { - 'data' => { - 'relationships' => { 'rel' => {} } - } - }] - end - - def test_hash - parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash) - assert_equal(@expected, parsed_hash) - end - - def test_actioncontroller_parameters - assert_equal(false, @params.permitted?) - parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@params) - assert_equal(@expected, parsed_hash) - end - - def test_illformed_payloads_safe - @illformed_payloads.each do |p| - parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse(p) - assert_equal({}, parsed_hash) - end - end - - def test_illformed_payloads_unsafe - @illformed_payloads.each do |p| - assert_raises(InvalidDocument) do - ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(p) - end - end - end - - def test_filter_fields_only - parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, only: [:id, :title, :author]) - expected = { - id: 'zorglub', - title: 'Ember Hamster', - author_id: nil - } - assert_equal(expected, parsed_hash) - end - - def test_filter_fields_except - parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, except: [:id, :title, :author]) - expected = { - src: 'http://example.com/images/productivity.png', - photographer_id: '9', - comment_ids: %w(1 2) - } - assert_equal(expected, parsed_hash) - end - - def test_keys - parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, keys: { author: :user, title: :post_title }) - expected = { - id: 'zorglub', - post_title: 'Ember Hamster', - src: 'http://example.com/images/productivity.png', - user_id: nil, - photographer_id: '9', - comment_ids: %w(1 2) - } - assert_equal(expected, parsed_hash) - end - - def test_polymorphic - parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, polymorphic: [:photographer]) - expected = { - id: 'zorglub', - title: 'Ember Hamster', - src: 'http://example.com/images/productivity.png', - author_id: nil, - photographer_id: '9', - photographer_type: 'people', - comment_ids: %w(1 2) - } - assert_equal(expected, parsed_hash) - end - end - end - end - end -end diff --git a/test/adapter/json_api/relationship_test.rb b/test/adapter/json_api/relationship_test.rb deleted file mode 100644 index cfd5be85..00000000 --- a/test/adapter/json_api/relationship_test.rb +++ /dev/null @@ -1,397 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonApi - class RelationshipTest < ActiveSupport::TestCase - def test_relationship_with_data - expected = { - data: { - id: '1', - type: 'blogs' - } - } - - model_attributes = { blog: Blog.new(id: 1) } - relationship_name = :blog - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_one :blog - end - assert_equal(expected, actual) - end - - def test_relationship_with_nil_model - 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 - has_one :blog - end - assert_equal(expected, actual) - end - - def test_relationship_with_data_array - expected = { - data: [ - { - id: '1', - type: 'posts' - }, - { - id: '2', - type: 'posts' - } - ] - } - - model_attributes = { posts: [Post.new(id: 1), Post.new(id: 2)] } - relationship_name = :posts - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_many :posts - end - assert_equal(expected, actual) - end - - def test_relationship_data_not_included - expected = { meta: {} } - - model_attributes = { blog: :does_not_matter } - relationship_name = :blog - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_one :blog do - include_data false - end - end - assert_equal(expected, actual) - end - - def test_relationship_many_links - expected = { - links: { - self: 'a link', - related: 'another link' - } - } - - model_attributes = { blog: :does_not_matter } - relationship_name = :blog - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_one :blog do - include_data false - link :self, 'a link' - link :related, 'another link' - end - end - assert_equal(expected, actual) - end - - def test_relationship_block_link_with_meta - expected = { - links: { - self: { - href: '1', - meta: { id: 1 } - } - } - } - - model_attributes = { blog: Blog.new(id: 1) } - relationship_name = :blog - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_one :blog do - include_data false - link :self do - href object.blog.id.to_s - meta(id: object.blog.id) - end - end - end - assert_equal(expected, actual) - end - - def test_relationship_simple_meta - expected = { meta: { id: '1' } } - - model_attributes = { blog: Blog.new(id: 1) } - relationship_name = :blog - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_one :blog do - include_data false - meta(id: object.blog.id.to_s) - end - end - assert_equal(expected, actual) - end - - def test_relationship_block_meta - expected = { - meta: { - id: 1 - } - } - - model_attributes = { blog: Blog.new(id: 1) } - relationship_name = :blog - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_one :blog do - include_data false - meta(id: object.blog.id) - end - end - assert_equal(expected, actual) - end - - def test_relationship_simple_link - expected = { - data: { - id: '1337', - type: 'bios' - }, - links: { - self: '//example.com/link_author/relationships/bio' - } - } - - model_attributes = { bio: Bio.new(id: 1337) } - relationship_name = :bio - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_one :bio do - link :self, '//example.com/link_author/relationships/bio' - end - end - assert_equal(expected, actual) - end - - def test_relationship_block_link - expected = { - data: { id: '1337', type: 'profiles' }, - links: { related: '//example.com/profiles/1337' } - } - - model_attributes = { profile: Profile.new(id: 1337) } - relationship_name = :profile - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_one :profile do - id = object.profile.id - link :related do - "//example.com/profiles/#{id}" if id != 123 - end - end - end - assert_equal(expected, actual) - end - - def test_relationship_with_everything - expected = { - data: [{ id: '1337', type: 'likes' }], - links: { - related: { - href: '//example.com/likes/1337', - meta: { ids: '1337' } - } - }, - meta: { liked: true } - } - - model_attributes = { likes: [Like.new(id: 1337)] } - relationship_name = :likes - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_many :likes do - link :related do - ids = object.likes.map(&:id).join(',') - href "//example.com/likes/#{ids}" - meta ids: ids - end - meta liked: object.likes.any? - end - end - assert_equal(expected, actual) - end - - def test_relationship_nil_link - expected = { - data: { id: '123', type: 'profiles' } - } - - model_attributes = { profile: Profile.new(id: 123) } - relationship_name = :profile - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_one :profile do - id = object.profile.id - link :related do - "//example.com/profiles/#{id}" if id != 123 - end - end - end - assert_equal(expected, actual) - end - - def test_relationship_block_link_href - expected = { - data: [{ id: '1337', type: 'locations' }], - links: { - related: { href: '//example.com/locations/1337' } - } - } - - model_attributes = { locations: [Location.new(id: 1337)] } - relationship_name = :locations - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_many :locations do - link :related do - ids = object.locations.map(&:id).join(',') - href "//example.com/locations/#{ids}" - end - end - end - assert_equal(expected, actual) - end - - def test_relationship_block_link_href_and_meta - expected = { - data: [{ id: '1337', type: 'posts' }], - links: { - related: { - href: '//example.com/posts/1337', - meta: { ids: '1337' } - } - } - } - - model_attributes = { posts: [Post.new(id: 1337, comments: [], author: nil)] } - relationship_name = :posts - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_many :posts do - link :related do - ids = object.posts.map(&:id).join(',') - href "//example.com/posts/#{ids}" - meta ids: ids - end - end - end - assert_equal(expected, actual) - end - - def test_relationship_block_link_meta - expected = { - data: [{ id: '1337', type: 'comments' }], - links: { - self: { - meta: { ids: [1] } - } - } - } - - model_attributes = { comments: [Comment.new(id: 1337)] } - relationship_name = :comments - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_many :comments do - link :self do - meta ids: [1] - end - end - end - assert_equal(expected, actual) - end - - def test_relationship_meta - expected = { - data: [{ id: 'from-serializer-method', type: 'roles' }], - meta: { count: 1 } - } - - model_attributes = { roles: [Role.new(id: 'from-record')] } - relationship_name = :roles - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_many :roles do |serializer| - meta count: object.roles.count - serializer.cached_roles - end - def cached_roles - [ - Role.new(id: 'from-serializer-method') - ] - end - end - assert_equal(expected, actual) - end - - def test_relationship_not_including_data - expected = { - links: { self: '//example.com/link_author/relationships/blog' } - } - - model_attributes = { blog: Object } - relationship_name = :blog - model = new_model(model_attributes) - model.define_singleton_method(:read_attribute_for_serialization) do |attr| - fail 'should not be called' if attr == :blog - super(attr) - end - assert_nothing_raised do - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - has_one :blog do - link :self, '//example.com/link_author/relationships/blog' - include_data false - end - end - assert_equal(expected, actual) - end - end - - def test_relationship_including_data_explicit - expected = { - data: { id: '1337', type: 'authors' }, - meta: { name: 'Dan Brown' } - } - - model_attributes = { reviewer: Author.new(id: 1337) } - relationship_name = :reviewer - model = new_model(model_attributes) - actual = build_serializer_and_serialize_relationship(model, relationship_name) do - belongs_to :reviewer do - meta name: 'Dan Brown' - include_data true - end - end - assert_equal(expected, actual) - end - - private - - def build_serializer_and_serialize_relationship(model, relationship_name, &block) - serializer_class = Class.new(ActiveModel::Serializer, &block) - hash = serializable(model, serializer: serializer_class, adapter: :json_api).serializable_hash - hash[:data][:relationships][relationship_name] - end - - def new_model(model_attributes) - Class.new(ActiveModelSerializers::Model) do - attributes(*model_attributes.keys) - - def self.name - 'TestModel' - end - end.new(model_attributes) - end - end - end - end -end 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 62b7d93b..00000000 --- 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 deleted file mode 100644 index fa281f30..00000000 --- a/test/adapter/json_api/resource_meta_test.rb +++ /dev/null @@ -1,100 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - module Adapter - class JsonApi - class ResourceMetaTest < Minitest::Test - class MetaHashPostSerializer < ActiveModel::Serializer - attributes :id - meta stuff: 'value' - end - - class MetaBlockPostSerializer < ActiveModel::Serializer - attributes :id - meta do - { comments_count: object.comments.count } - end - end - - class MetaBlockPostBlankMetaSerializer < ActiveModel::Serializer - attributes :id - meta do - {} - end - end - - class MetaBlockPostEmptyStringSerializer < ActiveModel::Serializer - attributes :id - meta do - '' - end - end - - def setup - @post = Post.new(id: 1337, comments: [], author: nil) - end - - def test_meta_hash_object_resource - hash = ActiveModelSerializers::SerializableResource.new( - @post, - serializer: MetaHashPostSerializer, - adapter: :json_api - ).serializable_hash - expected = { - stuff: 'value' - } - assert_equal(expected, hash[:data][:meta]) - end - - def test_meta_block_object_resource - hash = ActiveModelSerializers::SerializableResource.new( - @post, - serializer: MetaBlockPostSerializer, - adapter: :json_api - ).serializable_hash - expected = { - :"comments-count" => @post.comments.count - } - assert_equal(expected, hash[:data][:meta]) - end - - def test_meta_object_resource_in_array - post2 = Post.new(id: 1339, comments: [Comment.new]) - posts = [@post, post2] - hash = ActiveModelSerializers::SerializableResource.new( - posts, - each_serializer: MetaBlockPostSerializer, - adapter: :json_api - ).serializable_hash - expected = { - data: [ - { id: '1337', type: 'posts', meta: { :"comments-count" => 0 } }, - { id: '1339', type: 'posts', meta: { :"comments-count" => 1 } } - ] - } - assert_equal(expected, hash) - end - - def test_meta_object_blank_omitted - hash = ActiveModelSerializers::SerializableResource.new( - @post, - serializer: MetaBlockPostBlankMetaSerializer, - adapter: :json_api - ).serializable_hash - refute hash[:data].key? :meta - end - - def test_meta_object_empty_string_omitted - hash = ActiveModelSerializers::SerializableResource.new( - @post, - serializer: MetaBlockPostEmptyStringSerializer, - adapter: :json_api - ).serializable_hash - refute hash[:data].key? :meta - end - end - end - end - end -end diff --git a/test/adapter/json_api/toplevel_jsonapi_test.rb b/test/adapter/json_api/toplevel_jsonapi_test.rb deleted file mode 100644 index 7b0357e5..00000000 --- a/test/adapter/json_api/toplevel_jsonapi_test.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonApi - class TopLevelJsonApiTest < ActiveSupport::TestCase - def setup - @author = Author.new(id: 1, name: 'Steve K.') - @author.bio = nil - @author.roles = [] - @blog = Blog.new(id: 23, name: 'AMS Blog') - @post = Post.new(id: 42, title: 'New Post', body: 'Body') - @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post.comments = [@comment] - @post.blog = @blog - @anonymous_post.comments = [] - @anonymous_post.blog = nil - @comment.post = @post - @comment.author = nil - @post.author = @author - @anonymous_post.author = nil - @blog = Blog.new(id: 1, name: 'My Blog!!') - @blog.writer = @author - @blog.articles = [@post, @anonymous_post] - @author.posts = [] - end - - def test_toplevel_jsonapi_defaults_to_false - assert_equal config.fetch(:jsonapi_include_toplevel_object), false - end - - def test_disable_toplevel_jsonapi - with_config(jsonapi_include_toplevel_object: false) do - hash = serialize(@post) - assert_nil(hash[:jsonapi]) - end - end - - def test_enable_toplevel_jsonapi - with_config(jsonapi_include_toplevel_object: true) do - hash = serialize(@post) - refute_nil(hash[:jsonapi]) - end - end - - def test_default_toplevel_jsonapi_version - with_config(jsonapi_include_toplevel_object: true) do - hash = serialize(@post) - assert_equal('1.0', hash[:jsonapi][:version]) - end - end - - def test_toplevel_jsonapi_no_meta - with_config(jsonapi_include_toplevel_object: true) do - hash = serialize(@post) - assert_nil(hash[:jsonapi][:meta]) - end - end - - def test_toplevel_jsonapi_meta - new_config = { - jsonapi_include_toplevel_object: true, - jsonapi_toplevel_meta: { - 'copyright' => 'Copyright 2015 Example Corp.' - } - } - with_config(new_config) do - hash = serialize(@post) - assert_equal(new_config[:jsonapi_toplevel_meta], hash.fetch(:jsonapi).fetch(:meta)) - end - end - - private - - def serialize(resource, options = {}) - serializable(resource, { adapter: :json_api }.merge!(options)).serializable_hash - end - end - end - end -end diff --git a/test/adapter/json_api/transform_test.rb b/test/adapter/json_api/transform_test.rb deleted file mode 100644 index 887ec835..00000000 --- a/test/adapter/json_api/transform_test.rb +++ /dev/null @@ -1,512 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonApi - class KeyCaseTest < ActiveSupport::TestCase - class Post < ::Model - attributes :title, :body, :publish_at - associations :author, :comments - end - class Author < ::Model - attributes :first_name, :last_name - end - class Comment < ::Model - attributes :body - associations :author, :post - end - - class PostSerializer < ActiveModel::Serializer - type 'posts' - attributes :title, :body, :publish_at - belongs_to :author - has_many :comments - - link(:self) { post_url(object.id) } - link(:post_authors) { post_authors_url(object.id) } - link(:subscriber_comments) { post_comments_url(object.id) } - - meta do - { - rating: 5, - favorite_count: 10 - } - end - end - - class AuthorSerializer < ActiveModel::Serializer - type 'authors' - attributes :first_name, :last_name - end - - class CommentSerializer < ActiveModel::Serializer - type 'comments' - attributes :body - belongs_to :author - end - - def mock_request(transform = nil) - context = Minitest::Mock.new - context.expect(:request_url, URI) - context.expect(:query_parameters, {}) - context.expect(:url_helpers, Rails.application.routes.url_helpers) - @options = {} - @options[:key_transform] = transform if transform - @options[:serialization_context] = context - end - - def setup - Rails.application.routes.draw do - resources :posts do - resources :authors - resources :comments - end - end - @publish_at = 1.day.from_now - @author = Author.new(id: 1, first_name: 'Bob', last_name: 'Jones') - @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], - publish_at: @publish_at) - @comment1.post = @post - @comment2.post = @post - end - - def test_success_document_transform_default - mock_request - serializer = PostSerializer.new(@post) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - result = adapter.serializable_hash - assert_equal({ - data: { - id: '1337', - type: 'posts', - attributes: { - title: 'Title 1', - body: 'Body 1', - :"publish-at" => @publish_at - }, - relationships: { - author: { - data: { id: '1', type: 'authors' } - }, - comments: { - data: [ - { id: '7', type: 'comments' }, - { id: '12', type: 'comments' } - ] - } - }, - links: { - self: 'http://example.com/posts/1337', - :"post-authors" => 'http://example.com/posts/1337/authors', - :"subscriber-comments" => 'http://example.com/posts/1337/comments' - }, - meta: { rating: 5, :"favorite-count" => 10 } - } - }, result) - end - - def test_success_document_transform_global_config - mock_request - result = with_config(key_transform: :camel_lower) do - serializer = PostSerializer.new(@post) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - adapter.serializable_hash - end - assert_equal({ - data: { - id: '1337', - type: 'posts', - attributes: { - title: 'Title 1', - body: 'Body 1', - publishAt: @publish_at - }, - relationships: { - author: { - data: { id: '1', type: 'authors' } - }, - comments: { - data: [ - { id: '7', type: 'comments' }, - { id: '12', type: 'comments' } - ] - } - }, - links: { - self: 'http://example.com/posts/1337', - postAuthors: 'http://example.com/posts/1337/authors', - subscriberComments: 'http://example.com/posts/1337/comments' - }, - meta: { rating: 5, favoriteCount: 10 } - } - }, result) - end - - def test_success_doc_transform_serialization_ctx_overrides_global - mock_request(:camel) - result = with_config(key_transform: :camel_lower) do - serializer = PostSerializer.new(@post) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - adapter.serializable_hash - end - assert_equal({ - Data: { - Id: '1337', - Type: 'Posts', - Attributes: { - Title: 'Title 1', - Body: 'Body 1', - PublishAt: @publish_at - }, - Relationships: { - Author: { - Data: { Id: '1', Type: 'Authors' } - }, - Comments: { - Data: [ - { Id: '7', Type: 'Comments' }, - { Id: '12', Type: 'Comments' } - ] - } - }, - Links: { - Self: 'http://example.com/posts/1337', - PostAuthors: 'http://example.com/posts/1337/authors', - SubscriberComments: 'http://example.com/posts/1337/comments' - }, - Meta: { Rating: 5, FavoriteCount: 10 } - } - }, result) - end - - def test_success_document_transform_dash - mock_request(:dash) - serializer = PostSerializer.new(@post) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - result = adapter.serializable_hash - assert_equal({ - data: { - id: '1337', - type: 'posts', - attributes: { - title: 'Title 1', - body: 'Body 1', - :"publish-at" => @publish_at - }, - relationships: { - author: { - data: { id: '1', type: 'authors' } - }, - comments: { - data: [ - { id: '7', type: 'comments' }, - { id: '12', type: 'comments' } - ] - } - }, - links: { - self: 'http://example.com/posts/1337', - :"post-authors" => 'http://example.com/posts/1337/authors', - :"subscriber-comments" => 'http://example.com/posts/1337/comments' - }, - meta: { rating: 5, :"favorite-count" => 10 } - } - }, result) - end - - def test_success_document_transform_unaltered - mock_request(:unaltered) - serializer = PostSerializer.new(@post) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - result = adapter.serializable_hash - assert_equal({ - data: { - id: '1337', - type: 'posts', - attributes: { - title: 'Title 1', - body: 'Body 1', - publish_at: @publish_at - }, - relationships: { - author: { - data: { id: '1', type: 'authors' } - }, - comments: { - data: [ - { id: '7', type: 'comments' }, - { id: '12', type: 'comments' } - ] - } - }, - links: { - self: 'http://example.com/posts/1337', - post_authors: 'http://example.com/posts/1337/authors', - subscriber_comments: 'http://example.com/posts/1337/comments' - }, - meta: { rating: 5, favorite_count: 10 } - } - }, result) - end - - def test_success_document_transform_undefined - mock_request(:zoot) - serializer = PostSerializer.new(@post) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - exception = assert_raises NoMethodError do - adapter.serializable_hash - end - assert_match(/undefined method.*zoot/, exception.message) - end - - def test_success_document_transform_camel - mock_request(:camel) - serializer = PostSerializer.new(@post) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - result = adapter.serializable_hash - assert_equal({ - Data: { - Id: '1337', - Type: 'Posts', - Attributes: { - Title: 'Title 1', - Body: 'Body 1', - PublishAt: @publish_at - }, - Relationships: { - Author: { - Data: { Id: '1', Type: 'Authors' } - }, - Comments: { - Data: [ - { Id: '7', Type: 'Comments' }, - { Id: '12', Type: 'Comments' } - ] - } - }, - Links: { - Self: 'http://example.com/posts/1337', - PostAuthors: 'http://example.com/posts/1337/authors', - SubscriberComments: 'http://example.com/posts/1337/comments' - }, - Meta: { Rating: 5, FavoriteCount: 10 } - } - }, result) - end - - def test_success_document_transform_camel_lower - mock_request(:camel_lower) - serializer = PostSerializer.new(@post) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - result = adapter.serializable_hash - assert_equal({ - data: { - id: '1337', - type: 'posts', - attributes: { - title: 'Title 1', - body: 'Body 1', - publishAt: @publish_at - }, - relationships: { - author: { - data: { id: '1', type: 'authors' } - }, - comments: { - data: [ - { id: '7', type: 'comments' }, - { id: '12', type: 'comments' } - ] - } - }, - links: { - self: 'http://example.com/posts/1337', - postAuthors: 'http://example.com/posts/1337/authors', - subscriberComments: 'http://example.com/posts/1337/comments' - }, - meta: { rating: 5, favoriteCount: 10 } - } - }, result) - end - - def test_error_document_transform_default - mock_request - resource = ModelWithErrors.new - resource.errors.add(:published_at, 'must be in the future') - resource.errors.add(:title, 'must be longer') - serializer = ActiveModel::Serializer::ErrorSerializer.new(resource) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - result = adapter.serializable_hash - expected_errors_object = { - errors: [ - { - source: { pointer: '/data/attributes/published-at' }, - detail: 'must be in the future' - }, - { - source: { pointer: '/data/attributes/title' }, - detail: 'must be longer' - } - ] - } - assert_equal expected_errors_object, result - end - - def test_error_document_transform_global_config - mock_request - result = with_config(key_transform: :camel) do - resource = ModelWithErrors.new - resource.errors.add(:published_at, 'must be in the future') - resource.errors.add(:title, 'must be longer') - serializer = ActiveModel::Serializer::ErrorSerializer.new(resource) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - adapter.serializable_hash - end - expected_errors_object = { - Errors: [ - { - Source: { Pointer: '/data/attributes/PublishedAt' }, - Detail: 'must be in the future' - }, - { - Source: { Pointer: '/data/attributes/Title' }, - Detail: 'must be longer' - } - ] - } - assert_equal expected_errors_object, result - end - - def test_error_document_transform_serialization_ctx_overrides_global - mock_request(:camel) - result = with_config(key_transform: :camel_lower) do - resource = ModelWithErrors.new - resource.errors.add(:published_at, 'must be in the future') - resource.errors.add(:title, 'must be longer') - serializer = ActiveModel::Serializer::ErrorSerializer.new(resource) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - adapter.serializable_hash - end - expected_errors_object = { - Errors: [ - { - Source: { Pointer: '/data/attributes/PublishedAt' }, - Detail: 'must be in the future' - }, - { - Source: { Pointer: '/data/attributes/Title' }, - Detail: 'must be longer' - } - ] - } - assert_equal expected_errors_object, result - end - - def test_error_document_transform_dash - mock_request(:dash) - - resource = ModelWithErrors.new - resource.errors.add(:published_at, 'must be in the future') - resource.errors.add(:title, 'must be longer') - - serializer = ActiveModel::Serializer::ErrorSerializer.new(resource) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - result = adapter.serializable_hash - - expected_errors_object = { - errors: [ - { - source: { pointer: '/data/attributes/published-at' }, - detail: 'must be in the future' - }, - { - source: { pointer: '/data/attributes/title' }, - detail: 'must be longer' - } - ] - } - assert_equal expected_errors_object, result - end - - def test_error_document_transform_unaltered - mock_request(:unaltered) - - resource = ModelWithErrors.new - resource.errors.add(:published_at, 'must be in the future') - resource.errors.add(:title, 'must be longer') - - serializer = ActiveModel::Serializer::ErrorSerializer.new(resource) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - result = adapter.serializable_hash - - expected_errors_object = { - errors: [ - { source: { pointer: '/data/attributes/published_at' }, detail: 'must be in the future' }, - { source: { pointer: '/data/attributes/title' }, detail: 'must be longer' } - ] - } - assert_equal expected_errors_object, result - end - - def test_error_document_transform_undefined - mock_request(:krazy) - - resource = ModelWithErrors.new - resource.errors.add(:published_at, 'must be in the future') - resource.errors.add(:title, 'must be longer') - - serializer = ActiveModel::Serializer::ErrorSerializer.new(resource) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - - exception = assert_raises NoMethodError do - adapter.serializable_hash - end - assert_match(/undefined method.*krazy/, exception.message) - end - - def test_error_document_transform_camel - mock_request(:camel) - - resource = ModelWithErrors.new - resource.errors.add(:published_at, 'must be in the future') - resource.errors.add(:title, 'must be longer') - - serializer = ActiveModel::Serializer::ErrorSerializer.new(resource) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - result = adapter.serializable_hash - - expected_errors_object = { - Errors: [ - { Source: { Pointer: '/data/attributes/PublishedAt' }, Detail: 'must be in the future' }, - { Source: { Pointer: '/data/attributes/Title' }, Detail: 'must be longer' } - ] - } - assert_equal expected_errors_object, result - end - - def test_error_document_transform_camel_lower - mock_request(:camel_lower) - - resource = ModelWithErrors.new - resource.errors.add(:published_at, 'must be in the future') - resource.errors.add(:title, 'must be longer') - - serializer = ActiveModel::Serializer::ErrorSerializer.new(resource) - adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, @options) - result = adapter.serializable_hash - - expected_errors_object = { - errors: [ - { source: { pointer: '/data/attributes/publishedAt' }, detail: 'must be in the future' }, - { source: { pointer: '/data/attributes/title' }, detail: 'must be longer' } - ] - } - assert_equal expected_errors_object, result - end - end - end - end -end diff --git a/test/adapter/json_api/type_test.rb b/test/adapter/json_api/type_test.rb deleted file mode 100644 index 40b84cf2..00000000 --- a/test/adapter/json_api/type_test.rb +++ /dev/null @@ -1,61 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - 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_resource_type :plural do - assert_type(@author, 'authors') - end - end - - def test_config_singular - with_jsonapi_resource_type :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 - - 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 - end - end - end - end - end -end diff --git a/test/adapter/json_test.rb b/test/adapter/json_test.rb deleted file mode 100644 index f7f178f8..00000000 --- a/test/adapter/json_test.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class JsonTest < ActiveSupport::TestCase - def setup - ActionController::Base.cache_store.clear - @author = Author.new(id: 1, name: 'Steve K.') - @post = Post.new(id: 1, title: 'New Post', body: 'Body') - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - @post.comments = [@first_comment, @second_comment] - @first_comment.post = @post - @second_comment.post = @post - @post.author = @author - @blog = Blog.new(id: 1, name: 'My Blog!!') - @post.blog = @blog - - @serializer = PostSerializer.new(@post) - @adapter = ActiveModelSerializers::Adapter::Json.new(@serializer) - end - - def test_has_many - assert_equal([ - { id: 1, body: 'ZOMG A COMMENT' }, - { id: 2, body: 'ZOMG ANOTHER COMMENT' } - ], @adapter.serializable_hash[:post][:comments]) - end - - def test_custom_keys - serializer = PostWithCustomKeysSerializer.new(@post) - adapter = ActiveModelSerializers::Adapter::Json.new(serializer) - - assert_equal({ - id: 1, - reviews: [ - { id: 1, body: 'ZOMG A COMMENT' }, - { id: 2, body: 'ZOMG ANOTHER COMMENT' } - ], - writer: { id: 1, name: 'Steve K.' }, - site: { id: 1, name: 'My Blog!!' } - }, adapter.serializable_hash[:post]) - end - end - end -end diff --git a/test/adapter/null_test.rb b/test/adapter/null_test.rb deleted file mode 100644 index 4e701db1..00000000 --- a/test/adapter/null_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - module Adapter - class NullTest < ActiveSupport::TestCase - def setup - profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') - serializer = ProfileSerializer.new(profile) - - @adapter = Null.new(serializer) - end - - def test_serializable_hash - assert_equal({}, @adapter.serializable_hash) - end - - def test_it_returns_empty_json - assert_equal('{}', @adapter.to_json) - end - end - end -end diff --git a/test/adapter/polymorphic_test.rb b/test/adapter/polymorphic_test.rb deleted file mode 100644 index 87d5ff51..00000000 --- a/test/adapter/polymorphic_test.rb +++ /dev/null @@ -1,171 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - module Adapter - class PolymorphicTest < ActiveSupport::TestCase - setup do - @employee = Employee.new(id: 42, name: 'Zoop Zoopler', email: 'zoop@example.com') - @picture = @employee.pictures.new(id: 1, title: 'headshot-1.jpg') - @picture.imageable = @employee - end - - def serialization(resource, adapter = :attributes) - serializable(resource, adapter: adapter, serializer: PolymorphicBelongsToSerializer).as_json - end - - def tag_serialization(adapter = :attributes) - tag = PolyTag.new(id: 1, phrase: 'foo') - tag.object_tags << ObjectTag.new(id: 1, poly_tag_id: 1, taggable: @employee) - tag.object_tags << ObjectTag.new(id: 5, poly_tag_id: 1, taggable: @picture) - serializable(tag, adapter: adapter, serializer: PolymorphicTagSerializer, include: '*.*').as_json - end - - def test_attributes_serialization - expected = - { - id: 1, - title: 'headshot-1.jpg', - imageable: { - type: 'employee', - employee: { - id: 42, - name: 'Zoop Zoopler' - } - } - } - - assert_equal(expected, serialization(@picture)) - end - - def test_attributes_serialization_without_polymorphic_association - expected = - { - id: 2, - title: 'headshot-2.jpg', - imageable: nil - } - - simple_picture = Picture.new(id: 2, title: 'headshot-2.jpg') - assert_equal(expected, serialization(simple_picture)) - end - - def test_attributes_serialization_with_polymorphic_has_many - expected = - { - id: 1, - phrase: 'foo', - object_tags: [ - { - id: 1, - taggable: { - type: 'employee', - employee: { - id: 42 - } - } - }, - { - id: 5, - taggable: { - type: 'picture', - picture: { - id: 1 - } - } - } - ] - } - assert_equal(expected, tag_serialization) - end - - def test_json_serialization - expected = - { - picture: { - id: 1, - title: 'headshot-1.jpg', - imageable: { - type: 'employee', - employee: { - id: 42, - name: 'Zoop Zoopler' - } - } - } - } - - assert_equal(expected, serialization(@picture, :json)) - end - - def test_json_serialization_without_polymorphic_association - expected = - { - picture: { - id: 2, - title: 'headshot-2.jpg', - imageable: nil - } - } - - simple_picture = Picture.new(id: 2, title: 'headshot-2.jpg') - assert_equal(expected, serialization(simple_picture, :json)) - end - - def test_json_serialization_with_polymorphic_has_many - expected = - { - poly_tag: { - id: 1, - phrase: 'foo', - object_tags: [ - { - id: 1, - taggable: { - type: 'employee', - employee: { - id: 42 - } - } - }, - { - id: 5, - taggable: { - type: 'picture', - picture: { - id: 1 - } - } - } - ] - } - } - assert_equal(expected, tag_serialization(:json)) - end - - def test_json_api_serialization - expected = - { - data: { - id: '1', - type: 'pictures', - attributes: { - title: 'headshot-1.jpg' - }, - relationships: { - imageable: { - data: { - id: '42', - type: 'employees' - } - } - } - } - } - - assert_equal(expected, serialization(@picture, :json_api)) - end - end - end - end -end diff --git a/test/adapter_test.rb b/test/adapter_test.rb deleted file mode 100644 index c1b00d72..00000000 --- a/test/adapter_test.rb +++ /dev/null @@ -1,67 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - class AdapterTest < ActiveSupport::TestCase - def setup - profile = Profile.new - @serializer = ProfileSerializer.new(profile) - @adapter = ActiveModelSerializers::Adapter::Base.new(@serializer) - end - - def test_serializable_hash_is_abstract_method - assert_raises(NotImplementedError) do - @adapter.serializable_hash(only: [:name]) - end - end - - def test_serialization_options_ensures_option_is_a_hash - adapter = Class.new(ActiveModelSerializers::Adapter::Base) do - def serializable_hash(options = nil) - serialization_options(options) - end - end.new(@serializer) - assert_equal({}, adapter.serializable_hash(nil)) - assert_equal({}, adapter.serializable_hash({})) - ensure - ActiveModelSerializers::Adapter.adapter_map.delete_if { |k, _| k =~ /class/ } - end - - def test_serialization_options_ensures_option_is_one_of_valid_options - adapter = Class.new(ActiveModelSerializers::Adapter::Base) do - def serializable_hash(options = nil) - serialization_options(options) - end - end.new(@serializer) - filtered_options = { now: :see_me, then: :not } - valid_options = ActiveModel::Serializer::SERIALIZABLE_HASH_VALID_KEYS.each_with_object({}) do |option, result| - result[option] = option - end - assert_equal(valid_options, adapter.serializable_hash(filtered_options.merge(valid_options))) - ensure - ActiveModelSerializers::Adapter.adapter_map.delete_if { |k, _| k =~ /class/ } - end - - def test_serializer - assert_equal @serializer, @adapter.serializer - end - - def test_create_adapter - adapter = ActiveModelSerializers::Adapter.create(@serializer) - assert_equal ActiveModelSerializers::Adapter::Attributes, adapter.class - end - - def test_create_adapter_with_override - adapter = ActiveModelSerializers::Adapter.create(@serializer, adapter: :json_api) - assert_equal ActiveModelSerializers::Adapter::JsonApi, adapter.class - end - - def test_inflected_adapter_class_for_known_adapter - ActiveSupport::Inflector.inflections(:en) { |inflect| inflect.acronym 'API' } - klass = ActiveModelSerializers::Adapter.adapter_class(:json_api) - - ActiveSupport::Inflector.inflections.acronyms.clear - - assert_equal ActiveModelSerializers::Adapter::JsonApi, klass - end - end -end diff --git a/test/array_serializer_test.rb b/test/array_serializer_test.rb deleted file mode 100644 index 2ad55324..00000000 --- a/test/array_serializer_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'test_helper' -require_relative 'collection_serializer_test' - -module ActiveModel - class Serializer - class ArraySerializerTest < CollectionSerializerTest - extend Minitest::Assertions - def self.run_one_method(*) - _, stderr = capture_io do - super - end - if stderr !~ /NOTE: ActiveModel::Serializer::ArraySerializer.new is deprecated/ - fail Minitest::Assertion, stderr - end - end - - def collection_serializer - ArraySerializer - end - end - end -end diff --git a/test/benchmark/app.rb b/test/benchmark/app.rb deleted file mode 100644 index c39e9b4e..00000000 --- a/test/benchmark/app.rb +++ /dev/null @@ -1,65 +0,0 @@ -# https://github.com/rails-api/active_model_serializers/pull/872 -# approx ref 792fb8a9053f8db3c562dae4f40907a582dd1720 to test against -require 'bundler/setup' - -require 'rails' -require 'active_model' -require 'active_support' -require 'active_support/json' -require 'action_controller' -require 'action_controller/test_case' -require 'action_controller/railtie' -abort "Rails application already defined: #{Rails.application.class}" if Rails.application - -class NullLogger < Logger - def initialize(*_args) - end - - def add(*_args, &_block) - end -end -class BenchmarkLogger < ActiveSupport::Logger - def initialize - @file = StringIO.new - super(@file) - end - - def messages - @file.rewind - @file.read - end -end -# ref: https://gist.github.com/bf4/8744473 -class BenchmarkApp < Rails::Application - # Set up production configuration - config.eager_load = true - config.cache_classes = true - # CONFIG: CACHE_ON={on,off} - config.action_controller.perform_caching = ENV['CACHE_ON'] != 'off' - config.action_controller.cache_store = ActiveSupport::Cache.lookup_store(:memory_store) - - config.active_support.test_order = :random - config.secret_token = 'S' * 30 - config.secret_key_base = 'abc123' - config.consider_all_requests_local = false - - # otherwise deadlock occurred - config.middleware.delete 'Rack::Lock' - - # to disable log files - config.logger = NullLogger.new - config.active_support.deprecation = :log - config.log_level = :info -end - -require 'active_model_serializers' - -# Initialize app before any serializers are defined, for running across revisions. -# ref: https://github.com/rails-api/active_model_serializers/pull/1478 -Rails.application.initialize! -# HACK: Serializer::cache depends on the ActionController-dependent configs being set. -ActiveSupport.on_load(:action_controller) do - require_relative 'fixtures' -end - -require_relative 'controllers' diff --git a/test/benchmark/benchmarking_support.rb b/test/benchmark/benchmarking_support.rb deleted file mode 100644 index dd27f6c5..00000000 --- a/test/benchmark/benchmarking_support.rb +++ /dev/null @@ -1,67 +0,0 @@ -require 'benchmark/ips' -require 'json' - -# Add benchmarking runner from ruby-bench-suite -# https://github.com/ruby-bench/ruby-bench-suite/blob/master/rails/benchmarks/support/benchmark_rails.rb -module Benchmark - module ActiveModelSerializers - module TestMethods - def request(method, path) - response = Rack::MockRequest.new(BenchmarkApp).send(method, path) - if response.status.in?([404, 500]) - fail "omg, #{method}, #{path}, '#{response.status}', '#{response.body}'" - end - response - end - end - - # extend Benchmark with an `ams` method - def ams(label = nil, time:, disable_gc: true, warmup: 3, &block) - fail ArgumentError.new, 'block should be passed' unless block_given? - - if disable_gc - GC.disable - else - GC.enable - end - - report = Benchmark.ips(time, warmup, true) do |x| - x.report(label) { yield } - end - - entry = report.entries.first - - output = { - label: label, - version: ::ActiveModel::Serializer::VERSION.to_s, - rails_version: ::Rails.version.to_s, - iterations_per_second: entry.ips, - iterations_per_second_standard_deviation: entry.error_percentage, - total_allocated_objects_per_iteration: count_total_allocated_objects(&block) - }.to_json - - puts output - output - end - - def count_total_allocated_objects - if block_given? - key = - if RUBY_VERSION < '2.2' - :total_allocated_object - else - :total_allocated_objects - end - - before = GC.stat[key] - yield - after = GC.stat[key] - after - before - else - -1 - end - end - end - - extend Benchmark::ActiveModelSerializers -end diff --git a/test/benchmark/bm_active_record.rb b/test/benchmark/bm_active_record.rb deleted file mode 100644 index 0837e266..00000000 --- a/test/benchmark/bm_active_record.rb +++ /dev/null @@ -1,81 +0,0 @@ -require_relative './benchmarking_support' -require_relative './app' - -time = 10 -disable_gc = true - -# This is to disable any key transform effects that may impact performance -ActiveModelSerializers.config.key_transform = :unaltered - -########################################### -# Setup active record models -########################################## -require 'active_record' -require 'sqlite3' - -# For debugging SQL output -# ActiveRecord::Base.logger = Logger.new(STDERR) - -# Change the following to reflect your database settings -ActiveRecord::Base.establish_connection( - adapter: 'sqlite3', - database: ':memory:' -) - -# Don't show migration output when constructing fake db -ActiveRecord::Migration.verbose = false - -ActiveRecord::Schema.define do - create_table :authors, force: true do |t| - t.string :name - end - - create_table :posts, force: true do |t| - t.text :body - t.string :title - t.references :author - end - - create_table :profiles, force: true do |t| - t.text :project_url - t.text :bio - t.date :birthday - t.references :author - end -end - -class Author < ActiveRecord::Base - has_one :profile - has_many :posts -end - -class Post < ActiveRecord::Base - belongs_to :author -end - -class Profile < ActiveRecord::Base - belongs_to :author -end - -# Build out the data to serialize -author = Author.create(name: 'Preston Sego') -Profile.create(project_url: 'https://github.com/NullVoxPopuli', author: author) -50.times do - Post.create( - body: 'something about how password restrictions are evil, and less secure, and with the math to prove it.', - title: 'Your bank is does not know how to do security', - author: author - ) -end - -Benchmark.ams('AR: attributes', time: time, disable_gc: disable_gc) do - ActiveModelSerializers::SerializableResource.new(author, adapter: :attributes, include: 'profile,posts').serializable_hash -end - -Benchmark.ams('AR: json', time: time, disable_gc: disable_gc) do - ActiveModelSerializers::SerializableResource.new(author, adapter: :json, include: 'profile,posts').serializable_hash -end - -Benchmark.ams('AR: JSON API', time: time, disable_gc: disable_gc) do - ActiveModelSerializers::SerializableResource.new(author, adapter: :json_api, include: 'profile,posts').serializable_hash -end diff --git a/test/benchmark/bm_adapter.rb b/test/benchmark/bm_adapter.rb deleted file mode 100644 index c8bae66a..00000000 --- a/test/benchmark/bm_adapter.rb +++ /dev/null @@ -1,38 +0,0 @@ -require_relative './benchmarking_support' -require_relative './app' - -time = 10 -disable_gc = true -ActiveModelSerializers.config.key_transform = :unaltered -has_many_relationships = (0..60).map do |i| - HasManyRelationship.new(id: i, body: 'ZOMG A HAS MANY RELATIONSHIP') -end -has_one_relationship = HasOneRelationship.new( - id: 42, - first_name: 'Joao', - last_name: 'Moura' -) -primary_resource = PrimaryResource.new( - id: 1337, - title: 'New PrimaryResource', - virtual_attribute: nil, - body: 'Body', - has_many_relationships: has_many_relationships, - has_one_relationship: has_one_relationship -) -serializer = PrimaryResourceSerializer.new(primary_resource) - -Benchmark.ams('attributes', time: time, disable_gc: disable_gc) do - attributes = ActiveModelSerializers::Adapter::Attributes.new(serializer) - attributes.as_json -end - -Benchmark.ams('json_api', time: time, disable_gc: disable_gc) do - json_api = ActiveModelSerializers::Adapter::JsonApi.new(serializer) - json_api.as_json -end - -Benchmark.ams('json', time: time, disable_gc: disable_gc) do - json = ActiveModelSerializers::Adapter::Json.new(serializer) - json.as_json -end diff --git a/test/benchmark/bm_caching.rb b/test/benchmark/bm_caching.rb deleted file mode 100644 index ae3ad798..00000000 --- a/test/benchmark/bm_caching.rb +++ /dev/null @@ -1,119 +0,0 @@ -require_relative './benchmarking_support' -require_relative './app' - -# https://github.com/ruby-bench/ruby-bench-suite/blob/8ad567f7e43a044ae48c36833218423bb1e2bd9d/rails/benchmarks/actionpack_router.rb -class ApiAssertion - include Benchmark::ActiveModelSerializers::TestMethods - class BadRevisionError < StandardError; end - - def valid? - caching = get_caching - caching[:body].delete('meta') - non_caching = get_non_caching - non_caching[:body].delete('meta') - assert_responses(caching, non_caching) - rescue BadRevisionError => e - msg = { error: e.message } - STDERR.puts msg - STDOUT.puts msg - exit 1 - end - - def get_status(on_off = 'on'.freeze) - get("/status/#{on_off}") - end - - def clear - get('/clear') - end - - def get_caching(on_off = 'on'.freeze) - get("/caching/#{on_off}") - end - - def get_fragment_caching(on_off = 'on'.freeze) - get("/fragment_caching/#{on_off}") - end - - def get_non_caching(on_off = 'on'.freeze) - get("/non_caching/#{on_off}") - end - - def debug(msg = '') - if block_given? && ENV['DEBUG'] =~ /\Atrue|on|0\z/i - STDERR.puts yield - else - STDERR.puts msg - end - end - - private - - def assert_responses(caching, non_caching) - assert_equal(caching[:code], 200, "Caching response failed: #{caching}") - assert_equal(caching[:body], expected, "Caching response format failed: \n+ #{caching[:body]}\n- #{expected}") - assert_equal(caching[:content_type], 'application/json; charset=utf-8', "Caching response content type failed: \n+ #{caching[:content_type]}\n- application/json") - assert_equal(non_caching[:code], 200, "Non caching response failed: #{non_caching}") - assert_equal(non_caching[:body], expected, "Non Caching response format failed: \n+ #{non_caching[:body]}\n- #{expected}") - assert_equal(non_caching[:content_type], 'application/json; charset=utf-8', "Non caching response content type failed: \n+ #{non_caching[:content_type]}\n- application/json") - end - - def get(url) - response = request(:get, url) - { code: response.status, body: JSON.load(response.body), content_type: response.content_type } - end - - def expected - @expected ||= - { - 'primary_resource' => { - 'id' => 1337, - 'title' => 'New PrimaryResource', - 'body' => 'Body', - 'virtual_attribute' => { - 'id' => 999, - 'name' => 'Free-Range Virtual Attribute' - }, - 'has_one_relationship' => { - 'id' => 42, - 'first_name' => 'Joao', - 'last_name' => 'Moura' - }, - 'has_many_relationships' => [ - { - 'id' => 1, - 'body' => 'ZOMG A HAS MANY RELATIONSHIP' - } - ] - } - } - end - - def assert_equal(expected, actual, message) - return true if expected == actual - if ENV['FAIL_ASSERTION'] =~ /\Atrue|on|0\z/i # rubocop:disable Style/GuardClause - fail BadRevisionError, message - else - STDERR.puts message unless ENV['SUMMARIZE'] - end - end -end -assertion = ApiAssertion.new -assertion.valid? -assertion.debug { assertion.get_status } - -time = 10 -{ - 'caching on: caching serializers: gc off' => { disable_gc: true, send: [:get_caching, 'on'] }, - 'caching on: fragment caching serializers: gc off' => { disable_gc: true, send: [:get_fragment_caching, 'on'] }, - 'caching on: non-caching serializers: gc off' => { disable_gc: true, send: [:get_non_caching, 'on'] }, - 'caching off: caching serializers: gc off' => { disable_gc: true, send: [:get_caching, 'off'] }, - 'caching off: fragment caching serializers: gc off' => { disable_gc: true, send: [:get_fragment_caching, 'off'] }, - 'caching off: non-caching serializers: gc off' => { disable_gc: true, send: [:get_non_caching, 'off'] } -}.each do |label, options| - assertion.clear - Benchmark.ams(label, time: time, disable_gc: options[:disable_gc]) do - assertion.send(*options[:send]) - end - assertion.debug { assertion.get_status(options[:send][-1]) } -end diff --git a/test/benchmark/bm_lookup_chain.rb b/test/benchmark/bm_lookup_chain.rb deleted file mode 100644 index 3b32727f..00000000 --- a/test/benchmark/bm_lookup_chain.rb +++ /dev/null @@ -1,83 +0,0 @@ -require_relative './benchmarking_support' -require_relative './app' - -time = 10 -disable_gc = true -ActiveModelSerializers.config.key_transform = :unaltered - -module AmsBench - module Api - module V1 - class PrimaryResourceSerializer < ActiveModel::Serializer - attributes :title, :body - - has_many :has_many_relationships - end - - class HasManyRelationshipSerializer < ActiveModel::Serializer - attribute :body - end - end - end - class PrimaryResourceSerializer < ActiveModel::Serializer - attributes :title, :body - - has_many :has_many_relationships - - class HasManyRelationshipSerializer < ActiveModel::Serializer - attribute :body - end - end -end - -resource = PrimaryResource.new( - id: 1, - title: 'title', - body: 'body', - has_many_relationships: [ - HasManyRelationship.new(id: 1, body: 'body1'), - HasManyRelationship.new(id: 2, body: 'body1') - ] -) - -serialization = lambda do - ActiveModelSerializers::SerializableResource.new(resource, serializer: AmsBench::PrimaryResourceSerializer).as_json - ActiveModelSerializers::SerializableResource.new(resource, namespace: AmsBench::Api::V1).as_json - ActiveModelSerializers::SerializableResource.new(resource).as_json -end - -def clear_cache - AmsBench::PrimaryResourceSerializer.serializers_cache.clear - AmsBench::Api::V1::PrimaryResourceSerializer.serializers_cache.clear - ActiveModel::Serializer.serializers_cache.clear -end - -configurable = lambda do - clear_cache - Benchmark.ams('Configurable Lookup Chain', time: time, disable_gc: disable_gc, &serialization) -end - -old = lambda do - clear_cache - module ActiveModel - class Serializer - def self.serializer_lookup_chain_for(klass, namespace = nil) - chain = [] - - resource_class_name = klass.name.demodulize - resource_namespace = klass.name.deconstantize - serializer_class_name = "#{resource_class_name}Serializer" - - chain.push("#{namespace}::#{serializer_class_name}") if namespace - chain.push("#{name}::#{serializer_class_name}") if self != ActiveModel::Serializer - chain.push("#{resource_namespace}::#{serializer_class_name}") - chain - end - end - end - - Benchmark.ams('Old Lookup Chain (v0.10)', time: time, disable_gc: disable_gc, &serialization) -end - -configurable.call -old.call diff --git a/test/benchmark/bm_transform.rb b/test/benchmark/bm_transform.rb deleted file mode 100644 index 97c655c0..00000000 --- a/test/benchmark/bm_transform.rb +++ /dev/null @@ -1,45 +0,0 @@ -require_relative './benchmarking_support' -require_relative './app' - -time = 10 -disable_gc = true -ActiveModelSerializers.config.key_transform = :unaltered -has_many_relationships = (0..50).map do |i| - HasManyRelationship.new(id: i, body: 'ZOMG A HAS MANY RELATIONSHIP') -end -has_one_relationship = HasOneRelationship.new( - id: 42, - first_name: 'Joao', - last_name: 'Moura' -) -primary_resource = PrimaryResource.new( - id: 1337, - title: 'New PrimaryResource', - virtual_attribute: nil, - body: 'Body', - has_many_relationships: has_many_relationships, - has_one_relationship: has_one_relationship -) -serializer = PrimaryResourceSerializer.new(primary_resource) -adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) -serialization = adapter.as_json - -Benchmark.ams('camel', time: time, disable_gc: disable_gc) do - CaseTransform.camel(serialization) -end - -Benchmark.ams('camel_lower', time: time, disable_gc: disable_gc) do - CaseTransform.camel_lower(serialization) -end - -Benchmark.ams('dash', time: time, disable_gc: disable_gc) do - CaseTransform.dash(serialization) -end - -Benchmark.ams('unaltered', time: time, disable_gc: disable_gc) do - CaseTransform.unaltered(serialization) -end - -Benchmark.ams('underscore', time: time, disable_gc: disable_gc) do - CaseTransform.underscore(serialization) -end diff --git a/test/benchmark/config.ru b/test/benchmark/config.ru deleted file mode 100644 index 908eb28c..00000000 --- a/test/benchmark/config.ru +++ /dev/null @@ -1,3 +0,0 @@ -require File.expand_path(['..', 'app'].join(File::SEPARATOR), __FILE__) - -run Rails.application diff --git a/test/benchmark/controllers.rb b/test/benchmark/controllers.rb deleted file mode 100644 index 81108445..00000000 --- a/test/benchmark/controllers.rb +++ /dev/null @@ -1,83 +0,0 @@ -class PrimaryResourceController < ActionController::Base - PRIMARY_RESOURCE = - begin - if ENV['BENCH_STRESS'] - has_many_relationships = (0..50).map do |i| - HasManyRelationship.new(id: i, body: 'ZOMG A HAS MANY RELATIONSHIP') - end - else - has_many_relationships = [HasManyRelationship.new(id: 1, body: 'ZOMG A HAS MANY RELATIONSHIP')] - end - has_one_relationship = HasOneRelationship.new(id: 42, first_name: 'Joao', last_name: 'Moura') - PrimaryResource.new(id: 1337, title: 'New PrimaryResource', virtual_attribute: nil, body: 'Body', has_many_relationships: has_many_relationships, has_one_relationship: has_one_relationship) - end - - def render_with_caching_serializer - toggle_cache_status - render json: PRIMARY_RESOURCE, serializer: CachingPrimaryResourceSerializer, adapter: :json, meta: { caching: perform_caching } - end - - def render_with_fragment_caching_serializer - toggle_cache_status - render json: PRIMARY_RESOURCE, serializer: FragmentCachingPrimaryResourceSerializer, adapter: :json, meta: { caching: perform_caching } - end - - def render_with_non_caching_serializer - toggle_cache_status - render json: PRIMARY_RESOURCE, adapter: :json, meta: { caching: perform_caching } - end - - def render_cache_status - toggle_cache_status - # Uncomment to debug - # STDERR.puts cache_store.class - # STDERR.puts cache_dependencies - # ActiveSupport::Cache::Store.logger.debug [ActiveModelSerializers.config.cache_store, ActiveModelSerializers.config.perform_caching, CachingPrimaryResourceSerializer._cache, perform_caching, params].inspect - render json: { caching: perform_caching, meta: { cache_log: cache_messages, cache_status: cache_status } }.to_json - end - - def clear - ActionController::Base.cache_store.clear - # Test caching is on - # Uncomment to turn on logger; possible performance issue - # logger = BenchmarkLogger.new - # ActiveSupport::Cache::Store.logger = logger # seems to be the best way - # - # the below is used in some rails tests but isn't available/working in all versions, so far as I can tell - # https://github.com/rails/rails/pull/15943 - # ActiveSupport::Notifications.subscribe(/^cache_(.*)\.active_support$/) do |*args| - # logger.debug ActiveSupport::Notifications::Event.new(*args) - # end - render json: 'ok'.to_json - end - - private - - def cache_status - { - controller: perform_caching, - app: Rails.configuration.action_controller.perform_caching, - serializers: Rails.configuration.serializers.each_with_object({}) { |serializer, data| data[serializer.name] = serializer._cache.present? } - } - end - - def cache_messages - ActiveSupport::Cache::Store.logger.is_a?(BenchmarkLogger) && ActiveSupport::Cache::Store.logger.messages.split("\n") - end - - def toggle_cache_status - case params[:on] - when 'on'.freeze then self.perform_caching = true - when 'off'.freeze then self.perform_caching = false - else nil # no-op - end - end -end - -Rails.application.routes.draw do - get '/status(/:on)' => 'primary_resource#render_cache_status' - get '/clear' => 'primary_resource#clear' - get '/caching(/:on)' => 'primary_resource#render_with_caching_serializer' - get '/fragment_caching(/:on)' => 'primary_resource#render_with_fragment_caching_serializer' - get '/non_caching(/:on)' => 'primary_resource#render_with_non_caching_serializer' -end diff --git a/test/benchmark/fixtures.rb b/test/benchmark/fixtures.rb deleted file mode 100644 index c91e102d..00000000 --- a/test/benchmark/fixtures.rb +++ /dev/null @@ -1,219 +0,0 @@ -Rails.configuration.serializers = [] -class HasOneRelationshipSerializer < ActiveModel::Serializer - attributes :id, :first_name, :last_name - - has_many :primary_resources, embed: :ids - has_one :bio -end -Rails.configuration.serializers << HasOneRelationshipSerializer - -class VirtualAttributeSerializer < ActiveModel::Serializer - attributes :id, :name -end -Rails.configuration.serializers << VirtualAttributeSerializer - -class HasManyRelationshipSerializer < ActiveModel::Serializer - attributes :id, :body - - belongs_to :primary_resource - belongs_to :has_one_relationship -end -Rails.configuration.serializers << HasManyRelationshipSerializer - -class PrimaryResourceSerializer < ActiveModel::Serializer - attributes :id, :title, :body - - has_many :has_many_relationships, serializer: HasManyRelationshipSerializer - belongs_to :virtual_attribute, serializer: VirtualAttributeSerializer - belongs_to :has_one_relationship, serializer: HasOneRelationshipSerializer - - link(:primary_resource_has_one_relationships) { 'https://example.com/primary_resource_has_one_relationships' } - - meta do - { - rating: 5, - favorite_count: 10 - } - end - - def virtual_attribute - VirtualAttribute.new(id: 999, name: 'Free-Range Virtual Attribute') - end -end -Rails.configuration.serializers << PrimaryResourceSerializer - -class CachingHasOneRelationshipSerializer < HasOneRelationshipSerializer - cache key: 'writer', skip_digest: true -end -Rails.configuration.serializers << CachingHasOneRelationshipSerializer - -class CachingHasManyRelationshipSerializer < HasManyRelationshipSerializer - cache expires_in: 1.day, skip_digest: true -end -Rails.configuration.serializers << CachingHasManyRelationshipSerializer - -# see https://github.com/rails-api/active_model_serializers/pull/1690/commits/68715b8f99bc29677e8a47bb3f305f23c077024b#r60344532 -class CachingPrimaryResourceSerializer < ActiveModel::Serializer - cache key: 'primary_resource', expires_in: 0.1, skip_digest: true - - attributes :id, :title, :body - - belongs_to :virtual_attribute, serializer: VirtualAttributeSerializer - belongs_to :has_one_relationship, serializer: CachingHasOneRelationshipSerializer - has_many :has_many_relationships, serializer: CachingHasManyRelationshipSerializer - - link(:primary_resource_has_one_relationships) { 'https://example.com/primary_resource_has_one_relationships' } - - meta do - { - rating: 5, - favorite_count: 10 - } - end - - def virtual_attribute - VirtualAttribute.new(id: 999, name: 'Free-Range Virtual Attribute') - end -end -Rails.configuration.serializers << CachingPrimaryResourceSerializer - -class FragmentCachingHasOneRelationshipSerializer < HasOneRelationshipSerializer - cache key: 'writer', only: [:first_name, :last_name], skip_digest: true -end -Rails.configuration.serializers << FragmentCachingHasOneRelationshipSerializer - -class FragmentCachingHasManyRelationshipSerializer < HasManyRelationshipSerializer - cache expires_in: 1.day, except: [:body], skip_digest: true -end -Rails.configuration.serializers << CachingHasManyRelationshipSerializer - -# see https://github.com/rails-api/active_model_serializers/pull/1690/commits/68715b8f99bc29677e8a47bb3f305f23c077024b#r60344532 -class FragmentCachingPrimaryResourceSerializer < ActiveModel::Serializer - cache key: 'primary_resource', expires_in: 0.1, skip_digest: true - - attributes :id, :title, :body - - belongs_to :virtual_attribute, serializer: VirtualAttributeSerializer - belongs_to :has_one_relationship, serializer: FragmentCachingHasOneRelationshipSerializer - has_many :has_many_relationships, serializer: FragmentCachingHasManyRelationshipSerializer - - link(:primary_resource_has_one_relationships) { 'https://example.com/primary_resource_has_one_relationships' } - - meta do - { - rating: 5, - favorite_count: 10 - } - end - - def virtual_attribute - VirtualAttribute.new(id: 999, name: 'Free-Range Virtual Attribute') - end -end -Rails.configuration.serializers << FragmentCachingPrimaryResourceSerializer - -if ENV['ENABLE_ACTIVE_RECORD'] == 'true' - require 'active_record' - - ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') - ActiveRecord::Schema.define do - self.verbose = false - - create_table :virtual_attributes, force: true do |t| - t.string :name - t.timestamps null: false - end - create_table :has_one_relationships, force: true do |t| - t.string :first_name - t.string :last_name - t.timestamps null: false - end - create_table :primary_resources, force: true do |t| - t.string :title - t.text :body - t.references :has_one_relationship - t.references :virtual_attribute - t.timestamps null: false - end - create_table :has_many_relationships, force: true do |t| - t.text :body - t.references :has_one_relationship - t.references :primary_resource - t.timestamps null: false - end - end - - class HasManyRelationship < ActiveRecord::Base - belongs_to :has_one_relationship - belongs_to :primary_resource - end - - class HasOneRelationship < ActiveRecord::Base - has_many :primary_resources - has_many :has_many_relationships - end - - class PrimaryResource < ActiveRecord::Base - has_many :has_many_relationships - belongs_to :has_one_relationship - belongs_to :virtual_attribute - end - - class VirtualAttribute < ActiveRecord::Base - has_many :primary_resources - end -else - # ActiveModelSerializers::Model is a convenient - # serializable class to inherit from when making - # serializable non-activerecord objects. - class BenchmarkModel - include ActiveModel::Model - include ActiveModel::Serializers::JSON - - attr_reader :attributes - - def initialize(attributes = {}) - @attributes = attributes - super - end - - # Defaults to the downcased model name. - def id - attributes.fetch(:id) { self.class.name.downcase } - end - - # Defaults to the downcased model name and updated_at - def cache_key - attributes.fetch(:cache_key) { "#{self.class.name.downcase}/#{id}" } - end - - # Defaults to the time the serializer file was modified. - def updated_at - @updated_at ||= attributes.fetch(:updated_at) { File.mtime(__FILE__) } - end - - def read_attribute_for_serialization(key) - if key == :id || key == 'id' - attributes.fetch(key) { id } - else - attributes[key] - end - end - end - - class HasManyRelationship < BenchmarkModel - attr_accessor :id, :body - end - - class HasOneRelationship < BenchmarkModel - attr_accessor :id, :first_name, :last_name, :primary_resources - end - - class PrimaryResource < BenchmarkModel - attr_accessor :id, :title, :body, :has_many_relationships, :virtual_attribute, :has_one_relationship - end - - class VirtualAttribute < BenchmarkModel - attr_accessor :id, :name - end -end diff --git a/test/cache_test.rb b/test/cache_test.rb deleted file mode 100644 index f0958931..00000000 --- a/test/cache_test.rb +++ /dev/null @@ -1,651 +0,0 @@ -require 'test_helper' -require 'tmpdir' -require 'tempfile' - -module ActiveModelSerializers - class CacheTest < ActiveSupport::TestCase - class Article < ::Model - attributes :title - # To confirm error is raised when cache_key is not set and cache_key option not passed to cache - undef_method :cache_key - end - class ArticleSerializer < ActiveModel::Serializer - cache only: [:place], skip_digest: true - attributes :title - end - - class Author < ::Model - attributes :id, :name - associations :posts, :bio, :roles - end - # Instead of a primitive cache key (i.e. a string), this class - # returns a list of objects that require to be expanded themselves. - class AuthorWithExpandableCacheElements < Author - # For the test purposes it's important that #to_s for HasCacheKey differs - # between instances, hence not a Struct. - class HasCacheKey - attr_reader :cache_key - def initialize(cache_key) - @cache_key = cache_key - end - - def to_s - "HasCacheKey##{object_id}" - end - end - - def cache_key - [ - HasCacheKey.new(name), - HasCacheKey.new(id) - ] - end - end - class UncachedAuthor < Author - # To confirm cache_key is set using updated_at and cache_key option passed to cache - undef_method :cache_key - end - class AuthorSerializer < ActiveModel::Serializer - cache key: 'writer', skip_digest: true - attributes :id, :name - - has_many :posts - has_many :roles - has_one :bio - end - - class Blog < ::Model - attributes :name - associations :writer - end - class BlogSerializer < ActiveModel::Serializer - cache key: 'blog' - attributes :id, :name - - belongs_to :writer - end - - class Comment < ::Model - attributes :id, :body - associations :post, :author - - # Uses a custom non-time-based cache key - def cache_key - "comment/#{id}" - end - end - class CommentSerializer < ActiveModel::Serializer - cache expires_in: 1.day, skip_digest: true - attributes :id, :body - belongs_to :post - belongs_to :author - end - - class Post < ::Model - attributes :id, :title, :body - associations :author, :comments, :blog - end - class PostSerializer < ActiveModel::Serializer - cache key: 'post', expires_in: 0.1, skip_digest: true - attributes :id, :title, :body - - has_many :comments - belongs_to :blog - belongs_to :author - end - - class Role < ::Model - attributes :name, :description, :special_attribute - associations :author - end - class RoleSerializer < ActiveModel::Serializer - cache only: [:name, :slug], skip_digest: true - attributes :id, :name, :description - attribute :friendly_id, key: :slug - belongs_to :author - - def friendly_id - "#{object.name}-#{object.id}" - end - end - class InheritedRoleSerializer < RoleSerializer - cache key: 'inherited_role', only: [:name, :special_attribute] - attribute :special_attribute - end - - setup do - cache_store.clear - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post = Post.new(id: 'post', title: 'New Post', body: 'Body') - @bio = Bio.new(id: 1, content: 'AMS Contributor') - @author = Author.new(id: 'author', name: 'Joao M. D. Moura') - @blog = Blog.new(id: 999, name: 'Custom blog', writer: @author) - @role = Role.new(name: 'Great Author') - @location = Location.new(lat: '-23.550520', lng: '-46.633309') - @place = Place.new(name: 'Amazing Place') - @author.posts = [@post] - @author.roles = [@role] - @role.author = @author - @author.bio = @bio - @bio.author = @author - @post.comments = [@comment] - @post.author = @author - @comment.post = @post - @comment.author = @author - @post.blog = @blog - @location.place = @place - - @location_serializer = LocationSerializer.new(@location) - @bio_serializer = BioSerializer.new(@bio) - @role_serializer = RoleSerializer.new(@role) - @post_serializer = PostSerializer.new(@post) - @author_serializer = AuthorSerializer.new(@author) - @comment_serializer = CommentSerializer.new(@comment) - @blog_serializer = BlogSerializer.new(@blog) - end - - def test_explicit_cache_store - default_store = Class.new(ActiveModel::Serializer) do - cache - end - explicit_store = Class.new(ActiveModel::Serializer) do - cache cache_store: ActiveSupport::Cache::FileStore - end - - assert ActiveSupport::Cache::MemoryStore, ActiveModelSerializers.config.cache_store - assert ActiveSupport::Cache::MemoryStore, default_store.cache_store - assert ActiveSupport::Cache::FileStore, explicit_store.cache_store - end - - def test_inherited_cache_configuration - inherited_serializer = Class.new(PostSerializer) - - assert_equal PostSerializer._cache_key, inherited_serializer._cache_key - assert_equal PostSerializer._cache_options, inherited_serializer._cache_options - end - - def test_override_cache_configuration - inherited_serializer = Class.new(PostSerializer) do - cache key: 'new-key' - end - - assert_equal PostSerializer._cache_key, 'post' - assert_equal inherited_serializer._cache_key, 'new-key' - end - - def test_cache_definition - assert_equal(cache_store, @post_serializer.class._cache) - assert_equal(cache_store, @author_serializer.class._cache) - assert_equal(cache_store, @comment_serializer.class._cache) - end - - def test_cache_key_definition - assert_equal('post', @post_serializer.class._cache_key) - assert_equal('writer', @author_serializer.class._cache_key) - assert_nil(@comment_serializer.class._cache_key) - end - - def test_cache_key_interpolation_with_updated_at_when_cache_key_is_not_defined_on_object - uncached_author = UncachedAuthor.new(name: 'Joao M. D. Moura') - uncached_author_serializer = AuthorSerializer.new(uncached_author) - - render_object_with_cache(uncached_author) - key = "#{uncached_author_serializer.class._cache_key}/#{uncached_author_serializer.object.id}-#{uncached_author_serializer.object.updated_at.strftime('%Y%m%d%H%M%S%9N')}" - key = "#{key}/#{adapter.cache_key}" - assert_equal(uncached_author_serializer.attributes.to_json, cache_store.fetch(key).to_json) - end - - def test_cache_key_expansion - author = AuthorWithExpandableCacheElements.new(id: 10, name: 'hello') - same_author = AuthorWithExpandableCacheElements.new(id: 10, name: 'hello') - diff_author = AuthorWithExpandableCacheElements.new(id: 11, name: 'hello') - - author_serializer = AuthorSerializer.new(author) - same_author_serializer = AuthorSerializer.new(same_author) - diff_author_serializer = AuthorSerializer.new(diff_author) - adapter = AuthorSerializer.serialization_adapter_instance - - assert_equal(author_serializer.cache_key(adapter), same_author_serializer.cache_key(adapter)) - refute_equal(author_serializer.cache_key(adapter), diff_author_serializer.cache_key(adapter)) - end - - def test_default_cache_key_fallback - render_object_with_cache(@comment) - key = "#{@comment.cache_key}/#{adapter.cache_key}" - assert_equal(@comment_serializer.attributes.to_json, cache_store.fetch(key).to_json) - end - - def test_error_is_raised_if_cache_key_is_not_defined_on_object_or_passed_as_cache_option - article = Article.new(title: 'Must Read') - e = assert_raises ActiveModel::Serializer::UndefinedCacheKey do - render_object_with_cache(article) - end - assert_match(/ActiveModelSerializers::CacheTest::Article must define #cache_key, or the 'key:' option must be passed into 'ActiveModelSerializers::CacheTest::ArticleSerializer.cache'/, e.message) - end - - def test_cache_options_definition - assert_equal({ expires_in: 0.1, skip_digest: true }, @post_serializer.class._cache_options) - assert_nil(@blog_serializer.class._cache_options) - assert_equal({ expires_in: 1.day, skip_digest: true }, @comment_serializer.class._cache_options) - end - - def test_fragment_cache_definition - assert_equal([:name, :slug], @role_serializer.class._cache_only) - assert_equal([:content], @bio_serializer.class._cache_except) - end - - def test_associations_separately_cache - cache_store.clear - assert_nil(cache_store.fetch(@post.cache_key)) - assert_nil(cache_store.fetch(@comment.cache_key)) - - Timecop.freeze(Time.current) do - render_object_with_cache(@post) - - key = "#{@post.cache_key}/#{adapter.cache_key}" - assert_equal(@post_serializer.attributes, cache_store.fetch(key)) - key = "#{@comment.cache_key}/#{adapter.cache_key}" - assert_equal(@comment_serializer.attributes, cache_store.fetch(key)) - end - end - - def test_associations_cache_when_updated - Timecop.freeze(Time.current) do - # Generate a new Cache of Post object and each objects related to it. - render_object_with_cache(@post) - - # Check if it cached the objects separately - key = "#{@post.cache_key}/#{adapter.cache_key}" - assert_equal(@post_serializer.attributes, cache_store.fetch(key)) - key = "#{@comment.cache_key}/#{adapter.cache_key}" - assert_equal(@comment_serializer.attributes, cache_store.fetch(key)) - - # Simulating update on comments relationship with Post - new_comment = Comment.new(id: 2567, body: 'ZOMG A NEW COMMENT') - new_comment_serializer = CommentSerializer.new(new_comment) - @post.comments = [new_comment] - - # Ask for the serialized object - render_object_with_cache(@post) - - # Check if the the new comment was cached - key = "#{new_comment.cache_key}/#{adapter.cache_key}" - assert_equal(new_comment_serializer.attributes, cache_store.fetch(key)) - key = "#{@post.cache_key}/#{adapter.cache_key}" - assert_equal(@post_serializer.attributes, cache_store.fetch(key)) - end - end - - def test_fragment_fetch_with_virtual_associations - expected_result = { - id: @location.id, - lat: @location.lat, - lng: @location.lng, - address: 'Nowhere' - } - - hash = render_object_with_cache(@location) - - assert_equal(hash, expected_result) - key = "#{@location.cache_key}/#{adapter.cache_key}" - assert_equal({ address: 'Nowhere' }, cache_store.fetch(key)) - end - - def test_fragment_cache_with_inheritance - inherited = render_object_with_cache(@role, serializer: InheritedRoleSerializer) - base = render_object_with_cache(@role) - - assert_includes(inherited.keys, :special_attribute) - refute_includes(base.keys, :special_attribute) - end - - def test_uses_adapter_in_cache_key - render_object_with_cache(@post) - key = "#{@post.cache_key}/#{adapter.class.to_s.demodulize.underscore}" - assert_equal(@post_serializer.attributes, cache_store.fetch(key)) - end - - # Based on original failing test by @kevintyll - # rubocop:disable Metrics/AbcSize - def test_a_serializer_rendered_by_two_adapter_returns_differently_fetch_attributes - Object.const_set(:Alert, Class.new(ActiveModelSerializers::Model) do - attributes :id, :status, :resource, :started_at, :ended_at, :updated_at, :created_at - end) - Object.const_set(:UncachedAlertSerializer, Class.new(ActiveModel::Serializer) do - attributes :id, :status, :resource, :started_at, :ended_at, :updated_at, :created_at - end) - Object.const_set(:AlertSerializer, Class.new(UncachedAlertSerializer) do - cache - end) - - alert = Alert.new( - id: 1, - status: 'fail', - resource: 'resource-1', - started_at: Time.new(2016, 3, 31, 21, 36, 35, 0), - ended_at: nil, - updated_at: Time.new(2016, 3, 31, 21, 27, 35, 0), - created_at: Time.new(2016, 3, 31, 21, 37, 35, 0) - ) - - expected_fetch_attributes = { - id: 1, - status: 'fail', - resource: 'resource-1', - started_at: alert.started_at, - ended_at: nil, - updated_at: alert.updated_at, - created_at: alert.created_at - }.with_indifferent_access - expected_cached_jsonapi_attributes = { - id: '1', - type: 'alerts', - attributes: { - status: 'fail', - resource: 'resource-1', - started_at: alert.started_at, - ended_at: nil, - updated_at: alert.updated_at, - created_at: alert.created_at - } - }.with_indifferent_access - - # Assert attributes are serialized correctly - serializable_alert = serializable(alert, serializer: AlertSerializer, adapter: :attributes) - attributes_serialization = serializable_alert.as_json.with_indifferent_access - assert_equal expected_fetch_attributes, alert.attributes - assert_equal alert.attributes, attributes_serialization - attributes_cache_key = serializable_alert.adapter.serializer.cache_key(serializable_alert.adapter) - assert_equal attributes_serialization, cache_store.fetch(attributes_cache_key).with_indifferent_access - - serializable_alert = serializable(alert, serializer: AlertSerializer, adapter: :json_api) - jsonapi_cache_key = serializable_alert.adapter.serializer.cache_key(serializable_alert.adapter) - # Assert cache keys differ - refute_equal attributes_cache_key, jsonapi_cache_key - # Assert (cached) serializations differ - jsonapi_serialization = serializable_alert.as_json - assert_equal alert.status, jsonapi_serialization.fetch(:data).fetch(:attributes).fetch(:status) - serializable_alert = serializable(alert, serializer: UncachedAlertSerializer, adapter: :json_api) - assert_equal serializable_alert.as_json, jsonapi_serialization - - cached_serialization = cache_store.fetch(jsonapi_cache_key).with_indifferent_access - assert_equal expected_cached_jsonapi_attributes, cached_serialization - ensure - Object.send(:remove_const, :Alert) - Object.send(:remove_const, :AlertSerializer) - Object.send(:remove_const, :UncachedAlertSerializer) - end - # rubocop:enable Metrics/AbcSize - - def test_uses_file_digest_in_cache_key - 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_cache_digest_definition - file_digest = Digest::MD5.hexdigest(File.open(__FILE__).read) - assert_equal(file_digest, @post_serializer.class._cache_digest) - end - - def test_object_cache_keys - serializable = ActiveModelSerializers::SerializableResource.new([@comment, @comment]) - include_directive = JSONAPI::IncludeDirective.new('*', allow_wildcard: true) - - actual = ActiveModel::Serializer.object_cache_keys(serializable.adapter.serializer, serializable.adapter, include_directive) - - assert_equal 3, actual.size - expected_key = "comment/1/#{serializable.adapter.cache_key}" - assert actual.any? { |key| key == expected_key }, "actual '#{actual}' should include #{expected_key}" - expected_key = %r{post/post-\d+} - assert actual.any? { |key| key =~ expected_key }, "actual '#{actual}' should match '#{expected_key}'" - expected_key = %r{author/author-\d+} - assert actual.any? { |key| key =~ expected_key }, "actual '#{actual}' should match '#{expected_key}'" - end - - # rubocop:disable Metrics/AbcSize - def test_fetch_attributes_from_cache - serializers = ActiveModel::Serializer::CollectionSerializer.new([@comment, @comment]) - - Timecop.freeze(Time.current) do - render_object_with_cache(@comment) - - options = {} - 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 - - include_directive = ActiveModelSerializers.default_include_directive - manual_cached_attributes = ActiveModel::Serializer.cache_read_multi(serializers, adapter_instance, include_directive).with_indifferent_access - assert_equal manual_cached_attributes, cached_attributes - - assert_equal cached_attributes["#{@comment.cache_key}/#{adapter_instance.cache_key}"], Comment.new(id: 1, body: 'ZOMG A COMMENT').attributes - assert_equal cached_attributes["#{@comment.post.cache_key}/#{adapter_instance.cache_key}"], Post.new(id: 'post', title: 'New Post', body: 'Body').attributes - - writer = @comment.post.blog.writer - writer_cache_key = writer.cache_key - assert_equal cached_attributes["#{writer_cache_key}/#{adapter_instance.cache_key}"], Author.new(id: 'author', name: 'Joao M. D. Moura').attributes - end - end - # rubocop:enable Metrics/AbcSize - - def test_cache_read_multi_with_fragment_cache_enabled - post_serializer = Class.new(ActiveModel::Serializer) do - cache except: [:body] - end - - serializers = ActiveModel::Serializer::CollectionSerializer.new([@post, @post], serializer: post_serializer) - - Timecop.freeze(Time.current) do - # Warming up. - options = {} - adapter_options = {} - adapter_instance = ActiveModelSerializers::Adapter::Attributes.new(serializers, adapter_options) - serializers.serializable_hash(adapter_options, options, adapter_instance) - - # Should find something with read_multi now - adapter_options = {} - serializers.serializable_hash(adapter_options, options, adapter_instance) - cached_attributes = adapter_options.fetch(:cached_attributes) - - include_directive = ActiveModelSerializers.default_include_directive - manual_cached_attributes = ActiveModel::Serializer.cache_read_multi(serializers, adapter_instance, include_directive) - - refute_equal 0, cached_attributes.size - refute_equal 0, manual_cached_attributes.size - assert_equal manual_cached_attributes, cached_attributes - end - end - - def test_serializer_file_path_on_nix - path = '/Users/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb' - caller_line = "#{path}:1:in `'" - assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path - end - - def test_serializer_file_path_on_windows - path = 'c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb' - caller_line = "#{path}:1:in `'" - assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path - end - - def test_serializer_file_path_with_space - path = '/Users/git/ember js/ember-crm-backend/app/serializers/lead_serializer.rb' - caller_line = "#{path}:1:in `'" - assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path - end - - def test_serializer_file_path_with_submatch - # The submatch in the path ensures we're using a correctly greedy regexp. - path = '/Users/git/ember js/ember:123:in x/app/serializers/lead_serializer.rb' - caller_line = "#{path}:1:in `'" - assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path - end - - def test_digest_caller_file - contents = "puts 'AMS rocks'!" - dir = Dir.mktmpdir('space char') - file = Tempfile.new('some_ruby.rb', dir) - file.write(contents) - path = file.path - caller_line = "#{path}:1:in `'" - file.close - assert_equal ActiveModel::Serializer.digest_caller_file(caller_line), Digest::MD5.hexdigest(contents) - ensure - file.unlink - FileUtils.remove_entry dir - end - - def test_warn_on_serializer_not_defined_in_file - called = false - serializer = Class.new(ActiveModel::Serializer) - assert_output(nil, /_cache_digest/) do - serializer.digest_caller_file('') - called = true - end - assert called - end - - def test_cached_false_without_cache_store - cached_serializer = build_cached_serializer do |serializer| - serializer._cache = nil - end - refute cached_serializer.class.cache_enabled? - end - - def test_cached_true_with_cache_store_and_without_cache_only_and_cache_except - cached_serializer = build_cached_serializer do |serializer| - serializer._cache = Object - end - assert cached_serializer.class.cache_enabled? - end - - def test_cached_false_with_cache_store_and_with_cache_only - cached_serializer = build_cached_serializer do |serializer| - serializer._cache = Object - serializer._cache_only = [:name] - end - refute cached_serializer.class.cache_enabled? - end - - def test_cached_false_with_cache_store_and_with_cache_except - cached_serializer = build_cached_serializer do |serializer| - serializer._cache = Object - serializer._cache_except = [:content] - end - refute cached_serializer.class.cache_enabled? - end - - def test_fragment_cached_false_without_cache_store - cached_serializer = build_cached_serializer do |serializer| - serializer._cache = nil - serializer._cache_only = [:name] - end - refute cached_serializer.class.fragment_cache_enabled? - end - - def test_fragment_cached_true_with_cache_store_and_cache_only - cached_serializer = build_cached_serializer do |serializer| - serializer._cache = Object - serializer._cache_only = [:name] - end - assert cached_serializer.class.fragment_cache_enabled? - end - - def test_fragment_cached_true_with_cache_store_and_cache_except - cached_serializer = build_cached_serializer do |serializer| - serializer._cache = Object - serializer._cache_except = [:content] - end - assert cached_serializer.class.fragment_cache_enabled? - end - - def test_fragment_cached_false_with_cache_store_and_cache_except_and_cache_only - cached_serializer = build_cached_serializer do |serializer| - serializer._cache = Object - serializer._cache_except = [:content] - serializer._cache_only = [:name] - end - refute cached_serializer.class.fragment_cache_enabled? - end - - def test_fragment_fetch_with_virtual_attributes - author = Author.new(name: 'Joao M. D. Moura') - role = Role.new(name: 'Great Author', description: nil) - role.author = [author] - role_serializer = RoleSerializer.new(role) - adapter_instance = ActiveModelSerializers::Adapter.configured_adapter.new(role_serializer) - expected_result = { - id: role.id, - description: role.description, - slug: "#{role.name}-#{role.id}", - name: role.name - } - cache_store.clear - - role_hash = role_serializer.fetch_attributes_fragment(adapter_instance) - assert_equal(role_hash, expected_result) - - role.id = 'this has been updated' - role.name = 'this was cached' - - role_hash = role_serializer.fetch_attributes_fragment(adapter_instance) - assert_equal(expected_result.merge(id: role.id), role_hash) - end - - def test_fragment_fetch_with_except - adapter_instance = ActiveModelSerializers::Adapter.configured_adapter.new(@bio_serializer) - expected_result = { - id: @bio.id, - rating: nil, - content: @bio.content - } - cache_store.clear - - bio_hash = @bio_serializer.fetch_attributes_fragment(adapter_instance) - assert_equal(expected_result, bio_hash) - - @bio.content = 'this has been updated' - @bio.rating = 'this was cached' - - bio_hash = @bio_serializer.fetch_attributes_fragment(adapter_instance) - assert_equal(expected_result.merge(content: @bio.content), bio_hash) - end - - def test_fragment_fetch_with_namespaced_object - @spam = Spam::UnrelatedLink.new(id: 'spam-id-1') - @spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam) - adapter_instance = ActiveModelSerializers::Adapter.configured_adapter.new(@spam_serializer) - @spam_hash = @spam_serializer.fetch_attributes_fragment(adapter_instance) - expected_result = { - id: @spam.id - } - assert_equal(@spam_hash, expected_result) - end - - private - - def cache_store - ActiveModelSerializers.config.cache_store - end - - def build_cached_serializer - serializer = Class.new(ActiveModel::Serializer) - serializer._cache_key = nil - serializer._cache_options = nil - yield serializer if block_given? - serializer.new(Object) - end - - def render_object_with_cache(obj, options = {}) - @serializable_resource = serializable(obj, options) - @serializable_resource.serializable_hash - end - - def adapter - @serializable_resource.adapter - end - end -end diff --git a/test/collection_serializer_test.rb b/test/collection_serializer_test.rb deleted file mode 100644 index cdbebb15..00000000 --- a/test/collection_serializer_test.rb +++ /dev/null @@ -1,123 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class CollectionSerializerTest < ActiveSupport::TestCase - class SingularModel < ::Model; end - class SingularModelSerializer < ActiveModel::Serializer - end - class HasManyModel < ::Model - associations :singular_models - end - class HasManyModelSerializer < ActiveModel::Serializer - has_many :singular_models - - def custom_options - instance_options - end - end - class MessagesSerializer < ActiveModel::Serializer - type 'messages' - end - - def setup - @singular_model = SingularModel.new - @has_many_model = HasManyModel.new - @resource = build_named_collection @singular_model, @has_many_model - @serializer = collection_serializer.new(@resource, some: :options) - end - - def collection_serializer - CollectionSerializer - end - - def build_named_collection(*resource) - resource.define_singleton_method(:name) { 'MeResource' } - resource - end - - def test_has_object_reader_serializer_interface - assert_equal @serializer.object, @resource - end - - def test_respond_to_each - assert_respond_to @serializer, :each - end - - def test_each_object_should_be_serialized_with_appropriate_serializer - serializers = @serializer.to_a - - assert_kind_of SingularModelSerializer, serializers.first - assert_kind_of SingularModel, serializers.first.object - - assert_kind_of HasManyModelSerializer, serializers.last - assert_kind_of HasManyModel, serializers.last.object - - assert_equal :options, serializers.last.custom_options[:some] - end - - def test_serializer_option_not_passed_to_each_serializer - serializers = collection_serializer.new([@has_many_model], serializer: HasManyModelSerializer).to_a - - refute serializers.first.custom_options.key?(:serializer) - end - - def test_root_default - @serializer = collection_serializer.new([@singular_model, @has_many_model]) - assert_nil @serializer.root - end - - def test_root - expected = 'custom_root' - @serializer = collection_serializer.new([@singular_model, @has_many_model], root: expected) - assert_equal expected, @serializer.root - end - - def test_root_with_no_serializers - expected = 'custom_root' - @serializer = collection_serializer.new([], root: expected) - assert_equal expected, @serializer.root - end - - def test_json_key_with_resource_with_serializer - singular_key = @serializer.send(:serializers).first.json_key - assert_equal singular_key.pluralize, @serializer.json_key - end - - def test_json_key_with_resource_with_name_and_no_serializers - serializer = collection_serializer.new(build_named_collection) - assert_equal 'me_resources', serializer.json_key - end - - 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 - end - - def test_json_key_with_resource_without_name_and_no_serializers - serializer = collection_serializer.new([]) - assert_nil serializer.json_key - end - - def test_json_key_with_empty_resources_with_serializer - resource = [] - serializer = collection_serializer.new(resource, serializer: MessagesSerializer) - assert_equal 'messages', serializer.json_key - end - - def test_json_key_with_root - expected = 'custom_root' - serializer = collection_serializer.new(@resource, root: expected) - assert_equal expected, serializer.json_key - end - - def test_json_key_with_root_and_no_serializers - expected = 'custom_root' - serializer = collection_serializer.new(build_named_collection, root: expected) - assert_equal expected, serializer.json_key - end - end - end -end diff --git a/test/fixtures/active_record.rb b/test/fixtures/active_record.rb deleted file mode 100644 index 9dc3830d..00000000 --- a/test/fixtures/active_record.rb +++ /dev/null @@ -1,113 +0,0 @@ -require 'active_record' - -ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') -ActiveRecord::Schema.define do - self.verbose = false - create_table :posts, force: true do |t| - t.string :title - t.text :body - t.references :author - t.timestamps null: false - end - create_table :authors, force: true do |t| - t.string :name - t.timestamps null: false - end - create_table :comments, force: true do |t| - t.text :contents - t.references :author - t.references :post - t.timestamp null: false - end - create_table :employees, force: true do |t| - t.string :name - t.string :email - t.timestamp 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 - end - create_table :poly_tags, force: true do |t| - t.string :phrase - t.timestamp 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 - end -end - -module ARModels - class Post < ActiveRecord::Base - has_many :comments - belongs_to :author - end - class PostSerializer < ActiveModel::Serializer - attributes :id, :title, :body - - has_many :comments - belongs_to :author - end - - class Comment < ActiveRecord::Base - belongs_to :post - belongs_to :author - end - class CommentSerializer < ActiveModel::Serializer - attributes :id, :contents - - belongs_to :author - end - - class Author < ActiveRecord::Base - has_many :posts - end - class AuthorSerializer < ActiveModel::Serializer - attributes :id, :name - - has_many :posts - end -end - -class Employee < ActiveRecord::Base - has_many :pictures, as: :imageable - has_many :object_tags, as: :taggable -end - -class PolymorphicSimpleSerializer < ActiveModel::Serializer - attributes :id -end - -class ObjectTag < ActiveRecord::Base - belongs_to :poly_tag - belongs_to :taggable, polymorphic: true -end -class PolymorphicObjectTagSerializer < ActiveModel::Serializer - attributes :id - has_many :taggable, serializer: PolymorphicSimpleSerializer, polymorphic: true -end - -class PolyTag < ActiveRecord::Base - has_many :object_tags -end -class PolymorphicTagSerializer < ActiveModel::Serializer - attributes :id, :phrase - has_many :object_tags, serializer: PolymorphicObjectTagSerializer -end - -class Picture < ActiveRecord::Base - belongs_to :imageable, polymorphic: true - has_many :object_tags, as: :taggable -end -class PolymorphicHasManySerializer < ActiveModel::Serializer - attributes :id, :name -end -class PolymorphicBelongsToSerializer < ActiveModel::Serializer - attributes :id, :title - has_one :imageable, serializer: PolymorphicHasManySerializer, polymorphic: true -end diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb deleted file mode 100644 index 6245ad23..00000000 --- a/test/fixtures/poro.rb +++ /dev/null @@ -1,225 +0,0 @@ -class Model < ActiveModelSerializers::Model - rand(2).zero? && derive_attributes_from_names_and_fix_accessors - - attr_writer :id - - # At this time, just for organization of intent - class_attribute :association_names - self.association_names = [] - - def self.associations(*names) - self.association_names |= names.map(&:to_sym) - # Silence redefinition of methods warnings - ActiveModelSerializers.silence_warnings do - attr_accessor(*names) - end - end - - def associations - association_names.each_with_object({}) do |association_name, result| - result[association_name] = public_send(association_name).freeze - end.with_indifferent_access.freeze - end - - def attributes - super.except(*association_names) - end -end - -# see -# https://github.com/rails/rails/blob/4-2-stable/activemodel/lib/active_model/errors.rb -# The below allows you to do: -# -# model = ModelWithErrors.new -# model.validate! # => ["cannot be nil"] -# model.errors.full_messages # => ["name cannot be nil"] -class ModelWithErrors < Model - attributes :name -end - -class Profile < Model - attributes :name, :description - associations :comments -end -class ProfileSerializer < ActiveModel::Serializer - attributes :name, :description -end -class ProfilePreviewSerializer < ActiveModel::Serializer - attributes :name -end - -class Author < Model - attributes :name - associations :posts, :bio, :roles, :comments -end -class AuthorSerializer < ActiveModel::Serializer - cache key: 'writer', skip_digest: true - attribute :id - attribute :name - - has_many :posts - has_many :roles - has_one :bio -end -class AuthorPreviewSerializer < ActiveModel::Serializer - attributes :id - has_many :posts -end - -class Comment < Model - attributes :body, :date - associations :post, :author, :likes -end -class CommentSerializer < ActiveModel::Serializer - cache expires_in: 1.day, skip_digest: true - attributes :id, :body - belongs_to :post - belongs_to :author -end -class CommentPreviewSerializer < ActiveModel::Serializer - attributes :id - - belongs_to :post -end - -class Post < Model - attributes :title, :body - associations :author, :comments, :blog, :tags, :related -end -class PostSerializer < ActiveModel::Serializer - cache key: 'post', expires_in: 0.1, skip_digest: true - attributes :id, :title, :body - - has_many :comments - belongs_to :blog - belongs_to :author - - def blog - Blog.new(id: 999, name: 'Custom blog') - end -end -class SpammyPostSerializer < ActiveModel::Serializer - attributes :id - has_many :related -end -class PostPreviewSerializer < ActiveModel::Serializer - attributes :title, :body, :id - - has_many :comments, serializer: ::CommentPreviewSerializer - belongs_to :author, serializer: ::AuthorPreviewSerializer -end -class PostWithCustomKeysSerializer < ActiveModel::Serializer - attributes :id - has_many :comments, key: :reviews - belongs_to :author, key: :writer - has_one :blog, key: :site -end - -class Bio < Model - attributes :content, :rating - associations :author -end -class BioSerializer < ActiveModel::Serializer - cache except: [:content], skip_digest: true - attributes :id, :content, :rating - - belongs_to :author -end - -class Blog < Model - attributes :name, :type, :special_attribute - associations :writer, :articles -end -class BlogSerializer < ActiveModel::Serializer - cache key: 'blog' - attributes :id, :name - - belongs_to :writer - has_many :articles -end -class AlternateBlogSerializer < ActiveModel::Serializer - attribute :id - attribute :name, key: :title -end -class CustomBlogSerializer < ActiveModel::Serializer - attribute :id - attribute :special_attribute - has_many :articles -end - -class Role < Model - attributes :name, :description, :special_attribute - associations :author -end -class RoleSerializer < ActiveModel::Serializer - cache only: [:name, :slug], skip_digest: true - attributes :id, :name, :description - attribute :friendly_id, key: :slug - belongs_to :author - - def friendly_id - "#{object.name}-#{object.id}" - end -end - -class Location < Model - attributes :lat, :lng - associations :place -end -class LocationSerializer < ActiveModel::Serializer - cache only: [:address], skip_digest: true - attributes :id, :lat, :lng - - belongs_to :place, key: :address - - def place - 'Nowhere' - end -end - -class Place < Model - attributes :name - associations :locations -end -class PlaceSerializer < ActiveModel::Serializer - attributes :id, :name - has_many :locations -end - -class Like < Model - attributes :time - associations :likeable -end -class LikeSerializer < ActiveModel::Serializer - attributes :id, :time - belongs_to :likeable -end - -module Spam - class UnrelatedLink < Model - end - class UnrelatedLinkSerializer < ActiveModel::Serializer - cache only: [:id] - attributes :id - end -end - -class VirtualValue < Model; end -class VirtualValueSerializer < ActiveModel::Serializer - attributes :id - has_many :reviews, virtual_value: [{ type: 'reviews', id: '1' }, - { type: 'reviews', id: '2' }] - has_one :maker, virtual_value: { type: 'makers', id: '1' } - - def reviews - end - - def maker - end -end - -class PaginatedSerializer < ActiveModel::Serializer::CollectionSerializer - def json_key - 'paginated' - end -end diff --git a/test/generators/scaffold_controller_generator_test.rb b/test/generators/scaffold_controller_generator_test.rb deleted file mode 100644 index 183bb4f6..00000000 --- a/test/generators/scaffold_controller_generator_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'test_helper' -require 'generators/rails/resource_override' - -class ResourceGeneratorTest < Rails::Generators::TestCase - destination File.expand_path('../../../tmp/generators', __FILE__) - setup :prepare_destination, :copy_routes - - tests Rails::Generators::ResourceGenerator - arguments %w(account) - - def test_serializer_file_is_generated - run_generator - - assert_file 'app/serializers/account_serializer.rb', /class AccountSerializer < ActiveModel::Serializer/ - end - - private - - def copy_routes - config_dir = File.join(destination_root, 'config') - FileUtils.mkdir_p(config_dir) - File.write(File.join(config_dir, 'routes.rb'), 'Rails.application.routes.draw {}') - end -end diff --git a/test/generators/serializer_generator_test.rb b/test/generators/serializer_generator_test.rb deleted file mode 100644 index eef4a41e..00000000 --- a/test/generators/serializer_generator_test.rb +++ /dev/null @@ -1,75 +0,0 @@ -require 'test_helper' -require 'generators/rails/resource_override' -require 'generators/rails/serializer_generator' - -class SerializerGeneratorTest < Rails::Generators::TestCase - destination File.expand_path('../../../tmp/generators', __FILE__) - setup :prepare_destination - - tests Rails::Generators::SerializerGenerator - arguments %w(account name:string description:text business:references) - - def test_generates_a_serializer - run_generator - assert_file 'app/serializers/account_serializer.rb', /class AccountSerializer < ActiveModel::Serializer/ - end - - def test_generates_a_namespaced_serializer - run_generator ['admin/account'] - assert_file 'app/serializers/admin/account_serializer.rb', /class Admin::AccountSerializer < ActiveModel::Serializer/ - end - - def test_uses_application_serializer_if_one_exists - stub_safe_constantize(expected: 'ApplicationSerializer') do - run_generator - assert_file 'app/serializers/account_serializer.rb', /class AccountSerializer < ApplicationSerializer/ - end - end - - def test_uses_given_parent - Object.const_set(:ApplicationSerializer, Class.new) - run_generator ['Account', '--parent=MySerializer'] - assert_file 'app/serializers/account_serializer.rb', /class AccountSerializer < MySerializer/ - ensure - Object.send :remove_const, :ApplicationSerializer - end - - def test_generates_attributes_and_associations - run_generator - assert_file 'app/serializers/account_serializer.rb' do |serializer| - assert_match(/^ attributes :id, :name, :description$/, serializer) - assert_match(/^ has_one :business$/, serializer) - assert_match(/^end\n*\z/, serializer) - end - end - - def test_with_no_attributes_does_not_add_extra_space - run_generator ['account'] - assert_file 'app/serializers/account_serializer.rb' do |content| - if RUBY_PLATFORM =~ /mingw/ - assert_no_match(/\r\n\r\nend/, content) - else - assert_no_match(/\n\nend/, content) - end - end - end - - private - - def stub_safe_constantize(expected:) - String.class_eval do - alias_method :old, :safe_constantize - end - String.send(:define_method, :safe_constantize) do - Class if self == expected - end - - yield - ensure - String.class_eval do - undef_method :safe_constantize - alias_method :safe_constantize, :old - undef_method :old - end - end -end diff --git a/test/grape_test.rb b/test/grape_test.rb deleted file mode 100644 index 4851e57a..00000000 --- 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 deleted file mode 100644 index d404ccec..00000000 --- a/test/lint_test.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class LintTest < ActiveSupport::TestCase - include ActiveModel::Serializer::Lint::Tests - - class CompliantResource - def serializable_hash(options = nil) - end - - def read_attribute_for_serialization(name) - end - - def as_json(options = nil) - end - - def to_json(options = nil) - end - - def cache_key - end - - def id - end - - def updated_at - end - - def errors - end - - def self.human_attribute_name(_, _ = {}) - end - - def self.lookup_ancestors - end - - def self.model_name - @_model_name ||= ActiveModel::Name.new(self) - end - end - - def setup - @resource = CompliantResource.new - end - end - end -end diff --git a/test/logger_test.rb b/test/logger_test.rb deleted file mode 100644 index a15227bb..00000000 --- a/test/logger_test.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - class LoggerTest < ActiveSupport::TestCase - def test_logger_is_set_to_action_controller_logger_when_initializer_runs - assert_equal $action_controller_logger, ActionController::Base.logger # rubocop:disable Style/GlobalVars - end - - def test_logger_can_be_set - original_logger = ActiveModelSerializers.logger - logger = Logger.new(STDOUT) - - ActiveModelSerializers.logger = logger - - assert_equal ActiveModelSerializers.logger, logger - ensure - ActiveModelSerializers.logger = original_logger - end - end -end diff --git a/test/poro_test.rb b/test/poro_test.rb deleted file mode 100644 index e5fba858..00000000 --- a/test/poro_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -class PoroTest < ActiveSupport::TestCase - include ActiveModel::Serializer::Lint::Tests - - def setup - @resource = Model.new - end -end diff --git a/test/serializable_resource_test.rb b/test/serializable_resource_test.rb deleted file mode 100644 index ab12bc27..00000000 --- a/test/serializable_resource_test.rb +++ /dev/null @@ -1,79 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - class SerializableResourceTest < ActiveSupport::TestCase - def setup - @resource = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') - @serializer = ProfileSerializer.new(@resource) - @adapter = ActiveModelSerializers::Adapter.create(@serializer) - @serializable_resource = SerializableResource.new(@resource) - end - - def test_deprecation - assert_output(nil, /deprecated/) do - deprecated_serializable_resource = ActiveModel::SerializableResource.new(@resource) - assert_equal(@serializable_resource.as_json, deprecated_serializable_resource.as_json) - end - end - - def test_serializable_resource_delegates_serializable_hash_to_the_adapter - options = nil - assert_equal @adapter.serializable_hash(options), @serializable_resource.serializable_hash(options) - end - - def test_serializable_resource_delegates_to_json_to_the_adapter - options = nil - assert_equal @adapter.to_json(options), @serializable_resource.to_json(options) - end - - def test_serializable_resource_delegates_as_json_to_the_adapter - options = nil - assert_equal @adapter.as_json(options), @serializable_resource.as_json(options) - end - - def test_use_adapter_with_adapter_option - assert SerializableResource.new(@resource, adapter: 'json').use_adapter? - end - - def test_use_adapter_with_adapter_option_as_false - refute SerializableResource.new(@resource, adapter: false).use_adapter? - end - - class SerializableResourceErrorsTest < Minitest::Test - def test_serializable_resource_with_errors - options = nil - resource = ModelWithErrors.new - resource.errors.add(:name, 'must be awesome') - serializable_resource = ActiveModelSerializers::SerializableResource.new( - resource, serializer: ActiveModel::Serializer::ErrorSerializer, - adapter: :json_api - ) - expected_response_document = { - errors: [ - { source: { pointer: '/data/attributes/name' }, detail: 'must be awesome' } - ] - } - assert_equal serializable_resource.as_json(options), expected_response_document - end - - def test_serializable_resource_with_collection_containing_errors - options = nil - resources = [] - resources << resource = ModelWithErrors.new - resource.errors.add(:title, 'must be amazing') - resources << ModelWithErrors.new - serializable_resource = SerializableResource.new( - resources, serializer: ActiveModel::Serializer::ErrorsSerializer, - each_serializer: ActiveModel::Serializer::ErrorSerializer, - adapter: :json_api - ) - expected_response_document = { - errors: [ - { source: { pointer: '/data/attributes/title' }, detail: 'must be amazing' } - ] - } - assert_equal serializable_resource.as_json(options), expected_response_document - end - end - end -end diff --git a/test/serializers/association_macros_test.rb b/test/serializers/association_macros_test.rb deleted file mode 100644 index 3d5f05c5..00000000 --- a/test/serializers/association_macros_test.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class AssociationMacrosTest < ActiveSupport::TestCase - class AuthorSummarySerializer < ActiveModel::Serializer; end - - class AssociationsTestSerializer < Serializer - belongs_to :author, serializer: AuthorSummarySerializer - has_many :comments - has_one :category - end - - def before_setup - @reflections = AssociationsTestSerializer._reflections.values - end - - def test_has_one_defines_reflection - has_one_reflection = HasOneReflection.new(:category, {}) - - assert_includes(@reflections, has_one_reflection) - end - - def test_has_many_defines_reflection - has_many_reflection = HasManyReflection.new(:comments, {}) - - assert_includes(@reflections, has_many_reflection) - end - - def test_belongs_to_defines_reflection - belongs_to_reflection = BelongsToReflection.new(:author, serializer: AuthorSummarySerializer) - - assert_includes(@reflections, belongs_to_reflection) - end - end - end -end diff --git a/test/serializers/associations_test.rb b/test/serializers/associations_test.rb deleted file mode 100644 index c1b164b8..00000000 --- a/test/serializers/associations_test.rb +++ /dev/null @@ -1,424 +0,0 @@ -require 'test_helper' -module ActiveModel - class Serializer - class AssociationsTest < ActiveSupport::TestCase - class ModelWithoutSerializer < ::Model - attributes :id, :name - end - - def setup - @author = Author.new(name: 'Steve K.') - @author.bio = nil - @author.roles = [] - @blog = Blog.new(name: 'AMS Blog') - @post = Post.new(title: 'New Post', body: 'Body') - @tag = ModelWithoutSerializer.new(id: 'tagid', name: '#hashtagged') - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post.comments = [@comment] - @post.tags = [@tag] - @post.blog = @blog - @comment.post = @post - @comment.author = nil - @post.author = @author - @author.posts = [@post] - - @post_serializer = PostSerializer.new(@post, custom_options: true) - @author_serializer = AuthorSerializer.new(@author) - @comment_serializer = CommentSerializer.new(@comment) - end - - def test_has_many_and_has_one - @author_serializer.associations.each do |association| - key = association.key - serializer = association.lazy_association.serializer - - case key - when :posts - assert_equal true, association.include_data? - assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer) - when :bio - assert_equal true, association.include_data? - assert_nil serializer - when :roles - assert_equal true, association.include_data? - assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer) - else - flunk "Unknown association: #{key}" - end - end - end - - def test_has_many_with_no_serializer - post_serializer_class = Class.new(ActiveModel::Serializer) do - attributes :id - has_many :tags - end - post_serializer_class.new(@post).associations.each do |association| - key = association.key - serializer = association.lazy_association.serializer - - assert_equal :tags, key - assert_nil serializer - assert_equal [{ id: 'tagid', name: '#hashtagged' }].to_json, association.virtual_value.to_json - end - end - - def test_serializer_options_are_passed_into_associations_serializers - association = @post_serializer - .associations - .detect { |assoc| assoc.key == :comments } - - comment_serializer = association.lazy_association.serializer.first - class << comment_serializer - def custom_options - instance_options - end - end - assert comment_serializer.custom_options.fetch(:custom_options) - end - - def test_belongs_to - @comment_serializer.associations.each do |association| - key = association.key - serializer = association.lazy_association.serializer - - case key - when :post - assert_kind_of(PostSerializer, serializer) - when :author - assert_nil serializer - else - flunk "Unknown association: #{key}" - end - - assert_equal true, association.include_data? - end - end - - def test_belongs_to_with_custom_method - assert( - @post_serializer.associations.any? do |association| - association.key == :blog - end - ) - end - - def test_associations_inheritance - inherited_klass = Class.new(PostSerializer) - - assert_equal(PostSerializer._reflections, inherited_klass._reflections) - end - - def test_associations_inheritance_with_new_association - inherited_klass = Class.new(PostSerializer) do - has_many :top_comments, serializer: CommentSerializer - end - - assert( - PostSerializer._reflections.values.all? do |reflection| - inherited_klass._reflections.values.include?(reflection) - end - ) - - assert( - inherited_klass._reflections.values.any? do |reflection| - reflection.name == :top_comments - end - ) - end - - def test_associations_custom_keys - serializer = PostWithCustomKeysSerializer.new(@post) - - expected_association_keys = serializer.associations.map(&:key) - - assert expected_association_keys.include? :reviews - assert expected_association_keys.include? :writer - assert expected_association_keys.include? :site - end - - class BelongsToBlogModel < ::Model - attributes :id, :title - associations :blog - end - class BelongsToBlogModelSerializer < ActiveModel::Serializer - type :posts - belongs_to :blog - end - - def test_belongs_to_doesnt_load_record - attributes = { id: 1, title: 'Belongs to Blog', blog: Blog.new(id: 5) } - post = BelongsToBlogModel.new(attributes) - class << post - def blog - fail 'should use blog_id' - end - - def blog_id - 5 - end - end - - actual = serializable(post, adapter: :json_api, serializer: BelongsToBlogModelSerializer).as_json - expected = { data: { id: '1', type: 'posts', relationships: { blog: { data: { id: '5', type: 'blogs' } } } } } - - assert_equal expected, actual - end - - class InlineAssociationTestPostSerializer < ActiveModel::Serializer - has_many :comments - has_many :comments, key: :last_comments do - object.comments.last(1) - end - end - - def test_virtual_attribute_block - comment1 = ::ARModels::Comment.create!(contents: 'first comment') - comment2 = ::ARModels::Comment.create!(contents: 'last comment') - post = ::ARModels::Post.create!( - title: 'inline association test', - body: 'etc', - comments: [comment1, comment2] - ) - actual = serializable(post, adapter: :attributes, serializer: InlineAssociationTestPostSerializer).as_json - expected = { - comments: [ - { id: 1, contents: 'first comment' }, - { id: 2, contents: 'last comment' } - ], - last_comments: [ - { id: 2, contents: 'last comment' } - ] - } - - assert_equal expected, actual - ensure - ::ARModels::Post.delete_all - ::ARModels::Comment.delete_all - end - - class NamespacedResourcesTest < ActiveSupport::TestCase - class ResourceNamespace - class Post < ::Model - associations :comments, :author, :description - end - class Comment < ::Model; end - class Author < ::Model; end - class Description < ::Model; end - class PostSerializer < ActiveModel::Serializer - has_many :comments - belongs_to :author - has_one :description - end - class CommentSerializer < ActiveModel::Serializer; end - class AuthorSerializer < ActiveModel::Serializer; end - class DescriptionSerializer < ActiveModel::Serializer; end - end - - def setup - @comment = ResourceNamespace::Comment.new - @author = ResourceNamespace::Author.new - @description = ResourceNamespace::Description.new - @post = ResourceNamespace::Post.new(comments: [@comment], - author: @author, - description: @description) - @post_serializer = ResourceNamespace::PostSerializer.new(@post) - end - - def test_associations_namespaced_resources - @post_serializer.associations.each do |association| - case association.key - when :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 - end - class Comment < ::Model; end - class Author < ::Model; end - class Description < ::Model; end - class PostSerializer < ActiveModel::Serializer - has_many :comments - class CommentSerializer < ActiveModel::Serializer; end - belongs_to :author - class AuthorSerializer < ActiveModel::Serializer; end - has_one :description - 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 = PostSerializer.new(@post) - end - - def test_associations_namespaced_resources - @post_serializer.associations.each do |association| - case association.key - when :comments - assert_instance_of(PostSerializer::CommentSerializer, association.lazy_association.serializer.first) - when :author - assert_instance_of(PostSerializer::AuthorSerializer, association.lazy_association.serializer) - when :description - assert_instance_of(PostSerializer::DescriptionSerializer, association.lazy_association.serializer) - else - flunk "Unknown association: #{key}" - end - end - end - - # rubocop:disable Metrics/AbcSize - def test_conditional_associations - model = Class.new(::Model) do - attributes :true, :false - associations :something - end.new(true: true, false: false) - - scenarios = [ - { options: { if: :true }, included: true }, - { options: { if: :false }, included: false }, - { options: { unless: :false }, included: true }, - { options: { unless: :true }, included: false }, - { options: { if: 'object.true' }, included: true }, - { options: { if: 'object.false' }, included: false }, - { options: { unless: 'object.false' }, included: true }, - { options: { unless: 'object.true' }, included: false }, - { options: { if: -> { object.true } }, included: true }, - { options: { if: -> { object.false } }, included: false }, - { options: { unless: -> { object.false } }, included: true }, - { options: { unless: -> { object.true } }, included: false }, - { options: { if: -> (s) { s.object.true } }, included: true }, - { options: { if: -> (s) { s.object.false } }, included: false }, - { options: { unless: -> (s) { s.object.false } }, included: true }, - { options: { unless: -> (s) { s.object.true } }, included: false } - ] - - scenarios.each do |s| - serializer = Class.new(ActiveModel::Serializer) do - belongs_to :something, s[:options] - - def true - true - end - - def false - false - end - end - - hash = serializable(model, serializer: serializer).serializable_hash - assert_equal(s[:included], hash.key?(:something), "Error with #{s[:options]}") - end - end - - def test_illegal_conditional_associations - exception = assert_raises(TypeError) do - Class.new(ActiveModel::Serializer) do - belongs_to :x, if: nil - end - end - - assert_match(/:if should be a Symbol, String or Proc/, exception.message) - end - end - - class InheritedSerializerTest < ActiveSupport::TestCase - class PostSerializer < ActiveModel::Serializer - belongs_to :author - has_many :comments - belongs_to :blog - end - - class InheritedPostSerializer < PostSerializer - belongs_to :author, polymorphic: true - has_many :comments, key: :reviews - end - - class AuthorSerializer < ActiveModel::Serializer - has_many :posts - has_many :roles - has_one :bio - end - - class InheritedAuthorSerializer < AuthorSerializer - has_many :roles, polymorphic: true - has_one :bio, polymorphic: true - end - - def setup - @author = Author.new(name: 'Steve K.') - @post = Post.new(title: 'New Post', body: 'Body') - @post_serializer = PostSerializer.new(@post) - @author_serializer = AuthorSerializer.new(@author) - @inherited_post_serializer = InheritedPostSerializer.new(@post) - @inherited_author_serializer = InheritedAuthorSerializer.new(@author) - @author_associations = @author_serializer.associations.to_a.sort_by(&:name) - @inherited_author_associations = @inherited_author_serializer.associations.to_a.sort_by(&:name) - @post_associations = @post_serializer.associations.to_a - @inherited_post_associations = @inherited_post_serializer.associations.to_a - end - - test 'an author serializer must have [posts,roles,bio] associations' do - expected = [:posts, :roles, :bio].sort - result = @author_serializer.associations.map(&:name).sort - assert_equal(result, expected) - end - - test 'a post serializer must have [author,comments,blog] associations' do - expected = [:author, :comments, :blog].sort - result = @post_serializer.associations.map(&:name).sort - assert_equal(result, expected) - end - - test 'a serializer inheriting from another serializer can redefine has_many and has_one associations' do - expected = [:roles, :bio].sort - result = (@inherited_author_associations.map(&:reflection) - @author_associations.map(&:reflection)).map(&:name) - assert_equal(result, expected) - assert_equal [true, false, true], @inherited_author_associations.map(&:polymorphic?) - assert_equal [false, false, false], @author_associations.map(&:polymorphic?) - end - - test 'a serializer inheriting from another serializer can redefine belongs_to associations' do - assert_equal [:author, :comments, :blog], @post_associations.map(&:name) - assert_equal [:author, :comments, :blog, :comments], @inherited_post_associations.map(&:name) - - refute @post_associations.detect { |assoc| assoc.name == :author }.polymorphic? - assert @inherited_post_associations.detect { |assoc| assoc.name == :author }.polymorphic? - - refute @post_associations.detect { |assoc| assoc.name == :comments }.key? - original_comment_assoc, new_comments_assoc = @inherited_post_associations.select { |assoc| assoc.name == :comments } - refute original_comment_assoc.key? - assert_equal :reviews, new_comments_assoc.key - - original_blog = @post_associations.detect { |assoc| assoc.name == :blog } - inherited_blog = @inherited_post_associations.detect { |assoc| assoc.name == :blog } - original_parent_serializer = original_blog.lazy_association.association_options.delete(:parent_serializer) - inherited_parent_serializer = inherited_blog.lazy_association.association_options.delete(:parent_serializer) - assert_equal PostSerializer, original_parent_serializer.class - assert_equal InheritedPostSerializer, inherited_parent_serializer.class - end - - test 'a serializer inheriting from another serializer can have an additional association with the same name but with different key' do - expected = [:author, :comments, :blog, :reviews].sort - result = @inherited_post_serializer.associations.map(&:key).sort - assert_equal(result, expected) - end - end - end - end -end diff --git a/test/serializers/attribute_test.rb b/test/serializers/attribute_test.rb deleted file mode 100644 index 608898c3..00000000 --- a/test/serializers/attribute_test.rb +++ /dev/null @@ -1,153 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class AttributeTest < ActiveSupport::TestCase - def setup - @blog = Blog.new(id: 1, name: 'AMS Hints', type: 'stuff') - @blog_serializer = AlternateBlogSerializer.new(@blog) - end - - def test_attributes_definition - assert_equal([:id, :title], - @blog_serializer.class._attributes) - end - - def test_json_serializable_hash - adapter = ActiveModelSerializers::Adapter::Json.new(@blog_serializer) - assert_equal({ blog: { id: 1, title: 'AMS Hints' } }, adapter.serializable_hash) - end - - def test_attribute_inheritance_with_key - inherited_klass = Class.new(AlternateBlogSerializer) - blog_serializer = inherited_klass.new(@blog) - adapter = ActiveModelSerializers::Adapter::Attributes.new(blog_serializer) - assert_equal({ id: 1, title: 'AMS Hints' }, adapter.serializable_hash) - end - - def test_multiple_calls_with_the_same_attribute - serializer_class = Class.new(ActiveModel::Serializer) do - attribute :title - attribute :title - end - - assert_equal([:title], serializer_class._attributes) - end - - def test_id_attribute_override - serializer = Class.new(ActiveModel::Serializer) do - attribute :name, key: :id - end - - adapter = ActiveModelSerializers::Adapter::Json.new(serializer.new(@blog)) - assert_equal({ blog: { id: 'AMS Hints' } }, adapter.serializable_hash) - end - - def test_object_attribute_override - serializer = Class.new(ActiveModel::Serializer) do - attribute :name, key: :object - end - - adapter = ActiveModelSerializers::Adapter::Json.new(serializer.new(@blog)) - assert_equal({ blog: { object: 'AMS Hints' } }, adapter.serializable_hash) - end - - def test_type_attribute - attribute_serializer = Class.new(ActiveModel::Serializer) do - attribute :id, key: :type - end - attributes_serializer = Class.new(ActiveModel::Serializer) do - attributes :type - end - - adapter = ActiveModelSerializers::Adapter::Json.new(attribute_serializer.new(@blog)) - assert_equal({ blog: { type: 1 } }, adapter.serializable_hash) - - adapter = ActiveModelSerializers::Adapter::Json.new(attributes_serializer.new(@blog)) - assert_equal({ blog: { type: 'stuff' } }, adapter.serializable_hash) - end - - def test_id_attribute_override_before - serializer = Class.new(ActiveModel::Serializer) do - def id - 'custom' - end - - attribute :id - end - - hash = ActiveModelSerializers::SerializableResource.new(@blog, adapter: :json, serializer: serializer).serializable_hash - - assert_equal('custom', hash[:blog][:id]) - end - - class PostWithVirtualAttribute < ::Model; attributes :first_name, :last_name end - class PostWithVirtualAttributeSerializer < ActiveModel::Serializer - attribute :name do - "#{object.first_name} #{object.last_name}" - end - end - - def test_virtual_attribute_block - post = PostWithVirtualAttribute.new(first_name: 'Lucas', last_name: 'Hosseini') - hash = serializable(post).serializable_hash - expected = { name: 'Lucas Hosseini' } - - assert_equal(expected, hash) - end - - # rubocop:disable Metrics/AbcSize - def test_conditional_associations - model = Class.new(::Model) do - attributes :true, :false, :attribute - end.new(true: true, false: false) - - scenarios = [ - { options: { if: :true }, included: true }, - { options: { if: :false }, included: false }, - { options: { unless: :false }, included: true }, - { options: { unless: :true }, included: false }, - { options: { if: 'object.true' }, included: true }, - { options: { if: 'object.false' }, included: false }, - { options: { unless: 'object.false' }, included: true }, - { options: { unless: 'object.true' }, included: false }, - { options: { if: -> { object.true } }, included: true }, - { options: { if: -> { object.false } }, included: false }, - { options: { unless: -> { object.false } }, included: true }, - { options: { unless: -> { object.true } }, included: false }, - { options: { if: -> (s) { s.object.true } }, included: true }, - { options: { if: -> (s) { s.object.false } }, included: false }, - { options: { unless: -> (s) { s.object.false } }, included: true }, - { options: { unless: -> (s) { s.object.true } }, included: false } - ] - - scenarios.each do |s| - serializer = Class.new(ActiveModel::Serializer) do - attribute :attribute, s[:options] - - def true - true - end - - def false - false - end - end - - hash = serializable(model, serializer: serializer).serializable_hash - assert_equal(s[:included], hash.key?(:attribute), "Error with #{s[:options]}") - end - end - - def test_illegal_conditional_attributes - exception = assert_raises(TypeError) do - Class.new(ActiveModel::Serializer) do - attribute :x, if: nil - end - end - - assert_match(/:if should be a Symbol, String or Proc/, exception.message) - end - end - end -end diff --git a/test/serializers/attributes_test.rb b/test/serializers/attributes_test.rb deleted file mode 100644 index fb792b26..00000000 --- a/test/serializers/attributes_test.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class AttributesTest < ActiveSupport::TestCase - def setup - @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') - @profile_serializer = ProfileSerializer.new(@profile) - @comment = Comment.new(id: 1, body: 'ZOMG!!', date: '2015') - @serializer_klass = Class.new(CommentSerializer) - @serializer_klass_with_new_attributes = Class.new(CommentSerializer) do - attributes :date, :likes - end - end - - def test_attributes_definition - assert_equal([:name, :description], - @profile_serializer.class._attributes) - end - - def test_attributes_inheritance_definition - assert_equal([:id, :body], @serializer_klass._attributes) - end - - def test_attributes_inheritance - serializer = @serializer_klass.new(@comment) - assert_equal({ id: 1, body: 'ZOMG!!' }, - serializer.attributes) - end - - def test_attribute_inheritance_with_new_attribute_definition - assert_equal([:id, :body, :date, :likes], @serializer_klass_with_new_attributes._attributes) - assert_equal([:id, :body], CommentSerializer._attributes) - end - - def test_attribute_inheritance_with_new_attribute - serializer = @serializer_klass_with_new_attributes.new(@comment) - assert_equal({ id: 1, body: 'ZOMG!!', date: '2015', likes: nil }, - serializer.attributes) - end - - def test_multiple_calls_with_the_same_attribute - serializer_class = Class.new(ActiveModel::Serializer) do - attributes :id, :title - attributes :id, :title, :title, :body - end - - assert_equal([:id, :title, :body], serializer_class._attributes) - end - end - end -end diff --git a/test/serializers/caching_configuration_test_isolated.rb b/test/serializers/caching_configuration_test_isolated.rb deleted file mode 100644 index b5698dd1..00000000 --- a/test/serializers/caching_configuration_test_isolated.rb +++ /dev/null @@ -1,170 +0,0 @@ -# Execute this test in isolation -require 'support/isolated_unit' - -class CachingConfigurationTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation - - setup do - require 'rails' - # AMS needs to be required before Rails.application is initialized for - # Railtie's to fire in Rails.application.initialize! - # (and make_basic_app initializes the app) - require 'active_model_serializers' - # Create serializers before Rails.application.initialize! - # To ensure we're testing that the cache settings depend on - # the Railtie firing, not on the ActionController being loaded. - create_serializers - end - - def create_serializers - @cached_serializer = Class.new(ActiveModel::Serializer) do - cache skip_digest: true - attributes :id, :name, :title - end - @fragment_cached_serializer = Class.new(ActiveModel::Serializer) do - cache only: :id - attributes :id, :name, :title - end - @non_cached_serializer = Class.new(ActiveModel::Serializer) do - attributes :id, :name, :title - end - end - - class PerformCachingTrue < CachingConfigurationTest - setup do - # Let's make that Rails app and initialize it! - make_basic_app do |app| - app.config.action_controller.perform_caching = true - app.config.action_controller.cache_store = ActiveSupport::Cache.lookup_store(:memory_store) - end - controller_cache_store # Force ActiveSupport.on_load(:action_controller) to run - end - - test 'it sets perform_caching to true on AMS.config and serializers' do - assert Rails.configuration.action_controller.perform_caching - assert ActiveModelSerializers.config.perform_caching - assert ActiveModel::Serializer.perform_caching? - assert @cached_serializer.perform_caching? - assert @non_cached_serializer.perform_caching? - assert @fragment_cached_serializer.perform_caching? - end - - test 'it sets the AMS.config.cache_store to the controller cache_store' do - assert_equal controller_cache_store, ActiveSupport::Cache::MemoryStore - assert_equal controller_cache_store, ActiveModelSerializers.config.cache_store.class - end - - test 'it sets the cached serializer cache_store to the ActionController::Base.cache_store' do - assert_equal ActiveSupport::Cache::NullStore, @cached_serializer._cache.class - assert_equal controller_cache_store, @cached_serializer.cache_store.class - assert_equal ActiveSupport::Cache::MemoryStore, @cached_serializer._cache.class - end - - test 'the cached serializer has cache_enabled?' do - assert @cached_serializer.cache_enabled? - end - - test 'the cached serializer does not have fragment_cache_enabled?' do - refute @cached_serializer.fragment_cache_enabled? - end - - test 'the non-cached serializer cache_store is nil' do - assert_nil @non_cached_serializer._cache - assert_nil @non_cached_serializer.cache_store - assert_nil @non_cached_serializer._cache - end - - test 'the non-cached serializer does not have cache_enabled?' do - refute @non_cached_serializer.cache_enabled? - end - - test 'the non-cached serializer does not have fragment_cache_enabled?' do - refute @non_cached_serializer.fragment_cache_enabled? - end - - test 'it sets the fragment cached serializer cache_store to the ActionController::Base.cache_store' do - assert_equal ActiveSupport::Cache::NullStore, @fragment_cached_serializer._cache.class - assert_equal controller_cache_store, @fragment_cached_serializer.cache_store.class - assert_equal ActiveSupport::Cache::MemoryStore, @fragment_cached_serializer._cache.class - end - - test 'the fragment cached serializer does not have cache_enabled?' do - refute @fragment_cached_serializer.cache_enabled? - end - - test 'the fragment cached serializer has fragment_cache_enabled?' do - assert @fragment_cached_serializer.fragment_cache_enabled? - end - end - - class PerformCachingFalse < CachingConfigurationTest - setup do - # Let's make that Rails app and initialize it! - make_basic_app do |app| - app.config.action_controller.perform_caching = false - app.config.action_controller.cache_store = ActiveSupport::Cache.lookup_store(:memory_store) - end - controller_cache_store # Force ActiveSupport.on_load(:action_controller) to run - end - - test 'it sets perform_caching to false on AMS.config and serializers' do - refute Rails.configuration.action_controller.perform_caching - refute ActiveModelSerializers.config.perform_caching - refute ActiveModel::Serializer.perform_caching? - refute @cached_serializer.perform_caching? - refute @non_cached_serializer.perform_caching? - refute @fragment_cached_serializer.perform_caching? - end - - test 'it sets the AMS.config.cache_store to the controller cache_store' do - assert_equal controller_cache_store, ActiveSupport::Cache::MemoryStore - assert_equal controller_cache_store, ActiveModelSerializers.config.cache_store.class - end - - test 'it sets the cached serializer cache_store to the ActionController::Base.cache_store' do - assert_equal ActiveSupport::Cache::NullStore, @cached_serializer._cache.class - assert_equal controller_cache_store, @cached_serializer.cache_store.class - assert_equal ActiveSupport::Cache::MemoryStore, @cached_serializer._cache.class - end - - test 'the cached serializer does not have cache_enabled?' do - refute @cached_serializer.cache_enabled? - end - - test 'the cached serializer does not have fragment_cache_enabled?' do - refute @cached_serializer.fragment_cache_enabled? - end - - test 'the non-cached serializer cache_store is nil' do - assert_nil @non_cached_serializer._cache - assert_nil @non_cached_serializer.cache_store - assert_nil @non_cached_serializer._cache - end - - test 'the non-cached serializer does not have cache_enabled?' do - refute @non_cached_serializer.cache_enabled? - end - - test 'the non-cached serializer does not have fragment_cache_enabled?' do - refute @non_cached_serializer.fragment_cache_enabled? - end - - test 'it sets the fragment cached serializer cache_store to the ActionController::Base.cache_store' do - assert_equal ActiveSupport::Cache::NullStore, @fragment_cached_serializer._cache.class - assert_equal controller_cache_store, @fragment_cached_serializer.cache_store.class - assert_equal ActiveSupport::Cache::MemoryStore, @fragment_cached_serializer._cache.class - end - - test 'the fragment cached serializer does not have cache_enabled?' do - refute @fragment_cached_serializer.cache_enabled? - end - - test 'the fragment cached serializer does not have fragment_cache_enabled?' do - refute @fragment_cached_serializer.fragment_cache_enabled? - end - end - - def controller_cache_store - ActionController::Base.cache_store.class - end -end diff --git a/test/serializers/configuration_test.rb b/test/serializers/configuration_test.rb deleted file mode 100644 index 2c5f922f..00000000 --- a/test/serializers/configuration_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class ConfigurationTest < ActiveSupport::TestCase - def test_collection_serializer - assert_equal ActiveModel::Serializer::CollectionSerializer, ActiveModelSerializers.config.collection_serializer - end - - def test_array_serializer - assert_equal ActiveModel::Serializer::CollectionSerializer, ActiveModelSerializers.config.array_serializer - end - - def test_setting_array_serializer_sets_collection_serializer - config = ActiveModelSerializers.config - old_config = config.dup - begin - assert_equal ActiveModel::Serializer::CollectionSerializer, config.collection_serializer - config.array_serializer = :foo - assert_equal config.array_serializer, :foo - assert_equal config.collection_serializer, :foo - ensure - ActiveModelSerializers.config.replace(old_config) - end - end - - def test_default_adapter - assert_equal :attributes, ActiveModelSerializers.config.adapter - end - end - end -end diff --git a/test/serializers/fieldset_test.rb b/test/serializers/fieldset_test.rb deleted file mode 100644 index 5b99d57a..00000000 --- a/test/serializers/fieldset_test.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class FieldsetTest < ActiveSupport::TestCase - 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) - end - end - end -end diff --git a/test/serializers/meta_test.rb b/test/serializers/meta_test.rb deleted file mode 100644 index 64209321..00000000 --- a/test/serializers/meta_test.rb +++ /dev/null @@ -1,202 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class MetaTest < ActiveSupport::TestCase - def setup - @blog = Blog.new(id: 1, - name: 'AMS Hints', - writer: Author.new(id: 2, name: 'Steve'), - articles: [Post.new(id: 3, title: 'AMS')]) - end - - def test_meta_is_present_with_root - actual = ActiveModelSerializers::SerializableResource.new( - @blog, - adapter: :json, - serializer: AlternateBlogSerializer, - meta: { total: 10 } - ).as_json - expected = { - blog: { - id: 1, - title: 'AMS Hints' - }, - 'meta' => { - total: 10 - } - } - assert_equal(expected, actual) - end - - def test_meta_is_not_included_when_blank - actual = ActiveModelSerializers::SerializableResource.new( - @blog, - adapter: :json, - serializer: AlternateBlogSerializer, - meta: {} - ).as_json - expected = { - blog: { - id: 1, - title: 'AMS Hints' - } - } - assert_equal(expected, actual) - end - - def test_meta_is_not_included_when_empty_string - actual = ActiveModelSerializers::SerializableResource.new( - @blog, - adapter: :json, - serializer: AlternateBlogSerializer, - meta: '' - ).as_json - expected = { - blog: { - id: 1, - title: 'AMS Hints' - } - } - assert_equal(expected, actual) - end - - def test_meta_is_not_included_when_root_is_missing - actual = ActiveModelSerializers::SerializableResource.new( - @blog, - adapter: :attributes, - serializer: AlternateBlogSerializer, - meta: { total: 10 } - ).as_json - expected = { - id: 1, - title: 'AMS Hints' - } - assert_equal(expected, actual) - end - - def test_meta_key_is_used - actual = ActiveModelSerializers::SerializableResource.new( - @blog, - adapter: :json, - serializer: AlternateBlogSerializer, - meta: { total: 10 }, - meta_key: 'haha_meta' - ).as_json - expected = { - blog: { - id: 1, - title: 'AMS Hints' - }, - 'haha_meta' => { - total: 10 - } - } - assert_equal(expected, actual) - end - - def test_meta_key_is_not_used_with_json_api - actual = ActiveModelSerializers::SerializableResource.new( - @blog, - adapter: :json_api, - serializer: AlternateBlogSerializer, - meta: { total: 10 }, - meta_key: 'haha_meta' - ).as_json - expected = { - data: { - id: '1', - type: 'blogs', - attributes: { title: 'AMS Hints' } - }, - meta: { total: 10 } - } - assert_equal(expected, actual) - end - - def test_meta_key_is_not_present_when_empty_hash_with_json_api - actual = ActiveModelSerializers::SerializableResource.new( - @blog, - adapter: :json_api, - serializer: AlternateBlogSerializer, - meta: {} - ).as_json - expected = { - data: { - id: '1', - type: 'blogs', - attributes: { title: 'AMS Hints' } - } - } - assert_equal(expected, actual) - end - - def test_meta_key_is_not_present_when_empty_string_with_json_api - actual = ActiveModelSerializers::SerializableResource.new( - @blog, - adapter: :json_api, - serializer: AlternateBlogSerializer, - meta: '' - ).as_json - expected = { - data: { - id: '1', - type: 'blogs', - attributes: { title: 'AMS Hints' } - } - } - assert_equal(expected, actual) - end - - def test_meta_is_not_present_on_arrays_without_root - actual = ActiveModelSerializers::SerializableResource.new( - [@blog], - adapter: :attributes, - meta: { total: 10 } - ).as_json - expected = [{ - id: 1, - name: 'AMS Hints', - writer: { - id: 2, - name: 'Steve' - }, - articles: [{ - id: 3, - title: 'AMS', - body: nil - }] - }] - assert_equal(expected, actual) - end - - def test_meta_is_present_on_arrays_with_root - actual = ActiveModelSerializers::SerializableResource.new( - [@blog], - adapter: :json, - meta: { total: 10 }, - meta_key: 'haha_meta' - ).as_json - expected = { - blogs: [{ - id: 1, - name: 'AMS Hints', - writer: { - id: 2, - name: 'Steve' - }, - articles: [{ - id: 3, - title: 'AMS', - body: nil - }] - }], - 'haha_meta' => { - total: 10 - } - } - assert_equal(expected, actual) - end - end - end -end diff --git a/test/serializers/options_test.rb b/test/serializers/options_test.rb deleted file mode 100644 index 009388e3..00000000 --- a/test/serializers/options_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class OptionsTest < ActiveSupport::TestCase - class ModelWithOptions < ActiveModelSerializers::Model - attributes :name, :description - end - class ModelWithOptionsSerializer < ActiveModel::Serializer - attributes :name, :description - - def arguments_passed_in? - instance_options[:my_options] == :accessible - end - end - - setup do - @model_with_options = ModelWithOptions.new(name: 'Name 1', description: 'Description 1') - end - - def test_options_are_accessible - model_with_options_serializer = ModelWithOptionsSerializer.new(@model_with_options, my_options: :accessible) - assert model_with_options_serializer.arguments_passed_in? - end - - def test_no_option_is_passed_in - model_with_options_serializer = ModelWithOptionsSerializer.new(@model_with_options) - refute model_with_options_serializer.arguments_passed_in? - end - end - end -end diff --git a/test/serializers/read_attribute_for_serialization_test.rb b/test/serializers/read_attribute_for_serialization_test.rb deleted file mode 100644 index 02911c0e..00000000 --- a/test/serializers/read_attribute_for_serialization_test.rb +++ /dev/null @@ -1,79 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class ReadAttributeForSerializationTest < ActiveSupport::TestCase - # https://github.com/rails-api/active_model_serializers/issues/1653 - class Parent < ActiveModelSerializers::Model - attributes :id - end - class Child < Parent - attributes :name - end - class ParentSerializer < ActiveModel::Serializer - attributes :$id - - define_method(:$id) do - object.id - end - end - class ChildSerializer < ParentSerializer - attributes :name - end - - def test_child_serializer_calls_dynamic_method_in_parent_serializer - parent = ParentSerializer.new(Parent.new(id: 5)) - child = ChildSerializer.new(Child.new(id: 6, name: 'Child')) - assert_equal 5, parent.read_attribute_for_serialization(:$id) - assert_equal 6, child.read_attribute_for_serialization(:$id) - end - - # https://github.com/rails-api/active_model_serializers/issues/1658 - class ErrorResponse < ActiveModelSerializers::Model - attributes :error - end - class ApplicationSerializer < ActiveModel::Serializer - attributes :status - - def status - object.try(:errors).blank? && object.try(:error).blank? - end - end - class ErrorResponseSerializer < ApplicationSerializer - attributes :error - end - class ErrorResponseWithSuperSerializer < ApplicationSerializer - attributes :error - - def success - super - end - end - - def test_child_serializer_with_error_attribute - error = ErrorResponse.new(error: 'i have an error') - serializer = ErrorResponseSerializer.new(error) - serializer_with_super = ErrorResponseWithSuperSerializer.new(error) - assert_equal false, serializer.read_attribute_for_serialization(:status) - assert_equal false, serializer_with_super.read_attribute_for_serialization(:status) - end - - def test_child_serializer_with_errors - error = ErrorResponse.new - error.errors.add(:invalid, 'i am not valid') - serializer = ErrorResponseSerializer.new(error) - serializer_with_super = ErrorResponseWithSuperSerializer.new(error) - assert_equal false, serializer.read_attribute_for_serialization(:status) - assert_equal false, serializer_with_super.read_attribute_for_serialization(:status) - end - - def test_child_serializer_no_error_attribute_or_errors - error = ErrorResponse.new - serializer = ErrorResponseSerializer.new(error) - serializer_with_super = ErrorResponseWithSuperSerializer.new(error) - assert_equal true, serializer.read_attribute_for_serialization(:status) - assert_equal true, serializer_with_super.read_attribute_for_serialization(:status) - end - end - end -end diff --git a/test/serializers/reflection_test.rb b/test/serializers/reflection_test.rb deleted file mode 100644 index 11cb154b..00000000 --- a/test/serializers/reflection_test.rb +++ /dev/null @@ -1,427 +0,0 @@ -require 'test_helper' -module ActiveModel - class Serializer - class ReflectionTest < ActiveSupport::TestCase - class Blog < ActiveModelSerializers::Model - attributes :id - end - class BlogSerializer < ActiveModel::Serializer - type 'blog' - attributes :id - end - - setup do - @expected_meta = { id: 1 } - @expected_links = { self: 'no_uri_validation' } - @empty_links = {} - model_attributes = { blog: Blog.new(@expected_meta) } - @model = Class.new(ActiveModelSerializers::Model) do - attributes(*model_attributes.keys) - - def self.name - 'TestModel' - end - end.new(model_attributes) - @instance_options = {} - end - - def evaluate_association_value(association) - association.lazy_association.eval_reflection_block - end - - # TODO: Remaining tests - # test_reflection_value_block_with_scope - # test_reflection_value_uses_serializer_instance_method - # test_reflection_excluded_eh_blank_is_false - # test_reflection_excluded_eh_if - # test_reflection_excluded_eh_unless - # test_evaluate_condition_symbol_serializer_method - # test_evaluate_condition_string_serializer_method - # test_evaluate_condition_proc - # test_evaluate_condition_proc_yields_serializer - # test_evaluate_condition_other - # test_options_key - # test_options_polymorphic - # test_options_serializer - # test_options_virtual_value - # test_options_namespace - - def test_reflection_value - serializer_class = Class.new(ActiveModel::Serializer) do - has_one :blog - end - serializer_instance = serializer_class.new(@model, @instance_options) - - # Get Reflection - reflection = serializer_class._reflections.fetch(:blog) - - # Assert - assert_nil reflection.block - assert_equal Serializer.config.include_data_default, reflection.options.fetch(:include_data_setting) - assert_equal true, reflection.options.fetch(:include_data_setting) - - include_slice = :does_not_matter - assert_equal @model.blog, reflection.send(:value, serializer_instance, include_slice) - end - - def test_reflection_value_block - serializer_class = Class.new(ActiveModel::Serializer) do - has_one :blog do - object.blog - end - end - serializer_instance = serializer_class.new(@model, @instance_options) - - # Get Reflection - reflection = serializer_class._reflections.fetch(:blog) - - # Assert - assert_respond_to reflection.block, :call - assert_equal Serializer.config.include_data_default, reflection.options.fetch(:include_data_setting) - assert_equal true, reflection.options.fetch(:include_data_setting) - - include_slice = :does_not_matter - assert_equal @model.blog, reflection.send(:value, serializer_instance, include_slice) - end - - def test_reflection_value_block_with_explicit_include_data_true - serializer_class = Class.new(ActiveModel::Serializer) do - has_one :blog do - include_data true - object.blog - end - end - serializer_instance = serializer_class.new(@model, @instance_options) - - # Get Reflection - reflection = serializer_class._reflections.fetch(:blog) - - # Assert - assert_respond_to reflection.block, :call - assert_equal Serializer.config.include_data_default, reflection.options.fetch(:include_data_setting) - assert_equal true, reflection.options.fetch(:include_data_setting) - - include_slice = :does_not_matter - assert_equal @model.blog, reflection.send(:value, serializer_instance, include_slice) - end - - def test_reflection_value_block_with_include_data_false_mutates_the_reflection_include_data - serializer_class = Class.new(ActiveModel::Serializer) do - has_one :blog do - include_data false - object.blog - end - end - serializer_instance = serializer_class.new(@model, @instance_options) - - # Get Reflection - reflection = serializer_class._reflections.fetch(:blog) - - # Assert - assert_respond_to reflection.block, :call - assert_equal true, reflection.options.fetch(:include_data_setting) - include_slice = :does_not_matter - assert_nil reflection.send(:value, serializer_instance, include_slice) - assert_equal false, reflection.options.fetch(:include_data_setting) - end - - def test_reflection_value_block_with_include_data_if_sideloaded_included_mutates_the_reflection_include_data - serializer_class = Class.new(ActiveModel::Serializer) do - has_one :blog do - include_data :if_sideloaded - object.blog - end - end - serializer_instance = serializer_class.new(@model, @instance_options) - - # Get Reflection - reflection = serializer_class._reflections.fetch(:blog) - - # Assert - assert_respond_to reflection.block, :call - assert_equal true, reflection.options.fetch(:include_data_setting) - include_slice = {} - assert_nil reflection.send(:value, serializer_instance, include_slice) - assert_equal :if_sideloaded, reflection.options.fetch(:include_data_setting) - end - - def test_reflection_value_block_with_include_data_if_sideloaded_excluded_mutates_the_reflection_include_data - serializer_class = Class.new(ActiveModel::Serializer) do - has_one :blog do - include_data :if_sideloaded - object.blog - end - end - serializer_instance = serializer_class.new(@model, @instance_options) - - # Get Reflection - reflection = serializer_class._reflections.fetch(:blog) - - # Assert - assert_respond_to reflection.block, :call - assert_equal true, reflection.options.fetch(:include_data_setting) - include_slice = { blog: :does_not_matter } - assert_equal @model.blog, reflection.send(:value, serializer_instance, include_slice) - assert_equal :if_sideloaded, reflection.options.fetch(:include_data_setting) - end - - def test_reflection_block_with_link_mutates_the_reflection_links - serializer_class = Class.new(ActiveModel::Serializer) do - has_one :blog do - link :self, 'no_uri_validation' - end - end - serializer_instance = serializer_class.new(@model, @instance_options) - - # Get Reflection - reflection = serializer_class._reflections.fetch(:blog) - assert_equal @empty_links, reflection.options.fetch(:links) - - # Build Association - association = reflection.build_association(serializer_instance, @instance_options) - - # Assert association links empty when not yet evaluated - assert_equal @empty_links, reflection.options.fetch(:links) - assert_equal @empty_links, association.links - - evaluate_association_value(association) - - assert_equal @expected_links, association.links - assert_equal @expected_links, reflection.options.fetch(:links) - end - - def test_reflection_block_with_link_block_mutates_the_reflection_links - serializer_class = Class.new(ActiveModel::Serializer) do - has_one :blog do - link :self do - 'no_uri_validation' - end - end - end - serializer_instance = serializer_class.new(@model, @instance_options) - - # Get Reflection - reflection = serializer_class._reflections.fetch(:blog) - assert_equal @empty_links, reflection.options.fetch(:links) - - # Build Association - association = reflection.build_association(serializer_instance, @instance_options) - - # Assert association links empty when not yet evaluated - assert_equal @empty_links, association.links - - evaluate_association_value(association) - - # Assert before instance_eval link - link = association.links.fetch(:self) - assert_respond_to link, :call - assert_respond_to reflection.options.fetch(:links).fetch(:self), :call - - # Assert after instance_eval link - assert_equal @expected_links.fetch(:self), reflection.instance_eval(&link) - assert_respond_to reflection.options.fetch(:links).fetch(:self), :call - end - - def test_reflection_block_with_meta_mutates_the_reflection_meta - serializer_class = Class.new(ActiveModel::Serializer) do - has_one :blog do - meta(id: object.blog.id) - end - end - serializer_instance = serializer_class.new(@model, @instance_options) - - # Get Reflection - reflection = serializer_class._reflections.fetch(:blog) - assert_nil reflection.options.fetch(:meta) - - # Build Association - association = reflection.build_association(serializer_instance, @instance_options) - - evaluate_association_value(association) - - assert_equal @expected_meta, association.meta - assert_equal @expected_meta, reflection.options.fetch(:meta) - end - - def test_reflection_block_with_meta_block_mutates_the_reflection_meta - serializer_class = Class.new(ActiveModel::Serializer) do - has_one :blog do - meta do - { id: object.blog.id } - end - end - end - serializer_instance = serializer_class.new(@model, @instance_options) - - # Get Reflection - reflection = serializer_class._reflections.fetch(:blog) - assert_nil reflection.options.fetch(:meta) - - # Build Association - association = reflection.build_association(serializer_instance, @instance_options) - # Assert before instance_eval meta - - evaluate_association_value(association) - - assert_respond_to association.meta, :call - assert_respond_to reflection.options.fetch(:meta), :call - - # Assert after instance_eval meta - assert_equal @expected_meta, reflection.instance_eval(&association.meta) - assert_respond_to reflection.options.fetch(:meta), :call - assert_respond_to association.meta, :call - end - - # rubocop:disable Metrics/AbcSize - def test_reflection_block_with_meta_in_link_block_mutates_the_reflection_meta - serializer_class = Class.new(ActiveModel::Serializer) do - has_one :blog do - link :self do - meta(id: object.blog.id) - 'no_uri_validation' - end - end - end - serializer_instance = serializer_class.new(@model, @instance_options) - - # Get Reflection - reflection = serializer_class._reflections.fetch(:blog) - assert_nil reflection.options.fetch(:meta) - assert_equal @empty_links, reflection.options.fetch(:links) - - # Build Association - association = reflection.build_association(serializer_instance, @instance_options) - # Assert before instance_eval link meta - assert_nil association.meta - assert_nil reflection.options.fetch(:meta) - - evaluate_association_value(association) - - link = association.links.fetch(:self) - assert_respond_to link, :call - assert_respond_to reflection.options.fetch(:links).fetch(:self), :call - assert_nil reflection.options.fetch(:meta) - - # Assert after instance_eval link - assert_equal 'no_uri_validation', reflection.instance_eval(&link) - assert_equal @expected_meta, reflection.options.fetch(:meta) - assert_equal @expected_meta, association.meta - end - # rubocop:enable Metrics/AbcSize - - # rubocop:disable Metrics/AbcSize - def test_reflection_block_with_meta_block_in_link_block_mutates_the_reflection_meta - serializer_class = Class.new(ActiveModel::Serializer) do - has_one :blog do - link :self do - meta do - { id: object.blog.id } - end - 'no_uri_validation' - end - end - end - serializer_instance = serializer_class.new(@model, @instance_options) - - # Get Reflection - reflection = serializer_class._reflections.fetch(:blog) - assert_nil reflection.options.fetch(:meta) - - # Build Association - association = reflection.build_association(serializer_instance, @instance_options) - assert_nil association.meta - assert_nil reflection.options.fetch(:meta) - - # Assert before instance_eval link - - evaluate_association_value(association) - - link = association.links.fetch(:self) - assert_nil reflection.options.fetch(:meta) - assert_respond_to link, :call - assert_respond_to association.links.fetch(:self), :call - - # Assert after instance_eval link - assert_equal 'no_uri_validation', reflection.instance_eval(&link) - assert_respond_to association.links.fetch(:self), :call - # Assert before instance_eval link meta - assert_respond_to reflection.options.fetch(:meta), :call - assert_respond_to association.meta, :call - - # Assert after instance_eval link meta - assert_equal @expected_meta, reflection.instance_eval(&reflection.options.fetch(:meta)) - assert_respond_to association.meta, :call - end - # rubocop:enable Metrics/AbcSize - - def test_no_href_in_vanilla_reflection - serializer_class = Class.new(ActiveModel::Serializer) do - has_one :blog do - link :self do - href 'no_uri_validation' - end - end - end - serializer_instance = serializer_class.new(@model, @instance_options) - - # Get Reflection - reflection = serializer_class._reflections.fetch(:blog) - assert_equal @empty_links, reflection.options.fetch(:links) - - # Build Association - association = reflection.build_association(serializer_instance, @instance_options) - # Assert before instance_eval link - - evaluate_association_value(association) - - link = association.links.fetch(:self) - assert_respond_to link, :call - - # Assert after instance_eval link - exception = assert_raise(NoMethodError) do - reflection.instance_eval(&link) - end - assert_match(/undefined method `href'/, exception.message) - end - - # rubocop:disable Metrics/AbcSize - def test_mutating_reflection_block_is_not_thread_safe - serializer_class = Class.new(ActiveModel::Serializer) do - has_one :blog do - meta(id: object.blog.id) - end - end - model1_meta = @expected_meta - # Evaluate reflection meta for model with id 1 - serializer_instance = serializer_class.new(@model, @instance_options) - reflection = serializer_class._reflections.fetch(:blog) - assert_nil reflection.options.fetch(:meta) - association = reflection.build_association(serializer_instance, @instance_options) - - evaluate_association_value(association) - - assert_equal model1_meta, association.meta - assert_equal model1_meta, reflection.options.fetch(:meta) - - model2_meta = @expected_meta.merge(id: 2) - # Evaluate reflection meta for model with id 2 - @model.blog.id = 2 - assert_equal 2, @model.blog.id # sanity check - serializer_instance = serializer_class.new(@model, @instance_options) - reflection = serializer_class._reflections.fetch(:blog) - - # WARN: Thread-safety issue - # Before the reflection is evaluated, it has the value from the previous evaluation - assert_equal model1_meta, reflection.options.fetch(:meta) - - association = reflection.build_association(serializer_instance, @instance_options) - - evaluate_association_value(association) - - assert_equal model2_meta, association.meta - assert_equal model2_meta, reflection.options.fetch(:meta) - end - # rubocop:enable Metrics/AbcSize - end - end -end diff --git a/test/serializers/root_test.rb b/test/serializers/root_test.rb deleted file mode 100644 index 5bd4cdc3..00000000 --- a/test/serializers/root_test.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class RootTest < ActiveSupport::TestCase - def setup - @virtual_value = VirtualValue.new(id: 1) - end - - def test_overwrite_root - serializer = VirtualValueSerializer.new(@virtual_value, root: 'smth') - assert_equal('smth', serializer.json_key) - end - - def test_underscore_in_root - serializer = VirtualValueSerializer.new(@virtual_value) - assert_equal('virtual_value', serializer.json_key) - end - end - end -end diff --git a/test/serializers/serialization_test.rb b/test/serializers/serialization_test.rb deleted file mode 100644 index 3c1884e6..00000000 --- a/test/serializers/serialization_test.rb +++ /dev/null @@ -1,55 +0,0 @@ -module ActiveModel - class Serializer - class SerializationTest < ActiveSupport::TestCase - class Blog < ActiveModelSerializers::Model - attributes :id, :name, :authors - end - class Author < ActiveModelSerializers::Model - attributes :id, :name - end - class BlogSerializer < ActiveModel::Serializer - attributes :id - attribute :name, key: :title - - has_many :authors - end - class AuthorSerializer < ActiveModel::Serializer - attributes :id, :name - end - - setup do - @authors = [Author.new(id: 1, name: 'Blog Author')] - @blog = Blog.new(id: 2, name: 'The Blog', authors: @authors) - @serializer_instance = BlogSerializer.new(@blog) - @serializable = ActiveModelSerializers::SerializableResource.new(@blog, serializer: BlogSerializer, adapter: :attributes) - @expected_hash = { id: 2, title: 'The Blog', authors: [{ id: 1, name: 'Blog Author' }] } - @expected_json = '{"id":2,"title":"The Blog","authors":[{"id":1,"name":"Blog Author"}]}' - end - - test '#serializable_hash is the same as generated by the attributes adapter' do - assert_equal @serializable.serializable_hash, @serializer_instance.serializable_hash - assert_equal @expected_hash, @serializer_instance.serializable_hash - end - - test '#as_json is the same as generated by the attributes adapter' do - assert_equal @serializable.as_json, @serializer_instance.as_json - assert_equal @expected_hash, @serializer_instance.as_json - end - - test '#to_json is the same as generated by the attributes adapter' do - assert_equal @serializable.to_json, @serializer_instance.to_json - assert_equal @expected_json, @serializer_instance.to_json - end - - test '#to_h is an alias for #serializable_hash' do - assert_equal @serializable.serializable_hash, @serializer_instance.to_h - assert_equal @expected_hash, @serializer_instance.to_h - end - - test '#to_hash is an alias for #serializable_hash' do - assert_equal @serializable.serializable_hash, @serializer_instance.to_hash - assert_equal @expected_hash, @serializer_instance.to_hash - end - end - end -end diff --git a/test/serializers/serializer_for_test.rb b/test/serializers/serializer_for_test.rb deleted file mode 100644 index 9f691708..00000000 --- a/test/serializers/serializer_for_test.rb +++ /dev/null @@ -1,136 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class SerializerForTest < ActiveSupport::TestCase - class CollectionSerializerTest < ActiveSupport::TestCase - def setup - @array = [1, 2, 3] - @previous_collection_serializer = ActiveModelSerializers.config.collection_serializer - end - - def teardown - ActiveModelSerializers.config.collection_serializer = @previous_collection_serializer - end - - def test_serializer_for_array - serializer = ActiveModel::Serializer.serializer_for(@array) - assert_equal ActiveModelSerializers.config.collection_serializer, serializer - end - - def test_overwritten_serializer_for_array - new_collection_serializer = Class.new - ActiveModelSerializers.config.collection_serializer = new_collection_serializer - serializer = ActiveModel::Serializer.serializer_for(@array) - assert_equal new_collection_serializer, serializer - end - end - - class SerializerTest < ActiveSupport::TestCase - module ResourceNamespace - class Post < ::Model; end - class Comment < ::Model; end - - class PostSerializer < ActiveModel::Serializer - class CommentSerializer < ActiveModel::Serializer - end - end - end - - class MyProfile < Profile - end - - class CustomProfile - def serializer_class - ProfileSerializer - end - end - - class Tweet < ::Model; end - TweetSerializer = Class.new - - def setup - @profile = Profile.new - @my_profile = MyProfile.new - @custom_profile = CustomProfile.new - @model = ::Model.new - @tweet = Tweet.new - end - - def test_serializer_for_non_ams_serializer - serializer = ActiveModel::Serializer.serializer_for(@tweet) - assert_nil serializer - end - - def test_serializer_for_existing_serializer - serializer = ActiveModel::Serializer.serializer_for(@profile) - assert_equal ProfileSerializer, serializer - end - - def test_serializer_for_existing_serializer_with_lookup_disabled - serializer = with_serializer_lookup_disabled do - ActiveModel::Serializer.serializer_for(@profile) - end - assert_nil serializer - end - - def test_serializer_for_not_existing_serializer - serializer = ActiveModel::Serializer.serializer_for(@model) - assert_nil serializer - end - - def test_serializer_inherited_serializer - serializer = ActiveModel::Serializer.serializer_for(@my_profile) - assert_equal ProfileSerializer, serializer - end - - def test_serializer_inherited_serializer_with_lookup_disabled - serializer = with_serializer_lookup_disabled do - ActiveModel::Serializer.serializer_for(@my_profile) - end - assert_nil serializer - end - - def test_serializer_custom_serializer - serializer = ActiveModel::Serializer.serializer_for(@custom_profile) - assert_equal ProfileSerializer, serializer - end - - def test_serializer_custom_serializer_with_lookup_disabled - serializer = with_serializer_lookup_disabled do - ActiveModel::Serializer.serializer_for(@custom_profile) - end - assert_equal ProfileSerializer, serializer - end - - def test_serializer_for_namespaced_resource - post = ResourceNamespace::Post.new - serializer = ActiveModel::Serializer.serializer_for(post) - assert_equal ResourceNamespace::PostSerializer, serializer - end - - def test_serializer_for_namespaced_resource_with_lookup_disabled - post = ResourceNamespace::Post.new - serializer = with_serializer_lookup_disabled do - ActiveModel::Serializer.serializer_for(post) - end - assert_nil serializer - end - - def test_serializer_for_nested_resource - comment = ResourceNamespace::Comment.new - serializer = ResourceNamespace::PostSerializer.serializer_for(comment) - assert_equal ResourceNamespace::PostSerializer::CommentSerializer, serializer - end - - def test_serializer_for_nested_resource_with_lookup_disabled - comment = ResourceNamespace::Comment.new - serializer = with_serializer_lookup_disabled do - ResourceNamespace::PostSerializer.serializer_for(comment) - end - assert_nil serializer - end - end - end - end -end diff --git a/test/serializers/serializer_for_with_namespace_test.rb b/test/serializers/serializer_for_with_namespace_test.rb deleted file mode 100644 index 5c6e3e5e..00000000 --- a/test/serializers/serializer_for_with_namespace_test.rb +++ /dev/null @@ -1,88 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class SerializerForWithNamespaceTest < ActiveSupport::TestCase - class Book < ::Model - attributes :title, :author_name - associations :publisher, :pages - end - class Page < ::Model; attributes :number, :text end - class Publisher < ::Model; attributes :name end - - module Api - module V3 - class BookSerializer < ActiveModel::Serializer - attributes :title, :author_name - - has_many :pages - belongs_to :publisher - end - - class PageSerializer < ActiveModel::Serializer - attributes :number, :text - end - - class PublisherSerializer < ActiveModel::Serializer - attributes :name - end - end - end - - 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 - - expected = { title: 'A Post', author_name: 'hello' } - assert_equal expected, result - end - - test 'resource with namespace' do - book = Book.new(title: 'A Post', author_name: 'hi') - - result = ActiveModelSerializers::SerializableResource.new(book, namespace: Api::V3).serializable_hash - - expected = { title: 'A Post', author_name: 'hi', pages: nil, publisher: nil } - assert_equal expected, result - end - - test 'has_many with nested serializer under the namespace' do - page = Page.new(number: 1, text: 'hello') - book = Book.new(title: 'A Post', author_name: 'hi', pages: [page]) - - result = ActiveModelSerializers::SerializableResource.new(book, namespace: Api::V3).serializable_hash - - expected = { - title: 'A Post', author_name: 'hi', - publisher: nil, - pages: [{ - number: 1, text: 'hello' - }] - } - assert_equal expected, result - end - - test 'belongs_to with nested serializer under the namespace' do - publisher = Publisher.new(name: 'Disney') - book = Book.new(title: 'A Post', author_name: 'hi', publisher: publisher) - - result = ActiveModelSerializers::SerializableResource.new(book, namespace: Api::V3).serializable_hash - - expected = { - title: 'A Post', author_name: 'hi', - pages: nil, - publisher: { - name: 'Disney' - } - } - assert_equal expected, result - end - end - end -end diff --git a/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json b/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json deleted file mode 100644 index 9474c509..00000000 --- a/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "properties": { - "name" : { "type" : "string" }, - "description" : { "type" : "string" } - } -} diff --git a/test/support/isolated_unit.rb b/test/support/isolated_unit.rb deleted file mode 100644 index 26948d4a..00000000 --- a/test/support/isolated_unit.rb +++ /dev/null @@ -1,82 +0,0 @@ -# https://github.com/rails/rails/blob/v5.0.0.beta1/railties/test/isolation/abstract_unit.rb - -# Usage Example: -# -# require 'support/isolated_unit' -# -# class RailtieTest < ActiveSupport::TestCase -# include ActiveSupport::Testing::Isolation -# -# class WithRailsDefinedOnLoad < RailtieTest -# setup do -# require 'rails' -# require 'active_model_serializers' -# make_basic_app -# end -# -# # some tests -# end -# -# class WithoutRailsDefinedOnLoad < RailtieTest -# setup do -# require 'active_model_serializers' -# make_basic_app -# end -# -# # some tests -# end -# end -# -# Note: -# It is important to keep this file as light as possible -# the goal for tests that require this is to test booting up -# rails from an empty state, so anything added here could -# hide potential failures -# -# It is also good to know what is the bare minimum to get -# Rails booted up. -require 'bundler/setup' unless defined?(Bundler) -require 'active_support' -require 'active_support/core_ext/string/access' - -# These files do not require any others and are needed -# to run the tests -require 'active_support/testing/autorun' -require 'active_support/testing/isolation' - -module TestHelpers - module Generation - module_function - - # Make a very basic app, without creating the whole directory structure. - # Is faster and simpler than generating a Rails app in a temp directory - def make_basic_app - require 'rails' - require 'action_controller/railtie' - - @app = Class.new(Rails::Application) do - config.eager_load = false - config.session_store :cookie_store, key: '_myapp_session' - config.active_support.deprecation = :log - config.active_support.test_order = :parallel - ActiveSupport::TestCase.respond_to?(:test_order=) && ActiveSupport::TestCase.test_order = :parallel - config.root = File.dirname(__FILE__) - config.log_level = :info - # Set a fake logger to avoid creating the log directory automatically - fake_logger = Logger.new(nil) - config.logger = fake_logger - Rails.application.routes.default_url_options = { host: 'example.com' } - end - @app.respond_to?(:secrets) && @app.secrets.secret_key_base = '3b7cd727ee24e8444053437c36cc66c4' - - yield @app if block_given? - @app.initialize! - end - end -end - -module ActiveSupport - class TestCase - include TestHelpers::Generation - end -end diff --git a/test/support/rails5_shims.rb b/test/support/rails5_shims.rb deleted file mode 100644 index 03a036da..00000000 --- a/test/support/rails5_shims.rb +++ /dev/null @@ -1,53 +0,0 @@ -module Rails5Shims - module ControllerTests - # https://github.com/rails/rails/blob/b217354/actionpack/lib/action_controller/test_case.rb - REQUEST_KWARGS = [:params, :headers, :session, :flash, :method, :body, :xhr].freeze - - def get(path, *args) - fold_kwargs!(args) - super - end - - def post(path, *args) - fold_kwargs!(args) - super - end - - def patch(path, *args) - fold_kwargs!(args) - super - end - - def put(path, *args) - fold_kwargs!(args) - super - end - - # Fold kwargs from test request into args - # Band-aid for DEPRECATION WARNING - def fold_kwargs!(args) - hash = args && args[0] - return unless hash.respond_to?(:key) - Rails5Shims::ControllerTests::REQUEST_KWARGS.each do |kwarg| - next unless hash.key?(kwarg) - value = hash.delete(kwarg) - if value.is_a? String - args.insert(0, value) - else - hash.merge! value - end - end - end - - # Uncomment for debugging where the kwargs warnings come from - # def non_kwarg_request_warning - # super.tap do - # STDOUT.puts caller[2..3] - # end - # end - end -end -if Rails::VERSION::MAJOR < 5 - ActionController::TestCase.send :include, Rails5Shims::ControllerTests - ActionDispatch::IntegrationTest.send :include, Rails5Shims::ControllerTests -end diff --git a/test/support/rails_app.rb b/test/support/rails_app.rb deleted file mode 100644 index 43324b78..00000000 --- a/test/support/rails_app.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'support/isolated_unit' -module ActiveModelSerializers - RailsApplication = TestHelpers::Generation.make_basic_app do |app| - app.configure do - config.secret_key_base = 'abc123' - config.active_support.test_order = :random - config.action_controller.perform_caching = true - config.action_controller.cache_store = :memory_store - - config.filter_parameters += [:password] - end - - app.routes.default_url_options = { host: 'example.com' } - end -end - -Routes = ActionDispatch::Routing::RouteSet.new -Routes.draw do - get ':controller(/:action(/:id))' - get ':controller(/:action)' -end -ActionController::Base.send :include, Routes.url_helpers -ActionController::TestCase.class_eval do - def setup - @routes = Routes - end -end - -# ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../test/dummy/db/migrate", __FILE__)] -# ActiveRecord::Migrator.migrations_paths << File.expand_path('../../db/migrate', __FILE__) -# -# Load fixtures from the engine -# if ActiveSupport::TestCase.respond_to?(:fixture_path=) -# ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) -# ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path -# ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files" -# ActiveSupport::TestCase.fixtures :all -# end diff --git a/test/support/schemas/active_model_serializers/test/schema_test/my/index.json b/test/support/schemas/active_model_serializers/test/schema_test/my/index.json deleted file mode 100644 index 9474c509..00000000 --- a/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "properties": { - "name" : { "type" : "string" }, - "description" : { "type" : "string" } - } -} diff --git a/test/support/schemas/active_model_serializers/test/schema_test/my/show.json b/test/support/schemas/active_model_serializers/test/schema_test/my/show.json deleted file mode 100644 index 71313665..00000000 --- a/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "properties": { - "name" : { "type" : "integer" }, - "description" : { "type" : "boolean" } - } -} diff --git a/test/support/schemas/custom/show.json b/test/support/schemas/custom/show.json deleted file mode 100644 index 29a47e15..00000000 --- a/test/support/schemas/custom/show.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "file://custom/show.json#", - "properties": { - "name" : { "type" : "string" }, - "description" : { "type" : "string" } - } -} diff --git a/test/support/schemas/hyper_schema.json b/test/support/schemas/hyper_schema.json deleted file mode 100644 index ae1f8f33..00000000 --- a/test/support/schemas/hyper_schema.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/hyper-schema", - "title": "Profile", - "description": "Profile schema", - "stability": "prototype", - "strictProperties": true, - "type": [ - "object" - ], - "definitions": { - "name": { - "description": "unique name of profile", - "readOnly": true, - "type": [ - "string" - ] - }, - "description": { - "description": "description of profile", - "readOnly": true, - "type": [ - "string" - ] - }, - "identity": { - "anyOf": [ - { - "$ref": "/schemata/profile#/definitions/name" - } - ] - } - }, - "links": [ - { - "description": "Create a new profile.", - "href": "/profiles", - "method": "POST", - "rel": "create", - "schema": { - "properties": { - }, - "type": [ - "object" - ] - }, - "title": "Create" - }, - { - "description": "Delete an existing profile.", - "href": "/profiles/{(%2Fschemata%2Fprofile%23%2Fdefinitions%2Fidentity)}", - "method": "DELETE", - "rel": "destroy", - "title": "Delete" - }, - { - "description": "Info for existing profile.", - "href": "/profiles/{(%2Fschemata%2Fprofile%23%2Fdefinitions%2Fidentity)}", - "method": "GET", - "rel": "self", - "title": "Info" - }, - { - "description": "List existing profiles.", - "href": "/profiles", - "method": "GET", - "rel": "instances", - "title": "List" - }, - { - "description": "Update an existing profile.", - "href": "/profiles/{(%2Fschemata%2Fprofile%23%2Fdefinitions%2Fidentity)}", - "method": "PATCH", - "rel": "update", - "schema": { - "properties": { - }, - "type": [ - "object" - ] - }, - "title": "Update" - } - ], - "properties": { - "name": { - "$ref": "/schemata/profile#/definitions/name" - }, - "description": { - "$ref": "/schemata/profile#/definitions/description" - } - }, - "id": "/schemata/profile" -} diff --git a/test/support/schemas/render_using_json_api.json b/test/support/schemas/render_using_json_api.json deleted file mode 100644 index 1a8f8fe1..00000000 --- a/test/support/schemas/render_using_json_api.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "", - "type": "object", - "properties": { - "data": { - "id": "/data", - "type": "object", - "properties": { - "id": { - "id": "/data/id", - "type": "string" - }, - "type": { - "id": "/data/type", - "type": "string" - }, - "attributes": { - "id": "/data/attributes", - "type": "object", - "properties": { - "name": { - "id": "/data/attributes/name", - "type": "string" - }, - "description": { - "id": "/data/attributes/description", - "type": "string" - } - } - } - }, - "required": [ - "id", - "type", - "attributes" - ] - } - }, - "required": [ - "data" - ] -} diff --git a/test/support/schemas/simple_json_pointers.json b/test/support/schemas/simple_json_pointers.json deleted file mode 100644 index d1a6f1eb..00000000 --- a/test/support/schemas/simple_json_pointers.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "properties": { - "name": { - "$ref": "file://custom/show.json#/properties/name" - }, - "description": { - "$ref": "file://custom/show.json#/properties/description" - } - } -} diff --git a/test/support/serialization_testing.rb b/test/support/serialization_testing.rb deleted file mode 100644 index 524a3297..00000000 --- a/test/support/serialization_testing.rb +++ /dev/null @@ -1,71 +0,0 @@ -module SerializationTesting - def config - ActiveModelSerializers.config - end - - private - - def generate_cached_serializer(obj) - ActiveModelSerializers::SerializableResource.new(obj).to_json - end - - def with_namespace_separator(separator) - original_separator = ActiveModelSerializers.config.jsonapi_namespace_separator - ActiveModelSerializers.config.jsonapi_namespace_separator = separator - yield - ensure - ActiveModelSerializers.config.jsonapi_namespace_separator = original_separator - end - - def with_prepended_lookup(lookup_proc) - original_lookup = ActiveModelSerializers.config.serializer_lookup_cahin - ActiveModelSerializers.config.serializer_lookup_chain.unshift lookup_proc - yield - ensure - ActiveModelSerializers.config.serializer_lookup_cahin = original_lookup - end - - # Aliased as :with_configured_adapter to clarify that - # this method tests the configured adapter. - # When not testing configuration, it may be preferable - # to pass in the +adapter+ option to ActiveModelSerializers::SerializableResource. - # e.g ActiveModelSerializers::SerializableResource.new(resource, adapter: :json_api) - def with_adapter(adapter) - old_adapter = ActiveModelSerializers.config.adapter - ActiveModelSerializers.config.adapter = adapter - yield - ensure - ActiveModelSerializers.config.adapter = old_adapter - end - alias with_configured_adapter with_adapter - - def with_config(hash) - old_config = config.dup - ActiveModelSerializers.config.update(hash) - yield - ensure - ActiveModelSerializers.config.replace(old_config) - end - - def with_serializer_lookup_disabled - original_serializer_lookup = ActiveModelSerializers.config.serializer_lookup_enabled - ActiveModelSerializers.config.serializer_lookup_enabled = false - yield - ensure - ActiveModelSerializers.config.serializer_lookup_enabled = original_serializer_lookup - end - - def serializable(resource, options = {}) - ActiveModelSerializers::SerializableResource.new(resource, options) - end -end - -module Minitest - class Test - def before_setup - ActionController::Base.cache_store.clear - end - - include SerializationTesting - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb deleted file mode 100644 index 294fa33c..00000000 --- a/test/test_helper.rb +++ /dev/null @@ -1,70 +0,0 @@ -# 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' -require 'action_controller' -require 'action_controller/test_case' -require 'action_controller/railtie' -require 'active_model_serializers' -# For now, we only restrict the options to serializable_hash/as_json/to_json -# in tests, to ensure developers don't add any unsupported options. -# There's no known benefit, at this time, to having the filtering run in -# production when the excluded options would simply not be used. -# -# However, for documentation purposes, the constant -# ActiveModel::Serializer::SERIALIZABLE_HASH_VALID_KEYS is defined -# in the Serializer. -ActiveModelSerializers::Adapter::Base.class_eval do - alias_method :original_serialization_options, :serialization_options - - def serialization_options(options) - original_serialization_options(options) - .slice(*ActiveModel::Serializer::SERIALIZABLE_HASH_VALID_KEYS) - end -end -require 'fileutils' -FileUtils.mkdir_p(File.expand_path('../../tmp/cache', __FILE__)) - -gem 'minitest' -require 'minitest' -require 'minitest/autorun' -Minitest.backtrace_filter = Minitest::BacktraceFilter.new - -module TestHelper - module_function - - def silence_warnings - original_verbose = $VERBOSE - $VERBOSE = nil - yield - ensure - $VERBOSE = original_verbose - end -end - -require 'support/rails_app' - -# require "rails/test_help" - -require 'support/serialization_testing' - -require 'support/rails5_shims' - -require 'fixtures/active_record' - -require 'fixtures/poro' - -ActiveSupport.on_load(:action_controller) do - $action_controller_logger = ActiveModelSerializers.logger - ActiveModelSerializers.logger = Logger.new(IO::NULL) -end