enum_kit 0.1.0 → 0.2.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 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