straitjacket 0.5.0

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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Kyle Maxwell
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ = straitjacket
2
+
3
+ Constraint creation for PostgreSQL.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Kyle Maxwell. See LICENSE for details.
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "straitjacket"
8
+ gem.summary = %Q{PostgreSQL constraints in Ruby}
9
+ gem.description = %Q{PostgreSQL constraints in Ruby}
10
+ gem.email = "kyle@kylemaxwell.com"
11
+ gem.homepage = "http://github.com/fizx/straitjacket"
12
+ gem.authors = ["Kyle Maxwell"]
13
+ gem.add_dependency "pg", ">= 0.8"
14
+ gem.add_development_dependency "activerecord", ">= 2.3.8"
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
31
+ end
32
+
33
+ task :spec => :check_dependencies
34
+
35
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "straitjacket #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
46
+
47
+ namespace :spec do
48
+ task :setup do
49
+ require "rubygems"
50
+ require "active_record"
51
+ require "yaml"
52
+ config = YAML.load(File.read("spec/database.yml"))
53
+ ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
54
+ ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding))
55
+ ActiveRecord::Base.establish_connection(config)
56
+
57
+ end
58
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
@@ -0,0 +1,131 @@
1
+ class Straitjacket
2
+ class Error < ::RuntimeError
3
+ end
4
+
5
+ class Proxy
6
+
7
+ def initialize(inner, table)
8
+ @inner = inner
9
+ @table = table
10
+ end
11
+
12
+ def method_missing(method)
13
+ @name = method
14
+ self
15
+ end
16
+
17
+ def deprecated(*names)
18
+ (names << @name).each do |name|
19
+ add_constraint DeprecatedConstraint.new(@table, name)
20
+ end
21
+ end
22
+
23
+ def check(statement, options = {})
24
+ add_constraint CheckConstraint.new(@table, statement, named(options))
25
+ end
26
+
27
+ def foreign_key(column, options = {})
28
+ add_constraint ForeignKeyConstraint.new(@table, nil, named(options).merge(:column => column))
29
+ end
30
+
31
+ private
32
+ def add_constraint(constraint)
33
+ if @inner.constraints.any?{|c| c.name == constraint.name}
34
+ raise Error, "#{c.name} already exists"
35
+ else
36
+ @inner.constraints << constraint
37
+ end
38
+ end
39
+
40
+ def named(hash)
41
+ if @name
42
+ hash = hash.dup
43
+ hash[:name] = @name
44
+ @name = nil
45
+ end
46
+ hash
47
+ end
48
+ end
49
+
50
+ attr_reader :constraints
51
+
52
+ def initialize(&block)
53
+ @constraints = []
54
+ @names = {}
55
+ instance_eval(&block) if block
56
+ end
57
+
58
+ def apply(conn)
59
+ constraints.map{|c| c.apply(conn) }
60
+ end
61
+
62
+ def on(table, &block)
63
+ proxy = Proxy.new(self, table)
64
+ proxy.instance_eval(&block) if block
65
+ proxy
66
+ end
67
+
68
+ class Constraint
69
+ attr_accessor :name, :table, :sql, :content, :options, :column
70
+
71
+ def initialize(table, content, options)
72
+ @column = options[:column]
73
+ @name = (options[:name] || default_name(table, options[:column])).to_s
74
+ @table = table
75
+ @content = content
76
+ @options = options
77
+ end
78
+
79
+ def apply(conn)
80
+ conn.exec(sql)
81
+ rescue PGError => e
82
+ if e.message =~ /already exists/
83
+ conn.exec(%[ALTER TABLE "#{table}" DROP CONSTRAINT "#{name}"])
84
+ retry
85
+ else
86
+ raise
87
+ end
88
+ end
89
+
90
+ def sql; raise "abstract"; end
91
+
92
+ private
93
+ def default_name(table, column)
94
+ [table, column].compact.join("_")
95
+ end
96
+ end
97
+
98
+ class DeprecatedConstraint < Constraint
99
+ def initialize(table, name)
100
+ @table = table
101
+ @name = name
102
+ end
103
+
104
+ def sql
105
+ %[ALTER TABLE "#{table}" DROP CONSTRAINT "#{name}"]
106
+ end
107
+
108
+ def apply(conn)
109
+ conn.exec(sql)
110
+ rescue
111
+ false
112
+ end
113
+ end
114
+
115
+ class ForeignKeyConstraint < Constraint
116
+ def sql
117
+ more = ""
118
+ if options[:references]
119
+ on = options[:on] ? %[("#{options[:on]}")] : ""
120
+ more = %[REFERENCES "#{options[:references]}"#{on}]
121
+ end
122
+ %[ALTER TABLE "#{table}" ADD CONSTRAINT "#{name}" FOREIGN KEY ("#{column}") #{more} MATCH FULL]
123
+ end
124
+ end
125
+
126
+ class CheckConstraint < Constraint
127
+ def sql
128
+ %[ALTER TABLE "#{table}" ADD CONSTRAINT "#{name}" CHECK (#{@content})]
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,4 @@
1
+ adapter: postgresql
2
+ database: straightjacket_test
3
+ encoding: utf8
4
+ template: template0
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,39 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'straitjacket'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+ require "rubygems"
7
+ require "active_record"
8
+ require "yaml"
9
+
10
+ Spec::Runner.configure do |config|
11
+ cfg = YAML.load(File.read("#{File.dirname(__FILE__)}/database.yml"))
12
+ ActiveRecord::Base.establish_connection(cfg)
13
+ $conn = ActiveRecord::Base.connection.raw_connection
14
+
15
+ class User < ActiveRecord::Base
16
+ belongs_to :dog
17
+ end
18
+
19
+ class Dog < ActiveRecord::Base
20
+ has_many :users
21
+ end
22
+ end
23
+
24
+ def rebuild_sql
25
+ $conn.exec <<-SQL
26
+ DROP TABLE IF EXISTS users;
27
+ DROP TABLE IF EXISTS dogs;
28
+ CREATE TABLE users (
29
+ id serial PRIMARY KEY,
30
+ dog_id INT NOT NULL,
31
+ name varchar(255)
32
+ );
33
+
34
+ CREATE TABLE dogs (
35
+ id serial PRIMARY KEY,
36
+ name varchar(255)
37
+ );
38
+ SQL
39
+ end
@@ -0,0 +1,111 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ # Note these tests are stateful in the db.
4
+ rebuild_sql
5
+ describe "Straitjacket" do
6
+ context "with a sample" do
7
+ before do
8
+ @jacket = Straitjacket.new do
9
+ on :users do
10
+ name_gt_1.check "LENGTH(name) > 1"
11
+ dog1.foreign_key :dog_id, :references => :dogs, :on => :id
12
+ deprecated :foo
13
+ another.deprecated
14
+ end
15
+ end
16
+ end
17
+
18
+ it "should have correct number of constraints" do
19
+ @jacket.constraints.length.should == 5
20
+ end
21
+
22
+ it "should have unique names" do
23
+ proc {
24
+ @jacket.on(:users).name_gt_1.check("something else")
25
+ }.should raise_error(Straitjacket::Error)
26
+ end
27
+
28
+ describe "#apply" do
29
+ it "should apply all" do
30
+ conn = mock
31
+ @jacket.constraints.each do |c|
32
+ c.should_receive(:apply).with(conn)
33
+ end
34
+ @jacket.apply(conn)
35
+ end
36
+ end
37
+
38
+ context "deprecated" do
39
+ it "should remove a key" do
40
+ conn = mock
41
+ conn.should_receive(:exec).with(%[ALTER TABLE \"users\" DROP CONSTRAINT \"another\"])
42
+ @jacket.constraints.last.apply(conn)
43
+ end
44
+ end
45
+
46
+ context "foreign key" do
47
+ before do
48
+ @key = @jacket.constraints[1]
49
+ end
50
+
51
+ it "should generate sql" do
52
+ @key.sql.should == %[ALTER TABLE "users" ADD CONSTRAINT "dog1" FOREIGN KEY ("dog_id") REFERENCES "dogs"("id") MATCH FULL]
53
+ end
54
+
55
+ it "should be able to apply sql" do
56
+ @key.apply($conn)
57
+ end
58
+
59
+ it "should be able to reapply sql" do
60
+ @key.apply($conn)
61
+ end
62
+
63
+ end
64
+
65
+ context "check constraint" do
66
+ before do
67
+ @first = @jacket.constraints.first
68
+ end
69
+
70
+ it "should be named" do
71
+ rebuild_sql
72
+ @first.name.should == "name_gt_1"
73
+ end
74
+
75
+ it "should be a check" do
76
+ @first.should be_a(Straitjacket::CheckConstraint)
77
+ end
78
+
79
+ it "should generate sql" do
80
+ @first.sql.should == %[ALTER TABLE "users" ADD CONSTRAINT "name_gt_1" CHECK (LENGTH(name) > 1)]
81
+ end
82
+
83
+ it "should be able to apply sql" do
84
+ @first.apply($conn)
85
+ end
86
+
87
+ it "should be enforced" do
88
+ User.create! :name => "Joe", :dog_id => 1
89
+ proc {
90
+ User.create! :name => "e", :dog_id => 1
91
+ }.should raise_error(ActiveRecord::StatementInvalid)
92
+ end
93
+
94
+ it "should not be applyable to an invalid db" do
95
+ @first.content = "LENGTH(name) > 10"
96
+ proc {
97
+ @first.apply($conn)
98
+ }.should raise_error(PGError)
99
+ end
100
+
101
+ it "should be reapplyable/adjustable" do
102
+ User.delete_all
103
+ @first.content = "LENGTH(name) > 10"
104
+ @first.apply($conn)
105
+ proc {
106
+ User.create! :name => "Johnny", :dog_id => 1
107
+ }.should raise_error(ActiveRecord::StatementInvalid)
108
+ end
109
+ end
110
+ end
111
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: straitjacket
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 5
8
+ - 0
9
+ version: 0.5.0
10
+ platform: ruby
11
+ authors:
12
+ - Kyle Maxwell
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-09-25 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: pg
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 8
30
+ version: "0.8"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: activerecord
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ segments:
41
+ - 2
42
+ - 3
43
+ - 8
44
+ version: 2.3.8
45
+ type: :development
46
+ version_requirements: *id002
47
+ description: PostgreSQL constraints in Ruby
48
+ email: kyle@kylemaxwell.com
49
+ executables: []
50
+
51
+ extensions: []
52
+
53
+ extra_rdoc_files:
54
+ - LICENSE
55
+ - README.rdoc
56
+ files:
57
+ - .document
58
+ - .gitignore
59
+ - LICENSE
60
+ - README.rdoc
61
+ - Rakefile
62
+ - VERSION
63
+ - lib/straitjacket.rb
64
+ - spec/database.yml
65
+ - spec/spec.opts
66
+ - spec/spec_helper.rb
67
+ - spec/straitjacket_spec.rb
68
+ has_rdoc: true
69
+ homepage: http://github.com/fizx/straitjacket
70
+ licenses: []
71
+
72
+ post_install_message:
73
+ rdoc_options:
74
+ - --charset=UTF-8
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ requirements: []
92
+
93
+ rubyforge_project:
94
+ rubygems_version: 1.3.6
95
+ signing_key:
96
+ specification_version: 3
97
+ summary: PostgreSQL constraints in Ruby
98
+ test_files:
99
+ - spec/spec_helper.rb
100
+ - spec/straitjacket_spec.rb