I appreciate the reply David.

A little while after my initial post, I actually started coming to a 
similar conclusion as what you've described. The model should define the 
valid state of the system. This obviously does present a problem for a 
write-on-set database interface, because if you only set one field, even 
if you soon follow up by setting multiple other fields, the new or 
updated record will become invalid because it'll be missing field x, y 
and z.

I did manage to come up with a solution however, which is something I 
had already planned to implement, but for a different purpose. I've 
decided to use a queuing construct. So if you've created a new object, 
or you need to update multiple fields, you can wrap such operations in a 
"set" block. For example...

john.set do |doc|
   doc[:age] = 23
   doc[:hair] = 'long'
   doc[:skin] = 'fair'
end

Or you could simply pass in a hash depending on the situation...

john.set age: 23, hair: 'long', skin: 'fair'

Single field write-on-set operation can still be performed using: 
john[:hair] = 'long', but if you wish to perform an operation that will 
invalidate the object for any period of time, you'll have to wrap the 
operations in a 'set' block.

Behind the scenes, I've just implemented a simple queueing system which 
in essence, all it does is create a hash of new values which are only 
committed when commit() is called. Essentially, all the above set() 
method does, is the following internally...

john.queue_updates = true
john[:age] = 23
john[:hair] = 'long'
john[:skin] = 'fair'
# ...do plenty of other unrelated stuff here if you want...
john.commit
# Turn off queueing if you no longer wish to queue further operations.
john.queue_updates = false

I'm pretty happy with the solution I've managed to come up with here, as 
it's not just a construct for validation, but also allows one to batch 
operations to improve application performance. I'm yet to define the 
validation syntax, but it will be something like this...


class Person
  define_valid do
    cond :name, :age, :email {|val| !val.empty? }
    cond :name {|val| val.gsub /.+ .+/ }
    cond :age  {|val| val.is_a? Integer && (0..160) === val}
    cond :email {|val| 
val.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/)}
  end
end

As you can see, it uses blocks to define the validation logic. You can 
also map multiple fields to the same validation block. I still need to 
come up with an elegant way of setting validation messages. One idea is 
to simply return a string from the validation block when the condition 
fails, e.g.

cond :name do |val|
  return "Name field must contain both a first and a last name." unless 
val.gsub /.+ .+/
end

Whenever data is about to be committed, the validation conditions can be 
checked against the fields they apply to. This method allows one to 
define validations as complex or as simple as one desires.

-- 
Posted via http://www.ruby-forum.com/.