From 3e595c45408845d67070716949df1d5c9480c2a6 Mon Sep 17 00:00:00 2001 From: Daniel Ma Date: Thu, 18 Apr 2024 16:06:57 -0700 Subject: [PATCH] Support Ruby 2.7+ Pattern Matching Add `#deconstruct_keys` to `Expression::Base` and `Schedule` so that they can be used in pattern matches. In our products, we've been using `#to_h` for this, and it would be nice sugar to have it built in. A common example we've used in our products is building a humanized-readable version of a schedule. An extremely stripped down version might look something like this ```ruby case existing_user_schedule in weekday: { weekday: } "Every #{weekday}" in day_in_month: { day: } "Last #{day} of every month" end ``` --- CHANGELOG.md | 4 ++++ README.md | 17 +++++++++++++++++ lib/repeatable/expression/base.rb | 5 +++++ lib/repeatable/schedule.rb | 5 +++++ spec/repeatable/schedule_spec.rb | 8 ++++++++ spec/support/expression_examples.rb | 6 ++++++ 6 files changed, 45 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7599e5b..7daa83b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ### Unreleased +Changes: + +* Support pattern matching for `Expression` and `Schedule` + [Commits](https://github.com/molawson/repeatable/compare/v1.1.0...main) ### 1.1.0 (2022-02-25) diff --git a/README.md b/README.md index b166ea0..79ff2fb 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,21 @@ schedule.to_h # can be used to recreate an identical Schedule object at a later time ``` +#### Pattern Matching + +Both `Repeatable::Schedule` and all `Repeatable::Expression` classes support Ruby 2.7+ [pattern matching][ruby-pattern-matching] which is particularly useful for parsing or presenting an existing schedule. + +```ruby +case schedule +in weekday: { weekday: } + "Weekly on #{Date::DAYNAMES[weekday]}" +in day_in_month: { day: } + "Every month on the #{day.ordinalize}" +in weekday_in_month: { weekday:, count: } + "Every month on the #{count.ordinalize} #{Date::DAYNAMES[weekday]}" +end +``` + #### Equivalence Both `Repeatable::Schedule` and all `Repeatable::Expression` classes have equivalence `#==` defined according to what's appropriate for each class, so regardless of the order of arguments passed to each, you can tell whether one object is equivalent to the other in terms of whether or not, when asked the same questions, you'd receive the same results from each. @@ -218,3 +233,5 @@ The gem is available as open source under the terms of the [MIT License](https:/ ## Code of Conduct Everyone interacting in the Repeatable project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/molawson/repeatable/blob/main/CODE_OF_CONDUCT.md). + +[ruby-pattern-matching]: https://docs.ruby-lang.org/en/3.0/syntax/pattern_matching_rdoc.html diff --git a/lib/repeatable/expression/base.rb b/lib/repeatable/expression/base.rb index 17e4cff..33f33ff 100644 --- a/lib/repeatable/expression/base.rb +++ b/lib/repeatable/expression/base.rb @@ -26,6 +26,11 @@ def to_h {hash_key => hash_value} end + sig { params(_keys: T.nilable(T::Array[Symbol])).returns(T::Hash[Symbol, T.any(Types::SymbolHash, T::Array[Types::SymbolHash])]) } + def deconstruct_keys(_keys) + to_h + end + sig { params(other: Expression::Base).returns(Expression::Union) } def union(other) Union.new(self, other) diff --git a/lib/repeatable/schedule.rb b/lib/repeatable/schedule.rb index c05d55c..b4a030d 100644 --- a/lib/repeatable/schedule.rb +++ b/lib/repeatable/schedule.rb @@ -54,6 +54,11 @@ def to_h expression.to_h end + sig { params(_keys: T.nilable(T::Array[Symbol])).returns(T::Hash[Symbol, T.any(Types::SymbolHash, T::Array[Types::SymbolHash])]) } + def deconstruct_keys(_keys) + to_h + end + sig { params(other: Object).returns(T::Boolean) } def ==(other) other.is_a?(self.class) && expression == other.expression diff --git a/spec/repeatable/schedule_spec.rb b/spec/repeatable/schedule_spec.rb index 095c80d..7b9e728 100644 --- a/spec/repeatable/schedule_spec.rb +++ b/spec/repeatable/schedule_spec.rb @@ -328,6 +328,14 @@ module Repeatable end end + describe "pattern matching" do + let(:arg) { nested_set_expression_hash } + + it "can #deconstruct_keys" do + expect(subject.deconstruct_keys(nil)).to eq subject.to_h + end + end + describe "#==" do it "returns true if the schedules have the same identity" do schedule = described_class.new(nested_set_expression_object) diff --git a/spec/support/expression_examples.rb b/spec/support/expression_examples.rb index 45858a2..c8b86d0 100644 --- a/spec/support/expression_examples.rb +++ b/spec/support/expression_examples.rb @@ -11,4 +11,10 @@ expect { subject.to_h }.not_to raise_error end end + + describe "pattern matching" do + it "can #deconstruct_keys" do + expect(subject.deconstruct_keys(nil)).to be_a(Hash) + end + end end