Frequently rails applications need to process data that will not be stored in a database. How does one handle this sort of data?
An example of this might be a user filling out a search form, doing some sort of query. This quickly gets complicated to handle once you start searching by dates (which have to be casted from strings) or have complicated rules about which parameters are required.
This sort of data doesn’t really fit into Ruby on Rails models (which are usually concerned with persisting data). Additionally this data is often subject to business logic and doesn’t really seem to belong in the controller layer either.
The Rails answer to such things is ActiveModel, which provides some tools to get you started on building models that aren’t based off ActiveRecord::Base. Unfortunately, however, there are a number of great features, such as typecasting, that you can’t easily get this way.
Enter active_attr, a fantastic gem by Chris Griego, that helps you bootstrap your new non-persisted models and get into the good stuff. Chris’s gem provides validation, mass-assignment, and security features among others.
My favourite feature is typecasting. In a regular active-record model Rails can infer the appropriate types of your attributes from the database – not a possibility in a non-persisted model. With active_attr you can define the types of your attributes with ease, and get some of that Ruby magic going.
Here’s an example of a PersonQuery:
class PersonQuery
include ActiveAttr::BasicModel
include ActiveAttr::TypecastedAttributes
include ActiveAttr::MassAssignment
include ActiveModel::Validations
attribute :full_name, type: String
attribute :born_after, type: Date
validates :full_name, presence: true, :length => { :minimum => 5 }
validates :born_after, presence: true
def execute
Person.where(full_name: full_name).where("date_of_birth > ?", born_after) if valid?
end
end
Lets have a look at how we might use this:
query = PersonQuery.new(full_name: "John Doe", born_after: "12/12/1990") # => # query.born_after # => Wed, 12 Dec 1990 query.execute # => [#]
Notice in particular how the born_after attribute has been cast to a Date object.
In our controller we might have something like this:
def index @person_query = PersonQuery.new(params[:person_query]) @people = @person_query.execute end
Advantages of this technique include:
- Nice lean controllers and reusable code.
- Declarative validations, instead of messy inline checks.
As an added bonus, formtastic will play nicely with our PersonQuery objects, even displaying validation errors inline with inputs.
Happy hacking!
Marc Remolt
Feb 20, 2013
Small hint, just:
include ActiveAttr::ModelIt contains the other modules.
thomasfedb
Feb 20, 2013
Hey Marc, thanks for that.
Yes, you can simply include
ActiveAttr::Modelif you want everything fromactive_attr, which is a nice quick way to get started.PikachuEXE
Feb 21, 2013
Hope Rails 4.0 will be released soon
Then we can have model support without a gem!
Vitorino
Feb 22, 2013
@PikachuEXE that’s true, but until a mass migration for Rails 4 the ActiveAttr is a great addon.
This article by Carlos Antônio from Plataformatec Blog show out how ActiveMoldel will work in Rails 4. Check this out.
Damir Zekić
Feb 24, 2013
The following sentence is not completely correct: “there are a lot of great features, such as validation, that you can’t easily get this way”. It’s pretty easy to get validation in an ActiveModel model by including `ActiveModel::Validations` module.
Additionally, it’s quite easy to use ActiveModel::Model in Rails 3.2 codebase — we have simply copied the source from Rails master branch into our `lib` dir and it worked perfectly.
There’s still no type casting (that I’m aware of), so ActiveAttr wins. But if you don’t need the casts and you plan on migrating to Rails 4 anyway, this may be a simpler solution.
thomasfedb
Feb 24, 2013
@Damir
Thanks for that, I have updated the article to reflect what you said.