Scenic adds methods to ActiveRecord::Migration
to create and manage database
views in Rails.
Using Scenic, you can bring the power of SQL views to your Rails application without having to switch your schema format to SQL. Scenic provides a convention for versioning views that keeps your migration history consistent and reversible and avoids having to duplicate SQL strings across migrations. As an added bonus, you define the structure of your view in a SQL file, meaning you get full SQL syntax highlighting in the editor of your choice and can easily test your SQL in the database console during development.
Scenic ships with support for PostgreSQL. The adapter is configurable (see
Scenic::Configuration
) and has a minimal interface (see
Scenic::Adapters::Postgres
) that other gems can provide.
You've got this great idea for a view you'd like to call searches
. You can
create the migration and the corresponding view definition file with the
following command:
$ rails generate scenic:view searches
create db/views/searches_v01.sql
create db/migrate/[TIMESTAMP]_create_searches.rb
Edit the db/views/searches_v01.sql
file with the SQL statement that defines
your view. In our example, this might look something like this:
SELECT
statuses.id AS searchable_id,
'Status' AS searchable_type,
comments.body AS term
FROM statuses
JOIN comments ON statuses.id = comments.status_id
UNION
SELECT
statuses.id AS searchable_id,
'Status' AS searchable_type,
statuses.body AS term
FROM statuses
The generated migration will contain a create_view
statement. Run the
migration, and baby, you got a view going. The migration is reversible
and the schema will be dumped into your schema.rb
file.
$ rake db:migrate
Here's where Scenic really shines. Run that same view generator once more:
$ rails generate scenic:view searches
create db/views/searches_v02.sql
create db/migrate/[TIMESTAMP]_update_searches_to_version_2.rb
Scenic detected that we already had an existing searches
view at version 1,
created a copy of that definition as version 2, and created a migration to
update to the version 2 schema. All that's left for you to do is tweak the
schema in the new definition and run the update_view
migration.
You bet! Using view-backed models can help promote concepts hidden in your relational data to first-class domain objects and can clean up complex ActiveRecord or ARel queries. As far as ActiveRecord is concerned, you a view is no different than a table.
class Search < ActiveRecord::Base
private
# this isn't strictly necessary, but it will prevent
# rails from calling save, which would fail anyway.
def readonly?
true
end
end
Scenic even provides a scenic:model
generator that is a superset of
scenic:view
. It will act identically to the Rails model
generator except
that it will create a Scenic view migration rather than a table migration.
There is no special base class or mixin needed. If desired, any code the model generator adds can be removed without worry.
$ rails generate scenic:model recent_status
invoke active_record
create app/models/recent_status.rb
invoke test_unit
create test/models/recent_status_test.rb
create test/fixtures/recent_statuses.yml
create db/views/recent_statuses_v01.sql
create db/migrate/20151112015036_create_recent_statuses.rb
Your view cannot have a primary key, but ActiveRecord's find
method expects to
query based on one. You can use find_by!
or you can explicitly set the primary
key column on your model like so:
class People < ActiveRecord::Base
self.primary_key = :id
end
Materialized views are essentially SQL queries whose results can be cached to a table, indexed, and periodically refreshed when desired. Does Scenic support those? Of course!
The scenic:view
and scenic:model
generators accept a --materialized
option for this purpose. When used with the model generator, your model will
have the following method defined as a convenience to aid in scheduling
refreshes:
def self.refresh
Scenic.database.refresh_materialized_view(table_name)
end
Scenic gives you drop_view
too:
def change
drop_view :searches, revert_to_version: 2
end
Scenic is maintained by Derek Prior and Caleb Thompson, funded by thoughtbot, inc. The names and logos for thoughtbot are trademarks of thoughtbot, inc.
We love open source software! See our other projects or hire us to help build your product.