Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
# frozen_string_literal: true

source "https://rubygems.org"
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

# Specify your gem's dependencies in enummer.gemspec.
gemspec

gem "rubocop-rails"
gem "bundler-audit"
gem "standard"
gem "rails", "~> 7.1.0"
gem 'bundler-audit'
gem 'rails', '~> 7.1.0'
gem 'rubocop-rails'
gem 'standard'

group :test do
gem "simplecov", require: false
gem "simplecov-cobertura", require: false
gem 'simplecov', require: false
gem 'simplecov-cobertura', require: false
end

group :postgres do
gem "pg"
gem 'pg'
end

group :mysql do
gem "mysql2"
gem 'mysql2'
end

group :sqlite do
gem "sqlite3", "~> 1.4"
gem 'sqlite3', '~> 1.4'
end
4 changes: 2 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

require "bundler/setup"
require 'bundler/setup'

require "bundler/gem_tasks"
require 'bundler/gem_tasks'
29 changes: 15 additions & 14 deletions enummer.gemspec
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
# frozen_string_literal: true

require_relative "lib/enummer/version"
require_relative 'lib/enummer/version'

Gem::Specification.new do |spec|
spec.name = "enummer"
spec.name = 'enummer'
spec.version = Enummer::VERSION
spec.authors = ["Jamie Schembri"]
spec.email = ["jamie@schembri.me"]
spec.homepage = "https://github.com/shkm/enummer"
spec.summary = "Multi-value enums for Rails."
spec.description = "Enummer implements multi-value enums with bitwise operations."
spec.license = "MIT"
spec.authors = ['Jamie Schembri']
spec.email = ['jamie@schembri.me']
spec.homepage = 'https://github.com/shkm/enummer'
spec.summary = 'Multi-value enums for Rails.'
spec.description = 'Enummer implements multi-value enums with bitwise operations.'
spec.license = 'MIT'

spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = "https://github.com/shkm/enummer"
spec.metadata["changelog_uri"] = "https://github.com/shkm/enummer/CHANGELOG.md"
spec.metadata['homepage_uri'] = spec.homepage
spec.metadata['source_code_uri'] = 'https://github.com/shkm/enummer'
spec.metadata['changelog_uri'] = 'https://github.com/shkm/enummer/CHANGELOG.md'

spec.files = Dir.chdir(File.expand_path(__dir__)) do
Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
Dir['{app,config,db,lib}/**/*', 'MIT-LICENSE', 'Rakefile', 'README.md']
end

spec.required_ruby_version = ">= 3.1"
spec.add_dependency "rails", ">= 7.0.0"
spec.required_ruby_version = '>= 3.1'
spec.add_dependency 'rails', '>= 7.0.0'
spec.metadata['rubygems_mfa_required'] = 'true'
end
8 changes: 4 additions & 4 deletions lib/enummer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module Enummer; end

require "enummer/version"
require "enummer/enummer_type"
require "enummer/extension"
require "enummer/railtie"
require 'enummer/version'
require 'enummer/enummer_type'
require 'enummer/extension'
require 'enummer/railtie'
7 changes: 4 additions & 3 deletions lib/enummer/enummer_type.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
# frozen_string_literal: true

require "active_record/type"
require 'active_record/type'

module Enummer
class EnummerType < ::ActiveRecord::Type::Value
# @param [Array<Symbol>] values hash with bit-value pairs for all possible values for this type
def initialize(values:)
super
@values = values
end

# @return Symbol Representation of this type
# @example
# :enummer[read|write|execute]
def type
:"enummer[#{@values.keys.join("|")}]"
:"enummer[#{@values.keys.join('|')}]"
end

# @param [Symbol|Array<Symbol>] value Current value represented as one or more symbols
Expand All @@ -31,7 +32,7 @@ def deserialize(value)
return [] if value.to_i.zero?

@values.each_with_object([]) do |(pair_name, pair_value), value_names|
next if (value & pair_value).zero?
next if value.nobits?(pair_value)

value_names << pair_name
end
Expand Down
15 changes: 9 additions & 6 deletions lib/enummer/extension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

module Enummer
module Extension
# @param [Hash] values The attribute name to options mapping for an multi-option enum
# @option values [Boolean|String] :_prefix The prefix to give to generated methods. If true, uses the attribute name.
# @option values [Boolean|String] :_suffix The suffix to give to generated methods. If true, uses the attribute name.
# @example Defining an enummer with a prefix. This would generate `#can_read?`, `#can_read=`, `#can_read!`, `.can_read`, etc.
# @param [Hash] values The attribute name to options mapping for a multi-option enum
# @option values [Boolean|String] :_prefix The prefix to give to generated methods. If true, uses the
# attribute name.
# @option values [Boolean|String] :_suffix The suffix to give to generated methods. If true, uses the
# attribute name.
# @example Defining an enummer with a prefix. This would generate `#can_read?`, `#can_read=`, `#can_read!`,
# `.can_read`, etc.
# enummer permissions: %i[read write execute], :_prefix: 'can'
def enummer(values)
options = {}
Expand All @@ -27,7 +30,7 @@ def enummer(values)

def _enummer_build_with_scope(attribute_name, values)
scope "with_#{attribute_name}", lambda { |desired|
expected = Array.wrap(desired).sum(0) { |value| values[value.to_sym] }
expected = Array.wrap(desired).sum { |value| values[value.to_sym] }

where("#{attribute_name} & :expected = :expected", expected: expected)
}
Expand Down Expand Up @@ -59,7 +62,7 @@ def _enummer_method_name(attribute_name, value_name, options)
prefix = _enummer_affix(attribute_name, options[:_prefix])
suffix = _enummer_affix(attribute_name, options[:_suffix])

[prefix, value_name, suffix].compact.join("_")
[prefix, value_name, suffix].compact.join('_')
end

def _enummer_affix(attribute_name, value)
Expand Down
2 changes: 1 addition & 1 deletion lib/enummer/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module Enummer
class Railtie < ::Rails::Railtie
initializer "enummer" do
initializer 'enummer' do
ActiveSupport.on_load(:active_record) do
ActiveRecord::Type.register(:enummer, EnummerType)
extend Enummer::Extension
Expand Down
2 changes: 1 addition & 1 deletion lib/enummer/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Enummer
VERSION = "1.1.0"
VERSION = '1.1.0'
end
74 changes: 37 additions & 37 deletions test/enummer_test.rb
Original file line number Diff line number Diff line change
@@ -1,62 +1,62 @@
# frozen_string_literal: true

require "test_helper"
require 'test_helper'

class EnummerTest < ActiveSupport::TestCase
def setup
@user1 = User.new(permissions: %i[read write execute],
facial_features: %i[nose],
diets: %i[cigarettes alcohol],
transport: %i[submarine],
home: %i[box])
facial_features: %i[nose],
diets: %i[cigarettes alcohol],
transport: %i[submarine],
home: %i[box])
@user2 = User.new(permissions: %i[read write], diets: %i[cigarettes])
@user3 = User.new(permissions: %i[execute])

[@user1, @user2, @user3].map { |user| user.save && user.reload }
end

test "it has a version number" do
test 'it has a version number' do
assert Enummer::VERSION
end

test "get list of all valid values" do
test 'get list of all valid values' do
assert_equal %i[read write execute], User.permissions
end

test "plain scopes return users with those values set" do
test 'plain scopes return users with those values set' do
assert_equal [@user1, @user2], User.read
assert_equal [@user1, @user2], User.write
assert_equal [@user1, @user3], User.execute
end

test "with_ scope returns users with all of those bits set" do
test 'with_ scope returns users with all of those bits set' do
assert_equal [@user1], User.with_permissions(%i[execute read write])
assert_equal [@user1], User.with_permissions(%w[execute read write])
assert_equal [@user1, @user2], User.with_permissions(["read", :write])
assert_equal [@user1, @user2], User.with_permissions(['read', :write])
assert_equal [@user1, @user2], User.with_diets(%i[cigarettes])
end

test "not scopes return users without those bits set" do
test 'not scopes return users without those bits set' do
assert_equal [@user3], User.not_read
assert_equal [@user3], User.not_write
assert_equal [@user2], User.not_execute
end

test "it serializes into a numeric" do
test 'it serializes into a numeric' do
assert_equal 3, @user2.permissions_before_type_cast
end

test "it deserializes into an array of values" do
test 'it deserializes into an array of values' do
assert_equal %i[read write], @user2.permissions
end

test "it generates object query methods" do
test 'it generates object query methods' do
assert @user2.read?
assert @user2.write?
refute @user2.execute?
assert_not @user2.execute?
end

test "setting a setter to true adds the value" do
test 'setting a setter to true adds the value' do
@user3.read = true

assert_equal %i[execute read], @user3.permissions
Expand All @@ -66,7 +66,7 @@ def setup
assert_equal %i[execute read].sort, @user3.permissions.sort
end

test "setting a setter to false removes the value" do
test 'setting a setter to false removes the value' do
@user1.write = false

assert_equal %i[read execute], @user1.permissions
Expand All @@ -76,57 +76,57 @@ def setup
assert_equal %i[read execute].sort, @user1.permissions.sort
end

test "setting the attribute with strings adds the values" do
test 'setting the attribute with strings adds the values' do
@user3.update(permissions: %w[read write])

assert_equal %i[read write], @user3.permissions

updated = false
callback = lambda { |_name, _start, _finish, _id, payload| updated = true if payload[:sql].starts_with?("UPDATE") }
callback = ->(_name, _start, _finish, _id, payload) { updated = true if payload[:sql].starts_with?('UPDATE') }

ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do
ActiveSupport::Notifications.subscribed(callback, 'sql.active_record') do
@user3.update(permissions: %w[read write])
end

refute updated, "subsequent updates with the same values should be idempotent"
assert_not updated, 'subsequent updates with the same values should be idempotent'
end

test "using a bang method properly updates the persisted field" do
test 'using a bang method properly updates the persisted field' do
@user3.read!
@user3.reload

assert_equal %i[read execute], @user3.permissions
end

test "methods respect _prefix" do
test 'methods respect _prefix' do
assert @user1.facial_features_nose?
refute @user1.facial_features_mouth?
refute @user1.facial_features_eyes?
assert_not @user1.facial_features_mouth?
assert_not @user1.facial_features_eyes?

assert @user1.consumes_cigarettes?
assert @user1.consumes_alcohol?
refute @user1.consumes_greens?
assert_not @user1.consumes_greens?
end

test "update with prefix" do
test 'update with prefix' do
assert @user1.consumes_cigarettes?
refute @user1.consumes_greens?
assert_not @user1.consumes_greens?
@user1.update!(consumes_cigarettes: true)
refute @user1.consumes_greens?
assert_not @user1.consumes_greens?
end

test "recognizes boolean params" do
@user1.update!(ActionController::Parameters.new({"consumes_cigarettes" => "false"}).permit(:consumes_cigarettes))
refute @user1.consumes_cigarettes?
test 'recognizes boolean params' do
@user1.update!(ActionController::Parameters.new({ 'consumes_cigarettes' => 'false' }).permit(:consumes_cigarettes))
assert_not @user1.consumes_cigarettes?
end

test "methods respect _suffix" do
refute @user1.car_transport?
refute @user1.truck_transport?
test 'methods respect _suffix' do
assert_not @user1.car_transport?
assert_not @user1.truck_transport?
assert @user1.submarine_transport?

assert @user1.box_home?
refute @user1.apartment_home?
refute @user1.house_home?
assert_not @user1.apartment_home?
assert_not @user1.house_home?
end
end
18 changes: 9 additions & 9 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
# frozen_string_literal: true

require "simplecov"
require 'simplecov'
SimpleCov.start do
enable_coverage :branch
add_filter "/test/dummy/"
add_filter '/test/dummy/'

if ENV["CI"]
require "simplecov-cobertura"
if ENV['CI']
require 'simplecov-cobertura'
SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
end
end

# Configure Rails Environment
ENV["RAILS_ENV"] = "test"
ENV['RAILS_ENV'] = 'test'

require_relative "../test/dummy/config/environment"
ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)]
require "rails/test_help"
require_relative '../test/dummy/config/environment'
ActiveRecord::Migrator.migrations_paths = [File.expand_path('../test/dummy/db/migrate', __dir__)]
require 'rails/test_help'

# Load fixtures from the engine
if ActiveSupport::TestCase.respond_to?(:fixture_path=)
ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__)
ActiveSupport::TestCase.fixture_path = File.expand_path('fixtures', __dir__)
ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
ActiveSupport::TestCase.file_fixture_path = "#{ActiveSupport::TestCase.fixture_path}/files"
ActiveSupport::TestCase.fixtures :all
Expand Down
Loading