如果你想创建一个可重用的关联,你想使用多态关联:
class Comment < ApplicationRecord
belongs_to :user
belongs_to :commentable,
polymorphic: true
validates :content,
presence: true
end
不是使用单个列保存指向固定表的整数,而是通过使用一列作为其他表的主键并将实体的类名存储在字符串列中来“欺骗”关系数据库模型(在这个例子叫commentable_type)。
您可以在生成迁移(或模型)时通过传递多态选项来创建所需的列:
rails g migration add_commentable_to_comments commentable:belongs_to{polymorphic}
但是,控制器的操作有点像火车残骸。它应该看起来更像这样:
class CommentsController < ApplicationController
before_action :authenticate_user!
before_action :set_offer
before_action :authorize_user
def create
@comment = @offer.comments.new(comment_params) do |c|
c.user = current_user
end
if @comment.save
# redirect_to request.referrer, notice: 'Comment sent.'
CommentChannel.broadcast_to offer, message: render_comment(@comment)
# rendering json here is actually just stupid. Just use a HTTP status code
# as a response if you're not actually returning the rendered entity.
render json: { success: true },
status: :created,
location: @comment
else
# either return JSON which is actually useful or just a status code
render json: { success: false },
status: :unprocessable_entity
end
end
private
def set_offer
@offer = Offer.find(params[:offer_id])
end
# this should be extracted to an authorization layer such as Pundit
def authorize_offer
if @offer.user_id != current_user.id && @offer.landlord_id != current_user.id
render json: { success: false },
status: :unauthorized
end
end
end
有两种方法可以将此控制器重新用于不同类型的“评论”。您可以在单个控制器中检查 offer_id 或 maintenance_request_id 的存在并使用它来推断类,或者您可以使用继承来更好地分配职责:
# routes.rb
resources :offers do
resources :comments,
only: :create,
module: :offers
end
resources :maintainence_requests do
resources :comments,
only: :create,
module: :maintainence_requests
end
class CommentsController
before_action :set_commentable, only: [:new, :create, :index]
before_action :authenticate_user!
attr_reader :commentable
def create
@comment = commentable.comments.new(comment_params) do |c|
c.user = current_user
end
if @comment.save
# simple extendability feature that lets you "tap into" the flow
# by passing a block when calling super in subclasses
yield @comment if block_given?
render json: @comment,
status: :created
else
render json: { errors: @comment.errors.full_messages },
status: :unprocessable_entity
end
end
private
# Uses simple heuristics based on module nesting to guess the name of the model
# that is being commented upon. Overide if needed.
# @example Foos::BarsController -> Foo
def commentable_class
@commentable_class ||= self.class.module_parent.name.singularize.constantize
end
def commentable_param_key
commentable_class.model_name.param_key
end
def set_commentable
@commentable = commentable_class.find("#{commentable_param_key}_id")
end
def comment_params
params.require(:comment)
.permit(:content)
end
end
module Offers
class CommentsController < ::CommentsController
# POST /offers/:offer_id/comments
def create
# block is called after a comment is succesfully saved but before
# the response is set
super do |comment|
CommentChannel.broadcast_to comment.offer,
message: render_comment(comment)
end
end
end
end
module MaintainenceRequests
class CommentsController < ::CommentsController
end
end