├── lib ├── native_enum │ ├── version.rb │ ├── activerecord_enum_pre42.rb │ └── activerecord_enum_post42.rb ├── connection_adapters │ ├── sqlite3.rb │ └── mysql2.rb └── native_enum.rb ├── Gemfile ├── .gitignore ├── gemfiles ├── activerecord_5.0.gemfile ├── activerecord_5.1.gemfile ├── activerecord_4.2.gemfile └── activerecord_4.1.gemfile ├── spec ├── schema │ ├── enum_old.rb │ ├── set_old.rb │ ├── enum_new.rb │ └── set_new.rb ├── .travis.database.yml ├── database.yml.tmpl ├── enum_spec.rb ├── set_spec.rb └── spec_helper.rb ├── CHANGELOG.md ├── Appraisals ├── .travis.yml ├── LICENSE ├── native_enum.gemspec ├── Rakefile └── README.markdown /lib/native_enum/version.rb: -------------------------------------------------------------------------------- 1 | module NativeEnum 2 | VERSION = "2.0.0" 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in native_enum.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | gemfiles/*.gemfile.lock 5 | pkg/* 6 | spec/database.yml 7 | spec/vendor 8 | -------------------------------------------------------------------------------- /gemfiles/activerecord_5.0.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "activerecord", "~> 5.0.0" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/activerecord_5.1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "activerecord", "~> 5.1.0" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/activerecord_4.2.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "activerecord", "~> 4.2.0" 6 | gem "minitest", "< 5.12" 7 | 8 | gemspec path: "../" 9 | -------------------------------------------------------------------------------- /spec/schema/enum_old.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Schema.define do 2 | create_table :aircraft, :force => true do |t| 3 | t.column "color", "enum('blue','red','yellow')", :default => 'red', :null => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/schema/set_old.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Schema.define do 2 | create_table :aircraft, :force => true do |t| 3 | t.column "gadgets", "set('propeller','tail gun','gps')", :default => 'propeller,gps', :null => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/.travis.database.yml: -------------------------------------------------------------------------------- 1 | mysql: 2 | adapter: mysql2 3 | database: native_enum_test 4 | username: root 5 | supports_enums: true 6 | sqlite: 7 | adapter: sqlite3 8 | database: ":memory:" 9 | supports_enums: false 10 | -------------------------------------------------------------------------------- /gemfiles/activerecord_4.1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "activerecord", "~> 4.1.0" 6 | gem "minitest", "< 5.12" 7 | gem "mysql2", "~> 0.3.13" 8 | 9 | gemspec path: "../" 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | # 2.0.0 4 | 5 | * [Support Rails 5.0 and 5.1](https://github.com/iangreenleaf/native_enum/pull/13) 6 | * Drop support for Ruby 1.9.3 7 | * Drop support for Rails < 4.1.0 8 | 9 | # 1.0.0 10 | 11 | * First version 12 | -------------------------------------------------------------------------------- /spec/database.yml.tmpl: -------------------------------------------------------------------------------- 1 | mysql: 2 | adapter: mysql2 3 | host: localhost 4 | database: enum_test 5 | username: enum_test 6 | password: enum_test 7 | supports_enums: true 8 | sqlite: 9 | adapter: sqlite3 10 | database: ":memory:" 11 | supports_enums: false 12 | -------------------------------------------------------------------------------- /spec/schema/enum_new.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Schema.define do 2 | create_table :balloons, :force => true do |t| 3 | t.enum "color", :limit => ['red', 'gold'], :default => 'gold', :null => false 4 | t.column "size", :enum, :limit => ['small', 'medium', 'large'] 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/schema/set_new.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Schema.define do 2 | create_table :balloons, :force => true do |t| 3 | t.set "ribbons", :limit => ['red', 'green', 'gold'], :default => ['green','gold'], :null => false 4 | t.column "gasses", :set, :limit => ['helium', 'hydrogen'] 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise "activerecord-4.1" do 2 | gem "activerecord", "~> 4.1.0" 3 | gem "minitest", "< 5.12" 4 | gem "mysql2", "~> 0.3.13" 5 | end 6 | 7 | appraise "activerecord-4.2" do 8 | gem "activerecord", "~> 4.2.0" 9 | gem "minitest", "< 5.12" 10 | end 11 | 12 | appraise "activerecord-5.0" do 13 | gem "activerecord", "~> 5.0.0" 14 | end 15 | 16 | appraise "activerecord-5.1" do 17 | gem "activerecord", "~> 5.1.0" 18 | end 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | services: 3 | - mysql 4 | rvm: 5 | - "2.0" 6 | - "2.1" 7 | - "2.2" 8 | - "2.3" 9 | - "2.4" 10 | - "2.5" 11 | - "2.6" 12 | gemfile: 13 | - gemfiles/activerecord_4.1.gemfile 14 | - gemfiles/activerecord_4.2.gemfile 15 | - gemfiles/activerecord_5.0.gemfile 16 | - gemfiles/activerecord_5.1.gemfile 17 | env: 18 | - DB=mysql 19 | - DB=sqlite 20 | matrix: 21 | exclude: 22 | - rvm: "2.0" 23 | gemfile: gemfiles/activerecord_5.0.gemfile 24 | - rvm: "2.0" 25 | gemfile: gemfiles/activerecord_5.1.gemfile 26 | - rvm: "2.1" 27 | gemfile: gemfiles/activerecord_5.0.gemfile 28 | - rvm: "2.1" 29 | gemfile: gemfiles/activerecord_5.1.gemfile 30 | before_script: 31 | - "cp spec/{.travis.,}database.yml" 32 | - "bundle exec rake db:prepare" 33 | script: bundle exec rake spec 34 | sudo: false 35 | -------------------------------------------------------------------------------- /lib/connection_adapters/sqlite3.rb: -------------------------------------------------------------------------------- 1 | require 'active_record/connection_adapters/sqlite3_adapter' 2 | 3 | module ActiveRecord 4 | module ConnectionAdapters 5 | class SQLite3Adapter < (defined?(SQLiteAdapter) ? SQLiteAdapter : AbstractAdapter) 6 | if ActiveRecord::VERSION::MAJOR >= 5 && ActiveRecord::VERSION::MINOR >= 1 7 | def type_to_sql_with_enum(type, limit: nil, **args) 8 | if type.to_s == "enum" || type.to_s == "set" 9 | type, limit = :string, nil 10 | end 11 | type_to_sql_without_enum(type, limit: limit, **args) 12 | end 13 | else 14 | def type_to_sql_with_enum(type, limit=nil, *args) 15 | if type.to_s == "enum" || type.to_s == "set" 16 | type, limit = :string, nil 17 | end 18 | type_to_sql_without_enum(type, limit, *args) 19 | end 20 | end 21 | alias_method :type_to_sql_without_enum, :type_to_sql 22 | alias_method :type_to_sql, :type_to_sql_with_enum 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/native_enum.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | require 'active_record/base' 3 | require 'active_record/connection_adapters/abstract/schema_definitions.rb' 4 | 5 | require 'connection_adapters/sqlite3' if defined?( SQLite3 ) 6 | require 'connection_adapters/mysql2' if defined?( Mysql2 ) 7 | 8 | if ActiveRecord::VERSION::MAJOR < 4 || (ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR <= 1) 9 | require 'native_enum/activerecord_enum_pre42.rb' 10 | else 11 | require 'native_enum/activerecord_enum_post42.rb' 12 | end 13 | 14 | module ActiveRecord 15 | module ConnectionAdapters 16 | class TableDefinition 17 | def enum *args 18 | options = args.extract_options! 19 | column_names = args 20 | column_names.each { |name| column(name, :enum, options) } 21 | end 22 | def set *args 23 | options = args.extract_options! 24 | options[:default] = options[:default].join "," if options[:default].present? 25 | column_names = args 26 | column_names.each { |name| column(name, :set, options) } 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015 by Ian Young 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /native_enum.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "native_enum/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "native_enum" 7 | s.version = NativeEnum::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Ian Young"] 10 | s.email = ["dev@iangreenleaf.com"] 11 | s.homepage = "" 12 | s.summary = %q{Enum data types for ActiveRecord} 13 | s.description = %q{Adds the ENUM data type natively to ActiveRecord.} 14 | s.license = "MIT" 15 | 16 | s.rubyforge_project = "native_enum" 17 | 18 | s.add_dependency "activerecord", ">= 3.0" 19 | s.add_development_dependency "rake" 20 | s.add_development_dependency "bundler" 21 | s.add_development_dependency "mysql2", ">= 0.3.11", "< 0.5" 22 | s.add_development_dependency "sqlite3", "~>1.3.4" 23 | s.add_development_dependency "rspec", "~> 3.1" 24 | s.add_development_dependency "appraisal" 25 | 26 | s.files = `git ls-files`.split("\n") 27 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 28 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 29 | s.require_paths = ["lib"] 30 | end 31 | -------------------------------------------------------------------------------- /lib/native_enum/activerecord_enum_pre42.rb: -------------------------------------------------------------------------------- 1 | module ActiveRecord 2 | module ConnectionAdapters 3 | class Column 4 | def initialize_with_enum name, default, sql_type=nil, *args 5 | initialize_without_enum name, default, sql_type, *args 6 | @type = simplified_type_with_enum sql_type 7 | @limit = extract_limit_with_enum sql_type 8 | @default = extract_default_with_enum default 9 | end 10 | alias_method :initialize_without_enum, :initialize 11 | alias_method :initialize, :initialize_with_enum 12 | 13 | def simplified_type_with_enum field_type 14 | if field_type =~ /enum|set/i 15 | $&.to_sym 16 | else 17 | simplified_type field_type 18 | end 19 | end 20 | 21 | def extract_limit_with_enum field_type 22 | if field_type =~ /(?:enum|set)\(([^)]+)\)/i 23 | $1.scan( /'([^']*)'/ ).flatten 24 | else 25 | extract_limit field_type 26 | end 27 | end 28 | 29 | def extract_default_with_enum default 30 | if type == :set 31 | default.split "," if default.present? 32 | else 33 | extract_default default 34 | end 35 | end 36 | 37 | def set? 38 | type == :set 39 | end 40 | 41 | def enum? 42 | type == :enum 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/enum_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe "ENUM datatype" do 4 | 5 | describe "schema dump", :db_support => true do 6 | before { load_schema "enum_old" } 7 | subject { dumped_schema } 8 | 9 | it "dumps native format" do 10 | expect(subject).to match %r{t\.enum\s+"color",\s+(:limit =>|limit:) \["blue", "red", "yellow"\]} 11 | end 12 | 13 | it "dumps default option" do 14 | expect(subject).to match %r{t\.enum\s+"color",.+(:default =>|default:) "red"} 15 | end 16 | 17 | it "dumps null option" do 18 | expect(subject).to match %r{t\.enum\s+"color",.+(:null =>|null:) false$} 19 | end 20 | end 21 | 22 | describe "schema loading" do 23 | before { load_schema "enum_new" } 24 | subject { column_props :balloons, :color } 25 | 26 | it "loads native format", :db_support => true do 27 | expect(subject[:type]).to eq("enum('red','gold')") 28 | end 29 | 30 | it "falls back to text when missing db support", :db_support => false do 31 | expect(subject[:type]).to match(/varchar/) 32 | end 33 | 34 | it "loads default option" do 35 | expect(subject[:default]).to eq("gold") 36 | end 37 | 38 | it "loads null option" do 39 | expect(subject[:null]).to eq(false) 40 | end 41 | 42 | it "loads native column format", :db_support => true do 43 | subject = column_props :balloons, :size 44 | expect(subject[:type]).to eq("enum('small','medium','large')") 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/set_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe "SET datatype" do 4 | 5 | describe "schema dump", :db_support => true do 6 | before { load_schema "set_old" } 7 | subject { dumped_schema } 8 | 9 | it "dumps native format" do 10 | expect(subject).to match %r{t\.set\s+"gadgets",\s+(:limit =>|limit:) \["propeller", "tail gun", "gps"\]} 11 | end 12 | 13 | it "dumps default option" do 14 | expect(subject).to match %r{t\.set\s+"gadgets",.+(:default =>|default:) \["propeller", "gps"\]} 15 | end 16 | 17 | it "dumps null option" do 18 | expect(subject).to match %r{t\.set\s+"gadgets",.+(:null =>|null:) false$} 19 | end 20 | end 21 | 22 | describe "schema loading" do 23 | before { load_schema "set_new" } 24 | subject { column_props :balloons, :ribbons } 25 | 26 | it "loads native format", :db_support => true do 27 | expect(subject[:type]).to eq("set('red','green','gold')") 28 | end 29 | 30 | it "falls back to text when missing db support", :db_support => false do 31 | expect(subject[:type]).to match(/varchar/) 32 | end 33 | 34 | it "loads default option" do 35 | expect(subject[:default]).to eq("green,gold") 36 | end 37 | 38 | it "loads null option" do 39 | expect(subject[:null]).to eq(false) 40 | end 41 | 42 | it "loads native column format", :db_support => true do 43 | subject = column_props :balloons, :gasses 44 | expect(subject[:type]).to eq("set('helium','hydrogen')") 45 | end 46 | end 47 | end 48 | 49 | class Balloon < ActiveRecord::Base; end 50 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | require 'yaml' 3 | Bundler::GemHelper.install_tasks 4 | 5 | DB_CONFIG = "spec/database.yml" 6 | GEMFILES = "spec/Gemfile.rails_[0-9]_[0-9]" 7 | 8 | require 'rake' 9 | desc 'Default: run all unit tests.' 10 | task :default => :"spec:all" 11 | 12 | require 'active_record' 13 | 14 | # For more info on DatabaseTasks, see: 15 | # https://github.com/rails/rails/blob/v5.0.7/activerecord/lib/active_record/tasks/database_tasks.rb 16 | namespace :db do 17 | task :load_config do 18 | db_configs = YAML.load_file('spec/database.yml') 19 | ActiveRecord::Tasks::DatabaseTasks.tap do |db_tasks| 20 | ActiveRecord::Base.configurations = db_configs 21 | db_tasks.database_configuration = db_configs 22 | db_tasks.db_dir = 'db' 23 | db_tasks.root = File.dirname(__FILE__) 24 | end 25 | end 26 | 27 | desc 'Prepare the databases.' 28 | task prepare: :load_config do 29 | unless File.exist? DB_CONFIG 30 | cp "#{DB_CONFIG}.tmpl", DB_CONFIG 31 | end 32 | 33 | ActiveRecord::Tasks::DatabaseTasks.tap do |db_tasks| 34 | db_tasks.create_current('mysql') 35 | db_tasks.create_current('sqlite') 36 | end 37 | end 38 | 39 | desc "Drop all databases created for testing" 40 | task drop_all: :load_config do 41 | ActiveRecord::Tasks::DatabaseTasks.drop_all 42 | end 43 | end 44 | 45 | require "rspec/core/rake_task" 46 | desc 'Run the test suite.' 47 | RSpec::Core::RakeTask.new(:spec) do |t| 48 | t.pattern = 'spec/*_spec.rb' 49 | t.exclude_pattern = 'spec/**/vendor/*' 50 | end 51 | 52 | desc 'Run the test suite for all DBs.' 53 | namespace :spec do 54 | task :all do 55 | db_config = YAML::load(IO.read(DB_CONFIG)) 56 | db_config.each do |db, config| 57 | ENV["DB"] = db 58 | Rake::Task["spec"].reenable 59 | Rake::Task["spec"].invoke 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/connection_adapters/mysql2.rb: -------------------------------------------------------------------------------- 1 | require 'active_record/connection_adapters/mysql2_adapter' 2 | 3 | module ActiveRecord 4 | module ConnectionAdapters 5 | existing_class = defined?(Mysql2Adapter) ? Mysql2Adapter : AbstractMysqlAdapter 6 | 7 | existing_class.class_eval do 8 | def native_database_types_with_enum 9 | native_database_types_without_enum.merge({ 10 | :enum => { :name => "enum" }, 11 | :set => { :name => "set" } 12 | }) 13 | end 14 | alias_method :native_database_types_without_enum, :native_database_types 15 | alias_method :native_database_types, :native_database_types_with_enum 16 | 17 | 18 | 19 | if ActiveRecord::VERSION::MAJOR >= 5 && ActiveRecord::VERSION::MINOR >= 1 20 | def type_to_sql_with_enum(type, limit: nil, **args) 21 | if type.to_s == "enum" || type.to_s == "set" 22 | list = limit 23 | if limit.is_a?(Hash) 24 | list = limit[:limit] 25 | end 26 | "#{type}(#{quoted_comma_list(list)})" 27 | else 28 | type_to_sql_without_enum(type, limit: limit, **args) 29 | end 30 | end 31 | else 32 | def type_to_sql_with_enum(type, limit=nil, *args) 33 | if type.to_s == "enum" || type.to_s == "set" 34 | list = limit 35 | if limit.is_a?(Hash) 36 | list = limit[:limit] 37 | end 38 | "#{type}(#{quoted_comma_list(list)})" 39 | else 40 | type_to_sql_without_enum(type, limit, *args) 41 | end 42 | end 43 | end 44 | alias_method :type_to_sql_without_enum, :type_to_sql 45 | alias_method :type_to_sql, :type_to_sql_with_enum 46 | 47 | private 48 | 49 | def quoted_comma_list list 50 | list.to_a.map{|n| "'#{n}'"}.join(",") 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/native_enum/activerecord_enum_post42.rb: -------------------------------------------------------------------------------- 1 | module ActiveRecord 2 | module ConnectionAdapters 3 | if defined?(AbstractMysqlAdapter) 4 | class AbstractMysqlAdapter 5 | protected 6 | def initialize_type_map_with_enum(m = type_map) 7 | initialize_without_enum(m) 8 | register_enum_type(m, %r(^enum)i) 9 | register_set_type(m, %r(^set)i) 10 | end 11 | 12 | alias_method :initialize_without_enum, :initialize_type_map 13 | alias_method :initialize_type_map, :initialize_type_map_with_enum 14 | 15 | def register_enum_type(mapping, key) 16 | mapping.register_type(key) do |sql_type| 17 | if sql_type =~ /(?:enum)\(([^)]+)\)/i 18 | limit = $1.scan( /'([^']*)'/ ).flatten 19 | Type::Enum.new(limit: limit) 20 | end 21 | end 22 | end 23 | 24 | def register_set_type(mapping, key) 25 | mapping.register_type(key) do |sql_type| 26 | if sql_type =~ /(?:set)\(([^)]+)\)/i 27 | limit = $1.scan( /'([^']*)'/ ).flatten 28 | Type::Set.new(limit: limit) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | 36 | module Type 37 | class Enum < Type::Value 38 | def type 39 | :enum 40 | end 41 | 42 | def initialize(options = {}) 43 | options.assert_valid_keys(:limit) 44 | @limit = options[:limit] 45 | end 46 | end 47 | 48 | class Set < Type::Value 49 | def type 50 | :set 51 | end 52 | 53 | def initialize(options = {}) 54 | options.assert_valid_keys(:limit) 55 | @limit = options[:limit] 56 | end 57 | 58 | # Deserialize value from the database 59 | # 60 | # See: https://github.com/rails/rails/blob/v5.0.7/activemodel/lib/active_model/type/value.rb#L15-L23 61 | def deserialize(value) 62 | value.split(",") 63 | end 64 | # deserialize used to be called type_cast_from_database before v5 65 | alias_method :type_cast_from_database, :deserialize 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'yaml' 3 | 4 | module DBConfig 5 | class << self 6 | def db 7 | ENV["DB"] || "mysql" 8 | end 9 | 10 | def all_configs 11 | @all_configs ||= YAML::load(IO.read("spec/database.yml")) 12 | end 13 | 14 | def current 15 | all_configs[db] 16 | end 17 | end 18 | end 19 | 20 | module DatabaseHelpers 21 | def load_schema(filename) 22 | root = File.expand_path(File.dirname(__FILE__)) 23 | load root + "/schema/#{filename}.rb" 24 | end 25 | 26 | def dumped_schema 27 | stream = StringIO.new 28 | ActiveRecord::SchemaDumper.ignore_tables = [] 29 | ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) 30 | stream.string.lines.select {|l| /^\s*#/.match(l).nil? }.join 31 | end 32 | 33 | def column_props(table, column) 34 | case DBConfig.db 35 | when "mysql" 36 | result = ActiveRecord::Base.connection.select_one( 37 | "SHOW FIELDS FROM #{table} WHERE Field='#{column}'" 38 | ) 39 | { 40 | :type => result["Type"], 41 | :default => result["Default"], 42 | :null => ( result["Null"] == "YES" ) 43 | } 44 | when "sqlite" 45 | result = ActiveRecord::Base.connection.select_value "SELECT sql FROM sqlite_master WHERE type='table' AND name='#{table}'" 46 | matches = /"#{column}" ([^[:space:]]+) (?:DEFAULT '([^[:space:]]+)')?( NOT NULL)?,/.match result 47 | { :type => matches[1], :default => matches[2], :null => matches[3].nil? } 48 | end 49 | end 50 | 51 | def db_config 52 | DBConfig.db_config 53 | end 54 | 55 | def db 56 | DBConfig.db 57 | end 58 | end 59 | 60 | require DBConfig.current["adapter"] 61 | require 'native_enum' 62 | 63 | RSpec.configure do |c| 64 | c.disable_monkey_patching! 65 | c.expose_dsl_globally = false 66 | 67 | c.include DatabaseHelpers 68 | c.filter_run_excluding db_support: !DBConfig.current["supports_enums"] 69 | c.before :suite do 70 | db_config = DBConfig.all_configs 71 | db = DBConfig.db 72 | ActiveRecord::Base.configurations = db_config 73 | ActiveRecord::Base.establish_connection db.to_sym 74 | # Silence AR's logging (e.g. when loading the schema) 75 | ActiveRecord::Migration.verbose = false 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ***This gem has been retired and is no longer actively maintained. It retains partial functionality on Rails 6, but will not be receiving future updates.*** 2 | 3 | # Native ENUM types in ActiveRecord # 4 | 5 | Provides ActiveRecord (and thus Rails) support for the nonstandard `ENUM` and `SET` data types. 6 | 7 | ## How now? ## 8 | 9 | It sucks to have ActiveRecord not understand your `ENUM` columns and constantly write the wrong thing to your `schema.rb`. 10 | It also sucks to work with a database that uses `ENUM` in production when you'd prefer sqlite in development. 11 | Wait no longer... 12 | 13 | ```ruby 14 | create_table :balloons, :force => true do |t| 15 | t.enum "color", :limit => ['red', 'gold'], :default => 'gold', :null => false 16 | # or... 17 | t.column "size", :enum, :limit => ['small', 'medium', 'large'] 18 | end 19 | ``` 20 | 21 | Your schema<->db coupling will work again, and it will fall back to a `VARCHAR` column on any adapters that don't support `ENUM`. 22 | 23 | ## Installation ## 24 | 25 | ``` 26 | gem 'native_enum' 27 | ``` 28 | 29 | Make sure to put this line *after* the line for your database adapter gem, like so: 30 | 31 | ``` 32 | gem 'mysql2', '~> 0.3.20' 33 | gem 'native_enum' 34 | ``` 35 | 36 | That's it! 37 | 38 | ## Hypothetically asked questions ## 39 | 40 | ### Y U NO WORK?! ### 41 | 42 | It currently works with: 43 | 44 | * ActiveRecord 4.0, 4.1, 4.2, 5.0, and 5.1. 45 | * The `mysql2` and `sqlite` adapters. 46 | * Ruby 2.0 and above. 47 | 48 | If you'd like to support other adapters, pull requests are welcome! 49 | 50 | ### I thought Rails 4.1+ did enums? ### 51 | 52 | This gem provides compatibility for *native* `ENUM` types in the DB. 53 | [ActiveRecord::Enum](http://api.rubyonrails.org/classes/ActiveRecord/Enum.html) is actually a layer that takes an integer column in the DB and *presents* it as an enum type. 54 | These are fundamentally different approaches, with different motivations, costs and benefits. 55 | 56 | ### Why doesn't it validate anything? ### 57 | 58 | Following ActiveRecord's lead, this plugin doesn't do any validation work itself. 59 | 60 | For ENUM columns, you may be satisfied with something simple: 61 | 62 | validates_inclusion_of :attr, :in => [ :possible, :values ] 63 | 64 | Or if you prefer more bells and whistles, try [brainspec/enumerize](https://github.com/brainspec/enumerize). 65 | 66 | For SET columns, you may be interested in [iangreenleaf/active_set](https://github.com/iangreenleaf/active_set). 67 | 68 | ### Nonstandard SQL?! What's your problem, jerkweed? ### 69 | 70 | This isn't a plugin everyone should use. There are a number of plugins to simulate enum behavior backed by standard data types. Personally, I like [nofxx/symbolize](https://github.com/nofxx/symbolize). 71 | 72 | However, sometimes we can't or won't avoid working with these data types. When that happens, I got you covered. 73 | 74 | ## Contributing ## 75 | 76 | Pull requests welcome! Join 77 | [this lovely bunch of people](https://github.com/iangreenleaf/native_enum/graphs/contributors). 78 | 79 | 80 | ### Running the tests ### 81 | 82 | To run the tests for all adapters and all versions of ActiveRecord: 83 | 84 | appraisal rake spec:all 85 | 86 | To run the tests for a specific adapter: 87 | 88 | DB=mysql rake spec 89 | 90 | To run the tests for a specific version of ActiveRecord: 91 | 92 | appraisal activerecord-5.2 rake spec:all 93 | --------------------------------------------------------------------------------