Created
March 9, 2018 15:10
-
-
Save alpaca-tc/b5b9bd0ebbf86c20f14484cc84bf8f26 to your computer and use it in GitHub Desktop.
bitmask
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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