schema_plus_views 0.1.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 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