verifiable_view 0.0.1
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 +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: []
|