Want to see the full-length video right now for free?Sign In with GitHub for Free Access
Scenic is a gem developed here at thoughtbot that lets you work with database views in Rails. Rails migrations have no support for views out of the box, but Scenic integrates with Rails to make working with views easy.
A database view is a named, predefined query you can interact with as if it were a table. When you use it, the query is performed live, every time. Views are great for composing bits of data that exist in your schema, perhaps hidden in relationships, and can make application logic more visible from the database. They're fantastic for reports or any time you want to repeatably represent existing data.
shows can be routed to directly off the
root domain via their "slug". For example,
upcase.com/foo may to go to a
topic, a trail, or a slug. We have a
SlugConstraint class (below) which
prevents users from hitting records that don't exist. Unfortunately, we don't
have any object in our system that represents the core "slug" concept.
class SlugConstraint def initialize(model) @model = model end def matches?(request) @model.exists?(slug: request.path_parameters[:id]) end end
We can represent that slug concept with a database view.
scenic to the Gemfile, we can generate a database-view-backed
model using a Rails generator provided by Scenic:
$ rails generate scenic:model slug
We want a list of every slug in use, along with the corresponding type (Show, Repository, etc) that it belongs to. Here's the database view:
-- db/views/slugs_v01.sql SELECT slug, type as model FROM products UNION ALL SELECT slug, 'Trail' as model FROM trails
rake db:migrate to add the view to the database, we can use
Slug model in the
class SlugConstraint def initialize(model) @model = model end def matches?(request) slug_type(request) == model.to_s end private attr_reader :model def slug_type(request) requested_slug = request.path_parameters[:id] RequestStore.store[:slug_type] ||= Slug. find_by(slug: requested_slug). try(:model) end end
Thanks to our new database view, the slug query is now the same every time, and we can cache it.
For the real-life code we used on Upcase, check out our Upcase commit.
We made a mistake in our database view: we're not pulling the slugs from the
topics table. This is where Scenic really shines: it makes it easy to update
our view using a new migration, maintaining a clear history and the ability to
roll back easily. Let's generate a new view version:
rails generate scenic:view slug
db/views/slugs_v02.sql (note the
v02 in the filename). When
we migrate the database, Scenic will change the view to use the latest version,
just like standard Rails database migrations.
We add this to the end of the
+ + UNION ALL + + SELECT slug, 'Topic' as model + FROM topics
Now we can run
rake db:migrate to change the
slugs view to match the latest
db/views. Scenic can also revert to an older version with
db:rollback. It's seamlessly integrated into the standard Rails database
We don't use a materialized view in this video, but they can be helpful in certain cases.
Materialized database views are very similar to views except rather than the backing query being run each time the view is used, it's run once when the view is created and the results are cached to a table. The materialized view can then be refreshed on-demand.
The advantage of a materialized view is that it can be directly indexed (like a "real" table). Simple views can take advantage of underlying indexes on the base tables, but materialized views with custom indexes will perform better in certain situations.
Scenic is totally unique (as far as we know!) in how it handles materialized views. For example, Scenic automatically reapplies indexes when a new version of a view is created, rather than silently dropping old indexes from a previous version.
Rails values database agnosticism: everything at the application layer, and very little at the database level. But sometimes, database agnosticism can hurt our app. Scenic's simple integration with Rails makes it easier than ever to use views in your app today.