scenic 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +14 -4
  4. data/.yardopts +0 -1
  5. data/Appraisals +25 -0
  6. data/CONTRIBUTING.md +5 -4
  7. data/LICENSE.txt +1 -1
  8. data/NEWS.md +20 -0
  9. data/README.md +68 -28
  10. data/bin/appraisal +16 -0
  11. data/bin/setup +5 -0
  12. data/bin/yard +16 -0
  13. data/gemfiles/rails40.gemfile +8 -0
  14. data/gemfiles/rails41.gemfile +8 -0
  15. data/gemfiles/rails42.gemfile +8 -0
  16. data/gemfiles/rails50.gemfile +14 -0
  17. data/lib/generators/scenic/generators.rb +1 -0
  18. data/lib/generators/scenic/model/templates/model.erb +1 -1
  19. data/lib/scenic.rb +1 -0
  20. data/lib/scenic/adapters/postgres.rb +140 -33
  21. data/lib/scenic/adapters/postgres/connection.rb +57 -0
  22. data/lib/scenic/adapters/postgres/errors.rb +26 -0
  23. data/lib/scenic/adapters/postgres/index_reapplication.rb +71 -0
  24. data/lib/scenic/adapters/postgres/indexes.rb +53 -0
  25. data/lib/scenic/adapters/postgres/views.rb +51 -0
  26. data/lib/scenic/configuration.rb +2 -2
  27. data/lib/scenic/index.rb +36 -0
  28. data/lib/scenic/schema_dumper.rb +1 -1
  29. data/lib/scenic/statements.rb +7 -13
  30. data/lib/scenic/version.rb +1 -1
  31. data/lib/scenic/view.rb +0 -3
  32. data/scenic.gemspec +4 -1
  33. data/spec/dummy/config/application.rb +3 -0
  34. data/spec/scenic/adapters/postgres/connection_spec.rb +79 -0
  35. data/spec/scenic/adapters/postgres/views_spec.rb +37 -0
  36. data/spec/scenic/adapters/postgres_spec.rb +84 -26
  37. data/spec/scenic/statements_spec.rb +14 -15
  38. data/spec/smoke +16 -3
  39. data/spec/spec_helper.rb +4 -0
  40. metadata +64 -8
  41. data/spec/dummy/config/environments/development.rb +0 -6
  42. data/spec/dummy/config/environments/test.rb +0 -5
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ module Scenic
4
+ module Adapters
5
+ describe Postgres::Views, :db do
6
+ it "returns scenic view objects for plain old views" do
7
+ connection = ActiveRecord::Base.connection
8
+ connection.execute <<-SQL
9
+ CREATE VIEW children AS SELECT text 'Elliot' AS name
10
+ SQL
11
+
12
+ views = Postgres::Views.new(connection).all
13
+ first = views.first
14
+
15
+ expect(views.size).to eq 1
16
+ expect(first.name).to eq "children"
17
+ expect(first.materialized).to be false
18
+ expect(first.definition).to eq "SELECT 'Elliot'::text AS name;"
19
+ end
20
+
21
+ it "returns scenic view objects for materialized views" do
22
+ connection = ActiveRecord::Base.connection
23
+ connection.execute <<-SQL
24
+ CREATE MATERIALIZED VIEW children AS SELECT text 'Owen' AS name
25
+ SQL
26
+
27
+ views = Postgres::Views.new(connection).all
28
+ first = views.first
29
+
30
+ expect(views.size).to eq 1
31
+ expect(first.name).to eq "children"
32
+ expect(first.materialized).to be true
33
+ expect(first.definition).to eq "SELECT 'Owen'::text AS name;"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -3,7 +3,7 @@ require "spec_helper"
3
3
  module Scenic
4
4
  module Adapters
5
5
  describe Postgres, :db do
6
- describe "create_view" do
6
+ describe "#create_view" do
7
7
  it "successfully creates a view" do
8
8
  adapter = Postgres.new
9
9
 
@@ -13,7 +13,7 @@ module Scenic
13
13
  end
14
14
  end
15
15
 
16
- describe "create_materialized_view" do
16
+ describe "#create_materialized_view" do
17
17
  it "successfully creates a materialized view" do
18
18
  adapter = Postgres.new
19
19
 
@@ -26,9 +26,18 @@ module Scenic
26
26
  expect(view.name).to eq("greetings")
27
27
  expect(view.materialized).to eq true
28
28
  end
29
+
30
+ it "raises an exception if the version of PostgreSQL is too old" do
31
+ connection = double("Connection", supports_materialized_views?: false)
32
+ adapter = Postgres.new(connection)
33
+ err = Scenic::Adapters::Postgres::MaterializedViewsNotSupportedError
34
+
35
+ expect { adapter.create_materialized_view("greetings", "select 1") }
36
+ .to raise_error err
37
+ end
29
38
  end
30
39
 
31
- describe "drop_view" do
40
+ describe "#drop_view" do
32
41
  it "successfully drops a view" do
33
42
  adapter = Postgres.new
34
43
 
@@ -39,7 +48,7 @@ module Scenic
39
48
  end
40
49
  end
41
50
 
42
- describe "drop_materialized_view" do
51
+ describe "#drop_materialized_view" do
43
52
  it "successfully drops a materialized view" do
44
53
  adapter = Postgres.new
45
54
 
@@ -51,30 +60,79 @@ module Scenic
51
60
 
52
61
  expect(adapter.views.map(&:name)).not_to include("greetings")
53
62
  end
63
+
64
+ it "raises an exception if the version of PostgreSQL is too old" do
65
+ connection = double("Connection", supports_materialized_views?: false)
66
+ adapter = Postgres.new(connection)
67
+ err = Scenic::Adapters::Postgres::MaterializedViewsNotSupportedError
68
+
69
+ expect { adapter.drop_materialized_view("greetings") }
70
+ .to raise_error err
71
+ end
72
+ end
73
+
74
+ describe "#refresh_materialized_view" do
75
+ it "raises an exception if the version of PostgreSQL is too old" do
76
+ connection = double("Connection", supports_materialized_views?: false)
77
+ adapter = Postgres.new(connection)
78
+ err = Scenic::Adapters::Postgres::MaterializedViewsNotSupportedError
79
+
80
+ expect { adapter.refresh_materialized_view(:tests) }
81
+ .to raise_error err
82
+ end
83
+
84
+ context "refreshing concurrently" do
85
+ it "raises descriptive error if concurrent refresh is not possible" do
86
+ adapter = Postgres.new
87
+ adapter.create_materialized_view(:tests, "SELECT text 'hi' as text")
88
+
89
+ expect {
90
+ adapter.refresh_materialized_view(:tests, concurrently: true)
91
+ }.to raise_error(/Create a unique index with no WHERE clause/)
92
+ end
93
+
94
+ it "raises an exception if the version of PostgreSQL is too old" do
95
+ connection = double("Connection", postgresql_version: 90300)
96
+ adapter = Postgres.new(connection)
97
+ e = Scenic::Adapters::Postgres::ConcurrentRefreshesNotSupportedError
98
+
99
+ expect {
100
+ adapter.refresh_materialized_view(:tests, concurrently: true)
101
+ }.to raise_error e
102
+ end
103
+ end
54
104
  end
55
105
 
56
- it "finds views and builds Scenic::View objects" do
57
- adapter = Postgres.new
58
-
59
- ActiveRecord::Base.connection.execute(
60
- "CREATE VIEW greetings AS SELECT text 'hi' AS greeting"
61
- )
62
- ActiveRecord::Base.connection.execute(
63
- "CREATE MATERIALIZED VIEW farewells AS SELECT text 'bye' AS farewell"
64
- )
65
-
66
- expect(adapter.views).to eq([
67
- Scenic::View.new(
68
- name: "farewells",
69
- definition: "SELECT 'bye'::text AS farewell;",
70
- materialized: true,
71
- ),
72
- Scenic::View.new(
73
- name: "greetings",
74
- definition: "SELECT 'hi'::text AS greeting;",
75
- materialized: false,
76
- ),
77
- ])
106
+ describe "#views" do
107
+ it "returns the views defined on this connection" do
108
+ adapter = Postgres.new
109
+
110
+ ActiveRecord::Base.connection.execute <<-SQL
111
+ CREATE VIEW parents AS SELECT text 'Joe' AS name
112
+ SQL
113
+
114
+ ActiveRecord::Base.connection.execute <<-SQL
115
+ CREATE VIEW children AS SELECT text 'Owen' AS name
116
+ SQL
117
+
118
+ ActiveRecord::Base.connection.execute <<-SQL
119
+ CREATE MATERIALIZED VIEW people AS
120
+ SELECT name FROM parents UNION SELECT name FROM children
121
+ SQL
122
+
123
+ ActiveRecord::Base.connection.execute <<-SQL
124
+ CREATE VIEW people_with_names AS
125
+ SELECT name FROM people
126
+ WHERE name IS NOT NULL
127
+ SQL
128
+
129
+ expect(adapter.views.map(&:name)).to eq [
130
+ "parents",
131
+ "children",
132
+ "people",
133
+ "people_with_names",
134
+ ]
135
+ end
78
136
  end
79
137
  end
80
138
  end
@@ -65,7 +65,7 @@ module Scenic
65
65
  end
66
66
 
67
67
  describe "update_view" do
68
- it "drops the existing version and creates the new" do
68
+ it "updates the view in the database" do
69
69
  definition = instance_double("Definition", to_sql: "definition")
70
70
  allow(Definition).to receive(:new)
71
71
  .with(:name, 3)
@@ -73,8 +73,19 @@ module Scenic
73
73
 
74
74
  connection.update_view(:name, version: 3)
75
75
 
76
- expect(Scenic.database).to have_received(:drop_view).with(:name)
77
- expect(Scenic.database).to have_received(:create_view)
76
+ expect(Scenic.database).to have_received(:update_view)
77
+ .with(:name, definition.to_sql)
78
+ end
79
+
80
+ it "updates the materialized view in the database" do
81
+ definition = instance_double("Definition", to_sql: "definition")
82
+ allow(Definition).to receive(:new)
83
+ .with(:name, 3)
84
+ .and_return(definition)
85
+
86
+ connection.update_view(:name, version: 3, materialized: true)
87
+
88
+ expect(Scenic.database).to have_received(:update_materialized_view)
78
89
  .with(:name, definition.to_sql)
79
90
  end
80
91
 
@@ -84,18 +95,6 @@ module Scenic
84
95
  end
85
96
  end
86
97
 
87
- describe "update_view :materialized" do
88
- it "raises an error because this is not supported" do
89
- definition = instance_double("Definition").as_null_object
90
- allow(Definition).to receive(:new).and_return(definition)
91
-
92
- expect { connection.update_view(:name, version: 3, materialized: true) }.
93
- to raise_error(/not supported/)
94
- expect(Scenic.database).not_to have_received(:drop_materialized_view)
95
- expect(Scenic.database).not_to have_received(:create_materialized_view)
96
- end
97
- end
98
-
99
98
  def connection
100
99
  Class.new { extend Statements }
101
100
  end
data/spec/smoke CHANGED
@@ -21,7 +21,7 @@ trap teardown EXIT
21
21
  verifySearchResults() {
22
22
  echo "verify search results"
23
23
  local expectedResult=$1
24
- local actualResult=$(rails runner "puts Search.first.results")
24
+ local actualResult=$(rails runner "puts Search.take.results")
25
25
  [[ "$actualResult" == "$expectedResult" ]] || exit 1
26
26
  echo "[success]"
27
27
  }
@@ -32,7 +32,7 @@ writeToFileAndMigrateAndVerifySearchResults() {
32
32
  local expectedResult=$2
33
33
  local filePath=db/views/searches_v$version\.sql
34
34
  cp /dev/null $filePath
35
- echo "SELECT '$expectedResult'::text AS results" >> $filePath
35
+ echo "SELECT '$expectedResult'::text AS results, 1 AS user_id" >> $filePath
36
36
  rake db:migrate
37
37
  echo "[success]"
38
38
  verifySearchResults $expectedResult
@@ -97,11 +97,24 @@ main() {
97
97
  rails runner "Search.refresh" || exit 1
98
98
  echo "[success]"
99
99
 
100
+ echo "add indexes to materialized view"
101
+ rails runner "ActiveRecord::Base.connection.execute 'CREATE INDEX searches_test_1 ON searches USING btree (results);'" || exit 1
102
+ rails runner "ActiveRecord::Base.connection.execute 'CREATE INDEX searches_test_2 ON searches USING btree (user_id);'" || exit 1
103
+ echo "[success]"
104
+
105
+ echo "update materialized view"
106
+ rails generate scenic:view search --materialized
107
+ echo "SELECT 'test'::text AS results" > db/views/searches_v02.sql
108
+ rake db:migrate
109
+ verifySearchResults 'test'
110
+
100
111
  echo "rake db:rollback"
101
112
  rake db:rollback
113
+ rake db:rollback
102
114
  echo "[success]"
103
115
 
104
- echo "rails destroy scenic:view search --materialized"
116
+ echo "rails destroy scenic:model search --materialized"
117
+ rails destroy scenic:view search --materialized
105
118
  rails destroy scenic:model search --materialized
106
119
  [[ ! -f app/models/search.rb ]] || exit 1
107
120
  [[ ! -f db/views/searches_v01.sql ]] || exit 1
@@ -15,4 +15,8 @@ RSpec.configure do |config|
15
15
  example.run
16
16
  DatabaseCleaner.clean
17
17
  end
18
+
19
+ if defined? ActiveSupport::Testing::Stream
20
+ config.include ActiveSupport::Testing::Stream
21
+ end
18
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scenic
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Prior
@@ -9,8 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-12-03 00:00:00.000000000 Z
12
+ date: 2016-01-20 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: appraisal
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: bundler
16
30
  requirement: !ruby/object:Gem::Requirement
@@ -109,6 +123,34 @@ dependencies:
109
123
  - - ">="
110
124
  - !ruby/object:Gem::Version
111
125
  version: 1.1.3
126
+ - !ruby/object:Gem::Dependency
127
+ name: yard
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ - !ruby/object:Gem::Dependency
141
+ name: redcarpet
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
112
154
  - !ruby/object:Gem::Dependency
113
155
  name: activerecord
114
156
  requirement: !ruby/object:Gem::Requirement
@@ -151,13 +193,20 @@ files:
151
193
  - ".hound.yml"
152
194
  - ".travis.yml"
153
195
  - ".yardopts"
196
+ - Appraisals
154
197
  - CONTRIBUTING.md
155
198
  - Gemfile
156
199
  - LICENSE.txt
157
200
  - NEWS.md
158
201
  - README.md
159
202
  - Rakefile
203
+ - bin/appraisal
160
204
  - bin/setup
205
+ - bin/yard
206
+ - gemfiles/rails40.gemfile
207
+ - gemfiles/rails41.gemfile
208
+ - gemfiles/rails42.gemfile
209
+ - gemfiles/rails50.gemfile
161
210
  - lib/generators/scenic/generators.rb
162
211
  - lib/generators/scenic/materializable.rb
163
212
  - lib/generators/scenic/model/USAGE
@@ -169,10 +218,16 @@ files:
169
218
  - lib/generators/scenic/view/view_generator.rb
170
219
  - lib/scenic.rb
171
220
  - lib/scenic/adapters/postgres.rb
221
+ - lib/scenic/adapters/postgres/connection.rb
222
+ - lib/scenic/adapters/postgres/errors.rb
223
+ - lib/scenic/adapters/postgres/index_reapplication.rb
224
+ - lib/scenic/adapters/postgres/indexes.rb
225
+ - lib/scenic/adapters/postgres/views.rb
172
226
  - lib/scenic/command_recorder.rb
173
227
  - lib/scenic/command_recorder/statement_arguments.rb
174
228
  - lib/scenic/configuration.rb
175
229
  - lib/scenic/definition.rb
230
+ - lib/scenic/index.rb
176
231
  - lib/scenic/railtie.rb
177
232
  - lib/scenic/schema_dumper.rb
178
233
  - lib/scenic/statements.rb
@@ -189,13 +244,13 @@ files:
189
244
  - spec/dummy/config/boot.rb
190
245
  - spec/dummy/config/database.yml
191
246
  - spec/dummy/config/environment.rb
192
- - spec/dummy/config/environments/development.rb
193
- - spec/dummy/config/environments/test.rb
194
247
  - spec/dummy/db/migrate/.keep
195
248
  - spec/dummy/db/views/.keep
196
249
  - spec/generators/scenic/model/model_generator_spec.rb
197
250
  - spec/generators/scenic/view/view_generator_spec.rb
198
251
  - spec/integration/revert_spec.rb
252
+ - spec/scenic/adapters/postgres/connection_spec.rb
253
+ - spec/scenic/adapters/postgres/views_spec.rb
199
254
  - spec/scenic/adapters/postgres_spec.rb
200
255
  - spec/scenic/command_recorder/statement_arguments_spec.rb
201
256
  - spec/scenic/command_recorder_spec.rb
@@ -219,7 +274,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
219
274
  requirements:
220
275
  - - "~>"
221
276
  - !ruby/object:Gem::Version
222
- version: '2.0'
277
+ version: '2.1'
223
278
  required_rubygems_version: !ruby/object:Gem::Requirement
224
279
  requirements:
225
280
  - - ">="
@@ -227,7 +282,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
227
282
  version: '0'
228
283
  requirements: []
229
284
  rubyforge_project:
230
- rubygems_version: 2.4.8
285
+ rubygems_version: 2.5.1
231
286
  signing_key:
232
287
  specification_version: 4
233
288
  summary: Support for database views in Rails migrations
@@ -242,13 +297,13 @@ test_files:
242
297
  - spec/dummy/config/boot.rb
243
298
  - spec/dummy/config/database.yml
244
299
  - spec/dummy/config/environment.rb
245
- - spec/dummy/config/environments/development.rb
246
- - spec/dummy/config/environments/test.rb
247
300
  - spec/dummy/db/migrate/.keep
248
301
  - spec/dummy/db/views/.keep
249
302
  - spec/generators/scenic/model/model_generator_spec.rb
250
303
  - spec/generators/scenic/view/view_generator_spec.rb
251
304
  - spec/integration/revert_spec.rb
305
+ - spec/scenic/adapters/postgres/connection_spec.rb
306
+ - spec/scenic/adapters/postgres/views_spec.rb
252
307
  - spec/scenic/adapters/postgres_spec.rb
253
308
  - spec/scenic/command_recorder/statement_arguments_spec.rb
254
309
  - spec/scenic/command_recorder_spec.rb
@@ -260,3 +315,4 @@ test_files:
260
315
  - spec/spec_helper.rb
261
316
  - spec/support/generator_spec_setup.rb
262
317
  - spec/support/view_definition_helpers.rb
318
+ has_rdoc:
@@ -1,6 +0,0 @@
1
- Rails.application.configure do
2
- config.cache_classes = false
3
-
4
- # Do not eager load code on boot.
5
- config.eager_load = false
6
- end