:_render_with_renderer_json
or:_render_option_json,
(e.g. see ActionController:: Serialization)# 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
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 default)
which basically calls json = @model.to_json(options)
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
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 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]
- def get_serializer(resource) | ||
- @_serializer ||= @_serializer_opts.delete(:serializer) | ||
- @_serializer ||= ActiveModel::Serializer.serializer_for(resource) | ||
- | ||
- if @_serializer_opts.key?(:each_serializer) | ||
- @_serializer_opts[:serializer] = @_serializer_opts.delete(:each_serializer) | ||
+ def get_serializer(resource, options = {}) | ||
+ serializable_resource = ActiveModel::Serializer::build(resource, options) do |builder| | ||
+ if builder.serializer? | ||
+ builder.serialization_scope ||= serialization_scope | ||
+ builder.serialization_scope_name = _serialization_scope | ||
+ builder.adapter | ||
+ else | ||
+ resource | ||
+ end | ||
end | ||
- | ||
- @_serializer | ||
end |
[:_render_option_json, :_render_with_renderer_json].each do |renderer_method| | ||
define_method renderer_method do |resource, options| |
- @_adapter_opts, @_serializer_opts = | ||
- options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] } | ||
- | ||
- if use_adapter? && (serializer = get_serializer(resource)) | ||
- | ||
- @_serializer_opts[:scope] ||= serialization_scope | ||
- @_serializer_opts[:scope_name] = _serialization_scope | ||
- | ||
- # omg hax | ||
- object = serializer.new(resource, @_serializer_opts) | ||
- adapter = ActiveModel::Serializer::Adapter.create(object, @_adapter_opts) | ||
- super(adapter, options) | ||
- else | ||
- super(resource, options) | ||
- end | ||
+ serializable_resource = get_serializer(resource, options) | ||
+ super(serializable_resource, options) | ||
end | ||
end |
--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 rubyonrails-co...@googlegroups.com.
To post to this group, send email to rubyonra...@googlegroups.com.
Visit this group at http://groups.google.com/group/rubyonrails-core.
For more options, visit https://groups.google.com/d/optout.
The renderer is basically a "serializer" that takes an object and an options hash and returns a string. The reason it is not greppable and uses meta-programming is that we allow for a huge list of formats other than json, and this is a generic set up that works well for most formats (including, imo, json). Do we only try to look up a serializer object for `render json: ...`? What about `render xml: ...`? `render text: ...`? So long as we don't special-case json or a handful of things (imo, that seems like a bad idea), I think we will still end up with the same problem with metaprogramming and greppability.