2016-03-24 10 views
6

Possiedo un oggetto PORO (Plain Old Ruby Object) per gestire alcune logiche di business. Riceve un oggetto ActiveRecord e lo classifica. Per motivi di semplicità, prendo il seguente come esempio:Logica aziendale duplicata in Ruby e SQL

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 

Tuttavia, ho anche voglia di fare le query che raggruppa gli oggetti sulla base di questi "attributo virtuale" state_id. Attualmente mi occupo di questo creando questo attributo nelle query SQL e utilizzandolo nelle istruzioni GROUP BY. Vedere l'esempio:

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 

In questo modo, posso ottenere questa logica di business in SQL e fare questo tipo di query in un modo molto efficiente.

Il problema è: ho duplicato la logica aziendale. Esiste nel codice "ruby", per classificare un singolo oggetto e anche in "SQL" per classificare una collezione di oggetti a livello di database.

È una cattiva pratica? c'è un modo per evitarlo? Io in realtà era in grado di farlo, facendo quanto segue:

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

Ma facendo questo, perdo la possibilità di classificare gli oggetti che non sono persistevano nel database. L'altra via d'uscita sarebbe classificare ogni oggetto in ruby, usando un Iterator, ma poi perderei le prestazioni del database.

Sembra che sia inevitabile mantenere una logica aziendale duplicata se ho bisogno del meglio dei due casi. Ma voglio solo essere sicuro di questo. :)

Grazie!

risposta

0

C'è qualche possibilità di introdurre il trigger nel database? Se è così, vorrei andare con il campo “calcolato” state_id nel database, che cambia il suo valore sia INSERT e UPDATE (questo porterà ancora più benefici di produttività) e questo codice in Ruby:

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 
+0

Probabilmente migliorerebbe le prestazioni aggiornando questo campo nel database. Tuttavia, credo che avrei ancora una logica commerciale duplicata, giusto? Sia sul trigger del database che sul metodo 'state_if'. –

+0

@ JoãoDaniel si e no. Se la logica aziendale è assolutamente la stessa per gli oggetti persistenti e non persistenti, con questo approccio potresti mettere tutta la logica nel livello DB e fare 'start_transaction ⇒ read_state ⇒ rollback' hack. – mudasobwa

0

mi piacerebbe piuttosto mantenere il database semplice e inserire la logica nel codice Ruby il più possibile. Poiché la classificazione non è memorizzata nel database, non mi aspetto che le query restituiscano.

La mia soluzione è definire un problema che verrà incluso nelle classi di modelli di 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 

E recupero gli elementi dal database proprio come al solito.

items = Item.where(...) 

Poiché ogni item conosce il proprio valore di classificazione, non ho chiedere database per esso.

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