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 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: []