Skip to content

Instantly share code, notes, and snippets.

@alpaca-tc
Created March 9, 2018 15:10
Show Gist options
  • Save alpaca-tc/b5b9bd0ebbf86c20f14484cc84bf8f26 to your computer and use it in GitHub Desktop.
Save alpaca-tc/b5b9bd0ebbf86c20f14484cc84bf8f26 to your computer and use it in GitHub Desktop.
bitmask
# frozen_string_literal: true
module ActiveModelType
class BitmapType < ActiveModel::Type::Value
delegate :type, to: :subtype
attr_reader :name, :mapping, :subtype
def initialize(name, mapping, subtype)
@name = name
@mapping = {}
@subtype = subtype
mapping.each_with_index do |key, index|
@mapping[key.to_sym] = 1 << index
end
end
def cast(value)
return [] if value.blank?
keys = case value
when Numeric
deserialize(value)
when String, Symbol, Array
symbols = Array.wrap(value).map(&:to_sym)
mapping.keys & symbols
else
assert_valid_value(value)
end
sort_by_mapping(keys)
end
def deserialize(value)
return [] if value.nil?
value = subtype.deserialize(value)
mapping.each_with_object([]) do |(key, bitmask), result|
result << key if (value & bitmask).nonzero?
end
end
def serialize(value)
return if value.nil?
keys = cast(value)
keys.inject(0) { |sum, key| sum + mapping.fetch(key, 0) }
end
def assert_valid_value(value)
unless value.blank? || mapping.include?(value) || (value.is_a?(Numeric) && value < (1 << mapping.length))
raise ArgumentError, "'#{value}' is not a valid #{name}"
end
end
private
def sort_by_mapping(array)
array.sort_by { |value| mapping[value] }
end
end
end
# frozen_string_literal: true
module BitmapAttributes
extend ActiveSupport::Concern
module ClassMethods
def bitmap_attributes(definitions)
definitions.each do |name, mapping|
name = name.to_s
decorate_attribute_type(name, :bitmap) do |subtype|
ActiveModelType::BitmapType.new(name, mapping, subtype)
end
singleton_class.define_method(name.pluralize) { mapping }
end
end
end
end
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActiveModelType::BitmapType do
let(:instance) { described_class.new(name, mapping, subtype) }
let(:name) { :column_name }
let(:subtype) { ActiveModel::Type.lookup(:integer) }
let(:mapping) { %i[administrator cs developer operator] }
describe '#cast' do
subject { instance.cast(value) }
context 'given :administrator' do
let(:value) { :administrator }
it { is_expected.to eq([:administrator]) }
end
context 'given [:administrator]' do
let(:value) { [:administrator] }
it { is_expected.to eq([:administrator]) }
end
context 'given nil' do
let(:value) { nil }
it { is_expected.to eq([]) }
end
context 'given 0' do
let(:value) { 0 }
it { is_expected.to eq([]) }
end
context 'given 1' do
let(:value) { 1 }
it { is_expected.to eq([:administrator]) }
end
context 'given 2' do
let(:value) { 2 }
it { is_expected.to eq([:cs]) }
end
context 'given 3' do
let(:value) { 3 }
it { is_expected.to eq(%i[administrator cs]) }
end
context 'given 4' do
let(:value) { 4 }
it { is_expected.to eq([:developer]) }
end
context 'given 5' do
let(:value) { 5 }
it { is_expected.to eq(%i[administrator developer]) }
end
context 'given 6' do
let(:value) { 6 }
it { is_expected.to eq(%i[cs developer]) }
end
context 'given 7' do
let(:value) { 7 }
it { is_expected.to eq(%i[administrator cs developer]) }
end
context 'given 8' do
let(:value) { 8 }
it { is_expected.to eq([:operator]) }
end
context 'given 9' do
let(:value) { 9 }
it { is_expected.to eq(%i[administrator operator]) }
end
context 'given 10' do
let(:value) { 10 }
it { is_expected.to eq(%i[cs operator]) }
end
context 'given 11' do
let(:value) { 11 }
it { is_expected.to eq(%i[administrator cs operator]) }
end
context 'given 12' do
let(:value) { 12 }
it { is_expected.to eq(%i[developer operator]) }
end
context 'given 13' do
let(:value) { 13 }
it { is_expected.to eq(%i[administrator developer operator]) }
end
context 'given 14' do
let(:value) { 14 }
it { is_expected.to eq(%i[cs developer operator]) }
end
context 'given 15' do
let(:value) { 15 }
it { is_expected.to eq(%i[administrator cs developer operator]) }
end
context 'given 16' do
let(:value) { 16 }
it { is_expected.to eq([]) }
end
end
describe '#deserialize' do
subject { instance.deserialize(value) }
context 'given nil' do
let(:value) { nil }
it { is_expected.to eq([]) }
end
context 'given 0' do
let(:value) { 0 }
it { is_expected.to eq([]) }
end
context 'given 1' do
let(:value) { 1 }
it { is_expected.to eq([:administrator]) }
end
context 'given 2' do
let(:value) { 2 }
it { is_expected.to eq([:cs]) }
end
context 'given 3' do
let(:value) { 3 }
it { is_expected.to eq(%i[administrator cs]) }
end
context 'given 4' do
let(:value) { 4 }
it { is_expected.to eq([:developer]) }
end
context 'given 5' do
let(:value) { 5 }
it { is_expected.to eq(%i[administrator developer]) }
end
context 'given 6' do
let(:value) { 6 }
it { is_expected.to eq(%i[cs developer]) }
end
context 'given 7' do
let(:value) { 7 }
it { is_expected.to eq(%i[administrator cs developer]) }
end
context 'given 8' do
let(:value) { 8 }
it { is_expected.to eq([:operator]) }
end
context 'given 9' do
let(:value) { 9 }
it { is_expected.to eq(%i[administrator operator]) }
end
context 'given 10' do
let(:value) { 10 }
it { is_expected.to eq(%i[cs operator]) }
end
context 'given 11' do
let(:value) { 11 }
it { is_expected.to eq(%i[administrator cs operator]) }
end
context 'given 12' do
let(:value) { 12 }
it { is_expected.to eq(%i[developer operator]) }
end
context 'given 13' do
let(:value) { 13 }
it { is_expected.to eq(%i[administrator developer operator]) }
end
context 'given 14' do
let(:value) { 14 }
it { is_expected.to eq(%i[cs developer operator]) }
end
context 'given 15' do
let(:value) { 15 }
it { is_expected.to eq(%i[administrator cs developer operator]) }
end
context 'given 16' do
let(:value) { 16 }
it { is_expected.to eq([]) }
end
end
describe '#serialize' do
subject { instance.serialize(value) }
context 'given nil' do
let(:value) { nil }
it { is_expected.to be_nil }
end
context 'given []' do
let(:value) { [] }
it { is_expected.to eq(0) }
end
context 'given [:administrator]' do
let(:value) { [:administrator] }
it { is_expected.to eq(1) }
end
context 'given [:cs]' do
let(:value) { [:cs] }
it { is_expected.to eq(2) }
end
context 'given %i[administrator cs]' do
let(:value) { %i[administrator cs] }
it { is_expected.to eq(3) }
end
context 'given [:developer]' do
let(:value) { [:developer] }
it { is_expected.to eq(4) }
end
context 'given %i[administrator developer]' do
let(:value) { %i[administrator developer] }
it { is_expected.to eq(5) }
end
context 'given %i[cs developer]' do
let(:value) { %i[cs developer] }
it { is_expected.to eq(6) }
end
context 'given %i[administrator cs developer]' do
let(:value) { %i[administrator cs developer] }
it { is_expected.to eq(7) }
end
context 'given [:operator]' do
let(:value) { [:operator] }
it { is_expected.to eq(8) }
end
context 'given %i[administrator operator]' do
let(:value) { %i[administrator operator] }
it { is_expected.to eq(9) }
end
context 'given %i[cs operator]' do
let(:value) { %i[cs operator] }
it { is_expected.to eq(10) }
end
context 'given %i[administrator cs operator]' do
let(:value) { %i[administrator cs operator] }
it { is_expected.to eq(11) }
end
context 'given %i[developer operator]' do
let(:value) { %i[developer operator] }
it { is_expected.to eq(12) }
end
context 'given %i[administrator developer operator]' do
let(:value) { %i[administrator developer operator] }
it { is_expected.to eq(13) }
end
context 'given %i[cs developer operator]' do
let(:value) { %i[cs developer operator] }
it { is_expected.to eq(14) }
end
context 'given %i[administrator cs developer operator]' do
let(:value) { %i[administrator cs developer operator] }
it { is_expected.to eq(15) }
end
end
describe '#assert_valid_value' do
subject do
-> { instance.assert_valid_value(value) }
end
context 'given nil' do
let(:value) { nil }
it { is_expected.to_not raise_error }
end
context 'given 0' do
let(:value) { 0 }
it { is_expected.to_not raise_error }
end
context 'given 15' do
let(:value) { 15 }
it { is_expected.to_not raise_error }
end
context 'given 16' do
let(:value) { 16 }
it { is_expected.to raise_error(ArgumentError) }
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment