has_many :codes

A serialisable and validatable tableless model to get rid of a few tables and speed things up

Published  

I have just published a tiny gem, called tableless_model. It includes some functionality I have used quite often in my recent projects, which can help reduce database complexity, and improve performance, by replacing tables and associations with fewer tables storing serialised data instead.

Fewer tables often mean databases that are easier to manage, as well as improved performance thanks to the reduced number of queries that would otherwise be needed to fetch associated data. I use serialisation in many ways but, in particular, with one-to-one associations between a “parent” model and a “child” model, with the latter only containing information such as options, settings, or debugging data, that uniquely belongs to a single instance of the parent model.

For example’s sake, let’s say you are working on a CMS application, are implementing some SEO optimisations, and want to be able to set custom SEO options for each of the pages managed by the application, with these custom options -if set- overriding some global SEO options that otherwise apply to all of the pages. So the parent model would be a class named Page, while the child model would be a class named SeoOptions, with a one-to-one association between the two of them:

class Page < ActiveRecord::Base

  # having columns such as id, title, etc

  has_one :seo_options

end

class SeoOptions < ActiveRecord::Base

  set_table_name "seo_options"

  # having columns such as id, title_tag, meta_description, meta_keywords,
  # noindex, nofollow, noarchive, page_id

  belongs_to :page

end

In such cases, the application would normally require two tables in your database -one for each of these models- and either one expensive join or two queries (depending on whether you use :join, :include, or lazy loading) to fetch both a page’s data as well as its SEO options, from these two tables.

Using the tableless_model gem, your could remove the one-to-one association between the two models, and the seo_options table altogether, by storing the SEO options in a column of the pages table, in YAML serialised format. So the models would become:

class Page < ActiveRecord::Base # having columns such as id, title, seo, etc has_tableless :seo => SeoOptions

end

class SeoOptions < ActiveRecord::TablelessModel attribute :title_tag, :type => :string, :default => "default title tag"
  attribute :meta_description, :type => :string, :default => ""
  attribute :meta_keywords, :type => :string, :default => ""
  attribute :noindex, :type => :boolean, :default => false
  attribute :nofollow, :type => :boolean, :default => false
  attribute :noarchive, :type => :boolean, :default => false

end

That’s it. When you now create an instance of SeoOptions, you can get and set its attributes as you would do with a normal model:

seo_options = SeoOptions.new
=> <#SeoOptions meta_description="" meta_keywords="" noarchive=false nofollow=false noindex=false title_tag="default title tag">

seo_options.title_tag
=> "default title tag"

seo_options.title_tag = "new title tag"
=> "new title tag"

Of course, you can also override the default values for the defined attributes when creating a new instance:

seo_options = SeoOptions.new( :title_tag => "a different title tag" )
=> <#SeoOptions meta_description="" meta_keywords="" noarchive=false nofollow=false noindex=false title_tag="a different title tag">

Now, if you have used the has_tableless macro in the parent class Page, each instance of Page will store its own SEO options directly -in YAML serialised format- in the column seo.

page = Page.new

page.seo
=> <#SeoOptions meta_description="" meta_keywords="" noarchive=false nofollow=false noindex=false title_tag="default title tag">

page.seo.title_tag = "changed title tag"
=> <#SeoOptions meta_description="" meta_keywords="" noarchive=false nofollow=false noindex=false title_tag="changed title tag">

And this is how the content of the serialised column would look like in the database if you saved the changes as in the example above:

--- !map:SeoOptions
noarchive: false
meta_description:
meta_keywords:
nofollow: false
title_tag: "changed title tag"
noindex: false

Validations: tableless_model also supports validation methods and callbacks, like for table-based models, through the validatable gem. Note: it currently supports only Rails 2.x syntax. Example:

class SeoOptions < ActiveRecord::TablelessModel attribute :title_tag, :type => :string, :default => ""
  attribute :meta_description, :type => :string, :default => ""
  attribute :meta_keywords, :type => :string, :default => ""
  attribute :noindex, :type => :boolean, :default => false
  attribute :nofollow, :type => :boolean, :default => false
  attribute :noarchive, :type => :boolean, :default => false

  validates_presence_of :meta_keywords

end

Testing:

x = SeoOptions.new
=> <#SeoOptions meta_description="" meta_keywords="" noarchive=false nofollow=false noindex=false title_tag="">

x.valid?
=> false

x.meta_keywords = "test"
=> "test"

x.valid?
=> true

To install the gem, as usual:

gem install tableless_model

I am planning on writing more on serialisation and other similar tricks that help reduce complexity and/or improve performance, in the meantime I hope you find this tiny gem useful too.

© Vito Botta