schema_plus_views 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 94cd524efd83a80577764570786aa30b828218b4
4
+ data.tar.gz: e86c50e980bd4e483caea91b6ab00f787a0f7425
5
+ SHA512:
6
+ metadata.gz: 56febaf08947bed4af3cb7efd7c45a5ccd465f2c8204d71facd52ccea83147342a3ef62cd5cb7a491d9e33a8a7e6c363d6abd418c5513bb7e42f76289dc460ad
7
+ data.tar.gz: 562f3fbf5c349ee0373948e8e1186d713ef91c90754847ba162ed44d78d3bb0c753d47d747ca63851d51ce3582420c44e567d97da73a91e8fc39c8010a36927c
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /coverage
2
+ /tmp
3
+ /pkg
4
+ /Gemfile.local
5
+
6
+ *.lock
7
+ *.log
8
+ *.sqlite3
9
+ !gemfiles/**/*.sqlite3
data/.travis.yml ADDED
@@ -0,0 +1,18 @@
1
+ # This file was auto-generated by the schema_dev tool, based on the data in
2
+ # ./schema_dev.yml
3
+ # Please do not edit this file; any changes will be overwritten next time
4
+ # schema_dev gets run.
5
+ ---
6
+ sudo: false
7
+ rvm:
8
+ - 2.1.5
9
+ gemfile:
10
+ - gemfiles/activerecord-4.2/Gemfile.mysql2
11
+ - gemfiles/activerecord-4.2/Gemfile.postgresql
12
+ - gemfiles/activerecord-4.2/Gemfile.sqlite3
13
+ env: POSTGRESQL_DB_USER=postgres MYSQL_DB_USER=travis
14
+ addons:
15
+ postgresql: '9.3'
16
+ before_script: bundle exec rake create_databases
17
+ after_script: bundle exec rake drop_databases
18
+ script: bundle exec rake travis
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ File.exist?(gemfile_local = File.expand_path('../Gemfile.local', __FILE__)) and eval File.read(gemfile_local), binding, gemfile_local
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 ronen barzel
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,160 @@
1
+ [![Gem Version](https://badge.fury.io/rb/schema_plus_views.svg)](http://badge.fury.io/rb/schema_plus_views)
2
+ [![Build Status](https://secure.travis-ci.org/SchemaPlus/schema_plus_views.svg)](http://travis-ci.org/SchemaPlus/schema_plus_views)
3
+ [![Coverage Status](https://img.shields.io/coveralls/SchemaPlus/schema_plus_views.svg)](https://coveralls.io/r/SchemaPlus/schema_plus_views)
4
+ [![Dependency Status](https://gemnasium.com/lomba/schema_plus_views.svg)](https://gemnasium.com/SchemaPlus/schema_plus_views)
5
+
6
+ # SchemaPlus::Views
7
+
8
+ SchemaPlus::Views adds support for creating and dropping views in ActiveRecord migrations, as well as querying views.
9
+
10
+ SchemaPlus::Views is part of the [SchemaPlus](https://github.com/SchemaPlus/) family of Ruby on Rails extension gems.
11
+
12
+ ## Installation
13
+
14
+ <!-- SCHEMA_DEV: TEMPLATE INSTALLATION - begin -->
15
+ <!-- These lines are auto-inserted from a schema_dev template -->
16
+ As usual:
17
+
18
+ ```ruby
19
+ gem "schema_plus_views" # in a Gemfile
20
+ gem.add_dependency "schema_plus_views" # in a .gemspec
21
+ ```
22
+
23
+ To use with a rails app, also include
24
+
25
+ ```ruby
26
+ gem "schema_monkey_rails"
27
+ ```
28
+
29
+ which creates a Railtie to that will insert SchemaPlus::Views appropriately into the rails stack. To use with Padrino, see [schema_monkey_padrino](https://github.com/SchemaPlus/schema_monkey_padrino).
30
+
31
+ <!-- SCHEMA_DEV: TEMPLATE INSTALLATION - end -->
32
+
33
+ ## Compatibility
34
+
35
+ SchemaPlus::Views is tested on:
36
+
37
+ <!-- SCHEMA_DEV: MATRIX - begin -->
38
+ <!-- These lines are auto-generated by schema_dev based on schema_dev.yml -->
39
+ * ruby **2.1.5** with activerecord **4.2**, using **mysql2**, **sqlite3** or **postgresql**
40
+
41
+ <!-- SCHEMA_DEV: MATRIX - end -->
42
+
43
+ ## Usage
44
+
45
+ ### Creating views
46
+
47
+ In a migration, a view can be created using literal SQL:
48
+
49
+ ```ruby
50
+ create_view :uncommented_posts, "SELECT * FROM posts LEFT OUTER JOIN comments ON comments.post_id = posts.id WHERE comments.id IS NULL"
51
+ ```
52
+
53
+ or using an object that responds to `:to_sql`, such as a relation:
54
+
55
+ ```ruby
56
+ create_view :posts_commented_by_staff, Post.joins(comment: user).where(users: {role: 'staff'}).uniq
57
+ ```
58
+
59
+ (It's of course a questionable idea for your migrations to depend on your model definitions. But you *can* if you want.)
60
+
61
+ ### Dropping views
62
+
63
+ In a migration:
64
+
65
+ ```ruby
66
+ drop_view :posts_commented_by_staff
67
+ drop_view :uncommented_posts, :if_exists => true
68
+ ```
69
+
70
+ ### Using views
71
+
72
+ ActiveRecord models can be use views the same as ordinary tables. That is, for the above views you can define
73
+
74
+ ```ruby
75
+ class UncommentedPost < ActiveRecord::Base
76
+ end
77
+
78
+ class PostCommentedByStaff < ActiveRecord::Base
79
+ table_name = "posts_commented_by_staff"
80
+ end
81
+ ```
82
+
83
+ ### Querying views
84
+
85
+ You can look up the defined views analogously to looking up tables:
86
+
87
+ ```ruby
88
+ connection.tables # => array of table names (defined by ActiveRecord)
89
+ connection.views # => array of names of views (defined by SchemaPlus::Views)
90
+ ```
91
+
92
+ Notes:
93
+
94
+ 1. For Mysql and SQLite3, ActiveRecord's `connection.tables` method would return views as well as tables; SchemaPlus::Views normalizes them to return only tables.
95
+
96
+ 2. For PostgreSQL, `connection.views` does *not* return views prefixed with `pg_` as those are presumed to be internal.
97
+
98
+ ### Querying view definitions
99
+
100
+ You can look up the definition of a view using
101
+
102
+ ```ruby
103
+ connection.view_definition(view_name) # => returns SQL string
104
+ ```
105
+
106
+ This returns just the body of the definition, i.e. the part after the `CREATE VIEW 'name' AS` command.
107
+
108
+
109
+ ## History
110
+
111
+ * 0.1.0 - Initial release, extracted from schema_plus 1.x
112
+
113
+ ## Development & Testing
114
+
115
+ Are you interested in contributing to SchemaPlus::Views? Thanks! Please follow
116
+ the standard protocol: fork, feature branch, develop, push, and issue pull
117
+ request.
118
+
119
+ Some things to know about to help you develop and test:
120
+
121
+ <!-- SCHEMA_DEV: TEMPLATE USES SCHEMA_DEV - begin -->
122
+ <!-- These lines are auto-inserted from a schema_dev template -->
123
+ * **schema_dev**: SchemaPlus::Views uses [schema_dev](https://github.com/SchemaPlus/schema_dev) to
124
+ facilitate running rspec tests on the matrix of ruby, activerecord, and database
125
+ versions that the gem supports, both locally and on
126
+ [travis-ci](http://travis-ci.org/SchemaPlus/schema_plus_views)
127
+
128
+ To to run rspec locally on the full matrix, do:
129
+
130
+ $ schema_dev bundle install
131
+ $ schema_dev rspec
132
+
133
+ You can also run on just one configuration at a time; For info, see `schema_dev --help` or the [schema_dev](https://github.com/SchemaPlus/schema_dev) README.
134
+
135
+ The matrix of configurations is specified in `schema_dev.yml` in
136
+ the project root.
137
+
138
+
139
+ <!-- SCHEMA_DEV: TEMPLATE USES SCHEMA_DEV - end -->
140
+
141
+ <!-- SCHEMA_DEV: TEMPLATE USES SCHEMA_PLUS_CORE - begin -->
142
+ <!-- These lines are auto-inserted from a schema_dev template -->
143
+ * **schema_plus_core**: SchemaPlus::Views uses the SchemaPlus::Core API that
144
+ provides middleware callback stacks to make it easy to extend
145
+ ActiveRecord's behavior. If that API is missing something you need for
146
+ your contribution, please head over to
147
+ [schema_plus_core](https://github/SchemaPlus/schema_plus_core) and open
148
+ an issue or pull request.
149
+
150
+ <!-- SCHEMA_DEV: TEMPLATE USES SCHEMA_PLUS_CORE - end -->
151
+
152
+ <!-- SCHEMA_DEV: TEMPLATE USES SCHEMA_MONKEY - begin -->
153
+ <!-- These lines are auto-inserted from a schema_dev template -->
154
+ * **schema_monkey**: SchemaPlus::Views is implemented as a
155
+ [schema_monkey](https://github.com/SchemaPlus/schema_monkey) client,
156
+ using [schema_monkey](https://github.com/SchemaPlus/schema_monkey)'s
157
+ convention-based protocols for extending ActiveRecord and using middleware stacks.
158
+ For more information see [schema_monkey](https://github.com/SchemaPlus/schema_monkey)'s README.
159
+
160
+ <!-- SCHEMA_DEV: TEMPLATE USES SCHEMA_MONKEY - end -->
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'schema_dev/tasks'
5
+
6
+ task :default => :spec
7
+
8
+ require 'rspec/core/rake_task'
9
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+ gemspec :path => File.expand_path('..', __FILE__)
3
+
4
+ File.exist?(gemfile_local = File.expand_path('../Gemfile.local', __FILE__)) and eval File.read(gemfile_local), binding, gemfile_local
@@ -0,0 +1,3 @@
1
+ eval File.read File.expand_path('../../Gemfile.base', __FILE__)
2
+
3
+ gem "activerecord", "~> 4.2.0"
@@ -0,0 +1,10 @@
1
+ require "pathname"
2
+ eval(Pathname.new(__FILE__).dirname.join("Gemfile.base").read, binding)
3
+
4
+ platform :ruby do
5
+ gem "mysql2"
6
+ end
7
+
8
+ platform :jruby do
9
+ gem 'activerecord-jdbcmysql-adapter'
10
+ end
@@ -0,0 +1,10 @@
1
+ require "pathname"
2
+ eval(Pathname.new(__FILE__).dirname.join("Gemfile.base").read, binding)
3
+
4
+ platform :ruby do
5
+ gem "pg"
6
+ end
7
+
8
+ platform :jruby do
9
+ gem 'activerecord-jdbcpostgresql-adapter'
10
+ end
@@ -0,0 +1,10 @@
1
+ require "pathname"
2
+ eval(Pathname.new(__FILE__).dirname.join("Gemfile.base").read, binding)
3
+
4
+ platform :ruby do
5
+ gem "sqlite3"
6
+ end
7
+
8
+ platform :jruby do
9
+ gem 'activerecord-jdbcsqlite3-adapter', '>=1.3.0.beta2'
10
+ end
@@ -0,0 +1,40 @@
1
+ module SchemaPlus::Views
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module AbstractAdapter
5
+ # Create a view given the SQL definition. Specify :force => true
6
+ # to first drop the view if it already exists.
7
+ def create_view(view_name, definition, options={})
8
+ definition = definition.to_sql if definition.respond_to? :to_sql
9
+ if options[:force]
10
+ drop_view(view_name, if_exists: true)
11
+ end
12
+ execute "CREATE VIEW #{quote_table_name(view_name)} AS #{definition}"
13
+ end
14
+
15
+ # Drop the named view. Specify :if_exists => true
16
+ # to fail silently if the view doesn't exist.
17
+ def drop_view(view_name, options = {})
18
+ sql = "DROP VIEW"
19
+ sql += " IF EXISTS" if options[:if_exists]
20
+ sql += " #{quote_table_name(view_name)}"
21
+ execute sql
22
+ end
23
+
24
+ #####################################################################
25
+ #
26
+ # The functions below here are abstract; each subclass should
27
+ # define them all. Defining them here only for reference.
28
+ #
29
+
30
+ # (abstract) Returns the names of all views, as an array of strings
31
+ def views(name = nil) raise "Internal Error: Connection adapter didn't override abstract function"; [] end
32
+
33
+ # (abstract) Returns the SQL definition of a given view. This is
34
+ # the literal SQL would come after 'CREATVE VIEW viewname AS ' in
35
+ # the SQL statement to create a view.
36
+ def view_definition(view_name, name = nil) raise "Internal Error: Connection adapter didn't override abstract function"; end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,30 @@
1
+ module SchemaPlus::Views
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module Mysql2Adapter
5
+
6
+ def views(name = nil)
7
+ views = []
8
+ select_all("SELECT table_name FROM information_schema.views WHERE table_schema = SCHEMA()", name).each do |row|
9
+ views << row["table_name"]
10
+ end
11
+ views
12
+ end
13
+
14
+ def view_definition(view_name, name = nil)
15
+ results = select_all("SELECT view_definition, check_option FROM information_schema.views WHERE table_schema = SCHEMA() AND table_name = #{quote(view_name)}", name)
16
+ return nil unless results.any?
17
+ row = results.first
18
+ sql = row["view_definition"]
19
+ sql.gsub!(%r{#{quote_table_name(current_database)}[.]}, '')
20
+ case row["check_option"]
21
+ when "CASCADED" then sql += " WITH CASCADED CHECK OPTION"
22
+ when "LOCAL" then sql += " WITH LOCAL CHECK OPTION"
23
+ end
24
+ sql
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ module SchemaPlus::Views
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module PostgresqlAdapter
5
+
6
+ def views(name = nil) #:nodoc:
7
+ sql = <<-SQL
8
+ SELECT viewname
9
+ FROM pg_views
10
+ WHERE schemaname = ANY (current_schemas(false))
11
+ AND viewname NOT LIKE 'pg\_%'
12
+ SQL
13
+ sql += " AND schemaname != 'postgis'" if adapter_name == 'PostGIS'
14
+ query(sql, name).map { |row| row[0] }
15
+ end
16
+
17
+ def view_definition(view_name, name = nil) #:nodoc:
18
+ result = query(<<-SQL, name)
19
+ SELECT pg_get_viewdef(oid)
20
+ FROM pg_class
21
+ WHERE relkind = 'v'
22
+ AND relname = '#{view_name}'
23
+ SQL
24
+ row = result.first
25
+ row.first.chomp(';') unless row.nil?
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ module SchemaPlus::Views
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module Sqlite3Adapter
5
+
6
+ def views(name = nil)
7
+ execute("SELECT name FROM sqlite_master WHERE type='view'", name).collect{|row| row["name"]}
8
+ end
9
+
10
+ def view_definition(view_name, name = nil)
11
+ sql = execute("SELECT sql FROM sqlite_master WHERE type='view' AND name=#{quote(view_name)}", name).collect{|row| row["sql"]}.first
12
+ sql.sub(/^CREATE VIEW \S* AS\s+/im, '') unless sql.nil?
13
+ end
14
+
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,49 @@
1
+ module SchemaPlus::Views
2
+ module Middleware
3
+
4
+ module Dumper
5
+ module Tables
6
+
7
+ # Dump views
8
+ def after(env)
9
+ re_view_referent = %r{(?:(?i)FROM|JOIN) \S*\b(\S+)\b}
10
+ env.connection.views.each do |view_name|
11
+ next if env.dumper.ignored?(view_name)
12
+ view = View.new(name: view_name, definition: env.connection.view_definition(view_name))
13
+ env.dump.tables[view.name] = view
14
+ env.dump.depends(view.name, view.definition.scan(re_view_referent).flatten)
15
+ end
16
+ end
17
+
18
+ # quacks like a SchemaMonkey Dump::Table
19
+ class View < KeyStruct[:name, :definition]
20
+ def assemble(stream)
21
+ stream.puts(" create_view #{name.inspect}, #{definition.inspect}, :force => true\n")
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ module Schema
28
+ module Tables
29
+
30
+ module Mysql
31
+ def after(env)
32
+ Tables.filter_out_views(env)
33
+ end
34
+ end
35
+
36
+ module Sqlite3
37
+ def after(env)
38
+ Tables.filter_out_views(env)
39
+ end
40
+ end
41
+
42
+ def self.filter_out_views(env)
43
+ env.tables -= env.connection.views(env.query_name)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,5 @@
1
+ module SchemaPlus
2
+ module Views
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,22 @@
1
+ require 'schema_plus/core'
2
+
3
+ module SchemaPlus
4
+ module Views
5
+ end
6
+ end
7
+
8
+ require_relative 'views/version'
9
+ require_relative 'views/active_record/connection_adapters/abstract_adapter'
10
+ require_relative 'views/middleware'
11
+
12
+ module SchemaPlus::Views
13
+ module ActiveRecord
14
+ module ConnectionAdapters
15
+ autoload :Mysql2Adapter, 'schema_plus/views/active_record/connection_adapters/mysql2_adapter'
16
+ autoload :PostgresqlAdapter, 'schema_plus/views/active_record/connection_adapters/postgresql_adapter'
17
+ autoload :Sqlite3Adapter, 'schema_plus/views/active_record/connection_adapters/sqlite3_adapter'
18
+ end
19
+ end
20
+ end
21
+
22
+ SchemaMonkey.register SchemaPlus::Views
@@ -0,0 +1 @@
1
+ require_relative 'schema_plus/views.rb'
data/schema_dev.yml ADDED
@@ -0,0 +1,8 @@
1
+ ruby:
2
+ - 2.1.5
3
+ activerecord:
4
+ - 4.2
5
+ db:
6
+ - mysql2
7
+ - sqlite3
8
+ - postgresql
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'schema_plus/views/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "schema_plus_views"
8
+ gem.version = SchemaPlus::Views::VERSION
9
+ gem.authors = ["ronen barzel"]
10
+ gem.email = ["ronen@barzel.org"]
11
+ gem.summary = %q{Adds support for views to ActiveRecord}
12
+ gem.homepage = "https://github.com/SchemaPlus/schema_plus_views"
13
+ gem.license = "MIT"
14
+
15
+ gem.files = `git ls-files -z`.split("\x0")
16
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency "activerecord", "~> 4.2"
21
+ gem.add_dependency "schema_plus_core", "~> 0.1"
22
+
23
+ gem.add_development_dependency "bundler", "~> 1.7"
24
+ gem.add_development_dependency "rake", "~> 10.0"
25
+ gem.add_development_dependency "rspec", "~> 3.0"
26
+ gem.add_development_dependency "schema_dev", "~> 3.2"
27
+ gem.add_development_dependency "simplecov"
28
+ gem.add_development_dependency "simplecov-gem-profile"
29
+ end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+
3
+ describe "with multiple schemas" do
4
+ def connection
5
+ ActiveRecord::Base.connection
6
+ end
7
+
8
+ before(:each) do
9
+ newdb = case connection.adapter_name
10
+ when /^mysql/i then "CREATE SCHEMA IF NOT EXISTS schema_plus_views_test2"
11
+ when /^postgresql/i then "CREATE SCHEMA schema_plus_views_test2"
12
+ when /^sqlite/i then "ATTACH ':memory:' AS schema_plus_views_test2"
13
+ end
14
+ begin
15
+ ActiveRecord::Base.connection.execute newdb
16
+ rescue ActiveRecord::StatementInvalid => e
17
+ raise unless e.message =~ /already/
18
+ end
19
+
20
+ class User < ::ActiveRecord::Base ; end
21
+ end
22
+
23
+ before(:each) do
24
+ ActiveRecord::Schema.define do
25
+ create_table :users, :force => true do |t|
26
+ t.string :login
27
+ end
28
+ end
29
+
30
+ connection.execute 'DROP TABLE IF EXISTS schema_plus_views_test2.users'
31
+ connection.execute 'CREATE TABLE schema_plus_views_test2.users (id ' + case connection.adapter_name
32
+ when /^mysql/i then "integer primary key auto_increment"
33
+ when /^postgresql/i then "serial primary key"
34
+ when /^sqlite/i then "integer primary key autoincrement"
35
+ end + ", login varchar(255))"
36
+ end
37
+
38
+ context "with views in each schema" do
39
+ around(:each) do |example|
40
+ begin
41
+ example.run
42
+ ensure
43
+ connection.execute 'DROP VIEW schema_plus_views_test2.myview' rescue nil
44
+ connection.execute 'DROP VIEW myview' rescue nil
45
+ end
46
+ end
47
+
48
+ before(:each) do
49
+ connection.views.each { |view| connection.drop_view view }
50
+ connection.execute 'CREATE VIEW schema_plus_views_test2.myview AS SELECT * FROM users'
51
+ end
52
+
53
+ it "should not find views in other schema" do
54
+ expect(connection.views).to be_empty
55
+ end
56
+
57
+ it "should find views in this schema" do
58
+ connection.execute 'CREATE VIEW myview AS SELECT * FROM users'
59
+ expect(connection.views).to eq(['myview'])
60
+ end
61
+ end
62
+
63
+ context "when using PostGIS", :postgresql => :only do
64
+ before(:all) do
65
+ begin
66
+ connection.execute "CREATE SCHEMA postgis"
67
+ rescue ActiveRecord::StatementInvalid => e
68
+ raise unless e.message =~ /already exists/
69
+ end
70
+ end
71
+
72
+ around(:each) do |example|
73
+ begin
74
+ connection.execute "SET search_path to '$user','public','postgis'"
75
+ example.run
76
+ ensure
77
+ connection.execute "SET search_path to '$user','public'"
78
+ end
79
+ end
80
+
81
+ before(:each) do
82
+ allow(connection).to receive(:adapter_name).and_return('PostGIS')
83
+ end
84
+
85
+ it "should hide views in postgis schema" do
86
+ begin
87
+ connection.create_view "postgis.hidden", "select 1", :force => true
88
+ connection.create_view :myview, "select 2", :force => true
89
+ expect(connection.views).to eq(["myview"])
90
+ ensure
91
+ connection.execute 'DROP VIEW postgis.hidden' rescue nil
92
+ connection.execute 'DROP VIEW myview' rescue nil
93
+ end
94
+ end
95
+ end
96
+
97
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ # A basic sanity check to have as a spec when first starting. Feel free to delete this
5
+ # once you've got real content.
6
+
7
+ describe "Sanity Check" do
8
+
9
+ it "database is connected" do
10
+ expect(ActiveRecord::Base).to be_connected
11
+ end
12
+
13
+ end
@@ -0,0 +1,29 @@
1
+ require 'simplecov'
2
+ require 'simplecov-gem-profile'
3
+ SimpleCov.start "gem"
4
+
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+
8
+ require 'rspec'
9
+ require 'active_record'
10
+ require 'schema_plus_views'
11
+ require 'schema_dev/rspec'
12
+
13
+ SchemaDev::Rspec.setup
14
+
15
+ Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
16
+
17
+ RSpec.configure do |config|
18
+ config.warnings = true
19
+ config.around(:each) do |example|
20
+ ActiveRecord::Migration.suppress_messages do
21
+ ActiveRecord::Base.connection.tables.each do |table|
22
+ ActiveRecord::Migration.drop_table table, force: :cascade
23
+ end
24
+ example.run
25
+ end
26
+ end
27
+ end
28
+
29
+ SimpleCov.command_name "[ruby #{RUBY_VERSION} - ActiveRecord #{::ActiveRecord::VERSION::STRING} - #{ActiveRecord::Base.connection.adapter_name}]"
@@ -0,0 +1,194 @@
1
+ require 'spec_helper'
2
+
3
+ class Item < ActiveRecord::Base
4
+ end
5
+
6
+ class AOnes < ActiveRecord::Base
7
+ end
8
+
9
+ class ABOnes < ActiveRecord::Base
10
+ end
11
+
12
+ describe ActiveRecord do
13
+
14
+ let(:schema) { ActiveRecord::Schema }
15
+
16
+ let(:migration) { ActiveRecord::Migration }
17
+
18
+ let(:connection) { ActiveRecord::Base.connection }
19
+
20
+ context "views" do
21
+
22
+ around(:each) do |example|
23
+ define_schema_and_data
24
+ example.run
25
+ drop_definitions
26
+ end
27
+
28
+ it "should query correctly" do
29
+ expect(AOnes.all.collect(&:s)).to eq(%W[one_one one_two])
30
+ expect(ABOnes.all.collect(&:s)).to eq(%W[one_one])
31
+ end
32
+
33
+ it "should instrospect" do
34
+ # for postgresql, ignore views named pg_*
35
+ expect(connection.views.sort).to eq(%W[a_ones ab_ones])
36
+ expect(connection.view_definition('a_ones')).to match(%r{^ ?SELECT .*b.*,.*s.* FROM .*items.* WHERE .*a.* = 1}mi)
37
+ expect(connection.view_definition('ab_ones')).to match(%r{^ ?SELECT .*s.* FROM .*a_ones.* WHERE .*b.* = 1}mi)
38
+ end
39
+
40
+ it "should not be listed as a table" do
41
+ expect(connection.tables).not_to include('a_ones')
42
+ expect(connection.tables).not_to include('ab_ones')
43
+ end
44
+
45
+
46
+ it "should be included in schema dump" do
47
+ expect(dump).to match(%r{create_view "a_ones", " ?SELECT .*b.*,.*s.* FROM .*items.* WHERE .*a.* = 1.*, :force => true}mi)
48
+ expect(dump).to match(%r{create_view "ab_ones", " ?SELECT .*s.* FROM .*a_ones.* WHERE .*b.* = 1.*, :force => true}mi)
49
+ end
50
+
51
+ it "should be included in schema dump in dependency order" do
52
+ expect(dump).to match(%r{create_table "items".*create_view "a_ones".*create_view "ab_ones"}m)
53
+ end
54
+
55
+ it "should not be included in schema if listed in ignore_tables" do
56
+ dump(ignore_tables: /b_/) do |dump|
57
+ expect(dump).to match(%r{create_view "a_ones", " ?SELECT .*b.*,.*s.* FROM .*items.* WHERE .*a.* = 1.*, :force => true}mi)
58
+ expect(dump).not_to match(%r{"ab_ones"})
59
+ end
60
+ end
61
+
62
+
63
+ it "dump should not reference current database" do
64
+ # why check this? mysql default to providing the view definition
65
+ # with tables explicitly scoped to the current database, which
66
+ # resulted in the dump being bound to the current database. this
67
+ # caused trouble for rails, in which creates the schema dump file
68
+ # when in the (say) development database, but then uses it to
69
+ # initialize the test database when testing. this meant that the
70
+ # test database had views into the development database.
71
+ db = connection.respond_to?(:current_database)? connection.current_database : SchemaDev::Rspec.db_configuration[:database]
72
+ expect(dump).not_to match(%r{#{connection.quote_table_name(db)}[.]})
73
+ end
74
+
75
+ context "duplicate view creation" do
76
+ around(:each) do |example|
77
+ migration.suppress_messages do
78
+ begin
79
+ migration.create_view('dupe_me', 'SELECT * FROM items WHERE (a=1)')
80
+ example.run
81
+ ensure
82
+ migration.drop_view('dupe_me')
83
+ end
84
+ end
85
+ end
86
+
87
+
88
+ it "should raise an error by default" do
89
+ expect {migration.create_view('dupe_me', 'SELECT * FROM items WHERE (a=2)')}.to raise_error ActiveRecord::StatementInvalid
90
+ end
91
+
92
+ it "should override existing definition if :force true" do
93
+ migration.create_view('dupe_me', 'SELECT * FROM items WHERE (a=2)', :force => true)
94
+ expect(connection.view_definition('dupe_me')).to match(%r{WHERE .*a.*=.*2}i)
95
+ end
96
+ end
97
+
98
+ context "dropping views" do
99
+ it "should raise an error if the view doesn't exist" do
100
+ expect { migration.drop_view('doesnt_exist') }.to raise_error ActiveRecord::StatementInvalid
101
+ end
102
+
103
+ it "should fail silently when using if_exists option" do
104
+ expect { migration.drop_view('doesnt_exist', :if_exists => true) }.not_to raise_error
105
+ end
106
+
107
+ context "with a view that exists" do
108
+ before { migration.create_view('view_that_exists', 'SELECT * FROM items WHERE (a=1)') }
109
+
110
+ it "should succeed" do
111
+ migration.drop_view('view_that_exists')
112
+ expect(connection.views).not_to include('view_that_exists')
113
+ end
114
+ end
115
+ end
116
+
117
+ context "in mysql", :mysql => :only do
118
+
119
+ around(:each) do |example|
120
+ migration.suppress_messages do
121
+ begin
122
+ migration.drop_view :check if connection.views.include? 'check'
123
+ example.run
124
+ ensure
125
+ migration.drop_view :check if connection.views.include? 'check'
126
+ end
127
+ end
128
+ end
129
+
130
+ it "should introspect WITH CHECK OPTION" do
131
+ migration.create_view :check, 'SELECT * FROM items WHERE (a=2) WITH CHECK OPTION'
132
+ expect(connection.view_definition('check')).to match(%r{WITH CASCADED CHECK OPTION$})
133
+ end
134
+
135
+ it "should introspect WITH CASCADED CHECK OPTION" do
136
+ migration.create_view :check, 'SELECT * FROM items WHERE (a=2) WITH CASCADED CHECK OPTION'
137
+ expect(connection.view_definition('check')).to match(%r{WITH CASCADED CHECK OPTION$})
138
+ end
139
+
140
+ it "should introspect WITH LOCAL CHECK OPTION" do
141
+ migration.create_view :check, 'SELECT * FROM items WHERE (a=2) WITH LOCAL CHECK OPTION'
142
+ expect(connection.view_definition('check')).to match(%r{WITH LOCAL CHECK OPTION$})
143
+ end
144
+ end
145
+ end
146
+
147
+ protected
148
+
149
+ def define_schema_and_data
150
+ migration.suppress_messages do
151
+ connection.views.each do |view| connection.drop_view view end
152
+ connection.tables.each do |table| connection.drop_table table, cascade: true end
153
+
154
+ schema.define do
155
+
156
+ create_table :items, :force => true do |t|
157
+ t.integer :a
158
+ t.integer :b
159
+ t.string :s
160
+ end
161
+
162
+ create_view :a_ones, Item.select('b, s').where(:a => 1)
163
+ create_view :ab_ones, "select s from a_ones where b = 1"
164
+ create_view :pg_dummy_internal, "select 1" if SchemaDev::Rspec::Helpers.postgresql?
165
+ end
166
+ end
167
+ connection.execute "insert into items (a, b, s) values (1, 1, 'one_one')"
168
+ connection.execute "insert into items (a, b, s) values (1, 2, 'one_two')"
169
+ connection.execute "insert into items (a, b, s) values (2, 1, 'two_one')"
170
+ connection.execute "insert into items (a, b, s) values (2, 2, 'two_two')"
171
+
172
+ end
173
+
174
+ def drop_definitions
175
+ migration.suppress_messages do
176
+ schema.define do
177
+ drop_view "ab_ones"
178
+ drop_view "a_ones"
179
+ drop_table "items"
180
+ drop_view :pg_dummy_internal if SchemaDev::Rspec::Helpers.postgresql?
181
+ end
182
+ end
183
+ end
184
+
185
+ def dump(opts={})
186
+ StringIO.open { |stream|
187
+ ActiveRecord::SchemaDumper.ignore_tables = Array.wrap(opts[:ignore_tables])
188
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
189
+ yield stream.string if block_given?
190
+ stream.string
191
+ }
192
+ end
193
+
194
+ end
metadata ADDED
@@ -0,0 +1,185 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: schema_plus_views
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - ronen barzel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: schema_plus_core
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: schema_dev
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.2'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov-gem-profile
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description:
126
+ email:
127
+ - ronen@barzel.org
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - ".travis.yml"
134
+ - Gemfile
135
+ - LICENSE.txt
136
+ - README.md
137
+ - Rakefile
138
+ - gemfiles/Gemfile.base
139
+ - gemfiles/activerecord-4.2/Gemfile.base
140
+ - gemfiles/activerecord-4.2/Gemfile.mysql2
141
+ - gemfiles/activerecord-4.2/Gemfile.postgresql
142
+ - gemfiles/activerecord-4.2/Gemfile.sqlite3
143
+ - lib/schema_plus/views.rb
144
+ - lib/schema_plus/views/active_record/connection_adapters/abstract_adapter.rb
145
+ - lib/schema_plus/views/active_record/connection_adapters/mysql2_adapter.rb
146
+ - lib/schema_plus/views/active_record/connection_adapters/postgresql_adapter.rb
147
+ - lib/schema_plus/views/active_record/connection_adapters/sqlite3_adapter.rb
148
+ - lib/schema_plus/views/middleware.rb
149
+ - lib/schema_plus/views/version.rb
150
+ - lib/schema_plus_views.rb
151
+ - schema_dev.yml
152
+ - schema_plus_views.gemspec
153
+ - spec/named_schemas_spec.rb
154
+ - spec/sanity_spec.rb
155
+ - spec/spec_helper.rb
156
+ - spec/views_spec.rb
157
+ homepage: https://github.com/SchemaPlus/schema_plus_views
158
+ licenses:
159
+ - MIT
160
+ metadata: {}
161
+ post_install_message:
162
+ rdoc_options: []
163
+ require_paths:
164
+ - lib
165
+ required_ruby_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ">="
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ required_rubygems_version: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ requirements: []
176
+ rubyforge_project:
177
+ rubygems_version: 2.2.2
178
+ signing_key:
179
+ specification_version: 4
180
+ summary: Adds support for views to ActiveRecord
181
+ test_files:
182
+ - spec/named_schemas_spec.rb
183
+ - spec/sanity_spec.rb
184
+ - spec/spec_helper.rb
185
+ - spec/views_spec.rb