scenic 0.3.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -4
  3. data/.yardopts +5 -0
  4. data/CONTRIBUTING.md +25 -0
  5. data/LICENSE.txt +1 -1
  6. data/NEWS.md +61 -9
  7. data/README.md +98 -62
  8. data/Rakefile +5 -0
  9. data/bin/setup +7 -0
  10. data/lib/generators/scenic/generators.rb +11 -0
  11. data/lib/generators/scenic/materializable.rb +22 -0
  12. data/lib/generators/scenic/model/USAGE +2 -0
  13. data/lib/generators/scenic/model/model_generator.rb +31 -4
  14. data/lib/generators/scenic/model/templates/model.erb +3 -2
  15. data/lib/generators/scenic/view/USAGE +2 -0
  16. data/lib/generators/scenic/view/templates/db/migrate/create_view.erb +1 -1
  17. data/lib/generators/scenic/view/templates/db/migrate/update_view.erb +7 -0
  18. data/lib/generators/scenic/view/view_generator.rb +5 -2
  19. data/lib/scenic.rb +13 -2
  20. data/lib/scenic/adapters/postgres.rb +74 -7
  21. data/lib/scenic/command_recorder.rb +1 -0
  22. data/lib/scenic/command_recorder/statement_arguments.rb +1 -0
  23. data/lib/scenic/configuration.rb +37 -0
  24. data/lib/scenic/definition.rb +2 -1
  25. data/lib/scenic/railtie.rb +4 -0
  26. data/lib/scenic/schema_dumper.rb +3 -6
  27. data/lib/scenic/statements.rb +61 -41
  28. data/lib/scenic/version.rb +1 -1
  29. data/lib/scenic/view.rb +41 -9
  30. data/scenic.gemspec +2 -2
  31. data/spec/dummy/db/views/.keep +0 -0
  32. data/spec/generators/scenic/model/model_generator_spec.rb +11 -0
  33. data/spec/generators/scenic/view/view_generator_spec.rb +13 -0
  34. data/spec/scenic/adapters/postgres_spec.rb +57 -14
  35. data/spec/scenic/configuration_spec.rb +27 -0
  36. data/spec/scenic/statements_spec.rb +33 -2
  37. data/spec/smoke +92 -67
  38. data/spec/support/generator_spec_setup.rb +1 -0
  39. metadata +19 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e917734f36443e8d261a50f8bcef5c5e65eeebda
4
- data.tar.gz: d013d537a689625087039b4d6cb2e54a584c73ca
3
+ metadata.gz: aad0218a7caab0f7bb48d964a0b2fb7b45d3da68
4
+ data.tar.gz: 567bf836c969e9a5287a19c10a5dcd40b9a0d03d
5
5
  SHA512:
6
- metadata.gz: be0c588362c95ab41cf71670954569c6c06e8ff7e7ef5b2a27b23b32eb61dc86ea8e13e3a969c7596ca9d55a86d2f8c9999486ecfdcac025dc70cbca81cddbb7
7
- data.tar.gz: d1b4171587355aef488df5ebc9d998da4eaa690baf3e8aad2a6ca9ab6db1b11a4bbe1ac07c43c2c9305b5761b43d24298c983cdf1129e795488d1b035cb0daf8
6
+ metadata.gz: 57c9ba2f5803667e7f256730881e75f95ffa113b6a75d3cb94b1ef884d9e258a26c696697840a4118a88ba010fb759d339e2c89f30a903ad967e700aeb227690
7
+ data.tar.gz: bb6f28f0b4a24ac0e737c98fe4081e4f2a67bf9205bf89701a4deb2d1fc7ccdb1bc5d4fe5d52a002b5cc4f99154ff64d7906ea53ccd49f2e420a87c96f5bce31
@@ -1,19 +1,21 @@
1
+ addons:
2
+ postgresql: "9.3"
1
3
  before_install:
2
4
  - "echo '--colour' > ~/.rspec"
3
5
  - "echo 'gem: --no-document' > ~/.gemrc"
4
6
  - git config --global user.name 'Travis CI'
5
7
  - git config --global user.email 'travis-ci@example.com'
6
- before_script:
7
- - pushd spec/dummy && bundle exec rake db:create && mkdir db/views && popd
8
8
  branches:
9
9
  only:
10
10
  - master
11
+ install:
12
+ - travis_retry bin/setup
11
13
  language:
12
14
  - ruby
13
15
  notifications:
14
16
  email:
15
17
  - false
16
18
  rvm:
17
- - 2.1.5
18
- - 2.2.0
19
+ - 2.1.7
20
+ - 2.2.3
19
21
  sudo: false
@@ -0,0 +1,5 @@
1
+ --hide-api private
2
+ --hide-api extension
3
+ --exclude templates
4
+ --markup markdown
5
+ --markup-provider redcarpet
@@ -0,0 +1,25 @@
1
+ # Contributing
2
+
3
+ We love pull requests from everyone. By participating in this project, you
4
+ agree to abide by the thoughtbot [code of conduct].
5
+
6
+ [code of conduct]: https://thoughtbot.com/open-source-code-of-conduct
7
+
8
+ We expect everyone to follow the code of conduct anywhere in thoughtbot's
9
+ project codebases, issue trackers, chatrooms, and mailing lists.
10
+
11
+ ## Setting Up for Development
12
+
13
+ 1. For the repository.
14
+ 2. Run `bin/setup`, which will install dependencies and create the dummy
15
+ application database.
16
+ 3. Run `rake` to verify that the tests pass.
17
+ 4. Make your change with new passing tests, following the [style guide].
18
+ 5. Write a [good commit message], push your fork, and submit a pull request.
19
+
20
+ [style guide]: https://github.com/thoughtbot/guides/tree/master/style
21
+ [good commit message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
22
+
23
+ Others will give constructive feedback. This is a time for discussion and
24
+ improvements, and making the necessary changes will be required before we can
25
+ merge the contribution.
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 Derek Prior
1
+ Copyright (c) 2014 Derek Prior, Caleb Thompson, and thoughtbot.
2
2
 
3
3
  MIT License
4
4
 
data/NEWS.md CHANGED
@@ -1,12 +1,64 @@
1
- New in 0.3.0 (January 23, 2015)
2
- * Previous view definition is copied into new view definition file when updating
1
+ # News
2
+
3
+ The noteworthy changes for each Scenic version are included here. For a complete
4
+ changelog, see the [CHANGELOG] for each version via the version links.
5
+
6
+ [CHANGELOG]: https://github.com/thoughtbot/scenic/commits/master
7
+
8
+ ## [1.0.0] - November 23, 2015
9
+
10
+ ### Added
11
+ - Added support for [materialized views].
12
+ - Allow changing the database adapter via `Scenic::Configuration`.
13
+
14
+ ### Fixed
15
+ - Improved formatting of the view when dumped to `schema.rb`.
16
+ - Fixed generation of namespaced models by using ActiveRecord's own model
17
+ generator.
18
+ - Eliminated `alias_method_chain` deprecation when running with Rails master
19
+ (5.0).
20
+
21
+ [materialized views]:https://github.com/thoughtbot/scenic/blob/v1.0.0/README.md
22
+ [1.0.0]: https://github.com/thoughtbot/scenic/compare/v0.3.0...v1.0.0
23
+
24
+ ## [0.3.0] - January 23, 2015
25
+
26
+ ### Added
27
+ - Previous view definition is copied into new view definition file when updating
3
28
  an existing view.
4
- * We avoid dumping views that belong to Postgres extensions
5
- * `db/schema.rb` is prettier thanks to a blank line after each view definition.
6
29
 
7
- New in 0.2.1
8
- * View generator will now create `db/views` directory if necessary
30
+ ### Fixed
31
+ - We avoid dumping views that belong to Postgres extensions.
32
+ - `db/schema.rb` is prettier thanks to a blank line after each view definition.
33
+
34
+ [0.3.0]: https://github.com/thoughtbot/scenic/compare/v0.2.1...v0.3.0
35
+
36
+ ## [0.2.1] - January 5, 2015
37
+
38
+ ### Fixed
39
+ - View generator will now create `db/views` directory if necessary.
40
+
41
+ [0.2.1]: https://github.com/thoughtbot/scenic/compare/v0.2.0...v0.2.1
42
+
43
+ ## [0.2.0] - August 11, 2014
44
+
45
+ ### Added
46
+ - Teach view generator to update existing views.
47
+
48
+ ### Fixed
49
+ - Raise an error if view definition is empty.
50
+
51
+ [0.2.0]: https://github.com/thoughtbot/scenic/compare/v0.1.0...v0.2.0
52
+
53
+ ## [0.1.0] - August 4, 2014
54
+
55
+ Scenic makes it easier to work with Postgres views in Rails.
56
+
57
+ It introduces view methods to ActiveRecord::Migration and allows views to be
58
+ dumped to db/schema.rb. It provides generators for models, view definitions,
59
+ and migrations. It is built around a basic versioning system for view
60
+ definition files.
61
+
62
+ In short, go add a view to your app.
9
63
 
10
- New in 0.2.0
11
- * Teach view generator to update existing views [683361d](https://github.com/thoughtbot/scenic/commit/683361d59410f46aba508a3ceb850161dd0be027)
12
- * Raise an error if view definition is empty. [PR #38](https://github.com/thoughtbot/scenic/issues/38)
64
+ [0.1.0]: https://github.com/thoughtbot/scenic/compare/8599daa132880cd6c07efb0395c0fb023b171f47...v0.1.0
data/README.md CHANGED
@@ -1,27 +1,34 @@
1
1
  # Scenic
2
2
 
3
- ![Boston cityscape - it's scenic](http://www.california-tour.com/blog/wp-content/uploads/2011/11/skyline-boats-shutterstock-superreduced.jpg)
3
+ Scenic adds methods to `ActiveRecord::Migration` to create and manage database
4
+ views in Rails.
4
5
 
5
- **Scenic (v0.2.0) is in an early stage of development. While it hasn't been
6
- tested in an actual application, we are relatively confident in its readiness
7
- and value, and stand ready to help resolve any issues it may have**
6
+ Using Scenic, you can bring the power of SQL views to your Rails application
7
+ without having to switch your schema format to SQL. Scenic provides a convention
8
+ for versioning views that keeps your migration history consistent and reversible
9
+ and avoids having to duplicate SQL strings across migrations. As an added bonus,
10
+ you define the structure of your view in a SQL file, meaning you get full SQL
11
+ syntax highlighting in the editor of your choice and can easily test your SQL in
12
+ the database console during development.
8
13
 
9
- ## Description
14
+ Scenic ships with support for PostgreSQL. The adapter is configurable (see
15
+ `Scenic::Configuration`) and has a minimal interface (see
16
+ `Scenic::Adapters::Postgres`) that other gems can provide.
10
17
 
11
- Scenic adds methods to ActiveRecord::Migration to create and manage database
12
- views in Rails.
18
+ ## Great, how do I create a view?
13
19
 
14
- Using Scenic, you can use the power of SQL views in your Rails application
15
- without having to switch your schema format to SQL. Scenic also handles
16
- versioning your views in a way that eliminates duplication across migrations. As
17
- an added bonus, you define the structure of your view in a SQL file, meaning you
18
- get full SQL syntax highlighting support in the editor of your choice.
20
+ You've got this great idea for a view you'd like to call `searches`. You can
21
+ create the migration and the corresponding view definition file with the
22
+ following command:
19
23
 
20
- ## Great, how do I create a view?
24
+ ```sh
25
+ $ rails generate scenic:view searches
26
+ create db/views/searches_v01.sql
27
+ create db/migrate/[TIMESTAMP]_create_searches.rb
28
+ ```
21
29
 
22
- You've got this great idea for a view you'd like to call `searches`. Create a
23
- definition file at `db/views/searches_v01.sql` which contains the query you'd
24
- like to build your view with. Perhaps that looks something like this:
30
+ Edit the `db/views/searches_v01.sql` file with the SQL statement that defines
31
+ your view. In our example, this might look something like this:
25
32
 
26
33
  ```sql
27
34
  SELECT
@@ -40,34 +47,37 @@ SELECT
40
47
  FROM statuses
41
48
  ```
42
49
 
43
- Generate a new migration with the following `change` method:
50
+ The generated migration will contain a `create_view` statement. Run the
51
+ migration, and [baby, you got a view going][carl]. The migration is reversible
52
+ and the schema will be dumped into your `schema.rb` file.
44
53
 
45
- ```ruby
46
- def change
47
- create_view :searches
48
- end
49
- ```
54
+ [carl]: https://www.youtube.com/watch?v=Sr2PlqXw03Y
50
55
 
51
- Run that migration and congrats, you've got yourself a view. The migration is
52
- reversible and it will be dumped into your `schema.rb` file.
56
+ ```sh
57
+ $ rake db:migrate
58
+ ```
53
59
 
54
60
  ## Cool, but what if I need to change that view?
55
61
 
56
- Add the new query to `db/views/searches_v02.sql` and generate a new migration with
57
- the following `change` method:
62
+ Here's where Scenic really shines. Run that same view generator once more:
58
63
 
59
- ```ruby
60
- def change
61
- update_view :searches, version: 2, revert_to_version: 1
62
- end
64
+ ```sh
65
+ $ rails generate scenic:view searches
66
+ create db/views/searches_v02.sql
67
+ create db/migrate/[TIMESTAMP]_update_searches_to_version_2.rb
63
68
  ```
64
69
 
65
- When you run that migration, your view will be updated. The `revert_to_version`
66
- option makes that migration reversible.
70
+ Scenic detected that we already had an existing `searches` view at version 1,
71
+ created a copy of that definition as version 2, and created a migration to
72
+ update to the version 2 schema. All that's left for you to do is tweak the
73
+ schema in the new definition and run the `update_view` migration.
67
74
 
68
75
  ## Can I use this view to back a model?
69
76
 
70
- You bet!
77
+ You bet! Using view-backed models can help promote concepts hidden in your
78
+ relational data to first-class domain objects and can clean up complex
79
+ ActiveRecord or ARel queries. As far as ActiveRecord is concerned, you a view is
80
+ no different than a table.
71
81
 
72
82
  ```ruby
73
83
  class Search < ActiveRecord::Base
@@ -81,50 +91,76 @@ class Search < ActiveRecord::Base
81
91
  end
82
92
  ```
83
93
 
84
- ## Can you make this easier?
85
-
86
- Sure thing. How about some generators?
87
-
88
- ### Model generator
94
+ Scenic even provides a `scenic:model` generator that is a superset of
95
+ `scenic:view`. It will act identically to the Rails `model` generator except
96
+ that it will create a Scenic view migration rather than a table migration.
97
+
98
+ There is no special base class or mixin needed. If desired, any code the model
99
+ generator adds can be removed without worry.
100
+
101
+ ```sh
102
+ $ rails generate scenic:model recent_status
103
+ invoke active_record
104
+ create app/models/recent_status.rb
105
+ invoke test_unit
106
+ create test/models/recent_status_test.rb
107
+ create test/fixtures/recent_statuses.yml
108
+ create db/views/recent_statuses_v01.sql
109
+ create db/migrate/20151112015036_create_recent_statuses.rb
110
+ ```
89
111
 
90
- The `scenic:model` generator builds you a model, view, and migration from
91
- scratch. `db/views/[model]_v01.sql` wil be an empty file that you fill in only
92
- the [query] portion of the view with.
112
+ ### When I query that model with `find` I get an error. What gives?
93
113
 
94
- [query]: http://www.postgresql.org/docs/current/static/sql-createview.html
114
+ Your view cannot have a primary key, but ActiveRecord's `find` method expects to
115
+ query based on one. You can use `find_by!` or you can explicitly set the primary
116
+ key column on your model like so:
95
117
 
118
+ ```ruby
119
+ class People < ActiveRecord::Base
120
+ self.primary_key = :id
121
+ end
96
122
  ```
97
- $ rails generate scenic:model search
98
- create app/models/search.rb
99
- create db/views/searches_v01.sql
100
- create db/migrate/[TIMESTAMP]_create_searches.rb
101
- ```
102
-
103
- ### View generator
104
123
 
105
- The `scenic:view` generator is functionally equivalent to `scenic:model` except
106
- that it doesn't create the model. Convenient.
124
+ ## What about materialized views?
107
125
 
108
- ```
109
- $ rails generate scenic:view search
110
- create db/views/searches_v01.sql
111
- create db/migrate/[TIMESTAMP]_create_searches.rb
112
- ```
126
+ Materialized views are essentially SQL queries whose results can be cached to a
127
+ table, indexed, and periodically refreshed when desired. Does Scenic support
128
+ those? Of course!
113
129
 
114
- Subsequent invocations will create updated view versions and update migrations:
130
+ The `scenic:view` and `scenic:model` generators accept a `--materialized`
131
+ option for this purpose. When used with the model generator, your model will
132
+ have the following method defined as a convenience to aid in scheduling
133
+ refreshes:
115
134
 
116
- ```
117
- rails generate scenic:view search
118
- create db/views/searches_v02.sql
119
- create db/migrate/[TIMESTAMP]_update_searches_to_version_2.rb
135
+ ```ruby
136
+ def self.refresh
137
+ Scenic.database.refresh_materialized_view(table_name)
138
+ end
120
139
  ```
121
140
 
122
141
  ## I don't need this view anymore. Make it go away.
123
142
 
124
- We give you `drop_view` too:
143
+ Scenic gives you `drop_view` too:
125
144
 
126
145
  ```ruby
127
146
  def change
128
147
  drop_view :searches, revert_to_version: 2
129
148
  end
130
149
  ```
150
+
151
+ ## About
152
+
153
+ Scenic is maintained by [Derek Prior] and [Caleb Thompson], funded by
154
+ thoughtbot, inc. The names and logos for thoughtbot are trademarks of
155
+ thoughtbot, inc.
156
+
157
+ [Derek Prior]: http://prioritized.net
158
+ [Caleb Thompson]: http://calebthompson.io
159
+
160
+ ![thoughtbot](https://thoughtbot.com/logo.png)
161
+
162
+ We love open source software! See [our other projects][community] or [hire
163
+ us][hire] to help build your product.
164
+
165
+ [community]: https://thoughtbot.com/community?utm_source=github
166
+ [hire]: https://thoughtbot.com/hire-us?utm_source=github
data/Rakefile CHANGED
@@ -7,4 +7,9 @@ task :smoke do
7
7
  exec "spec/smoke"
8
8
  end
9
9
 
10
+ namespace :dummy do
11
+ require_relative "spec/dummy/config/application"
12
+ Dummy::Application.load_tasks
13
+ end
14
+
10
15
  task default: [:spec, :smoke]
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+
3
+ set -e
4
+
5
+ gem install bundler --conservative
6
+ bundle check || bundle install
7
+ bundle exec rake dummy:db:create
@@ -0,0 +1,11 @@
1
+ module Scenic
2
+ # Scenic provides generators for creating and updating views and ActiveRecord
3
+ # models that are backed by views.
4
+ #
5
+ # See:
6
+ # * {file:lib/generators/scenic/model/USAGE Model Generator}
7
+ # * {file:lib/generators/scenic/view/USAGE View Generator}
8
+ # * {file:README.md README}
9
+ module Generators
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ module Scenic
2
+ module Generators
3
+ # @api private
4
+ module Materializable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_option :materialized,
9
+ type: :boolean,
10
+ required: false,
11
+ desc: "Makes the view materialized",
12
+ default: false
13
+ end
14
+
15
+ private
16
+
17
+ def materialized?
18
+ options[:materialized]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -2,6 +2,8 @@ Description:
2
2
  Create a new database view and ActiveRecord::Base subclass for your
3
3
  application.
4
4
 
5
+ To create a materialized view, pass the '--materialized' option.
6
+
5
7
  Examples:
6
8
  rails generate scenic:model search
7
9
 
@@ -1,19 +1,46 @@
1
1
  require "rails/generators"
2
+ require "rails/generators/rails/model/model_generator"
2
3
  require "generators/scenic/view/view_generator"
4
+ require "generators/scenic/materializable"
3
5
 
4
6
  module Scenic
5
7
  module Generators
8
+ # @api private
6
9
  class ModelGenerator < Rails::Generators::NamedBase
10
+ include Scenic::Generators::Materializable
7
11
  source_root File.expand_path("../templates", __FILE__)
8
12
 
9
- check_class_collision
13
+ def invoke_rails_model_generator
14
+ invoke "model", [name], options.merge(migration: false)
15
+ end
10
16
 
11
- def create_model_file
12
- template("model.erb", "app/models/#{file_name}.rb")
17
+ def inject_model_methods
18
+ if materialized? && generating?
19
+ inject_into_class "app/models/#{file_path}.rb", class_name do
20
+ evaluate_template("model.erb")
21
+ end
22
+ end
13
23
  end
14
24
 
15
25
  def invoke_view_generator
16
- invoke "scenic:view", [singular_name]
26
+ invoke "scenic:view", [table_name], options
27
+ end
28
+
29
+ private
30
+
31
+ def evaluate_template(source)
32
+ source = File.expand_path(find_in_source_paths(source.to_s))
33
+ context = instance_eval("binding")
34
+ ERB.new(
35
+ ::File.binread(source),
36
+ nil,
37
+ "-",
38
+ "@output_buffer",
39
+ ).result(context)
40
+ end
41
+
42
+ def generating?
43
+ behavior != :revoke
17
44
  end
18
45
  end
19
46
  end