-
Notifications
You must be signed in to change notification settings - Fork 9
Including Columns
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 passedfoo
, then the above example would instead allow formy_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 tofalse
, 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; onlymy_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.