active_record-postgres-constraints 0.1.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/MIT-LICENSE +20 -0
- data/README.md +67 -0
- data/Rakefile +9 -0
- data/lib/active_record/postgres/constraints.rb +49 -0
- data/lib/active_record/postgres/constraints/command_recorder.rb +30 -0
- data/lib/active_record/postgres/constraints/postgresql_adapter.rb +27 -0
- data/lib/active_record/postgres/constraints/railtie.rb +33 -0
- data/lib/active_record/postgres/constraints/schema_creation.rb +31 -0
- data/lib/active_record/postgres/constraints/schema_dumper.rb +20 -0
- data/lib/active_record/postgres/constraints/table_definition.rb +16 -0
- data/lib/active_record/postgres/constraints/version.rb +8 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3a19e03721ddaa7c12b76748d4fa59fe093e3c6a
|
4
|
+
data.tar.gz: dd98645605fe1088cd41c333f46479487edfb261
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 056b2a22a6b4eac0db4449b041f448b53133c47d0deecab89b70e6036fc3326089f14128f1f091df9760d131056c50eac8e284fcaced43e6a9a1a048dbb79cc0
|
7
|
+
data.tar.gz: 6f4b979cd7ed8f4065381fb854bf15c82af512692a798be132c2d8a6fb656901fc93330897c84b00b26b692ac4a78dc712e88babdc46bee11a9faa9e95f888be
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2017 Isaac Betesh
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# ActiveRecord::Postgres::Constraints
|
2
|
+
|
3
|
+
From http://edgeguides.rubyonrails.org/active_record_migrations.html#types-of-schema-dumps:
|
4
|
+
|
5
|
+
> There is however a trade-off: db/schema.rb cannot express database
|
6
|
+
specific items such as triggers, stored procedures or check constraints.
|
7
|
+
While in a migration you can execute custom SQL statements, the schema
|
8
|
+
dumper cannot reconstitute those statements from the database. If you are
|
9
|
+
using features like this, then you should set the schema format to :sql.
|
10
|
+
|
11
|
+
No longer is this the case. You can now use the default schema format
|
12
|
+
(:ruby) and still preserve your check constraints.
|
13
|
+
|
14
|
+
At this time, this only supports check constraints for the postgresql ActiveRecord database adapter.
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
#### Add a check constraint
|
19
|
+
Add check constraints to a table in a migration:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
create_table :people do |t|
|
23
|
+
t.string :title
|
24
|
+
t.check_constraint title: ['Mr.', 'Mrs.', 'Dr.']
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
OR
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
add_check_constraint :people, title: ['Mr.', 'Mrs.', 'Dr.']
|
32
|
+
```
|
33
|
+
|
34
|
+
#### Remove a check constraint
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# If you don't need it to be reversible:
|
38
|
+
remove_check_constraint :people
|
39
|
+
|
40
|
+
# If you need it to be reversible (Recommended):
|
41
|
+
remove_check_constraint :people, title: ['Mr.', 'Mrs.', 'Dr.']
|
42
|
+
```
|
43
|
+
|
44
|
+
## Installation
|
45
|
+
Add this line to your application's Gemfile:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
gem 'active_record-postgres-constraints'
|
49
|
+
```
|
50
|
+
|
51
|
+
And then execute:
|
52
|
+
|
53
|
+
```bash
|
54
|
+
$ bundle
|
55
|
+
```
|
56
|
+
|
57
|
+
## Testing
|
58
|
+
```bash
|
59
|
+
$ (cd spec/dummy && bin/rake db:create RAILS_ENV=test) # One time before running tests
|
60
|
+
$ bundle exec rspec # To run tests as often as you'd like
|
61
|
+
```
|
62
|
+
|
63
|
+
## Contributing
|
64
|
+
If you're interested in building support for other database adapters, we welcome your contribution!
|
65
|
+
|
66
|
+
## License
|
67
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'constraints/command_recorder'
|
3
|
+
require_relative 'constraints/postgresql_adapter'
|
4
|
+
require_relative 'constraints/railtie'
|
5
|
+
require_relative 'constraints/schema_creation'
|
6
|
+
require_relative 'constraints/schema_dumper'
|
7
|
+
require_relative 'constraints/table_definition'
|
8
|
+
require_relative 'constraints/version'
|
9
|
+
|
10
|
+
module ActiveRecord
|
11
|
+
module Postgres
|
12
|
+
module Constraints
|
13
|
+
class << self
|
14
|
+
def normalize_conditions(conditions)
|
15
|
+
conditions = [conditions] unless conditions.is_a?(Array)
|
16
|
+
conditions = conditions.map do |condition|
|
17
|
+
if condition.is_a?(Hash)
|
18
|
+
normalize_conditions_hash(condition)
|
19
|
+
else
|
20
|
+
condition
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
return conditions.first if 1 == conditions.length
|
25
|
+
"(#{conditions.join(') AND (')})"
|
26
|
+
end
|
27
|
+
|
28
|
+
def normalize_conditions_hash(hash)
|
29
|
+
hash = hash.reduce([]) do |array, (column, predicate)|
|
30
|
+
predicate = predicate.join("', '") if predicate.is_a?(Array)
|
31
|
+
array << "#{column} IN ('#{predicate}')"
|
32
|
+
end
|
33
|
+
"(#{hash.join(') AND (')})"
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_sql(table, name_or_conditions, conditions = nil)
|
37
|
+
if conditions
|
38
|
+
name = name_or_conditions
|
39
|
+
else
|
40
|
+
name = "#{table}_#{Time.zone.now.nsec}"
|
41
|
+
conditions = name_or_conditions
|
42
|
+
end
|
43
|
+
|
44
|
+
"CONSTRAINT #{name} CHECK (#{normalize_conditions(conditions)})"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ActiveRecord
|
3
|
+
module Postgres
|
4
|
+
module Constraints
|
5
|
+
module CommandRecorder
|
6
|
+
def add_check_constraint(*args, &block)
|
7
|
+
record(:add_check_constraint, args, &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def invert_add_check_constraint(args, &block)
|
11
|
+
[:remove_check_constraint, args, block]
|
12
|
+
end
|
13
|
+
|
14
|
+
def remove_check_constraint(*args, &block)
|
15
|
+
if args.length < 3
|
16
|
+
raise ActiveRecord::IrreversibleMigration,
|
17
|
+
'To make this migration reversible, pass the constraint to '\
|
18
|
+
'remove_check_constraint, i.e. `remove_check_constraint, '\
|
19
|
+
"#{args[0].inspect}, #{args[1].inspect}, 'price > 999'`"
|
20
|
+
end
|
21
|
+
record(:remove_check_constraint, args, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def invert_remove_check_constraint(args, &block)
|
25
|
+
[:add_check_constraint, args, block]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ActiveRecord
|
3
|
+
module Postgres
|
4
|
+
module Constraints
|
5
|
+
module PostgreSQLAdapter
|
6
|
+
def add_check_constraint(table, name_or_conditions, conditions = nil)
|
7
|
+
constraint = Constraints.to_sql(table, name_or_conditions, conditions)
|
8
|
+
execute("ALTER TABLE #{table} ADD #{constraint}")
|
9
|
+
end
|
10
|
+
|
11
|
+
def remove_check_constraint(table, name, _conditions = nil)
|
12
|
+
execute("ALTER TABLE #{table} DROP CONSTRAINT #{name}")
|
13
|
+
end
|
14
|
+
|
15
|
+
def constraints(table)
|
16
|
+
sql = "SELECT conname, consrc FROM pg_constraint
|
17
|
+
JOIN pg_class ON pg_constraint.conrelid = pg_class.oid
|
18
|
+
WHERE
|
19
|
+
pg_constraint.contype = 'c'
|
20
|
+
AND
|
21
|
+
pg_class.relname = '#{table}'".tr("\n", ' ').squeeze(' ')
|
22
|
+
execute sql
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ActiveRecord
|
3
|
+
module Postgres
|
4
|
+
module Constraints
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer 'active_record.postgres.constraints.patch_active_record' do
|
7
|
+
ActiveSupport.on_load(:active_record) do
|
8
|
+
AR_CAS = ::ActiveRecord::ConnectionAdapters
|
9
|
+
|
10
|
+
connection = ActiveRecord::Base.connection
|
11
|
+
using_pg = connection.class.to_s == "#{AR_CAS}::PostgreSQLAdapter"
|
12
|
+
if using_pg
|
13
|
+
Rails.logger.info do
|
14
|
+
'Applying Postgres Constraints patches to ActiveRecord'
|
15
|
+
end
|
16
|
+
AR_CAS::TableDefinition.include TableDefinition
|
17
|
+
AR_CAS::PostgreSQLAdapter.include PostgreSQLAdapter
|
18
|
+
AR_CAS::AbstractAdapter::SchemaCreation.prepend SchemaCreation
|
19
|
+
|
20
|
+
::ActiveRecord::Migration::CommandRecorder.include CommandRecorder
|
21
|
+
::ActiveRecord::SchemaDumper.prepend SchemaDumper
|
22
|
+
else
|
23
|
+
Rails.logger.warn do
|
24
|
+
'Not applying Postgres Constraints patches to ActiveRecord ' \
|
25
|
+
'since the database is not postgres'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ActiveRecord
|
3
|
+
module Postgres
|
4
|
+
module Constraints
|
5
|
+
module SchemaCreation
|
6
|
+
# rubocop:disable Style/MethodName
|
7
|
+
def visit_TableDefinition(o)
|
8
|
+
# rubocop:enable Style/MethodName
|
9
|
+
result = super
|
10
|
+
return result unless o.check_constraints
|
11
|
+
nesting = 0
|
12
|
+
# Find the closing paren of the "CREATE TABLE ( ... )" clause
|
13
|
+
index = result.length.times do |i|
|
14
|
+
token = result[i]
|
15
|
+
nesting, should_break = adjust_nesting(nesting, token)
|
16
|
+
break i if should_break
|
17
|
+
end
|
18
|
+
result[index] = ", #{o.check_constraints.join(', ')})"
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
def adjust_nesting(nesting, token)
|
23
|
+
nesting_was = nesting
|
24
|
+
nesting += 1 if '(' == token
|
25
|
+
nesting -= 1 if ')' == token
|
26
|
+
[nesting, (1 == nesting_was && nesting.zero?)]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ActiveRecord
|
3
|
+
module Postgres
|
4
|
+
module Constraints
|
5
|
+
module SchemaDumper
|
6
|
+
def indexes_in_create(table, stream)
|
7
|
+
super
|
8
|
+
constraints = @connection.constraints(table)
|
9
|
+
return unless constraints.any?
|
10
|
+
constraint_statements = constraints.map do |constraint|
|
11
|
+
name = constraint['conname']
|
12
|
+
conditions = constraint['consrc']
|
13
|
+
" t.check_constraint :#{name}, #{conditions.inspect}"
|
14
|
+
end
|
15
|
+
stream.puts constraint_statements.sort.join("\n")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ActiveRecord
|
3
|
+
module Postgres
|
4
|
+
module Constraints
|
5
|
+
module TableDefinition
|
6
|
+
attr_reader :check_constraints
|
7
|
+
|
8
|
+
def check_constraint(name_or_conditions, conditions = nil)
|
9
|
+
@check_constraints ||= []
|
10
|
+
constraint = Constraints.to_sql(name, name_or_conditions, conditions)
|
11
|
+
check_constraints << constraint
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_record-postgres-constraints
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Isaac Betesh
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.0.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.0.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pg
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
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-rails
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: "\n From http://edgeguides.rubyonrails.org/active_record_migrations.html#types-of-schema-dumps:\n\n
|
70
|
+
\ There is however a trade-off: db/schema.rb cannot express database\n specific
|
71
|
+
items such as triggers, stored procedures or check constraints.\n While in
|
72
|
+
a migration you can execute custom SQL statements, the schema\n dumper cannot
|
73
|
+
reconstitute those statements from the database. If you are\n using features
|
74
|
+
like this, then you should set the schema format to :sql.\n\n No longer is this
|
75
|
+
the case. You can now use the default schema format\n (:ruby) and still preserve
|
76
|
+
your check constraints.\n "
|
77
|
+
email:
|
78
|
+
- ibetesh@on-site.com
|
79
|
+
executables: []
|
80
|
+
extensions: []
|
81
|
+
extra_rdoc_files: []
|
82
|
+
files:
|
83
|
+
- MIT-LICENSE
|
84
|
+
- README.md
|
85
|
+
- Rakefile
|
86
|
+
- lib/active_record/postgres/constraints.rb
|
87
|
+
- lib/active_record/postgres/constraints/command_recorder.rb
|
88
|
+
- lib/active_record/postgres/constraints/postgresql_adapter.rb
|
89
|
+
- lib/active_record/postgres/constraints/railtie.rb
|
90
|
+
- lib/active_record/postgres/constraints/schema_creation.rb
|
91
|
+
- lib/active_record/postgres/constraints/schema_dumper.rb
|
92
|
+
- lib/active_record/postgres/constraints/table_definition.rb
|
93
|
+
- lib/active_record/postgres/constraints/version.rb
|
94
|
+
homepage: https://github.com/on-site/active_record-postgres-constraints
|
95
|
+
licenses:
|
96
|
+
- MIT
|
97
|
+
metadata: {}
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 2.6.10
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: Store your constraints in db/schema.rb
|
118
|
+
test_files: []
|