与active_model_serializers的条件链接

时间:2016-09-12 19:29:34

标签: ruby-on-rails active-model-serializers hypermedia

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

这是一种博客应用程序,用户可以关注其他用户。因此,当我序列化用户资源时,例如对于UserA,我希望有一个与rel的链接:如果current_user不跟随UserA,则跟随,如果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的更改搞砸了,我不会感到惊讶。

1 个答案:

答案 0 :(得分: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