jeudi 24 mars 2016

Duplicated business logic in Ruby and SQL

I have a PORO (Plain Old Ruby Object) to deal with some business logic. It receives an ActiveRecord object and classify it. For the sake of simplicity, take the following as an example:

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

However, I also want to do queries that groups objects based on these state_id "virtual attribute". I'm currently dealing with that by creating this attribute in the SQL queries and using it in GROUP BY statements. See the example:

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

This way, I can get this business logic into SQL and make this kind of query in a very efficient way.

The problem is: I have duplicated business logic. It exists in "ruby" code, to classify a single object and also in "SQL" to classify a collection of objects in database-level.

Is it a bad practice? Is there a way to avoid this? I actually was able to do this, doing the following:

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

But by doing this, I loose the ability to classify objects that are not persisted in database. The other way out would be classifying each object in ruby, using an Iterator, but then I would lose database performance.

It's seem to be unavoidable to keep duplicated business logic if I need the best of the two cases. But I just want to be sure about this. :)

Thanks!

Aucun commentaire:

Enregistrer un commentaire