Provide pattern matching for ActiveModel

It would be nice to be able to pattern match against ActiveModel (and
transitively ActiveRecord). If you want to check multiple attributes
with conditions, it's nice to be able use the pattern matching syntax.

For example:

```ruby
case Current.user
in { superuser: true }
  "Thanks for logging in. You are a superuser."
in { admin: true, name: }
  "Thanks for logging in, admin #{name}!"
in { name: }
  "Welcome, #{name}!"
end
```
This commit is contained in:
Kevin Newton 2022-05-06 16:34:07 -04:00
parent e1a9b3d861
commit 7e499b25ac
No known key found for this signature in database
GPG Key ID: FBE789CDA03B16F6
3 changed files with 64 additions and 0 deletions

View File

@ -1,3 +1,21 @@
* Define `deconstruct_keys` in `ActiveModel::AttributeMethods`
This provides the Ruby 2.7+ pattern matching interface for hash patterns,
which allows the user to pattern match against anything that includes the
`ActiveModel::AttributeMethods` module (e.g., `ActiveRecord::Base`). As an
example, you can now:
```ruby
class Person < ActiveRecord::Base
end
person = Person.new(name: "Mary")
person => { name: }
name # => "Mary"
```
*Kevin Newton*
* Fix casting long strings to `Date`, `Time` or `DateTime`
*fatkodima*

View File

@ -489,6 +489,43 @@ module ActiveModel
end
end
# Returns a hash of attributes for the given keys. Provides the pattern
# matching interface for matching against hash patterns. For example:
#
# class Person
# include ActiveModel::AttributeMethods
#
# attr_accessor :name
# define_attribute_method :name
# end
#
# def greeting_for(person)
# case person
# in { name: "Mary" }
# "Welcome back, Mary!"
# in { name: }
# "Welcome, stranger!"
# end
# end
#
# person = Person.new
# person.name = "Mary"
# greeting_for(person) # => "Welcome back, Mary!"
#
# person = Person.new
# person.name = "Bob"
# greeting_for(person) # => "Welcome, stranger!"
def deconstruct_keys(keys)
deconstructed = {}
keys.each do |key|
string_key = key.to_s
deconstructed[key] = _read_attribute(string_key) if attribute_method?(string_key)
end
deconstructed
end
private
def attribute_method?(attr_name)
respond_to_without_attributes?(:attributes) && attributes.include?(attr_name)

View File

@ -175,5 +175,14 @@ module ActiveModel
ModelForAttributesTest.attribute :foo, :unknown
end
end
test "pattern matching against keys" do
case ModelForAttributesTest.new(integer_field: 1)
in { integer_field: 1 }
assert(true)
else
assert(false, "Failed to pattern match")
end
end
end
end