The validations provided by the Rails framework are very powerful and provide a very easy, no cost way of validating forms prior to submitting them to the database. You might see the validations in a class this way:
class User < ActiveRecord::Base
validate_presence_of :name
end
If there is not a name entered on the form when creating/updating the user, the save will fail with an error that can be displayed to the user.
I was hoping to leverage this same technique for some other forms that I had in my Rails application. These forms did not have a corresponding model in the database, as they were models that described sending an email. The goal was to provide for some required fields and then check whether or not the form was valid prior to sending the email.
My first pass at this looked like this:
class ContactInformation < ActiveRecord::Base
attr_accessor :name
attr_accessor :email_address
attr_accessor :body
validates_presence_of :name
validates_presence_of :email_address
validates_presence_of :body
end
and in the controller:
def send_mail
@contact_info = ContactInformation.new(params[:email])
Notification::deliver_contact_form(@contact_info) if @contact_info.valid?
# error checking omitted for brevity
end
This technique did not work, since ActiveRecord makes a database call to define the accessors when valid?
is called. I needed something a little different, so I looked to Google. I found a solution here that almost worked for me. What didnt work for me was that in the first piece of code, the behavior was overwritten for all ActiveRecord::Base, meaning that the rest of my application would not have database access with ActiveRecord. The second solution on the page seemed way more complicated than I felt that it needed to be. After all, the first solution worked. Using the first technique, I just needed to find a way to limit scope.
A little Ruby metaprogramming led me to this, which I think is an excellent solution to this problem (which I put in $RAILS_APP/lib/active_form.rb).
module ActiveForm
def self.included(base)
base.class_eval do
alias_method :save, :valid?
def self.columns() @columns ||= []; end
def self.column(name, sql_type = nil, defaults = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type, null)
end
end
end
end
Essentially, what we’re doing is using Module.included and Module.class_eval methods to execute code whenever the ActiveForm module is included in another class or model. When the class is evaluated, we alias the save method to the valid method and add a method that allows are model to register columns. This method simply mocks out an ActiveRecord column (without connecting to the database), which basically fools ActiveRecord into thinking there actually is a database table.
The model class is modified slightly from the example above. In addition to including the ActiveForm module, which provides us this functionality, the attr_accessor methods are changed to the column method described above. The model now looks like this:
class ContactInformation < ActiveRecord::Base
include ActiveForm
column :name
column :email_address
column :body
validates_presence_of :name
validates_presence_of :email_address
validates_presence_of :body
end
The controller code works as I initially had written it. And, I get all the joys of the ActiveRecord validations without having a database table for information I dont want to save in the database.