{"id":15608859,"url":"https://github.com/serradura/u-attributes","last_synced_at":"2025-04-07T12:08:21.433Z","repository":{"id":47546313,"uuid":"194790553","full_name":"serradura/u-attributes","owner":"serradura","description":"Create \"immutable\" objects with no setters, just getters.","archived":false,"fork":false,"pushed_at":"2022-04-01T19:51:06.000Z","size":380,"stargazers_count":92,"open_issues_count":3,"forks_count":7,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-31T10:11:12.462Z","etag":null,"topics":["activemodel","change-detection","data-integrity","data-validation","getters","immutability","no-setters","ruby","ruby-gem"],"latest_commit_sha":null,"homepage":"http://rubygems.org/gems/u-attributes","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":null,"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":"2019-07-02T04:54:21.000Z","updated_at":"2024-10-16T12:36:47.000Z","dependencies_parsed_at":"2022-09-11T21:44:18.288Z","dependency_job_id":null,"html_url":"https://github.com/serradura/u-attributes","commit_stats":null,"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serradura%2Fu-attributes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serradura%2Fu-attributes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serradura%2Fu-attributes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serradura%2Fu-attributes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/serradura","download_url":"https://codeload.github.com/serradura/u-attributes/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247648977,"owners_count":20972945,"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":["activemodel","change-detection","data-integrity","data-validation","getters","immutability","no-setters","ruby","ruby-gem"],"created_at":"2024-10-03T05:40:21.904Z","updated_at":"2025-04-07T12:08:21.404Z","avatar_url":"https://github.com/serradura.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./assets/u-attributes_logo_v1.png\" alt='Create \"immutable\" objects. No setters, just getters!'\u003e\n\n  \u003cp align=\"center\"\u003e\u003ci\u003eCreate \"immutable\" objects with no setters, just getters.\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-\u003e%3D%202.2.0-ruby.svg?colorA=99004d\u0026colorB=cc0066\" alt=\"Ruby\"\u003e\n\n  \u003ca href=\"https://rubygems.org/gems/u-attributes\"\u003e\n    \u003cimg alt=\"Gem\" src=\"https://img.shields.io/gem/v/u-attributes.svg?style=flat-square\"\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://github.com/serradura/u-attributes/actions/workflows/ci.yml\"\u003e\n    \u003cimg alt=\"Build Status\" src=\"https://github.com/serradura/u-attributes/actions/workflows/ci.yml/badge.svg\"\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://codeclimate.com/github/serradura/u-attributes/maintainability\"\u003e\n    \u003cimg alt=\"Maintainability\" src=\"https://api.codeclimate.com/v1/badges/b562e6b877a9edf4dbf6/maintainability\"\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://codeclimate.com/github/serradura/u-attributes/test_coverage\"\u003e\n    \u003cimg alt=\"Test Coverage\" src=\"https://api.codeclimate.com/v1/badges/b562e6b877a9edf4dbf6/test_coverage\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\nThis gem allows you to define \"immutable\" objects, when using it your objects will only have getters and no setters.\nSo, if you change [[1](#with_attribute)] [[2](#with_attributes)] an attribute of the object, you’ll have a new object instance. That is, you transform the object instead of modifying it.\n\n## Documentation \u003c!-- omit in toc --\u003e\n\nVersion    | Documentation\n---------- | -------------\nunreleased | https://github.com/serradura/u-case/blob/main/README.md\n2.8.0      | https://github.com/serradura/u-case/blob/v2.x/README.md\n1.2.0      | https://github.com/serradura/u-case/blob/v1.x/README.md\n\n# Table of contents \u003c!-- omit in toc --\u003e\n- [Installation](#installation)\n- [Compatibility](#compatibility)\n- [Usage](#usage)\n  - [How to define attributes?](#how-to-define-attributes)\n    - [`Micro::Attributes#attributes=`](#microattributesattributes)\n      - [How to extract attributes from an object or hash?](#how-to-extract-attributes-from-an-object-or-hash)\n      - [Is it possible to define an attribute as required?](#is-it-possible-to-define-an-attribute-as-required)\n    - [`Micro::Attributes#attribute`](#microattributesattribute)\n    - [`Micro::Attributes#attribute!`](#microattributesattribute-1)\n  - [How to define multiple attributes?](#how-to-define-multiple-attributes)\n  - [`Micro::Attributes.with(:initialize)`](#microattributeswithinitialize)\n    - [`#with_attribute()`](#with_attribute)\n    - [`#with_attributes()`](#with_attributes)\n  - [Defining default values to the attributes](#defining-default-values-to-the-attributes)\n  - [The strict initializer](#the-strict-initializer)\n  - [Is it possible to inherit the attributes?](#is-it-possible-to-inherit-the-attributes)\n    - [`.attribute!()`](#attribute)\n  - [How to query the attributes?](#how-to-query-the-attributes)\n    - [`.attributes`](#attributes)\n    - [`.attribute?()`](#attribute-1)\n    - [`#attribute?()`](#attribute-2)\n    - [`#attributes()`](#attributes-1)\n      - [`#attributes(keys_as:)`](#attributeskeys_as)\n      - [`#attributes(*names)`](#attributesnames)\n      - [`#attributes([names])`](#attributesnames-1)\n      - [`#attributes(with:, without:)`](#attributeswith-without)\n    - [`#defined_attributes`](#defined_attributes)\n- [Built-in extensions](#built-in-extensions)\n  - [Picking specific features](#picking-specific-features)\n    - [`Micro::Attributes.with`](#microattributeswith)\n    - [`Micro::Attributes.without`](#microattributeswithout)\n  - [Picking all the features](#picking-all-the-features)\n  - [Extensions](#extensions)\n    - [`ActiveModel::Validation` extension](#activemodelvalidation-extension)\n      - [`.attribute()` options](#attribute-options)\n    - [Diff extension](#diff-extension)\n    - [Initialize extension](#initialize-extension)\n      - [Strict mode](#strict-mode)\n    - [Keys as symbol extension](#keys-as-symbol-extension)\n- [Development](#development)\n- [Contributing](#contributing)\n- [License](#license)\n- [Code of Conduct](#code-of-conduct)\n\n# Installation\n\nAdd this line to your application's Gemfile and `bundle install`:\n\n```ruby\ngem 'u-attributes'\n```\n\n# Compatibility\n\n| u-attributes   | branch  | ruby     |  activemodel  |\n| -------------- | ------- | -------- | ------------- |\n| unreleased     | main    | \u003e= 2.2.0 | \u003e= 3.2, \u003c 7   |\n| 2.8.0          | v2.x    | \u003e= 2.2.0 | \u003e= 3.2, \u003c 7   |\n| 1.2.0          | v1.x    | \u003e= 2.2.0 | \u003e= 3.2, \u003c 6.1 |\n\n\u003e **Note**: The activemodel is an optional dependency, this module [can be enabled](#activemodelvalidation-extension) to validate the attributes.\n\n[⬆️ Back to Top](#table-of-contents-)\n\n# Usage\n\n## How to define attributes?\n\nBy default, you must define the class constructor.\n\n```ruby\nclass Person\n  include Micro::Attributes\n\n  attribute :age\n  attribute :name\n\n  def initialize(name: 'John Doe', age:)\n    @name, @age = name, age\n  end\nend\n\nperson = Person.new(age: 21)\n\nperson.age  # 21\nperson.name # John Doe\n\n# By design the attributes are always exposed as reader methods (getters).\n# If you try to call a setter you will see a NoMethodError.\n#\n# person.name = 'Rodrigo'\n# NoMethodError (undefined method `name=' for #\u003cPerson:0x0000... @name='John Doe', @age=21\u003e)\n```\n\n[⬆️ Back to Top](#table-of-contents-)\n\n### `Micro::Attributes#attributes=`\n\nThis is a protected method to make easier the assignment in a constructor. e.g.\n\n```ruby\nclass Person\n  include Micro::Attributes\n\n  attribute :age\n  attribute :name, default: 'John Doe'\n\n  def initialize(options)\n    self.attributes = options\n  end\nend\n\nperson = Person.new(age: 20)\n\nperson.age  # 20\nperson.name # John Doe\n```\n\n#### How to extract attributes from an object or hash?\n\nYou can extract attributes using the `extract_attributes_from` method, it will try to fetch attributes from the\nobject using either the `object[attribute_key]` accessor or the reader method `object.attribute_key`.\n\n```ruby\nclass Person\n  include Micro::Attributes\n\n  attribute :age\n  attribute :name, default: 'John Doe'\n\n  def initialize(user:)\n    self.attributes = extract_attributes_from(user)\n  end\nend\n\n# extracting from an object\n\nclass User\n  attr_accessor :age, :name\nend\n\nuser = User.new\nuser.age = 20\n\nperson = Person.new(user: user)\n\nperson.age  # 20\nperson.name # John Doe\n\n# extracting from a hash\n\nanother_person = Person.new(user: { age: 55, name: 'Julia Not Roberts' })\n\nanother_person.age  # 55\nanother_person.name # Julia Not Roberts\n```\n\n#### Is it possible to define an attribute as required?\n\nYou only need to use the `required: true` option.\n\nBut to this work, you need to assign the attributes using the [`#attributes=`](#microattributesattributes) method or the extensions: [initialize](#initialize-extension), [activemodel_validations](#activemodelvalidation-extension).\n\n```ruby\nclass Person\n  include Micro::Attributes\n\n  attribute :age\n  attribute :name, required: true\n\n  def initialize(attributes)\n    self.attributes = attributes\n  end\nend\n\nPerson.new(age: 32) # ArgumentError (missing keyword: :name)\n```\n\n[⬆️ Back to Top](#table-of-contents-)\n\n### `Micro::Attributes#attribute`\n\nUse this method with a valid attribute name to get its value.\n\n```ruby\nperson = Person.new(age: 20)\n\nperson.attribute('age') # 20\nperson.attribute(:name) # John Doe\nperson.attribute('foo') # nil\n```\n\nIf you pass a block, it will be executed only if the attribute was valid.\n\n```ruby\nperson.attribute(:name) { |value| puts value } # John Doe\nperson.attribute('age') { |value| puts value } # 20\nperson.attribute('foo') { |value| puts value } # !! Nothing happened, because of the attribute doesn't exist.\n```\n\n[⬆️ Back to Top](#table-of-contents-)\n\n### `Micro::Attributes#attribute!`\n\nWorks like the `#attribute` method, but it will raise an exception when the attribute doesn't exist.\n\n```ruby\nperson.attribute!('foo')                   # NameError (undefined attribute `foo)\n\nperson.attribute!('foo') { |value| value } # NameError (undefined attribute `foo)\n```\n\n[⬆️ Back to Top](#table-of-contents-)\n\n## How to define multiple attributes?\n\nUse `.attributes` with a list of attribute names.\n\n```ruby\nclass Person\n  include Micro::Attributes\n\n  attributes :age, :name\n\n  def initialize(options)\n    self.attributes = options\n  end\nend\n\nperson = Person.new(age: 32)\n\nperson.name # nil\nperson.age  # 32\n```\n\n\u003e **Note:** This method can't define default values. To do this, use the `#attribute()` method.\n\n[⬆️ Back to Top](#table-of-contents-)\n\n## `Micro::Attributes.with(:initialize)`\n\nUse `Micro::Attributes.with(:initialize)` to define a constructor to assign the attributes. e.g.\n\n```ruby\nclass Person\n  include Micro::Attributes.with(:initialize)\n\n  attribute :age, required: true\n  attribute :name, default: 'John Doe'\nend\n\nperson = Person.new(age: 18)\n\nperson.age  # 18\nperson.name # John Doe\n```\n\nThis extension enables two methods for your objects.\nThe `#with_attribute()` and `#with_attributes()`.\n\n### `#with_attribute()`\n\n```ruby\nanother_person = person.with_attribute(:age, 21)\n\nanother_person.age            # 21\nanother_person.name           # John Doe\nanother_person.equal?(person) # false\n```\n\n### `#with_attributes()`\n\nUse it to assign multiple attributes\n```ruby\nother_person = person.with_attributes(name: 'Serradura', age: 32)\n\nother_person.age            # 32\nother_person.name           # Serradura\nother_person.equal?(person) # false\n```\n\nIf you pass a value different of a Hash, a Kind::Error will be raised.\n\n```ruby\nPerson.new(1) # Kind::Error (1 expected to be a kind of Hash)\n```\n\n[⬆️ Back to Top](#table-of-contents-)\n\n## Defining default values to the attributes\n\nTo do this, you only need make use of the `default:` keyword. e.g.\n\n```ruby\nclass Person\n  include Micro::Attributes.with(:initialize)\n\n  attribute :age\n  attribute :name, default: 'John Doe'\nend\n```\n\nThere are two different strategies to define default values.\n1. Pass a regular object, like in the previous example.\n2. Pass a `proc`/`lambda`, and if it has an argument you will receive the attribute value to do something before assign it.\n\n```ruby\nclass Person\n  include Micro::Attributes.with(:initialize)\n\n  attribute :age, default: -\u003e age { age\u0026.to_i }\n  attribute :name, default: -\u003e name { String(name || 'John Doe').strip }\nend\n```\n\n[⬆️ Back to Top](#table-of-contents-)\n\n## The strict initializer\n\nUse `.with(initialize: :strict)` to forbids an instantiation without all the attribute keywords.\n\nIn other words, it is equivalent to you define all the attributes using the [`required: true` option](#is-it-possible-to-define-an-attribute-as-required).\n\n```ruby\nclass StrictPerson\n  include Micro::Attributes.with(initialize: :strict)\n\n  attribute :age\n  attribute :name, default: 'John Doe'\nend\n\nStrictPerson.new({}) # ArgumentError (missing keyword: :age)\n```\n\nAn attribute with a default value can be omitted.\n\n``` ruby\nperson_without_age = StrictPerson.new(age: nil)\n\nperson_without_age.age  # nil\nperson_without_age.name # 'John Doe'\n```\n\n\u003e **Note:** Except for this validation the `.with(initialize: :strict)` method will works in the same ways of `.with(:initialize)`.\n\n[⬆️ Back to Top](#table-of-contents-)\n\n## Is it possible to inherit the attributes?\n\nYes. e.g.\n\n```ruby\nclass Person\n  include Micro::Attributes.with(:initialize)\n\n  attribute :age\n  attribute :name, default: 'John Doe'\nend\n\nclass Subclass \u003c Person # Will preserve the parent class attributes\n  attribute :foo\nend\n\ninstance = Subclass.new({})\n\ninstance.name              # John Doe\ninstance.respond_to?(:age) # true\ninstance.respond_to?(:foo) # true\n```\n\n[⬆️ Back to Top](#table-of-contents-)\n\n### `.attribute!()`\n\nThis method allows us to redefine the attributes default data that was defined in the parent class. e.g.\n\n```ruby\nclass AnotherSubclass \u003c Person\n  attribute! :name, default: 'Alfa'\nend\n\nalfa_person = AnotherSubclass.new({})\n\nalfa_person.name # 'Alfa'\nalfa_person.age  # nil\n\nclass SubSubclass \u003c Subclass\n  attribute! :age, default: 0\n  attribute! :name, default: 'Beta'\nend\n\nbeta_person = SubSubclass.new({})\n\nbeta_person.name # 'Beta'\nbeta_person.age  # 0\n```\n\n[⬆️ Back to Top](#table-of-contents-)\n\n## How to query the attributes?\n\nAll of the methods that will be explained can be used with any of the built-in extensions.\n\n**PS:** We will use the class below for all of the next examples.\n\n```ruby\nclass Person\n  include Micro::Attributes\n\n  attribute :age\n  attribute :first_name, default: 'John'\n  attribute :last_name, default: 'Doe'\n\n  def initialize(options)\n    self.attributes = options\n  end\n\n  def name\n    \"#{first_name} #{last_name}\"\n  end\nend\n```\n\n### `.attributes`\n\nListing all the class attributes.\n\n```ruby\nPerson.attributes # [\"age\", \"first_name\", \"last_name\"]\n```\n\n### `.attribute?()`\n\nChecking the existence of some attribute.\n\n```ruby\nPerson.attribute?(:first_name)  # true\nPerson.attribute?('first_name') # true\n\nPerson.attribute?('foo') # false\nPerson.attribute?(:foo)  # false\n```\n\n### `#attribute?()`\n\nChecking the existence of some attribute in an instance.\n\n```ruby\nperson = Person.new(age: 20)\n\nperson.attribute?(:name)  # true\nperson.attribute?('name') # true\n\nperson.attribute?('foo') # false\nperson.attribute?(:foo)  # false\n```\n\n### `#attributes()`\n\nFetching all the attributes with their values.\n\n```ruby\nperson1 = Person.new(age: 20)\nperson1.attributes # {\"age\"=\u003e20, \"first_name\"=\u003e\"John\", \"last_name\"=\u003e\"Doe\"}\n\nperson2 = Person.new(first_name: 'Rodrigo', last_name: 'Rodrigues')\nperson2.attributes # {\"age\"=\u003enil, \"first_name\"=\u003e\"Rodrigo\", \"last_name\"=\u003e\"Rodrigues\"}\n```\n\n#### `#attributes(keys_as:)`\n\nUse the `keys_as:` option with `Symbol`/`:symbol` or `String`/`:string` to transform the attributes hash keys.\n\n```ruby\nperson1 = Person.new(age: 20)\nperson2 = Person.new(first_name: 'Rodrigo', last_name: 'Rodrigues')\n\nperson1.attributes(keys_as: Symbol) # {:age=\u003e20, :first_name=\u003e\"John\", :last_name=\u003e\"Doe\"}\nperson2.attributes(keys_as: String) # {\"age\"=\u003enil, \"first_name\"=\u003e\"Rodrigo\", \"last_name\"=\u003e\"Rodrigues\"}\n\nperson1.attributes(keys_as: :symbol) # {:age=\u003e20, :first_name=\u003e\"John\", :last_name=\u003e\"Doe\"}\nperson2.attributes(keys_as: :string) # {\"age\"=\u003enil, \"first_name\"=\u003e\"Rodrigo\", \"last_name\"=\u003e\"Rodrigues\"}\n```\n\n#### `#attributes(*names)`\n\nSlices the attributes to include only the given keys (in their types).\n\n```ruby\nperson = Person.new(age: 20)\n\nperson.attributes(:age)               # {:age =\u003e 20}\nperson.attributes(:age, :first_name)  # {:age =\u003e 20, :first_name =\u003e \"John\"}\nperson.attributes('age', 'last_name') # {\"age\" =\u003e 20, \"last_name\" =\u003e \"Doe\"}\n\nperson.attributes(:age, 'last_name') # {:age =\u003e 20, \"last_name\" =\u003e \"Doe\"}\n\n# You could also use the keys_as: option to ensure the same type for all of the hash keys.\n\nperson.attributes(:age, 'last_name', keys_as: Symbol) # {:age=\u003e20, :last_name=\u003e\"Doe\"}\n```\n\n#### `#attributes([names])`\n\nAs the previous example, this methods accepts a list of keys to slice the attributes.\n\n```ruby\nperson = Person.new(age: 20)\n\nperson.attributes([:age])               # {:age =\u003e 20}\nperson.attributes([:age, :first_name])  # {:age =\u003e 20, :first_name =\u003e \"John\"}\nperson.attributes(['age', 'last_name']) # {\"age\" =\u003e 20, \"last_name\" =\u003e \"Doe\"}\n\nperson.attributes([:age, 'last_name']) # {:age =\u003e 20, \"last_name\" =\u003e \"Doe\"}\n\n# You could also use the keys_as: option to ensure the same type for all of the hash keys.\n\nperson.attributes([:age, 'last_name'], keys_as: Symbol) # {:age=\u003e20, :last_name=\u003e\"Doe\"}\n```\n\n#### `#attributes(with:, without:)`\n\nUse the `with:` option to include any method value of the instance inside of the hash, and,\nyou can use the `without:` option to exclude one or more attribute keys from the final hash.\n\n```ruby\nperson = Person.new(age: 20)\n\nperson.attributes(without: :age)               # {\"first_name\"=\u003e\"John\", \"last_name\"=\u003e\"Doe\"}\nperson.attributes(without: [:age, :last_name]) # {\"first_name\"=\u003e\"John\"}\n\nperson.attributes(with: [:name], without: [:first_name, :last_name]) # {\"age\"=\u003e20, \"name\"=\u003e\"John Doe\"}\n\n# To achieves the same output of the previous example, use the attribute names to slice only them.\n\nperson.attributes(:age, with: [:name]) # {:age=\u003e20, \"name\"=\u003e\"John Doe\"}\n\n# You could also use the keys_as: option to ensure the same type for all of the hash keys.\n\nperson.attributes(:age, with: [:name], keys_as: Symbol) # {:age=\u003e20, :name=\u003e\"John Doe\"}\n```\n\n### `#defined_attributes`\n\nListing all the available attributes.\n\n```ruby\nperson = Person.new(age: 20)\n\nperson.defined_attributes # [\"age\", \"first_name\", \"last_name\"]\n```\n\n[⬆️ Back to Top](#table-of-contents-)\n\n# Built-in extensions\n\nYou can use the method `Micro::Attributes.with()` to combine and require only the features that better fit your needs.\n\nBut, if you desire except one or more features, use the `Micro::Attributes.without()` method.\n\n## Picking specific features\n\n### `Micro::Attributes.with`\n\n```ruby\nMicro::Attributes.with(:initialize)\n\nMicro::Attributes.with(:initialize, :keys_as_symbol)\n\nMicro::Attributes.with(:keys_as_symbol, initialize: :strict)\n\nMicro::Attributes.with(:diff, :initialize)\n\nMicro::Attributes.with(:diff, initialize: :strict)\n\nMicro::Attributes.with(:diff, :keys_as_symbol, initialize: :strict)\n\nMicro::Attributes.with(:activemodel_validations)\n\nMicro::Attributes.with(:activemodel_validations, :diff)\n\nMicro::Attributes.with(:activemodel_validations, :diff, initialize: :strict)\n\nMicro::Attributes.with(:activemodel_validations, :diff, :keys_as_symbol, initialize: :strict)\n```\n\nThe method `Micro::Attributes.with()` will raise an exception if no arguments/features were declared.\n\n```ruby\nclass Job\n  include Micro::Attributes.with() # ArgumentError (Invalid feature name! Available options: :accept, :activemodel_validations, :diff, :initialize, :keys_as_symbol)\nend\n```\n\n### `Micro::Attributes.without`\n\nPicking *except* one or more features\n\n```ruby\nMicro::Attributes.without(:diff) # will load :activemodel_validations, :keys_as_symbol and initialize: :strict\n\nMicro::Attributes.without(initialize: :strict) # will load :activemodel_validations, :diff and :keys_as_symbol\n```\n\n## Picking all the features\n\n```ruby\nMicro::Attributes.with_all_features\n\n# This method returns the same of:\n\nMicro::Attributes.with(:activemodel_validations, :diff, :keys_as_symbol, initialize: :strict)\n```\n\n[⬆️ Back to Top](#table-of-contents-)\n\n## Extensions\n\n### `ActiveModel::Validation` extension\n\nIf your application uses ActiveModel as a dependency (like a regular Rails app). You will be enabled to use the `activemodel_validations` extension.\n\n```ruby\nclass Job\n  include Micro::Attributes.with(:activemodel_validations)\n\n  attribute :id\n  attribute :state, default: 'sleeping'\n\n  validates! :id, :state, presence: true\nend\n\nJob.new({}) # ActiveModel::StrictValidationFailed (Id can't be blank)\n\njob = Job.new(id: 1)\n\njob.id    # 1\njob.state # 'sleeping'\n```\n\n#### `.attribute()` options\n\nYou can use the `validate` or `validates` options to define your attributes. e.g.\n\n```ruby\nclass Job\n  include Micro::Attributes.with(:activemodel_validations)\n\n  attribute :id, validates: { presence: true }\n  attribute :state, validate: :must_be_a_filled_string\n\n  def must_be_a_filled_string\n    return if state.is_a?(String) \u0026\u0026 state.present?\n\n    errors.add(:state, 'must be a filled string')\n  end\nend\n```\n\n[⬆️ Back to Top](#table-of-contents-)\n\n### Diff extension\n\nProvides a way to track changes in your object attributes.\n\n```ruby\nrequire 'securerandom'\n\nclass Job\n  include Micro::Attributes.with(:initialize, :diff)\n\n  attribute :id\n  attribute :state, default: 'sleeping'\nend\n\njob = Job.new(id: SecureRandom.uuid())\n\njob.id    # A random UUID generated from SecureRandom.uuid(). e.g: 'e68bcc74-b91c-45c2-a904-12f1298cc60e'\njob.state # 'sleeping'\n\njob_running = job.with_attribute(:state, 'running')\n\njob_running.state # 'running'\n\njob_changes = job.diff_attributes(job_running)\n\n#-----------------------------#\n# #present?, #blank?, #empty? #\n#-----------------------------#\n\njob_changes.present? # true\njob_changes.blank?   # false\njob_changes.empty?   # false\n\n#-----------#\n# #changed? #\n#-----------#\njob_changes.changed? # true\n\njob_changes.changed?(:id)    # false\n\njob_changes.changed?(:state) # true\njob_changes.changed?(:state, from: 'sleeping', to: 'running') # true\n\n#----------------#\n# #differences() #\n#----------------#\njob_changes.differences # {'state'=\u003e {'from' =\u003e 'sleeping', 'to' =\u003e 'running'}}\n```\n\n[⬆️ Back to Top](#table-of-contents-)\n\n### Initialize extension\n\n1. Creates a constructor to assign the attributes.\n2. Add methods to build new instances when some data was assigned.\n\n```ruby\nclass Job\n  include Micro::Attributes.with(:initialize)\n\n  attributes :id, :state\nend\n\njob_null = Job.new({})\n\njob.id    # nil\njob.state # nil\n\njob = Job.new(id: 1, state: 'sleeping')\n\njob.id    # 1\njob.state # 'sleeping'\n\n##############################################\n# Assigning new values to get a new instance #\n##############################################\n\n#-------------------#\n# #with_attribute() #\n#-------------------#\n\nnew_job = job.with_attribute(:state, 'running')\n\nnew_job.id          # 1\nnew_job.state       # running\nnew_job.equal?(job) # false\n\n#--------------------#\n# #with_attributes() #\n#--------------------#\n#\n# Use it to assign multiple attributes\n\nother_job = job.with_attributes(id: 2, state: 'killed')\n\nother_job.id          # 2\nother_job.state       # killed\nother_job.equal?(job) # false\n```\n\n[⬆️ Back to Top](#table-of-contents-)\n\n#### Strict mode\n\n1. Creates a constructor to assign the attributes.\n2. Adds methods to build new instances when some data was assigned.\n3. **Forbids missing keywords**.\n\n```ruby\nclass Job\n  include Micro::Attributes.with(initialize: :strict)\n\n  attributes :id, :state\nend\n#-----------------------------------------------------------------------#\n# The strict initialize mode will require all the keys when initialize. #\n#-----------------------------------------------------------------------#\n\nJob.new({})\n\n# The code above will raise:\n# ArgumentError (missing keywords: :id, :state)\n\n#---------------------------#\n# Samples passing some data #\n#---------------------------#\n\njob_null = Job.new(id: nil, state: nil)\n\njob.id    # nil\njob.state # nil\n\njob = Job.new(id: 1, state: 'sleeping')\n\njob.id    # 1\njob.state # 'sleeping'\n```\n\n\u003e **Note**: This extension works like the `initialize` extension. So, look at its section to understand all of the other features.\n\n[⬆️ Back to Top](#table-of-contents-)\n\n### Keys as symbol extension\n\nDisables the indifferent access requiring the declaration/usage of the attributes as symbols.\n\nThe advantage of this extension over the default behavior is because it avoids an unnecessary allocation in memory of strings. All the keys are transformed into strings in the indifferent access mode, but, with this extension, this typecasting will be avoided. So, it has a better performance and reduces the usage of memory/Garbage collector, but gives for you the responsibility to always use symbols to set/access the attributes.\n\n```ruby\nclass Job\n  include Micro::Attributes.with(:initialize, :keys_as_symbol)\n\n  attribute :id\n  attribute :state, default: 'sleeping'\nend\n\njob = Job.new(id: 1)\n\njob.attributes # {:id =\u003e 1, :state =\u003e \"sleeping\"}\n\njob.attribute?(:id) # true\njob.attribute?('id') # false\n\njob.attribute(:id) # 1\njob.attribute('id') # nil\n\njob.attribute!(:id) # 1\njob.attribute!('id') # NameError (undefined attribute `id)\n```\n\nAs you could see in the previous example only symbols will work to do something with the attributes.\n\nThis extension also changes the `diff extension` making everything (arguments, outputs) working only with symbols.\n\n[⬆️ Back to Top](#table-of-contents-)\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\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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n# Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/serradura/u-attributes. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.\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# Code of Conduct\n\nEveryone interacting in the Micro::Attributes project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/serradura/u-attributes/blob/main/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserradura%2Fu-attributes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fserradura%2Fu-attributes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserradura%2Fu-attributes/lists"}