spectacles 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/Gemfile +12 -0
- data/Rakefile +25 -0
- data/Readme.rdoc +43 -0
- data/lib/spectacles.rb +42 -0
- data/lib/spectacles/schema_dumper.rb +17 -0
- data/lib/spectacles/schema_statements.rb +18 -0
- data/lib/spectacles/schema_statements/abstract_adapter.rb +40 -0
- data/lib/spectacles/schema_statements/mysql2_adapter.rb +9 -0
- data/lib/spectacles/schema_statements/mysql_adapter.rb +20 -0
- data/lib/spectacles/schema_statements/postgresql_adapter.rb +37 -0
- data/lib/spectacles/schema_statements/sqlite_adapter.rb +33 -0
- data/lib/spectacles/schema_statements/sqlserver_adapter.rb +23 -0
- data/lib/spectacles/version.rb +3 -0
- data/lib/spectacles/view.rb +9 -0
- data/specs/adapters/mysql2_adapter_spec.rb +16 -0
- data/specs/adapters/mysql_adapter_spec.rb +15 -0
- data/specs/adapters/postgresql_adapter_spec.rb +17 -0
- data/specs/adapters/sqlite_adapter_spec.rb +14 -0
- data/specs/spec_helper.rb +53 -0
- data/specs/spectacles/schema_statements/abstract_adapter_spec.rb +19 -0
- data/specs/spectacles/view_spec.rb +7 -0
- data/specs/support/minitest_matchers.rb +5 -0
- data/specs/support/minitest_shared.rb +21 -0
- data/specs/support/schema_statement_examples.rb +98 -0
- data/specs/support/view_examples.rb +31 -0
- data/spectacles.gemspec +31 -0
- metadata +162 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
namespace :test do
|
5
|
+
adapters = [:mysql, :mysql2, :postgresql, :sqlite]
|
6
|
+
task :all => [:spectacles] + adapters
|
7
|
+
|
8
|
+
adapters.each do |adapter|
|
9
|
+
Rake::TestTask.new(adapter) do |t|
|
10
|
+
t.libs.push "lib"
|
11
|
+
t.libs.push "specs"
|
12
|
+
t.pattern = "specs/adapters/#{t.name}*_spec.rb"
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Rake::TestTask.new(:spectacles) do |t|
|
18
|
+
t.libs.push "lib"
|
19
|
+
t.libs.push "specs"
|
20
|
+
t.pattern = "specs/spectacles/**/*_spec.rb"
|
21
|
+
t.verbose = true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
task :default => 'test:all'
|
data/Readme.rdoc
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
= Spectacles
|
2
|
+
|
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+.
|
4
|
+
|
5
|
+
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
|
+
|
7
|
+
It currently only works with the SQLite and PostgreSQL drivers. MySQL & MySQL2 have a couple of issues that are still being worked out.
|
8
|
+
|
9
|
+
= Using Spectacles
|
10
|
+
Install it
|
11
|
+
gem install spectacles # => OR include it in your Gemfile
|
12
|
+
|
13
|
+
== Migrations
|
14
|
+
|
15
|
+
Create a migration from an query string:
|
16
|
+
|
17
|
+
create_view :product_users do
|
18
|
+
"SELECT name AS product_name, first_name AS username FROM
|
19
|
+
products JOIN users ON users.id = products.user_id"
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
Create a migration from an ARel object:
|
24
|
+
|
25
|
+
create_view :product_users do
|
26
|
+
Product.select("products.name AS product_name).
|
27
|
+
select("users.first_name AS username").
|
28
|
+
join(:users)
|
29
|
+
end
|
30
|
+
|
31
|
+
== Models
|
32
|
+
|
33
|
+
class ProductUser < Spectacles::View
|
34
|
+
# Add relationships
|
35
|
+
|
36
|
+
# Use scopes
|
37
|
+
|
38
|
+
# Your fancy methods
|
39
|
+
end
|
40
|
+
|
41
|
+
= License
|
42
|
+
|
43
|
+
Spectacles is licensed under MIT license (Read lib/spectactles.rb for full license)
|
data/lib/spectacles.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# (The MIT License)
|
2
|
+
#
|
3
|
+
# Copyright (c) 2012 Adam Hutchison, http://github.com/liveh2o
|
4
|
+
# Copyright (c) 2012 Brandon Dewitt, http://abrandoned.com
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'),
|
7
|
+
# to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
8
|
+
# and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
11
|
+
#
|
12
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
13
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
14
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
15
|
+
# IN THE SOFTWARE.
|
16
|
+
|
17
|
+
require 'active_record'
|
18
|
+
require 'active_support/core_ext'
|
19
|
+
require 'spectacles/schema_statements'
|
20
|
+
require 'spectacles/schema_dumper'
|
21
|
+
require 'spectacles/view'
|
22
|
+
require 'spectacles/version'
|
23
|
+
|
24
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
|
25
|
+
alias_method(:_spectacles_orig_inherited, :inherited) if method_defined?(:inherited)
|
26
|
+
|
27
|
+
def self.inherited(klass)
|
28
|
+
::Spectacles::load_adapters
|
29
|
+
_spectacles_orig_inherited if method_defined?(:_spectacles_orig_inherited)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
ActiveRecord::SchemaDumper.class_eval do
|
34
|
+
alias_method(:_spectacles_orig_trailer, :trailer)
|
35
|
+
|
36
|
+
def trailer(stream)
|
37
|
+
::Spectacles::SchemaDumper.dump_views(stream, @connection)
|
38
|
+
_spectacles_orig_trailer(stream)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
Spectacles::load_adapters
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Spectacles
|
2
|
+
module SchemaDumper
|
3
|
+
def self.dump_views(stream, connection)
|
4
|
+
connection.views.each do |view|
|
5
|
+
dump_view(stream, connection, view)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.dump_view(stream, connection, view_name)
|
10
|
+
stream.print <<-CREATEVIEW
|
11
|
+
create_view :#{view_name} do
|
12
|
+
"#{connection.view_build_query(view_name)}"
|
13
|
+
end
|
14
|
+
CREATEVIEW
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spectacles/schema_statements/abstract_adapter'
|
2
|
+
|
3
|
+
module Spectacles
|
4
|
+
SUPPORTED_ADAPTERS = %w( Mysql Mysql2 PostgreSQL SQLServer SQLite )
|
5
|
+
|
6
|
+
def self.load_adapters
|
7
|
+
SUPPORTED_ADAPTERS.each do |db|
|
8
|
+
adapter_class = "#{db}Adapter"
|
9
|
+
|
10
|
+
if ActiveRecord::ConnectionAdapters.const_defined?(adapter_class)
|
11
|
+
require "spectacles/schema_statements/#{db.downcase}_adapter"
|
12
|
+
ActiveRecord::ConnectionAdapters.const_get(adapter_class).class_eval do
|
13
|
+
include Spectacles::SchemaStatements.const_get(adapter_class)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Spectacles
|
2
|
+
module SchemaStatements
|
3
|
+
module AbstractAdapter
|
4
|
+
def create_view(view_name, build_query = nil)
|
5
|
+
raise "#{self.class} requires a query or block" if build_query.nil? && !block_given?
|
6
|
+
|
7
|
+
build_query = yield if block_given?
|
8
|
+
build_query = build_query.to_sql if build_query.respond_to?(:to_sql)
|
9
|
+
|
10
|
+
query = create_view_statement(view_name, build_query)
|
11
|
+
execute(query)
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_view_statement(view_name, create_query)
|
15
|
+
query = "CREATE VIEW ? AS #{create_query}"
|
16
|
+
query_array = [query, view_name.to_s]
|
17
|
+
|
18
|
+
#return ActiveRecord::Base.__send__(:sanitize_sql_array, query_array)
|
19
|
+
"CREATE VIEW #{view_name} AS #{create_query}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def drop_view(view_name)
|
23
|
+
query = drop_view_statement(view_name)
|
24
|
+
execute(query)
|
25
|
+
end
|
26
|
+
|
27
|
+
def drop_view_statement(view_name)
|
28
|
+
query = "DROP VIEW IF EXISTS ? "
|
29
|
+
query_array = [query, view_name.to_s]
|
30
|
+
|
31
|
+
#return ActiveRecord::Base.__send__(:sanitize_sql_array, query_array)
|
32
|
+
"DROP VIEW IF EXISTS #{view_name} "
|
33
|
+
end
|
34
|
+
|
35
|
+
def views
|
36
|
+
raise "Override view for your db adapter in #{self.class}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spectacles/schema_statements/abstract_adapter'
|
2
|
+
|
3
|
+
module Spectacles
|
4
|
+
module SchemaStatements
|
5
|
+
module MysqlAdapter
|
6
|
+
include Spectacles::SchemaStatements::AbstractAdapter
|
7
|
+
|
8
|
+
def views(name = nil) #:nodoc:
|
9
|
+
execute("SHOW FULL TABLES WHERE TABLE_TYPE='VIEW'").map { |row| row[0] }
|
10
|
+
end
|
11
|
+
|
12
|
+
def view_build_query(view, name = nil)
|
13
|
+
row = execute("SHOW CREATE VIEW #{view}", name).first
|
14
|
+
return row[1].gsub(/CREATE .*? (AS)+/i, "")
|
15
|
+
rescue ActiveRecord::StatementInvalid => e
|
16
|
+
raise "No view called #{view} found"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spectacles/schema_statements/abstract_adapter'
|
2
|
+
|
3
|
+
module Spectacles
|
4
|
+
module SchemaStatements
|
5
|
+
module PostgreSQLAdapter
|
6
|
+
include Spectacles::SchemaStatements::AbstractAdapter
|
7
|
+
|
8
|
+
def views(name = nil) #:nodoc:
|
9
|
+
q = <<-SQL
|
10
|
+
SELECT table_name, table_type
|
11
|
+
FROM information_schema.tables
|
12
|
+
WHERE table_schema IN (#{schemas})
|
13
|
+
AND table_type = 'VIEW'
|
14
|
+
SQL
|
15
|
+
|
16
|
+
query(q, name).map { |row| row[0] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def view_build_query(view, name = nil)
|
20
|
+
q = <<-SQL
|
21
|
+
SELECT view_definition
|
22
|
+
FROM information_schema.views
|
23
|
+
WHERE table_catalog = (SELECT catalog_name FROM information_schema.information_schema_catalog_name)
|
24
|
+
AND table_schema IN (#{schemas})
|
25
|
+
AND table_name = '#{view}'
|
26
|
+
SQL
|
27
|
+
|
28
|
+
select_value(q, name) or raise "No view called #{view} found"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def schemas
|
33
|
+
schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spectacles/schema_statements/abstract_adapter'
|
2
|
+
|
3
|
+
module Spectacles
|
4
|
+
module SchemaStatements
|
5
|
+
module SQLiteAdapter
|
6
|
+
include Spectacles::SchemaStatements::AbstractAdapter
|
7
|
+
|
8
|
+
def generate_view_query(*columns)
|
9
|
+
sql = <<-SQL
|
10
|
+
SELECT #{columns.join(',')}
|
11
|
+
FROM sqlite_master
|
12
|
+
WHERE type = 'view'
|
13
|
+
SQL
|
14
|
+
end
|
15
|
+
|
16
|
+
def views #:nodoc:
|
17
|
+
sql = generate_view_query(:name)
|
18
|
+
|
19
|
+
exec_query(sql, "SCHEMA").map do |row|
|
20
|
+
row['name']
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def view_build_query(table_name)
|
25
|
+
sql = generate_view_query(:sql)
|
26
|
+
sql << " AND name = #{quote_table_name(table_name)}"
|
27
|
+
|
28
|
+
row = exec_query(sql, "SCHEMA").first
|
29
|
+
row['sql'].gsub(/CREATE VIEW .*? AS/i, "")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spectacles/schema_statements/abstract_adapter'
|
2
|
+
|
3
|
+
module Spectacles
|
4
|
+
module SchemaStatements
|
5
|
+
module SQLServerAdapter
|
6
|
+
include Spectacles::SchemaStatements::AbstractAdapter
|
7
|
+
|
8
|
+
def views(name = nil) #:nodoc:
|
9
|
+
select_values("SELECT table_name FROM information_schema.views", name)
|
10
|
+
end
|
11
|
+
|
12
|
+
def view_build_query(view, name = nil)
|
13
|
+
q =<<-ENDSQL
|
14
|
+
SELECT view_definition FROM information_schema.views
|
15
|
+
WHERE table_name = '#{view}'
|
16
|
+
ENDSQL
|
17
|
+
|
18
|
+
q = select_value(q, name) or raise "No view called #{view} found"
|
19
|
+
q.gsub(/CREATE VIEW .*? AS/i, "")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Spectacles::SchemaStatements::Mysql2Adapter" do
|
4
|
+
config = {
|
5
|
+
:adapter => "mysql2",
|
6
|
+
:host => "localhost",
|
7
|
+
:username => "root"
|
8
|
+
}
|
9
|
+
|
10
|
+
configure_database(config)
|
11
|
+
recreate_database("spectacles_test")
|
12
|
+
load_schema
|
13
|
+
|
14
|
+
it_behaves_like "an adapter", "Mysql2Adapter"
|
15
|
+
it_behaves_like "a view model"
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
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
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Spectacles::SchemaStatements::PostgreSQLAdapter" do
|
4
|
+
config = {
|
5
|
+
:adapter => "postgresql",
|
6
|
+
:host => "localhost",
|
7
|
+
:username => "postgres",
|
8
|
+
:database => "postgres",
|
9
|
+
:min_messages => "error"
|
10
|
+
}
|
11
|
+
configure_database(config)
|
12
|
+
recreate_database("spectacles_test")
|
13
|
+
load_schema
|
14
|
+
|
15
|
+
it_behaves_like "an adapter", "PostgreSQLAdapter"
|
16
|
+
it_behaves_like "a view model"
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Spectacles::SchemaStatements::SQLiteAdapter" do
|
4
|
+
File.delete(File.expand_path(File.dirname(__FILE__) + "/../test.db")) rescue nil
|
5
|
+
|
6
|
+
ActiveRecord::Base.establish_connection(
|
7
|
+
:adapter => "sqlite3",
|
8
|
+
:database => "specs/test.db"
|
9
|
+
)
|
10
|
+
load_schema
|
11
|
+
|
12
|
+
it_behaves_like "an adapter", "SQLiteAdapter"
|
13
|
+
it_behaves_like "a view model"
|
14
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter "/specs"
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'bundler'
|
8
|
+
Bundler.require(:default, :development, :test)
|
9
|
+
|
10
|
+
require 'minitest/spec'
|
11
|
+
require 'minitest/autorun'
|
12
|
+
require 'minitest/pride'
|
13
|
+
require 'support/minitest_shared'
|
14
|
+
require 'support/minitest_matchers'
|
15
|
+
require 'support/schema_statement_examples'
|
16
|
+
require 'support/view_examples'
|
17
|
+
|
18
|
+
class User < ActiveRecord::Base
|
19
|
+
has_many :products
|
20
|
+
end
|
21
|
+
|
22
|
+
class Product < ActiveRecord::Base
|
23
|
+
belongs_to :user
|
24
|
+
end
|
25
|
+
|
26
|
+
ActiveRecord::Schema.verbose = false
|
27
|
+
|
28
|
+
def configure_database(config)
|
29
|
+
@database_config = config
|
30
|
+
end
|
31
|
+
|
32
|
+
def load_schema
|
33
|
+
ActiveRecord::Schema.define(:version => 1) do
|
34
|
+
create_table :users do |t|
|
35
|
+
t.string :first_name
|
36
|
+
t.string :last_name
|
37
|
+
end
|
38
|
+
|
39
|
+
create_table :products do |t|
|
40
|
+
t.string :name
|
41
|
+
t.integer :value
|
42
|
+
t.boolean :available, :default => true
|
43
|
+
t.belongs_to :user
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def recreate_database(database)
|
49
|
+
ActiveRecord::Base.establish_connection(@database_config)
|
50
|
+
ActiveRecord::Base.connection.drop_database(database) rescue nil
|
51
|
+
ActiveRecord::Base.connection.create_database(database)
|
52
|
+
ActiveRecord::Base.establish_connection(@database_config.merge(:database => database))
|
53
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Spectacles::SchemaStatements::AbstractAdapter do
|
4
|
+
class TestBase
|
5
|
+
extend Spectacles::SchemaStatements::AbstractAdapter
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#create_view" do
|
9
|
+
it "throws error when block not given and no build_query" do
|
10
|
+
lambda { TestBase.create_view(:view_name) }.must_raise(RuntimeError)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#views" do
|
15
|
+
it "throws error when accessed on AbstractAdapter" do
|
16
|
+
lambda { TestBase.views }.must_raise(RuntimeError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
|
3
|
+
MiniTest::Spec.class_eval do
|
4
|
+
def self.shared_examples
|
5
|
+
@shared_examples ||= {}
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module MiniTest::Spec::SharedExamples
|
10
|
+
def shared_examples_for(desc, &block)
|
11
|
+
MiniTest::Spec.shared_examples[desc] = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def it_behaves_like(desc, *args)
|
15
|
+
self.instance_eval do
|
16
|
+
MiniTest::Spec.shared_examples[desc].call(*args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Object.class_eval { include(MiniTest::Spec::SharedExamples) }
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_examples_for "an adapter" do |adapter|
|
4
|
+
shared_base = Class.new do
|
5
|
+
extend Spectacles::SchemaStatements.const_get(adapter)
|
6
|
+
def self.execute(query); query; end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "ActiveRecord::SchemaDumper#dump" do
|
10
|
+
before(:each) do
|
11
|
+
ActiveRecord::Base.connection.drop_view(:new_product_users)
|
12
|
+
|
13
|
+
ActiveRecord::Base.connection.create_view(:new_product_users) do
|
14
|
+
"SELECT name AS product_name, first_name AS username FROM
|
15
|
+
products JOIN users ON users.id = products.user_id"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return create_view in dump stream" do
|
20
|
+
stream = StringIO.new
|
21
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
22
|
+
stream.string.must_match(/create_view/)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return create_view in dump stream" do
|
26
|
+
stream = StringIO.new
|
27
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
28
|
+
|
29
|
+
ActiveRecord::Base.connection.views.each do |view|
|
30
|
+
ActiveRecord::Base.connection.drop_view(view)
|
31
|
+
end
|
32
|
+
|
33
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
34
|
+
ActiveRecord::Base.connection.drop_table(table)
|
35
|
+
end
|
36
|
+
|
37
|
+
eval(stream.string)
|
38
|
+
|
39
|
+
ActiveRecord::Base.connection.views.must_include('new_product_users')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#create_view" do
|
44
|
+
let(:view_name) { :view_name }
|
45
|
+
|
46
|
+
it "throws error when block not given and no build_query" do
|
47
|
+
lambda { shared_base.create_view(view_name) }.must_raise(RuntimeError)
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "view_name" do
|
51
|
+
it "takes a symbol as the view_name" do
|
52
|
+
shared_base.create_view(view_name.to_sym, Product.scoped).must_match(/#{view_name}/)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "takes a string as the view_name" do
|
56
|
+
shared_base.create_view(view_name.to_s, Product.scoped).must_match(/#{view_name}/)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "build_query" do
|
61
|
+
it "uses a string if passed" do
|
62
|
+
select_statement = "SELECT * FROM products"
|
63
|
+
shared_base.create_view(view_name, select_statement).must_match(/#{Regexp.escape(select_statement)}/)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "uses an Arel::Relation if passed" do
|
67
|
+
select_statement = Product.scoped.to_sql
|
68
|
+
shared_base.create_view(view_name, Product.scoped).must_match(/#{Regexp.escape(select_statement)}/)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "block" do
|
73
|
+
it "can use an Arel::Relation from the yield" do
|
74
|
+
select_statement = Product.scoped.to_sql
|
75
|
+
shared_base.create_view(view_name) { Product.scoped }.must_match(/#{Regexp.escape(select_statement)}/)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "can use a String from the yield" do
|
79
|
+
select_statement = "SELECT * FROM products"
|
80
|
+
shared_base.create_view(view_name) { "SELECT * FROM products" }.must_match(/#{Regexp.escape(select_statement)}/)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#drop_view" do
|
86
|
+
let(:view_name) { :view_name }
|
87
|
+
|
88
|
+
describe "view_name" do
|
89
|
+
it "takes a symbol as the view_name" do
|
90
|
+
shared_base.drop_view(view_name.to_sym).must_match(/#{view_name}/)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "takes a string as the view_name" do
|
94
|
+
shared_base.drop_view(view_name.to_s).must_match(/#{view_name}/)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_examples_for "a view model" do
|
4
|
+
ActiveRecord::Base.connection.create_view(:new_product_users) do
|
5
|
+
"SELECT name AS product_name, first_name AS username FROM
|
6
|
+
products JOIN users ON users.id = products.user_id"
|
7
|
+
end
|
8
|
+
|
9
|
+
class NewProductUser < Spectacles::View
|
10
|
+
scope :duck_lovers, where(:product_name => 'Rubber Duck')
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "Spectacles::View" do
|
14
|
+
describe "inherited class" do
|
15
|
+
it "can has scopes" do
|
16
|
+
User.destroy_all
|
17
|
+
Product.destroy_all
|
18
|
+
@john = User.create(:first_name => 'John', :last_name => 'Doe')
|
19
|
+
@john.products.create(:name => 'Rubber Duck', :value => 10)
|
20
|
+
|
21
|
+
NewProductUser.duck_lovers.first.username.must_be @john.first_name
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "an instance" do
|
25
|
+
it "is readonly" do
|
26
|
+
NewProductUser.new.readonly?.must_be true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/spectacles.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "spectacles/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "spectacles"
|
7
|
+
s.version = Spectacles::VERSION
|
8
|
+
s.authors = ["Adam Hutchison, Brandon Dewitt"]
|
9
|
+
s.email = ["liveh2o@gmail.com, brandonsdewitt@gmail.com"]
|
10
|
+
s.homepage = "http://github.com/liveh2o/spectacles"
|
11
|
+
s.summary = %q{Spectacles (derived from RailsSQLViews) adds database view functionality to ActiveRecord.}
|
12
|
+
s.description = %q{Still working out some of the kinks. Almost ready for Prime Time(TM). If you decide to use it and have problems, please report them at github.com/liveh2o/spectactles/issues}
|
13
|
+
|
14
|
+
s.rubyforge_project = "spectacles"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "minitest"
|
23
|
+
s.add_development_dependency "mysql"
|
24
|
+
s.add_development_dependency "mysql2"
|
25
|
+
s.add_development_dependency "pg"
|
26
|
+
s.add_development_dependency "rake"
|
27
|
+
s.add_development_dependency "sqlite3-ruby"
|
28
|
+
|
29
|
+
s.add_runtime_dependency "activerecord"
|
30
|
+
s.add_runtime_dependency "activesupport"
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spectacles
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Adam Hutchison, Brandon Dewitt
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-10 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: minitest
|
16
|
+
requirement: &2165825860 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2165825860
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mysql
|
27
|
+
requirement: &2165825440 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2165825440
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: mysql2
|
38
|
+
requirement: &2165825020 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2165825020
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: pg
|
49
|
+
requirement: &2165824600 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *2165824600
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rake
|
60
|
+
requirement: &2165824180 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *2165824180
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3-ruby
|
71
|
+
requirement: &2165823760 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *2165823760
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: activerecord
|
82
|
+
requirement: &2165823340 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :runtime
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *2165823340
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: activesupport
|
93
|
+
requirement: &2165822920 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
type: :runtime
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *2165822920
|
102
|
+
description: Still working out some of the kinks. Almost ready for Prime Time(TM).
|
103
|
+
If you decide to use it and have problems, please report them at github.com/liveh2o/spectactles/issues
|
104
|
+
email:
|
105
|
+
- liveh2o@gmail.com, brandonsdewitt@gmail.com
|
106
|
+
executables: []
|
107
|
+
extensions: []
|
108
|
+
extra_rdoc_files: []
|
109
|
+
files:
|
110
|
+
- .gitignore
|
111
|
+
- Gemfile
|
112
|
+
- Rakefile
|
113
|
+
- Readme.rdoc
|
114
|
+
- lib/spectacles.rb
|
115
|
+
- lib/spectacles/schema_dumper.rb
|
116
|
+
- lib/spectacles/schema_statements.rb
|
117
|
+
- lib/spectacles/schema_statements/abstract_adapter.rb
|
118
|
+
- lib/spectacles/schema_statements/mysql2_adapter.rb
|
119
|
+
- lib/spectacles/schema_statements/mysql_adapter.rb
|
120
|
+
- lib/spectacles/schema_statements/postgresql_adapter.rb
|
121
|
+
- lib/spectacles/schema_statements/sqlite_adapter.rb
|
122
|
+
- lib/spectacles/schema_statements/sqlserver_adapter.rb
|
123
|
+
- lib/spectacles/version.rb
|
124
|
+
- lib/spectacles/view.rb
|
125
|
+
- specs/adapters/mysql2_adapter_spec.rb
|
126
|
+
- specs/adapters/mysql_adapter_spec.rb
|
127
|
+
- specs/adapters/postgresql_adapter_spec.rb
|
128
|
+
- specs/adapters/sqlite_adapter_spec.rb
|
129
|
+
- specs/spec_helper.rb
|
130
|
+
- specs/spectacles/schema_statements/abstract_adapter_spec.rb
|
131
|
+
- specs/spectacles/view_spec.rb
|
132
|
+
- specs/support/minitest_matchers.rb
|
133
|
+
- specs/support/minitest_shared.rb
|
134
|
+
- specs/support/schema_statement_examples.rb
|
135
|
+
- specs/support/view_examples.rb
|
136
|
+
- spectacles.gemspec
|
137
|
+
homepage: http://github.com/liveh2o/spectacles
|
138
|
+
licenses: []
|
139
|
+
post_install_message:
|
140
|
+
rdoc_options: []
|
141
|
+
require_paths:
|
142
|
+
- lib
|
143
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
144
|
+
none: false
|
145
|
+
requirements:
|
146
|
+
- - ! '>='
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
none: false
|
151
|
+
requirements:
|
152
|
+
- - ! '>='
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
requirements: []
|
156
|
+
rubyforge_project: spectacles
|
157
|
+
rubygems_version: 1.8.15
|
158
|
+
signing_key:
|
159
|
+
specification_version: 3
|
160
|
+
summary: Spectacles (derived from RailsSQLViews) adds database view functionality
|
161
|
+
to ActiveRecord.
|
162
|
+
test_files: []
|