[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-7830":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":17,"stars30d":17,"stars90d":16,"forks30d":16,"starsTrendScore":18,"compositeScore":19,"rankGlobal":10,"rankLanguage":10,"license":20,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":21,"hasPages":21,"topics":23,"createdAt":10,"pushedAt":10,"updatedAt":33,"readmeContent":34,"aiSummary":35,"trendingCount":16,"starSnapshotCount":16,"syncStatus":36,"lastSyncTime":37,"discoverSource":38},7830,"scenic","scenic-views\u002Fscenic","scenic-views","Versioned database views for Rails","https:\u002F\u002Fthoughtbot.com\u002Fblog\u002Fannouncing-scenic--versioned-database-views-for-rails",null,"Ruby",3622,243,47,17,0,1,3,29.16,"MIT License",false,"main",[24,25,26,27,28,29,30,31,32],"activerecord","database","database-views","hacktoberfest","postgres","rails","ruby","schema","sql","2026-06-12 02:01:45","# Scenic\n\n![Scenic Landscape](https:\u002F\u002Fuser-images.githubusercontent.com\u002F152152\u002F49344534-a8817480-f646-11e8-8431-3d95d349c070.png)\n\n[![Build Status](https:\u002F\u002Fgithub.com\u002Fscenic-views\u002Fscenic\u002Factions\u002Fworkflows\u002Fci.yml\u002Fbadge.svg)](https:\u002F\u002Fgithub.com\u002Fscenic-views\u002Fscenic\u002Factions\u002Fworkflows\u002Fci.yml)\n[![Documentation Quality](http:\u002F\u002Finch-ci.org\u002Fgithub\u002Fscenic-views\u002Fscenic.svg?branch=master)](http:\u002F\u002Finch-ci.org\u002Fgithub\u002Fscenic-views\u002Fscenic)\n\nScenic adds methods to `ActiveRecord::Migration` to create and manage database\nviews in Rails.\n\nUsing Scenic, you can bring the power of SQL views to your Rails application\nwithout having to switch your schema format to SQL. Scenic provides a convention\nfor versioning views that keeps your migration history consistent and reversible\nand avoids having to duplicate SQL strings across migrations. As an added bonus,\nyou define the structure of your view in a SQL file, meaning you get full SQL\nsyntax highlighting in the editor of your choice and can easily test your SQL in\nthe database console during development.\n\nScenic ships with support for PostgreSQL. The adapter is configurable (see\n`Scenic::Configuration`) and has a minimal interface (see\n`Scenic::Adapters::Postgres`) that other gems can provide.\n\n## So how do I install this?\n\nIf you're using Postgres, Add `gem \"scenic\"` to your Gemfile and run `bundle\ninstall`. If you're using something other than Postgres, check out the available\n[third-party adapters](https:\u002F\u002Fgithub.com\u002Fscenic-views\u002Fscenic#when-will-you-support-mysql-sqlite-or-other-databases).\n\n## Great, how do I create a view?\n\nYou've got this great idea for a view you'd like to call `search_results`. You\ncan create the migration and the corresponding view definition file with the\nfollowing command:\n\n```sh\n$ rails generate scenic:view search_results\n      create  db\u002Fviews\u002Fsearch_results_v01.sql\n      create  db\u002Fmigrate\u002F[TIMESTAMP]_create_search_results.rb\n```\n\nEdit the `db\u002Fviews\u002Fsearch_results_v01.sql` file with the SQL statement that\ndefines your view. In our example, this might look something like this:\n\n```sql\nSELECT\n  statuses.id AS searchable_id,\n  'Status' AS searchable_type,\n  comments.body AS term\nFROM statuses\nJOIN comments ON statuses.id = comments.status_id\n\nUNION\n\nSELECT\n  statuses.id AS searchable_id,\n  'Status' AS searchable_type,\n  statuses.body AS term\nFROM statuses\n```\n\nThe generated migration will contain a `create_view` statement. Run the\nmigration, and [baby, you got a view going][carl]. The migration is reversible\nand the schema will be dumped into your `schema.rb` file.\n\n[carl]: https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=Sr2PlqXw03Y\n\n```sh\n$ rake db:migrate\n```\n\n## Cool, but what if I need to change that view?\n\nHere's where Scenic really shines. Run that same view generator once more:\n\n```sh\n$ rails generate scenic:view search_results\n      create  db\u002Fviews\u002Fsearch_results_v02.sql\n      create  db\u002Fmigrate\u002F[TIMESTAMP]_update_search_results_to_version_2.rb\n```\n\nScenic detected that we already had an existing `search_results` view at version\n1, created a copy of that definition as version 2, and created a migration to\nupdate to the version 2 schema. All that's left for you to do is tweak the\nschema in the new definition and run the `update_view` migration.\n\n## What if I want to change a view without dropping it?\n\nThe `update_view` statement used by default will drop your view then create a\nnew version of it. This may not be desirable when you have complicated\nhierarchies of dependent views.\n\nScenic offers a `replace_view` schema statement, resulting in a `CREATE OR\nREPLACE VIEW` SQL query which will update the supplied view in place, retaining\nall dependencies. Materialized views cannot be replaced in this fashion, though\nthe `side_by_side` update strategy may yield similar results (see below).\n\nYou can generate a migration that uses the `replace_view` schema statement by\npassing the `--replace` option to the `scenic:view` generator:\n\n```sh\n$ rails generate scenic:view search_results --replace\n      create  db\u002Fviews\u002Fsearch_results_v02.sql\n      create  db\u002Fmigrate\u002F[TIMESTAMP]_update_search_results_to_version_2.rb\n```\n\nThe migration will look something like this:\n\n```ruby\nclass UpdateSearchResultsToVersion2 \u003C ActiveRecord::Migration\n  def change\n    replace_view :search_results, version: 2, revert_to_version: 1\n  end\nend\n```\n\n## Can I use this view to back a model?\n\nYou bet! Using view-backed models can help promote concepts hidden in your\nrelational data to first-class domain objects and can clean up complex\nActiveRecord or ARel queries. As far as ActiveRecord is concerned, a view is\nno different than a table.\n\n```ruby\nclass SearchResult \u003C ApplicationRecord\n  belongs_to :searchable, polymorphic: true\n\n  # If you want to be able to call +Model.find+, you\n  # must declare the primary key. It can not be\n  # inferred from column information.\n  # self.primary_key = :id\n\n  # this isn't strictly necessary, but it will prevent\n  # rails from calling save, which would fail anyway.\n  def readonly?\n    true\n  end\nend\n```\n\nScenic even provides a `scenic:model` generator that is a superset of\n`scenic:view`. It will act identically to the Rails `model` generator except\nthat it will create a Scenic view migration rather than a table migration.\n\nThere is no special base class or mixin needed. If desired, any code the model\ngenerator adds can be removed without worry.\n\n```sh\n$ rails generate scenic:model recent_status\n      invoke  active_record\n      create    app\u002Fmodels\u002Frecent_status.rb\n      invoke    test_unit\n      create      test\u002Fmodels\u002Frecent_status_test.rb\n      create      test\u002Ffixtures\u002Frecent_statuses.yml\n      create  db\u002Fviews\u002Frecent_statuses_v01.sql\n      create  db\u002Fmigrate\u002F20151112015036_create_recent_statuses.rb\n```\n\n## What about materialized views?\n\nMaterialized views are essentially SQL queries whose results can be cached to a\ntable, indexed, and periodically refreshed when desired. Does Scenic support\nthose? Of course!\n\nThe `scenic:view` and `scenic:model` generators accept a `--materialized`\noption for this purpose. When used with the model generator, your model will\nhave the following method defined as a convenience to aid in scheduling\nrefreshes:\n\n```ruby\ndef self.refresh\n  Scenic.database.refresh_materialized_view(table_name, concurrently: false, cascade: false)\nend\n```\n\nThis will perform a non-concurrent refresh, locking the view for selects until\nthe refresh is complete. You can avoid locking the view by passing\n`concurrently: true` but this requires both PostgreSQL 9.4 and your view to have\nat least one unique index that covers all rows. You can add or update indexes for\nmaterialized views using table migration methods (e.g. `add_index table_name`)\nand these will be automatically re-applied when views are updated.\n\nThe `cascade` option is to refresh materialized views that depend on other\nmaterialized views. For example, say you have materialized view A, which selects\ndata from materialized view B. To get the most up to date information in view A\nyou would need to refresh view B first, then right after refresh view A. If you\nwould like this cascading refresh of materialized views, set `cascade: true`\non the refresh method in view B.\n\n## Can I update the definition of a materialized view without dropping it?\n\nNo, but Scenic can help you approximate this behavior with its `side_by_side`\nupdate strategy.\n\nGenerally, changing the definition of a materialized view requires dropping it\nand recreating it, either without data or with a non-concurrent refresh. The\nmaterialized view will be locked for selects during the refresh process, which\ncan cause problems in your application if the refresh is not fast.\n\nThe `side_by_side` update strategy prepares the new version of the view under a\ntemporary name. This includes copying the indexes from the original view and\nrefreshing the data. Once prepared, the original view is dropped and the new\nview is renamed to the original view's name. This process minimizes the time the\nview is locked for selects at the cost of additional disk space.\n\nYou can generate a migration that uses the `side_by_side` strategy by passing\nthe `--side-by-side` option to the `scenic:view` generator:\n\n```sh\n$ rails generate scenic:view search_results --materialized --side-by-side\n      create  db\u002Fviews\u002Fsearch_results_v02.sql\n      create  db\u002Fmigrate\u002F[TIMESTAMP]_update_search_results_to_version_2.rb\n```\n\nThe migration will look something like this:\n\n```ruby\nclass UpdateSearchResultsToVersion2 \u003C ActiveRecord::Migration\n  def change\n    update_view :search_results,\n      version: 2,\n      revert_to_version: 1,\n      materialized: { side_by_side: true }\n  end\nend\n```\n\n## I don't need this view anymore. Make it go away.\n\nScenic gives you `drop_view` too:\n\n```ruby\ndef change\n  drop_view :search_results, revert_to_version: 2\n  drop_view :materialized_admin_reports, revert_to_version: 3, materialized: true\nend\n```\n\n## FAQs\n\n### Why do I get an error when querying a view-backed model with `find`, `last`, or `first`?\n\nActiveRecord's `find` method expects to query based on your model's primary key,\nbut views do not have primary keys. Additionally, the `first` and `last` methods\nwill produce queries that attempt to sort based on the primary key.\n\nYou can get around these issues by setting the primary key column on your Rails\nmodel like so:\n\n```ruby\nclass People \u003C ApplicationRecord\n  self.primary_key = :my_unique_identifier_field\nend\n```\n\n### Why is my view missing columns from the underlying table?\n\nDid you create the view with `SELECT [table_name].*`? Most (possibly all)\nrelational databases freeze the view definition at the time of creation. New\ncolumns will not be available in the view until the definition is updated once\nagain. This can be accomplished by \"updating\" the view to its current definition\nto bake in the new meaning of `*`.\n\n```ruby\nadd_column :posts, :title, :string\nupdate_view :posts_with_aggregate_data, version: 2, revert_to_version: 2\n```\n\n### When will you support MySQL, SQLite, or other databases?\n\nWe have no plans to add first-party adapters for other relational databases at\nthis time because we (the maintainers) do not currently have a use for them.\nIt's our experience that maintaining a library effectively requires regular use\nof its features. We're not in a good position to support MySQL, SQLite or other\ndatabase users.\n\nScenic _does_ support configuring different database adapters and should be\nextendable with adapter libraries. If you implement such an adapter, we're happy\nto review and link to it. We're also happy to make changes that would better\naccommodate adapter gems.\n\nWe are aware of the following existing adapter libraries for Scenic which may\nmeet your needs:\n\n- [`scenic_sqlite_adapter`](https:\u002F\u002Fgithub.com\u002Fpdebelak\u002Fscenic_sqlite_adapter)\n- [`scenic-mysql_adapter`](https:\u002F\u002Fgithub.com\u002Fcainlevy\u002Fscenic-mysql_adapter)\n- [`scenic-sqlserver-adapter`](https:\u002F\u002Fgithub.com\u002FClickMechanic\u002Fscenic_sqlserver_adapter)\n- [`scenic-oracle_adapter`](https:\u002F\u002Fgithub.com\u002Fcdinger\u002Fscenic-oracle_adapter)\n\nPlease note that the maintainers of Scenic make no assertions about the\nquality or security of the above adapters.\n\n## About\n\n### Used By\n\nScenic is used by some popular open source Rails apps:\n[Mastodon](https:\u002F\u002Fgithub.com\u002Fmastodon\u002Fmastodon\u002F),\n[Code.org](https:\u002F\u002Fgithub.com\u002Fcode-dot-org\u002Fcode-dot-org), and\n[Lobste.rs](https:\u002F\u002Fgithub.com\u002Flobsters\u002Flobsters\u002F).\n\n### Related projects\n\n- [`fx`](https:\u002F\u002Fgithub.com\u002Fteoljungberg\u002Ffx) Versioned database functions and\n  triggers for Rails\n\n### Media\n\nHere are a few posts we've seen discussing Scenic:\n\n- [Announcing Scenic - Versioned Database Views for Rails](https:\u002F\u002Fthoughtbot.com\u002Fblog\u002Fannouncing-scenic--versioned-database-views-for-rails) by Derek Prior for thoughtbot\n- [Effectively Using Materialized Views in Ruby on Rails](https:\u002F\u002Fpganalyze.com\u002Fblog\u002Fmaterialized-views-ruby-rails) by Leigh Halliday for pganalyze\n- [Optimizing String Concatenation in Ruby on Rails](https:\u002F\u002Fdev.to\u002Fpimp_my_ruby\u002Ffrom-slow-to-lightning-fast-optimizing-string-concatenation-in-ruby-on-rails-28nk)\n- [Materialized Views In Ruby On Rails With Scenic](https:\u002F\u002Fwww.ideamotive.co\u002Fblog\u002Fmaterialized-views-ruby-rails-scenic) by Dawid Karczewski for Ideamotive\n- [Using Scenic and SQL views to aggregate data](https:\u002F\u002Fdev.to\u002Fweareredlight\u002Fusing-scenic-and-sql-views-to-aggregate-data-226k) by André Perdigão for Redlight Software\n\n### Maintainers\n\nScenic is maintained by [Derek Prior], [Caleb Hearth], and you, our\ncontributors.\n\n[Derek Prior]: http:\u002F\u002Fprioritized.net\n[Caleb Hearth]: http:\u002F\u002Fcalebhearth.com\n","Scenic 是一个为 Rails 应用提供版本化数据库视图管理的工具。它通过扩展 ActiveRecord::Migration 的方法，使开发者能够在不改变 schema 格式的情况下利用 SQL 视图的强大功能。Scenic 支持视图版本控制，确保迁移历史的一致性和可逆性，并允许在单独的 SQL 文件中定义视图结构，从而获得更好的编辑器支持和测试便利性。该项目主要支持 PostgreSQL 数据库，但其适配器接口设计简洁，易于扩展到其他数据库系统。Scenic 适用于需要高效管理和维护复杂查询逻辑的 Rails 项目，特别是在数据处理与分析场景下能够显著提升开发效率。",2,"2026-06-11 03:14:38","top_language"]