轨道中具有冗余操作逻辑的多个控制器

时间:2017-02-03 16:56:06

标签: ruby-on-rails design-patterns controller composition api-design

我有3个控制器。两个返回专门项目(文章,公告),一个返回。

GET api/announcements/1 -- produces Announcement json
GET api/articles/2      -- produces Article json
GET api/posts/1         -- produces Announcement json
GET api/posts/2         -- produces Article json

文章控制器的show方法包含必须在帖子控制器中复制的逻辑。更具体地说:

def show
  deal_with_params(...)
  authorize!(...)
  render json: @resource
end

我按unique_id获取资源后,我知道它的类型并且可以从那里分支出来,但我只想authorize并对类型为Article的资源执行其他操作。

任何人都有建议,模式或想法,有助于确保在帖子控制器中不必重复对ArticlesController#show的更改?

2 个答案:

答案 0 :(得分:1)

您可以使用controller concerns来提取常用功能。对于您的特定用例,您可能有两个问题:

  • AnnouncementsConcern,其中包含处理Announcements所需的所有代码。
  • ArticlesConcern,其中包含处理Articles的所有代码。

然后,您可以根据需要在控制器中包含这些问题。即您将AnnouncementsConcern中的AnnouncementsControllerArticlesConcern中的ArticlesController包括在内,然后在PostsController中包含这两项问题。

答案 1 :(得分:0)

你可以使用继承来干掉它。这是使用PunditRespondersjsonapi

的极端干燥基本控制器的示例
class ResourcesController < ApplicationController

  respond_to :json

  before_action :authenticate_user # use a secure by default model
  before_action :set_resource, only: [:show, :update, :destroy]
  before_action :authorize_resource, only: [:show, :update, :destroy]
  before_action :set_resources, only: [:index]

  def create
    @resource = resource_class.create(permitted_attributes) do |r|
      yield(r) if block_given?
    end
    respond_with(@resource)
  end

  def show
    respond_with(@resource)
  end

  def index
    respond_with(@resources)
  end

  def update
    @resource.update(permitted_attributes) do |r|
      yield(r) if block_given?
    end
    respond_with(@resource)
  end

  def destroy
    @resource.destroy
    respond_with(@resource)
  end


  private 

  def set_resource
    @resource = resource_class.find(params[:id])
  end

  def set_resources
    @resources = policy_scope(resource_class)
  end

  def authorize_resource
    authorize( @resource )
  end

  def resource_class
    self.class.controller_name.classify.constantize
  end

  def permitted_attributes
    whitelist = policy(@resource || self.class.resource_class)
                      .permitted_attributes
    params.require(:data).require(:attributes).permit(whitelist)
  end
end

这里有一些非常好的技巧:

  • self.class.controller_name.classify.constantize使用约定优于配置来根据控制器名称猜测模型。
  • 使用before_action回调可让子类使用skip_before_action选择退出,或通过声明回调方法来更改行为。
  • 使用yield可让子类进入控制器流程。
  • JSON API格式使用相同的数据格式,而不考虑使用model_name.param_key作为根参数键的rails默认值。减少自行车画是一件好事。

子类示例:

# Nothing special going on here
class ThingsController < ResourcesController; end

class ArticlesController < ResourcesController
  def create 
    # taps into the yield
    super do |article|
      article.author = current_user
    end
  end
end

class StranglyNamedController < ResourcesController
  def resource_class
    Thing
  end
end