Skip to content

Commit

Permalink
ActiveModel type for flags
Browse files Browse the repository at this point in the history
  • Loading branch information
joeldrapper committed Nov 21, 2024
1 parent 8a9fc35 commit a8a3ba5
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 4 deletions.
21 changes: 17 additions & 4 deletions lib/literal/flags.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ def initialize(value = 0, **new_flags)
end

@value = value

freeze
end

Expand Down Expand Up @@ -138,6 +137,15 @@ def pack
[@value].pack(self.class::PACKER)
end

def with(**attributes)
new_val = attributes.reduce(value) do |value, (k, v)|
v ? value |= bitmap(k) : value &= ~(bitmap(k))
value
end

self.class.allocate.__initialize_from_value__(new_val)
end

# () -> String
def inspect
to_h.inspect
Expand All @@ -156,7 +164,7 @@ def each

# (Symbol) -> Boolean
def [](key)
@value & (2 ** self.class::FLAGS.fetch(key)) > 0
@value & (bitmap(key)) > 0
end

def |(other)
Expand Down Expand Up @@ -197,14 +205,19 @@ def to_a

def deconstruct_keys(keys = nil)
if keys
flags = self.class::FLAGS
keys.to_h do |key|
[key, @value & (2 ** flags.fetch(key)) > 0]
[key, @value & (bitmap(key)) > 0]
end
else
to_h
end
end

private

def bitmap(key)
2 ** self.class::FLAGS.fetch(key)
end
end

class Literal::Flags8 < Literal::Flags
Expand Down
1 change: 1 addition & 0 deletions lib/literal/rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@

module Literal::Rails
autoload :EnumType, "literal/rails/enum_type"
autoload :FlagsType, "literal/rails/flags_type"
autoload :EnumSerializer, "literal/rails/enum_serializer"
end
41 changes: 41 additions & 0 deletions lib/literal/rails/flags_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

class Literal::Rails::FlagsType < ActiveModel::Type::Value
def initialize(flags_class)
@flags_class = flags_class
super()
end

def cast(value)
case value
when @flags_class
value
else
deserialize(value)
end
end

def serialize(value)
case value
when nil
nil
when @flags_class
value.to_bit_string
else
raise Literal::ArgumentError.new(
"Invalid value: #{value.inspect}. Expected an #{@flags_class.inspect}.",
)
end
end

def deserialize(value)
case value
when nil
nil
else
@flags_class.from_bit_string(value) || raise(
ArgumentError.new("Invalid value: #{value.inspect} for #{@flags_class}"),
)
end
end
end
8 changes: 8 additions & 0 deletions lib/literal/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@ class Literal::Railtie < Rails::Railtie
Literal::Rails::EnumType.new(type)
end

ActiveRecord::Type.register(:literal_flags) do |name, type:|
Literal::Rails::FlagsType.new(type)
end

ActiveModel::Type.register(:literal_enum) do |name, type:|
Literal::Rails::EnumType.new(type)
end

ActiveModel::Type.register(:literal_flags) do |name, type:|
Literal::Rails::FlagsType.new(type)
end
end
end
13 changes: 13 additions & 0 deletions test/flags.test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ class Example < Literal::Flags8
)
end

test "with" do
a = Example.new(italic: true, underlined: true)
b = a.with(bold: true, underlined: false)

refute a.bold?
assert a.italic?
assert a.underlined?

assert b.bold?
assert b.italic?
refute b.underlined?
end

test "from_array" do
flags = Example.from_array([false, false, false, false, false, false, false, true])

Expand Down

0 comments on commit a8a3ba5

Please sign in to comment.