Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Literal::Enum indexes #64

Merged
merged 5 commits into from
Nov 21, 2023
Merged
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
71 changes: 63 additions & 8 deletions lib/literal/enum.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# frozen_string_literal: true

class Literal::Enum
extend Literal::Types
include Literal::ModuleDefined
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should extend Literal::Types here now that index accepts a type argument.


IndexDefinition = Data.define(:name, :type, :unique, :proc)

class << self
include Enumerable

Expand All @@ -17,13 +22,6 @@ def respond_to_missing?(name, include_private = false)
end

def inherited(subclass)
TracePoint.trace(:end) do |tp|
if Class === tp.self && tp.self < Literal::Enum
tp.self.deep_freeze
tp.disable
end
end

subclass.instance_exec do
@values = {}
@members = []
Expand Down Expand Up @@ -54,7 +52,64 @@ def method_missing(name, value, *args, **kwargs, &)
define_method("#{name.to_s.gsub(/([^A-Z])([A-Z]+)/, '\1_\2').downcase}?") { self == member }
end

def deep_freeze
def index(name, type, unique: false, &block)
raise ArgumentError unless Symbol === name
raise ArgumentError if frozen?

@index_definitions ||= {}
@indexes = {}

@index_definitions[name] = IndexDefinition.new(
name:, type:, unique:, proc: block || name.to_proc
)
end

def where(**kwargs)
unless kwargs.length == 1
raise ArgumentError, "You can only specify one index when using `where`."
end

key, value = kwargs.first

@indexes.fetch(key)[value]
end

def find_by(**kwargs)
unless kwargs.length == 1
raise ArgumentError, "You can only specify one index when using `find_by`."
end

key, value = kwargs.first

unless @index_definitions.fetch(key).unique
raise ArgumentError, "You can only use `find_by` on unique indexes."
end

@indexes.fetch(key)[value][0]
end

def after_defined
raise ArgumentError if frozen?

@index_definitions&.each_value do |definition|
index = @members.group_by(&definition.proc).freeze

index.each do |key, value|
unless definition.type === key
raise Literal::TypeError.expected(key, to_be_a: definition.type)
end

if definition.unique && value.length > 1
raise ArgumentError, "Index #{name} is not unique for #{key}."
end
end

@indexes[definition.name] = index
end

@index_definitions.freeze
@indexes.freeze

@values.freeze
@members.freeze
freeze
Expand Down
18 changes: 18 additions & 0 deletions lib/literal/module_defined.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Literal::ModuleDefined
module ClassMethods
def after_defined
end
end

def self.included(klass)
klass.extend(ClassMethods)
end
end

TracePoint.trace(:end) do |tp|
if Class === tp.self && tp.self < Literal::ModuleDefined
tp.self.after_defined
end
end
48 changes: 44 additions & 4 deletions test/literal/enum.test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,30 @@
extend Literal::Types

class Color < Literal::Enum(Integer)
Red(0)
Green(1)
Blue(3)
LightRed(4)
attr_accessor :rgb

index :rgb, Array, unique: true
index :hex, String, unique: true

def hex
rgb.map { |c| c.to_s(16).rjust(2, "0") }.join
end

Red(0) do
self.rgb = [255, 0, 0]
end

Green(1) do
self.rgb = [0, 255, 0]
end

Blue(3) do
self.rgb = [0, 0, 255]
end

LightRed(4) do
self.rgb = [255, 128, 128]
end
end

class Switch < Literal::Enum(_Boolean)
Expand All @@ -19,6 +39,26 @@ def toggle = Switch::On
end
end

test "where" do
expect(
Color.where(rgb: [0, 0, 255])
) == [Color::Blue]

expect(
Color.where(hex: "0000ff")
) == [Color::Blue]
end

test "find_by" do
expect(
Color.find_by(rgb: [0, 0, 255])
) == Color::Blue

expect(
Color.find_by(hex: "0000ff")
) == Color::Blue
end

test "handle" do
output = Color::Red.handle do |c|
c.when(Color::Red) { "red" }
Expand Down