Ruby和SQL中的重复业务逻辑

时间:2016-03-24 13:08:33

标签: ruby-on-rails ruby oop design-patterns architecture

我有一个PORO(普通旧Ruby对象)来处理一些业务逻辑。它接收一个ActiveRecord对象并对其进行分类。为简单起见,请以下面的示例为例:

class Classificator
    STATES = {
      1 => "Positive",
      2 => "Neutral",
      3 => "Negative"
    }

    def initializer(item)
      @item = item
    end

    def name
      STATES.fetch(state_id)
    end

    private

    def state_id
      return 1 if @item.value > 0
      return 2 if @item.value == 0
      return 3 if @item.value < 0
    end
end

但是,我还想根据这些state_id&#34;虚拟属性&#34;进行分组对象的查询。我目前正在通过在SQL查询中创建此属性并在GROUP BY语句中使用它来处理它。参见示例:

class Classificator::Query
  SQL_CONDITIONS = {
    1 => "items.value > 0",
    2 => "items.value = 0",
    3 => "items.value < 0"
  }

  def initialize(relation = Item.all)
    @relation = relation
  end

  def count
    @relation.select(group_conditions).group('state_id').count
  end

  private
  def group_conditions
    'CASE ' + SQL_CONDITIONS.map do |k, v|
      'WHEN ' + v.to_s + " THEN " + k.to_s
    end.join(' ') + " END AS state_id"
  end
end

这样,我就可以将这个业务逻辑变成SQL,并以非常有效的方式进行这种查询。

问题是:我有重复的业务逻辑。它存在于&#34; ruby​​&#34;代码,对单个对象进行分类,也在&#34; SQL&#34;在数据库级别对对象集合进行分类。

这是一种不好的做法吗?有办法避免这种情况吗?我实际上能够做到这一点,做到以下几点:

item = Item.find(4)
items.select(group_conditions).where(id: item.id).select('state_id')

但是通过这样做,我失去了对未在数据库中持久化的对象进行分类的能力。另一种方法是使用Iterator对ruby中的每个对象进行分类,但之后我将失去数据库性能。

如果我需要两种情况中的最佳情况,那么保持重复的业务逻辑似乎是不可避免的。但我只是想确定这一点。 :)

谢谢!

3 个答案:

答案 0 :(得分:0)

有没有机会在数据库中引入触发器?如果是这样,我会使用数据库中的“计算”字段state_id来改变它在INSERTUPDATE上的值(这将带来更高的生产力效益)和此代码红宝石:

def state_if
  return @item.state_id if @item.state_id # persistent object

  case @item.value
  when 0 then 2
  when -Float::INFINITY...0 then 3
  else 1
  end
end

答案 1 :(得分:0)

我宁愿保持数据库简单,并尽可能地将逻辑放在Ruby代码中。由于分类没有存储在数据库中,我不希望查询返回它。

我的解决方案是定义一个将包含在ActiveRecord模型类中的问题。

module Classified
  extend ActiveSupport::Concern

  STATES = {
    1 => "Positive",
    2 => "Neutral",
    3 => "Negative"
  }

  included do
    def state_name
      STATES.fetch(state_id)
    end

    private

    def state_id
      (0 <=> value.to_i) + 2
    end
  end
end

class Item < ActiveRecord::Base
  include Classified
end

我像往常一样从数据库中获取项目。

items = Item.where(...)

由于每个item都知道自己的分类值,所以我不必向数据库询问它。

items.each do |item|
  puts item.state_name
end

答案 2 :(得分:0)

ActiveRecord 本身意味着持久性和业务逻辑之间存在一定程度的耦合。然而,只要模式允许,如果你没有真正的性能限制,第一个选择应该是让你的持久性代码尽可能的愚蠢,并移动这个“分类”(它显然是一个业务规则)尽可能远离数据库。

基本原理是更改与数据库相关的代码成本更高(尤其是当您的系统已投入生产时),并且通常比纯业务逻辑更难测试且速度更慢。