如何在 Rails 中实现“业务规则”?

Posted

技术标签:

【中文标题】如何在 Rails 中实现“业务规则”?【英文标题】:How to implement "business rules" in Rails? 【发布时间】:2011-06-27 14:59:06 【问题描述】:

Rails 中实现“业务规则”的方式是什么?

假设我有一辆车想卖掉它:

car = Cars.find(24)
car.sell

car.sell 方法会检查一些事情:

does current_user own the car?
    check: car.user_id == current_user.id
is the car listed for sale in the sales catalog?
    check: car.catalogs.ids.include? car.id
    
if all o.k. then car is marked as sold.

我正在考虑创建一个名为 Rules 的类:

class Rules
    def initialize(user,car)
        @user = user
        @car = car
    end

    def can_sell_car?
        @car.user_id == @user.id && @car.catalogs.ids.include? @car.id
    end
end

并像这样使用它:

def Car
    def sell
        if Rules.new(current_user,self).can_sell_car
            ..sell the car...
        else
            @error_message = "Cannot sell this car"
            nil
        end
    end
end

至于获得current_user,我想将它存储在一个全局变量中? 我认为每当调用控制器动作时,它总是一个“新鲜”的调用,对吗?如果是这样,那么将当前用户存储为全局变量不应该带来任何风险..(就像其他用户能够访问其他用户的详细信息一样)

感谢任何见解!

更新

所以,全局变量路由出来了!感谢 PeterWong 指出全局变量仍然存在!

我现在正在考虑使用这种方式:

class Rules
    def self.can_sell_car?(current_user, car)
       ......checks....
    end
end

然后从控制器操作中调用Rules.can_sell_car?(current_user,@car)。 对这种新方式有什么想法吗?

【问题讨论】:

没有。全局变量存在于请求和机器之间。 这里的全局变量是什么意思?您可能会将用户作为参数传递给您的方法。 @PeterWong,哇!不知道。但是全局变量如何跨机器持续存在? @Yar,我的意思是$someglobal rails 实际上是一个在服务器上运行的普通应用程序。只要有一个应用程序,每个全局变量在该特定应用程序中都是唯一的。 这将是应用程序范围的。即使是会话范围的范围对于您想要的东西来说也太大了。将用户作为参数传递。 【参考方案1】:

我会使用以下表格:

对于买家和卖家:

人(id:int,name:string)

class Person << ActiveRecord::Base
  has_many :cars, :as => :owner
  has_many :sales, :as => :seller, :class_name => 'Transfer'
  has_many :purchases, :as => :buyer, :class_name => 'Transfer'
end

汽车(id:int,owner_id:int, vin:string, year:int,make:string,model:string,listed_at:datetime)

listed_at 是查看汽车是否在售的标志

class Car << ActiveRecord::Base
  belongs_to :owner, :class_name => 'Person'
  has_many :transfers

  def for_sale?
    not listed_at.nil?
  end
end

转账(id:int,car_id:int,seller_id:int,buyer_id:int)

class Transfer << ActiveRecord::Base
  belongs_to :car
  belongs_to :seller, :class_name => 'Person'
  belongs_to :buyer, :class_name => 'Person'

  validates_with Transfer::Validator

  def car_owned_by_seller?
     seller_id == car.owner_id
  end
end

然后您可以使用此自定义验证器来设置您的规则。

class Transfer::Validator << ActiveModel::Validator
  def validate(transfer)
     transfer.errors[:base] = "Seller doesn't own car" unless transfer.car_owned_by_seller?
     transfer.errors[:base] = "Car isn't for sale" unless transfer.car.for_sale?
  end
end

【讨论】:

【参考方案2】:

首先,标准的 Rails 做法是将所有业务逻辑保留在模型中,而不是控制器中。看起来您正朝着这个方向前进,所以这很好 -- 但是:请注意,没有一种干净的方式可以从模型中获取 current_user

我不会制作新的 Rules 模型(尽管如果您真的想这样做,您可以这样做),我只会涉及用户模型和汽车。所以,例如:

class User < ActiveRecord::Base
...
  def sell_car( car )
    if( car.user_id == self.id && car.for_sale? )
      # sell car
    end
  end
...
end

class Car < ActiveRecord::Base
...
  def for_sale?
    !catalog_id.nil?
  end
...
end

显然,我在假设您的目录的工作方式,但如果是 for_sale belong_to 目录的汽车,那么该方法将起作用 - 否则只需根据需要调整方法以检查汽车是否列在目录中或不。老实说,在 Car 模型本身上设置一个布尔值可能是个好主意,这样用户就可以在需要时简单地切换要出售或不出售的汽车(通过将汽车标记为待售,或通过将汽车添加到目录等)。

我希望这能给你一些方向!请随时提出问题。


编辑:另一种方法是在您的模型中使用以下方法:

user.buy_car( car )
car.transfer_to( user )

有很多方法可以将逻辑放入与之交互的对象中。

【讨论】:

感谢您的回答。我可以在 Car 和其他模型中执行业务规则,但我认为将所有逻辑放在一个地方会更好,因为某些模型之间的交互非常密切。另外,为什么将sell_car 放入User 而不是Car 把卖车放在用户中,因为这样你就知道谁在卖车。从控制器调用user.sell_car @myCar 或其他。通过从用户调用它,您不必尝试将会话信息 (current_user) 获取到您的模型中,这是一个坏主意。 就像我说的,如果你愿意,你当然可以创建一个规则类,但我不确定这是否具有很多面向对象的意义。 Rails 方式似乎是向模型询问关于他们自己的问题。 IE:用户,卖车。用户,买车。车,有卖吗?所讨论的逻辑都非常特定于各个模型,那么为什么不在适当的模型中实现它并让模型自然交互呢?当然,如果您有极其复杂的事务逻辑,那么我理解您为什么要将其分离为 Transaction 模型。【参考方案3】:

我认为这将是使用数据库的主要候选者,然后您可以使用 Ruby 来查询不同的表。

【讨论】:

【参考方案4】:

您可以查看声明式授权 gem - https://github.com/stffn/declarative_authorization

虽然它已针对 CRUD 操作进行了预配置,但您可以轻松添加自己的操作(购买、出售)并将其业务逻辑放入 authentication_rules.rb 配置文件中。然后,在您的控制器、视图甚至模型中!您可以轻松地询问 allowed_to? :买,@汽车

【讨论】:

【参考方案5】:

我正在对用户做类似的事情,以及他们可以对照片库做些什么。我正在使用用户和身份验证的设计,然后我在用户模型中设置了几种方法来确定用户是否具有各种权限(用户通过权限拥有许多画廊)来操作该画廊。我认为您遇到的最大问题似乎是确定当前用户,这可以使用 Devise 轻松处理,然后您可以向用户模型添加一个方法并检查 current_user.can_sell?授权销售。

【讨论】:

以上是关于如何在 Rails 中实现“业务规则”?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Spring Boot 中实现 Camunda SendTask

SpringBoot中实现业务校验,这种方式才叫优雅~

何时使用业务规则引擎

规则引擎用户界面设计

业务逻辑和规则——如何将它们与领域模型解耦

业务层设计——如何定义规则