Skip to content
Andrew Geweke edited this page Dec 13, 2013 · 3 revisions

There are many advantages to segregating the flex columns associated with a particular model into an entirely separate table. If the attributes in the flex column are not used in most bulk operations (which we recommend), this will prevent the database from having to load the flex-column data at all, and keep the original table far smaller.

For example, consider this migration:

class CreateUsers < ActiveRecord::Migration
  def up
    create_table :users do |t|
      t.string :first_name, :limit => 64
      t.string :last_name, :limit => 64
      t.string :username, :limit => 64

      t.float :current_latitude, :precision => 15, :scale => 12
      t.float :current_longitude, :precision => 15, :scale => 12

      t.date :date_of_birth
    end

    create_table :user_details, :id => :user_id do |t|
      t.text :details
    end
  end
end

Assuming a four-byte primary key, with an average first-name length of 5 characters, last-name length of 7 characters, and username length of 11 characters, this results in a row width of 49 bytes. A hundred million users would take 4.56 GiB of memory to fully cache.

If we store the user's hashed password (a surprisingly good choice for a flex column), locale, whether or not they've agreed to our terms of service, and custom background color in the flex column, we can easily end up with a JSON string that's 140 characters long. If we stored this directly in the users table, we would suddenly need 17.6 GiB of memory to fully cache the table — an increase of 286%! (Even if we stored this data directly in columns in the users table, it would still be an increase of 152% — the key is to get this data out of the users table entirely.)

flex_columns offers built-in support for exactly this model:

class UserDetails < ActiveRecord::Base
  self.primary_key = :user_id
  belongs_to :user

  flex_column :details do
    field :password
    field :locale
    field :agreed_to_tos
    field :background_color
  end
end

class User < ActiveRecord::Base
  has_one :user_detail
  include_flex_columns_from :user_detail
end

Now we can write code like:

my_user = User.find(...)
my_user.background_color # => 'green'
# You can also access it via the association...
my_user.user_detail.details.background_color # => 'green'
# ...or just via the flex-column name:
my_user.details.background_color # => 'green'
my_user.locale = 'fr_FR'

...and so on.

include_flex_columns_from requires an association name (which must be a has_one or belongs_to association), and includes all flex columns that are defined on that association. It also accepts the following options:

  • :prefix (must be a String or Symbol): If supplied, then methods delegated (including the method that returns the flex-column object itself) will be prefixed with the given string, followed by an underscore. For example, if passed foo, then the above example would instead allow for my_user.foo_background_color = 'green', my_user.foo_details.locale, and so on.
  • :visibility (must be :private or :public): If supplied and set to :private, then methods delegated will be marked as private to the including class. (If set to :public, they will be public; this is the default.)
  • :delegate: If supplied and set to false, then attributes and custom methods defined on each flex column will not be automatically delegated from the class; only the flex-column accessor itself will be. (In the above example, my_user.background_color would not work; only my_user.details.background_color would.)

Note that include_flex_columns_from does not affect the loading of the association in any way, shape, or form; you're still on your own to :include it, or risk N+1 queries. It also means that the associated object will not be loaded unless you request data from it; simply using include_flex_columns_from, on its own, has no performance penalty.

Clone this wiki locally