enum_kit 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0edccc6b488902ea7344f63e24014979d60f2a315c79335fc959c7cfeafb1473
4
- data.tar.gz: 2776e409ad4108dfaf9b504d6cdb3d43cafe3368e9889d05f27635f8240c33d6
3
+ metadata.gz: 6f5949293c0b433b8a014dd718bdb0db6f65b13293bc3c781d51296e13a3023d
4
+ data.tar.gz: 5d8e7e994ec2f47c932fb120e1bb9373d6b9d64ab9380cbba98de58483377004
5
5
  SHA512:
6
- metadata.gz: 7f41dc0fd5399088b5d5cdc02fb2bd48fd0a1c3ff3b4fdb44d53920d9c6d4b12ece952c2d6ee9c3174423bd79d3f96b7babd1e7a107315acb35ea58a6aa8cda5
7
- data.tar.gz: c43cd3f3a197c828d958de2f5374d6a8d98d1ff1d6f381ec8d1265753d081682cca917af8cd220317ca0014313812102bfe6cde7e999913ab161fac480002395
6
+ metadata.gz: 281be07f6004fc567ceb889b1bf63574e08ba8a0e3f250a098b37ba19d9e035718e75993248cc9e4a9d80d1640a5453b71ee4c89848a9f125bae84db16ea046b
7
+ data.tar.gz: 6ecf20f2cf802b611c6a5604dd5940962448b70bb3816668f596f81d6c0b46e5292ebf032eeb562c74cd6ea0a78d17623d39280e6e6b73bf25e4c42f38f90b99
data/.travis.yml CHANGED
@@ -1,6 +1,9 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 2.6.5
4
+ gemfile:
5
+ - travis/gemfiles/5.2.gemfile
6
+ - travis/gemfiles/6.0.gemfile
4
7
  services:
5
8
  - postgresql
6
9
  before_install:
@@ -8,6 +11,13 @@ before_install:
8
11
  - gem install bundler
9
12
  before_script:
10
13
  - psql -c 'CREATE DATABASE enum_kit_test;' -U postgres
14
+ addons:
15
+ postgresql: 10
16
+ apt:
17
+ packages:
18
+ - postgresql-10
19
+ - postgresql-client-10
11
20
  env:
12
21
  global:
13
22
  - DATABASE_URL="postgresql://127.0.0.1:5432/enum_kit_test"
23
+ - RSPEC_DEFAULT_FORMATTER=doc
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # EnumKit
2
2
 
3
+ [![Build Status](https://travis-ci.org/NialtoServices/enum_kit.svg?branch=master)](https://travis-ci.org/NialtoServices/enum_kit)
4
+
3
5
  EnumKit provides native support for PostgreSQL enums in Ruby on Rails projects.
4
6
 
5
7
  ## Installation
data/config.ru CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rubygems"
4
- require "bundler"
3
+ require 'rubygems'
4
+ require 'bundler'
5
5
 
6
6
  Bundler.require :default, :development
7
7
 
data/enum_kit.gemspec CHANGED
@@ -21,8 +21,8 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.metadata['yard.run'] = 'yri'
23
23
 
24
- spec.add_runtime_dependency 'activerecord', '>= 4.0.0'
25
- spec.add_runtime_dependency 'activesupport', '>= 4.0.0'
24
+ spec.add_runtime_dependency 'activerecord', '>= 5.2.0'
25
+ spec.add_runtime_dependency 'activesupport', '>= 5.2.0'
26
26
  spec.add_runtime_dependency 'pg'
27
27
 
28
28
  spec.add_development_dependency 'bundler', '~> 2.0'
@@ -19,7 +19,7 @@ module EnumKit
19
19
  #
20
20
  def prepare_column_options(column)
21
21
  spec = super
22
- spec[:name] = column.sql_type.inspect if column.type == :enum
22
+ spec[:enum_type] = column.sql_type.inspect if column.type == :enum
23
23
  spec
24
24
  end
25
25
  end
@@ -19,7 +19,7 @@ module EnumKit
19
19
  #
20
20
  def prepare_column_options(column)
21
21
  spec = super
22
- spec[:name] = column.sql_type.inspect if column.type == :enum
22
+ spec[:enum_type] = column.sql_type.inspect if column.type == :enum
23
23
  spec
24
24
  end
25
25
  end
@@ -30,14 +30,14 @@ module EnumKit
30
30
  # @return [Hash] The enum types available in the database.
31
31
  #
32
32
  def enums
33
- @enums ||= select_all(ENUM_QUERY.tr("\n", ' ').strip).each_with_object({}) do |row, enums|
33
+ select_all(ENUM_QUERY.tr("\n", ' ').strip).each_with_object({}) do |row, enums|
34
34
  enums[row['typname'].to_sym] = row['values'].split("\t\t")
35
35
  end
36
36
  end
37
37
 
38
38
  # Create a new enum type in the database.
39
39
  #
40
- # @param name [Symbol] The name of the new enum type.
40
+ # @param name [Symbol] The enum's name.
41
41
  # @param values [Array] The enum's acceptable values.
42
42
  #
43
43
  def create_enum(name, values)
@@ -47,27 +47,88 @@ module EnumKit
47
47
  execute "CREATE TYPE #{name} AS ENUM #{EnumKit.sqlize(values)}"
48
48
  end
49
49
 
50
+ # Rename an existing enum type.
51
+ #
52
+ # @param current_name [Symbol] The enum's current name.
53
+ # @param new_name [Symbol] The enum's new name.
54
+ #
55
+ def rename_enum(current_name, new_name)
56
+ current_name = EnumKit.sanitize_name!(current_name)
57
+ new_name = EnumKit.sanitize_name!(new_name)
58
+
59
+ execute "ALTER TYPE #{current_name} RENAME TO #{new_name}"
60
+ end
61
+
50
62
  # Drop an existing enum type from the database.
51
63
  #
52
- # @param name [Symbol] The name of the existing enum type.
64
+ # @param name [Symbol] The enum's name.
53
65
  #
54
66
  def drop_enum(name)
67
+ name = EnumKit.sanitize_name!(name)
68
+
55
69
  execute "DROP TYPE #{name}"
56
70
  end
57
71
 
72
+ # Add a new value to an enum type in the database.
73
+ #
74
+ # Note that you can't specify both :before and :after.
75
+ #
76
+ # @param name [Symbol] The enum's name.
77
+ # @param value [String|Symbol] The value to add.
78
+ # @param after [String|Symbol] An existing value after which the new value should be inserted.
79
+ # @param before [String|Symbol] An existing value before which the new value should be inserted.
80
+ #
81
+ def add_enum_value(name, value, after: nil, before: nil)
82
+ name = EnumKit.sanitize_name!(name)
83
+ value = EnumKit.sanitize_value!(value)
84
+
85
+ statement = "ALTER TYPE #{name} ADD VALUE #{EnumKit.sqlize(value)}"
86
+
87
+ raise ArgumentError, "You can't specify both :before and :after" if before && after
88
+
89
+ statement += " AFTER #{EnumKit.sqlize(EnumKit.sanitize_value!(after))}" if after
90
+ statement += " BEFORE #{EnumKit.sqlize(EnumKit.sanitize_value!(before))}" if before
91
+
92
+ execute(statement)
93
+ end
94
+
95
+ # Rename a value within an enum type in the database.
96
+ #
97
+ # @param name [Symbol] The enum's name.
98
+ # @param current_value [String|Symbol] The enum value's current name.
99
+ # @param new_value [String|Symbol] The enum value's new name.
100
+ #
101
+ def rename_enum_value(name, current_name, new_name)
102
+ ensure_renaming_enum_values_is_supported!
103
+
104
+ name = EnumKit.sanitize_name!(name)
105
+ current_name = EnumKit.sanitize_value!(current_name)
106
+ new_name = EnumKit.sanitize_value!(new_name)
107
+
108
+ execute "ALTER TYPE #{name} RENAME VALUE #{EnumKit.sqlize(current_name)} TO #{EnumKit.sqlize(new_name)}"
109
+ end
110
+
58
111
  # :nodoc:
59
112
  #
60
113
  def migration_keys
61
- super + [:name]
114
+ super + [:enum_type]
62
115
  end
63
116
 
64
117
  # :nodoc:
65
118
  #
66
- def prepare_column_options(column, types)
67
- spec = super(column, types)
68
- spec[:name] = column.cast_type.type.inspect if column.type == :enum
119
+ def prepare_column_options(column)
120
+ spec = super
121
+ spec[:enum_type] = column.sql_type.inspect if column.type == :enum
69
122
  spec
70
123
  end
124
+
125
+ # Raise an exception if the active PostgreSQL version doesn't support renaming enum values.
126
+ #
127
+ def ensure_renaming_enum_values_is_supported!
128
+ return if ActiveRecord::Base.connection.postgresql_version >= 100_000
129
+
130
+ raise NotImplementedError, 'PostgreSQL 10.0+ is required to enable renaming of enum values.'
131
+ end
71
132
  end
72
133
  end
73
134
  end
@@ -12,33 +12,72 @@ module EnumKit
12
12
  # :nodoc:
13
13
  #
14
14
  module CommandRecorder
15
- # :nodoc:
15
+ # Record the creation of an enum type.
16
16
  #
17
17
  def create_enum(*args)
18
18
  record(:create_enum, args)
19
19
  end
20
20
 
21
- # :nodoc:
21
+ # Record the renaming of an enum type.
22
+ #
23
+ def rename_enum(*args)
24
+ record(:rename_enum, args)
25
+ end
26
+
27
+ # Record the deletion of an enum type.
22
28
  #
23
29
  def drop_enum(*args)
24
30
  record(:drop_enum, args)
25
31
  end
26
32
 
27
- # :nodoc:
33
+ # Record the addition of a value to an enum type.
34
+ #
35
+ def add_enum_value(*args)
36
+ record(:add_enum_value, args)
37
+ end
38
+
39
+ # Record the renaming of a value in an enum type.
40
+ #
41
+ def rename_enum_value(*args)
42
+ record(:rename_enum_value, args)
43
+ end
44
+
45
+ # Invert the creation of an enum type by deleting it.
28
46
  #
29
47
  def invert_create_enum(args)
30
48
  record(:drop_enum, args.first)
31
49
  end
32
50
 
33
- # :nodoc:
51
+ # Invert the renaming of an enum by renaming it back to the previous name.
52
+ #
53
+ def invert_rename_enum(args)
54
+ record(:rename_enum, args.reverse)
55
+ end
56
+
57
+ # Invert the deletion of an enum type by creating it.
58
+ #
59
+ # Note that `drop_enum` can only be reversed if given a collection of values to call `create_enum` with as the
60
+ # previously deleted enum values cannot be automatically determined.
34
61
  #
35
62
  def invert_drop_enum(args)
36
63
  unless args.length > 1
37
- raise ::ActiveRecord::IrreversibleMigration, 'drop_enum is only reversible if given an Array of values.'
64
+ raise ActiveRecord::IrreversibleMigration, 'drop_enum is only reversible if given an Array of values.'
38
65
  end
39
66
 
40
67
  record(:create_enum, args)
41
68
  end
69
+
70
+ # Invert the addition of a value to an enum type by removing the value.
71
+ #
72
+ def invert_add_enum_value(_args)
73
+ raise ActiveRecord::IrreversibleMigration, 'add_enum_value is not reversible.'
74
+ end
75
+
76
+ # Invert the renaming of an enum's value by renaming it back to the previous value.
77
+ #
78
+ def invert_rename_enum_value(args)
79
+ record(:rename_enum_value, args[0], args[2], args[1])
80
+ end
42
81
  end
43
82
  end
44
83
  end
@@ -12,19 +12,25 @@ module EnumKit
12
12
  # :nodoc:
13
13
  #
14
14
  def tables(stream)
15
- export_enums(stream)
15
+ enums(stream)
16
16
  super
17
17
  end
18
18
 
19
- # :nodoc:
19
+ # Write `create_enum` statements for each of the enum types created in the database to the specified stream.
20
+ #
21
+ # @param stream [IO] The stream to write the statements into.
20
22
  #
21
- def export_enums(stream)
22
- @connection.enums.each do |name, values|
23
- values = values.map(&:inspect).join(', ')
24
- stream.puts " create_enum #{name.inspect}, [#{values}]"
23
+ def enums(stream)
24
+ return unless @connection.respond_to?(:enums)
25
+
26
+ statements = @connection.enums.map do |name, values|
27
+ " create_enum #{name.inspect}, #{values.inspect}"
25
28
  end
26
29
 
27
- stream.puts if @connection.enums.any?
30
+ return if statements.empty?
31
+
32
+ stream.puts statements.join("\n")
33
+ stream.puts
28
34
  end
29
35
  end
30
36
  end
@@ -15,16 +15,16 @@ module ActiveRecord
15
15
  # Create an enum column with the provided name.
16
16
  #
17
17
  # By default, the enum type will match the name of the column.
18
- # You can change this behaviour by providing the enum type as an option under the `:name` key.
18
+ # You can change this behaviour by providing the enum type as an option under the `:enum_type` key.
19
19
  #
20
20
  # @example Creating a user role.
21
- # t.enum :role, name: :user_role
21
+ # t.enum :role, enum_type: :user_role
22
22
  #
23
- # @param name [String] The name of the enum column.
24
- # @param options [Hash] The options (including the name of the enum type).
23
+ # @param enum_type [String] The name of the enum column.
24
+ # @param options [Hash] The options (including the enum type).
25
25
  #
26
26
  def enum(name, options = {})
27
- column(name, options[:name] || name, options.except(:name))
27
+ column(name, options[:enum_type] || name, options.except(:enum_type))
28
28
  end
29
29
  end
30
30
  end
@@ -15,14 +15,14 @@ module ActiveRecord
15
15
  # :nodoc:
16
16
  #
17
17
  class Enum < Type::Value
18
- # @return [String] The PostgreSQL type for the enum.
18
+ # @return [String] The name of the PostgreSQL type representing the enum.
19
19
  #
20
- attr_reader :name
20
+ attr_reader :enum_type
21
21
 
22
22
  # :nodoc:
23
23
  #
24
24
  def initialize(options = {})
25
- @name = options.delete(:name).to_sym
25
+ @enum_type = options.delete(:enum_type).to_sym
26
26
  super
27
27
  end
28
28
  end
@@ -18,7 +18,7 @@ module ActiveRecord
18
18
  # :nodoc:
19
19
  #
20
20
  def register_enum_type(row)
21
- register row['oid'], ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Enum.new(name: row['typname'])
21
+ register row['oid'], ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Enum.new(enum_type: row['typname'])
22
22
  end
23
23
  end
24
24
  end
@@ -12,12 +12,11 @@ module ActiveRecord
12
12
  # @return [Array] The acceptable values for the enum type associated with the column.
13
13
  #
14
14
  def pg_enum_values(name)
15
- # Determine the PostgreSQL type name for the enum.
16
- type = type_for_attribute(name)
17
- type = type.instance_eval { subtype } if type.is_a?(ActiveRecord::Enum::EnumType)
15
+ # Determine the PostgreSQL type for the enum.
16
+ type = columns_hash[name.to_s].sql_type
18
17
 
19
18
  # Query the PostgreSQL database for the enum's acceptable values.
20
- connection.enums[type.name]
19
+ connection.enums[type.to_sym]
21
20
  end
22
21
 
23
22
  # Define a PostgreSQL enum type.
@@ -31,6 +30,9 @@ module ActiveRecord
31
30
  enum(name => Hash[values])
32
31
 
33
32
  enum = type_for_attribute(name)
33
+
34
+ raise 'Expected an ActiveRecord::Enum::EnumType' unless enum.is_a?(ActiveRecord::Enum::EnumType)
35
+
34
36
  enum.disable_exceptions = options.key?(:exceptions) && !options[:exceptions]
35
37
 
36
38
  nil
@@ -3,5 +3,5 @@
3
3
  module EnumKit
4
4
  # @return [String] The gem's semantic version number.
5
5
  #
6
- VERSION = '0.1.0'
6
+ VERSION = '0.2.1'
7
7
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- RSpec.describe ActiveRecord::Base do
3
+ RSpec.describe ActiveRecord::Base, :unit do
4
4
  describe '.pg_enum' do
5
5
  it 'is defined' do
6
6
  expect(described_class).to respond_to(:pg_enum)
@@ -1,25 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- RSpec.describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
3
+ RSpec.describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter, :unit do
4
4
  subject(:connection) { ActiveRecord::Base.connection }
5
5
 
6
- describe '#create_enum' do
7
- after { connection.execute 'DROP TYPE IF EXISTS an_enum' }
6
+ before do
7
+ # Sanity check to ensure ActiveRecord is configured to use a PostgreSQL database.
8
+ expect(connection).to be_a(described_class)
9
+ end
8
10
 
9
- it 'is defined' do
10
- expect(connection).to respond_to(:create_enum)
11
+ %i[create_enum rename_enum drop_enum add_enum_value rename_enum_value].each do |method|
12
+ define_method(method) do |*args, &block|
13
+ connection.send(method, *args, &block).result_status
11
14
  end
15
+ end
16
+
17
+ describe '#create_enum' do
18
+ after { connection.execute 'DROP TYPE IF EXISTS sizes' }
12
19
 
13
20
  context 'when called with valid arguments' do
14
- subject { connection.create_enum(:an_enum, [:first_value, 'second value']) }
21
+ subject { create_enum(:sizes, [:small, :medium, :large, 'extra large']) }
15
22
 
16
23
  it 'creates an enum' do
17
- expect(subject.result_status).to eq(PG::PGRES_COMMAND_OK)
24
+ expect(subject).to eq(PG::PGRES_COMMAND_OK)
25
+ expect(connection.enums[:sizes]).to eq(['small', 'medium', 'large', 'extra large'])
18
26
  end
19
27
  end
20
28
 
21
29
  context 'when called with a malformed name' do
22
- subject { connection.create_enum('an enum', [:first_value, 'second value']) }
30
+ subject { create_enum('bad enum name', [:small, :medium, :large, 'extra large']) }
23
31
 
24
32
  it 'raises an ArgumentError' do
25
33
  expect { subject }.to raise_exception(ArgumentError)
@@ -27,7 +35,7 @@ RSpec.describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
27
35
  end
28
36
 
29
37
  context 'when called with malformed values' do
30
- subject { connection.create_enum(:an_enum, [:good_value, 'bad$value']) }
38
+ subject { create_enum(:sizes, [:small, :medium, :large, 'extra large', 'extra+extra+large']) }
31
39
 
32
40
  it 'raises an ArgumentError' do
33
41
  expect { subject }.to raise_exception(ArgumentError)
@@ -35,24 +43,210 @@ RSpec.describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
35
43
  end
36
44
  end
37
45
 
38
- describe '#drop_enum' do
39
- before { connection.execute "CREATE TYPE an_enum AS ENUM ('first', 'second')" }
40
- after { connection.execute 'DROP TYPE IF EXISTS an_enum' }
46
+ describe '#rename_enum' do
47
+ before do
48
+ connection.execute "CREATE TYPE sizes AS ENUM ('small', 'medium', 'large', 'extra large')"
49
+ end
50
+
51
+ after do
52
+ connection.execute 'DROP TYPE IF EXISTS lengths'
53
+ connection.execute 'DROP TYPE IF EXISTS sizes'
54
+ end
55
+
56
+ context 'when called with valid arguments' do
57
+ subject { rename_enum(:sizes, :lengths) }
41
58
 
42
- it 'is defined' do
43
- expect(connection).to respond_to(:drop_enum)
59
+ it 'renames the enum' do
60
+ expect(subject).to eq(PG::PGRES_COMMAND_OK)
61
+ expect(connection.enums).to have_key(:lengths)
62
+ expect(connection.enums).not_to have_key(:sizes)
63
+ end
44
64
  end
45
65
 
46
- context 'when called with an existing enum' do
47
- subject { connection.drop_enum(:an_enum) }
66
+ context 'when called with a non-existent enum name' do
67
+ subject { rename_enum(:non_existent_enum, :lengths) }
68
+
69
+ it 'raises ActiveRecord::StatementInvalid' do
70
+ expect { subject }.to raise_exception(ActiveRecord::StatementInvalid)
71
+ end
72
+ end
73
+
74
+ context 'when called with a malformed current name' do
75
+ subject { rename_enum('bad enum name', :lengths) }
76
+
77
+ it 'raises an ArgumentError' do
78
+ expect { subject }.to raise_exception(ArgumentError)
79
+ end
80
+ end
81
+
82
+ context 'when called with a malformed new name' do
83
+ subject { rename_enum(:sizes, 'bad enum name') }
84
+
85
+ it 'raises an ArgumentError' do
86
+ expect { subject }.to raise_exception(ArgumentError)
87
+ end
88
+ end
89
+ end
90
+
91
+ describe '#drop_enum' do
92
+ before { connection.execute "CREATE TYPE sizes AS ENUM ('small', 'medium', 'large', 'extra large')" }
93
+ after { connection.execute 'DROP TYPE IF EXISTS sizes' }
94
+
95
+ context 'when called with valid arguments' do
96
+ subject { drop_enum(:sizes) }
48
97
 
49
98
  it 'drops the enum' do
50
- expect(subject.result_status).to eq(PG::PGRES_COMMAND_OK)
99
+ expect(subject).to eq(PG::PGRES_COMMAND_OK)
100
+ expect(connection.enums).not_to have_key(:sizes)
101
+ end
102
+ end
103
+
104
+ context 'when called with a non-existent enum name' do
105
+ subject { drop_enum(:non_existent_enum) }
106
+
107
+ it 'raises ActiveRecord::StatementInvalid' do
108
+ expect { subject }.to raise_exception(ActiveRecord::StatementInvalid)
109
+ end
110
+ end
111
+
112
+ context 'when called with a malformed name' do
113
+ subject { drop_enum('bad enum name') }
114
+
115
+ it 'raises an ArgumentError' do
116
+ expect { subject }.to raise_exception(ArgumentError)
117
+ end
118
+ end
119
+ end
120
+
121
+ describe '#add_enum_value' do
122
+ before { connection.execute "CREATE TYPE sizes AS ENUM ('small', 'large', 'extra large')" }
123
+ after { connection.execute 'DROP TYPE IF EXISTS sizes' }
124
+
125
+ context 'when called with valid arguments' do
126
+ subject { add_enum_value(:sizes, 'extra extra large') }
127
+
128
+ it 'appends the value' do
129
+ expect(subject).to eq(PG::PGRES_COMMAND_OK)
130
+ expect(connection.enums[:sizes]).to eq(['small', 'large', 'extra large', 'extra extra large'])
131
+ end
132
+
133
+ context 'with :before' do
134
+ subject { add_enum_value(:sizes, 'extra small', before: :small) }
135
+
136
+ it 'inserts the value at the correct position' do
137
+ expect(subject).to eq(PG::PGRES_COMMAND_OK)
138
+ expect(connection.enums[:sizes]).to eq(['extra small', 'small', 'large', 'extra large'])
139
+ end
140
+ end
141
+
142
+ context 'with :after' do
143
+ subject { add_enum_value(:sizes, :medium, after: :small) }
144
+
145
+ it 'inserts the value at the correct position' do
146
+ expect(subject).to eq(PG::PGRES_COMMAND_OK)
147
+ expect(connection.enums[:sizes]).to eq(['small', 'medium', 'large', 'extra large'])
148
+ end
149
+ end
150
+
151
+ context 'with :before and :after' do
152
+ subject { add_enum_value(:sizes, :medium, before: :large, after: :small) }
153
+
154
+ it 'raises an ArgumentError' do
155
+ expect { subject }.to raise_exception(ArgumentError)
156
+ end
157
+ end
158
+ end
159
+
160
+ context 'when called with a non-existent enum name' do
161
+ subject { add_enum_value(:non_existent_enum, :new_value) }
162
+
163
+ it 'raises ActiveRecord::StatementInvalid' do
164
+ expect { subject }.to raise_exception(ActiveRecord::StatementInvalid)
165
+ end
166
+ end
167
+
168
+ context 'when called with a malformed name' do
169
+ subject { add_enum_value('bad enum name', :new_value) }
170
+
171
+ it 'raises an ArgumentError' do
172
+ expect { subject }.to raise_exception(ArgumentError)
173
+ end
174
+ end
175
+ end
176
+
177
+ describe '#rename_enum_value' do
178
+ before { connection.execute "CREATE TYPE sizes AS ENUM ('small', 'medium', 'large', 'extra large')" }
179
+ after { connection.execute 'DROP TYPE IF EXISTS sizes' }
180
+
181
+ context 'when called with valid arguments' do
182
+ subject { rename_enum_value(:sizes, 'extra large', :extra_large) }
183
+
184
+ it 'renames the value' do
185
+ expect(subject).to eq(PG::PGRES_COMMAND_OK)
186
+ expect(connection.enums[:sizes]).to eq(%w[small medium large extra_large])
187
+ end
188
+
189
+ context 'when using PostgreSQL < 10.0' do
190
+ before do
191
+ allow(connection).to receive(:postgresql_version).and_return(99_999)
192
+ end
193
+
194
+ it 'raises a NotImplementedError' do
195
+ expect { subject }.to raise_exception(NotImplementedError)
196
+ end
197
+ end
198
+ end
199
+
200
+ context 'when called with a non-existent enum name' do
201
+ subject { rename_enum_value(:non_existent_enum, :previous_value, :new_value) }
202
+
203
+ it 'raises ActiveRecord::StatementInvalid' do
204
+ expect { subject }.to raise_exception(ActiveRecord::StatementInvalid)
205
+ end
206
+ end
207
+
208
+ context 'when called with a non-existent enum value' do
209
+ subject { rename_enum_value(:sizes, 'extra extra large', :extra_extra_large) }
210
+
211
+ it 'raises ActiveRecord::StatementInvalid' do
212
+ expect { subject }.to raise_exception(ActiveRecord::StatementInvalid)
213
+ end
214
+ end
215
+
216
+ context 'when called with a malformed name' do
217
+ subject { rename_enum_value('bad enum name', 'extra large', :extra_large) }
218
+
219
+ it 'raises an ArgumentError' do
220
+ expect { subject }.to raise_exception(ArgumentError)
221
+ end
222
+ end
223
+
224
+ context 'when called with a malformed current value' do
225
+ subject { rename_enum_value(:sizes, 'extra-large', :extra_large) }
226
+
227
+ it 'raises an ArgumentError' do
228
+ expect { subject }.to raise_exception(ArgumentError)
229
+ end
230
+ end
231
+
232
+ context 'when called with a malformed new value' do
233
+ subject { rename_enum_value(:sizes, 'extra large', 'extra-large') }
234
+
235
+ it 'raises an ArgumentError' do
236
+ expect { subject }.to raise_exception(ArgumentError)
237
+ end
238
+ end
239
+
240
+ context 'when called with an equivalent current value and new value' do
241
+ subject { rename_enum_value(:sizes, 'extra large', 'extra large') }
242
+
243
+ it 'raises ActiveRecord::StatementInvalid' do
244
+ expect { subject }.to raise_exception(ActiveRecord::StatementInvalid)
51
245
  end
52
246
  end
53
247
 
54
- context 'when called with a non-existent enum' do
55
- subject { connection.drop_enum(:non_existent_enum) }
248
+ context 'when called with a new value matching an existing value' do
249
+ subject { rename_enum_value(:sizes, 'extra large', :large) }
56
250
 
57
251
  it 'raises ActiveRecord::StatementInvalid' do
58
252
  expect { subject }.to raise_exception(ActiveRecord::StatementInvalid)
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe ActiveRecord::SchemaDumper, :unit do
4
+ let(:connection) { ActiveRecord::Base.connection }
5
+ let(:stream) { StringIO.new }
6
+
7
+ subject do
8
+ options = {
9
+ table_name_prefix: ActiveRecord::Base.table_name_prefix,
10
+ table_name_suffix: ActiveRecord::Base.table_name_suffix
11
+ }
12
+
13
+ described_class.send(:new, connection, options)
14
+ end
15
+
16
+ describe '#enums' do
17
+ let(:result) do
18
+ subject.enums(stream)
19
+ stream.rewind
20
+ stream.read
21
+ end
22
+
23
+ context 'when the database has enums' do
24
+ before do
25
+ expect(connection).to receive(:enums).and_return(color: %w[red green blue], size: %w[small medium large])
26
+ end
27
+
28
+ it 'generates matching `create_enum` statements' do
29
+ expect(result).to include('create_enum :color, ["red", "green", "blue"]')
30
+ expect(result).to include('create_enum :size, ["small", "medium", "large"]')
31
+ end
32
+ end
33
+
34
+ context 'when the database has no enums' do
35
+ before do
36
+ expect(connection).to receive(:enums).and_return({})
37
+ end
38
+
39
+ it 'generates nothing' do
40
+ expect(result).to be_empty
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '#tables' do
46
+ it 'invokes #enums' do
47
+ expect(subject).to receive(:enums).with(stream).once
48
+ subject.tables(stream)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'ActiveRecord Schema', :integration do
4
+ let(:connection) { ActiveRecord::Base.connection }
5
+ let(:stream) { StringIO.new }
6
+
7
+ subject do
8
+ ActiveRecord::SchemaDumper.dump(connection, stream)
9
+ stream.rewind
10
+ stream.read.split("\n").map { |line| line.gsub(/\s+/, ' ').strip }
11
+ end
12
+
13
+ it 'includes `create_enum` statements' do
14
+ expect(subject).to include('create_enum :shirt_size, ["small", "medium", "large"]')
15
+ end
16
+
17
+ it 'includes `t.enum` statements' do
18
+ expect(subject).to include('t.enum "size", enum_type: "shirt_size"')
19
+ end
20
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- RSpec.describe ActiveRecord::Validations::PgEnumValidator do
3
+ RSpec.describe ActiveRecord::Validations::PgEnumValidator, :unit do
4
4
  subject { Shirt.create(name: 'Plain Shirt', size: :small) }
5
5
 
6
6
  it 'permits known values' do
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  ActiveRecord::Schema.define do
4
- create_enum :shirt_size, %w[small medium large]
4
+ create_enum :shirt_size, %w[small medium large]
5
5
 
6
6
  create_table :shirts do |t|
7
7
  t.string :name
8
- t.enum :size, name: :shirt_size
8
+ t.enum :size, enum_type: :shirt_size
9
9
  end
10
10
  end
data/spec/spec_helper.rb CHANGED
@@ -70,11 +70,14 @@ RSpec.configure do |config|
70
70
  # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
71
71
  config.disable_monkey_patching!
72
72
 
73
- # Many RSpec users commonly either run the entire suite or an individual file, and it's useful to allow more verbose
74
- # output when running an individual spec file.
75
- if config.files_to_run.one?
76
- # Use the documentation formatter for detailed output, unless a formatter has already been configured (e.g. via a
77
- # command-line flag).
73
+ # Retrieve the default formatter from the current environment.
74
+ default_formatter = ENV['RSPEC_DEFAULT_FORMATTER']
75
+
76
+ if default_formatter.is_a?(String) && !default_formatter.empty?
77
+ config.default_formatter = default_formatter
78
+ elsif config.files_to_run.one?
79
+ # Use the documentation formatter for detailed output when running an individual spec file, unless a formatter has
80
+ # already been configured (e.g. via a command-line flag or using the RSPEC_DEFAULT_FORMAT environment variable).
78
81
  config.default_formatter = 'doc'
79
82
  end
80
83
 
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec path: '../../'
6
+
7
+ gem 'activerecord', '~> 5.2.0'
8
+ gem 'activesupport', '~> 5.2.0'
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec path: '../../'
6
+
7
+ gem 'activerecord', '~> 6.0.0'
8
+ gem 'activesupport', '~> 6.0.0'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enum_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nialto Services
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-29 00:00:00.000000000 Z
11
+ date: 2019-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 4.0.0
19
+ version: 5.2.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 4.0.0
26
+ version: 5.2.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 4.0.0
33
+ version: 5.2.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 4.0.0
40
+ version: 5.2.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: pg
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -186,6 +186,8 @@ files:
186
186
  - lib/enum_kit/helpers.rb
187
187
  - spec/active_record/base_spec.rb
188
188
  - spec/active_record/connection_adapters/postgresql_adapter_spec.rb
189
+ - spec/active_record/schema_dumper_spec.rb
190
+ - spec/active_record/schema_spec.rb
189
191
  - spec/active_record/validations/pg_enum_validator_spec.rb
190
192
  - spec/enum_kit/constants_spec.rb
191
193
  - spec/enum_kit/helpers_spec.rb
@@ -194,6 +196,8 @@ files:
194
196
  - spec/internal/db/schema.rb
195
197
  - spec/internal/log/.gitignore
196
198
  - spec/spec_helper.rb
199
+ - travis/gemfiles/5.2.gemfile
200
+ - travis/gemfiles/6.0.gemfile
197
201
  homepage: https://github.com/nialtoservices/enum_kit
198
202
  licenses:
199
203
  - MIT
@@ -221,6 +225,8 @@ summary: Native PostgreSQL enum support for Ruby on Rails.
221
225
  test_files:
222
226
  - spec/active_record/base_spec.rb
223
227
  - spec/active_record/connection_adapters/postgresql_adapter_spec.rb
228
+ - spec/active_record/schema_dumper_spec.rb
229
+ - spec/active_record/schema_spec.rb
224
230
  - spec/active_record/validations/pg_enum_validator_spec.rb
225
231
  - spec/enum_kit/constants_spec.rb
226
232
  - spec/enum_kit/helpers_spec.rb