0

Rails でハイパーメディア API を作成しようとしています。json_api アダプターを使用して、active_model_serializers でペイロードをシリアライズしたいと考えています。しかし、条件付きでリンクをシリアライズするのは簡単ではないようです。

ユーザーが他のユーザーをフォローできるブログアプリケーションのようなものです。したがって、User リソースをシリアル化するとき、たとえば UserA の場合、current_user が UserA をフォローしていない場合は rel :follow とのリンクが必要であり、current_user が既に UserA をフォローしている場合は rel :unfollow とのリンクが必要です。ハイパーメディア API を作成する場合、これは非常に些細な使用例のように思えます。active_model_serializers でこれを行う良い方法があるかどうかは誰にもわかりませんか?

私は現在、次のようなものを書いています(そして、すべてのシリアライザーに含めています):

def self.link(rel, &block)
      serializer = self
      super do
        user = scope
        next unless serializer.can?(user, rel, @object)
        instance_eval(&block)
      end
    end

# And in serializer (just as usual):

link :self do
  api_user_path(object.id)
end

それは機能します。しかし、それは気分が悪いだけです。そして、active_model_serializers への将来の変更が私にとって問題になったとしても、私は驚かないでしょう。

4

1 に答える 1

0

他の誰かがこれに対する解決策を探している場合、ここで私がしたことです。gem Pundit を追加し、"link_#{rel}" というメソッドを追加して、リンクのシリアル化 (および通常の承認) を担当する Policy クラスを作成しました。次のような基本シリアライザーを作成しました。

module Api
  class BaseSerializer < ActiveModel::Serializer
    include Pundit

    def self.link(rel, &block)
      unless block_given?
        Rails.logger.warn "Link without block (rel '#{rel}'), no authorization check"
        return super
      end
      method = "link_#{rel}"
      # We need to let the super class handle the evaluation since
      # we don't have the object here in the class method. This block
      # will be evalutated with instance_eval in the adapter (which has
      # the object to be serialized)
      super do
        policy_class = PolicyFinder.new(object).policy
        unless policy_class
          Rails.logger.warn "Could not find policy class for #{object.class}."
          next
        end
        user = scope
        policy = policy_class.new(user, object)
        unless policy.respond_to?(method)
          Rails.logger.warn "Serialization of #{object.class} infers link with rel '#{rel}'. " \
            "But no method '#{method}' in #{policy.class}."
          next
        end
        next unless policy.public_send(method)
        instance_eval(&block)
      end
    end

  end
end

次に、次のような他のシリアライザーが BaseSerializer から継承します。

module Api
  class UserSerializer < BaseSerializer
    type 'user'
    attributes :name,
               :email,
               :followers_count,
               :following_count,
               :created_at,
               :updated_at

    link :self do
      api_user_url(object)
    end

    link :edit do
      api_user_url(object)
    end

    link :follow do
      follow_api_user_url(object)
    end

    link :unfollow do
      unfollow_api_user_url(object)
    end
  end
end

したがって、ポリシーは通常の Pundit ポリシーと同様に、シリアル化する必要がある (またはシリアル化しない) リンクごとにいくつかのメソッドが追加されています。

class ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    @user = user
    @record = record
  end

  def link_self
    true
  end

end

module Api
  class UserPolicy < ApplicationPolicy

    alias current_user user
    alias user record

    def link_edit
      current_user && current_user.id == user.id
    end

    # show follow link if user is not current_user and
    # current_user is not already following user
    def link_follow
      current_user && current_user.id != user.id && !current_user.following?(user)
    end

    # show follow link if user is not current_user and
    # current_user is following user
    def link_unfollow
      current_user && current_user.id != user.id && current_user.following?(user)
    end
  end
end
于 2016-10-31T22:42:01.403 に答える