verifiable_view 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gems +3 -0
- data/Readme.md +45 -0
- data/lib/verifiable_view.rb +56 -0
- data/test/support/setup_database_helper.rb +31 -0
- data/test/verifiable_view_test.rb +72 -0
- data/verifiable_view.gemspec +14 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d8a982a6eff95584ff03dabe667bd7ad548aa523
|
4
|
+
data.tar.gz: 65bea1f3a80b9eb5891681de82d5bfb8bf0f7823
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7ba43c9d9fd53c831609618a66385ee3d600c1e22b298ee999f23f5209b2cce9e848a229737f347c1fdf4fca723d61c29ff6108508c0b4ce26539e2cfe62f86e
|
7
|
+
data.tar.gz: 2fa2d18cf62b8b587553134bf4ef9a70c34d17bc5acd1bced3362b2e92a001818ebe9d33695b7801619772cdab0927e29940f32338fe248eb69be0a499ea30b6
|
data/.gems
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Verifiable View
|
2
|
+
|
3
|
+
Keep your view definitions in sync with your named scopes and associations'
|
4
|
+
definitions.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
`gem install verifiable_view`
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
```rb
|
13
|
+
class RegularProduct
|
14
|
+
extend VerifiableView
|
15
|
+
|
16
|
+
definition do
|
17
|
+
Product.not_deprecated.where(..)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
Then in your tests you'll have a
|
23
|
+
```rb
|
24
|
+
RegularProduct.verify
|
25
|
+
```
|
26
|
+
|
27
|
+
That will check that you have a `regular_products` view defined in your database
|
28
|
+
and that the query matches with your definition (Products that are not
|
29
|
+
deprecated, etc)
|
30
|
+
|
31
|
+
## Why
|
32
|
+
|
33
|
+
Named scopes and named associations are easy to write with Arel and easy to
|
34
|
+
maintain. Every time you write a view by crafting your SQL by hand you risk
|
35
|
+
forgetting a condition. If you already have a definition of your associations,
|
36
|
+
why to violate DRYness and duplicate them?
|
37
|
+
|
38
|
+
Also when updating the definition of a named scope, you will notice immediately
|
39
|
+
that your view got out of your sync.
|
40
|
+
|
41
|
+
## Testing
|
42
|
+
|
43
|
+
`gem install dep`
|
44
|
+
`dep install`
|
45
|
+
`cutest test/*.rb`
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module VerifiableView
|
4
|
+
def verify
|
5
|
+
VerifiableView.verification_method.call(
|
6
|
+
code_view_definition,
|
7
|
+
db_view_definition
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
def definition(&block)
|
12
|
+
@definition ||= block or raise "Missing view definition"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.verification_method=(some_proc)
|
16
|
+
@verification_method = some_proc
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def code_view_definition
|
22
|
+
view_name = "view_#{SecureRandom.hex(10)}"
|
23
|
+
result = ""
|
24
|
+
transaction do
|
25
|
+
create_view(view_name)
|
26
|
+
result = get_view_sql(view_name)
|
27
|
+
raise ActiveRecord::Rollback
|
28
|
+
end
|
29
|
+
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
def db_view_definition
|
34
|
+
get_view_sql(table_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.verification_method
|
38
|
+
@verification_method || (lambda do |code_def, db_def|
|
39
|
+
return true if db_def == code_def
|
40
|
+
raise "The view definition in your DB doesn't match with your code"
|
41
|
+
end)
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_view(name)
|
45
|
+
definition_sql = definition.call.to_sql
|
46
|
+
connection.execute("create view #{name} as #{definition_sql}")
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_view_sql(view_name)
|
50
|
+
connection.
|
51
|
+
execute("select view_definition from information_schema.views
|
52
|
+
where table_name = '#{view_name}'").
|
53
|
+
first&.
|
54
|
+
fetch("view_definition")
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "active_record"
|
2
|
+
|
3
|
+
ActiveRecord::Base.establish_connection(
|
4
|
+
adapter: "postgresql",
|
5
|
+
database: "verifiable_view",
|
6
|
+
encoding: "utf8",
|
7
|
+
host: "localhost",
|
8
|
+
min_messages: "warning"
|
9
|
+
)
|
10
|
+
|
11
|
+
class CreateSchema < ActiveRecord::Migration[5.2]
|
12
|
+
def self.up
|
13
|
+
execute <<~SQL
|
14
|
+
DROP VIEW IF EXISTS regular_products
|
15
|
+
SQL
|
16
|
+
|
17
|
+
create_table :products, force: true do |table|
|
18
|
+
table.string :product_category, null: false
|
19
|
+
table.boolean :deprecated, null: false
|
20
|
+
end
|
21
|
+
|
22
|
+
execute <<-SQL
|
23
|
+
CREATE VIEW regular_products AS
|
24
|
+
SELECT "products".* FROM "products" WHERE "products"."product_category" = 'regular' AND "products"."deprecated" = FALSE
|
25
|
+
SQL
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
CreateSchema.suppress_messages do
|
30
|
+
CreateSchema.migrate(:up)
|
31
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
2
|
+
require_relative "support/setup_database_helper"
|
3
|
+
require 'verifiable_view'
|
4
|
+
|
5
|
+
class Product < ActiveRecord::Base
|
6
|
+
scope :regular, -> { where(product_category: :regular) }
|
7
|
+
scope :active, -> { where(deprecated: false) }
|
8
|
+
end
|
9
|
+
|
10
|
+
class RegularProduct < ActiveRecord::Base
|
11
|
+
extend VerifiableView
|
12
|
+
|
13
|
+
definition do
|
14
|
+
Product.regular.active
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
test ".verify" do
|
19
|
+
VerifiableView.verification_method = (lambda do |expected, actual|
|
20
|
+
assert_equal(expected, actual)
|
21
|
+
end)
|
22
|
+
|
23
|
+
RegularProduct.verify
|
24
|
+
end
|
25
|
+
|
26
|
+
test ".verify raises exception when missing code definition" do
|
27
|
+
class NoDefinitionView < ActiveRecord::Base
|
28
|
+
extend VerifiableView
|
29
|
+
|
30
|
+
self.table_name = :regular_products
|
31
|
+
end
|
32
|
+
|
33
|
+
error = assert_raise do
|
34
|
+
NoDefinitionView.verify
|
35
|
+
end
|
36
|
+
assert_equal("Missing view definition", error.message)
|
37
|
+
end
|
38
|
+
|
39
|
+
test ".verify with unmatched db definition" do
|
40
|
+
class DeprecatedRegularProduct < ActiveRecord::Base
|
41
|
+
extend VerifiableView
|
42
|
+
self.table_name = :regular_products
|
43
|
+
|
44
|
+
definition do
|
45
|
+
Product.regular.where(deprecated: true)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
assert_raise do
|
50
|
+
DeprecatedRegularProduct.verify
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
test ".verify compares with nil when missing db view" do
|
55
|
+
class NoView < ActiveRecord::Base
|
56
|
+
extend VerifiableView
|
57
|
+
|
58
|
+
definition do
|
59
|
+
Product.regular
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
values = {}
|
64
|
+
VerifiableView.verification_method = (lambda do |expected, actual|
|
65
|
+
values[:actual] = actual
|
66
|
+
values[:expected] = expected
|
67
|
+
end)
|
68
|
+
|
69
|
+
NoView.verify
|
70
|
+
assert values[:expected].downcase.include?("select")
|
71
|
+
assert_equal(nil, values[:actual])
|
72
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "verifiable_view"
|
3
|
+
s.version = "0.0.1"
|
4
|
+
s.summary = "Database Views that don't break"
|
5
|
+
s.description = "Easier way to mantain Database Views with ActiveRecord"
|
6
|
+
s.authors = ["CarlosIPe"]
|
7
|
+
s.email = ["carlos2@compendium.com.ar"]
|
8
|
+
s.homepage = "https://github.com/carlosipe/verifiable_view"
|
9
|
+
s.license = "MIT"
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
|
13
|
+
s.add_development_dependency "cutest", '~> 1'
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: verifiable_view
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- CarlosIPe
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-12-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: cutest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
description: Easier way to mantain Database Views with ActiveRecord
|
28
|
+
email:
|
29
|
+
- carlos2@compendium.com.ar
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".gems"
|
35
|
+
- Readme.md
|
36
|
+
- lib/verifiable_view.rb
|
37
|
+
- test/support/setup_database_helper.rb
|
38
|
+
- test/verifiable_view_test.rb
|
39
|
+
- verifiable_view.gemspec
|
40
|
+
homepage: https://github.com/carlosipe/verifiable_view
|
41
|
+
licenses:
|
42
|
+
- MIT
|
43
|
+
metadata: {}
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements: []
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 2.6.14.1
|
61
|
+
signing_key:
|
62
|
+
specification_version: 4
|
63
|
+
summary: Database Views that don't break
|
64
|
+
test_files: []
|