spectacles 1.2.0 → 2.0.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
- 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