0

我正在尝试在 rails 中创建一个超媒体 api。我想使用 json_api 适配器使用 active_model_serializers 序列化我的有效负载。但是有条件地序列化链接似乎并不简单。

这是一种博客应用程序,用户可以在其中关注其他用户。因此,当我序列化一个用户资源时,比如对于 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 回答