{"id":15608959,"url":"https://github.com/serradura/u-struct","last_synced_at":"2025-10-07T16:06:36.512Z","repository":{"id":43002574,"uuid":"434260712","full_name":"serradura/u-struct","owner":"serradura","description":"Create powered Ruby structs.","archived":false,"fork":false,"pushed_at":"2022-03-24T02:39:43.000Z","size":220,"stargazers_count":18,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-14T02:50:00.130Z","etag":null,"topics":["immutability","ruby","rubygem","u-gems"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/serradura.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-12-02T14:49:34.000Z","updated_at":"2024-10-16T12:36:46.000Z","dependencies_parsed_at":"2022-08-23T14:40:56.502Z","dependency_job_id":null,"html_url":"https://github.com/serradura/u-struct","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serradura%2Fu-struct","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serradura%2Fu-struct/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serradura%2Fu-struct/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serradura%2Fu-struct/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/serradura","download_url":"https://codeload.github.com/serradura/u-struct/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251309839,"owners_count":21568913,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["immutability","ruby","rubygem","u-gems"],"created_at":"2024-10-03T05:40:37.663Z","updated_at":"2025-10-07T16:06:31.485Z","avatar_url":"https://github.com/serradura.png","language":"Ruby","readme":"\u003cp align=\"center\"\u003e\n  \u003ch1 align=\"center\"\u003e🧱 μ-struct\u003c/h1\u003e\n  \u003cp align=\"center\"\u003e\u003ci\u003eCreate powered Ruby structs.\u003c/i\u003e\u003c/p\u003e\n  \u003cbr\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/ruby%20%3E=%202.2,%20%3C%203.2-ruby.svg?colorA=99004d\u0026colorB=cc0066\" alt=\"Ruby\"\u003e\n  \u003ca href=\"https://rubygems.org/gems/u-struct\"\u003e\n    \u003cimg alt=\"Gem\" src=\"https://img.shields.io/gem/v/u-struct.svg?style=flat-square\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/serradura/u-struct/actions/workflows/ci.yml\"\u003e\n    \u003cimg alt=\"Build Status\" src=\"https://github.com/serradura/u-struct/actions/workflows/ci.yml/badge.svg\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://codeclimate.com/github/serradura/u-struct/maintainability\"\u003e\n    \u003cimg alt=\"Maintainability\" src=\"https://api.codeclimate.com/v1/badges/2cc0204411cc2b392b7a/maintainability\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://codeclimate.com/github/serradura/u-struct/test_coverage\"\u003e\n    \u003cimg alt=\"Test Coverage\" src=\"https://api.codeclimate.com/v1/badges/2cc0204411cc2b392b7a/test_coverage\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n# Table of contents: \u003c!-- omit in toc --\u003e\n- [Introduction](#introduction)\n  - [Motivation](#motivation)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [`Micro::Struct.new`](#microstructnew)\n    - [`optional:` option](#optional-option)\n    - [`required:` option](#required-option)\n    - [Defining custom methods/behavior](#defining-custom-methodsbehavior)\n  - [`Micro::Struct.with`](#microstructwith)\n  - [`Micro::Struct[]`](#microstruct)\n    - [`:to_ary`](#to_ary)\n    - [`:to_hash`](#to_hash)\n    - [`:to_proc`](#to_proc)\n    - [`:readonly`](#readonly)\n    - [`:instance_copy`](#instance_copy)\n    - [`:exposed_features`](#exposed_features)\n  - [`Micro::Struct.instance` or `Micro::Struct.with(...).instance`](#microstructinstance-or-microstructwithinstance)\n  - [`Micro::Struct.immutable`](#microstructimmutable)\n  - [`Micro::Struct.readonly`](#microstructreadonly)\n  - [TL;DR](#tldr)\n- [FAQ](#faq)\n  - [How to override the Struct `.new` method?](#how-to-override-the-struct-new-method)\n  - [Can I override the Struct initializer?](#can-i-override-the-struct-initializer)\n- [Development](#development)\n- [Contributing](#contributing)\n- [License](#license)\n- [Code of Conduct](#code-of-conduct)\n- [Contact](#contact)\n- [Acknowledgments](#acknowledgments)\n\n## Introduction\n\nRuby `Struct` is a versatile data structure because it can behave like an `Array`, `Hash`, and ordinary object:\n\n```ruby\nPerson = Struct.new(:first_name, :last_name)\n\nperson = Person.new('Rodrigo', 'Serradura')\n# #\u003cstruct Person first_name=\"Rodrigo\", last_name=\"Serradura\"\u003e\n\n# -- Ordinary object behavior --\n\nperson.first_name # \"Rodrigo\"\nperson.last_name  # \"Serradura\"\n\nperson.first_name = 'John' # \"John\"\nperson.last_name = 'Doe'   # \"Doe\"\n\nperson\n# #\u003cstruct Person first_name=\"John\", last_name=\"Doe\"\u003e\n\n# -- Hash behavior --\n\nperson[:first_name] # \"Doe\"\nperson['last_name'] # \"John\"\n\nperson[:first_name] = 'Rodrigo'   # \"Rodrigo\"\nperson['last_name'] = 'Serradura' # \"Serradura\"\n\nperson\n# #\u003cstruct Person first_name=\"Rodrigo\", last_name=\"Serradura\"\u003e\n\n\n# Transforming a Struct into a Hash\nperson.to_h\n# {:first_name=\u003e\"Rodrigo\", :last_name=\u003e\"Serradura\"}\n\n# -- Array behavior --\n\nperson[0] # \"Rodrigo\"\nperson[1] # \"Serradura\"\n\nperson[0] = 'John' # \"John\"\nperson[1] = 'Doe'  # \"Doe\"\n\nperson\n# #\u003cstruct Person first_name=\"John\", last_name=\"Doe\"\u003e\n\n# Transforming a Struct into an Array\nperson.to_a\n# [\"John\", \"Doe\"]\n```\n\nBecause of these characteristics, structs could be excellent candidates to create different kinds of POROs (Plain Old Ruby Objects). However, it is very common to see developers avoiding its usage because of some of its behaviors, like setters or the constructor's positional arguments. The addition of keywords arguments on its constructor ([available on Ruby \u003e= 2.5](https://www.bigbinary.com/blog/ruby-2-5-allows-creating-structs-with-keyword-arguments)) improved the experience to instantiate `Struct` objects but it doesn't require all the arguments. Some developers can still feel uncomfortable with that and they might avoid its usage.\n\nLook at the example showing the Struct's `keyword_init:` option creating a constructor with optional keyword arguments:\n\n```ruby\nPerson = Struct.new(:first_name, :last_name, keyword_init: true)\n\nPerson.superclass # Struct\n\nPerson.new\n# #\u003cstruct Person first_name=nil, last_name=nil\u003e\n\n# Because of this, you will only see an exception\n# if you pass one or more invalid keywords.\n\nPerson.new(foo: 1, bar: 2)\n# ArgumentError (unknown keywords: foo, bar)\n```\n\n### Motivation\n\nSo, given this introduction, the idea of this project is to provide a way of creating Ruby Structs with some [powerful features](#microstructwith). Let's see how the `Micro::Struct.new()` works.\n\n```ruby\nrequire 'u-struct'\n\nPerson = Micro::Struct.new(:first_name, :last_name)\n\nPerson.superclass\n# Struct\n\nPerson.new\n# ArgumentError (missing keywords: :first_name, :last_name)\n```\n\nAs you can see, the struct instantiation raised an error because all of the keywords arguments are required.\n\nIf you need one or many optional arguments, you can use the `optional:` option to define them:\n\n```ruby\nPerson = Micro::Struct.new(:first_name, optional: :last_name)\n\nPerson.new\n# ArgumentError (missing keyword: :first_name)\n\nPerson.new(first_name: 'Rodrigo')\n# #\u003cstruct Person first_name=\"Rodrigo\", last_name=nil\u003e\n```\n\nIf you want a `Struct` only with optional members (or attributes), as the `keyword_init:` option does, you can declare all attributes within the optional: option:\n\n```ruby\nPerson = Micro::Struct.new(optional: [:first_name, :last_name])\n\nPerson.new\n# #\u003cstruct Person first_name=nil, last_name=nil\u003e\n```\n\nYou can also use the `required:` option to define required attributes.\n\n```ruby\nPerson = Micro::Struct.new(\n  required: [:first_name, :last_name],\n  optional: [:age]\n)\n```\n\nSo, what did you think? If you liked it, continue the reading to understand what this gem can do for you.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'u-struct'\n```\n\nAnd then execute:\n\n    $ bundle install\n\nOr install it yourself as:\n\n    $ gem install u-struct\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n## Usage\n\n### `Micro::Struct.new`\n\nLike `Struct.new`, you will use `Micro::Struct.new` to create your `Struct` classes.\n\nThe key difference is: the `Struct` created from `Micro::Struct` will use keyword arguments in their constructors.\n\n```ruby\nPerson = Struct.new(:name)          # Person\nPersona = Micro::Struct.new(:name)  # Persona\n\nPerson.ancestors  # [Person, Struct, Enumerable, Object, Kernel, BasicObject]\nPersona.ancestors # [Person, Struct, Enumerable, Object, Kernel, BasicObject]\n\nPerson.new('Rodrigo')        # #\u003cstruct Person name=\"Rodrigo\"\u003e\nPersona.new(name: 'Rodrigo') # #\u003cstruct Person name=\"Rodrigo\"\u003e\n\nPerson.new  # #\u003cstruct Person name=nil\u003e\n\nPersona.new # ArgumentError (missing keyword: :name)\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n#### `optional:` option\n\nIf you need optional attributes, you can use this to define them.\n\n```ruby\nPerson = Micro::Struct.new(:name, optional: :age)\n\nPerson.new\n# ArgumentError (missing keyword: :name)\n\nPerson.new(name: 'John')\n# #\u003cstruct Person name=\"John\", age=nil\u003e\n```\n\nUse an array to define multiple optional attributes.\n\n```ruby\nPerson = Micro::Struct.new(:name, optional: [:age, :nickname])\n\nPerson.new\n# ArgumentError (missing keyword: :name)\n\nPerson.new(name: 'John')\n# #\u003cstruct Person name=\"John\", age=nil, nickname=nil\u003e\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n#### `required:` option\n\nIt is an alternative way to define required attributes. Use a symbol to define one or an array to define multiple attributes.\n\n```ruby\nPerson = Micro::Struct.new(\n  required: [:first_name, :last_name],\n  optional: [:age]\n)\n\nPerson.new\n# ArgumentError (missing keywords: :first_name, :last_name)\n\nPerson.new first_name: 'John', last_name: 'Doe'\n# #\u003cstruct Person first_name=\"John\", last_name=\"Doe\", age=nil\u003e\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n#### Defining custom methods/behavior\n\nThe `Micro::Struct.new` accepts a block as a regular `Struct`, and you can use it to define some custom behavior/methods.\n\n```ruby\nPerson = Micro::Struct.new(:first_name, :last_name, optional: :age) do\n  def name\n    \"#{first_name} #{last_name}\"\n  end\nend\n\nperson = Person.new(first_name: 'Rodrigo', last_name: 'Serradura')\n# #\u003cstruct Person first_name=\"Rodrigo\", last_name=\"Serradura\", age=nil\u003e\n\nperson.first_name # \"Rodrigo\"\nperson.last_name  # \"Serradura\"\nperson.name       # \"Rodrigo Serradura\"\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n### `Micro::Struct.with`\n\nThis method can do two things: first, it can create `Struct` factories; second, it sets some special behavior to their structs.\n\nThese are all of the available features which you can use (pick one, many, or all of them):\n- [`:to_ary`](#to_ary)\n- [`:to_hash`](#to_hash)\n- [`:to_proc`](#to_proc)\n- [`:readonly`](#readonly)\n- [`:instance_copy`](#instance_copy)\n- [`:exposed_features`](#exposed_features)\n\nLook at an example of defining a `Struct` factory that can create \"immutable\" structs by picking the `:readonly`, `:instance_copy` features.\n\n```ruby\nReadonlyStruct = Micro::Struct.with(:readonly, :instance_copy)\n\n# Use the factory to create structs with the same characteristics:\n\nPerson = ReadonlyStruct.new(:first_name, :last_name)\n\nperson = Person.new(first_name: 'Rodrigo', last_name: 'Rodrigues')\n# #\u003cstruct Person first_name=\"Rodrigo\", last_name=\"Rodrigues\"\u003e\n\n# The `:readonly` sets all the Struct writers as private.\n\nperson.last_name = ''\n# NoMethodError (private method `last_name=' called for #\u003cstruct Person ...\u003e)\n\nperson[:last_name] = ''\n# NoMethodError (private method `[]=' called for #\u003cstruct Person ...\u003e)\n\n# The `:instance_copy` defines a `#with` instance method,\n# which allows you to create a new instance from the current struct state.\n\nnew_person = person.with(last_name: 'Serradura')\n# #\u003cstruct Person first_name=\"Rodrigo\", last_name=\"Serradura\"\u003e\n\nnew_person == person\n# false\n\nnew_person.class == person.class\n# true\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n### `Micro::Struct[]`\n\nThe `[]` brackets method is as an alias of `Micro::Struct.with`. e.g.\n```ruby\nMicro::Struct[:readonly, :to_hash] # is the same as Micro::Struct.with(:readonly, :to_hash)\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n#### `:to_ary`\n\nDefines a `#to_ary` method which will invoke the struct `#to_a` method. If you override the `#to_a` method you will also affect it.\n\nThe `#to_ary` makes Ruby know how to deconstruct an object like an array.\n\n```ruby\nPerson = Micro::Struct.with(:to_ary).new(:first_name, :last_name)\n\nperson = Person.new(first_name: 'Rodrigo', last_name: 'Serradura')\n\nfirst_name, last_name = person\n\np first_name # \"Rodrigo\"\np last_name  # \"Serradura\"\n\n*first_and_last_name = person\n\np first_and_last_name # [\"Rodrigo\", \"Serradura\"]\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n#### `:to_hash`\n\nDefines a `#to_hash` method which will invoke the struct `#to_h` method. If you override the `#to_h` method you will also affect it.\n\nThe `#to_hash` makes Ruby know how to deconstruct an object like a hash.\n\n```ruby\nPerson = Micro::Struct.with(:to_hash).new(:first_name, :last_name)\n\nperson = Person.new(first_name: 'Rodrigo', last_name: 'Serradura')\n\ndef greet(first_name:, last_name:)\n  puts \"Hi #{first_name} #{last_name}!\"\nend\n\ngreet(**person)\n# Hi Rodrigo Serradura!\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n#### `:to_proc`\n\nThe `#to_proc` tells Ruby how to invoke it as a block replacement (by using `\u0026`).\n\nThe lambda returned from the `#to_proc` will require a hash as its argument.\n\n```ruby\nPerson = Micro::Struct.with(:to_proc).new(:first_name, :last_name)\n\n[\n  {first_name: 'John', last_name: 'Doe'},\n  {first_name: 'Mary', last_name: 'Doe'}\n].map(\u0026Person)\n# [\n#  #\u003cstruct Person::Struct first_name=\"John\", last_name=\"Doe\"\u003e,\n#  #\u003cstruct Person::Struct first_name=\"Mary\", last_name=\"Doe\"\u003e\n# ]\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n#### `:readonly`\n\nThis feature sets the Struct members' writers as private.\n\n```ruby\nPerson = Micro::Struct.with(:readonly).new(:first_name, :last_name)\n\nperson = Person.new(first_name: 'Rodrigo', last_name: 'Rodrigues')\n# #\u003cstruct Person first_name=\"Rodrigo\", last_name=\"Rodrigues\"\u003e\n\nperson.last_name = ''\n# NoMethodError (private method `last_name=' called for #\u003cstruct Person ...\u003e)\n\nperson[:last_name] = ''\n# NoMethodError (private method `[]=' called for #\u003cstruct Person ...\u003e)\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n#### `:instance_copy`\n\nCreates the `#with` method, which will instantiate a struct of the same kind and reuse its current state.\n\n```ruby\nPerson = Micro::Struct.with(:instance_copy).new(:first_name, :last_name)\n\nperson = Person.new(first_name: 'Rodrigo', last_name: 'Serradura')\n# =\u003e #\u003cstruct Person::Struct first_name=\"Rodrigo\", last_name=\"Serradura\"\u003e\n\nperson.first_name = 'John'\n# =\u003e \"John\"\n\nperson.inspect\n# =\u003e #\u003cstruct Person::Struct first_name=\"John\", last_name=\"Serradura\"\u003e\n\nnew_person = person.with(last_name: 'Doe')\n# =\u003e #\u003cstruct Person::Struct first_name=\"John\", last_name=\"Doe\"\u003e\n\nperson === new_person     # =\u003e false\nperson.equal?(new_person) # =\u003e false\n\nperson.last_name     # =\u003e \"Serradura\"\nnew_person.last_name # =\u003e \"Doe\"\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n#### `:exposed_features`\n\nThis feature exposes the struct's configured features. Via the methods: `.features` and `.__features__`.\n\n```ruby\nPerson = Micro::Struct.with(:exposed_features, :readonly, :to_proc).new(:name)\n\nPerson.features\n# =\u003e #\u003cstruct Micro::Struct::Features::Exposed\n#      names=[:readonly, :to_proc],\n#      options={:to_ary=\u003efalse, :to_hash=\u003efalse, :to_proc=\u003etrue, :readonly=\u003etrue, :instance_copy=\u003efalse}\u003e\n\nPerson.__features__.equal?(Person.features) # `.__features__` is an alias of `.features` method\n\nPerson.features.names   # =\u003e [:readonly, :to_proc]\nPerson.features.options # =\u003e {:to_ary=\u003efalse, :to_hash=\u003efalse, :to_proc=\u003etrue, :readonly=\u003etrue, :instance_copy=\u003efalse}\n\nPerson.features.option?(:to_proc)  # =\u003e true\nPerson.features.option?(:readonly) # =\u003e true\n\nPerson.features.options?(:to_proc)  # =\u003e true\nPerson.features.options?(:readonly) # =\u003e true\n\nPerson.features.options?(:to_proc, :readonly) # =\u003e true\nPerson.features.options?(:to_ary, :readonly)  # =\u003e false\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n### `Micro::Struct.instance` or `Micro::Struct.with(...).instance`\n\nCreates a struct instance from a given hash. This could be useful to create constants or a singleton value.\n\n```ruby\nperson1 = Micro::Struct.instance(first_name: 'Rodrigo', last_name: 'Serradura')\n# =\u003e #\u003cstruct  first_name=\"Rodrigo\", last_name=\"Serradura\"\u003e\n\nperson1.first_name = 'John'\n\nperson1.first_name # =\u003e \"John\"\n```\n\nYou can also use the instance method after defining some struct feature ([`Micro::Struct.with`](#microstructwith)).\n\n```ruby\nperson2 = Micro::Struct.with(:readonly).instance(first_name: 'Rodrigo', last_name: 'Serradura')\n# =\u003e #\u003cstruct  first_name=\"Rodrigo\", last_name=\"Serradura\"\u003e\n\nperson2.first_name = 'John'\n# NoMethodError (private method `first_name=' called for #\u003cstruct first_name=\"Rodrigo\", last_name=\"Serradura\"\u003e)\n```\n\nAnd if you need some custom behavior, use a block to define them.\n\n```ruby\nperson3 = Micro::Struct.instance(first_name: 'Rodrigo', last_name: 'Serradura') do\n  def name\n    \"#{first_name} #{last_name}\"\n  end\nend\n\nperson4 = Micro::Struct.with(:readonly).instance(first_name: 'Rodrigo', last_name: 'Serradura') do\n  def name\n    \"#{first_name} #{last_name}\"\n  end\nend\n\nperson3.name # =\u003e \"Rodrigo Serradura\"\nperson4.name # =\u003e \"Rodrigo Serradura\"\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n### `Micro::Struct.immutable`\n\nThis method is as a shortcut to `Micro::Struct.with(:readonly, :instance_copy)`.\nAs it is quite common to see the usage of these two features, I decided to create this method to improve the DX.\n\nAdditional info:\n1. It accepts the `with:` option, which can be used to define additional features.\n2. The `.instance` method can be called after its usage.\n\nUsage examples:\n\n```ruby\nMicro::Struct.immutable.new(:name)\n\nMicro::Struct.immutable.new(:name) do\n  def hi(other_name)\n    \"Hi, #{other_name}! My name is #{name}\"\n  end\nend\n\nMicro::Struct.immutable(with: :to_hash).new(:name)\n\nMicro::Struct.immutable(with: [:to_hash, :to_proc]).new(:name)\n\nMicro::Struct.immutable.instance(name: 'Rodrigo')\n\nMicro::Struct.immutable(with: [:to_hash]).instance(name: 'Serradura')\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n### `Micro::Struct.readonly`\n\nThis method is as a shortcut to `Micro::Struct.with(:readonly)`.\n\nAdditional info:\n1. It accepts the `with:` option, which can be used to define additional features.\n2. The `.instance` method can be called after its usage.\n\nUsage examples:\n\n```ruby\nMicro::Struct.readonly.new(:name)\n\nMicro::Struct.readonly.new(:name) do\n  def hi(other_name)\n    \"Hi, #{other_name}! My name is #{name}\"\n  end\nend\n\nMicro::Struct.readonly(with: :to_hash).new(:name)\n\nMicro::Struct.readonly(with: [:to_hash, :to_proc]).new(:name)\n\nMicro::Struct.readonly.instance(name: 'Rodrigo')\n\nMicro::Struct.readonly(with: [:to_hash]).instance(name: 'Serradura')\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n### TL;DR\n\nLike in a regular `Struct`, you can define one or many attributes but all of them will be required by default.\n\n```ruby\nMicro::Struct.new(:first_name, :last_name, ...)\n```\n\nUse the `optional:` argument if you want some optional attributes.\n\n```ruby\nMicro::Struct.new(:first_name, :last_name, optional: :gender)\n\n# Using `optional:` to define all attributes are optional.\n\nMicro::Struct.new(optional: [:first_name, :last_name])\n```\n\nUse the `required:` argument to define required attributes.\n\n```ruby\nMicro::Struct.new(\n  required: [:first_name, :last_name],\n  optional: [:gender, :age]\n)\n```\n\nYou can also pass a block to define custom methods.\n\n```ruby\nMicro::Struct.new(:name) {}\n```\n\nAvailable features (use one, many, or all) to create Structs with a special behavior:\n\n```ruby\nMicro::Struct.with(:to_ary)\nMicro::Struct.with(:to_ary, :to_hash)\nMicro::Struct.with(:to_ary, :to_hash, :to_proc)\nMicro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly)\nMicro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy)\nMicro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy, :exposed_features)\n```\n\nAll of the possible combinations to create a Ruby Struct using `Micro::Struct`:\n\n```ruby\nMicro::Struct.new(*required)\nMicro::Struct.new(*required) {}\n\nMicro::Struct.new(optional: *)\nMicro::Struct.new(optional: *) {}\n\nMicro::Struct.new(required: *)\nMicro::Struct.new(required: *) {}\n\nMicro::Struct.new(*required, optional: *)\nMicro::Struct.new(*required, optional: *) {}\n\nMicro::Struct.new(required: *, optional: *)\nMicro::Struct.new(required: *, optional: *) {}\n```\n\nAny options above can be used by the `.new()` method of the struct creator returned by the `.with()` method.\n\n```ruby\nMicro::Struct.with(*features).new(...) {}\n```\n\nUse `Micro::Struct.instance()` or `Micro::Struct.with(...).instance()` to create a struct instance from a given hash.\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n## FAQ\n\n### How to override the Struct `.new` method?\n\nThe `.new` is an alias for the `.__new__` method, so you can use `.__new__` when overriding it.\n\n```ruby\nmodule RGB\n  Number = -\u003e(value) do\n    return value if value.is_a?(::Integer) \u0026\u0026 value \u003e= 0 \u0026\u0026 value \u003c= 255\n\n    raise TypeError, \"#{value} must be an Integer(\u003e= 0 and \u003c= 255)\"\n  end\n\n  Color = Micro::Struct.new(:red, :green, :blue) do\n    def self.new(r, g, b)\n      __new__(\n        red: Number[r],\n        green: Number[g],\n        blue: Number[b],\n      )\n    end\n\n    def to_hex\n      \"##{red}#{green}#{blue}\"\n    end\n  end\nend\n\nrgb_color = RGB::Color.new(1, 5, 255)\n# =\u003e #\u003cstruct RGB::Color::Struct red=#\u003cstruct RGB::Number value=1\u003e, green=#\u003cstruct RGB::Number value=5\u003e, blue=#\u003cstruct RGB::Number value=255\u003e\u003e\n\nrgb_color.to_hex\n# =\u003e \"#0105ff\"\n\nRGB::Color.new(-1, 5, 255)\n# =\u003e TypeError: -1 must be an Integer(\u003e= 0 and \u003c= 255)\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n### Can I override the Struct initializer?\n\nYes, you can, but the initializer must handle the arguments as positional ones.\n\n```ruby\nRGBColor = Micro::Struct.with(:readonly, :to_ary).new(:red, :green, :blue) do\n  Number = -\u003e(value) do\n    return value if value.is_a?(::Integer) \u0026\u0026 value \u003e= 0 \u0026\u0026 value \u003c= 255\n\n    raise TypeError, \"#{value} must be an Integer(\u003e= 0 and \u003c= 255)\"\n  end\n\n  def initialize(r, g, b)\n    super(Number[r], Number[g], Number[b])\n  end\n\n  def to_hex\n    '#%02x%02x%02x' % self\n  end\nend\n\nrgb_color = RGBColor.new(red: 1, green: 1, blue: 255)\n# #\u003cstruct RGBColor red=1, green=1, blue=255\u003e\n\nr, g, b = rgb_color\n\n[r,g,b]\n# [1, 1, 255]\n\nrgb_color.to_hex\n# \"#0101ff\"\n\nRGBColor.new(red: 1, green: -1, blue: 255)\n# TypeError (-1 must be an Integer(\u003e= 0 and \u003c= 255))\n```\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nAdditional tools:\n- Sorbet (type checker): `bundle exec srb tc` (requires `Ruby \u003e= 2.7`).\n- Rubocop (linter and code formatter): `bundle rubocop` (requires `Ruby \u003e= 2.5`).\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/serradura/u-struct. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/serradura/u-struct/blob/main/CODE_OF_CONDUCT.md).\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n## Code of Conduct\n\nEveryone interacting in the `Micro::Struct` project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/serradura/u-struct/blob/main/CODE_OF_CONDUCT.md).\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n## Contact\n\nRodrigo Serradura - [Twitter](https://twitter.com/serradura) | [LinkedIn](https://www.linkedin.com/in/rodrigo-serradura/).\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#table-of-contents-\"\u003e⬆️ \u0026nbsp;back to top\u003c/a\u003e)\u003c/p\u003e\n\n## Acknowledgments\n\n- [`@vitoravelino`](https://github.com/vitoravelino) thanks for talking about some gem's ideas and reviewing the documentation.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserradura%2Fu-struct","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fserradura%2Fu-struct","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserradura%2Fu-struct/lists"}