scenic 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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