spectacles 1.2.0 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2c61cd91dee27e0e255cc4e1d98f493e6d94a12f
4
- data.tar.gz: 1d95c23e8f0235f4468ec337632a79a7caed2fa2
2
+ SHA256:
3
+ metadata.gz: 1c759a317da5654cfbfb8aadbabe40a10122b75fa2cd0a569b9d7cb6792b490a
4
+ data.tar.gz: a76b8d1e07255df03cfed644e868cc7bd4f153ce189e8e457fd2e155f09b5398
5
5
  SHA512:
6
- metadata.gz: 289104b7bf5334b9af7861017d91c3ec7235af1ec01f038708dc9617ba0d3fe581ccb2972882332faf3035c3f56408c3148cdf81b64841b76d7fdfcbe696fbdb
7
- data.tar.gz: ee5dbd357210a7f218b21b9811325904ece703065ae82968fe187a57532e0e2c2c367d6d8c18ae506ca9838df2a41a12bcf25f62f196b5aa02b64ba7e1320d27
6
+ metadata.gz: 25f113ee6cfc34e1989378d3710c899a340271a37879be95aea22817fbb86ad09c14fb978c90b20a0dd5476f895739fdc902d97b6b5a288a9ae012291736b2dd
7
+ data.tar.gz: d372e0d4dae6f83b2396f582012de107a39cdfd0d57acfb77974aa9d5b9297972eb111b6eb07862c0d34241075392daddc6ef3fdd47224b8b2b9f85035a5723a
@@ -1,10 +1,14 @@
1
1
  language: ruby
2
+ jdk:
3
+ - openjdk8
2
4
  rvm:
3
- - 2.2
4
- - 2.3
5
- - 2.4
6
- - jruby
5
+ - 2.2
6
+ - 2.3
7
+ - 2.4
8
+ - 2.5
9
+ - 2.6
10
+ - jruby
7
11
  cache: bundler
8
- branches:
9
- only:
10
- - master
12
+ services:
13
+ - mysql
14
+ - postgresql
data/Gemfile CHANGED
@@ -10,7 +10,6 @@ platforms :jruby do
10
10
  end
11
11
 
12
12
  platforms :ruby do
13
- gem "mysql"
14
13
  gem "mysql2"
15
14
  gem "pg"
16
15
  gem "sqlite3"
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
@@ -2,7 +2,7 @@ require "bundler/gem_tasks"
2
2
  require 'rake/testtask'
3
3
 
4
4
  namespace :test do
5
- adapters = [ :mysql, :mysql2, :postgresql, :sqlite3 ]
5
+ adapters = [ :mysql2, :postgresql, :sqlite3 ]
6
6
  task :all => [ :spectacles ] + adapters
7
7
 
8
8
  adapters.each do |adapter|
@@ -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 (http://github.com/aeden/rails_sql_views/) and built from the ground-up to work with Rails 3.2+.
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 these materialized views cache a snapshot of the data as it
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 lib/spectactles.rb for full license)
120
+ Spectacles is licensed under MIT license (Read the LICENSE file for full license)
@@ -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
- self.connection.refresh_materialized_view(self.view_name)
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
@@ -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::MysqlAdapter
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
@@ -68,7 +68,7 @@ module Spectacles
68
68
  definition = row["definition"].strip.sub(/;$/, "")
69
69
 
70
70
  options = {}
71
- options[:data] = false if ispopulated == 'f'
71
+ options[:data] = false if ispopulated == 'f' || ispopulated == false
72
72
  options[:storage] = parse_storage_definition(storage) if storage.present?
73
73
  options[:tablespace] = tablespace if tablespace.present?
74
74
 
@@ -135,6 +135,10 @@ module Spectacles
135
135
  execute "REFRESH MATERIALIZED VIEW #{quote_table_name(view_name)}"
136
136
  end
137
137
 
138
+ def refresh_materialized_view_concurrently(view_name)
139
+ execute "REFRESH MATERIALIZED VIEW CONCURRENTLY #{quote_table_name(view_name)}"
140
+ end
141
+
138
142
  def parse_storage_definition(storage)
139
143
  # JRuby 9000 returns storage as an Array, whereas
140
144
  # MRI returns a string.
@@ -21,7 +21,7 @@ module Spectacles
21
21
  end
22
22
 
23
23
  def generate_view_query(*columns)
24
- sql = <<-SQL
24
+ <<-SQL
25
25
  SELECT #{columns.join(',')}
26
26
  FROM sqlite_master
27
27
  WHERE type = 'view'
@@ -1,3 +1,3 @@
1
1
  module Spectacles
2
- VERSION = "1.2.0"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -3,8 +3,7 @@ module Spectacles
3
3
  self.abstract_class = true
4
4
 
5
5
  def self.new(*)
6
- warn "DEPRECATION WARNING: #{self} is an abstract class and should not be instantiated. In v1.0, calling `#{self}.new` will raise a NotImplementedError."
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?
@@ -6,7 +6,7 @@ describe "Spectacles::SchemaStatements::Mysql2Adapter" do
6
6
  :host => "localhost",
7
7
  :username => "root"
8
8
  }
9
-
9
+
10
10
  configure_database(config)
11
11
  recreate_database("spectacles_test")
12
12
  load_schema
@@ -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?.must_equal true
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
@@ -50,4 +50,4 @@ def recreate_database(database)
50
50
  ActiveRecord::Base.connection.drop_database(database) rescue nil
51
51
  ActiveRecord::Base.connection.create_database(database)
52
52
  ActiveRecord::Base.establish_connection(@database_config.merge(:database => database))
53
- end
53
+ end
@@ -9,6 +9,6 @@ describe "loading an adapter" do
9
9
  end
10
10
  load File.join(__dir__, '../../lib/spectacles/abstract_adapter_override.rb')
11
11
  Class.new(ActiveRecord::ConnectionAdapters::AbstractAdapter)
12
- ActiveRecord::ConnectionAdapters::AbstractAdapter.instance_variable_get("@_spectacles_inherited_called").must_equal true
12
+ _(ActiveRecord::ConnectionAdapters::AbstractAdapter.instance_variable_get("@_spectacles_inherited_called")).must_equal true
13
13
  end
14
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?.must_equal false
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
 
@@ -2,6 +2,6 @@ require 'spec_helper'
2
2
 
3
3
  describe Spectacles::View do
4
4
  it "is an abstract class" do
5
- Spectacles::View.abstract_class?.must_be true
5
+ _(Spectacles::View.abstract_class?).must_be true
6
6
  end
7
- end
7
+ end
@@ -1,65 +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.create_materialized_view(:materialized_product_users, force: true) do
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
27
  ActiveRecord::Base.connection.add_index :materialized_product_users, :product_name
27
28
 
28
- ActiveRecord::Base.connection.create_materialized_view(:empty_materialized_product_users, storage: { fillfactor: 50 }, data: false, force: true) do
29
+ ActiveRecord::Base.connection.create_materialized_view(:empty_materialized_product_users, storage: { fillfactor: 50 }, data: false, force: true) do
29
30
  "SELECT name AS product_name, first_name AS username FROM
30
31
  products JOIN users ON users.id = products.user_id"
31
32
  end
32
33
  end
33
34
  end
34
35
 
35
- it "should return create_view in dump stream" do
36
+ it "should return create_view in dump stream" do
36
37
  stream = StringIO.new
37
38
  ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
38
- stream.string.must_match(/create_view/)
39
+ _(stream.string).must_match(/create_view/)
39
40
  end
40
41
 
41
42
  if ActiveRecord::Base.connection.supports_materialized_views?
42
- it "should return create_materialized_view in dump stream" do
43
+ it "should return create_materialized_view in dump stream" do
43
44
  stream = StringIO.new
44
45
  ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
45
- stream.string.must_match(/create_materialized_view/)
46
+ _(stream.string).must_match(/create_materialized_view/)
46
47
  end
47
48
 
48
- it 'should return add_index in dump stream' do
49
+ it "should return add_index in dump stream" do
49
50
  stream = StringIO.new
50
51
  ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
51
- stream.string.must_match(/add_index/)
52
+ _(stream.string).must_match(/add_index/)
52
53
  end
53
54
 
54
55
  it "should include options for create_materialized_view" do
55
56
  stream = StringIO.new
56
57
  ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
57
- stream.string.must_match(/create_materialized_view.*fillfactor: 50/)
58
- 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/)
59
60
  end
60
61
  end
61
62
 
62
- it "should rebuild views in dump stream" do
63
+ it "should rebuild views in dump stream" do
63
64
  stream = StringIO.new
64
65
  ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
65
66
 
@@ -79,10 +80,10 @@ shared_examples_for "an adapter" do |adapter|
79
80
 
80
81
  eval(stream.string)
81
82
 
82
- ActiveRecord::Base.connection.views.must_include('new_product_users')
83
+ _(ActiveRecord::Base.connection.views).must_include('new_product_users')
83
84
 
84
85
  if ActiveRecord::Base.connection.supports_materialized_views?
85
- ActiveRecord::Base.connection.materialized_views.must_include('materialized_product_users')
86
+ _(ActiveRecord::Base.connection.materialized_views).must_include('materialized_product_users')
86
87
  end
87
88
  end
88
89
  end
@@ -90,41 +91,41 @@ shared_examples_for "an adapter" do |adapter|
90
91
  describe "#create_view" do
91
92
  let(:view_name) { :view_name }
92
93
 
93
- it "throws error when block not given and no build_query" do
94
- 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)
95
96
  end
96
97
 
97
98
  describe "view_name" do
98
- it "takes a symbol as the view_name" do
99
- 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}/)
100
101
  end
101
102
 
102
- it "takes a string as the view_name" do
103
- 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}/)
104
105
  end
105
106
  end
106
107
 
107
- describe "build_query" do
108
- it "uses a string if passed" do
108
+ describe "build_query" do
109
+ it "uses a string if passed" do
109
110
  select_statement = "SELECT * FROM products"
110
- 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)}/)
111
112
  end
112
113
 
113
- it "uses an Arel::Relation if passed" do
114
+ it "uses an Arel::Relation if passed" do
114
115
  select_statement = Product.all.to_sql
115
- 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)}/)
116
117
  end
117
118
  end
118
119
 
119
- describe "block" do
120
- 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
121
122
  select_statement = Product.all.to_sql
122
- 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)}/)
123
124
  end
124
125
 
125
- it "can use a String from the yield" do
126
+ it "can use a String from the yield" do
126
127
  select_statement = "SELECT * FROM products"
127
- 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)}/)
128
129
  end
129
130
  end
130
131
  end
@@ -133,55 +134,72 @@ shared_examples_for "an adapter" do |adapter|
133
134
  let(:view_name) { :view_name }
134
135
 
135
136
  describe "view_name" do
136
- it "takes a symbol as the view_name" do
137
- 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}/)
138
139
  end
139
140
 
140
- it "takes a string as the view_name" do
141
- 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}/)
142
143
  end
143
144
  end
144
145
  end
145
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
+
146
164
  if shared_base.supports_materialized_views?
147
165
  describe "#create_materialized_view" do
148
166
  let(:view_name) { :view_name }
149
167
 
150
168
  it "throws error when block not given and no build_query" do
151
- lambda { shared_base.create_materialized_view(view_name) }.must_raise(RuntimeError)
169
+ _(lambda { shared_base.create_materialized_view(view_name) }).must_raise(RuntimeError)
152
170
  end
153
171
 
154
172
  describe "view_name" do
155
173
  it "takes a symbol as the view_name" do
156
- 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}/)
157
175
  end
158
176
 
159
177
  it "takes a string as the view_name" do
160
- 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}/)
161
179
  end
162
180
  end
163
181
 
164
182
  describe "build_query" do
165
183
  it "uses a string if passed" do
166
184
  select_statement = "SELECT * FROM products"
167
- 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)}/)
168
186
  end
169
187
 
170
188
  it "uses an Arel::Relation if passed" do
171
189
  select_statement = Product.all.to_sql
172
- 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)}/)
173
191
  end
174
192
  end
175
193
 
176
194
  describe "block" do
177
195
  it "can use an Arel::Relation from the yield" do
178
196
  select_statement = Product.all.to_sql
179
- 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)}/)
180
198
  end
181
199
 
182
200
  it "can use a String from the yield" do
183
201
  select_statement = "SELECT * FROM products"
184
- 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)}/)
185
203
  end
186
204
  end
187
205
  end
@@ -191,11 +209,11 @@ shared_examples_for "an adapter" do |adapter|
191
209
 
192
210
  describe "view_name" do
193
211
  it "takes a symbol as the view_name" do
194
- 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}/)
195
213
  end
196
214
 
197
215
  it "takes a string as the view_name" do
198
- 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}/)
199
217
  end
200
218
  end
201
219
  end
@@ -205,18 +223,18 @@ shared_examples_for "an adapter" do |adapter|
205
223
 
206
224
  describe "view_name" do
207
225
  it "takes a symbol as the view_name" do
208
- 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}/)
209
227
  end
210
228
 
211
229
  it "takes a string as the view_name" do
212
- 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}/)
213
231
  end
214
232
  end
215
233
  end
216
234
  else
217
235
  describe "#materialized_views" do
218
236
  it "should not be supported by #{adapter}" do
219
- lambda { shared_base.materialized_views }.must_raise(NotImplementedError)
237
+ _(lambda { shared_base.materialized_views }).must_raise(NotImplementedError)
220
238
  end
221
239
  end
222
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
- it "can has scopes" do
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
- NewProductUser.duck_lovers.load.first.username.must_be @john.first_name
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
- NewProductUser.new.readonly?.must_be true
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 has scopes" do
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?.must_be true
58
+ _(MaterializedProductUser.first.readonly?).must_be true
57
59
  end
58
60
  end
59
61
  end
@@ -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 Vertica (MySQL is close).}
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.0.0"
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.0"
26
+ gem.add_dependency "activesupport", ">= 3.2.0", "< 6.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: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Hutchison, Brandon Dewitt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-21 00:00:00.000000000 Z
11
+ date: 2019-12-17 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.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.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.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.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 Vertica (MySQL is close).
82
+ supported adapters include Postgres, SQLite, Vertica, and MySQL.
71
83
  email:
72
84
  - liveh2o@gmail.com, brandonsdewitt@gmail.com
73
85
  executables: []
@@ -89,7 +101,6 @@ files:
89
101
  - lib/spectacles/schema_statements.rb
90
102
  - lib/spectacles/schema_statements/abstract_adapter.rb
91
103
  - lib/spectacles/schema_statements/mysql2_adapter.rb
92
- - lib/spectacles/schema_statements/mysql_adapter.rb
93
104
  - lib/spectacles/schema_statements/postgresql_adapter.rb
94
105
  - lib/spectacles/schema_statements/sqlite3_adapter.rb
95
106
  - lib/spectacles/schema_statements/sqlserver_adapter.rb
@@ -97,7 +108,6 @@ files:
97
108
  - lib/spectacles/version.rb
98
109
  - lib/spectacles/view.rb
99
110
  - specs/adapters/mysql2_adapter_spec.rb
100
- - specs/adapters/mysql_adapter_spec.rb
101
111
  - specs/adapters/postgresql_adapter_spec.rb
102
112
  - specs/adapters/sqlite3_adapter_spec.rb
103
113
  - specs/spec_helper.rb
@@ -121,15 +131,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
121
131
  requirements:
122
132
  - - ">="
123
133
  - !ruby/object:Gem::Version
124
- version: 2.0.0
134
+ version: 2.2.0
125
135
  required_rubygems_version: !ruby/object:Gem::Requirement
126
136
  requirements:
127
137
  - - ">="
128
138
  - !ruby/object:Gem::Version
129
139
  version: '0'
130
140
  requirements: []
131
- rubyforge_project:
132
- rubygems_version: 2.6.12
141
+ rubygems_version: 3.0.6
133
142
  signing_key:
134
143
  specification_version: 4
135
144
  summary: Spectacles (derived from RailsSQLViews) adds database view functionality
@@ -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