I have name-spaced my API controllers like so:
module EngineName
class Post < ActiveRecord::Base; end
class API::V1::APIController < ActionController::API; end
class API::V1::PostsController < API::V1::APIController
def index
@posts = Post.all
render json: @posts
end
end
end
Since each API version might serialize the models differently, ideally the serializers should be namespaced accordingly like so:
module EngineName
class Post < ActiveRecord::Base; end
class API::V1::PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body
end
class API::V1::APIController < ActionController::API; end
class API::V1::PostsController < API::V1::APIController
def index
@posts = Post.all
render json: @posts, serializer: PostSerializer # resolves to ::EngineName::API::V1::PostSerializer
end
end
end
This gets quite repetitive when you have to do it for all your controllers. I am wondering if it is possible to take into account of the controller's scope when looking up a serializer. In the example above, render json: @posts should these in order (via controller.class.const_get :PostSerializer):
::EngineName::API::V1::PostsController::PostSerializer
::EngineName::API::V1::PostSerializer
::EngineName::API::PostSerializer
::EngineName::PostSerializer
::PostSerializer
(I also thought about overriding default_serializer_options in APIController, but that won't work as the options are merged _after_ the serializer lookup, so def default_serializer_options; { serializer: SomeSerializer }; end won't work.)
With #114, you can now work around this by doing this:
class ApplicationController < ActionController::Base
def default_serializer_options
serializer_name = (self.class.name.demodulize.sub(/Controller$/,'').singularize + 'Serializer').to_sym
serializer = self.class.const_get serializer_name
if serializer
super.merge(serializer: serializer)
else
super
end
end
end
But this is obviously very hackish (it relies on controller name matching the model), so I'm still looking for a better solution. Perhaps make active_model_serializer (the method on the model) take an optional "lookup_context" argument?
Has anything happened with this?
Nope. I think it has to do with this is just pretty damn hard to implement cleanly..
Sent from my phone
On 2012-10-04, at 7:41 PM, Christopher Bull [email protected] wrote:
Has anything happened with this?
—
Reply to this email directly or view it on GitHub.
Yep. I don't see how we can support something like this in a clean manner. If you want to do crazy nesting, you should figure out how to handle your complexity. I don't think we can support this effectively in the general case.
I've been looking to integrate this into our app (versioned api), but haven't been able to because of the name spacing of the controllers. Has there been any effort to revise this? Am I correct in understanding that the intent is to version models with the versioned controller API? If so, then this gem specifically excludes the concept of versioning your API...? Open to revisions?
This gem specifically does nothing about versioning.
Serializers are supposed to be 1-Many with models, not 1-1. Make two different serializers for two different versions.
@chancancode hi awesome job on the PR!
Looks like the ability to call super.merge was removed in the latest. Do you recommend another workaround for adopting this kind of versioning support?
@chourobin Why? As far as I can this should still work. The default implementation of default_serializer_options is empty so you'll have to do (super || {}).merge( ... ), but other than that if it doesn't work you probably ran into a bug.
@chancancode : Thank you for the trick.
However when dealing with Mongoid::Criteria it seems to be harder than that.
First, I was previously obliged to use these lines to support Criteria :
Mongoid::Document.send(:include, ActiveModel::SerializerSupport)
Mongoid::Criteria.delegate(:active_model_serializer, to: :to_a)
It worked, but then, since I introduced the default_serializer_options trick, all these workarounds lamentably started to fail and I always get :
V1::UserSerializer is not an ArraySerializer. You may want to use the :each_serializer option instead.
Hello, we have some methods to support serializers namespace.
I don't know if it is a pretty solution but temporary it works.
I hope it helps.
class ApplicationController < ActionController::Base
private
def default_serializer_options
{
serializer_key => serializer
}
end
def namespace
self.class.to_s.deconstantize
end
def serializer_key
single_action? ? :serializer : :each_serializer
end
def serializer
namespaced_serializer || active_model_serializer || default_serializer
end
def namespaced_serializer
"#{namespace}::#{serializer_name}".constantize rescue nil
end
def active_model_serializer
serializer_name.constantize rescue nil
end
def default_serializer
ActiveModel::DefaultSerializer
end
def serializer_name
"#{send(:resource_class).model_name}Serializer"
end
def single_action?
!collection_actions.include?(params[:action])
end
def collection_actions
%w(index)
end
end
Cool !
I hacked a bit on your code and I managed to get it worked for me.
Thank you @franciscodelgadodev
:D you're welcome.
won't this break for has_one and has_many (where the serializer name is assumed from the object)?
in other words, what happens if you have
class V1::PostSerializer < ActiveModel::Serializer
has_many :comments #will it know the correct version to use?
end
class V1::CommentSerializer < ActiveModel::Serializer
end
@chancancode @franciscodelgadodev @chourobin
By using default_serializer_options to manually override the serializer name you also create a side effect. Imagine that you have something like that in your ApplicationController :
rescue_from(ActionController::ParameterMissing) do |exception|
error = {}
error[exception.param] = ['parameter is required']
response = { errors: [error] }
render json: response, status: :unprocessable_entity
end
(JSON rendering on exceptions raised by strong_parameters gem).
If the following exception is raised from, let's say, V1::UsersController, then default_serializer_options method will use V1::UserSerializer because of your hack, rather than just returning false on that portion of code :
def build_json(controller, resource, options)
default_options = controller.send(:default_serializer_options) || {}
options = default_options.merge(options || {})
serializer = options.delete(:serializer) ||
(resource.respond_to?(:active_model_serializer) &&
resource.active_model_serializer)
return serializer unless serializer
which leads to that kind of errors :
NoMethodError (undefined method `read_attribute_for_serialization' for {:errors=>[{:auth_token=>["parameter is required"]}]}:Hash):
Side effects could be even worse if you have an ErrorsController whose responsibility is to render JSON errors on 404, 422, 500 ...
That's sad because, indeed, it would be interesting to respect controller namespace when looking up serializers.
If some people here have (safe enough) solutions to add this behaviour to AMS, please share. :heart: .
You can manually render the response by replacing render json: response, status: :unprocessable_entity with render json: response.to_json, status: :unprocessable_entity.
That said, I think AM::S could provide some better hooks for this purpose. I started a new fork lately to experiment with that concept, my goal is to eventually propose those changes here when I figured it out and hopefully have that merged upstream.
@chancancode
I had already tried to do a render json: response.to_json, status: :unprocessable_entity in the previous rescue_from block, however it still uses AMS, calls default_serializer_options method and I end up with the same read_attribute_for_serialization undefined method error.
But it works if I do the following (and ugly) thing :
render json: response, serializer: nil
That said, I think AM::S could provide some better hooks for this purpose. I started a new fork lately to experiment with that concept, my goal is to eventually propose those changes here when I figured it out and hopefully have that merged upstream.
Awesome ! ;)
PS : I'm on 0.8.1 version of AMS.
Anyone coming across this issue in the brave new world of 2017 should look here: https://github.com/rails-api/active_model_serializers/blob/master/docs/general/rendering.md#namespace
Anyone coming across this issue in the brave new world of 2018 should look here:
Most helpful comment
Anyone coming across this issue in the brave new world of 2018 should look here: