scenic 1.4.1 → 1.5.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 (44) hide show
  1. checksums.yaml +5 -5
  2. data/.hound.yml +2 -4
  3. data/.rubocop.yml +129 -0
  4. data/.travis.yml +11 -17
  5. data/Appraisals +14 -21
  6. data/CODE_OF_CONDUCT.md +76 -0
  7. data/CONTRIBUTING.md +3 -7
  8. data/Gemfile +1 -1
  9. data/NEWS.md +32 -13
  10. data/README.md +15 -16
  11. data/Rakefile +2 -2
  12. data/gemfiles/rails42.gemfile +1 -1
  13. data/gemfiles/rails50.gemfile +1 -1
  14. data/gemfiles/rails51.gemfile +1 -1
  15. data/gemfiles/rails52.gemfile +8 -0
  16. data/gemfiles/rails_edge.gemfile +3 -3
  17. data/lib/generators/scenic/materializable.rb +9 -0
  18. data/lib/generators/scenic/model/model_generator.rb +2 -2
  19. data/lib/generators/scenic/view/USAGE +1 -0
  20. data/lib/generators/scenic/view/templates/db/migrate/create_view.erb +1 -1
  21. data/lib/generators/scenic/view/templates/db/migrate/update_view.erb +1 -1
  22. data/lib/generators/scenic/view/view_generator.rb +13 -5
  23. data/lib/scenic/adapters/postgres.rb +14 -4
  24. data/lib/scenic/adapters/postgres/refresh_dependencies.rb +12 -2
  25. data/lib/scenic/adapters/postgres/views.rb +10 -1
  26. data/lib/scenic/schema_dumper.rb +2 -2
  27. data/lib/scenic/statements.rb +24 -6
  28. data/lib/scenic/version.rb +1 -1
  29. data/lib/scenic/view.rb +1 -2
  30. data/scenic.gemspec +22 -23
  31. data/spec/acceptance/user_manages_views_spec.rb +2 -1
  32. data/spec/dummy/app/models/application_record.rb +5 -0
  33. data/spec/generators/scenic/model/model_generator_spec.rb +1 -1
  34. data/spec/generators/scenic/view/view_generator_spec.rb +1 -1
  35. data/spec/scenic/adapters/postgres/refresh_dependencies_spec.rb +60 -26
  36. data/spec/scenic/adapters/postgres_spec.rb +2 -2
  37. data/spec/scenic/definition_spec.rb +1 -1
  38. data/spec/scenic/schema_dumper_spec.rb +17 -2
  39. data/spec/scenic/statements_spec.rb +48 -13
  40. data/spec/spec_helper.rb +1 -1
  41. data/spec/support/generator_spec_setup.rb +1 -1
  42. metadata +15 -13
  43. data/gemfiles/rails40.gemfile +0 -8
  44. data/gemfiles/rails41.gemfile +0 -8
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Scenic
2
2
 
3
- ![Scenic Landscape](https://images.thoughtbot.com/announcing-scenic--versioned-database-views-for-rails/MRUcPsxrTGCeWKyE59Zg_landscape.png)
3
+ ![Scenic Landscape](https://user-images.githubusercontent.com/152152/49344534-a8817480-f646-11e8-8431-3d95d349c070.png)
4
4
 
5
- [![Build Status](https://travis-ci.org/thoughtbot/scenic.svg)](https://travis-ci.org/thoughtbot/scenic)
6
- [![Code Climate](https://codeclimate.com/repos/53c9736269568066a3000c35/badges/85aa9b19f3037252c55d/gpa.svg)](https://codeclimate.com/repos/53c9736269568066a3000c35/feed)
7
- [![Documentation Quality](http://inch-ci.org/github/thoughtbot/scenic.svg?branch=master)](http://inch-ci.org/github/thoughtbot/scenic)
5
+ [![Build Status](https://travis-ci.org/scenic-views/scenic.svg?branch=master)](https://travis-ci.org/scenic-views/scenic)
6
+ [![Documentation Quality](http://inch-ci.org/github/scenic-views/scenic.svg?branch=master)](http://inch-ci.org/github/scenic-views/scenic)
7
+ [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)
8
8
 
9
9
  Scenic adds methods to `ActiveRecord::Migration` to create and manage database
10
10
  views in Rails.
@@ -21,6 +21,12 @@ Scenic ships with support for PostgreSQL. The adapter is configurable (see
21
21
  `Scenic::Configuration`) and has a minimal interface (see
22
22
  `Scenic::Adapters::Postgres`) that other gems can provide.
23
23
 
24
+ ## So how do I install this?
25
+
26
+ If you're using Postgres, Add `gem "scenic"` to your Gemfile and run `bundle
27
+ install`. If you're using something other than Postgres, check out the available
28
+ [third party adapters](https://github.com/scenic-views/scenic#faqs).
29
+
24
30
  ## Great, how do I create a view?
25
31
 
26
32
  You've got this great idea for a view you'd like to call `search_results`. You
@@ -247,21 +253,14 @@ We are aware of the following existing adapter libraries for Scenic which may
247
253
  meet your needs:
248
254
 
249
255
  * [scenic_sqlite_adapter](https://github.com/pdebelak/scenic_sqlite_adapter)
250
- * [scenic-mysql_adapter](https://github.com/EmpaticoOrg/scenic-mysql_adapter.)
256
+ * [scenic-mysql_adapter](https://github.com/EmpaticoOrg/scenic-mysql_adapter)
257
+ * [scenic-sqlserver-adapter](https://github.com/ClickMechanic/scenic_sqlserver_adapter)
258
+ * [scenic-oracle_enhanced_adapter](https://github.com/PMACS/scenic_oracle_enhanced_adapter)
251
259
 
252
260
  ## About
253
261
 
254
- Scenic is maintained by [Derek Prior] and [Caleb Thompson], funded by
255
- thoughtbot, inc. The names and logos for thoughtbot are trademarks of
256
- thoughtbot, inc.
262
+ Scenic is maintained by [Derek Prior], [Caleb Thompson], and you, our
263
+ contributors.
257
264
 
258
265
  [Derek Prior]: http://prioritized.net
259
266
  [Caleb Thompson]: http://calebthompson.io
260
-
261
- ![thoughtbot](http://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg)
262
-
263
- We love open source software! See [our other projects][community] or [hire
264
- us][hire] to help build your product.
265
-
266
- [community]: https://thoughtbot.com/community?utm_source=github
267
- [hire]: https://thoughtbot.com/hire-us?utm_source=github
data/Rakefile CHANGED
@@ -1,5 +1,5 @@
1
- require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
@@ -5,4 +5,4 @@ source "https://rubygems.org"
5
5
  gem "activerecord", "~> 4.2.0"
6
6
  gem "railties", "~> 4.2.0"
7
7
 
8
- gemspec :path => "../"
8
+ gemspec path: "../"
@@ -5,4 +5,4 @@ source "https://rubygems.org"
5
5
  gem "activerecord", "~> 5.0.0"
6
6
  gem "railties", "~> 5.0.0"
7
7
 
8
- gemspec :path => "../"
8
+ gemspec path: "../"
@@ -5,4 +5,4 @@ source "https://rubygems.org"
5
5
  gem "activerecord", "~> 5.1.0"
6
6
  gem "railties", "~> 5.1.0"
7
7
 
8
- gemspec :path => "../"
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 5.2.0"
6
+ gem "railties", "~> 5.2.0"
7
+
8
+ gemspec path: "../"
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", :git => "https://github.com/rails/rails"
6
- gem "arel", :git => "https://github.com/rails/arel"
5
+ gem "rails", git: "https://github.com/rails/rails"
6
+ gem "arel", git: "https://github.com/rails/arel"
7
7
 
8
- gemspec :path => "../"
8
+ gemspec path: "../"
@@ -10,6 +10,11 @@ module Scenic
10
10
  required: false,
11
11
  desc: "Makes the view materialized",
12
12
  default: false
13
+ class_option :no_data,
14
+ type: :boolean,
15
+ required: false,
16
+ desc: "Adds WITH NO DATA when materialized view creates/updates",
17
+ default: false
13
18
  end
14
19
 
15
20
  private
@@ -17,6 +22,10 @@ module Scenic
17
22
  def materialized?
18
23
  options[:materialized]
19
24
  end
25
+
26
+ def no_data?
27
+ options[:no_data]
28
+ end
20
29
  end
21
30
  end
22
31
  end
@@ -8,7 +8,7 @@ module Scenic
8
8
  # @api private
9
9
  class ModelGenerator < Rails::Generators::NamedBase
10
10
  include Scenic::Generators::Materializable
11
- source_root File.expand_path("../templates", __FILE__)
11
+ source_root File.expand_path("templates", __dir__)
12
12
 
13
13
  def invoke_rails_model_generator
14
14
  invoke "model",
@@ -35,7 +35,7 @@ module Scenic
35
35
 
36
36
  def evaluate_template(source)
37
37
  source = File.expand_path(find_in_source_paths(source.to_s))
38
- context = instance_eval("binding")
38
+ context = instance_eval("binding", __FILE__, __LINE__)
39
39
  ERB.new(
40
40
  ::File.binread(source),
41
41
  nil,
@@ -6,6 +6,7 @@ Description:
6
6
  and a migration to replace the old version with the new.
7
7
 
8
8
  To create a materialized view, pass the '--materialized' option.
9
+ To create a materialized view with NO DATA, pass '--no-data' option.
9
10
 
10
11
  Examples:
11
12
  rails generate scenic:view searches
@@ -1,5 +1,5 @@
1
1
  class <%= migration_class_name %> < <%= activerecord_migration_class %>
2
2
  def change
3
- create_view <%= formatted_plural_name %><%= ", materialized: true" if materialized? %>
3
+ create_view <%= formatted_plural_name %><%= create_view_options %>
4
4
  end
5
5
  end
@@ -4,7 +4,7 @@ class <%= migration_class_name %> < <%= activerecord_migration_class %>
4
4
  update_view <%= formatted_plural_name %>,
5
5
  version: <%= version %>,
6
6
  revert_to_version: <%= previous_version %>,
7
- materialized: true
7
+ materialized: <%= no_data? ? "{ no_data: true }" : true %>
8
8
  <%- else -%>
9
9
  update_view <%= formatted_plural_name %>, version: <%= version %>, revert_to_version: <%= previous_version %>
10
10
  <%- end -%>
@@ -8,7 +8,7 @@ module Scenic
8
8
  class ViewGenerator < Rails::Generators::NamedBase
9
9
  include Rails::Generators::Migration
10
10
  include Scenic::Generators::Materializable
11
- source_root File.expand_path("../templates", __FILE__)
11
+ source_root File.expand_path("templates", __dir__)
12
12
 
13
13
  def create_views_directory
14
14
  unless views_directory_path.exist?
@@ -56,7 +56,7 @@ module Scenic
56
56
 
57
57
  def migration_class_name
58
58
  if creating_new_view?
59
- "Create#{class_name.gsub('.', '').pluralize}"
59
+ "Create#{class_name.tr('.', '').pluralize}"
60
60
  else
61
61
  "Update#{class_name.pluralize}ToVersion#{version}"
62
62
  end
@@ -74,7 +74,7 @@ module Scenic
74
74
  private
75
75
 
76
76
  def views_directory_path
77
- @views_directory_path ||= Rails.root.join(*%w(db views))
77
+ @views_directory_path ||= Rails.root.join("db", "views")
78
78
  end
79
79
 
80
80
  def version_regex
@@ -82,7 +82,7 @@ module Scenic
82
82
  end
83
83
 
84
84
  def creating_new_view?
85
- previous_version == 0
85
+ previous_version.zero?
86
86
  end
87
87
 
88
88
  def definition
@@ -94,7 +94,7 @@ module Scenic
94
94
  end
95
95
 
96
96
  def plural_file_name
97
- @plural_file_name ||= file_name.pluralize.gsub(".", "_")
97
+ @plural_file_name ||= file_name.pluralize.tr(".", "_")
98
98
  end
99
99
 
100
100
  def destroying?
@@ -109,6 +109,14 @@ module Scenic
109
109
  end
110
110
  end
111
111
 
112
+ def create_view_options
113
+ if materialized?
114
+ ", materialized: #{no_data? ? '{ no_data: true }' : true}"
115
+ else
116
+ ""
117
+ end
118
+ end
119
+
112
120
  def destroying_initial_view?
113
121
  destroying? && version == 1
114
122
  end
@@ -122,6 +122,9 @@ module Scenic
122
122
  #
123
123
  # @param name The name of the materialized view to create
124
124
  # @param sql_definition The SQL schema that defines the materialized view.
125
+ # @param no_data [Boolean] Default: false. Set to true to create
126
+ # materialized view without running the associated query. You will need
127
+ # to perform a non-concurrent refresh to populate with data.
125
128
  #
126
129
  # This is typically called in a migration via {Statements#create_view}.
127
130
  #
@@ -129,9 +132,13 @@ module Scenic
129
132
  # in use does not support materialized views.
130
133
  #
131
134
  # @return [void]
132
- def create_materialized_view(name, sql_definition)
135
+ def create_materialized_view(name, sql_definition, no_data: false)
133
136
  raise_unless_materialized_views_supported
134
- execute "CREATE MATERIALIZED VIEW #{quote_table_name(name)} AS #{sql_definition};"
137
+ execute <<-SQL
138
+ CREATE MATERIALIZED VIEW #{quote_table_name(name)} AS
139
+ #{sql_definition}
140
+ #{'WITH NO DATA' if no_data};
141
+ SQL
135
142
  end
136
143
 
137
144
  # Updates a materialized view in the database.
@@ -144,17 +151,20 @@ module Scenic
144
151
  #
145
152
  # @param name The name of the view to update
146
153
  # @param sql_definition The SQL schema for the updated view.
154
+ # @param no_data [Boolean] Default: false. Set to true to create
155
+ # materialized view without running the associated query. You will need
156
+ # to perform a non-concurrent refresh to populate with data.
147
157
  #
148
158
  # @raise [MaterializedViewsNotSupportedError] if the version of Postgres
149
159
  # in use does not support materialized views.
150
160
  #
151
161
  # @return [void]
152
- def update_materialized_view(name, sql_definition)
162
+ def update_materialized_view(name, sql_definition, no_data: false)
153
163
  raise_unless_materialized_views_supported
154
164
 
155
165
  IndexReapplication.new(connection: connection).on(name) do
156
166
  drop_materialized_view(name)
157
- create_materialized_view(name, sql_definition)
167
+ create_materialized_view(name, sql_definition, no_data: no_data)
158
168
  end
159
169
  end
160
170
 
@@ -46,10 +46,20 @@ module Scenic
46
46
  def to_sorted_array
47
47
  dependency_hash = parse_to_hash(raw_dependencies)
48
48
  sorted_arr = tsort(dependency_hash)
49
+
49
50
  idx = sorted_arr.find_index do |dep|
50
- dep.include?(view_to_refresh.to_s)
51
+ if view_to_refresh.to_s.include?(".")
52
+ dep == view_to_refresh.to_s
53
+ else
54
+ dep.ends_with?(".#{view_to_refresh}")
55
+ end
56
+ end
57
+
58
+ if idx.present?
59
+ sorted_arr[0...idx]
60
+ else
61
+ []
51
62
  end
52
- sorted_arr[0...idx]
53
63
  end
54
64
 
55
65
  private
@@ -57,7 +57,16 @@ module Scenic
57
57
 
58
58
  def pg_identifier(name)
59
59
  return name if name =~ /^[a-zA-Z_][a-zA-Z0-9_]*$/
60
- PGconn.quote_ident(name)
60
+
61
+ pgconn.quote_ident(name)
62
+ end
63
+
64
+ def pgconn
65
+ if defined?(PG::Connection)
66
+ PG::Connection
67
+ else
68
+ PGconn
69
+ end
61
70
  end
62
71
  end
63
72
  end
@@ -32,8 +32,8 @@ module Scenic
32
32
  def ignored?(table_name)
33
33
  ["schema_migrations", ignore_tables].flatten.any? do |ignored|
34
34
  case ignored
35
- when String; remove_prefix_and_suffix(table_name) == ignored
36
- when Regexp; remove_prefix_and_suffix(table_name) =~ ignored
35
+ when String then remove_prefix_and_suffix(table_name) == ignored
36
+ when Regexp then remove_prefix_and_suffix(table_name) =~ ignored
37
37
  else
38
38
  raise StandardError, "ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values."
39
39
  end
@@ -9,8 +9,9 @@ module Scenic
9
9
  # @param sql_definition [String] The SQL query for the view schema. An error
10
10
  # will be raised if `sql_definition` and `version` are both set,
11
11
  # as they are mutually exclusive.
12
- # @param materialized [Boolean] Set to true to create a materialized view.
13
- # Defaults to false.
12
+ # @param materialized [Boolean, Hash] Set to true to create a materialized
13
+ # view. Set to { no_data: true } to create materialized view without
14
+ # loading data. Defaults to false.
14
15
  # @return The database response from executing the create statement.
15
16
  #
16
17
  # @example Create from `db/views/searches_v02.sql`
@@ -36,7 +37,11 @@ module Scenic
36
37
  sql_definition ||= definition(name, version)
37
38
 
38
39
  if materialized
39
- Scenic.database.create_materialized_view(name, sql_definition)
40
+ Scenic.database.create_materialized_view(
41
+ name,
42
+ sql_definition,
43
+ no_data: no_data(materialized),
44
+ )
40
45
  else
41
46
  Scenic.database.create_view(name, sql_definition)
42
47
  end
@@ -75,8 +80,9 @@ module Scenic
75
80
  # as they are mutually exclusive.
76
81
  # @param revert_to_version [Fixnum] The version number to rollback to on
77
82
  # `rake db rollback`
78
- # @param materialized [Boolean] True if updating a materialized view.
79
- # Defaults to false.
83
+ # @param materialized [Boolean, Hash] True if updating a materialized view.
84
+ # Set to { no_data: true } to update materialized view without loading
85
+ # data. Defaults to false.
80
86
  # @return The database response from executing the create statement.
81
87
  #
82
88
  # @example
@@ -100,7 +106,11 @@ module Scenic
100
106
  sql_definition ||= definition(name, version)
101
107
 
102
108
  if materialized
103
- Scenic.database.update_materialized_view(name, sql_definition)
109
+ Scenic.database.update_materialized_view(
110
+ name,
111
+ sql_definition,
112
+ no_data: no_data(materialized),
113
+ )
104
114
  else
105
115
  Scenic.database.update_view(name, sql_definition)
106
116
  end
@@ -141,5 +151,13 @@ module Scenic
141
151
  def definition(name, version)
142
152
  Scenic::Definition.new(name, version).to_sql
143
153
  end
154
+
155
+ def no_data(materialized)
156
+ if materialized.is_a?(Hash)
157
+ materialized.fetch(:no_data, false)
158
+ else
159
+ false
160
+ end
161
+ end
144
162
  end
145
163
  end
@@ -1,3 +1,3 @@
1
1
  module Scenic
2
- VERSION = "1.4.1".freeze
2
+ VERSION = "1.5.0".freeze
3
3
  end
@@ -45,10 +45,9 @@ module Scenic
45
45
  materialized_option = materialized ? "materialized: true, " : ""
46
46
 
47
47
  <<-DEFINITION
48
- create_view #{name.inspect}, #{materialized_option} sql_definition: <<-\SQL
48
+ create_view #{name.inspect}, #{materialized_option}sql_definition: <<-\SQL
49
49
  #{definition.indent(2)}
50
50
  SQL
51
-
52
51
  DEFINITION
53
52
  end
54
53
  end
@@ -1,38 +1,37 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path("lib", __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'scenic/version'
3
+ require "scenic/version"
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = 'scenic'
6
+ spec.name = "scenic"
8
7
  spec.version = Scenic::VERSION
9
- spec.authors = ['Derek Prior', 'Caleb Thompson']
10
- spec.email = ['derekprior@gmail.com', 'caleb@calebthompson.io']
11
- spec.summary = %q{Support for database views in Rails migrations}
8
+ spec.authors = ["Derek Prior", "Caleb Thompson"]
9
+ spec.email = ["derekprior@gmail.com", "caleb@calebthompson.io"]
10
+ spec.summary = "Support for database views in Rails migrations"
12
11
  spec.description = <<-DESCRIPTION
13
12
  Adds methods to ActiveRecord::Migration to create and manage database views
14
13
  in Rails
15
14
  DESCRIPTION
16
- spec.homepage = 'https://github.com/thoughtbot/scenic'
17
- spec.license = 'MIT'
15
+ spec.homepage = "https://github.com/scenic-views/scenic"
16
+ spec.license = "MIT"
18
17
 
19
18
  spec.files = `git ls-files -z`.split("\x0")
20
19
  spec.test_files = spec.files.grep(%r{^spec/})
21
- spec.require_paths = ['lib']
20
+ spec.require_paths = ["lib"]
22
21
 
23
- spec.add_development_dependency 'appraisal'
24
- spec.add_development_dependency 'bundler', '>= 1.5'
25
- spec.add_development_dependency 'database_cleaner'
26
- spec.add_development_dependency 'rake'
27
- spec.add_development_dependency 'rspec', '>= 3.3'
28
- spec.add_development_dependency 'pg'
29
- spec.add_development_dependency 'pry'
30
- spec.add_development_dependency 'ammeter', '>= 1.1.3'
31
- spec.add_development_dependency 'yard'
32
- spec.add_development_dependency 'redcarpet'
22
+ spec.add_development_dependency "appraisal"
23
+ spec.add_development_dependency "bundler", ">= 1.5"
24
+ spec.add_development_dependency "database_cleaner"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec", ">= 3.3"
27
+ spec.add_development_dependency "pg", "~> 0.19"
28
+ spec.add_development_dependency "pry"
29
+ spec.add_development_dependency "ammeter", ">= 1.1.3"
30
+ spec.add_development_dependency "yard"
31
+ spec.add_development_dependency "redcarpet"
33
32
 
34
- spec.add_dependency 'activerecord', '>= 4.0.0'
35
- spec.add_dependency 'railties', '>= 4.0.0'
33
+ spec.add_dependency "activerecord", ">= 4.0.0"
34
+ spec.add_dependency "railties", ">= 4.0.0"
36
35
 
37
- spec.required_ruby_version = '~> 2.1'
36
+ spec.required_ruby_version = ">= 2.3.0"
38
37
  end