agi_active_model_serializers 0.10.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE.md +29 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- data/.gitignore +35 -0
- data/.rubocop.yml +102 -0
- data/.simplecov +110 -0
- data/.travis.yml +51 -0
- data/CHANGELOG.md +612 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +105 -0
- data/Gemfile +56 -0
- data/MIT-LICENSE +22 -0
- data/README.md +307 -0
- data/Rakefile +103 -0
- data/active_model_serializers.gemspec +63 -0
- data/appveyor.yml +24 -0
- data/bin/bench +171 -0
- data/bin/bench_regression +316 -0
- data/bin/serve_benchmark +39 -0
- data/docs/README.md +41 -0
- data/docs/STYLE.md +58 -0
- data/docs/general/adapters.md +247 -0
- data/docs/general/caching.md +58 -0
- data/docs/general/configuration_options.md +169 -0
- data/docs/general/deserialization.md +100 -0
- data/docs/general/fields.md +31 -0
- data/docs/general/getting_started.md +133 -0
- data/docs/general/instrumentation.md +40 -0
- data/docs/general/key_transforms.md +40 -0
- data/docs/general/logging.md +14 -0
- data/docs/general/rendering.md +279 -0
- data/docs/general/serializers.md +461 -0
- data/docs/how-open-source-maintained.jpg +0 -0
- data/docs/howto/add_pagination_links.md +138 -0
- data/docs/howto/add_relationship_links.md +137 -0
- data/docs/howto/add_root_key.md +55 -0
- data/docs/howto/grape_integration.md +42 -0
- data/docs/howto/outside_controller_use.md +65 -0
- data/docs/howto/passing_arbitrary_options.md +27 -0
- data/docs/howto/serialize_poro.md +32 -0
- data/docs/howto/test.md +154 -0
- data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
- data/docs/integrations/ember-and-json-api.md +144 -0
- data/docs/integrations/grape.md +19 -0
- data/docs/jsonapi/errors.md +56 -0
- data/docs/jsonapi/schema.md +151 -0
- data/docs/jsonapi/schema/schema.json +366 -0
- data/docs/rfcs/0000-namespace.md +106 -0
- data/docs/rfcs/template.md +15 -0
- data/lib/action_controller/serialization.rb +66 -0
- data/lib/active_model/serializable_resource.rb +11 -0
- data/lib/active_model/serializer.rb +231 -0
- data/lib/active_model/serializer/adapter.rb +24 -0
- data/lib/active_model/serializer/adapter/attributes.rb +15 -0
- data/lib/active_model/serializer/adapter/base.rb +18 -0
- data/lib/active_model/serializer/adapter/json.rb +15 -0
- data/lib/active_model/serializer/adapter/json_api.rb +15 -0
- data/lib/active_model/serializer/adapter/null.rb +15 -0
- data/lib/active_model/serializer/array_serializer.rb +12 -0
- data/lib/active_model/serializer/association.rb +34 -0
- data/lib/active_model/serializer/attribute.rb +25 -0
- data/lib/active_model/serializer/belongs_to_reflection.rb +7 -0
- data/lib/active_model/serializer/collection_reflection.rb +7 -0
- data/lib/active_model/serializer/collection_serializer.rb +87 -0
- data/lib/active_model/serializer/concerns/associations.rb +102 -0
- data/lib/active_model/serializer/concerns/attributes.rb +82 -0
- data/lib/active_model/serializer/concerns/caching.rb +292 -0
- data/lib/active_model/serializer/concerns/configuration.rb +59 -0
- data/lib/active_model/serializer/concerns/links.rb +35 -0
- data/lib/active_model/serializer/concerns/meta.rb +29 -0
- data/lib/active_model/serializer/concerns/type.rb +25 -0
- data/lib/active_model/serializer/error_serializer.rb +14 -0
- data/lib/active_model/serializer/errors_serializer.rb +32 -0
- data/lib/active_model/serializer/field.rb +90 -0
- data/lib/active_model/serializer/fieldset.rb +31 -0
- data/lib/active_model/serializer/has_many_reflection.rb +7 -0
- data/lib/active_model/serializer/has_one_reflection.rb +7 -0
- data/lib/active_model/serializer/lint.rb +150 -0
- data/lib/active_model/serializer/null.rb +17 -0
- data/lib/active_model/serializer/reflection.rb +163 -0
- data/lib/active_model/serializer/singular_reflection.rb +7 -0
- data/lib/active_model/serializer/version.rb +5 -0
- data/lib/active_model_serializers.rb +53 -0
- data/lib/active_model_serializers/adapter.rb +98 -0
- data/lib/active_model_serializers/adapter/attributes.rb +13 -0
- data/lib/active_model_serializers/adapter/base.rb +83 -0
- data/lib/active_model_serializers/adapter/json.rb +21 -0
- data/lib/active_model_serializers/adapter/json_api.rb +517 -0
- data/lib/active_model_serializers/adapter/json_api/deserialization.rb +213 -0
- data/lib/active_model_serializers/adapter/json_api/error.rb +96 -0
- data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +49 -0
- data/lib/active_model_serializers/adapter/json_api/link.rb +83 -0
- data/lib/active_model_serializers/adapter/json_api/meta.rb +37 -0
- data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +69 -0
- data/lib/active_model_serializers/adapter/json_api/relationship.rb +63 -0
- data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +51 -0
- data/lib/active_model_serializers/adapter/null.rb +9 -0
- data/lib/active_model_serializers/callbacks.rb +55 -0
- data/lib/active_model_serializers/deprecate.rb +54 -0
- data/lib/active_model_serializers/deserialization.rb +15 -0
- data/lib/active_model_serializers/json_pointer.rb +14 -0
- data/lib/active_model_serializers/logging.rb +122 -0
- data/lib/active_model_serializers/lookup_chain.rb +80 -0
- data/lib/active_model_serializers/model.rb +71 -0
- data/lib/active_model_serializers/railtie.rb +48 -0
- data/lib/active_model_serializers/register_jsonapi_renderer.rb +78 -0
- data/lib/active_model_serializers/serializable_resource.rb +82 -0
- data/lib/active_model_serializers/serialization_context.rb +39 -0
- data/lib/active_model_serializers/test.rb +7 -0
- data/lib/active_model_serializers/test/schema.rb +138 -0
- data/lib/active_model_serializers/test/serializer.rb +125 -0
- data/lib/generators/rails/USAGE +6 -0
- data/lib/generators/rails/resource_override.rb +10 -0
- data/lib/generators/rails/serializer_generator.rb +36 -0
- data/lib/generators/rails/templates/serializer.rb.erb +15 -0
- data/lib/grape/active_model_serializers.rb +16 -0
- data/lib/grape/formatters/active_model_serializers.rb +32 -0
- data/lib/grape/helpers/active_model_serializers.rb +17 -0
- data/test/action_controller/adapter_selector_test.rb +53 -0
- data/test/action_controller/explicit_serializer_test.rb +135 -0
- data/test/action_controller/json/include_test.rb +246 -0
- data/test/action_controller/json_api/deserialization_test.rb +112 -0
- data/test/action_controller/json_api/errors_test.rb +40 -0
- data/test/action_controller/json_api/fields_test.rb +66 -0
- data/test/action_controller/json_api/linked_test.rb +202 -0
- data/test/action_controller/json_api/pagination_test.rb +116 -0
- data/test/action_controller/json_api/transform_test.rb +189 -0
- data/test/action_controller/lookup_proc_test.rb +49 -0
- data/test/action_controller/namespace_lookup_test.rb +232 -0
- data/test/action_controller/serialization_scope_name_test.rb +229 -0
- data/test/action_controller/serialization_test.rb +472 -0
- data/test/active_model_serializers/adapter_for_test.rb +208 -0
- data/test/active_model_serializers/json_pointer_test.rb +22 -0
- data/test/active_model_serializers/logging_test.rb +77 -0
- data/test/active_model_serializers/model_test.rb +69 -0
- data/test/active_model_serializers/railtie_test_isolated.rb +63 -0
- data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +161 -0
- data/test/active_model_serializers/serialization_context_test_isolated.rb +71 -0
- data/test/active_model_serializers/test/schema_test.rb +131 -0
- data/test/active_model_serializers/test/serializer_test.rb +62 -0
- data/test/active_record_test.rb +9 -0
- data/test/adapter/attributes_test.rb +43 -0
- data/test/adapter/deprecation_test.rb +100 -0
- data/test/adapter/json/belongs_to_test.rb +45 -0
- data/test/adapter/json/collection_test.rb +104 -0
- data/test/adapter/json/has_many_test.rb +45 -0
- data/test/adapter/json/transform_test.rb +93 -0
- data/test/adapter/json_api/belongs_to_test.rb +155 -0
- data/test/adapter/json_api/collection_test.rb +96 -0
- data/test/adapter/json_api/errors_test.rb +76 -0
- data/test/adapter/json_api/fields_test.rb +96 -0
- data/test/adapter/json_api/has_many_embed_ids_test.rb +43 -0
- data/test/adapter/json_api/has_many_explicit_serializer_test.rb +96 -0
- data/test/adapter/json_api/has_many_test.rb +165 -0
- data/test/adapter/json_api/has_one_test.rb +80 -0
- data/test/adapter/json_api/include_data_if_sideloaded_test.rb +168 -0
- data/test/adapter/json_api/json_api_test.rb +33 -0
- data/test/adapter/json_api/linked_test.rb +413 -0
- data/test/adapter/json_api/links_test.rb +95 -0
- data/test/adapter/json_api/pagination_links_test.rb +193 -0
- data/test/adapter/json_api/parse_test.rb +137 -0
- data/test/adapter/json_api/relationship_test.rb +397 -0
- data/test/adapter/json_api/resource_identifier_test.rb +110 -0
- data/test/adapter/json_api/resource_meta_test.rb +100 -0
- data/test/adapter/json_api/toplevel_jsonapi_test.rb +82 -0
- data/test/adapter/json_api/transform_test.rb +512 -0
- data/test/adapter/json_api/type_test.rb +61 -0
- data/test/adapter/json_test.rb +46 -0
- data/test/adapter/null_test.rb +22 -0
- data/test/adapter/polymorphic_test.rb +171 -0
- data/test/adapter_test.rb +67 -0
- data/test/array_serializer_test.rb +22 -0
- data/test/benchmark/app.rb +65 -0
- data/test/benchmark/benchmarking_support.rb +67 -0
- data/test/benchmark/bm_active_record.rb +81 -0
- data/test/benchmark/bm_adapter.rb +38 -0
- data/test/benchmark/bm_caching.rb +119 -0
- data/test/benchmark/bm_lookup_chain.rb +83 -0
- data/test/benchmark/bm_transform.rb +45 -0
- data/test/benchmark/config.ru +3 -0
- data/test/benchmark/controllers.rb +83 -0
- data/test/benchmark/fixtures.rb +219 -0
- data/test/cache_test.rb +595 -0
- data/test/collection_serializer_test.rb +123 -0
- data/test/fixtures/active_record.rb +113 -0
- data/test/fixtures/poro.rb +232 -0
- data/test/generators/scaffold_controller_generator_test.rb +24 -0
- data/test/generators/serializer_generator_test.rb +74 -0
- data/test/grape_test.rb +178 -0
- data/test/lint_test.rb +49 -0
- data/test/logger_test.rb +20 -0
- data/test/poro_test.rb +9 -0
- data/test/serializable_resource_test.rb +79 -0
- data/test/serializers/association_macros_test.rb +37 -0
- data/test/serializers/associations_test.rb +383 -0
- data/test/serializers/attribute_test.rb +153 -0
- data/test/serializers/attributes_test.rb +52 -0
- data/test/serializers/caching_configuration_test_isolated.rb +170 -0
- data/test/serializers/configuration_test.rb +32 -0
- data/test/serializers/fieldset_test.rb +14 -0
- data/test/serializers/meta_test.rb +202 -0
- data/test/serializers/options_test.rb +32 -0
- data/test/serializers/read_attribute_for_serialization_test.rb +79 -0
- data/test/serializers/root_test.rb +21 -0
- data/test/serializers/serialization_test.rb +55 -0
- data/test/serializers/serializer_for_test.rb +136 -0
- data/test/serializers/serializer_for_with_namespace_test.rb +88 -0
- data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
- data/test/support/isolated_unit.rb +82 -0
- data/test/support/rails5_shims.rb +53 -0
- data/test/support/rails_app.rb +36 -0
- data/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
- data/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +6 -0
- data/test/support/schemas/custom/show.json +7 -0
- data/test/support/schemas/hyper_schema.json +93 -0
- data/test/support/schemas/render_using_json_api.json +43 -0
- data/test/support/schemas/simple_json_pointers.json +10 -0
- data/test/support/serialization_testing.rb +71 -0
- data/test/test_helper.rb +58 -0
- metadata +602 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module ActiveModelSerializers
|
|
2
|
+
module LookupChain
|
|
3
|
+
# Standard appending of Serializer to the resource name.
|
|
4
|
+
#
|
|
5
|
+
# Example:
|
|
6
|
+
# Author => AuthorSerializer
|
|
7
|
+
BY_RESOURCE = lambda do |resource_class, _serializer_class, _namespace|
|
|
8
|
+
serializer_from(resource_class)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Uses the namespace of the resource to find the serializer
|
|
12
|
+
#
|
|
13
|
+
# Example:
|
|
14
|
+
# British::Author => British::AuthorSerializer
|
|
15
|
+
BY_RESOURCE_NAMESPACE = lambda do |resource_class, _serializer_class, _namespace|
|
|
16
|
+
resource_namespace = namespace_for(resource_class)
|
|
17
|
+
serializer_name = serializer_from(resource_class)
|
|
18
|
+
|
|
19
|
+
"#{resource_namespace}::#{serializer_name}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Uses the controller namespace of the resource to find the serializer
|
|
23
|
+
#
|
|
24
|
+
# Example:
|
|
25
|
+
# Api::V3::AuthorsController => Api::V3::AuthorSerializer
|
|
26
|
+
BY_NAMESPACE = lambda do |resource_class, _serializer_class, namespace|
|
|
27
|
+
resource_name = resource_class_name(resource_class)
|
|
28
|
+
namespace ? "#{namespace}::#{resource_name}Serializer" : nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Allows for serializers to be defined in parent serializers
|
|
32
|
+
# - useful if a relationship only needs a different set of attributes
|
|
33
|
+
# than if it were rendered independently.
|
|
34
|
+
#
|
|
35
|
+
# Example:
|
|
36
|
+
# class BlogSerializer < ActiveModel::Serializer
|
|
37
|
+
# class AuthorSerialier < ActiveModel::Serializer
|
|
38
|
+
# ...
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# belongs_to :author
|
|
42
|
+
# ...
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# The belongs_to relationship would be rendered with
|
|
46
|
+
# BlogSerializer::AuthorSerialier
|
|
47
|
+
BY_PARENT_SERIALIZER = lambda do |resource_class, serializer_class, _namespace|
|
|
48
|
+
return if serializer_class == ActiveModel::Serializer
|
|
49
|
+
|
|
50
|
+
serializer_name = serializer_from(resource_class)
|
|
51
|
+
"#{serializer_class}::#{serializer_name}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
DEFAULT = [
|
|
55
|
+
BY_PARENT_SERIALIZER,
|
|
56
|
+
BY_NAMESPACE,
|
|
57
|
+
BY_RESOURCE_NAMESPACE,
|
|
58
|
+
BY_RESOURCE
|
|
59
|
+
].freeze
|
|
60
|
+
|
|
61
|
+
module_function
|
|
62
|
+
|
|
63
|
+
def namespace_for(klass)
|
|
64
|
+
klass.name.deconstantize
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def resource_class_name(klass)
|
|
68
|
+
klass.name.demodulize
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def serializer_from_resource_name(name)
|
|
72
|
+
"#{name}Serializer"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def serializer_from(klass)
|
|
76
|
+
name = resource_class_name(klass)
|
|
77
|
+
serializer_from_resource_name(name)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# ActiveModelSerializers::Model is a convenient
|
|
2
|
+
# serializable class to inherit from when making
|
|
3
|
+
# serializable non-activerecord objects.
|
|
4
|
+
module ActiveModelSerializers
|
|
5
|
+
class Model
|
|
6
|
+
include ActiveModel::Serializers::JSON
|
|
7
|
+
include ActiveModel::Model
|
|
8
|
+
|
|
9
|
+
class_attribute :attribute_names
|
|
10
|
+
# Initialize +attribute_names+ for all subclasses. The array is usually
|
|
11
|
+
# mutated in the +attributes+ method, but can be set directly, as well.
|
|
12
|
+
self.attribute_names = []
|
|
13
|
+
|
|
14
|
+
def self.attributes(*names)
|
|
15
|
+
self.attribute_names |= names.map(&:to_sym)
|
|
16
|
+
# Silence redefinition of methods warnings
|
|
17
|
+
ActiveModelSerializers.silence_warnings do
|
|
18
|
+
attr_accessor(*names)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attr_reader :errors
|
|
23
|
+
# NOTE that +updated_at+ isn't included in +attribute_names+,
|
|
24
|
+
# which means it won't show up in +attributes+ unless a subclass has
|
|
25
|
+
# either <tt>attributes :updated_at</tt> which will redefine the methods
|
|
26
|
+
# or <tt>attribute_names << :updated_at</tt>.
|
|
27
|
+
attr_writer :updated_at
|
|
28
|
+
# NOTE that +id+ will always be in +attributes+.
|
|
29
|
+
attributes :id
|
|
30
|
+
|
|
31
|
+
def initialize(attributes = {})
|
|
32
|
+
@errors = ActiveModel::Errors.new(self)
|
|
33
|
+
super
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The the fields in +attribute_names+ determines the returned hash.
|
|
37
|
+
# +attributes+ are returned frozen to prevent any expectations that mutation affects
|
|
38
|
+
# the actual values in the model.
|
|
39
|
+
def attributes
|
|
40
|
+
attribute_names.each_with_object({}) do |attribute_name, result|
|
|
41
|
+
result[attribute_name] = public_send(attribute_name).freeze
|
|
42
|
+
end.with_indifferent_access.freeze
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# To customize model behavior, this method must be redefined. However,
|
|
46
|
+
# there are other ways of setting the +cache_key+ a serializer uses.
|
|
47
|
+
def cache_key
|
|
48
|
+
ActiveSupport::Cache.expand_cache_key([
|
|
49
|
+
self.class.model_name.name.downcase,
|
|
50
|
+
"#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}"
|
|
51
|
+
].compact)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# When no set, defaults to the time the file was modified.
|
|
55
|
+
# See NOTE by attr_writer :updated_at
|
|
56
|
+
def updated_at
|
|
57
|
+
defined?(@updated_at) ? @updated_at : File.mtime(__FILE__)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# The following methods are needed to be minimally implemented for ActiveModel::Errors
|
|
61
|
+
# :nocov:
|
|
62
|
+
def self.human_attribute_name(attr, _options = {})
|
|
63
|
+
attr
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.lookup_ancestors
|
|
67
|
+
[self]
|
|
68
|
+
end
|
|
69
|
+
# :nocov:
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'rails/railtie'
|
|
2
|
+
require 'action_controller'
|
|
3
|
+
require 'action_controller/railtie'
|
|
4
|
+
require 'action_controller/serialization'
|
|
5
|
+
|
|
6
|
+
module ActiveModelSerializers
|
|
7
|
+
class Railtie < Rails::Railtie
|
|
8
|
+
config.to_prepare do
|
|
9
|
+
ActiveModel::Serializer.serializers_cache.clear
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
initializer 'active_model_serializers.action_controller' do
|
|
13
|
+
ActiveSupport.on_load(:action_controller) do
|
|
14
|
+
include(::ActionController::Serialization)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
initializer 'active_model_serializers.prepare_serialization_context' do
|
|
19
|
+
SerializationContext.url_helpers = Rails.application.routes.url_helpers
|
|
20
|
+
SerializationContext.default_url_options = Rails.application.routes.default_url_options
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# This hook is run after the action_controller railtie has set the configuration
|
|
24
|
+
# based on the *environment* configuration and before any config/initializers are run
|
|
25
|
+
# and also before eager_loading (if enabled).
|
|
26
|
+
initializer 'active_model_serializers.set_configs', after: 'action_controller.set_configs' do
|
|
27
|
+
ActiveModelSerializers.logger = Rails.configuration.action_controller.logger
|
|
28
|
+
ActiveModelSerializers.config.perform_caching = Rails.configuration.action_controller.perform_caching
|
|
29
|
+
# We want this hook to run after the config has been set, even if ActionController has already loaded.
|
|
30
|
+
ActiveSupport.on_load(:action_controller) do
|
|
31
|
+
ActiveModelSerializers.config.cache_store = ActionController::Base.cache_store
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# :nocov:
|
|
36
|
+
generators do |app|
|
|
37
|
+
Rails::Generators.configure!(app.config.generators)
|
|
38
|
+
Rails::Generators.hidden_namespaces.uniq!
|
|
39
|
+
require 'generators/rails/resource_override'
|
|
40
|
+
end
|
|
41
|
+
# :nocov:
|
|
42
|
+
|
|
43
|
+
if Rails.env.test?
|
|
44
|
+
ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Schema)
|
|
45
|
+
ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Serializer)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Based on discussion in https://github.com/rails/rails/pull/23712#issuecomment-184977238,
|
|
2
|
+
# the JSON API media type will have its own format/renderer.
|
|
3
|
+
#
|
|
4
|
+
# > We recommend the media type be registered on its own as jsonapi
|
|
5
|
+
# when a jsonapi Renderer and deserializer (Http::Parameters::DEFAULT_PARSERS) are added.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
#
|
|
9
|
+
# ActiveSupport.on_load(:action_controller) do
|
|
10
|
+
# require 'active_model_serializers/register_jsonapi_renderer'
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# And then in controllers, use `render jsonapi: model` rather than `render json: model, adapter: :json_api`.
|
|
14
|
+
#
|
|
15
|
+
# For example, in a controller action, we can:
|
|
16
|
+
# respond_to do |format|
|
|
17
|
+
# format.jsonapi { render jsonapi: model }
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# or
|
|
21
|
+
#
|
|
22
|
+
# render jsonapi: model
|
|
23
|
+
#
|
|
24
|
+
# No wrapper format needed as it does not apply (i.e. no `wrap_parameters format: [jsonapi]`)
|
|
25
|
+
module ActiveModelSerializers
|
|
26
|
+
module Jsonapi
|
|
27
|
+
MEDIA_TYPE = 'application/vnd.api+json'.freeze
|
|
28
|
+
HEADERS = {
|
|
29
|
+
response: { 'CONTENT_TYPE'.freeze => MEDIA_TYPE },
|
|
30
|
+
request: { 'ACCEPT'.freeze => MEDIA_TYPE }
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
def self.install
|
|
34
|
+
# actionpack/lib/action_dispatch/http/mime_types.rb
|
|
35
|
+
Mime::Type.register MEDIA_TYPE, :jsonapi
|
|
36
|
+
|
|
37
|
+
if Rails::VERSION::MAJOR >= 5
|
|
38
|
+
ActionDispatch::Request.parameter_parsers[:jsonapi] = parser
|
|
39
|
+
else
|
|
40
|
+
ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime[:jsonapi]] = parser
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# ref https://github.com/rails/rails/pull/21496
|
|
44
|
+
ActionController::Renderers.add :jsonapi do |json, options|
|
|
45
|
+
json = serialize_jsonapi(json, options).to_json(options) unless json.is_a?(String)
|
|
46
|
+
self.content_type ||= Mime[:jsonapi]
|
|
47
|
+
self.response_body = json
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Proposal: should actually deserialize the JSON API params
|
|
52
|
+
# to the hash format expected by `ActiveModel::Serializers::JSON`
|
|
53
|
+
# actionpack/lib/action_dispatch/http/parameters.rb
|
|
54
|
+
def self.parser
|
|
55
|
+
lambda do |body|
|
|
56
|
+
data = JSON.parse(body)
|
|
57
|
+
data = { _json: data } unless data.is_a?(Hash)
|
|
58
|
+
data.with_indifferent_access
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
module ControllerSupport
|
|
63
|
+
def serialize_jsonapi(json, options)
|
|
64
|
+
options[:adapter] = :json_api
|
|
65
|
+
options.fetch(:serialization_context) do
|
|
66
|
+
options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request)
|
|
67
|
+
end
|
|
68
|
+
get_serializer(json, options)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
ActiveModelSerializers::Jsonapi.install
|
|
75
|
+
|
|
76
|
+
ActiveSupport.on_load(:action_controller) do
|
|
77
|
+
include ActiveModelSerializers::Jsonapi::ControllerSupport
|
|
78
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
module ActiveModelSerializers
|
|
4
|
+
class SerializableResource
|
|
5
|
+
ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter, :meta, :meta_key, :links, :serialization_context, :key_transform])
|
|
6
|
+
include ActiveModelSerializers::Logging
|
|
7
|
+
|
|
8
|
+
delegate :serializable_hash, :as_json, :to_json, to: :adapter
|
|
9
|
+
notify :serializable_hash, :render
|
|
10
|
+
notify :as_json, :render
|
|
11
|
+
notify :to_json, :render
|
|
12
|
+
|
|
13
|
+
# Primary interface to composing a resource with a serializer and adapter.
|
|
14
|
+
# @return the serializable_resource, ready for #as_json/#to_json/#serializable_hash.
|
|
15
|
+
def initialize(resource, options = {})
|
|
16
|
+
@resource = resource
|
|
17
|
+
@adapter_opts, @serializer_opts =
|
|
18
|
+
options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def serialization_scope=(scope)
|
|
22
|
+
serializer_opts[:scope] = scope
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def serialization_scope
|
|
26
|
+
serializer_opts[:scope]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def serialization_scope_name=(scope_name)
|
|
30
|
+
serializer_opts[:scope_name] = scope_name
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# NOTE: if no adapter is available, returns the resource itself. (i.e. adapter is a no-op)
|
|
34
|
+
def adapter
|
|
35
|
+
@adapter ||= find_adapter
|
|
36
|
+
end
|
|
37
|
+
alias adapter_instance adapter
|
|
38
|
+
|
|
39
|
+
def find_adapter
|
|
40
|
+
return resource unless serializer?
|
|
41
|
+
adapter = catch :no_serializer do
|
|
42
|
+
ActiveModelSerializers::Adapter.create(serializer_instance, adapter_opts)
|
|
43
|
+
end
|
|
44
|
+
adapter || resource
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def serializer_instance
|
|
48
|
+
@serializer_instance ||= serializer.new(resource, serializer_opts)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Get serializer either explicitly :serializer or implicitly from resource
|
|
52
|
+
# Remove :serializer key from serializer_opts
|
|
53
|
+
# Remove :each_serializer if present and set as :serializer key
|
|
54
|
+
def serializer
|
|
55
|
+
@serializer ||=
|
|
56
|
+
begin
|
|
57
|
+
@serializer = serializer_opts.delete(:serializer)
|
|
58
|
+
@serializer ||= ActiveModel::Serializer.serializer_for(resource, serializer_opts)
|
|
59
|
+
|
|
60
|
+
if serializer_opts.key?(:each_serializer)
|
|
61
|
+
serializer_opts[:serializer] = serializer_opts.delete(:each_serializer)
|
|
62
|
+
end
|
|
63
|
+
@serializer
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
alias serializer_class serializer
|
|
67
|
+
|
|
68
|
+
# True when no explicit adapter given, or explicit appear is truthy (non-nil)
|
|
69
|
+
# False when explicit adapter is falsy (nil or false)
|
|
70
|
+
def use_adapter?
|
|
71
|
+
!(adapter_opts.key?(:adapter) && !adapter_opts[:adapter])
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def serializer?
|
|
75
|
+
use_adapter? && !serializer.nil?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
protected
|
|
79
|
+
|
|
80
|
+
attr_reader :resource, :adapter_opts, :serializer_opts
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'active_support/core_ext/array/extract_options'
|
|
2
|
+
module ActiveModelSerializers
|
|
3
|
+
class SerializationContext
|
|
4
|
+
class << self
|
|
5
|
+
attr_writer :url_helpers, :default_url_options
|
|
6
|
+
def url_helpers
|
|
7
|
+
@url_helpers ||= Module.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def default_url_options
|
|
11
|
+
@default_url_options ||= {}
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
module UrlHelpers
|
|
15
|
+
def self.included(base)
|
|
16
|
+
base.send(:include, SerializationContext.url_helpers)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def default_url_options
|
|
20
|
+
SerializationContext.default_url_options
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :request_url, :query_parameters, :key_transform
|
|
25
|
+
|
|
26
|
+
def initialize(*args)
|
|
27
|
+
options = args.extract_options!
|
|
28
|
+
if args.size == 1
|
|
29
|
+
request = args.pop
|
|
30
|
+
options[:request_url] = request.original_url[/\A[^?]+/]
|
|
31
|
+
options[:query_parameters] = request.query_parameters
|
|
32
|
+
end
|
|
33
|
+
@request_url = options.delete(:request_url)
|
|
34
|
+
@query_parameters = options.delete(:query_parameters)
|
|
35
|
+
@url_helpers = options.delete(:url_helpers) || self.class.url_helpers
|
|
36
|
+
@default_url_options = options.delete(:default_url_options) || self.class.default_url_options
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module ActiveModelSerializers
|
|
2
|
+
module Test
|
|
3
|
+
module Schema
|
|
4
|
+
# A Minitest Assertion that test the response is valid against a schema.
|
|
5
|
+
# @param schema_path [String] a custom schema path
|
|
6
|
+
# @param message [String] a custom error message
|
|
7
|
+
# @return [Boolean] true when the response is valid
|
|
8
|
+
# @return [Minitest::Assertion] when the response is invalid
|
|
9
|
+
# @example
|
|
10
|
+
# get :index
|
|
11
|
+
# assert_response_schema
|
|
12
|
+
def assert_response_schema(schema_path = nil, message = nil)
|
|
13
|
+
matcher = AssertResponseSchema.new(schema_path, request, response, message)
|
|
14
|
+
assert(matcher.call, matcher.message)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def assert_request_schema(schema_path = nil, message = nil)
|
|
18
|
+
matcher = AssertRequestSchema.new(schema_path, request, response, message)
|
|
19
|
+
assert(matcher.call, matcher.message)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# May be renamed
|
|
23
|
+
def assert_request_response_schema(schema_path = nil, message = nil)
|
|
24
|
+
assert_request_schema(schema_path, message)
|
|
25
|
+
assert_response_schema(schema_path, message)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def assert_schema(payload, schema_path = nil, message = nil)
|
|
29
|
+
matcher = AssertSchema.new(schema_path, request, response, message, payload)
|
|
30
|
+
assert(matcher.call, matcher.message)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
MissingSchema = Class.new(Minitest::Assertion)
|
|
34
|
+
InvalidSchemaError = Class.new(Minitest::Assertion)
|
|
35
|
+
|
|
36
|
+
class AssertSchema
|
|
37
|
+
attr_reader :schema_path, :request, :response, :message, :payload
|
|
38
|
+
|
|
39
|
+
# Interface may change.
|
|
40
|
+
def initialize(schema_path, request, response, message, payload = nil)
|
|
41
|
+
require_json_schema!
|
|
42
|
+
@request = request
|
|
43
|
+
@response = response
|
|
44
|
+
@payload = payload
|
|
45
|
+
@schema_path = schema_path || schema_path_default
|
|
46
|
+
@message = message
|
|
47
|
+
@document_store = JsonSchema::DocumentStore.new
|
|
48
|
+
add_schema_to_document_store
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def call
|
|
52
|
+
json_schema.expand_references!(store: document_store)
|
|
53
|
+
status, errors = json_schema.validate(response_body)
|
|
54
|
+
@message = [message, errors.map(&:to_s).to_sentence].compact.join(': ')
|
|
55
|
+
status
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
protected
|
|
59
|
+
|
|
60
|
+
attr_reader :document_store
|
|
61
|
+
|
|
62
|
+
def controller_path
|
|
63
|
+
request.filtered_parameters[:controller]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def action
|
|
67
|
+
request.filtered_parameters[:action]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def schema_directory
|
|
71
|
+
ActiveModelSerializers.config.schema_path
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def schema_full_path
|
|
75
|
+
"#{schema_directory}/#{schema_path}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def schema_path_default
|
|
79
|
+
"#{controller_path}/#{action}.json"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def schema_data
|
|
83
|
+
load_json_file(schema_full_path)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def response_body
|
|
87
|
+
load_json(response.body)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def request_params
|
|
91
|
+
request.env['action_dispatch.request.request_parameters']
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def json_schema
|
|
95
|
+
@json_schema ||= JsonSchema.parse!(schema_data)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def add_schema_to_document_store
|
|
99
|
+
Dir.glob("#{schema_directory}/**/*.json").each do |path|
|
|
100
|
+
schema_data = load_json_file(path)
|
|
101
|
+
extra_schema = JsonSchema.parse!(schema_data)
|
|
102
|
+
document_store.add_schema(extra_schema)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def load_json(json)
|
|
107
|
+
JSON.parse(json)
|
|
108
|
+
rescue JSON::ParserError => ex
|
|
109
|
+
raise InvalidSchemaError, ex.message
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def load_json_file(path)
|
|
113
|
+
load_json(File.read(path))
|
|
114
|
+
rescue Errno::ENOENT
|
|
115
|
+
raise MissingSchema, "No Schema file at #{schema_full_path}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def require_json_schema!
|
|
119
|
+
require 'json_schema'
|
|
120
|
+
rescue LoadError
|
|
121
|
+
raise LoadError, "You don't have json_schema installed in your application. Please add it to your Gemfile and run bundle install"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
class AssertResponseSchema < AssertSchema
|
|
125
|
+
def initialize(*)
|
|
126
|
+
super
|
|
127
|
+
@payload = response_body
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
class AssertRequestSchema < AssertSchema
|
|
131
|
+
def initialize(*)
|
|
132
|
+
super
|
|
133
|
+
@payload = request_params
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|