schema_plus_views 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3bd09e6485e8794d0454b7d3b6f9e79107f134d2
4
- data.tar.gz: a5ccbf2996d268630f616dded120613df7f9596a
3
+ metadata.gz: b52a3bd48e7f5213ce4cf51909573fd2d02a0fe8
4
+ data.tar.gz: 768b79ea664fa768a68dcec297044845a1ec116d
5
5
  SHA512:
6
- metadata.gz: 04ad39d4d0c9a6cdc8b2624326ff1fd3aac5e8ee3a95ef0fa3d84e17c0573494e2ffbe7de62f46f90bdac073278f9bbc0fd02b2d10db4bb493370f6e43caa2ed
7
- data.tar.gz: dbe18499e3d0108003110873f3738f99c0350870da6e5c7e338935b2a1f509a336554df69328f476348b10771cdd53a114865ca82c9e9d457f32d1da0e50b794
6
+ metadata.gz: 5f1571bf1b1ba2a42dd6f9829711865a436bab9531f8a8b9154e9194481bb2c2c699afec992128e97c5b890d89bb6012da5ebb330e1b41dd1cc78da8a3fd6dbe
7
+ data.tar.gz: 63ae45c9d04e7939aa3ec5d8cae671b7b9bb0acfb87895d912e85943d076a0479d3325579f35f2c8027db10a75f49bf3477184b7d39c666ef8064cf1b8f97d59
data/README.md CHANGED
@@ -56,7 +56,6 @@ Additional options can be provided:
56
56
 
57
57
  * `:allow_replace => true` will use the command "CREATE OR REPLACE" when creating the view, for seamlessly redefining the view even if other views depend on it. It's only supported by MySQL and PostgreSQL, and each has some limitations on when a view can be replaced; see their docs for details.
58
58
 
59
-
60
59
  SchemaPlus::Views also arranges to include the `create_view` statements (with literal SQL) in the schema dump.
61
60
 
62
61
  ### Dropping views
@@ -106,9 +105,70 @@ connection.view_definition(view_name) # => returns SQL string
106
105
 
107
106
  This returns just the body of the definition, i.e. the part after the `CREATE VIEW 'name' AS` command.
108
107
 
108
+ ## Customization API: Middleware Stacks
109
+
110
+ All the methods defined by SchemaPlus::Views provide middleware stacks, in case you need to do any custom filtering, rewriting, triggering, or whatever. For info on how to use middleware stacks, see the READMEs of [schema_monkey](https://SchemaPlus/schema_monkey) and [schema_plus_core](https://SchemaPlus/schema_plus_core).
111
+
112
+
113
+ ### `Schema::Views` stack
114
+
115
+ Wraps the `connection.views` method. Env contains:
116
+
117
+ Env Field | Description | Initialized
118
+ --- | --- | ---
119
+ `:views` | The result of the lookup | `[]`
120
+ `:connection` | The current ActiveRecord connection | *context*
121
+ `:query_name` | Optional label for ActiveRecord logging | *arg*
122
+
123
+ The base implementation appends its results to `env.views`
124
+
125
+ ### `Schema::ViewDefinition` stack
126
+
127
+ Wraps the `connection.view_definition` method. Env contains:
128
+
129
+ Env Field | Description | Initialized
130
+ --- | --- | ---
131
+ `:connection` | The current ActiveRecord connection | *context*
132
+ `:view_name` | The view to look up | *arg*
133
+ `:query_name` | Optional label for ActiveRecord logging | *arg*
134
+ `:definition` | The view definition SQL | `nil`
135
+
136
+ The base implementation looks up the definition of the view named
137
+ `env.view_name` and assigns the result to `env.definition`
138
+
139
+ ### `Migration::CreateView` stack
140
+
141
+ Wraps the `migration.create_view` method. Env contains:
142
+
143
+ Env Field | Description | Initialized
144
+ --- | --- | ---
145
+ `:connection` | The current ActiveRecord connection | *context*
146
+ `:view_name` | The view name | *arg*
147
+ `:definition` | The view definition SQL | *arg*
148
+ `:options` | Create view options | *arg*
149
+
150
+ The base implementation creates the view named `env.view_name` using the
151
+ definition in `env.definition` with options in `env.options`
152
+
153
+ ### `Migration::DropView` stack
154
+
155
+ Wraps the `migration.drop_view` method. Env contains:
156
+
157
+ Env Field | Description | Initialized
158
+ --- | --- | ---
159
+ `:connection` | The current ActiveRecord connection | *context*
160
+ `:view_name` | The view name | *arg*
161
+ `:options` | Drop view options | *arg*
162
+
163
+ The base implementation drops the view named `env.view_name` using the
164
+ options in `env.options`
165
+
109
166
 
110
167
  ## History
111
168
 
169
+ * 0.3.0
170
+ - Added middleware stacks
171
+ - Bug fix: view_definition: strip white space from result (postgresql)
112
172
  * 0.2.3 - Remove unnecessary escaping in dump; use single-quote heredoc
113
173
  * 0.2.2 - Prettier dumps: use heredoc for definition string
114
174
  * 0.2.1 - Fix db:rollback
@@ -5,27 +5,36 @@ module SchemaPlus::Views
5
5
  # Create a view given the SQL definition. Specify :force => true
6
6
  # to first drop the view if it already exists.
7
7
  def create_view(view_name, definition, options={})
8
- definition = definition.to_sql if definition.respond_to? :to_sql
9
- if options[:force]
10
- drop_view(view_name, if_exists: true)
11
- end
8
+ SchemaMonkey::Middleware::Migration::CreateView.start(connection: self, view_name: view_name, definition: definition, options: options) do |env|
9
+ definition = env.definition
10
+ view_name = env.view_name
11
+ options = env.options
12
+ definition = definition.to_sql if definition.respond_to? :to_sql
13
+ if options[:force]
14
+ drop_view(view_name, if_exists: true)
15
+ end
12
16
 
13
- command = if options[:allow_replace]
14
- "CREATE OR REPLACE"
15
- else
16
- "CREATE"
17
- end
17
+ command = if options[:allow_replace]
18
+ "CREATE OR REPLACE"
19
+ else
20
+ "CREATE"
21
+ end
18
22
 
19
- execute "#{command} VIEW #{quote_table_name(view_name)} AS #{definition}"
23
+ execute "#{command} VIEW #{quote_table_name(view_name)} AS #{definition}"
24
+ end
20
25
  end
21
26
 
22
27
  # Drop the named view. Specify :if_exists => true
23
28
  # to fail silently if the view doesn't exist.
24
29
  def drop_view(view_name, options = {})
25
- sql = "DROP VIEW"
26
- sql += " IF EXISTS" if options[:if_exists]
27
- sql += " #{quote_table_name(view_name)}"
28
- execute sql
30
+ SchemaMonkey::Middleware::Migration::DropView.start(connection: self, view_name: view_name, options: options) do |env|
31
+ view_name = env.view_name
32
+ options = env.options
33
+ sql = "DROP VIEW"
34
+ sql += " IF EXISTS" if options[:if_exists]
35
+ sql += " #{quote_table_name(view_name)}"
36
+ execute sql
37
+ end
29
38
  end
30
39
 
31
40
  #####################################################################
@@ -4,24 +4,27 @@ module SchemaPlus::Views
4
4
  module Mysql2Adapter
5
5
 
6
6
  def views(name = nil)
7
- views = []
8
- select_all("SELECT table_name FROM information_schema.views WHERE table_schema = SCHEMA()", name).each do |row|
9
- views << row["table_name"]
10
- end
11
- views
7
+ SchemaMonkey::Middleware::Schema::Views.start(connection: self, query_name: name, views: []) { |env|
8
+ select_all("SELECT table_name FROM information_schema.views WHERE table_schema = SCHEMA()", env.query_name).each do |row|
9
+ env.views << row["table_name"]
10
+ end
11
+ }.views
12
12
  end
13
13
 
14
14
  def view_definition(view_name, name = nil)
15
- results = select_all("SELECT view_definition, check_option FROM information_schema.views WHERE table_schema = SCHEMA() AND table_name = #{quote(view_name)}", name)
16
- return nil unless results.any?
17
- row = results.first
18
- sql = row["view_definition"]
19
- sql.gsub!(%r{#{quote_table_name(current_database)}[.]}, '')
20
- case row["check_option"]
21
- when "CASCADED" then sql += " WITH CASCADED CHECK OPTION"
22
- when "LOCAL" then sql += " WITH LOCAL CHECK OPTION"
23
- end
24
- sql
15
+ SchemaMonkey::Middleware::Schema::ViewDefinition.start(connection: self, view_name: view_name, query_name: name) { |env|
16
+ results = select_all("SELECT view_definition, check_option FROM information_schema.views WHERE table_schema = SCHEMA() AND table_name = #{quote(view_name)}", name)
17
+ if results.any?
18
+ row = results.first
19
+ sql = row["view_definition"]
20
+ sql.gsub!(%r{#{quote_table_name(current_database)}[.]}, '')
21
+ case row["check_option"]
22
+ when "CASCADED" then sql += " WITH CASCADED CHECK OPTION"
23
+ when "LOCAL" then sql += " WITH LOCAL CHECK OPTION"
24
+ end
25
+ env.definition = sql
26
+ end
27
+ }.definition
25
28
  end
26
29
 
27
30
  end
@@ -4,25 +4,29 @@ module SchemaPlus::Views
4
4
  module PostgresqlAdapter
5
5
 
6
6
  def views(name = nil) #:nodoc:
7
- sql = <<-SQL
7
+ SchemaMonkey::Middleware::Schema::Views.start(connection: self, query_name: name, views: []) { |env|
8
+ sql = <<-SQL
8
9
  SELECT viewname
9
10
  FROM pg_views
10
11
  WHERE schemaname = ANY (current_schemas(false))
11
12
  AND viewname NOT LIKE 'pg\_%'
12
- SQL
13
- sql += " AND schemaname != 'postgis'" if adapter_name == 'PostGIS'
14
- query(sql, name).map { |row| row[0] }
13
+ SQL
14
+ sql += " AND schemaname != 'postgis'" if adapter_name == 'PostGIS'
15
+ env.views += env.connection.query(sql, env.query_name).map { |row| row[0] }
16
+ }.views
15
17
  end
16
18
 
17
19
  def view_definition(view_name, name = nil) #:nodoc:
18
- result = query(<<-SQL, name)
19
- SELECT pg_get_viewdef(oid)
20
- FROM pg_class
21
- WHERE relkind = 'v'
22
- AND relname = '#{view_name}'
23
- SQL
24
- row = result.first
25
- row.first.chomp(';') unless row.nil?
20
+ SchemaMonkey::Middleware::Schema::ViewDefinition.start(connection: self, view_name: view_name, query_name: name) { |env|
21
+ result = env.connection.query(<<-SQL, name)
22
+ SELECT pg_get_viewdef(oid)
23
+ FROM pg_class
24
+ WHERE relkind = 'v'
25
+ AND relname = '#{env.view_name}'
26
+ SQL
27
+ row = result.first
28
+ env.definition = row.first.chomp(';').strip unless row.nil?
29
+ }.definition
26
30
  end
27
31
 
28
32
  end
@@ -4,12 +4,17 @@ module SchemaPlus::Views
4
4
  module Sqlite3Adapter
5
5
 
6
6
  def views(name = nil)
7
- execute("SELECT name FROM sqlite_master WHERE type='view'", name).collect{|row| row["name"]}
7
+ SchemaMonkey::Middleware::Schema::Views.start(connection: self, query_name: name, views: []) { |env|
8
+ env.views += env.connection.execute("SELECT name FROM sqlite_master WHERE type='view'", env.query_name).collect{|row| row["name"]}
9
+ }.views
8
10
  end
9
11
 
10
12
  def view_definition(view_name, name = nil)
11
- sql = execute("SELECT sql FROM sqlite_master WHERE type='view' AND name=#{quote(view_name)}", name).collect{|row| row["sql"]}.first
12
- sql.sub(/^CREATE VIEW \S* AS\s+/im, '') unless sql.nil?
13
+ SchemaMonkey::Middleware::Schema::ViewDefinition.start(connection: self, view_name: view_name, query_name: name) { |env|
14
+ sql = env.connection.execute("SELECT sql FROM sqlite_master WHERE type='view' AND name=#{quote(env.view_name)}", env.query_name).collect{|row| row["sql"]}.first
15
+ sql.sub!(/^CREATE VIEW \S* AS\s+/im, '') unless sql.nil?
16
+ env.definition = sql
17
+ }.definition
13
18
  end
14
19
 
15
20
  end
@@ -50,6 +50,28 @@ module SchemaPlus::Views
50
50
  end
51
51
  end
52
52
  end
53
+
54
+ #
55
+ # Define new middleware stacks patterned on SchemaPlus::Core's naming
56
+ # for tables
57
+
58
+ module Schema
59
+ module Views
60
+ ENV = [:connection, :query_name, :views]
61
+ end
62
+ module ViewDefinition
63
+ ENV = [:connection, :view_name, :query_name, :definition]
64
+ end
65
+ end
66
+
67
+ module Migration
68
+ module CreateView
69
+ ENV = [:connection, :view_name, :definition, :options]
70
+ end
71
+ module DropView
72
+ ENV = [:connection, :view_name, :options]
73
+ end
74
+ end
53
75
  end
54
76
 
55
77
  end
@@ -1,5 +1,5 @@
1
1
  module SchemaPlus
2
2
  module Views
3
- VERSION = "0.2.3"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
data/spec/dumper_spec.rb CHANGED
@@ -16,7 +16,6 @@ describe "Dumper" do
16
16
  end
17
17
 
18
18
  it "should include view definitions" do
19
- puts dump
20
19
  expect(dump).to match(view_re("a_ones", /SELECT .*b.*,.*s.* FROM .*items.* WHERE \(?.*a.* = 1\)?/mi))
21
20
  expect(dump).to match(view_re("ab_ones", /SELECT .*s.* FROM .*a_ones.* WHERE \(?.*b.* = 1\)?/mi))
22
21
  end
@@ -17,8 +17,8 @@ describe "Introspection" do
17
17
 
18
18
  it "should list all views" do
19
19
  expect(connection.views.sort).to eq(%W[a_ones ab_ones])
20
- expect(connection.view_definition('a_ones')).to match(%r{^ ?SELECT .*b.*,.*s.* FROM .*items.* WHERE .*a.* = 1}mi)
21
- expect(connection.view_definition('ab_ones')).to match(%r{^ ?SELECT .*s.* FROM .*a_ones.* WHERE .*b.* = 1}mi)
20
+ expect(connection.view_definition('a_ones')).to match(%r{^SELECT .*b.*,.*s.* FROM .*items.* WHERE .*a.* = 1}mi)
21
+ expect(connection.view_definition('ab_ones')).to match(%r{^SELECT .*s.* FROM .*a_ones.* WHERE .*b.* = 1}mi)
22
22
  end
23
23
 
24
24
  it "should ignore views named pg_*", postgresql: :only do
@@ -36,8 +36,8 @@ describe "Introspection" do
36
36
  end
37
37
 
38
38
  it "should introspect definition" do
39
- expect(connection.view_definition('a_ones')).to match(%r{^ ?SELECT .*b.*,.*s.* FROM .*items.* WHERE .*a.* = 1}mi)
40
- expect(connection.view_definition('ab_ones')).to match(%r{^ ?SELECT .*s.* FROM .*a_ones.* WHERE .*b.* = 1}mi)
39
+ expect(connection.view_definition('a_ones')).to match(%r{^SELECT .*b.*,.*s.* FROM .*items.* WHERE .*a.* = 1}mi)
40
+ expect(connection.view_definition('ab_ones')).to match(%r{^SELECT .*s.* FROM .*a_ones.* WHERE .*b.* = 1}mi)
41
41
  end
42
42
 
43
43
  context "in mysql", :mysql => :only do
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+
3
+ module TestMiddleware
4
+ module Middleware
5
+
6
+ module Schema
7
+ module Views
8
+ SPY = []
9
+ def after(env)
10
+ SPY << env.to_hash.except(:connection)
11
+ end
12
+ end
13
+ module ViewDefinition
14
+ SPY = []
15
+ def after(env)
16
+ SPY << env.to_hash.except(:connection)
17
+ end
18
+ end
19
+ end
20
+
21
+ module Migration
22
+ module CreateView
23
+ SPY = []
24
+ def after(env)
25
+ SPY << env.to_hash.except(:connection)
26
+ end
27
+ end
28
+ module DropView
29
+ SPY = []
30
+ def after(env)
31
+ SPY << env.to_hash.except(:connection)
32
+ end
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+
39
+ SchemaMonkey.register TestMiddleware
40
+
41
+ context SchemaPlus::Views::Middleware do
42
+
43
+ let(:schema) { ActiveRecord::Schema }
44
+ let(:migration) { ActiveRecord::Migration }
45
+ let(:connection) { ActiveRecord::Base.connection }
46
+
47
+ before(:each) do
48
+ schema.define do
49
+ create_table :items, force: true do |t|
50
+ t.integer :a
51
+ end
52
+ create_view 'a_view', "select a from items"
53
+ end
54
+ end
55
+
56
+ context TestMiddleware::Middleware::Schema::Views do
57
+ it "calls middleware" do
58
+ expect(spy_on {connection.views 'qn'}).to eq({
59
+ #connection: connection,
60
+ views: ['a_view'],
61
+ query_name: 'qn'
62
+ })
63
+ end
64
+ end
65
+
66
+ context TestMiddleware::Middleware::Schema::ViewDefinition do
67
+ it "calls middleware" do
68
+ spied = spy_on {connection.view_definition('a_view', 'qn')}
69
+ expect(spied[:view_name]).to eq('a_view')
70
+ expect(spied[:definition]).to match(%r{SELECT .*a.* FROM .*items.*}mi)
71
+ expect(spied[:query_name]).to eq('qn')
72
+ end
73
+ end
74
+
75
+ context TestMiddleware::Middleware::Migration::CreateView do
76
+ it "calls middleware" do
77
+ expect(spy_on {migration.create_view('newview', 'select a from items', force: true)}).to eq({
78
+ #connection: connection,
79
+ view_name: 'newview',
80
+ definition: 'select a from items',
81
+ options: { force: true }
82
+ })
83
+ end
84
+ end
85
+
86
+ context TestMiddleware::Middleware::Migration::DropView do
87
+ it "calls middleware" do
88
+ expect(spy_on {migration.drop_view('a_items', if_exists: true)}).to eq({
89
+ #connection: connection,
90
+ view_name: 'a_items',
91
+ options: { if_exists: true }
92
+ })
93
+ end
94
+ end
95
+
96
+
97
+ private
98
+
99
+ def spy_on
100
+ spy = described_class.const_get :SPY
101
+ spy.clear
102
+ yield
103
+ spy.first
104
+ end
105
+
106
+ end
data/spec/spec_helper.rb CHANGED
@@ -21,6 +21,9 @@ RSpec.configure do |config|
21
21
  ActiveRecord::Base.connection.tables.each do |table|
22
22
  ActiveRecord::Migration.drop_table table, force: :cascade
23
23
  end
24
+ ActiveRecord::Base.connection.views.each do |view|
25
+ ActiveRecord::Migration.drop_view view, force: :cascade
26
+ end
24
27
  example.run
25
28
  end
26
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: schema_plus_views
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ronen barzel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-25 00:00:00.000000000 Z
11
+ date: 2015-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -153,6 +153,7 @@ files:
153
153
  - schema_plus_views.gemspec
154
154
  - spec/dumper_spec.rb
155
155
  - spec/introspection_spec.rb
156
+ - spec/middleware_spec.rb
156
157
  - spec/migration_spec.rb
157
158
  - spec/named_schemas_spec.rb
158
159
  - spec/sanity_spec.rb
@@ -184,6 +185,7 @@ summary: Adds support for views to ActiveRecord
184
185
  test_files:
185
186
  - spec/dumper_spec.rb
186
187
  - spec/introspection_spec.rb
188
+ - spec/middleware_spec.rb
187
189
  - spec/migration_spec.rb
188
190
  - spec/named_schemas_spec.rb
189
191
  - spec/sanity_spec.rb