Rails validando vários modelos infantis em uma forma complexa
-
18-09-2019 - |
Pergunta
Estou usando vários modelos infantis de forma complexa (a la http://railsforum.com/viewtopic.php?id=28447). O formulário funciona muito bem, mas preciso validar uma propriedade do definir de modelos infantis antes de aceitar os dados do formulário no banco de dados. Eu criei uma maneira principalmente de fazer isso. Parece que tem que haver uma maneira melhor, então estou pedindo sugestões ...
Basicamente, uma pessoa possui distribuições. A distribuição tem (entre outras coisas) um atributo percentual. Para uma determinada pessoa, suas distribuições devem totalizar 100% para serem válidas. Isso grita "transação" para mim, mas achei que deveria dar um tiro aos validadores primeiro.
Tentei escrever isso como um validador personalizado, mas o validador funciona apenas em dados já salvos no banco de dados. Ele não verificou os parâmetros enviados pelo formulário. Em outras palavras, pude inserir porcentagens inválidas através do formulário, que foram salvas e, em seguida, as edições subsequentes falharam devido aos dados ruins já no modelo.
Em seguida, estendi update_attributes no meu modelo de pessoa, adicionando uma transação:
def update_attributes(params)
retval = true
self.transaction do
retval = super
unless distributions_exactly_100?
retval = false
errors.add_to_base("Distribution must add up to exactly 100%")
raise ActiveRecord::Rollback
end
end
retval
end
O negócio retval é feio, mas isso mais ou menos funciona (às vezes algumas das distribuições pendentes estão faltando no formulário, quando encontra um erro e renderizadores). Há uma nuance adicional de que me convence, essa é uma abordagem ruim: se minha associação de distirbions é definida com métodos auxiliares, como abaixo, não posso usar os métodos auxiliares no meu update_attributes()
(ou em distributions_exactly_100?
), porque eles vão para o banco de dados, em vez de operar no conjunto de distribuições recém-assinadas por não-comprometidas.
has_many :distributions do
def for_month_and_year(month, year)
find :all, :conditions => ['month = ? and year = ?', month, year]
end
def total_for_month_and_year(month, year)
sum :percentage, :conditions => ['month = ? and year = ?', month, year]
end
...
def years_and_months
ds = find(:all, :order => 'year DESC, month DESC')
(ds.collect {|d| [d.year, d.month]}).uniq
end
end
A única outra coisa em que consigo pensar é processar os próprios parâmetros, como texto, a caminho do Update_Attributes. Mas isso está errado. :)
Alguém mais fazendo validação em uma coleção inteira de crianças? Qual é o caminho certo para fazer isso?
Solução
Eu não recomendo definir erros em update_attributes
. Mantenha as validações em seu lugar normal.
De volta ao seu problema, você pode alterar a verificação de validação para funcionar nas distribuições na memória, em vez de executar o cálculo no banco de dados?
# in Person model
validate :ensure_distributions_equal_100
def ensure_distributions_equal_100
percent = distributions.map(&:percent).sum
if percent != 100
errors.add_to_base("Distribution must add up to exactly 100%, they are #{percent}")
end
end