Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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.
- Host: GitHub
- URL: https://github.com/bkuhlmann/wholable
- Owner: bkuhlmann
- License: other
- Created: 2023-07-27T22:38:40.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-10-04T16:22:25.000Z (about 2 months ago)
- Last Synced: 2024-10-04T19:49:25.286Z (about 2 months ago)
- Topics: equality, value, whole
- Language: Ruby
- Homepage: https://alchemists.io/projects/wholable
- Size: 132 KB
- Stars: 1
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.adoc
- Funding: .github/FUNDING.yml
- License: LICENSE.adoc
- Citation: CITATION.cff
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
endjill = 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? # truejill.inspect # "#"
jill_two.inspect # "#"
jack.inspect # "#"jill == jill # true
jill == jill_two # true
jill == jack # falsejill.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 # falsejill.equal? jill # true
jill.equal? jill_two # false
jill.equal? jack # falsejill.hash # 3650965837788801745
jill_two.hash # 3650965837788801745
jack.hash # 4460658980509842640jill.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].