From b6a00655cae4f1d1e62fe10422bada8a52db3ea3 Mon Sep 17 00:00:00 2001 From: hatsu38 Date: Sat, 31 Aug 2024 00:21:53 +0900 Subject: [PATCH 1/4] Add flexible enum support to ActiveHash::Enum - Implement `enum` method to support both array and hash-style definitions - Allow predicate methods generation for enum values - Support Rails-like enum syntax: `enum status: [:draft, :published, :archived]` - Support custom value mapping: `enum action: { like: 'LIKE', comment: 'COMMENT', ... }` - Add comprehensive tests for both enum definition styles This enhancement allows ActiveHash::Enum to handle enum definitions similar to Rails, improving compatibility and ease of use. It provides a more flexible way to define and use enums in ActiveHash models. --- lib/enum/enum.rb | 11 +++++++ spec/enum/enum_spec.rb | 67 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/lib/enum/enum.rb b/lib/enum/enum.rb index c331f389..fef66651 100644 --- a/lib/enum/enum.rb +++ b/lib/enum/enum.rb @@ -14,6 +14,17 @@ def enum_accessor(*field_names) reload end + def enum(columns) + columns.each do |column, values| + values = values.zip(values.map(&:to_s)).to_h if values.is_a?(Array) + values.each do |method, value| + define_method("#{method}?") do + send(column) == value + end + end + end + end + def insert(record) super set_constant(record) if defined?(@enum_accessors) diff --git a/spec/enum/enum_spec.rb b/spec/enum/enum_spec.rb index 68956ed3..39b18669 100644 --- a/spec/enum/enum_spec.rb +++ b/spec/enum/enum_spec.rb @@ -79,6 +79,73 @@ class Neighborhood < ActiveHash::Base expect(Movie::THE_INFORMANT.name).to eq('The Informant!') expect(Movie::IN_OUT.name).to eq('In & Out') end + + describe "enum(columns)" do + it "defines a predicate method for each value in the enum" do + Article = Class.new(ActiveHash::Base) do + include ActiveHash::Enum + + self.data = [ + { name: 'Article 1', status: 'draft'}, + { name: 'Article 2', status: 'published'}, + { name: 'Article 3', status: 'archived'} + ] + + enum_accessor :name + + enum status: [:draft, :published, :archived] + end + + expect(Article::ARTICLE_1.draft?).to be_truthy + expect(Article::ARTICLE_1.published?).to be_falsey + expect(Article::ARTICLE_1.archived?).to be_falsey + + expect(Article::ARTICLE_2.draft?).to be_falsey + expect(Article::ARTICLE_2.published?).to be_truthy + expect(Article::ARTICLE_2.archived?).to be_falsey + + expect(Article::ARTICLE_3.draft?).to be_falsey + expect(Article::ARTICLE_3.published?).to be_falsey + expect(Article::ARTICLE_3.archived?).to be_truthy + end + + it "defines a predicate method for each value in the enum" do + NotifyType = Class.new(ActiveHash::Base) do + include ActiveHash::Enum + + self.data = [ + { name: 'Like', action: 'LIKE'}, + { name: 'Comment', action: 'COMMENT'}, + { name: 'Follow', action: 'FOLLOW'}, + { name: 'Mention', action: 'MENTION'} + ] + + enum_accessor :name + + enum action: { like: 'LIKE', comment: 'COMMENT', follow: 'FOLLOW', mention: 'MENTION' } + end + + expect(NotifyType::LIKE.like?).to be_truthy + expect(NotifyType::LIKE.comment?).to be_falsey + expect(NotifyType::LIKE.follow?).to be_falsey + expect(NotifyType::LIKE.mention?).to be_falsey + + expect(NotifyType::COMMENT.like?).to be_falsey + expect(NotifyType::COMMENT.comment?).to be_truthy + expect(NotifyType::COMMENT.follow?).to be_falsey + expect(NotifyType::COMMENT.mention?).to be_falsey + + expect(NotifyType::FOLLOW.like?).to be_falsey + expect(NotifyType::FOLLOW.comment?).to be_falsey + expect(NotifyType::FOLLOW.follow?).to be_truthy + expect(NotifyType::FOLLOW.mention?).to be_falsey + + expect(NotifyType::MENTION.like?).to be_falsey + expect(NotifyType::MENTION.comment?).to be_falsey + expect(NotifyType::MENTION.follow?).to be_falsey + expect(NotifyType::MENTION.mention?).to be_truthy + end + end end context "ActiveHash with an enum_accessor set" do From 81a1d8c0bf9a05d34fbe4227fe763ca90d763d33 Mon Sep 17 00:00:00 2001 From: hatsu38 Date: Sat, 31 Aug 2024 22:54:50 +0900 Subject: [PATCH 2/4] refactor: use class_eval --- lib/enum/enum.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/enum/enum.rb b/lib/enum/enum.rb index fef66651..6f07ebcf 100644 --- a/lib/enum/enum.rb +++ b/lib/enum/enum.rb @@ -15,14 +15,14 @@ def enum_accessor(*field_names) end def enum(columns) + method_definitions = [] columns.each do |column, values| values = values.zip(values.map(&:to_s)).to_h if values.is_a?(Array) values.each do |method, value| - define_method("#{method}?") do - send(column) == value - end + method_definitions << "def #{method}?; #{column} == '#{value}'; end" end end + class_eval(method_definitions.uniq.join(";")) end def insert(record) From 3a7a17f5d3c680424852999fe78e676241bfc092 Mon Sep 17 00:00:00 2001 From: hatsu38 Date: Wed, 4 Sep 2024 09:59:26 +0900 Subject: [PATCH 3/4] feat: add inspect --- lib/enum/enum.rb | 2 +- spec/enum/enum_spec.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/enum/enum.rb b/lib/enum/enum.rb index 6f07ebcf..e0223050 100644 --- a/lib/enum/enum.rb +++ b/lib/enum/enum.rb @@ -19,7 +19,7 @@ def enum(columns) columns.each do |column, values| values = values.zip(values.map(&:to_s)).to_h if values.is_a?(Array) values.each do |method, value| - method_definitions << "def #{method}?; #{column} == '#{value}'; end" + method_definitions << "def #{method}?; #{column} == #{value.inspect}; end" end end class_eval(method_definitions.uniq.join(";")) diff --git a/spec/enum/enum_spec.rb b/spec/enum/enum_spec.rb index 39b18669..8f089d88 100644 --- a/spec/enum/enum_spec.rb +++ b/spec/enum/enum_spec.rb @@ -109,20 +109,20 @@ class Neighborhood < ActiveHash::Base expect(Article::ARTICLE_3.archived?).to be_truthy end - it "defines a predicate method for each value in the enum" do + it "multi type data (ex: string, integer and symbol) enum" do NotifyType = Class.new(ActiveHash::Base) do include ActiveHash::Enum self.data = [ { name: 'Like', action: 'LIKE'}, - { name: 'Comment', action: 'COMMENT'}, - { name: 'Follow', action: 'FOLLOW'}, + { name: 'Comment', action: 1}, + { name: 'Follow', action: :FOLLOW}, { name: 'Mention', action: 'MENTION'} ] enum_accessor :name - enum action: { like: 'LIKE', comment: 'COMMENT', follow: 'FOLLOW', mention: 'MENTION' } + enum action: { like: 'LIKE', comment: 1, follow: :FOLLOW, mention: 'MENTION' } end expect(NotifyType::LIKE.like?).to be_truthy From 2173c30f1900ca490b18125c87b89698f8399fd2 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Wed, 4 Sep 2024 08:13:21 -0400 Subject: [PATCH 4/4] Follow Rails project conventions for class_eval Setting the line number properly helps with stack walkbacks and debugging. --- lib/enum/enum.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/enum/enum.rb b/lib/enum/enum.rb index e0223050..3ba5a7c4 100644 --- a/lib/enum/enum.rb +++ b/lib/enum/enum.rb @@ -15,14 +15,17 @@ def enum_accessor(*field_names) end def enum(columns) - method_definitions = [] columns.each do |column, values| values = values.zip(values.map(&:to_s)).to_h if values.is_a?(Array) values.each do |method, value| - method_definitions << "def #{method}?; #{column} == #{value.inspect}; end" + class_eval <<~METHOD, __FILE__, __LINE__ + 1 + # frozen_string_literal: true + def #{method}? + #{column} == #{value.inspect} + end + METHOD end end - class_eval(method_definitions.uniq.join(";")) end def insert(record)