From 89ef76e135d9bda70f9dc14feff2c89fa90f2187 Mon Sep 17 00:00:00 2001 From: Noel Rappin Date: Wed, 31 Jan 2024 14:27:58 -0600 Subject: [PATCH] Rails 7 support (#33) * First pass * Update to support Rails 7 * Clean up version testing * Update dependency location * Update to support multiple Rails versions * Update tests.yml * Update abstract_mysql.rb --- .env.local | 1 + .github/workflows/tests.yml | 6 +-- .gitignore | 1 + Appraisals | 15 +++--- Dockerfile | 3 +- Gemfile | 2 +- Gemfile.lock | 52 +++++++++++-------- README.md | 6 +++ docker-compose.yml | 3 +- gemfiles/rails_5.2.gemfile | 7 --- gemfiles/rails_6.0.gemfile | 7 --- gemfiles/rails_6.1.gemfile | 2 +- gemfiles/rails_7.0.gemfile | 2 +- .../{rails_5.1.gemfile => rails_7.1.gemfile} | 2 +- .../connection_adapters/abstract_mysql.rb | 47 +++++++++++++---- lib/sql_enum.rb | 4 ++ lib/sql_enum/class_methods.rb | 14 +++-- lib/sql_enum/version.rb | 2 +- spec/spec_helper.rb | 13 +++++ spec/sql_enum_spec.rb | 4 +- spec/support/macros/define_constant.rb | 12 ----- sql_enum.gemspec | 5 +- 22 files changed, 119 insertions(+), 91 deletions(-) create mode 100644 .env.local delete mode 100644 gemfiles/rails_5.2.gemfile delete mode 100644 gemfiles/rails_6.0.gemfile rename gemfiles/{rails_5.1.gemfile => rails_7.1.gemfile} (79%) diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..c7b247f --- /dev/null +++ b/.env.local @@ -0,0 +1 @@ +DATABASE_URL="mysql2://root:@mysql/" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2644d1f..3de5d4b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,14 +10,14 @@ jobs: strategy: matrix: ruby: - - '2.7' - '3.0' - '3.1' - '3.2' + - '3.3' rails: - - '6.0' - '6.1' - + - '7.0' + - '7.1' runs-on: ubuntu-latest name: RSpec (Rails ${{ matrix.rails }}) (Ruby ${{ matrix.ruby }}) diff --git a/.gitignore b/.gitignore index 8eef8f5..eb10a5b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ .rspec_status gemfiles/*.lock +.env.local diff --git a/Appraisals b/Appraisals index 40d06bb..b85fe68 100644 --- a/Appraisals +++ b/Appraisals @@ -1,12 +1,11 @@ -appraise "rails-6.0" do - gem "rails", "~> 6.0.0" +appraise "rails-6.1" do + gem "rails", "~> 6.1" end -appraise "rails-6.1" do - gem "rails", "~> 6.1.0" +appraise "rails-7.0" do + gem "rails", "~> 7.0" end -# Rails 7.0 doesn't work yet -# appraise "rails-7.0" do -# gem "rails", "~> 7.0" -# end \ No newline at end of file +appraise "rails-7.1" do + gem "rails", "~> 7.1" +end diff --git a/Dockerfile b/Dockerfile index 52e72de..fb8c4cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,4 +6,5 @@ RUN mkdir -p lib/sql_enum gemfiles COPY lib/sql_enum/version.rb ./lib/sql_enum/ COPY gemfiles/*.gemfile gemfiles/ COPY sql_enum.gemspec Gemfile Gemfile.lock Appraisals ./ -RUN bundle install && exec appraisal install +RUN bundle install && \ + bundle exec appraisal install diff --git a/Gemfile b/Gemfile index 21eeb1a..599a997 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source "https://rubygems.org" -git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } # Specify your gem's dependencies in sql_enum.gemspec gemspec diff --git a/Gemfile.lock b/Gemfile.lock index f220911..ce53fb0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,43 +1,48 @@ PATH remote: . specs: - sql_enum (0.4.0) - activerecord (>= 6.0, < 7.0) - activesupport (>= 6.0, < 7.0) + sql_enum (1.0.0) + activerecord (>= 6.1.0) + activesupport (>= 6.1.0) mysql2 GEM remote: https://rubygems.org/ specs: - activemodel (6.1.7.6) - activesupport (= 6.1.7.6) - activerecord (6.1.7.6) - activemodel (= 6.1.7.6) - activesupport (= 6.1.7.6) - activesupport (6.1.7.6) + activemodel (7.0.8) + activesupport (= 7.0.8) + activerecord (7.0.8) + activemodel (= 7.0.8) + activesupport (= 7.0.8) + activesupport (7.0.8) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) appraisal (2.5.0) bundler rake thor (>= 0.14.0) - concurrent-ruby (1.2.2) - debug (1.8.0) - irb (>= 1.5.0) - reline (>= 0.3.1) + awesome_print (1.9.2) + concurrent-ruby (1.2.3) + debug (1.9.1) + irb (~> 1.10) + reline (>= 0.3.8) diff-lcs (1.5.0) i18n (1.14.1) concurrent-ruby (~> 1.0) - io-console (0.6.0) - irb (1.7.4) - reline (>= 0.3.6) - minitest (5.19.0) + io-console (0.7.2) + irb (1.11.1) + rdoc + reline (>= 0.4.2) + minitest (5.21.2) mysql2 (0.5.5) - rake (13.0.6) - reline (0.3.8) + psych (5.1.2) + stringio + rake (13.1.0) + rdoc (6.6.2) + psych (>= 4.0.0) + reline (0.4.2) io-console (~> 0.5) rspec (3.12.0) rspec-core (~> 3.12.0) @@ -52,10 +57,10 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) rspec-support (3.12.1) - thor (1.2.2) + stringio (3.1.0) + thor (1.3.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - zeitwerk (2.6.11) PLATFORMS ruby @@ -63,6 +68,7 @@ PLATFORMS DEPENDENCIES appraisal + awesome_print bundler debug rake (~> 13.0) @@ -70,4 +76,4 @@ DEPENDENCIES sql_enum! BUNDLED WITH - 2.2.30 + 2.5.5 diff --git a/README.md b/README.md index 5c702be..614eb76 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ Enables usage of native sql enums with ActiveRecord +## NOTE + +Version 1.0 of this is compatible with Rails 7 and above. + +For Rails versions below Rails 7, use version 0.4 + ## Installation Add this line to your application's Gemfile: diff --git a/docker-compose.yml b/docker-compose.yml index 1e46404..7cf255e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,12 +12,13 @@ services: mysql: image: mysql:5.7 + platform: linux/amd64 environment: - MYSQL_ALLOW_EMPTY_PASSWORD=yes - MYSQL_DATABASE - MYSQL_USER - MYSQL_PASSWORD healthcheck: - test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] timeout: 20s retries: 10 diff --git a/gemfiles/rails_5.2.gemfile b/gemfiles/rails_5.2.gemfile deleted file mode 100644 index 5a706dc..0000000 --- a/gemfiles/rails_5.2.gemfile +++ /dev/null @@ -1,7 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "rails", "~> 5.2.0" - -gemspec path: "../" diff --git a/gemfiles/rails_6.0.gemfile b/gemfiles/rails_6.0.gemfile deleted file mode 100644 index 15b9b27..0000000 --- a/gemfiles/rails_6.0.gemfile +++ /dev/null @@ -1,7 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "rails", "~> 6.0.0" - -gemspec path: "../" diff --git a/gemfiles/rails_6.1.gemfile b/gemfiles/rails_6.1.gemfile index dd95a47..c34b486 100644 --- a/gemfiles/rails_6.1.gemfile +++ b/gemfiles/rails_6.1.gemfile @@ -2,6 +2,6 @@ source "https://rubygems.org" -gem "rails", "~> 6.1.0" +gem "rails", "~> 6.1" gemspec path: "../" diff --git a/gemfiles/rails_7.0.gemfile b/gemfiles/rails_7.0.gemfile index 9af0ae3..9d2735b 100644 --- a/gemfiles/rails_7.0.gemfile +++ b/gemfiles/rails_7.0.gemfile @@ -2,6 +2,6 @@ source "https://rubygems.org" -gem "rails", "~> 7.0.0" +gem "rails", "~> 7.0" gemspec path: "../" diff --git a/gemfiles/rails_5.1.gemfile b/gemfiles/rails_7.1.gemfile similarity index 79% rename from gemfiles/rails_5.1.gemfile rename to gemfiles/rails_7.1.gemfile index 6100e83..1d906c3 100644 --- a/gemfiles/rails_5.1.gemfile +++ b/gemfiles/rails_7.1.gemfile @@ -2,6 +2,6 @@ source "https://rubygems.org" -gem "rails", "~> 5.1.0" +gem "rails", "~> 7.1" gemspec path: "../" diff --git a/lib/active_record/connection_adapters/abstract_mysql.rb b/lib/active_record/connection_adapters/abstract_mysql.rb index 7f8770d..b5d0841 100644 --- a/lib/active_record/connection_adapters/abstract_mysql.rb +++ b/lib/active_record/connection_adapters/abstract_mysql.rb @@ -1,21 +1,46 @@ -# This module fails in Rails 7.0 becuase the method being modified has been -# changed to a class method. - module ActiveRecord module ConnectionAdapters class AbstractMysqlAdapter - def initialize_type_map_with_enum(m = type_map) - initialize_without_enum(m) - register_enum_type(m) + class << self + def register_enum_type(mapping) + mapping.register_type(%r(enum)i) do |sql_type| + Type::Enum.new(limit: sql_type.to_s.scan(/'(.*?)'/).flatten) + end + end end - alias_method :initialize_without_enum, :initialize_type_map - alias_method :initialize_type_map, :initialize_type_map_with_enum + # In Rails 6.1, registering the enum type is an instance method and is + # done on initialization, In Rails 7.0 it is a class method and + # the registration happens when the class is loaded. So, in Rails 6.1, + # we can override the `initialize_type_map` method to register the enum + # but in Rails 7.1, we need to call register_enum_type explicitly. - def register_enum_type(mapping) - mapping.register_type(%r(enum)i) do |sql_type| - Type::Enum.new(limit: sql_type.scan(/'(.*?)'/).flatten) + if SqlEnum.rails_version_match?("6.1") + module SqlEnumMapper + def initialize_type_map(m = type_map) + super(m) + AbstractMysqlAdapter.register_enum_type(m) + end end + + ActiveRecord::ConnectionAdapters::Mysql2Adapter.prepend(SqlEnumMapper) + end + + + if SqlEnum.rails_version_match?("7.0") + [ + ActiveRecord::ConnectionAdapters::Mysql2Adapter::TYPE_MAP, + ActiveRecord::ConnectionAdapters::Mysql2Adapter::TYPE_MAP_WITH_BOOLEAN + ].each do |m| + AbstractMysqlAdapter.register_enum_type(m) + end + end + + # Rails 7.1 drops the TYPE_MAP_WITH_BOOLEAN constant + if SqlEnum.rails_version_match?("7.1") + AbstractMysqlAdapter.register_enum_type( + ActiveRecord::ConnectionAdapters::Mysql2Adapter::TYPE_MAP + ) end end end diff --git a/lib/sql_enum.rb b/lib/sql_enum.rb index 879521a..792e215 100644 --- a/lib/sql_enum.rb +++ b/lib/sql_enum.rb @@ -7,6 +7,10 @@ def self.configure self.configuration ||= Configuration.new yield(configuration) end + + def self.rails_version_match?(version_string) + ActiveSupport.version.to_s.start_with?(version_string) + end end require 'active_record' diff --git a/lib/sql_enum/class_methods.rb b/lib/sql_enum/class_methods.rb index 745d3ec..890991f 100644 --- a/lib/sql_enum/class_methods.rb +++ b/lib/sql_enum/class_methods.rb @@ -1,6 +1,9 @@ module SqlEnum module ClassMethods def sql_enum(column_name, options = {}) + # skip redefinitions + return if defined_enums.key?(column_name.to_s) + # Query values enum_column = EnumColumn.new(table_name, column_name) values_map = enum_column.values.to_h { |value| [value.to_sym, value.to_s] } @@ -14,20 +17,15 @@ def sql_enum(column_name, options = {}) # Override reader to return symbols type_definition = ->(subtype) { EnumType.new(attr, send(column_name.to_s.pluralize), subtype) } - case method(:decorate_attribute_type).arity - when 2 # Rails 5.1, 5.2, 6.0 - decorate_attribute_type(column_name, :enum, &type_definition) - else - decorate_attribute_type(column_name, &type_definition) - end + attribute(column_name, &type_definition) prefix_str = format_affix(column_name, prefix, suffix: '_') suffix_str = format_affix(column_name, suffix, prefix: '_') # Fix query methods to compare symbols to symbols values_map.each_value do |value| - method_name = "#{prefix_str}#{value}#{suffix_str}" - define_method("#{method_name}?") { self[column_name] == value.to_sym } + method_name = "#{prefix_str}#{value}#{suffix_str}?" + define_method(method_name) { self[column_name] == value.to_sym } end end diff --git a/lib/sql_enum/version.rb b/lib/sql_enum/version.rb index f644cc8..b58f273 100644 --- a/lib/sql_enum/version.rb +++ b/lib/sql_enum/version.rb @@ -1,3 +1,3 @@ module SqlEnum - VERSION = "0.4.0" + VERSION = "1.0.0" end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3351374..0af6860 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,7 @@ require "sql_enum" require 'debug' +require "awesome_print" Dir['spec/support/**/*.rb'].each { |f| require File.expand_path(f) } @@ -15,4 +16,16 @@ config.expect_with :rspec do |c| c.syntax = :expect end + + # allow "fit" examples + config.filter_run_when_matching :focus + config.include DefineConstantMacros + + config.before(:all) do + ActiveRecord::Base.establish_connection(ENV.fetch('DATABASE_URL')) + end + + config.after do + clear_generated_tables + end end diff --git a/spec/sql_enum_spec.rb b/spec/sql_enum_spec.rb index f12abea..28e646f 100644 --- a/spec/sql_enum_spec.rb +++ b/spec/sql_enum_spec.rb @@ -1,5 +1,3 @@ -require 'spec_helper' - RSpec.describe SqlEnum do it "has a version number" do expect(SqlEnum::VERSION).not_to be_nil @@ -11,12 +9,12 @@ before do SqlEnum.configure { |config| config.default_prefix = true } - define_model('Task', status: [:enum, limit: statuses, default: 'pending'], priority: [:enum, limit: priorities]) do sql_enum :status sql_enum :priority, _prefix: false, _suffix: true + sql_enum :status # duplicate should be no-op end end diff --git a/spec/support/macros/define_constant.rb b/spec/support/macros/define_constant.rb index 2eda672..586b787 100644 --- a/spec/support/macros/define_constant.rb +++ b/spec/support/macros/define_constant.rb @@ -56,15 +56,3 @@ def created_tables @created_tables ||= [] end end - -RSpec.configure do |config| - config.include DefineConstantMacros - - config.before(:all) do - ActiveRecord::Base.establish_connection(ENV.fetch('DATABASE_URL')) - end - - config.after do - clear_generated_tables - end -end diff --git a/sql_enum.gemspec b/sql_enum.gemspec index 9ba9b41..686fc63 100644 --- a/sql_enum.gemspec +++ b/sql_enum.gemspec @@ -25,11 +25,12 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_dependency "activesupport", ">= 6.0", "< 7.0" - spec.add_dependency "activerecord", ">= 6.0", "< 7.0" spec.add_dependency "mysql2" + spec.add_dependency "activesupport", ">= 6.1.0" + spec.add_dependency "activerecord", ">= 6.1.0" spec.add_development_dependency "appraisal" + spec.add_development_dependency "awesome_print" spec.add_development_dependency "bundler" spec.add_development_dependency "debug" spec.add_development_dependency "rake", "~> 13.0"