2015-08-18 17 views
18

Ich Rails 4.2.1 und active_model_serializers 0.10.0.rc2Serialisierung tief verschachtelte Assoziationen mit active_model_serializers

mit Ich bin neu in API und wählte active_model_serializers, weil es immer mehr zum Standard für Schienen zu sein scheint (Obwohl ich nicht auf der Verwendung RABL Gegensatz bin oder ein anderer Serialisierer)

Das Problem, das ich habe, ist, dass ich nicht scheinen kann, verschiedene Attribute in Multilevelbeziehungen einzuschließen. Zum Beispiel habe ich:

Projekte

class ProjectSerializer < ActiveModel::Serializer 
    attributes      :id, 
            :name, 
            :updated_at 

    has_many      :estimates, include_nested_associations: true 

end 

und

class EstimateSerializer < ActiveModel::Serializer 
    attributes      :id, 
            :name, 
            :release_version, 
            :exchange_rate, 
            :updated_at, 

            :project_id, 
            :project_code_id, 
            :tax_type_id 

    belongs_to      :project 
    belongs_to      :project_code 
    belongs_to      :tax_type 

    has_many      :proposals 

end 

Vorschläge

class ProposalSerializer < ActiveModel::Serializer 
    attributes      :id, 
            :name, 
            :updated_at, 

            :estimate_id 

    belongs_to      :estimate 
end 

Schätzungen Wenn Ich schlug die /projects/1 die oben erzeugt:

{ 
    "id": 1, 
    "name": "123 Park Ave.", 
    "updated_at": "2015-08-09T02:36:23.950Z", 
    "estimates": [ 
    { 
     "id": 1, 
     "name": "E1", 
     "release_version": "v1.0", 
     "exchange_rate": "0.0", 
     "updated_at": "2015-08-12T04:23:38.183Z", 
     "project_id": 1, 
     "project_code_id": 8, 
     "tax_type_id": 1 
    } 
    ] 
} 

Aber was ich mag würde es produzieren:

{ 
    "id": 1, 
    "name": "123 Park Ave.", 
    "updated_at": "2015-08-09T02:36:23.950Z", 
    "estimates": [ 
    { 
     "id": 1, 
     "name": "E1", 
     "release_version": "v1.0", 
     "exchange_rate": "0.0", 
     "updated_at": "2015-08-12T04:23:38.183Z", 
     "project": { 
     "id": 1, 
     "name": "123 Park Ave." 
     }, 
     "project_code": { 
     "id": 8, 
     "valuation": 30 
     }, 
     "tax_type": { 
     "id": 1, 
     "name": "no-tax" 
     }, 
     "proposals": [ 
     { 
      "id": 1, 
      "name": "P1", 
      "updated_at": "2015-08-12T04:23:38.183Z" 
     }, 
     { 
      "id": 2, 
      "name": "P2", 
      "updated_at": "2015-10-12T04:23:38.183Z" 
     } 
     ] 
    } 
    ] 
} 

Idealerweise würde ich auch in der Lage sein möchten angeben, welche Attribute, Verbände und Attribute dieser Zuordnungen, die in jedem Serializer enthalten sind.

Ich habe durch die AMS-Probleme geschaut, und es scheint einige hin und her zu geben, wie dies behandelt werden sollte (oder wenn diese Art von Funktionalität sogar tatsächlich unterstützt wird), aber ich habe Schwierigkeiten herauszufinden genau wie der aktuelle Zustand ist.

Eine der vorgeschlagenen Lösungen war das Attribut mit einer Methode zu überschreiben die verschachtelten Attribute zu nennen, aber das scheint zu betrachten als Hack, also wollte ich es möglichst vermeiden.

Wie auch immer, ein Beispiel dafür, wie man diesen oder einen allgemeinen API-Rat einhält, wäre sehr willkommen.

Antwort

8

Also das ist nicht die beste oder sogar eine gute Antwort, aber das funktioniert, wie ich es brauche.

Während die Integration geschachtelter und seitengeladener Attribute bei Verwendung des json_api Adapters mit AMS unterstützt wird, benötigte ich Unterstützung für flat json. Außerdem funktionierte diese Methode gut, weil jeder Serializer genau das erzeugt, was ich brauche, um unabhängig von einem anderen Serialisierer zu sein und ohne irgendetwas in der Steuerung zu tun.

Kommentare/alternative Methoden sind immer willkommen.

Projektmodell

class Project < ActiveRecord::Base  
    has_many :estimates, autosave: true, dependent: :destroy 
end 

ProjectsController

def index 
    @projects = Project.all 
    render json: @projects 
end 

ProjectSerializer

class ProjectSerializer < ActiveModel::Serializer 
    attributes :id, 
       :name, 
       :updated_at, 

       # has_many 
       :estimates 



    def estimates 
    customized_estimates = [] 

    object.estimates.each do |estimate| 
     # Assign object attributes (returns a hash) 
     # =========================================================== 
     custom_estimate = estimate.attributes 


     # Custom nested and side-loaded attributes 
     # =========================================================== 
     # belongs_to 
     custom_estimate[:project] = estimate.project.slice(:id, :name) # get only :id and :name for the project 
     custom_estimate[:project_code] = estimate.project_code 
     custom_estimate[:tax_type] = estimate.tax_type 

     # has_many w/only specified attributes 
     custom_estimate[:proposals] = estimate.proposals.collect{|proposal| proposal.slice(:id, :name, :updated_at)} 

     # =========================================================== 
     customized_estimates.push(custom_estimate) 
    end 

    return customized_estimates 
    end 
end 

Ergebnis

[ 
    { 
    "id": 1, 
    "name": "123 Park Ave.", 
    "updated_at": "2015-08-09T02:36:23.950Z", 
    "estimates": [ 
     { 
     "id": 1, 
     "name": "E1", 
     "release_version": "v1.0", 
     "exchange_rate": "0.0", 
     "created_at": "2015-08-12T04:23:38.183Z", 
     "updated_at": "2015-08-12T04:23:38.183Z", 
     "project": { 
      "id": 1, 
      "name": "123 Park Ave." 
     }, 
     "project_code": { 
      "id": 8, 
      "valuation": 30, 
      "created_at": "2015-08-09T18:02:42.079Z", 
      "updated_at": "2015-08-09T18:02:42.079Z" 
     }, 
     "tax_type": { 
      "id": 1, 
      "name": "No Tax", 
      "created_at": "2015-08-09T18:02:42.079Z", 
      "updated_at": "2015-08-09T18:02:42.079Z" 
     }, 
     "proposals": [ 
      { 
      "id": 1, 
      "name": "P1", 
      "updated_at": "2015-08-12T04:23:38.183Z" 
      }, 
      { 
      "id": 2, 
      "name": "P2", 
      "updated_at": "2015-10-12T04:23:38.183Z" 
      } 
     ] 
     } 
    ] 
    } 
] 

ich irgendwelche has_many oder belongs_to Verbände in dem Serializer zur Umsetzung im Grunde außer Acht gelassen versuchen und einfach bezaubernd das Verhalten. Ich habe slice verwendet, um bestimmte Attribute auszuwählen. Hoffentlich wird eine elegantere Lösung kommen.

+0

Es sieht vielleicht aus wie eine fieses Workaround, aber Sie haben mich gerettet !!!!! – yossico

+2

Bravo auf Ruby und was AMS gibt Ihnen, was Sie wollen! – BF4

+1

Während die meisten Antworten auf diese Frage die richtige * konventionelle * Art ist, ist es schwer zu bekommen, was Sie wollen, wenn Sie den AMS-Konventionen folgen. Ich schreibe auch meine eigenen Rückrufe für die Anzeige von verschachtelten Assoziationen und finde die AMS-Konvention zu unhandlich. Ich finde, dass dies für so ziemlich alles, was Rails und verschachtelte Assoziationen betrifft, der Fall ist - Konventionen sind unhandlich, schieben sich auf einfachen Rubin. – Todd

1

Dies sollte tun, was Sie suchen.

@project.to_json(include: { estimates: { include: {:project, :project_code, :tax_type, :proposals } } })

Die oberste Ebene Verschachtelung automatisch aufgenommen werden, aber etwas tiefer als das braucht in Ihrer Show Aktion aufgenommen werden oder wo auch immer Sie diese anrufen.

+4

Ist dieser Ansatz nutzen tatsächlich' active_model_serializers' oder ist es nur die Standardmethode für die Ausgabe von 'JSON'? –

+0

@Greetification Nein, es verwendet eine Spur von AMS, die in Rails zurückgelassen wurde, als es zurückgesetzt wurde. Siehe in Rails ActiveModel :: Serializers :: JSON und ActiveModel :: Serialization – BF4

5

Wenn Sie den JSONAPI Adapter verwenden, können Sie folgendes tun verschachtelte Beziehungen zu machen:

render json: @project, include: ['estimates', 'estimates.project_code', 'estimates.tax_type', 'estimates.proposals'] 

Sie können mehr von der jsonapi Dokumentation lesen: http://jsonapi.org/format/#fetching-includes

+0

Ich denke, mein größtes Problem bei dieser Methode ist, dass man den Code in zwei verschiedenen Dateien verwalten muss; der Controller und der Serializer. Das heißt nicht, dass es nicht die richtige Art ist, dies zu tun. nur dass ich es unbequemer finde. –

+0

Dies ist der beste Weg, um 'Nesting' zu bekommen .. obwohl JSON: API tatsächlich flacht es. – BF4

34

Per begehen 1426: https://github.com/rails-api/active_model_serializers/pull/1426 - und verwandte Diskussion, können Sie sehen, dass die Standardverschachtelung für json und attributes Serialisierung eine Ebene ist.

Wenn Sie standardmäßig tief Verschachtelung möchten, können Sie eine Konfigurationseigenschaft in einem active_model_serializer initializer gesetzt:

ActiveModelSerializers.config.default_includes = '**'

Ausführliche Referenz von v0.10.6: https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/general/adapters.md#include-option

+0

Mit Abstand die beste Lösung als diese PR. –

+1

Ich möchte hinzufügen, dass der obige Vorschlag das "s" fehlt und sollte sein: ActiveModelSerializers.config.default_includes = '**' –

+0

behoben! Ich hätte das aus einer Datei herausschneiden und hinter mir lassen sollen ... –

4

Sie können ändern default_includes für die ActiveModel::Serializer:

# config/initializers/active_model_serializer.rb 
ActiveModel::Serializer.config.default_includes = '**' # (default '*') 

Darüber hinaus, um eine unendliche Rekursion zu vermeiden, können Sie die verschachtelte Serialisierung steuern folgt:

class UserSerializer < ActiveModel::Serializer 
    include Rails.application.routes.url_helpers 

    attributes :id, :phone_number, :links, :current_team_id 

    # Using serializer from app/serializers/profile_serializer.rb 
    has_one :profile 
    # Using serializer using serializer described below: 
    # UserSerializer::TeamSerializer 
    has_many :teams 

    def links 
    { 
     self: user_path(object.id), 
     api: api_v1_user_path(id: object.id, format: :json) 
    } 
    end 

    def current_team_id 
    object.teams&.first&.id 
    end 

    class TeamSerializer < ActiveModel::Serializer 
    attributes :id, :name, :image_url, :user_id 

    # Using serializer using serializer described below: 
    # UserSerializer::TeamSerializer::GameSerializer 
    has_many :games 

    class GameSerializer < ActiveModel::Serializer 
     attributes :id, :kind, :address, :date_at 

     # Using serializer from app/serializers/gamers_serializer.rb 
     has_many :gamers 
    end 
    end 
end 

Ergebnis:

{ 
    "user":{ 
     "id":1, 
     "phone_number":"79202700000", 
     "links":{ 
     "self":"https://stackoverflow.com/users/1", 
     "api":"/api/v1/users/1.json" 
     }, 
     "current_team_id":1, 
     "profile":{ 
     "id":1, 
     "name":"Alexander Kalinichev", 
     "username":"Blackchestnut", 
     "birthday_on":"1982-11-19", 
     "avatar_url":null 
     }, 
     "teams":[ 
     { 
      "id":1, 
      "name":"Agile Season", 
      "image_url":null, 
      "user_id":1, 
      "games":[ 
       { 
        "id":13, 
        "kind":"training", 
        "address":"", 
        "date_at":"2016-12-21T10:05:00.000Z", 
        "gamers":[ 
        { 
         "id":17, 
         "user_id":1, 
         "game_id":13, 
         "line":1, 
         "created_at":"2016-11-21T10:05:54.653Z", 
         "updated_at":"2016-11-21T10:05:54.653Z" 
        } 
        ] 
       } 
      ] 
     } 
     ] 
    } 
} 
+1

thx, rette mich eine Menge Zeit. –

+0

doppelte Antwort von http://StackOverflow.com/A/38157268/879854 – BF4

6

In meinem Fall habe ich eine Datei mit dem Namen ‚active_model_serializer.rb‘gesetzt bei 'MyApp/config/initializers' mit folgendem Inhalt:

ActiveModelSerializers.config.default_includes = '**' 

enter image description here

Vergessen Sie nicht, den Server neu zu starten:

$ rails s 
+0

doppelte Antwort von http://StackOverflow.com/A/38157268/879854 – BF4

Verwandte Themen