Adapted from original comment 
at https://github.com/rails/rails/pull/19832#discussion_r30468412

Since this post is long, I've divided it into sections: 

   1. Summary
   2. Background 
   1. How Serializers Work Now
      2. How Renderers Work Now
      3. Example code changing the renderer
      4. Example code changing the serializer
      3. Proposal



*Summary:*

I propose there be a first-class serializer interface in Rails much like 
there is for renderers.
Proposed interfaces are: serializer_for  and ActionController::Serializers
.register

*Background:*

*  How serializers work now:*
Right now, that interface is a private-looking method 
:_render_with_renderer_json or:_render_option_json, (e.g. see 
ActionController:: Serialization 
<https://github.com/rails-api/active_model_serializers/pull/675/files#diff-07e5a5aaa325f8dc2bcf944d12f0db83R9>
)

Such that creating a serializer is complicated and non-obvious

# When Rails renders JSON, it calls the json renderer method# 
"_render_with_renderer_#{key = json}"# 
https://github.com/rails/rails/blob/8c2c1bccccb3ea1f22503a184429d94193a1d9aa/actionpack/lib/action_controller/metal/renderers.rb#L44-L45#
 which is defined in AMS 
https://github.com/rails-api/active_model_serializers/blob/1577969cb76309d1f48c68facc73e44c49489744/lib/action_controller/serialization.rb#L36-L37
    [:_render_option_json, :_render_with_renderer_json].each do 
|renderer_method|
      define_method renderer_method do |resource, options|
          super(adapter, options)
      end
    end


which is *really* hard to find in the source code... since it calls 
"_render_with_renderer_#{key}" where the key is json...

# 
https://github.com/rails/rails/blob/8c2c1bccccb3ea1f22503a184429d94193a1d9aa/actionpack/lib/action_controller/metal/renderers.rb#L44-L45
   def _render_to_body_with_renderer(options)
      _renderers.each do |name|
        if options.key?(name)
          _process_options(options)
          method_name = Renderers._render_with_renderer_method_name(name)
          return send(method_name, options.delete(name), options)
        end
      end
      nil
    end

    # A Set containing renderer names that correspond to available renderer 
procs.
    # Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
    RENDERERS = Set.new

    def self._render_with_renderer_method_name(key)
      "_render_with_renderer_#{key}"
    end

that is, *it calls "send(:render_with_renderer_json, @model, options)" 
which is not greppable in the code base*
(`@model` is the argument to json: in the controller, for example `render 
json: @model`)


*  How renderers work now:*

How does the renderer fit into this?  Well, when a controller has

render json: @model

the  @model  is passed to the JSON Renderer (see ActionController::Renderers 
<https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_controller/metal/renderers.rb#L66-L128>
 
default)

which basically calls  json = @model.to_json(options)
*  Example code changing the renderer:*

If, for example, I wanted to change how the JSON was rendered, to pretty 
print it, I would just redefine the :json renderer as below

# 
https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_controller/metal/renderers.rb#L66-L128#
 
https://github.com/rails/rails/blob/4-2-stable//actionview/lib/action_view/renderer/renderer.rb#L32
 ActionController::Renderers.remove :json
 ActionController::Renderers.add :json do |json, options|
    if !json.is_a?(String)
      # changed from
      # json = json.to_json(options)
      # changed to
      json = json.as_json(options) if json.respond_to?(:as_json)
      json = JSON.pretty_generate(json, options)
    end

    if options[:callback].present?
      if content_type.nil? || content_type == Mime::JSON
        self.content_type = Mime::JS
      end

      "/**/#{options[:callback]}(#{json})"
    else
      self.content_type ||= Mime::JSON
      json
    end
  end

*  Example code changing the serializer:*

But, to change how the @model is serialized, a library such as what 
ActiveModelSerializers overrides :_render_option_json, 
:_render_with_renderer_json]

to basically change @model = ModelSerializer.new(@model) so that the 
renderer is calling to_json/as_json on the serializer

I think this could be way better:

*PROPOSAL:*

1)  Renderer 
<https://github.com/rails/rails/blob/8c2c1bccccb3ea1f22503a184429d94193a1d9aa/actionpack/lib/action_controller/metal/renderers.rb#L44-L45>
 could have a method serializer_for that can by default returns its 
argument, but can be overridden in the controller

    add :json do |json, options|-      json = json.to_json(options) unless 
json.kind_of?(String)+      json = serializer_for(json).to_json(options) unless 
json.kind_of?(String)

    example controller code:

def serializer_for(model)
  ActiveModel::Serializer::Adapter.create(
    ActiveModel::Serializer.serializer_for(resource).new(resource, 
serializer_opts),
    adapter_opts
  ) end

or

2) have a serializer registry (like renderers and mime-types have), that 
may be called in a method just as in #1

 ActionController::Serializers.register :user, UserSerializer, only: [:json]

*Benefits:*

- Simple, clear, extendable interface for model serialization
- Less meta-programming
- I wouldn't have needed to spend hours in the debugger and rails source 
code trying to find out the path from `render json: @model` to 
`_render_with_renderer_json(@model, options)`, 


Thanks for your thoughts!

-Benjamin

-- 
You received this message because you are subscribed to the Google Groups "Ruby 
on Rails: Core" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/rubyonrails-core.
For more options, visit https://groups.google.com/d/optout.

Reply via email to