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.
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.
You've got the right idea by storing it in a constant. The only thing I would do differently is put it in an initializer file so that it's not tied to any particular model like it is in your example. If you're worried about potential name collisions at the top level, you could put it in a module in the lib directory and include the module only in the places you intend to use it.
I agree with putting this in a constant. I'd also put the strings themselves in constants because (1) they can change, and (2) when used in a conditional, the system will catch if you mistype them. E.g., in your environment.rb:
MALE = 'male'
FEMALE = 'female'
GENDERS = [MALE, FEMALE]
And then in your code, you only ever refer to these constants, e.g.:
def male?
return gender == MALE
end
I would write a custom validation plugin (say, validates_gender). Then you would call:
class Event < ActiveRecord::Base
validates_gender :gender
end
Grab a copy of my validates_as_email plugin and use that, replacing value =~ EMAIL_ADDRESS_RE with your own logic.
Related
Where do the basic validators lie when dealing with Form objects and regular Rails models?
Following the concept of decoupling forms from the persistence layer in Rails. I've setup a Form Object Cage that creates two objects together... say Animal and Plant.
Following Form Object examples 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 these show the Form object itself has validations... no problem... part of the benefits include being able to validate objects in a more contextually aware way.
The issue:
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 do I validate animal's "only one brown" rule without:
Too much duplication.
A lot of code to make Cage still act like an AR model
If we don't duplicate the validation code, when "only one brown" is false, Cage doesn't have an error for it... we'll raise, which requires the controller to catch and handle, which is bad.
If we do duplicate the code, and if there are several custom validations, we're duplicating a lot of code and each other form object that deals with Animal needs the duplicated validations now.
If we move the validation code out of Animal into Cage entirely, similar issue: all objects that interact with Animal need to know about the "only one brown" rule, which is just duplicating validators and opening up an easy way to forget to enforce it somewhere.
If we move Animal's error array up to Cage's, Animal's error is on :color, which is ambiguous to Cage, and shows an error on an attribute name the client never sent in. If you want to map Animal's error keys to Cage's, now you need to keep an map for each Form Object, feels stinky.
Are there any good patterns or ways to deal with this situation? I feel like it is very common when you start using Form Objects but all examples are quite trivial.
Thanks in advance!
At the end of point 3 on http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ the author says: "As a bonus, since validation logic is often contextual, it can be defined in the place exactly where it matters instead of needing to guard validations in the ActiveRecord itself." I'm agree with Bryan Helmkamp, puts the validation where it matters, you don't need to duplicate it.
edited:
If I were you, I'll put the validation only on the ActiveRecord model. And I'll update 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'll add an errors method that returns the errors of Cage, Plant and Animal instances.
edited:
I think you can redefine the valid? method, and then errors works 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 works Virtus, with Rails 4 you can use ActiveModel::Model, if using rails 3 I need research.
edited:
If you are using Rails 3.2, you can't use ActiveModel::Model, but you get the same with this:
class Cage
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
...
end
I want to filter out specific fields from ActiveRecord/ActiveModel classes when outputting JSON.
The most straightforward way to do this is just overriding as_json, perhaps like so:
def as_json (options = nil)
options ||= {}
super(options.deep_merge({:except => filter_attributes}))
end
def filter_attributes
[:password_digest, :some_attribute]
end
This works, but it's a little verbose and lends itself to not being DRY pretty fast. I thought it would be nice to just declare the filtered properties with a magical class method. For example:
class User < ActiveRecord::Base
include FilterJson
has_secure_password
filter_json :password_digest
#...
end
module FilterJson
extend ActiveSupport::Concern
module ClassMethods
def filter_json (*attributes)
(#filter_attributes ||= Set.new).merge(attributes.map(&:to_s))
end
def filter_attributes
#filter_attributes
end
end
def as_json (options = nil)
options ||= {}
super(options.deep_merge({:except => self.class.filter_attributes.to_a}))
end
end
The problem with this is getting it to deal with inheritance properly. Let's say I subclass User:
class SecretiveUser < User
filter_json :some_attribute, :another_attribute
#...
end
Logically, it makes sense to filter out :some_attribute, :another_attribute, and also :password_digest.
However, this will only filter the attributes declared on the class. To the desired end, I tried to call super within filter_attributes, but that failed. I came up with this, and it's a hack.
def filter_attributes
if superclass.respond_to?(:filter_attributes)
superclass.filter_attributes + #filter_attributes
else
#filter_attributes
end
end
This is obviously brittle and not idiomatic, but there's the "what" that I'm trying to accomplish.
Can anyone think of a way to do it more correctly (and hopefully more elegantly)? Thanks!
I think it is a safer solution to white-list attributes than to black-list them. This will prevent unwanted future attributes added to User or SomeUser from making it into your JSON response because you forgot to add said attributes to filter_json.
You seem to be looking for a solution to your specific inheritance issue. I'm still going to point out active_model_serializers, as I feel it is a saner way to manage serialization.
class UserSerializer < ActiveModel::Serializer
attributes :id, :first_name, :last_name
end
class SecretUserSerializer < UserSerializer
attributes :secret_attribute, :another_attribute
end
Given some SecretUser s you can do
SecretUserSerializer.new(s).as_json
and you'll get :id, :first_name, :last_name, :secret_attribute, and :another_attribute. The inheritance works as expected.
How does attr_accessor works in ActiveResource?
class User < ActiveResource::Base
attr_accessor :name
end
How its different from attr_accessor in ActiveRecord?
attr_accessor is built into Ruby, not rails. You may be confusing it with attr_accessible, which is part of ActiveRecord. Here's the difference:
attr_accessor
Take a class:
class Dog
attr_accessor :first_name, :last_name
def initialize(first_name, last_name)
self.first_name = first_name
self.last_name = last_name
end
end
attr_accessor creates a property and creates methods that allow it to be readable and writeable. Therefore, the above class would allow you to do this:
my_dog = Dog.new('Rex', 'Thomas')
puts my_dog.first_name #=> "Rex"
my_dog.first_name = "Baxter"
puts my_dog.first_name #=> "Baxter"
It creates two methods, one for setting the value and one for reading it. If you only want to read or write, then you can use attr_reader and attr_writer respectively.
attr_accessible
This is an ActiveRecord specific thing that looks similar to attr_accessor. However, it behaves very differently. It specifies which fields are allowed to be mass-assigned. For example:
class User
attr_accessible :name, :email
end
Mass assignment comes from passing the hash of POST parameters into the new or create action of a Rails controller. The values of the hash are then assigned to the user being created, e.g.:
def create
# params[:user] contains { name: "Example", email: "..."}
User.create(params[:user])
#...
end
For the sake of security, attr_accessible has to be used to specify which fields are allowed to be mass-assigned. Otherwise, if the user had an admin flag, someone could just post admin: true as data to your app, and make themselves an admin.
In summary
attr_accessor is a helper method for Ruby classes, whereas attr_accessible is an ActiveRecord thing for rails, to tighten up security.
You don't need to have attr_accessor to work with ActiveResource.
The base model (ActiveResource::Base) contains the #attributes hash in which you can 'dump' properties as you wish. (you should be careful though on what params you allow)
The way it does this, is by handling the method_missing? method.
You can take a look here
If you define attr_accessor, what ruby does is that it creates a setter and a getter method, so it will break the method_missing functionality since it will never get to execute that code.
If you still want to use attr_accessor, you should create a Concern something like this:
module Attributes
extend ActiveSupport::Concern
module ClassMethods
def attr_accessor(*attribs)
attribs.each do |a|
define_method(a) do
#attributes[a]
end
define_method("#{a}=") do |val|
#attributes[a] = val
end
end
end
end
end
How do I limit the object of any class to one. My class looks like :
class Speaker
include Mongoid::Document
field :name, :type => String
end
I just want to let a single instance of speaker . One way would be to add a validation which would check the number of objects already present of the Speaker class.
Is there a ruby way of doing thing ?
How about using the Singleton module?
In this case I would write proper validation:
validate :only_one
def only_one
errors.add(:base, "Only one Speaker can exist") if self.count > 0
end
I recommend using a class/module that is tailored to storing configuration values rather rolling your own on top of a vanilla ActiveRecord model.
I use a old copy of the rails-settings plugin with some custom modification (it still works just fine in Rails 3). There are also a number of variant offerings listed on Github, so feel free to look and take your pick.
Why not provide a default Speaker object, and just not provide controller actions for create or delete?
Seems the simplest solution by far.
I see you're using Mongoid
The functionality you request is not available using mongoid validations.
Therefore, you will need to write your own. before_validation is a supported callback and chained Speaker.all.count methods are available to your model.
class Speaker
include Mongoid::Document
field :name, :type => String
before_validation(:ensure_has_only_one_record, :on => :create)
def ensure_has_only_one_record
self.errors.add :base, "There can only be one Speaker." if Speaker.all.count > 0
end
end
However, the best practice is to put all key/value settings in a single table.
Using the Singleton module and a little overriding of its methods, I believe this works and it's thread safe (on ruby 1.8):
class Speaker
include Singleton
include Mongoid::Document
field :name, :type => String
##singleton__instance__ = nil
##singleton__mutex__ = Mutex.new
def self.instance
return ##singleton__instance__ if ##singleton__instance__
##singleton__mutex__.synchronize {
return ##singleton__instance__ if ##singleton__instance__
##singleton__instance__ = self.first
##singleton__instance__ ||= new()
}
##singleton__instance__
end
def destroy
##singleton__mutex__.synchronize {
super
##singleton__instance__ = nil
}
end
end
I have a model named Calendar.
The validations that will be applied to it varies from the selections made by the user.
I know that I can use custom validation + conditional validation to do this, but doesn't look very clean to me.
I wonder if I can store it on a database column and pass it to a "generic" validator method.
What do you think?
Explaining further:
A user has a calendar.
Other users that have access to this calendar, can schedule appointments.
To schedule an appointment the app should validate according to the rules defined by the calendar's owner.
There are many combinations, so what I came to is:
Create custom validator classes to each of the possible validations and make then conditional.
class Calendar
validate_allowed_in_hollydays :appointment_date if :allowedinhollydays?
(tenths of other cases)
...
end
This works, but feels wrong.
I'm thinking about storing somewhere which rules should be applied to that calendar and then doing something like:
validate_stored_rules :appointment_date
It seems a little backwards to save the data in the database and then validate it.
I think your initial thought of going with some custom validation is the best bet. validates_with looks like your best option. You could then create a separate class and build all the validation inside that to keep it separate from your main model.
class Person < ActiveRecord::Base
validates_with GoodnessValidator
end
class GoodnessValidator < ActiveModel::Validator
def validate
if record.first_name == "Evil"
record.errors[:base] << "This person is evil"
end
end
end
Code lifted straight from the Rails Validation Guide
you should use with_options it allows to put default options into your code:
class User < ActiveRecord::Base
with_options :if => :is_admin do |admin|
admin.validates_length_of :password, :minimum => 10
end
end
in the example is_admin might be an database column, attr_accessor or an method
Thank you all for your help.
I've got it working like this:
def after_initialize
singleton = class << self; self; end
validations = eval(calendar.cofig)
validations.each do |val|
singleton.class_eval(val)
end
end