#74 ✓resolved
spgarbet

Validation Dicussion

Reported by spgarbet | November 26th, 2007 @ 11:06 AM

I like what I see with data mapper. There has been an ongoing debate (2+ years) about validations in Rails. I wondered what the waters are like over here.

The essence of the debate centered around the orthogonality of the validation operators. When calling things like validates_format_of, and the column being nil, one gets a validates_presence_of automatically. Implying that validates_format_of and validates_presence_of are non-orthogonal. It would be better if these operators are orthogonal, and one only gets what is explicitly asked for and there is no automatic inclusion between validators. Short of that, at least make it configurable to allow for this. The rails core team has stubbornly refused to allow this to be configurable, only to have the issue raised up again repeatedly.

Hence, models end up with :allow_nil => true all over validations in a non-DRY manner.

I've also found validates_absence_of to be useful in STI situations where a field should not be defined for a subclass.

I'll write code/tests for these things if this is acceptable. Otherwise, I'll go quitely away.

Comments and changes to this ticket

  • Shalev NessAiver

    Shalev NessAiver November 26th, 2007 @ 01:18 PM

    I agree (about the orthogonality.) Why not make it the opposite and create an :allow_nil => false flag.

    That way you don't need to create a whole other (validates_presence_of) if you don't want to.

  • Paul Sadauskas (Rando)

    Paul Sadauskas (Rando) November 26th, 2007 @ 05:14 PM

    Since we are already defining the properties of a model, and using that to create the database columns, I would be with thinking about adding it to the #property class method.

    It would be nice to be able to say:

    class Person
      property :name, :string, :not_null => true
    end
    

    And automatically get a validates_presence_of :name. Or the opposite, validates_presence_of would set the NOT NULL constraint on the column.

    Some of the validators are to complex to encode in database constraints, but the simple ones (not null, unique, etc..) should be part of the table schema, as well as validators.

  • spgarbet

    spgarbet November 28th, 2007 @ 11:08 AM

    Paul,

    I like the cut of your jib. That's exactly what's needed. All database constraints are reflected into validations automatically. Then one can add constraints as needed. Super!

  • Sam Smoot

    Sam Smoot December 28th, 2007 @ 10:43 PM

    • Milestone cleared.
    • State changed from “new” to “open”

    All good stuff. Right, don't want to stuff every validation into the property declaration, but basic ones for presence, size/allowed-range, format, etc seem like a great match.

    Happy to consider any contributions on this front...

    Not quite sure I agree with the original description of the ticket with regards to orthogonality... guess I could go either way on that.

    I do think validations probably need to be accrued in a Set however, so you don't get lousy error messages, but that's more for the author of the Validatable gem. IOW, if you add a validates_length_of, along with a validates_presence_of on the same attribute, I imagine you could get a list of errors saying that:

    • Age must not be blank
    • Age must be between 18 and 65

    Which could probably be better represented by not displaying the validates_presence_of message once a not-nullable range validation was added.

    If that makes any sense.

    Anyways, we should probably start with specs we can agree on before going off coding up an implementation, so if anyones game for that...

  • Sam Smoot

    Sam Smoot January 2nd, 2008 @ 03:45 PM

    • Assigned user set to “Bernerd Schaefer”

    Bernerd's done some of this now I think...

  • Bernerd Schaefer

    Bernerd Schaefer January 3rd, 2008 @ 05:28 PM

    I added #auto_validations! to DM::Property.

    Now if you do:

    class Zoo
        property :name, :string, :nullable => false, :default => "Zoo"
    end
    

    It adds a validates_presence_of to :name.

    I'm working on adding some of the other basic validations.

  • Bernerd Schaefer

    Bernerd Schaefer January 3rd, 2008 @ 06:28 PM

    [r670]

    Now you can do length/size validation and format (using the formats defined in validatable).

    class Zoo
      include DataMapper::Persistence
      property :name, :string, :size => 250
      # adds: validates_length_of :name, :maximum => 250
    end
    
    class User
      include DataMapper::Persistence
      property :username, :string, :length => 5..25
      # adds: validates_length_of :username, :minimum => 5, :maximum => 25
      property :email, :format => :email_address
      # adds: validates_format_of :email, :with => :email_address
    end
    
  • Quinn Slack

    Quinn Slack January 3rd, 2008 @ 08:26 PM

    Re: this code

    class Zoo
        property :name, :string, :nullable => false, :default => "Zoo"
    end
    

    It seems to me that has one foot in the database world and one foot in the Ruby world. A "string" is a Ruby thing, while the concept of "null" is a database thing. I think consistency would be better. What about :nillable? Or :not_nil?

  • Sam Smoot

    Sam Smoot January 5th, 2008 @ 11:24 AM

    Quinn: I see your point... but the :default is database-side as well. If you wanted a Ruby default you'd just overwrite #initialize.

    So... I don't know that I'm in favor of doing anything about it right now unless we can think of a consistent idea for what a Property represents.

    To me it's basically all about the database except for the first argument (:name in your example).

  • David Lee

    David Lee January 6th, 2008 @ 03:28 AM

    I think properties are neither database-side nor ruby-side but business-logic.

    This follows closely with what a datamapper is defined to be by martin fowler:

    http://martinfowler.com/eaaCatal...

    (A mapper "that moves data between objects and a database while keeping them independent of each other and the mapper itself")

    Note that the mapper is not the object. Instead, it connects the object to a (relational) database.

    So by definition, the mapper itself needs to be able to understand both the object (a business model object in our case) and the database.

    That's why we also have things like 'check', 'reference', 'key', to describe the database side, while not having explicit 'associated_model_id'. We instead do 'has_many :associated_models' to describe the business model object.

    The thing is, 'string' does not represent the Ruby object 'String' but rather the business idea of a string of characters.

    Ruby is just a way to describe the business model that we have, and the business model shouldn't conform to Ruby. By this, I mean that we should call properties "properties" instead of "instance variables," and properties should be defined as they currently are, instead of in the 'initialize' method.

    However, for consistency (for the purpose of "the principle of least surprise"), I think it would be best to conform to database terminology, or at least support both:

    property :name, :string

    property :name, :varchar

    property :name, :nullable => false

    property :name, :nillable => false

  • Sam Smoot

    Sam Smoot January 7th, 2008 @ 09:03 PM

    David, your description of the situation sounds good to me. I guess I can live with aliased options. Kind of a POLS thing I suppose.

    Maybe an aliasing Hash might be nice so we could do something like this:

    h = AliasingHash.new(:nullable => true)
    h.alias(:nullable => :nillable)
    h[:nillable].should eql(h[:nullable])
    

    Or something like that anyways...

    I don't want to bloat things with all sorts of special-cased bits of code, but wanting to alias option keys seems a common enough use-case, and messy enough inline implementation, that a specific class for it might be handy.

  • Elliott Mason

    Elliott Mason February 11th, 2008 @ 06:36 PM

    class Zoo
        property :name, :string, :nullable => false, :default => "Zoo"
    end
    

    I don't like how this adds a validation for me. Here's the problem I'm running into: say I have a column that is a counter like posts_count, and it defaults to 0. Under no circumstances would I want a value in that column to be nil, so to cover my butt I set :nullable => false on that property. Unfortunately, with the current behavior of DataMapper, when I try to save an object without setting its posts_count manually, I get a validation error; posts_count must have a value before being saved, even though it's going to receive that value automatically once it is saved. Ideally, it should be saved and the database will take care of automatically setting posts_count to zero upon being saved.

    One of the main things that turned me off of Rails migrations is they didn't give me enough control. At the end of the day I was going into my databases and fixing up columns and making them more specific; making sure certain columns were not-null and that I wasn't using integers where I could use tinyints and that my varchar columns weren't longer or shorter than necessary and that I was using a char column for hashed passwords since they had a definite length, and so on. DataMapper exacerbates this problem because it would destroy all that fine-tunning every time I changed my schema and automigrated.

    I think the assumed-validations thing is an OK idea and I was excited about it until I realized some of the ways it could make things difficult. I'd rather just set the validations for myself or at least have it so that I could set a property to not assume a validation (something like :validate => false [or maybe even set :validate => true]). I think the property method should be used to define only database schema and validations should contain the logic for whether or not a record gets inserted/updated.

  • Matt

    Matt March 15th, 2008 @ 03:31 PM

    What about going one step further and allowing domain constraints to be defined, much like the in syntax,

         property :name, :string, :nullable => false, :default => "Zoo", :in => ['Zoo', 'Zoo2', 'Zoo3']
    

    which would add the SQL

    CREATE DOMAIN zoo_name as VARCHAR
     NOT NULL DEFAULT 'Zoo'
     CHECK (VALUE IN ('Zoo', 'Zoo2', 'Zoo3'))
    

    What are your thoughts?

  • Matt

    Matt March 15th, 2008 @ 03:55 PM

    Well, this is actually be:

    when creating table:
    
    name VARCHAR NOT NULL DEFAULT 'Zoo' CHECK (name in('Zoo', 'Zoo2', 'Zoo3'))
    
  • Sam Smoot

    Sam Smoot March 17th, 2008 @ 10:15 AM

    Matt,

    That's the eventual goal (property declarations to flow down into database constraints). So yeah, I'd like to support that.

    On the other hand, I'd really like to push the idea of ContextualValidations, which is difficult with the inline declarations, so I don't think the inlines should be the end-all, be-all way to validate. More of a convenience for the most obvious declarations.

  • David Lee

    David Lee March 17th, 2008 @ 02:29 PM

    What do you think about using the NotNaughty plugin that budded off from the sequel project?

    http://not-naughty.rubyforge.org/

  • Dan Kubb (dkubb)
  • Sam Smoot

    Sam Smoot April 27th, 2008 @ 09:46 PM

    • State changed from “open” to “resolved”

    Ok, most of the work discussed here is actually done. I'm closing this ticket since if there's any specific requests we want addressed aside from that, it's probably best for a new ticket since this one covers like a dozen different ideas. ;-)

Please Sign in or create a free account to add a new ticket.

With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.

New-ticket Create new ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

Referenced by

Pages