active_record-postgres-constraints 0.1.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: 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,9 @@
1
+ # frozen_string_literal: true
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ require 'bundler/gem_tasks'
9
+ task default: :spec
@@ -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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ module ActiveRecord
3
+ module Postgres
4
+ module Constraints
5
+ VERSION = '0.1.1'
6
+ end
7
+ end
8
+ 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: []