verifiable_view 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ cutest -v 1.2.3
2
+ activerecord -v 5.2.1.1
3
+ pg -v 1.1.3
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: []