diff --git a/README.md b/README.md index 9c7fd6fd2..a6fe7881c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,21 @@ [![Build Status](https://api.travis-ci.org/rails-api/active_model_serializers.png?branch=0-9-stable)](https://travis-ci.org/rails-api/active_model_serializers) [![Code Climate](https://codeclimate.com/github/rails-api/active_model_serializers.png)](https://codeclimate.com/github/rails-api/active_model_serializers) +## This Fork + +Adds polymorphic support for serializers + +Allows the the passed in `context` option to be passed through to a `has_many` +association (by modifying the `ArraySerializer`). This can be used by +specifying `:context` as a key when invoking the serializer, either via `.new` +or via `respond_with`/`to_json`. + +Context example: + + respond_with(:api, :v2, @event, serializer: ::API::V2::EventSerializer, context: { render_guest: true }) do |format| + # Render code + end + # ActiveModel::Serializers ## Purpose @@ -235,7 +250,6 @@ end You can specify that serializers use the lower-camel key format at the config, class or instance level. ```ruby - ActiveModel::Serializer.setup do |config| config.key_format = :lower_camel end @@ -251,11 +265,11 @@ BlogSerializer.new(object, key_format: :lower_camel) You can specify that serializers use unsuffixed names as association keys by default. -`````ruby +```ruby ActiveModel::Serializer.setup do |config| config.default_key_type = :name end -```` +``` This will build association keys like `comments` or `author` instead of `comment_ids` or `author_id`. @@ -805,6 +819,29 @@ end } ``` +You can also define serializers for a polymorphic relationship like so: + + +```ruby +class PostSerializer < ActiveModel::Serializer + attributes :id, :title + has_many :attachments, polymorphic: true, each_serializer: { video: SpecialVideoSerializer, audio: AudioSummarySerializer } +end + +``` + +Or, for `has_one` relationships: + +```ruby +class PostSerializer < ActiveModel::Serializer + attributes :id, :title + has_one :attachment, polymorphic: true, serializer: { video: SpecialVideoSerializer, audio: AudioSummarySerializer } +end +``` + +In this case, if the `attachment` is a Video object, instead of using the +standard VideoSerializer, the SpecialVideoSerializer will be used. Any other +objects not defined by the hash will use the default serializers. ## Customizing Scope diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index df23aa956..4c840b518 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -60,7 +60,7 @@ def serialization_scope(scope) private def namespace_for_serializer - @namespace_for_serializer ||= self.class.parent unless self.class.parent == Object + @namespace_for_serializer ||= self.class.module_parent unless self.class.module_parent == Object end def default_serializer(resource) @@ -87,8 +87,8 @@ def build_json_serializer(resource, options = {}) if serializer = options.fetch(:serializer, default_serializer(resource)) options[:scope] = serialization_scope unless options.has_key?(:scope) - if resource.respond_to?(:each) - options[:resource_name] = controller_name + if resource.respond_to?(:each) && !resource.is_a?(Hash) + options[:resource_name] = controller_name options[:namespace] = namespace_for_serializer if namespace_for_serializer end diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index e819de3df..a5e143417 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -18,12 +18,14 @@ def initialize(object, options={}) @polymorphic = options.fetch(:polymorphic, false) @meta_key = options[:meta_key] || :meta @meta = options[@meta_key] - @each_serializer = options[:each_serializer] @resource_name = options[:resource_name] @only = options[:only] ? Array(options[:only]) : nil @except = options[:except] ? Array(options[:except]) : nil @namespace = options[:namespace] @key_format = options[:key_format] || options[:each_serializer].try(:key_format) + @each_serializer = options[:each_serializer] + @each_serializer_from_options = @each_serializer.is_a?(Hash) ? nil : @each_serializer + @context = options[:context] end attr_accessor :object, :scope, :root, :meta_key, :meta, :key_format @@ -34,8 +36,8 @@ def json_key end def serializer_for(item) - serializer_class = @each_serializer || Serializer.serializer_for(item, namespace: @namespace) || DefaultSerializer - serializer_class.new(item, scope: scope, key_format: key_format, only: @only, except: @except, polymorphic: @polymorphic, namespace: @namespace) + serializer_class = @each_serializer_from_options || Serializer.serializer_for(item, namespace: @namespace) || DefaultSerializer + serializer_class.new(item, scope: scope, key_format: key_format, only: @only, except: @except, polymorphic: @polymorphic, namespace: @namespace, context: @context) end def serializable_object(options={}) diff --git a/lib/active_model/polymorphic_array_serializer.rb b/lib/active_model/polymorphic_array_serializer.rb new file mode 100644 index 000000000..1e20add0b --- /dev/null +++ b/lib/active_model/polymorphic_array_serializer.rb @@ -0,0 +1,13 @@ +module ActiveModel + class PolymorphicArraySerializer < ArraySerializer + def serializer_for(item) + if @each_serializer.is_a?(Hash) && polymorphic? + polymorphic_object_key = item.class.name.underscore.to_sym + return nil unless @each_serializer.has_key?(polymorphic_object_key) + @each_serializer[polymorphic_object_key] + else + super + end + end + end +end diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 7a6ee7ca2..79651c09a 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -56,7 +56,7 @@ def format_keys(format) attr_reader :key_format def serializer_for(resource, options = {}) - if resource.respond_to?(:each) + if resource.respond_to?(:each) && !resource.is_a?(Hash) if Object.constants.include?(:ArraySerializer) ::ArraySerializer else @@ -230,7 +230,7 @@ def association_options_for_serializer(association) prefix = association.options[:prefix] namespace = association.options[:namespace] || @namespace || self.namespace - { scope: scope }.tap do |opts| + { scope: scope, context: context }.tap do |opts| opts[:namespace] = namespace if namespace opts[:prefix] = prefix if prefix end diff --git a/lib/active_model/serializer/association.rb b/lib/active_model/serializer/association.rb index 5ea22056d..781e46acf 100644 --- a/lib/active_model/serializer/association.rb +++ b/lib/active_model/serializer/association.rb @@ -24,8 +24,14 @@ def initialize(name, options={}) @embed_in_root_key = options.fetch(:embed_in_root_key) { CONFIG.embed_in_root_key } @embed_namespace = options.fetch(:embed_namespace) { CONFIG.embed_namespace } - serializer = @options[:serializer] - @serializer_from_options = serializer.is_a?(String) ? serializer.constantize : serializer + @serializer = options[:serializer] + @serializer_from_options = if @serializer.is_a?(Hash) + nil + elsif @serializer.is_a?(String) + @serializer.constantize + else + @serializer + end end attr_reader :name, :embed_ids, :embed_objects, :polymorphic @@ -43,7 +49,13 @@ def embed=(embed) end def serializer_from_object(object, options = {}) - Serializer.serializer_for(object, options) + if @serializer.is_a?(Hash) && polymorphic? + polymorphic_object_key = object.class.name.underscore.to_sym + return nil unless @serializer.has_key?(polymorphic_object_key) + @serializer[polymorphic_object_key] + else + Serializer.serializer_for(object, options) + end end def default_serializer diff --git a/lib/active_model/serializer/association/has_many.rb b/lib/active_model/serializer/association/has_many.rb index f05e92181..da49e3213 100644 --- a/lib/active_model/serializer/association/has_many.rb +++ b/lib/active_model/serializer/association/has_many.rb @@ -1,3 +1,5 @@ +require 'active_model/polymorphic_array_serializer' + module ActiveModel class Serializer class Association @@ -13,7 +15,7 @@ def initialize(name, *args) def serializer_class(object, _) if use_array_serializer? - ArraySerializer + polymorphic? ? PolymorphicArraySerializer : ArraySerializer else serializer_from_options end diff --git a/lib/active_model/serializer/association/has_one.rb b/lib/active_model/serializer/association/has_one.rb index 38c8b9bcb..e9763aa55 100644 --- a/lib/active_model/serializer/association/has_one.rb +++ b/lib/active_model/serializer/association/has_one.rb @@ -22,4 +22,4 @@ def build_serializer(object, options = {}) end end end -end \ No newline at end of file +end diff --git a/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb b/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb index 6647683a1..41f0ed13d 100644 --- a/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +++ b/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb @@ -7,7 +7,7 @@ class ScaffoldControllerGenerator if Rails::VERSION::MAJOR >= 4 source_root File.expand_path('../templates', __FILE__) - hook_for :serializer, default: true + hook_for :serializer, default: true, type: :boolean end end end diff --git a/test/integration/action_controller/serialization_test.rb b/test/integration/action_controller/serialization_test.rb index e3ec71b34..d5ae5434d 100644 --- a/test/integration/action_controller/serialization_test.rb +++ b/test/integration/action_controller/serialization_test.rb @@ -299,5 +299,21 @@ def test_render_array assert_equal '{"my":[{"name":"Name 1"}]}', @response.body end end + + class SimpleHashObjectTest < ActionController::TestCase + class MyController < ActionController::Base + def render_hash + render json: { name: 'Name 1', description: 'Description 1', comments: 'Comments 1' } + end + end + + tests MyController + + def test_render_hash + get :render_hash + assert_equal 'application/json', @response.content_type + assert_equal '{"name":"Name 1","description":"Description 1","comments":"Comments 1"}', @response.body + end + end end end