dbcode 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: 6921302d6cddedad8cd57765fe94e150adbe26ef
4
+ data.tar.gz: e7ff40acb9567290c73f33bbede5208e4d4b72c5
5
+ SHA512:
6
+ metadata.gz: 82c1d1eaf6bbbc9029819010f4d5d9b42609436299777997b6cd29dc2a95946f269c9e7a091a3da2405456afd8f67df580ce66ba7c9eb9159e583ad735cc6d12
7
+ data.tar.gz: e33e20612fee946d2361b590748b985c1dfd3b7c5b798c0bb1631f9f5560b42cf38c2ff978dc19c7396a2c5695203df638cc1d3c88a43ba8030c0a6042cb477d
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Brian Dunn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # dbcode
2
+
3
+ ## A Database Code Pipeline for ActiveRecord
4
+
5
+ ### Objective
6
+
7
+ Migrations are fine for mutating table structure. Even a necessary evil given some changes require carefully preserving valuable production data.
8
+
9
+ Lately I've been taking advantage of more database features like constraints, views, and functions. Iterating on these during development via migrations is intolerable.
10
+
11
+ dbcode will borrow from a similar out of process dependency synchronization mechanism in rails: the asset pipeline. In development mode any changes to a sql view file ought to be one refresh away. A test ran after a change to a sql view file ought to execute against the latest version. In a production deploy sql views can be bundled into a migration - just like pre-compiling assets.
12
+
13
+ ### Features
14
+
15
+ Database code files live in a directory structure like this:
16
+
17
+ app
18
+ └── db
19
+ └── code
20
+ ├── functions
21
+ ├── triggers
22
+ └── views
23
+
24
+ Interdependency of db/code elements is expressed via magic comments:
25
+
26
+ -- require views/foo
27
+
28
+ Writing drop statements or downward migrations is not necessary. All of your code is kept in a separate schema that is replaced when your declaration files change. Want to migrate to a previous version? Check out that revision in your SCM and connect. In test and development mode this will just work. Production is a slightly different story: read more below.
29
+
30
+ #### Test
31
+
32
+ dbcode ensures that the declarations in your test database are up to date. Any time you change a `db/code` file the changes will be available on your next test run. This happens automatically for tests that boot rails. If you have a test that integrates the database, but doesn't boot rails, call `DBCode.ensure_freshness!` in a before block.
33
+
34
+ #### Development
35
+
36
+ A request made after a change to a `db/code` file will see the latest version of that declaration.
37
+
38
+ #### Production
39
+
40
+ In production mode the automatic synchronization step is skipped. Connecting to a database that doesn't contain the latest version on the `code` schema will log a warning.
41
+
42
+ Running migrations on the production database will do the trick. Alternatively run the task `db:code:sync`
43
+
44
+ ### Disclaimer
45
+
46
+ * This is only intended to be used with postgresql. Your other db is 💩..
47
+
48
+ * Booting into a raw psql connection will not configure your schema search to place `code` first. Try something like this in the repl: `set search_path to code,public;`
49
+
50
+ * Can't be used to manage code in schemas intended for name spacing or permission control. You'll need to use migrations to manage code that lives in schemas other than `code`.
@@ -0,0 +1,42 @@
1
+ require 'tsort'
2
+ module DBCode
3
+ LoadError = Class.new RuntimeError
4
+
5
+ class Graph
6
+ include TSort
7
+
8
+ def initialize(files)
9
+ @files = (files.map {|f| { f.name => f } }.reduce(:merge) || {}).freeze
10
+ end
11
+
12
+ def digest
13
+ Digest::MD5.base64digest to_sql
14
+ end
15
+
16
+ def compile
17
+ tsort.map(&:to_sql).join(";\n")
18
+ end
19
+
20
+ def to_sql
21
+ @to_sql ||= compile
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :files
27
+
28
+ def tsort_each_child(file, &block)
29
+ file.dependency_names.each do |name|
30
+ dependency = files.fetch name do
31
+ raise LoadError, %Q{cannot load file -- #{name}}
32
+ end
33
+
34
+ block.call dependency
35
+ end
36
+ end
37
+
38
+ def tsort_each_node(&b)
39
+ files.values.each(&b)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,14 @@
1
+ module DBCode
2
+ class Railtie < Rails::Railtie
3
+ initializer "dbcode.setup" do |app|
4
+ DBCode.sql_file_path = app.root.join 'db/code'
5
+ config.watchable_dirs[DBCode.sql_file_path.to_s] = ['sql']
6
+ end
7
+
8
+ config.to_prepare &DBCode.method(:ensure_freshness!)
9
+
10
+ rake_tasks do
11
+ load 'dbcode/tasks/db_code.rake'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,77 @@
1
+ module DBCode
2
+ class SearchPath
3
+ def initialize(path)
4
+ @path = path.to_s.split ','
5
+ end
6
+
7
+ def prepend(schema)
8
+ from_parts [schema] + path
9
+ end
10
+
11
+ def append(schema)
12
+ from_parts path + [schema]
13
+ end
14
+
15
+ def to_s
16
+ path.join ','
17
+ end
18
+
19
+ private
20
+ attr_reader :path
21
+
22
+ def from_parts(parts)
23
+ self.class.new parts.join ','
24
+ end
25
+ end
26
+
27
+ class Schema
28
+ attr_reader :name, :connection
29
+ def initialize(name:, connection:)
30
+ @name, @connection = name, connection
31
+ end
32
+
33
+ delegate :execute, to: :connection
34
+
35
+ def reset!
36
+ execute <<-SQL
37
+ drop schema if exists #@name cascade;
38
+ create schema #@name;
39
+ SQL
40
+ end
41
+
42
+ def digest=(digest)
43
+ execute <<-SQL
44
+ comment on schema #@name is 'dbcode_md5:#{digest}'
45
+ SQL
46
+ end
47
+
48
+ def digest
49
+ comment = connection.select_one <<-SQL
50
+ select pg_catalog.obj_description(n.oid, 'pg_namespace') as md5
51
+ from pg_catalog.pg_namespace n where n.nspname = '#@name'
52
+ SQL
53
+ comment && comment['md5'] =~ /^dbcode_md5:(.+)$/ && $1
54
+ end
55
+
56
+ def within_schema(&block)
57
+ old_path = search_path
58
+ connection.schema_search_path = old_path.prepend(name).to_s
59
+ connection.transaction(&block)
60
+ connection.schema_search_path = old_path.to_s
61
+ end
62
+
63
+ def append_path!(config)
64
+ #update all future connections
65
+ config.merge! schema_search_path: search_path.append(name)
66
+ #update all active connections
67
+ connection.pool.connections.each do |connection|
68
+ connection.schema_search_path = config[:schema_search_path]
69
+ end
70
+ end
71
+
72
+ private
73
+ def search_path
74
+ SearchPath.new(connection.schema_search_path)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,21 @@
1
+ module DBCode
2
+ class SQLFile
3
+ attr_reader :path
4
+
5
+ def initialize(path)
6
+ @path = Pathname(path)
7
+ end
8
+
9
+ def name
10
+ path.basename('.sql').to_s
11
+ end
12
+
13
+ def dependency_names
14
+ to_sql.scan(/^\s*-- require (\S+)\s*$/).flatten
15
+ end
16
+
17
+ def to_sql
18
+ @sql ||= path.read
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ namespace :db do
2
+ namespace :code do
3
+ desc "sync the database code schema with the declaration files"
4
+ task sync: :environment do
5
+ DBCode.ensure_freshness!
6
+ end
7
+ end
8
+
9
+ task :migrate do
10
+ DBCode.ensure_freshness!
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module DBCode
2
+ VERSION = '0.0.1'
3
+ end
data/lib/dbcode.rb ADDED
@@ -0,0 +1,30 @@
1
+ module DBCode
2
+ autoload :SQLFile, 'dbcode/sql_file'
3
+ autoload :Schema, 'dbcode/schema'
4
+ autoload :Graph, 'dbcode/graph'
5
+ extend self
6
+ attr_accessor :sql_file_path, :code_schema_name
7
+ self.code_schema_name ||= 'code'
8
+
9
+ def ensure_freshness!
10
+ code = Schema.new connection: ActiveRecord::Base.connection, name: code_schema_name
11
+ code.within_schema do
12
+ unless code.digest == graph.digest
13
+ code.reset!
14
+ code.execute graph.to_sql
15
+ code.digest = graph.digest
16
+ end
17
+ end
18
+ code.append_path!(ActiveRecord::Base.connection_config)
19
+ end
20
+
21
+ def graph
22
+ Graph.new file_names.sort.map &SQLFile.method(:new)
23
+ end
24
+
25
+ def file_names
26
+ Dir[sql_file_path.join('**/*.sql').expand_path]
27
+ end
28
+ end
29
+
30
+ require 'dbcode/railtie' if defined? Rails
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dbcode
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Brian Dunn
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.4'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pg
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.2'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.2'
69
+ description: A Database Code Pipeline for ActiveRecord
70
+ email: brian@hashrocket.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files:
74
+ - LICENSE
75
+ - README.md
76
+ files:
77
+ - LICENSE
78
+ - README.md
79
+ - lib/dbcode.rb
80
+ - lib/dbcode/graph.rb
81
+ - lib/dbcode/railtie.rb
82
+ - lib/dbcode/schema.rb
83
+ - lib/dbcode/sql_file.rb
84
+ - lib/dbcode/tasks/db_code.rake
85
+ - lib/dbcode/version.rb
86
+ homepage: http://github.com/briandunn/dbcode
87
+ licenses:
88
+ - MIT
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.4.5
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: A Database Code Pipeline for ActiveRecord
110
+ test_files: []