-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Question: Versioning Serializers #144
Comments
Hey @enrico ! This is a complex issue with many different answers. If I were you, I'd create a Since this is not a bug, and nobody's answered in a month, I'm giving it a close. |
Hi, Sorry for not reply until now, just forgot this thread I subscribed before resolving my similar case. I just created a custom responder who makes the right choice when selecting serializer to use: Include in your require 'app_responder'
require 'responders/serialized_responder'
class AppResponder < ActionController::Responder
include Responders::SerializedResponder
end
module Responders
module SerializedResponder
def initialize(controller, resources, options={})
super
serializer = (controller.class.name.gsub("Controller","").singularize + "Serializer").constantize
if resource.kind_of?(ActiveRecord::Relation)
options.merge!(arrayserializer: serializer)
else
options.merge!(serializer: serializer)
end
end
end
end With this you will get 2 things:
Maybe this addition could be added to https://github.com/plataformatec/responders Hope it helps |
Hey late to the party on this one, but I think its worth baking in some support for (optional) versioning. What I was thinking is something like this: class WhateverSerializer < ActiveModel::Serializer
root :whatever
version 1 do
attributes :id, :name, :body
end
# We've added some attributes here, we're just inheriting from the previous version
version 2 do
attributes :url, :created_at
end
# We've substantially changed the format, declarations here are made in an exclusive scope
version 3, :exclusive => true do
attributes :id, :name, :text, :something_else
def helper_method
# Override for v3
end
end
def helper_method
# do stuff
end
end You could then pass in the I'd be happy to champion this work if there's interest and consensus on the API direction -- we have a similar system brewing within our application that could be generalized and polished if there's a mergeable path forward. |
+1 |
I like @blakewatters idea. Any progress on this? |
I like @blakewatters idea too. Maybe no one is reading this since the issue was closed a while back? |
https://github.com/hookercookerman/active_model_version_serializers |
actually, what @blakewatters is describing is much better |
application_controller.rb: def default_serializer_options(options = {})
name = controller_name.classify
object = "V#{params[:api_version]}::#{name}Serializer"
options.merge(serializer: object.constantize)
end routes.rb: Rails.application.routes.draw do
constraints subdomain: 'api', format: 'json' do
scope '/v1', api_version: 1 do
resources :messages
end
end
end |
@plehoux that won't work for |
I like @blakewatters idea. That looks like I want it to look. It's going to be needed for many people soon enough. I think this is a great topic to be open, not closed. You can only find this when you google the topic. It's an open issue in a very real way. |
Unfortunately I am no longer responsible for any Rails applications so its unlikely that I’ll be implementing this myself. I still think the API design is what you really want though :-) |
I agree this should be re-opened. Not sure I agree with @blakewatters solution. What if I had a serializer that took 100 lines of code or more to define, and what if each new version added another large block of code. What if it gets up to 15 or 20 versions long? I'd prefer to keep things separated. Leaning more towards @juljimm's proposal |
+1 |
I also like @juljimm's approach. Follows the same pattern as scoping controllers. |
+1 |
I do believe working with versions should be easier, but I don't think it should be in the serializer for some reasons:
|
I think this should work the same way rails's controller and views work using the action view lookup context. It makes the most sense and was sort of what I expected to begin with. Except instead of looking up templates we'd be looking up serializers |
@steveklabnik This should really be re-opened as a feature request/enhancement. This is pretty much why I'm still using jBuilder. |
@MarkMurphy Agreed. @steveklabnik My v1 is using AMS but dealing with AMS for v2 was painful, we retreated to jBuilder (just for v2 though). +1 on reopening as a feature request/enhancement. |
👎 I'm of the opinion that versioning should happen with namespaces. Controllers class Api::V1::ApiController < ActionController::Base
end
class Api::V2::ApiController < Api::V1::ApiController
end Routes scope 'apis', module: 'api', defaults: {format: :json} do
namespace 'v1' do
resource ...
end
#or
scope 'v2/:api_key', module: 'v2' do
resources ...
end
end Serializers class V1::MySerializer < ActiveModel::Serializer
end
class V2::MySerializer < V1::MySerializer
#slightly different serializer attributes perhaps
end |
@saneshark I tried the namespacing route, and things are not very consistent. I've posted this issue at #804. |
I agree with you. I've set my routes and controllers up in a similar fashion. My suggestion still stands I'm not sure what your thumbs down was for. |
I'd like to try and be more clear with what I'm suggesting. I'm saying that if the For example: # app/controllers/api/v1/users_controller.rb
module API::V1
class UsersController < APIController
def show
@user = User.find(params[:id])
render json: @user
end
end
end Based on the above controllers' namespace, AMS would look for a User serializer at the following locations in order of priority:
So when api version 2 comes around you'll have the following: # app/controllers/api/v2/users_controller.rb
module API::V2
class UsersController < APIController
def show
@user = User.find(params[:id])
render json: @user
end
end
end Based on the above controllers' namespace, AMS would look for a User serializer at the following locations in order of priority:
Routes would look something like this: # config/routes.rb
constraints subdomain: 'api' do
namespace :api, path: nil, except: [:new, :edit], defaults: { format: 'json' } do
# API V1
constraints API::VersionConstraint.new(version: 1) do
scope module: 'v1' do
resources :users
end
end
# API V2
constraints API::VersionConstraint.new(version: 2, default: true) do
scope module: 'v2' do
resources :users
end
end
end
end |
@MarkMurphy I didn't realize there was this issue when you have the associations within a serializer. It does look like it should be easier to implement in that regard. My downvote was particularly in regard to having versions designated within a serializer it self in the fashion described by @blakewatters and others as that flies in the face of how most people implement version controllers and routes. One would think serializers should work similarly, indeed what I love about this gem is that it's not I had to hack my collection serializers to support mixed classes for STI. I also had to hack my standard Serializer for a few cases in which I needed a has_one relationship dynamically. I rather like the flexibility of being able to hack together solutions like these personally. class PaginationSerializer < ActiveModel::ArraySerializer
def initialize(object, options={})
meta_key = options[:meta_key] || :meta
options[meta_key] ||= {}
options[meta_key][:total_count] = object.total_entries.try(:to_i)
options[meta_key][:pagination] = {
previous: object.previous_page.try(:to_i),
next: object.next_page.try(:to_i),
current: object.current_page.try(:to_i),
per_page: object.per_page.try(:to_i),
pages: object.total_pages.try(:to_i)
}
#need to do this hack to make sure it works for STI subclasses as well.
options[:each_serializer] = options[:each_serializer] || get_serializer_for(object)
super(object, options)
end
private
def get_serializer_for(klass)
serializer_class_name = "#{klass.name}Serializer"
serializer_class = serializer_class_name.safe_constantize
if serializer_class
serializer_class
elsif klass.superclass
get_serializer_for(klass.superclass)
end
end
end So I'm thinking one could expand on that class ExampleSerializer < ActiveModel::Serializer
attributes :child_id, :created_at, :updated_at, :associated_class_name, :type
def initialize(object, options={})
self.class.has_one(:sibling) if object.try(:sibling)
self.class.has_one(:parent) if object.try(:parent)
super
end
def filter(keys)
keys.delete :sibling unless object.try(:reference_class) == :sibling && object.reference_id.present?
keys.delete :parent unless object.parent_id.present?
keys
end
def associated_class_name
object.parent_id.present? ? :parent : object.reference_class
end
end Similarly, here I had to dynamically add in has_one relationships based on the presence of associations so as to not have to write a serializer for every single STI class and its various potential associations. Haven't ran into an issue with versioning on this one per se, but would most likely specify the serializer with the appropriate namespace required should that be necessary. Not entirely sure if this helps you or not, but it illustrates how flexible I think AMS is to adapt for some interesting scenarios. I'd personally hate for that to change. |
@MarkMurphy Your suggested implementation is exactly what I did. My Inside my That is where the problem is for me, associations are defaulted to the bottom order of priority, at least it is for me. |
@chadwtaylor It sounds like there are two different but related issues at play. When you say you were able to call The secondary issue but also related is the fact that your I think both of these issues could be solved the same way or similarly using what I've previously outlined. Doing a bit more digging into how view paths are found in rails it looks like we need to build an AMS version of this: https://github.com/rails/rails/blob/6d72485b6977804a872dcfa5d7ade2cec74c768e/actionview/lib/action_view/lookup_context.rb |
@MarkMurphy The only reason I explicitly tell the controller to use a certain serializer was one of my troubleshooting processes. I agree that we shouldn't need to explicitly tell it if we follow the namespacing route as it would be large and tedious as you mentioned. I will continue to troubleshoot things and if I continue to have no luck with getting things to work with all the namespacing implementations, I'll report back here. I appreciate your patience and support @MarkMurphy! |
@steveklabnik this seems to be an open issue, by most accounts; thoughts on re-opening? @chadwtaylor @MarkMurphy appreciate the efforts and agree with y'alls direction here. Are y'all working off master (e.g. 0.10.x) or other (e.g. 0.8.x)? I'm transitioning from jbuilder to AMS and concerned about this versioning issue as well. Any update on progress, or any way I can assist? |
…so I wrote this gem: https://github.com/skalee/active_model_serializers-namespaces It defines It integrates seamlessly without any Rails hacking. Works across associations and collections. Just one drawback: you can no longer override Works with Active Model Serializers 0.8.x only. |
@eprothro I haven't put forth any other effort other than outlining the potential solution. It's tough to find the time. I'm still using jbuilder and will continue to until this is resolved. |
@eprothro Indeed the best way of versioning your serializers right now would be using namespaces. |
@joaomdmoura I've already proposed an implementation in my previous comments. Essentially the same implementation rails controllers use to lookup views. Feel free to extract those into a new issue. |
@joaomdmoura I agree with @MarkMurphy architecturally, serializers handling lookup context to more robustly support namespaced serializers feels like the right way forward. Opened #886 to continue any discussion in that context. |
Hello,
I just started playing with active model serializers.
Although my API is v1, I am trying to understand what the scenario looks like when I have to release v2.
What's the recommended way to handle different serialized attributes depending on version in active model serializers?
Right now, I'm planning to have 2 sets of namespaced API controllers, but wanted to know if it makes sense to also namespacing the serializer classes....
If I do this, will the has_many and has_one methods respect the namespace?
Does anyone have any input on what the recommended way is?
Thanks in advance.
The text was updated successfully, but these errors were encountered: