spectacles 0.0.2
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.
- 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: []
|