Duplicate validation on form objects and models

Where are the main validators for working with Form objects and regular Rails models?

Following the concept of untying forms from a save layer in Rails. I set up a Cage form object that creates two objects together ... say Animal and Plant .

The following examples of form objects are from http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ or https://github.com/solnic/virtus or https://github.com/makandra/active_type , each of which shows that the form object itself has validations ... no problem ... some of the advantages include the ability to validate objects in a more context-sensitive way.

Problem:

 class Animal < ActiveRecord::Base validates :color, presence: true validate :only_one_brown private def only_one_brown if some_complex_thing errors.add(:color, 'can not have more than one brown animal.') end end end class Plant < ActiveRecord::Base validates :color, presence: true end class Cage include Virtus.model # or ActiveType or whatever include ActiveModel::Validations attribute :bird_color, String attribute :plant_color, String validates :bird_color, presence: true validates :plant_color, presence: true def save if valid? animal.save! plant.save! true else false end end def animal @animal ||= Animal.new(color: bird_color) end def plant @plant ||= Plant.new(color: plant_color) end end 

How to check the correctness of the "only one brown" rule for animals:

  • Too much duplication.
  • A lot of code to make Cage still act as an AR model.

If we do not duplicate the verification code when "only one brown" is false, Cage has no error for it ... we will raise what the capture and processing controller requires from the controller, which is bad.

If we duplicate the code, and if there are several user checks, we duplicate a lot of code, and every other form object that deals with Animal needs duplicate checks.

If we completely move the verification code from Animal to Cage, a similar problem: all objects that interact with Animal should be aware of the "only one brown" rule, which simply duplicates validators and opens up an easy way to forget to provide it somewhere.

If we move the array of Animal errors to Cage's, the Animal error is at :color , which is ambiguous for Cage and shows an error in the attribute name that the client never sent. If you want to match the Animal Cage error keys, now you need to save a map for each shape object, to feel smelly.

Are there any good patterns or ways to deal with this situation? I feel this is very common when you start using form objects, but all the examples are pretty trivial.

Thanks in advance!

+5
source share
1 answer

At the end of paragraph 3 at http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/, the author says: "As a bonus, because the validation logic is often contextual, it can be defined where it matters, rather than storing checks in ActiveRecord itself. " I agree with Brian Helmkamp, ​​puts validation where it matters, you do not need to duplicate it.

edited by:

If I were you, I would put the check only on the ActiveRecord model. And I am updating the Cage class:

 def save if valid? ActiveRecord::Base.transaction do animal.save! plant.save! end true else false end rescue Exception => exception raise if valid? false end 

And I will add an error method that returns Cage, Plant, and Animal instance errors.

edited by:

I think you can override the existing ones? method and then errors work fine:

 class Cage include ActiveModel::Model def valid_with_mymodels? valid_without_mymodels? && animal.valid? && plant.valid? animal.errors.each do |attribute, error| self.errors.add :"bird_#{attribute.to_s}", error end plant.errors.each do |attribute, error| self.errors.add :"plant_#{attribute.to_s}", error end errors.empty? end alias_method_chain :valid?, :mymodels ... end 

Just be careful with the name of your attrs.

I'm not sure how Virtus works, with Rails 4 you can use ActiveModel :: Model if you use rails 3 I need to research.

edited by:

If you use Rails 3.2, you cannot use ActiveModel :: Model, but you will get the same with this:

 class Cage extend ActiveModel::Naming include ActiveModel::Conversion include ActiveModel::Validations ... end 
0
source

Source: https://habr.com/ru/post/1205443/


All Articles