【问题标题】:Reusing model in ruby on rails application在 ruby​​ on rails 应用程序中重用模型
【发布时间】:2021-12-17 17:25:53
【问题描述】:

我目前正在将 cmets 添加到我的一个模型 MaintenanceRequest 并想知道我应该如何去做。

我有一个模型Offer,它已经构建了 cmets,并使用 ActionCable 设置如下

# models/comment.rb

class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :offer
end
# channels/comment_channel.rb

class CommentChannel < ApplicationCable::Channel
  def subscribed
    offer = Offer.find params[:offer]
    stream_for offer
  end
end

评论控制器看起来像

# controllers/comments_controller.rb

class CommentsController < ApplicationController
  before_action :authenticate_user!
  before_action :is_valid_offer

  def create
    offer = Offer.find(comment_params[:offer_id])

    if comment_params[:content].blank?
      # return redirect_to request.referrer, alert: 'Comment cannot be empty.'
      return render json: {success: false}
    end

    if offer.user_id != current_user.id && offer.landlord_id != current_user.id
      return render json: {success: false}
    end

    @comment = Comment.new(
      user_id: current_user.id,
      offer_id: offer.id,
      content: comment_params[:content],
    )

    if @comment.save
      # redirect_to request.referrer, notice: 'Comment sent.'
      CommentChannel.broadcast_to offer, message: render_comment(@comment)
      render json: {success: true}
    else
      redirect_to request.referrer, alert: "Couldn't send comment."
      render json: {success: true}
    end
  end
end

我的问题是,有没有一种方法可以为属于 MaintenanceRequest 的 cmets 使用相同的模型,或者我应该为那里的 cmets 创建新模型和控制器?如果我尝试重用当前的评论模型,这似乎会变得非常混乱。这里的“最佳做法”是什么?

【问题讨论】:

    标签: ruby-on-rails ruby


    【解决方案1】:

    如果你想创建一个可重用的关联,你想使用多态关联:

    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_idmaintenance_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
    

    【讨论】:

    • 感谢重构控制器方法...您的建议似乎是对 cme​​ts 使用单个模型而不是创建另一个模型?
    • 这在一定程度上取决于用例,以及“提供评论”和“维护请求评论”是否实际上是同一件事(最初可能很难知道)以及 if 等方面您希望需要将其作为同质集合进行查询。
    • 在某种意义上它们是一回事。两者都只是文本字段,但与不同的模型相关。
    • 使用多态关联的缺点是它是一种绕过对象关系阻抗不匹配的技巧,并且关系只能由应用程序真正解决,并且缺乏外键约束来维护引用数据库级别的完整性。
    • 我想maintenance_requests的路由应该有module: :maintenance_requests?还有 MaintainenceRequests 模块是空的还是从 Offers 模块复制的?
    猜你喜欢
    • 1970-01-01
    • 2011-06-27
    • 1970-01-01
    • 1970-01-01
    • 2012-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-29
    相关资源
    最近更新 更多