Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/bkuhlmann/wholable

A whole value object enabler.
https://github.com/bkuhlmann/wholable

equality value whole

Last synced: 11 days ago
JSON representation

A whole value object enabler.

Awesome Lists containing this project

README

        

:toc: macro
:toclevels: 5
:figure-caption!:

:data_link: link:https://alchemists.io/articles/ruby_data[Data]
:pattern_matching_link: link:https://alchemists.io/articles/ruby_pattern_matching[pattern matching]
:ruby_link: link:https://www.ruby-lang.org[Ruby]
:data_link: link:https://alchemists.io/articles/ruby_data[Data]
:structs_link: link:https://alchemists.io/articles/ruby_structs[Structs]

= Wholable

⚠️ *This gem is deprecated and will be fully destroyed on 2025-01-10. Please use the link:https://alchemists.io/projects/wholeable[Wholeable] gem instead because it's identical and fixes the spelling of the name.* ⚠️

Wholable allows you to turn your object into a _whole value object_ by ensuring object equality is determined by the values of the object instead of by identity. Whole value objects -- or value objects in general -- have the following traits as also noted via link:https://en.wikipedia.org/wiki/Value_object[Wikipedia]:

* Equality is determined by the values that make up an object and not by link:https://en.wikipedia.org/wiki/Identity_(object-oriented_programming)[identity] (i.e. memory address) which is the default behavior for all {ruby_link} objects except for {data_link} and {structs_link}.
* Identity remains unique since two objects can have the same values but different identity. This means `BasicObject#equal?` is never overwritten -- which is strongly discouraged -- as per link:https://rubyapi.org/o/basicobject#method-i-3D-3D[BasicObject] documentation.
* Value objects should be immutable (i.e. frozen) by default. This implementation enforces a strict adherence to immutability in order to ensure value objects remain equal and discourage mutation.

toc::[]

== Features

* Ensures equality (i.e. `#==` and `#eql?`) is determined by attribute values and not object identity (i.e. `#equal?`).
* Allows you to compare two objects of same or different types and see their differences.
* Provides {pattern_matching_link}.
* Automatically defines public attribute readers (i.e. `.attr_reader`) based on provided keys.
* Ensures object inspection (i.e. `#inspect`) shows all registered attributes.
* Ensures object is frozen upon initialization.

== Requirements

. {ruby_link}.

== Setup

To install _with_ security, run:

[source,bash]
----
# 💡 Skip this line if you already have the public certificate installed.
gem cert --add <(curl --compressed --location https://alchemists.io/gems.pem)
gem install wholable --trust-policy HighSecurity
----

To install _without_ security, run:

[source,bash]
----
gem install wholable
----

You can also add the gem directly to your project:

[source,bash]
----
bundle add wholable
----

Once the gem is installed, you only need to require it:

[source,ruby]
----
require "wholable"
----

== Usage

To use, include Wholable along with a list of attributes that make up your whole value object:

[source,ruby]
----
class Person
include Wholable[:name, :email]

def initialize name:, email:
@name = name
@email = email
end
end

jill = Person.new name: "Jill Smith", email: "[email protected]"
jill_two = Person.new name: "Jill Smith", email: "[email protected]"
jack = Person.new name: "Jack Smith", email: "[email protected]"

jill.name # "Jill Smith"
jill.email # "[email protected]"

jill.frozen? # true
jill_two.frozen? # true
jack.frozen? # true

jill.inspect # "#"
jill_two.inspect # "#"
jack.inspect # "#"

jill == jill # true
jill == jill_two # true
jill == jack # false

jill.diff(jill) # {}
jill.diff(jack) # {
# name: ["Jill Smith", "Jack Smith"],
# email: ["[email protected]", "[email protected]"]
# }
jill.diff(Object.new) # {:name=>["Jill Smith", nil], :email=>["[email protected]", nil]}

jill.eql? jill # true
jill.eql? jill_two # true
jill.eql? jack # false

jill.equal? jill # true
jill.equal? jill_two # false
jill.equal? jack # false

jill.hash # 3650965837788801745
jill_two.hash # 3650965837788801745
jack.hash # 4460658980509842640

jill.to_a # ["Jill Smith", "[email protected]"]
jack.to_a # ["Jack Smith", "[email protected]"]

jill.to_h # {:name=>"Jill Smith", :email=>"[email protected]"}
jack.to_h # {:name=>"Jack Smith", :email=>"[email protected]"}

jill.with name: "Sue" # #
jill.with bad: "!" # unknown keyword: :bad (ArgumentError)
----

As you can see, object equality is determined by the object's values and _not_ by the object's identity. When you include `Wholable` along with a list of keys, the following happens:

. The corresponding _public_ `attr_reader` for each key is created which saves you time and reduces double entry when implementing your whole value object.
. The `#to_a` and `#to_h` methods are added for convenience in order to play nice with {data_link} and {structs_link}.
. The `#deconstruct` and `#deconstruct_keys` aliases are created so you can leverage {pattern_matching_link}.
. The `#==`, `#eql?`, `#hash`, `#inspect`, and `#with` methods are added to provide whole value behavior.
. The object is immediately frozen after initialization to ensure your instance is _immutable_ by default.

== Caveats

Whole values can be broken via the following:

* *Duplication*: Sending the `#dup` message will cause your whole value object to be unfrozen. This might be desired in certain situations but make sure to refreeze when able.
* *Post Attributes*: Adding additional attributes after what is defined when including `Wholable` will break your whole value object. To prevent this, let Wholable manage this for you (easiest). Otherwise (harder), you can manually override `#==`, `#eql?`, `#hash`, `#inspect`, `#to_a`, and `#to_h` behavior at which point you don't need Wholable anymore.
* *Deep Freezing*: The automatic freezing of your instances is shallow and will not deeply freeze nested attributes. This behavior mimics the behavior of {data_link} objects.

== Development

To contribute, run:

[source,bash]
----
git clone https://github.com/bkuhlmann/wholable
cd wholable
bin/setup
----

You can also use the IRB console for direct access to all objects:

[source,bash]
----
bin/console
----

== Tests

To test, run:

[source,bash]
----
bin/rake
----

== link:https://alchemists.io/policies/license[License]

== link:https://alchemists.io/policies/security[Security]

== link:https://alchemists.io/policies/code_of_conduct[Code of Conduct]

== link:https://alchemists.io/policies/contributions[Contributions]

== link:https://alchemists.io/policies/developer_certificate_of_origin[Developer Certificate of Origin]

== link:https://alchemists.io/projects/wholable/versions[Versions]

== link:https://alchemists.io/community[Community]

== Credits

* Built with link:https://alchemists.io/projects/gemsmith[Gemsmith].
* Engineered by link:https://alchemists.io/team/brooke_kuhlmann[Brooke Kuhlmann].