spectacles 1.0.1 → 6.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.
- checksums.yaml +5 -5
- data/.travis.yml +14 -0
- data/Gemfile +0 -1
- data/LICENSE +1 -1
- data/Rakefile +1 -1
- data/Readme.rdoc +5 -3
- data/lib/spectacles/abstract_adapter_override.rb +10 -0
- data/lib/spectacles/configuration.rb +2 -1
- data/lib/spectacles/materialized_view.rb +11 -3
- data/lib/spectacles/railtie.rb +5 -2
- data/lib/spectacles/schema_dumper.rb +11 -4
- data/lib/spectacles/schema_statements/abstract_adapter.rb +8 -4
- data/lib/spectacles/schema_statements/mysql2_adapter.rb +41 -3
- data/lib/spectacles/schema_statements/postgresql_adapter.rb +15 -5
- data/lib/spectacles/schema_statements/sqlite3_adapter.rb +1 -1
- data/lib/spectacles/version.rb +1 -1
- data/lib/spectacles/view.rb +1 -2
- data/lib/spectacles.rb +2 -10
- data/specs/adapters/mysql2_adapter_spec.rb +1 -1
- data/specs/adapters/postgresql_adapter_spec.rb +7 -7
- data/specs/spec_helper.rb +1 -1
- data/specs/spectacles/abstract_adapter_override_spec.rb +14 -0
- data/specs/spectacles/schema_statements/abstract_adapter_spec.rb +15 -14
- data/specs/spectacles/view_spec.rb +2 -2
- data/specs/support/schema_statement_examples.rb +74 -48
- data/specs/support/view_examples.rb +10 -8
- data/spectacles.gemspec +4 -4
- metadata +23 -11
- data/lib/spectacles/schema_statements/mysql_adapter.rb +0 -39
- data/specs/adapters/mysql_adapter_spec.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e2459706103157e49b71cf2cc254abd49a98a547c0e997c2a1fe7329d368c8e6
|
4
|
+
data.tar.gz: c65c091166383559ff45c37886b48c8a6252082765bb18c2ea40a63f351318d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 55a1ff8f400aa2d5d91bab39cde96f81e6bfe1d78e39a298896f830bd34469ec687c34744b463f769cbecb66031d12551b98f5aa08241c9cf492b4aab58b546f
|
7
|
+
data.tar.gz: 65440da01035016a79411268ff5239a5329b9c6227112a9b69760a662e6f41def749f1f0d844a6cd60f8a028685f23a1024a98f832256cff385a639f6b04db95
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/LICENSE
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2012 Adam Hutchison, Brandon Dewitt
|
3
|
+
Copyright (c) 2012-2019 Adam Hutchison, Brandon Dewitt
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
6
|
this software and associated documentation files (the "Software"), to deal in
|
data/Rakefile
CHANGED
data/Readme.rdoc
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
{<img src="https://travis-ci.org/liveh2o/spectacles.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/liveh2o/spectacles] {<img src="https://badge.fury.io/rb/spectacles.svg" alt="Gem Version" />}[https://badge.fury.io/rb/spectacles]
|
2
|
+
|
1
3
|
= Spectacles
|
2
4
|
|
3
|
-
Spectacles adds database view functionality to ActiveRecord. It is heavily inspired by Rails SQL Views (
|
5
|
+
Spectacles adds database view functionality to ActiveRecord. It is heavily inspired by Rails SQL Views (created by https://github.com/aeden but no longer maintained) and built from the ground up to work with Rails 3.2+.
|
4
6
|
|
5
7
|
Spectacles provides the ability to create views in migrations using a similar format to creating tables. It also provides an abstract view class that inherits from ActiveRecord::Base that can be used to create view-backed models.
|
6
8
|
|
@@ -57,7 +59,7 @@ they are kind of a cross between tables (which persist data) and views
|
|
57
59
|
# just like Spectacles::View
|
58
60
|
end
|
59
61
|
|
60
|
-
Because
|
62
|
+
Because materialized views cache a snapshot of the data as it
|
61
63
|
exists at a point in time (typically when the view was created), you
|
62
64
|
need to manually _refresh_ the view when new data is added to the
|
63
65
|
original tables. You can do this with the +#refresh!+ method on
|
@@ -115,4 +117,4 @@ to affect how the new view is created:
|
|
115
117
|
|
116
118
|
= License
|
117
119
|
|
118
|
-
Spectacles is licensed under MIT license (Read
|
120
|
+
Spectacles is licensed under MIT license (Read the LICENSE file for full license)
|
@@ -0,0 +1,10 @@
|
|
1
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
|
2
|
+
class << self
|
3
|
+
alias_method(:_spectacles_orig_inherited, :inherited) if method_defined?(:inherited)
|
4
|
+
|
5
|
+
def inherited(_subclass)
|
6
|
+
::Spectacles::load_adapters
|
7
|
+
_spectacles_orig_inherited(_subclass) if methods.include?(:_spectacles_orig_inherited)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -3,15 +3,23 @@ module Spectacles
|
|
3
3
|
self.abstract_class = true
|
4
4
|
|
5
5
|
def self.new(*)
|
6
|
-
raise NotImplementedError
|
6
|
+
raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.materialized_view_exists?
|
10
10
|
self.connection.materialized_view_exists?(self.view_name)
|
11
11
|
end
|
12
12
|
|
13
|
-
def self.refresh!
|
14
|
-
|
13
|
+
def self.refresh!(concurrently: false)
|
14
|
+
if concurrently
|
15
|
+
self.connection.refresh_materialized_view_concurrently(self.view_name)
|
16
|
+
else
|
17
|
+
self.connection.refresh_materialized_view(self.view_name)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.refresh_concurrently!
|
22
|
+
refresh!(concurrently: true)
|
15
23
|
end
|
16
24
|
|
17
25
|
class << self
|
data/lib/spectacles/railtie.rb
CHANGED
@@ -5,11 +5,14 @@ module Spectacles
|
|
5
5
|
class Railtie < ::Rails::Railtie
|
6
6
|
config.spectacles = ::ActiveSupport::OrderedOptions.new
|
7
7
|
|
8
|
-
initializer
|
8
|
+
initializer 'spectacles.configure' do |app|
|
9
9
|
Spectacles.configure do |config|
|
10
|
-
if app.config.spectacles.
|
10
|
+
if app.config.spectacles.key?(:enable_schema_dump)
|
11
11
|
config.enable_schema_dump = app.config.spectacles[:enable_schema_dump]
|
12
12
|
end
|
13
|
+
if app.config.spectacles.key?(:skip_views)
|
14
|
+
config.skip_views = app.config.spectacles[:skip_views]
|
15
|
+
end
|
13
16
|
end
|
14
17
|
end
|
15
18
|
end
|
@@ -1,18 +1,21 @@
|
|
1
1
|
module Spectacles
|
2
2
|
module SchemaDumper
|
3
3
|
def self.dump_views(stream, connection)
|
4
|
-
unless
|
4
|
+
unless Spectacles.config.enable_schema_dump == false
|
5
5
|
connection.views.sort.each do |view|
|
6
|
+
next if skip_view?(view)
|
6
7
|
dump_view(stream, connection, view)
|
7
8
|
end
|
8
9
|
end
|
9
10
|
end
|
10
11
|
|
11
|
-
def self.dump_materialized_views(stream, connection)
|
12
|
-
unless
|
12
|
+
def self.dump_materialized_views(dumper, stream, connection)
|
13
|
+
unless Spectacles.config.enable_schema_dump == false
|
13
14
|
if connection.supports_materialized_views?
|
14
15
|
connection.materialized_views.sort.each do |view|
|
16
|
+
next if skip_view?(view)
|
15
17
|
dump_materialized_view(stream, connection, view)
|
18
|
+
dumper.send(:indexes, view, stream)
|
16
19
|
end
|
17
20
|
end
|
18
21
|
end
|
@@ -45,7 +48,7 @@ module Spectacles
|
|
45
48
|
def self.format_option_hash(hash)
|
46
49
|
hash.map do |key, value|
|
47
50
|
"#{key}: #{format_option_value(value)}"
|
48
|
-
end.join(
|
51
|
+
end.join(', ')
|
49
52
|
end
|
50
53
|
|
51
54
|
def self.format_option_value(value)
|
@@ -57,5 +60,9 @@ module Spectacles
|
|
57
60
|
else raise "can't format #{value.inspect}"
|
58
61
|
end
|
59
62
|
end
|
63
|
+
|
64
|
+
def self.skip_view?(view)
|
65
|
+
Spectacles.config.skip_views.any? { |item| item === view }
|
66
|
+
end
|
60
67
|
end
|
61
68
|
end
|
@@ -19,8 +19,8 @@ module Spectacles
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def create_view_statement(view_name, create_query)
|
22
|
-
query = "CREATE VIEW ? AS #{create_query}"
|
23
|
-
query_array = [query, view_name.to_s]
|
22
|
+
#query = "CREATE VIEW ? AS #{create_query}"
|
23
|
+
#query_array = [query, view_name.to_s]
|
24
24
|
|
25
25
|
#return ActiveRecord::Base.__send__(:sanitize_sql_array, query_array)
|
26
26
|
"CREATE VIEW #{view_name} AS #{create_query}"
|
@@ -32,8 +32,8 @@ module Spectacles
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def drop_view_statement(view_name)
|
35
|
-
query = "DROP VIEW IF EXISTS ? "
|
36
|
-
query_array = [query, view_name.to_s]
|
35
|
+
#query = "DROP VIEW IF EXISTS ? "
|
36
|
+
#query_array = [query, view_name.to_s]
|
37
37
|
|
38
38
|
#return ActiveRecord::Base.__send__(:sanitize_sql_array, query_array)
|
39
39
|
"DROP VIEW IF EXISTS #{view_name} "
|
@@ -74,6 +74,10 @@ module Spectacles
|
|
74
74
|
def refresh_materialized_view(view_name)
|
75
75
|
raise NotImplementedError, "Override refresh_materialized_view for your db adapter in #{self.class}"
|
76
76
|
end
|
77
|
+
|
78
|
+
def refresh_materialized_view_concurrently(view_name)
|
79
|
+
raise NotImplementedError, "Override refresh_materialized_view_concurrently for your db adapter in #{self.class}"
|
80
|
+
end
|
77
81
|
end
|
78
82
|
end
|
79
83
|
end
|
@@ -1,9 +1,47 @@
|
|
1
|
-
require 'spectacles/schema_statements/mysql_adapter'
|
2
|
-
|
3
1
|
module Spectacles
|
4
2
|
module SchemaStatements
|
5
3
|
module Mysql2Adapter
|
6
|
-
include Spectacles::SchemaStatements::
|
4
|
+
include Spectacles::SchemaStatements::AbstractAdapter
|
5
|
+
|
6
|
+
# overrides the #tables method from ActiveRecord's MysqlAdapter
|
7
|
+
# to return only tables, and not views.
|
8
|
+
def tables(name = nil, database = nil, like = nil)
|
9
|
+
database = database ? quote_table_name(database) : "DATABASE()"
|
10
|
+
by_name = like ? "AND table_name LIKE #{quote(like)}" : ""
|
11
|
+
|
12
|
+
sql = <<-SQL.squish
|
13
|
+
SELECT table_name, table_type
|
14
|
+
FROM information_schema.tables
|
15
|
+
WHERE table_schema = #{database}
|
16
|
+
AND table_type = 'BASE TABLE'
|
17
|
+
#{by_name}
|
18
|
+
SQL
|
19
|
+
|
20
|
+
execute_and_free(sql, 'SCHEMA') do |result|
|
21
|
+
rows_from(result).map(&:first)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def views(name = nil) #:nodoc:
|
26
|
+
result = execute("SHOW FULL TABLES WHERE TABLE_TYPE='VIEW'")
|
27
|
+
|
28
|
+
rows_from(result).map(&:first)
|
29
|
+
end
|
30
|
+
|
31
|
+
def view_build_query(view, name = nil)
|
32
|
+
result = execute("SHOW CREATE VIEW #{view}", name)
|
33
|
+
algorithm_string = rows_from(result).first[1]
|
34
|
+
|
35
|
+
algorithm_string.gsub(/CREATE .*? (AS)+/i, "")
|
36
|
+
rescue ActiveRecord::StatementInvalid => e
|
37
|
+
raise "No view called #{view} found, #{e}"
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def rows_from(result)
|
43
|
+
result.respond_to?(:rows) ? result.rows : result
|
44
|
+
end
|
7
45
|
end
|
8
46
|
end
|
9
47
|
end
|
@@ -7,10 +7,12 @@ module Spectacles
|
|
7
7
|
|
8
8
|
def views(name = nil) #:nodoc:
|
9
9
|
q = <<-SQL
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
SELECT t.table_name, t.table_type
|
11
|
+
FROM information_schema.tables AS t
|
12
|
+
INNER JOIN pg_class AS c ON c.relname = t.table_name
|
13
|
+
WHERE t.table_schema = ANY(current_schemas(false))
|
14
|
+
AND t.table_type = 'VIEW'
|
15
|
+
AND pg_catalog.pg_get_userbyid(c.relowner) = #{quote(database_username)}
|
14
16
|
SQL
|
15
17
|
|
16
18
|
execute(q, name).map { |row| row['table_name'] }
|
@@ -66,7 +68,7 @@ module Spectacles
|
|
66
68
|
definition = row["definition"].strip.sub(/;$/, "")
|
67
69
|
|
68
70
|
options = {}
|
69
|
-
options[:data] = false if ispopulated == 'f'
|
71
|
+
options[:data] = false if ispopulated == 'f' || ispopulated == false
|
70
72
|
options[:storage] = parse_storage_definition(storage) if storage.present?
|
71
73
|
options[:tablespace] = tablespace if tablespace.present?
|
72
74
|
|
@@ -133,6 +135,10 @@ module Spectacles
|
|
133
135
|
execute "REFRESH MATERIALIZED VIEW #{quote_table_name(view_name)}"
|
134
136
|
end
|
135
137
|
|
138
|
+
def refresh_materialized_view_concurrently(view_name)
|
139
|
+
execute "REFRESH MATERIALIZED VIEW CONCURRENTLY #{quote_table_name(view_name)}"
|
140
|
+
end
|
141
|
+
|
136
142
|
def parse_storage_definition(storage)
|
137
143
|
# JRuby 9000 returns storage as an Array, whereas
|
138
144
|
# MRI returns a string.
|
@@ -145,6 +151,10 @@ module Spectacles
|
|
145
151
|
hash
|
146
152
|
end
|
147
153
|
end
|
154
|
+
|
155
|
+
def database_username
|
156
|
+
@config[:username]
|
157
|
+
end
|
148
158
|
end
|
149
159
|
end
|
150
160
|
end
|
data/lib/spectacles/version.rb
CHANGED
data/lib/spectacles/view.rb
CHANGED
@@ -3,8 +3,7 @@ module Spectacles
|
|
3
3
|
self.abstract_class = true
|
4
4
|
|
5
5
|
def self.new(*)
|
6
|
-
|
7
|
-
super # raise NotImplementedError, "#{self} is an abstract class and can not be instantiated."
|
6
|
+
raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
|
8
7
|
end
|
9
8
|
|
10
9
|
def self.view_exists?
|
data/lib/spectacles.rb
CHANGED
@@ -6,6 +6,7 @@ require 'spectacles/view'
|
|
6
6
|
require 'spectacles/materialized_view'
|
7
7
|
require 'spectacles/version'
|
8
8
|
require 'spectacles/configuration'
|
9
|
+
require 'spectacles/abstract_adapter_override'
|
9
10
|
|
10
11
|
require 'spectacles/railtie' if defined?(Rails)
|
11
12
|
|
@@ -23,21 +24,12 @@ module Spectacles
|
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
|
-
ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
|
27
|
-
alias_method(:_spectacles_original_inherited, :inherited) if method_defined?(:inherited)
|
28
|
-
|
29
|
-
def self.inherited(klass)
|
30
|
-
::Spectacles::load_adapters
|
31
|
-
_spectacles_orig_inherited if method_defined?(:_spectacles_original_inherited)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
27
|
ActiveRecord::SchemaDumper.class_eval do
|
36
28
|
alias_method(:_spectacles_orig_trailer, :trailer)
|
37
29
|
|
38
30
|
def trailer(stream)
|
39
31
|
::Spectacles::SchemaDumper.dump_views(stream, @connection)
|
40
|
-
::Spectacles::SchemaDumper.dump_materialized_views(stream, @connection)
|
32
|
+
::Spectacles::SchemaDumper.dump_materialized_views(self, stream, @connection)
|
41
33
|
_spectacles_orig_trailer(stream)
|
42
34
|
end
|
43
35
|
end
|
@@ -25,44 +25,44 @@ describe "Spectacles::SchemaStatements::PostgreSQLAdapter" do
|
|
25
25
|
|
26
26
|
describe "#view_build_query" do
|
27
27
|
it "should escape double-quotes returned by Postgres" do
|
28
|
-
test_base.view_build_query(:new_product_users).must_match(/\\"/)
|
28
|
+
_(test_base.view_build_query(:new_product_users)).must_match(/\\"/)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
32
|
describe "#materialized_views" do
|
33
33
|
it "should support materialized views" do
|
34
|
-
test_base.supports_materialized_views
|
34
|
+
_(test_base.supports_materialized_views?).must_equal true
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
38
|
describe "#create_materialized_view_statement" do
|
39
39
|
it "should work with no options" do
|
40
40
|
query = test_base.create_materialized_view_statement(:view_name, "select_query_here")
|
41
|
-
query.must_match(/create materialized view view_name as select_query_here with data/i)
|
41
|
+
_(query).must_match(/create materialized view view_name as select_query_here with data/i)
|
42
42
|
end
|
43
43
|
|
44
44
|
it "should allow column names to be specified" do
|
45
45
|
query = test_base.create_materialized_view_statement(:view_name, "select_query_here",
|
46
46
|
columns: %i(first second third))
|
47
|
-
query.must_match(/create materialized view view_name \(first,second,third\) as select_query_here with data/i)
|
47
|
+
_(query).must_match(/create materialized view view_name \(first,second,third\) as select_query_here with data/i)
|
48
48
|
end
|
49
49
|
|
50
50
|
it "should allow storage parameters to be specified" do
|
51
51
|
query = test_base.create_materialized_view_statement(:view_name, "select_query_here",
|
52
52
|
storage: { bats_in_belfry: true, max_wingspan: 15 })
|
53
|
-
query.must_match(/create materialized view view_name with \(bats_in_belfry=true, max_wingspan=15\) as select_query_here with data/i)
|
53
|
+
_(query).must_match(/create materialized view view_name with \(bats_in_belfry=true, max_wingspan=15\) as select_query_here with data/i)
|
54
54
|
end
|
55
55
|
|
56
56
|
it "should allow tablespace to be specified" do
|
57
57
|
query = test_base.create_materialized_view_statement(:view_name, "select_query_here",
|
58
58
|
tablespace: :the_final_frontier)
|
59
|
-
query.must_match(/create materialized view view_name tablespace the_final_frontier as select_query_here with data/i)
|
59
|
+
_(query).must_match(/create materialized view view_name tablespace the_final_frontier as select_query_here with data/i)
|
60
60
|
end
|
61
61
|
|
62
62
|
it "should allow empty view to be created" do
|
63
63
|
query = test_base.create_materialized_view_statement(:view_name, "select_query_here",
|
64
64
|
data: false)
|
65
|
-
query.must_match(/create materialized view view_name as select_query_here with no data/i)
|
65
|
+
_(query).must_match(/create materialized view view_name as select_query_here with no data/i)
|
66
66
|
end
|
67
67
|
end
|
68
68
|
end
|
data/specs/spec_helper.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "loading an adapter" do
|
4
|
+
it "calls the original AR::CA::AbstractAdapter.inherited method" do
|
5
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
|
6
|
+
def self.inherited(subclass)
|
7
|
+
@_spectacles_inherited_called = true
|
8
|
+
end
|
9
|
+
end
|
10
|
+
load File.join(__dir__, '../../lib/spectacles/abstract_adapter_override.rb')
|
11
|
+
Class.new(ActiveRecord::ConnectionAdapters::AbstractAdapter)
|
12
|
+
_(ActiveRecord::ConnectionAdapters::AbstractAdapter.instance_variable_get("@_spectacles_inherited_called")).must_equal true
|
13
|
+
end
|
14
|
+
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Spectacles::SchemaStatements::AbstractAdapter do
|
3
|
+
describe Spectacles::SchemaStatements::AbstractAdapter do
|
4
4
|
class TestBase
|
5
5
|
extend Spectacles::SchemaStatements::AbstractAdapter
|
6
6
|
|
7
7
|
def self.materialized_views
|
8
|
+
@materialized_views ||= nil
|
8
9
|
@materialized_views || super
|
9
10
|
end
|
10
11
|
|
@@ -17,64 +18,64 @@ describe Spectacles::SchemaStatements::AbstractAdapter do
|
|
17
18
|
end
|
18
19
|
|
19
20
|
describe "#create_view" do
|
20
|
-
it "throws error when block not given and no build_query" do
|
21
|
-
lambda { TestBase.create_view(:view_name) }.must_raise(RuntimeError)
|
21
|
+
it "throws error when block not given and no build_query" do
|
22
|
+
_(lambda { TestBase.create_view(:view_name) }).must_raise(RuntimeError)
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
25
|
-
describe "#views" do
|
26
|
-
it "throws error when accessed on AbstractAdapter" do
|
27
|
-
lambda { TestBase.views }.must_raise(RuntimeError)
|
26
|
+
describe "#views" do
|
27
|
+
it "throws error when accessed on AbstractAdapter" do
|
28
|
+
_(lambda { TestBase.views }).must_raise(RuntimeError)
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
31
32
|
describe "#supports_materialized_views?" do
|
32
33
|
it "returns false when accessed on AbstractAdapter" do
|
33
|
-
TestBase.supports_materialized_views
|
34
|
+
_(TestBase.supports_materialized_views?).must_equal false
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
37
38
|
describe "#materialized_views" do
|
38
39
|
it "throws error when accessed on AbstractAdapter" do
|
39
|
-
lambda { TestBase.materialized_views }.must_raise(NotImplementedError)
|
40
|
+
_(lambda { TestBase.materialized_views }).must_raise(NotImplementedError)
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
43
44
|
describe "#materialized_view_exists?" do
|
44
45
|
it "is true when materialized_views includes the view" do
|
45
46
|
TestBase.with_materialized_views(%w(alpha beta gamma)) do
|
46
|
-
TestBase.materialized_view_exists?(:beta).must_equal true
|
47
|
+
_(TestBase.materialized_view_exists?(:beta)).must_equal true
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
50
51
|
it "is false when materialized_views does not include the view" do
|
51
52
|
TestBase.with_materialized_views(%w(alpha beta gamma)) do
|
52
|
-
TestBase.materialized_view_exists?(:delta).must_equal false
|
53
|
+
_(TestBase.materialized_view_exists?(:delta)).must_equal false
|
53
54
|
end
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
57
58
|
describe "#materialized_view_build_query" do
|
58
59
|
it "throws error when accessed on AbstractAdapter" do
|
59
|
-
lambda { TestBase.materialized_view_build_query(:books) }.must_raise(NotImplementedError)
|
60
|
+
_(lambda { TestBase.materialized_view_build_query(:books) }).must_raise(NotImplementedError)
|
60
61
|
end
|
61
62
|
end
|
62
63
|
|
63
64
|
describe "#create_materialized_view" do
|
64
65
|
it "throws error when accessed on AbstractAdapter" do
|
65
|
-
lambda { TestBase.create_materialized_view(:books) }.must_raise(NotImplementedError)
|
66
|
+
_(lambda { TestBase.create_materialized_view(:books) }).must_raise(NotImplementedError)
|
66
67
|
end
|
67
68
|
end
|
68
69
|
|
69
70
|
describe "#drop_materialized_view" do
|
70
71
|
it "throws error when accessed on AbstractAdapter" do
|
71
|
-
lambda { TestBase.drop_materialized_view(:books) }.must_raise(NotImplementedError)
|
72
|
+
_(lambda { TestBase.drop_materialized_view(:books) }).must_raise(NotImplementedError)
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
75
76
|
describe "#refresh_materialized_view" do
|
76
77
|
it "throws error when accessed on AbstractAdapter" do
|
77
|
-
lambda { TestBase.refresh_materialized_view(:books) }.must_raise(NotImplementedError)
|
78
|
+
_(lambda { TestBase.refresh_materialized_view(:books) }).must_raise(NotImplementedError)
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
@@ -1,57 +1,66 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
1
|
shared_examples_for "an adapter" do |adapter|
|
4
2
|
shared_base = Class.new do
|
5
3
|
extend Spectacles::SchemaStatements.const_get(adapter)
|
6
4
|
def self.quote_table_name(name); name; end
|
7
5
|
def self.quote_column_name(name); name; end
|
8
|
-
def self.execute(query); query; end
|
6
|
+
def self.execute(query); query; end
|
9
7
|
end
|
10
8
|
|
11
|
-
describe "ActiveRecord::SchemaDumper#dump" do
|
9
|
+
describe "ActiveRecord::SchemaDumper#dump" do
|
12
10
|
before(:each) do
|
13
11
|
ActiveRecord::Base.connection.drop_view(:new_product_users)
|
14
12
|
|
15
|
-
ActiveRecord::Base.connection.create_view(:new_product_users) do
|
13
|
+
ActiveRecord::Base.connection.create_view(:new_product_users) do
|
16
14
|
"SELECT name AS product_name, first_name AS username FROM
|
17
15
|
products JOIN users ON users.id = products.user_id"
|
18
16
|
end
|
19
17
|
|
20
18
|
if ActiveRecord::Base.connection.supports_materialized_views?
|
21
|
-
ActiveRecord::Base.connection.
|
19
|
+
ActiveRecord::Base.connection.drop_materialized_view(:materialized_product_users)
|
20
|
+
ActiveRecord::Base.connection.drop_materialized_view(:empty_materialized_product_users)
|
21
|
+
|
22
|
+
ActiveRecord::Base.connection.create_materialized_view(:materialized_product_users, force: true) do
|
22
23
|
"SELECT name AS product_name, first_name AS username FROM
|
23
24
|
products JOIN users ON users.id = products.user_id"
|
24
25
|
end
|
25
26
|
|
26
|
-
ActiveRecord::Base.connection.
|
27
|
+
ActiveRecord::Base.connection.add_index :materialized_product_users, :product_name
|
28
|
+
|
29
|
+
ActiveRecord::Base.connection.create_materialized_view(:empty_materialized_product_users, storage: { fillfactor: 50 }, data: false, force: true) do
|
27
30
|
"SELECT name AS product_name, first_name AS username FROM
|
28
31
|
products JOIN users ON users.id = products.user_id"
|
29
32
|
end
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
33
|
-
it "should return create_view in dump stream" do
|
36
|
+
it "should return create_view in dump stream" do
|
34
37
|
stream = StringIO.new
|
35
38
|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
36
|
-
stream.string.must_match(/create_view/)
|
39
|
+
_(stream.string).must_match(/create_view/)
|
37
40
|
end
|
38
41
|
|
39
42
|
if ActiveRecord::Base.connection.supports_materialized_views?
|
40
|
-
it "should return create_materialized_view in dump stream" do
|
43
|
+
it "should return create_materialized_view in dump stream" do
|
44
|
+
stream = StringIO.new
|
45
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
46
|
+
_(stream.string).must_match(/create_materialized_view/)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should return add_index in dump stream" do
|
41
50
|
stream = StringIO.new
|
42
51
|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
43
|
-
stream.string.must_match(/
|
52
|
+
_(stream.string).must_match(/add_index/)
|
44
53
|
end
|
45
54
|
|
46
55
|
it "should include options for create_materialized_view" do
|
47
56
|
stream = StringIO.new
|
48
57
|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
49
|
-
stream.string.must_match(/create_materialized_view.*fillfactor: 50/)
|
50
|
-
stream.string.must_match(/create_materialized_view.*data: false/)
|
58
|
+
_(stream.string).must_match(/create_materialized_view.*fillfactor: 50/)
|
59
|
+
_(stream.string).must_match(/create_materialized_view.*data: false/)
|
51
60
|
end
|
52
61
|
end
|
53
62
|
|
54
|
-
it "should rebuild views in dump stream" do
|
63
|
+
it "should rebuild views in dump stream" do
|
55
64
|
stream = StringIO.new
|
56
65
|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
57
66
|
|
@@ -71,10 +80,10 @@ shared_examples_for "an adapter" do |adapter|
|
|
71
80
|
|
72
81
|
eval(stream.string)
|
73
82
|
|
74
|
-
ActiveRecord::Base.connection.views.must_include('new_product_users')
|
83
|
+
_(ActiveRecord::Base.connection.views).must_include('new_product_users')
|
75
84
|
|
76
85
|
if ActiveRecord::Base.connection.supports_materialized_views?
|
77
|
-
ActiveRecord::Base.connection.materialized_views.must_include('materialized_product_users')
|
86
|
+
_(ActiveRecord::Base.connection.materialized_views).must_include('materialized_product_users')
|
78
87
|
end
|
79
88
|
end
|
80
89
|
end
|
@@ -82,41 +91,41 @@ shared_examples_for "an adapter" do |adapter|
|
|
82
91
|
describe "#create_view" do
|
83
92
|
let(:view_name) { :view_name }
|
84
93
|
|
85
|
-
it "throws error when block not given and no build_query" do
|
86
|
-
lambda { shared_base.create_view(view_name) }.must_raise(RuntimeError)
|
94
|
+
it "throws error when block not given and no build_query" do
|
95
|
+
_(lambda { shared_base.create_view(view_name) }).must_raise(RuntimeError)
|
87
96
|
end
|
88
97
|
|
89
98
|
describe "view_name" do
|
90
|
-
it "takes a symbol as the view_name" do
|
91
|
-
shared_base.create_view(view_name.to_sym, Product.all).must_match(/#{view_name}/)
|
99
|
+
it "takes a symbol as the view_name" do
|
100
|
+
_(shared_base.create_view(view_name.to_sym, Product.all)).must_match(/#{view_name}/)
|
92
101
|
end
|
93
102
|
|
94
|
-
it "takes a string as the view_name" do
|
95
|
-
shared_base.create_view(view_name.to_s, Product.all).must_match(/#{view_name}/)
|
103
|
+
it "takes a string as the view_name" do
|
104
|
+
_(shared_base.create_view(view_name.to_s, Product.all)).must_match(/#{view_name}/)
|
96
105
|
end
|
97
106
|
end
|
98
107
|
|
99
|
-
describe "build_query" do
|
100
|
-
it "uses a string if passed" do
|
108
|
+
describe "build_query" do
|
109
|
+
it "uses a string if passed" do
|
101
110
|
select_statement = "SELECT * FROM products"
|
102
|
-
shared_base.create_view(view_name, select_statement).must_match(/#{Regexp.escape(select_statement)}/)
|
111
|
+
_(shared_base.create_view(view_name, select_statement)).must_match(/#{Regexp.escape(select_statement)}/)
|
103
112
|
end
|
104
113
|
|
105
|
-
it "uses an Arel::Relation if passed" do
|
114
|
+
it "uses an Arel::Relation if passed" do
|
106
115
|
select_statement = Product.all.to_sql
|
107
|
-
shared_base.create_view(view_name, Product.all).must_match(/#{Regexp.escape(select_statement)}/)
|
116
|
+
_(shared_base.create_view(view_name, Product.all)).must_match(/#{Regexp.escape(select_statement)}/)
|
108
117
|
end
|
109
118
|
end
|
110
119
|
|
111
|
-
describe "block" do
|
112
|
-
it "can use an Arel::Relation from the yield" do
|
120
|
+
describe "block" do
|
121
|
+
it "can use an Arel::Relation from the yield" do
|
113
122
|
select_statement = Product.all.to_sql
|
114
|
-
shared_base.create_view(view_name) { Product.all }.must_match(/#{Regexp.escape(select_statement)}/)
|
123
|
+
_(shared_base.create_view(view_name) { Product.all }).must_match(/#{Regexp.escape(select_statement)}/)
|
115
124
|
end
|
116
125
|
|
117
|
-
it "can use a String from the yield" do
|
126
|
+
it "can use a String from the yield" do
|
118
127
|
select_statement = "SELECT * FROM products"
|
119
|
-
shared_base.create_view(view_name) { "SELECT * FROM products" }.must_match(/#{Regexp.escape(select_statement)}/)
|
128
|
+
_(shared_base.create_view(view_name) { "SELECT * FROM products" }).must_match(/#{Regexp.escape(select_statement)}/)
|
120
129
|
end
|
121
130
|
end
|
122
131
|
end
|
@@ -125,55 +134,72 @@ shared_examples_for "an adapter" do |adapter|
|
|
125
134
|
let(:view_name) { :view_name }
|
126
135
|
|
127
136
|
describe "view_name" do
|
128
|
-
it "takes a symbol as the view_name" do
|
129
|
-
shared_base.drop_view(view_name.to_sym).must_match(/#{view_name}/)
|
137
|
+
it "takes a symbol as the view_name" do
|
138
|
+
_(shared_base.drop_view(view_name.to_sym)).must_match(/#{view_name}/)
|
130
139
|
end
|
131
140
|
|
132
|
-
it "takes a string as the view_name" do
|
133
|
-
shared_base.drop_view(view_name.to_s).must_match(/#{view_name}/)
|
141
|
+
it "takes a string as the view_name" do
|
142
|
+
_(shared_base.drop_view(view_name.to_s)).must_match(/#{view_name}/)
|
134
143
|
end
|
135
144
|
end
|
136
145
|
end
|
137
146
|
|
147
|
+
describe "#tables" do
|
148
|
+
it "returns an array of all table names" do
|
149
|
+
_(ActiveRecord::Base.connection.tables).must_include("products")
|
150
|
+
_(ActiveRecord::Base.connection.tables).must_include("users")
|
151
|
+
end
|
152
|
+
|
153
|
+
it "does not include the names of the views" do
|
154
|
+
_(ActiveRecord::Base.connection.tables).wont_include("new_product_users")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe "#views" do
|
159
|
+
it "returns an array of all views" do
|
160
|
+
_(ActiveRecord::Base.connection.views).must_include("new_product_users")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
138
164
|
if shared_base.supports_materialized_views?
|
139
165
|
describe "#create_materialized_view" do
|
140
166
|
let(:view_name) { :view_name }
|
141
167
|
|
142
168
|
it "throws error when block not given and no build_query" do
|
143
|
-
lambda { shared_base.create_materialized_view(view_name) }.must_raise(RuntimeError)
|
169
|
+
_(lambda { shared_base.create_materialized_view(view_name) }).must_raise(RuntimeError)
|
144
170
|
end
|
145
171
|
|
146
172
|
describe "view_name" do
|
147
173
|
it "takes a symbol as the view_name" do
|
148
|
-
shared_base.create_materialized_view(view_name.to_sym, Product.all).must_match(/#{view_name}/)
|
174
|
+
_(shared_base.create_materialized_view(view_name.to_sym, Product.all)).must_match(/#{view_name}/)
|
149
175
|
end
|
150
176
|
|
151
177
|
it "takes a string as the view_name" do
|
152
|
-
shared_base.create_materialized_view(view_name.to_s, Product.all).must_match(/#{view_name}/)
|
178
|
+
_(shared_base.create_materialized_view(view_name.to_s, Product.all)).must_match(/#{view_name}/)
|
153
179
|
end
|
154
180
|
end
|
155
181
|
|
156
182
|
describe "build_query" do
|
157
183
|
it "uses a string if passed" do
|
158
184
|
select_statement = "SELECT * FROM products"
|
159
|
-
shared_base.create_materialized_view(view_name, select_statement).must_match(/#{Regexp.escape(select_statement)}/)
|
185
|
+
_(shared_base.create_materialized_view(view_name, select_statement)).must_match(/#{Regexp.escape(select_statement)}/)
|
160
186
|
end
|
161
187
|
|
162
188
|
it "uses an Arel::Relation if passed" do
|
163
189
|
select_statement = Product.all.to_sql
|
164
|
-
shared_base.create_materialized_view(view_name, Product.all).must_match(/#{Regexp.escape(select_statement)}/)
|
190
|
+
_(shared_base.create_materialized_view(view_name, Product.all)).must_match(/#{Regexp.escape(select_statement)}/)
|
165
191
|
end
|
166
192
|
end
|
167
193
|
|
168
194
|
describe "block" do
|
169
195
|
it "can use an Arel::Relation from the yield" do
|
170
196
|
select_statement = Product.all.to_sql
|
171
|
-
shared_base.create_materialized_view(view_name) { Product.all }.must_match(/#{Regexp.escape(select_statement)}/)
|
197
|
+
_(shared_base.create_materialized_view(view_name) { Product.all }).must_match(/#{Regexp.escape(select_statement)}/)
|
172
198
|
end
|
173
199
|
|
174
200
|
it "can use a String from the yield" do
|
175
201
|
select_statement = "SELECT * FROM products"
|
176
|
-
shared_base.create_materialized_view(view_name) { "SELECT * FROM products" }.must_match(/#{Regexp.escape(select_statement)}/)
|
202
|
+
_(shared_base.create_materialized_view(view_name) { "SELECT * FROM products" }).must_match(/#{Regexp.escape(select_statement)}/)
|
177
203
|
end
|
178
204
|
end
|
179
205
|
end
|
@@ -183,11 +209,11 @@ shared_examples_for "an adapter" do |adapter|
|
|
183
209
|
|
184
210
|
describe "view_name" do
|
185
211
|
it "takes a symbol as the view_name" do
|
186
|
-
shared_base.drop_materialized_view(view_name.to_sym).must_match(/#{view_name}/)
|
212
|
+
_(shared_base.drop_materialized_view(view_name.to_sym)).must_match(/#{view_name}/)
|
187
213
|
end
|
188
214
|
|
189
215
|
it "takes a string as the view_name" do
|
190
|
-
shared_base.drop_materialized_view(view_name.to_s).must_match(/#{view_name}/)
|
216
|
+
_(shared_base.drop_materialized_view(view_name.to_s)).must_match(/#{view_name}/)
|
191
217
|
end
|
192
218
|
end
|
193
219
|
end
|
@@ -197,18 +223,18 @@ shared_examples_for "an adapter" do |adapter|
|
|
197
223
|
|
198
224
|
describe "view_name" do
|
199
225
|
it "takes a symbol as the view_name" do
|
200
|
-
shared_base.refresh_materialized_view(view_name.to_sym).must_match(/#{view_name}/)
|
226
|
+
_(shared_base.refresh_materialized_view(view_name.to_sym)).must_match(/#{view_name}/)
|
201
227
|
end
|
202
228
|
|
203
229
|
it "takes a string as the view_name" do
|
204
|
-
shared_base.refresh_materialized_view(view_name.to_s).must_match(/#{view_name}/)
|
230
|
+
_(shared_base.refresh_materialized_view(view_name.to_s)).must_match(/#{view_name}/)
|
205
231
|
end
|
206
232
|
end
|
207
233
|
end
|
208
234
|
else
|
209
235
|
describe "#materialized_views" do
|
210
236
|
it "should not be supported by #{adapter}" do
|
211
|
-
lambda { shared_base.materialized_views }.must_raise(NotImplementedError)
|
237
|
+
_(lambda { shared_base.materialized_views }).must_raise(NotImplementedError)
|
212
238
|
end
|
213
239
|
end
|
214
240
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
1
|
shared_examples_for "a view model" do
|
4
2
|
ActiveRecord::Base.connection.create_view(:new_product_users) do
|
5
3
|
"SELECT name AS product_name, first_name AS username FROM
|
@@ -12,18 +10,22 @@ shared_examples_for "a view model" do
|
|
12
10
|
|
13
11
|
describe "Spectacles::View" do
|
14
12
|
describe "inherited class" do
|
15
|
-
|
13
|
+
before(:each) do
|
16
14
|
User.destroy_all
|
17
15
|
Product.destroy_all
|
18
16
|
@john = User.create(:first_name => 'John', :last_name => 'Doe')
|
19
17
|
@john.products.create(:name => 'Rubber Duck', :value => 10)
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:new_product_user) { NewProductUser.duck_lovers.load.first }
|
20
21
|
|
21
|
-
|
22
|
+
it "can have scopes" do
|
23
|
+
_(new_product_user.username).must_be @john.first_name
|
22
24
|
end
|
23
25
|
|
24
26
|
describe "an instance" do
|
25
27
|
it "is readonly" do
|
26
|
-
|
28
|
+
_(new_product_user.readonly?).must_be true
|
27
29
|
end
|
28
30
|
end
|
29
31
|
end
|
@@ -48,12 +50,12 @@ shared_examples_for "a view model" do
|
|
48
50
|
MaterializedProductUser.refresh!
|
49
51
|
end
|
50
52
|
|
51
|
-
it "can
|
52
|
-
MaterializedProductUser.duck_lovers.load.first.username.must_be @john.first_name
|
53
|
+
it "can have scopes" do
|
54
|
+
_(MaterializedProductUser.duck_lovers.load.first.username).must_be @john.first_name
|
53
55
|
end
|
54
56
|
|
55
57
|
it "is readonly" do
|
56
|
-
MaterializedProductUser.first.readonly
|
58
|
+
_(MaterializedProductUser.first.readonly?).must_be true
|
57
59
|
end
|
58
60
|
end
|
59
61
|
end
|
data/spectacles.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |gem|
|
|
10
10
|
gem.email = ["liveh2o@gmail.com, brandonsdewitt@gmail.com"]
|
11
11
|
gem.homepage = "http://github.com/liveh2o/spectacles"
|
12
12
|
gem.summary = %q{Spectacles (derived from RailsSQLViews) adds database view functionality to ActiveRecord.}
|
13
|
-
gem.description = %q{Spectacles adds database view functionality to ActiveRecord. Current supported adapters include Postgres, SQLite and
|
13
|
+
gem.description = %q{Spectacles adds database view functionality to ActiveRecord. Current supported adapters include Postgres, SQLite, Vertica, and MySQL.}
|
14
14
|
gem.license = 'MIT'
|
15
15
|
|
16
16
|
gem.files = `git ls-files`.split($\)
|
@@ -21,9 +21,9 @@ Gem::Specification.new do |gem|
|
|
21
21
|
##
|
22
22
|
# Dependencies
|
23
23
|
#
|
24
|
-
gem.required_ruby_version = ">= 2.
|
25
|
-
gem.add_dependency "activerecord", ">= 3.2.0"
|
26
|
-
gem.add_dependency "activesupport", ">= 3.2.0"
|
24
|
+
gem.required_ruby_version = ">= 2.2.0"
|
25
|
+
gem.add_dependency "activerecord", ">= 3.2.0", "~> 6.1.0"
|
26
|
+
gem.add_dependency "activesupport", ">= 3.2.0", "~> 6.1.0"
|
27
27
|
|
28
28
|
##
|
29
29
|
# Development dependencies
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spectacles
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 6.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Hutchison, Brandon Dewitt
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -17,6 +17,9 @@ dependencies:
|
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 3.2.0
|
20
|
+
- - "~>"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 6.1.0
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -24,6 +27,9 @@ dependencies:
|
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: 3.2.0
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 6.1.0
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: activesupport
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -31,6 +37,9 @@ dependencies:
|
|
31
37
|
- - ">="
|
32
38
|
- !ruby/object:Gem::Version
|
33
39
|
version: 3.2.0
|
40
|
+
- - "~>"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 6.1.0
|
34
43
|
type: :runtime
|
35
44
|
prerelease: false
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -38,6 +47,9 @@ dependencies:
|
|
38
47
|
- - ">="
|
39
48
|
- !ruby/object:Gem::Version
|
40
49
|
version: 3.2.0
|
50
|
+
- - "~>"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 6.1.0
|
41
53
|
- !ruby/object:Gem::Dependency
|
42
54
|
name: rake
|
43
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,7 +79,7 @@ dependencies:
|
|
67
79
|
- !ruby/object:Gem::Version
|
68
80
|
version: '0'
|
69
81
|
description: Spectacles adds database view functionality to ActiveRecord. Current
|
70
|
-
supported adapters include Postgres, SQLite and
|
82
|
+
supported adapters include Postgres, SQLite, Vertica, and MySQL.
|
71
83
|
email:
|
72
84
|
- liveh2o@gmail.com, brandonsdewitt@gmail.com
|
73
85
|
executables: []
|
@@ -75,11 +87,13 @@ extensions: []
|
|
75
87
|
extra_rdoc_files: []
|
76
88
|
files:
|
77
89
|
- ".gitignore"
|
90
|
+
- ".travis.yml"
|
78
91
|
- Gemfile
|
79
92
|
- LICENSE
|
80
93
|
- Rakefile
|
81
94
|
- Readme.rdoc
|
82
95
|
- lib/spectacles.rb
|
96
|
+
- lib/spectacles/abstract_adapter_override.rb
|
83
97
|
- lib/spectacles/configuration.rb
|
84
98
|
- lib/spectacles/materialized_view.rb
|
85
99
|
- lib/spectacles/railtie.rb
|
@@ -87,7 +101,6 @@ files:
|
|
87
101
|
- lib/spectacles/schema_statements.rb
|
88
102
|
- lib/spectacles/schema_statements/abstract_adapter.rb
|
89
103
|
- lib/spectacles/schema_statements/mysql2_adapter.rb
|
90
|
-
- lib/spectacles/schema_statements/mysql_adapter.rb
|
91
104
|
- lib/spectacles/schema_statements/postgresql_adapter.rb
|
92
105
|
- lib/spectacles/schema_statements/sqlite3_adapter.rb
|
93
106
|
- lib/spectacles/schema_statements/sqlserver_adapter.rb
|
@@ -95,10 +108,10 @@ files:
|
|
95
108
|
- lib/spectacles/version.rb
|
96
109
|
- lib/spectacles/view.rb
|
97
110
|
- specs/adapters/mysql2_adapter_spec.rb
|
98
|
-
- specs/adapters/mysql_adapter_spec.rb
|
99
111
|
- specs/adapters/postgresql_adapter_spec.rb
|
100
112
|
- specs/adapters/sqlite3_adapter_spec.rb
|
101
113
|
- specs/spec_helper.rb
|
114
|
+
- specs/spectacles/abstract_adapter_override_spec.rb
|
102
115
|
- specs/spectacles/schema_statements/abstract_adapter_spec.rb
|
103
116
|
- specs/spectacles/view_spec.rb
|
104
117
|
- specs/support/minitest_matchers.rb
|
@@ -110,7 +123,7 @@ homepage: http://github.com/liveh2o/spectacles
|
|
110
123
|
licenses:
|
111
124
|
- MIT
|
112
125
|
metadata: {}
|
113
|
-
post_install_message:
|
126
|
+
post_install_message:
|
114
127
|
rdoc_options: []
|
115
128
|
require_paths:
|
116
129
|
- lib
|
@@ -118,16 +131,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
118
131
|
requirements:
|
119
132
|
- - ">="
|
120
133
|
- !ruby/object:Gem::Version
|
121
|
-
version: 2.
|
134
|
+
version: 2.2.0
|
122
135
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
136
|
requirements:
|
124
137
|
- - ">="
|
125
138
|
- !ruby/object:Gem::Version
|
126
139
|
version: '0'
|
127
140
|
requirements: []
|
128
|
-
|
129
|
-
|
130
|
-
signing_key:
|
141
|
+
rubygems_version: 3.3.6
|
142
|
+
signing_key:
|
131
143
|
specification_version: 4
|
132
144
|
summary: Spectacles (derived from RailsSQLViews) adds database view functionality
|
133
145
|
to ActiveRecord.
|
@@ -1,39 +0,0 @@
|
|
1
|
-
require 'spectacles/schema_statements/abstract_adapter'
|
2
|
-
|
3
|
-
module Spectacles
|
4
|
-
module SchemaStatements
|
5
|
-
module MysqlAdapter
|
6
|
-
include Spectacles::SchemaStatements::AbstractAdapter
|
7
|
-
|
8
|
-
# overrides the #tables method from ActiveRecord's MysqlAdapter
|
9
|
-
# to return only tables, and not views.
|
10
|
-
def tables(name = nil, database = nil, like = nil)
|
11
|
-
database = database ? quote_table_name(database) : "DATABASE()"
|
12
|
-
by_name = like ? "AND table_name LIKE #{quote(like)}" : ""
|
13
|
-
|
14
|
-
sql = <<-SQL.squish
|
15
|
-
SELECT table_name, table_type
|
16
|
-
FROM information_schema.tables
|
17
|
-
WHERE table_schema = #{database}
|
18
|
-
AND table_type = 'BASE TABLE'
|
19
|
-
#{by_name}
|
20
|
-
SQL
|
21
|
-
|
22
|
-
execute_and_free(sql, 'SCHEMA') do |result|
|
23
|
-
result.collect(&:first)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def views(name = nil) #:nodoc:
|
28
|
-
execute("SHOW FULL TABLES WHERE TABLE_TYPE='VIEW'").map { |row| row[0] }
|
29
|
-
end
|
30
|
-
|
31
|
-
def view_build_query(view, name = nil)
|
32
|
-
row = execute("SHOW CREATE VIEW #{view}", name).first
|
33
|
-
return row[1].gsub(/CREATE .*? (AS)+/i, "")
|
34
|
-
rescue ActiveRecord::StatementInvalid => e
|
35
|
-
raise "No view called #{view} found"
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe "Spectacles::SchemaStatements::MysqlAdapter" do
|
4
|
-
config = {
|
5
|
-
:adapter => "mysql",
|
6
|
-
:host => "localhost",
|
7
|
-
:username => "root"
|
8
|
-
}
|
9
|
-
configure_database(config)
|
10
|
-
recreate_database("spectacles_test")
|
11
|
-
load_schema
|
12
|
-
|
13
|
-
it_behaves_like "an adapter", "MysqlAdapter"
|
14
|
-
it_behaves_like "a view model"
|
15
|
-
end
|