<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kamil Walkowiak</title>
    <description>The latest articles on DEV Community by Kamil Walkowiak (@gracz21).</description>
    <link>https://dev.to/gracz21</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F230114%2F8b38aa64-3ce7-4d8e-9700-0b3c9f92e769.jpg</url>
      <title>DEV Community: Kamil Walkowiak</title>
      <link>https://dev.to/gracz21</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gracz21"/>
    <language>en</language>
    <item>
      <title>Database Views and How to Use Them in a Rails Based Application?</title>
      <dc:creator>Kamil Walkowiak</dc:creator>
      <pubDate>Sun, 15 Sep 2019 19:54:49 +0000</pubDate>
      <link>https://dev.to/netguru/database-views-and-how-to-use-them-in-a-rails-based-application-5dih</link>
      <guid>https://dev.to/netguru/database-views-and-how-to-use-them-in-a-rails-based-application-5dih</guid>
      <description>&lt;p&gt;Most of the time when we need to query some data from our database in a Rails-based application, we just use &lt;em&gt;ActiveRecord&lt;/em&gt; query interface. When the query is more complicated, we write it using the SQL language. Sometimes it may be needed to use such SQL query in more than one place. In such cases (to better stick to the DRY principle), we can consider using a database view. In this blog post, I will present what are the database views and how to easily use them in Ruby on Rails application with the help of a &lt;em&gt;Scenic&lt;/em&gt; gem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The database views
&lt;/h2&gt;

&lt;p&gt;The database view is a result set of a query stored in the Database Management System (DBMS). Every time access to the data is required, the stored query must be executed. Though database view is not connected to any persisted data table, it is possible to query it as a regular table (that is why views are often called &lt;em&gt;virtual tables&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;There are a few types of database views:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;read-only views&lt;/strong&gt; - can be only used for querying purposes.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;updatable views&lt;/strong&gt; - can be used for querying and data management (&lt;em&gt;INSERT&lt;/em&gt;, &lt;em&gt;UPDATE&lt;/em&gt;, &lt;em&gt;DELETE&lt;/em&gt; operations) purposes, there are some restrictions for this type of views such as only one &lt;em&gt;FROM&lt;/em&gt; clause or no aggregate functions in view's query definition. This type of view maybe not supported by all DBMS.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;materialized views&lt;/strong&gt; - provide a static snapshot of data while accessing the view to improve the querying performance. The query related to such view is executed only after the view's creation and on-demand (materialized view's refresh operation). Materialized views can be also indexed as the regular data tables to achieve even better performance. This type of view may be also not supported by all DBMS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Database views give us many benefits such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;more DRY database queries&lt;/strong&gt; - we can extract subqueries used in many places to a single view and use simple &lt;em&gt;JOIN&lt;/em&gt; to include the view's data in the main query.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;an additional level of abstraction&lt;/strong&gt; - we can encapsulate pretty complex SQL queries into one, simple to use a view.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;better-querying performance&lt;/strong&gt; - when we know that the data will be more often read than updated or it is possible to proceed with some cached data instead of the most current one we can use materialized view (with additional indexing if needed). It will make the querying process more efficient.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The &lt;em&gt;Scenic&lt;/em&gt; gem
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Scenic&lt;/em&gt; is a gem that allows to easily use database views in a Ruby on Rails-based application without having to switch the database schema to SQL format. It supports versioning of views and provides out-of-the-box support for PostgreSQL. There are available additional adapters for other DBMS such as SQLite, MySQL or SQL Server. Below, we will take a look at the main features offered by the gem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a new view
&lt;/h3&gt;

&lt;p&gt;After including Scenic in the &lt;em&gt;Gemfile&lt;/em&gt;, we can use generators provided by the gem. Let's say that we have the &lt;em&gt;Users&lt;/em&gt; table with one of its columns being called &lt;em&gt;active&lt;/em&gt; and indicating that the user is active. We want to create a view that will return only active users (that have an &lt;em&gt;active&lt;/em&gt; field equal to &lt;em&gt;true&lt;/em&gt;). First, we need to create the view by typing in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rails generate scenic:view active_users
   create  db/views/search_results_v01.sql &lt;span class="c"&gt;# this is a view's SQL query file&lt;/span&gt;
   create  db/migrate/[TIMESTAMP]_create_active_users.rb &lt;span class="c"&gt;# this is a migration file&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As the effect of this command, we get two files. First, let's take look at the migration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateActiveUsers&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_view&lt;/span&gt; &lt;span class="ss"&gt;:active_users&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here, we are using &lt;em&gt;create_view&lt;/em&gt; function provided by the gem. As the argument, we need to provide the view's name. We don't need to do anything more in this file. Now let's move on to the SQL query file. There, we need to provide a SQL query of our database view. Let's write an easy query to fetch all of the active users from our database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It is important to mention that all the views' SQL queries are versioned. We need to create a new version if we want to modify the logic standing behind our view. Each query version can be used in many migrations (e.g. &lt;em&gt;create_view&lt;/em&gt; function mentioned earlier takes an optional parameter &lt;em&gt;version&lt;/em&gt; if no value is given it defaults to 1).&lt;/p&gt;

&lt;p&gt;After providing SQL query of our view we can run the migration. After that, we are ready to use the newly created database view. Let's say that we have such records in the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;001&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;ActiveRecord::Relation [#&amp;lt;User id: 1, active: true&amp;gt;, #&amp;lt;User id: 2, active: false&amp;gt;]&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After calling our database view (by executing a raw SQL query) we get the only user with &lt;em&gt;id&lt;/em&gt; 1 (as only this one is active).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;002&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SELECT * FROM active_users'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rows&lt;/span&gt;
  &lt;span class="no"&gt;SQL&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;active_users&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"t"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  View as an ActiveRecord model
&lt;/h3&gt;

&lt;p&gt;OK, but are we limited to use the views in SQL queries only? Fortunately, the answer is: no. We can create an &lt;em&gt;ActiveRecord&lt;/em&gt; model based on our view and it will behave like a regular AR's model. There is only one exception: the data provided by such model are available in read-only mode. The simplest model based on our view can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActiveUser&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;primary_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;readonly?&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In this piece of code, we set our model's &lt;em&gt;primary key&lt;/em&gt; to &lt;em&gt;id&lt;/em&gt; returned by the view. It is not required but helps to better map our view to AR's model, without it we would get objects with &lt;em&gt;id&lt;/em&gt; field always equal to &lt;em&gt;nil&lt;/em&gt;. Then, we mark our model as read-only so AR will not even try to reach the database when we accidentally call &lt;em&gt;save&lt;/em&gt; on an instance of such a model.&lt;/p&gt;

&lt;p&gt;Now, we can go back to the Rails console and try to use the newly defined model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;003&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ActiveUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;ActiveRecord::Relation [#&amp;lt;ActiveUser id: 1, active: true&amp;gt;]&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Feels good, doesn't it?&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating view
&lt;/h3&gt;

&lt;p&gt;Let's say that we have added a new column (called &lt;em&gt;full_name&lt;/em&gt;) to the &lt;em&gt;Users&lt;/em&gt; table. Now, our data looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;004&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;ActiveRecord::Relation [#&amp;lt;User id: 1, active: true, full_name: 'Jan Kowalski'&amp;gt;, #&amp;lt;User id: 2, active: false, full_name: 'James Bond'&amp;gt;]&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's call out active users view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;005&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ActiveUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;ActiveRecord::Relation [#&amp;lt;ActiveUser id: 1, active: true&amp;gt;]&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see, the new &lt;em&gt;full_name&lt;/em&gt; column is not reflected in the database view. It's because some DBMS (like PostgreSQL which was used while preparing this example) is freezing the columns returned by the view. So even when we have used &lt;em&gt;*&lt;/em&gt; selector, columns added after view creation will be not included in the result set. To get the &lt;em&gt;full_name&lt;/em&gt; column, we need to update the view. To do so, we can use the existing SQL query version that we have used while creating the view, but this time (for academic purposes) we will create a new query version (this time without &lt;em&gt;*&lt;/em&gt; selector to avoid confusion in the future).&lt;/p&gt;

&lt;p&gt;Let's execute the same command that we have used for generating a view for the first time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rails generate scenic:view active_users
   create  db/views/search_results_v02.sql &lt;span class="c"&gt;# new SQL query version&lt;/span&gt;
   create  db/migrate/[TIMESTAMP]_update_active_users_to_version_2.rb &lt;span class="c"&gt;# update migration file&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see, we got a different output than after executing the command last time. &lt;em&gt;Scenic&lt;/em&gt; gem had recognized the existence of the requested view so it created an update migration file (instead of creating) alongside with a new SQL query version file. Let's take a look at the migration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UpdateActiveUsersToVersion2&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;update_view&lt;/span&gt; &lt;span class="ss"&gt;:active_users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;version: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;revert_to_version: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To update the view, we are using the &lt;em&gt;update_view&lt;/em&gt; function. It will first drop the existing view version and then recreate it. As parameters, we are passing the current version (&lt;em&gt;revert_to_version&lt;/em&gt; parameter) and the desired version of the view after update (&lt;em&gt;version&lt;/em&gt; parameter).&lt;/p&gt;

&lt;p&gt;To update the view but without dropping it at first, we can use the &lt;em&gt;replace_view&lt;/em&gt; function. It accepts the same params as &lt;em&gt;update_view&lt;/em&gt; but there are some restrictions resulting from usage of this function (e.g. we may only add additional new columns to the end of the columns list returned by the view). You can find more information about this function in the &lt;em&gt;Scenic&lt;/em&gt; documentation&lt;/p&gt;

&lt;p&gt;Now, let's move on to the newly generated SQL query file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The new query file has been populated with the SQL query from the previous version. Let's modify it so our view will return the &lt;em&gt;id&lt;/em&gt;, &lt;em&gt;active&lt;/em&gt; and &lt;em&gt;full_name&lt;/em&gt; columns but without  using &lt;em&gt;*&lt;/em&gt; selector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, when we migrate the database, we should get the &lt;em&gt;full_name&lt;/em&gt; column in the result set of our view. Let's test it in the Rails console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;006&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ActiveUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;ActiveRecord::Relation [#&amp;lt;ActiveUser id: 1, active: true, full_name: 'Jan Kowalski'&amp;gt;]&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Everything is working as expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Materialized views in  &lt;em&gt;Scenic&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;As mentioned earlier, materialized views can provide a performance boost by serving some kind of cached data instead of executing the query every time we refer to the view. They can be also indexed as regular DB tables. Scenic provides support for such type of views.&lt;/p&gt;

&lt;p&gt;Let's say, that we want to migrate our active users view to a materialized form and add some indexes on top of it. The generator command and the function presented in the create view step accept an optional parameter for creating the materialized views. However, instead of removing the existing one and creating another version (with duplication of the SQL query) of active users view, let's try to migrate the existing one. For this purpose, let's create a new migration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MigrateActiveUsersToMaterializedView&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
    &lt;span class="n"&gt;drop_view&lt;/span&gt; &lt;span class="ss"&gt;:active_users&lt;/span&gt;
    &lt;span class="n"&gt;create_view&lt;/span&gt; &lt;span class="ss"&gt;:active_users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;version: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;materialized: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:active_users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:full_name&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;down&lt;/span&gt;
    &lt;span class="n"&gt;remove_index&lt;/span&gt; &lt;span class="ss"&gt;:active_users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:full_name&lt;/span&gt;

    &lt;span class="n"&gt;drop_view&lt;/span&gt; &lt;span class="ss"&gt;:active_users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;materialized: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="n"&gt;create_view&lt;/span&gt; &lt;span class="ss"&gt;:active_users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;version: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see, we are defining two methods: &lt;em&gt;up&lt;/em&gt; (executed while running the migration) and &lt;em&gt;down&lt;/em&gt; (executed while reverting the migration). In the &lt;em&gt;up&lt;/em&gt; method, we first remove the existing view from our database and then re-create it in the materialized form (using the same SQL query version that was provided earlier). At this stage, the DBMS executes query related to the view and stores its result. When the materialized view is ready, we also add an index on the &lt;em&gt;full_name&lt;/em&gt; column.&lt;/p&gt;

&lt;p&gt;In the &lt;em&gt;down&lt;/em&gt; method we do an opposed set of operations. First, we remove the index on &lt;em&gt;full_name&lt;/em&gt; column, then we remove materialized view and finally we re-create active users as a regular database view.&lt;/p&gt;

&lt;p&gt;After executing such migration, the active users view should be migrated to the materialized form. The querying performance boost for this view could be not so visible as this is a pretty simple view. For the more complicated ones, the improvement in the performance may be much more significant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;A database view is a powerful tool provided by the relational DBMS. We can benefit a lot when using it properly. &lt;em&gt;Scenic&lt;/em&gt; gem allows us to use views easily in Ruby on Rails-based applications. We can also create an &lt;em&gt;ActiveRecord&lt;/em&gt; model based on a view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/scenic-views/scenic"&gt;Scenic gem documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/9.3/tutorial-views.html"&gt;PostgreSQL documentation - database views&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/rules-materializedviews.html"&gt;PostgreSQL documentation - materialized views&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/photos/w7ZyuGYNpRQ?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Kevin Ku&lt;/a&gt; on &lt;a href="https://unsplash.com/search/photos/database?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This blog post was originally posted on &lt;a href="https://www.netguru.com/codestories/database-views-and-how-to-use-them-in-a-ror-based-app"&gt;Netguru's Codestories&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>sql</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
  </channel>
</rss>
