Gender Validation in Multiple Rails Models
data-modeling, ruby-on-rails
Solution
Here's my solution. I like to go with the standard plugin-style libraries. I'd put this in lib/acts_as_gendered:
module ActsAsGendered
GENDERS = ['male', 'female']
def self.included(base)
base.extend(ActsAsGenderedMethods)
end
module ActsAsGenderedMethods
def acts_as_gendered
extend ClassMethods
include InstanceMethods
validates_inclusion_of :gender, :in => GENDERS
end
end
module ClassMethods
def is_gendered?
true
end
end
module InstanceMethods
def is_male
gender = 'male'
end
def is_female
gender = 'female'
end
def is_male?
gender == 'male'
end
def is_female?
gender == 'female'
end
end
end
Yeah, it might be overkill for simple genders, but you can see where all the pieces go - the GENDERS constant, the `acts_as_gendered` ActiveRecord hook, which then includes the class and instance methods and the validation.
Then, in config/initializers/gender.rb:
require 'acts_as_gendered'
ActiveRecord::Base.send(:include, ActsAsGendered)
Then, for the grand finale, the model:
class User < ActiveRecord::Base
acts_as_gendered
end
This pattern may seem overly complicated, but that's how most libraries end up eventually :)
UPDATE: To answer your comment, this is how I'd modify the acts_as_gendered method to make validations optional on a per-model basis:
def acts_as_gendered options={}
config = {:allow_nil => false}
config.merge(options) if options.is_a?(Hash)
extend ClassMethods
include InstanceMethods
if config[:allow_nil]
validates_inclusion_of :gender, :in => (GENDERS + nil)
else
validates_inclusion_of :gender, :in => GENDERS
end
end
Now you can call it in the User model like this:
class User < ActiveRecord::Base
acts_as_gendered :allow_nil => true
end
I could have made it a simple parameter you pass in, but I like the clarity of passing in a hash. And it sets you up for adding other options down the road.
Problem
Hey all -- this question is specifically about a `gender` validation, but I'm interested in hearing how you've all handled similar situations with much larger collections (Country selection, for example.) I'm working on a system that lets athletes register for various events, and am currently working on a good gender validation. My question is, what's the best, most DRY way to run the same validation on many different models? Let's say I want to validate the gender property of `Event` and `User`. I can create a helper for `validates_each` that checks values for inclusion in the very short array of ["male", "female"] before updating the `gender` attribute. But what if I want to access this same gender array in a `form_for` block, say, as an input to `collection_select`? I have it working for one model -- I declare a `GENDERS` constant in `Event`, and have a short class method ``` def self.genders GENDERS end ``` for access by forms. But where should I store the array if multiple models need access? EDIT: One idea would be to use a class method in the application controller. Any thoughts on how appropriate this approach is would be great.