README.md in u-attributes-2.0.0 vs README.md in u-attributes-2.0.1

- old
+ new

@@ -1,18 +1,18 @@ ![Ruby](https://img.shields.io/badge/ruby-2.2+-ruby.svg?colorA=99004d&colorB=cc0066) [![Gem](https://img.shields.io/gem/v/u-attributes.svg?style=flat-square)](https://rubygems.org/gems/u-attributes) -[![Build Status](https://travis-ci.com/serradura/u-attributes.svg?branch=master)](https://travis-ci.com/serradura/u-attributes) +[![Build Status](https://travis-ci.com/serradura/u-attributes.svg?branch=main)](https://travis-ci.com/serradura/u-attributes) [![Maintainability](https://api.codeclimate.com/v1/badges/b562e6b877a9edf4dbf6/maintainability)](https://codeclimate.com/github/serradura/u-attributes/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/b562e6b877a9edf4dbf6/test_coverage)](https://codeclimate.com/github/serradura/u-attributes/test_coverage) μ-attributes (Micro::Attributes) <!-- omit in toc --> ================================ -This gem allows defining read-only attributes, that is, your objects will have only getters to access their attributes data. +This gem allows you to define "immutable" objects, and your objects will have only getters and no setters. +So, if you change [[1](#with_attribute)] [[2](#with_attributes)] some object attribute, you will have a new object instance. That is, you transform the object instead of modifying it. ## Table of contents <!-- omit in toc --> -- [Required Ruby version](#required-ruby-version) - [Installation](#installation) - [Compatibility](#compatibility) - [Usage](#usage) - [How to define attributes?](#how-to-define-attributes) - [`Micro::Attributes#attributes=`](#microattributesattributes) @@ -21,105 +21,114 @@ - [How to define multiple attributes?](#how-to-define-multiple-attributes) - [`Micro::Attributes.with(:initialize)`](#microattributeswithinitialize) - [`#with_attribute()`](#with_attribute) - [`#with_attributes()`](#with_attributes) - [Defining default values to the attributes](#defining-default-values-to-the-attributes) - - [Strict initializer](#strict-initializer) + - [The strict initializer](#the-strict-initializer) - [Is it possible to inherit the attributes?](#is-it-possible-to-inherit-the-attributes) - - [.attribute!()](#attribute) + - [`.attribute!()`](#attribute) - [How to query the attributes?](#how-to-query-the-attributes) - [Built-in extensions](#built-in-extensions) - - [ActiveModel::Validations extension](#activemodelvalidations-extension) - - [Attribute options](#attribute-options) - - [Diff extension](#diff-extension) - - [Initialize extension](#initialize-extension) - - [Strict initialize mode](#strict-initialize-mode) + - [Picking specific features](#picking-specific-features) + - [`Micro::Attributes.with`](#microattributeswith) + - [`Micro::Attributes.without`](#microattributeswithout) + - [Picking all the features](#picking-all-the-features) + - [Extensions](#extensions) + - [`ActiveModel::Validation` extension](#activemodelvalidation-extension) + - [`.attribute()` options](#attribute-options) + - [Diff extension](#diff-extension) + - [Initialize extension](#initialize-extension) + - [Strict mode](#strict-mode) - [Development](#development) - [Contributing](#contributing) - [License](#license) - [Code of Conduct](#code-of-conduct) -## Required Ruby version +# Installation -> \>= 2.2.0 - -## Installation - Add this line to your application's Gemfile and `bundle install`: ```ruby gem 'u-attributes' ``` -## Compatibility +# Compatibility | u-attributes | branch | ruby | activemodel | | -------------- | ------- | -------- | ------------- | -| 2.0.0 | master | >= 2.2.0 | >= 3.2, < 6.1 | +| 2.0.0 | main | >= 2.2.0 | >= 3.2, < 6.1 | | 1.2.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 | -## Usage +> **Note**: The activemodel is an optional dependency, this module [can be enabled](#activemodelvalidation-extension) to validate the attributes. -### How to define attributes? +[⬆️ Back to Top](#table-of-contents-) +# Usage + +## How to define attributes? + ```ruby # By default you must to define the class constructor. class Person include Micro::Attributes - attribute :name attribute :age + attribute :name def initialize(name: 'John Doe', age:) @name, @age = name, age end end person = Person.new(age: 21) -person.name # John Doe person.age # 21 +person.name # John Doe # By design the attributes are always exposed as reader methods (getters). # If you try to call a setter you will see a NoMethodError. # # person.name = 'Rodrigo' # NoMethodError (undefined method `name=' for #<Person:0x0000... @name='John Doe', @age=21>) ``` -#### `Micro::Attributes#attributes=` +[⬆️ Back to Top](#table-of-contents-) +### `Micro::Attributes#attributes=` + This is a protected method to make easier the assignment in a constructor. e.g. ```ruby class Person include Micro::Attributes - attribute :name, default: 'John Doe' attribute :age + attribute :name, default: 'John Doe' def initialize(options) self.attributes = options end end person = Person.new(age: 20) -person.name # John Doe person.age # 20 +person.name # John Doe ``` -#### `Micro::Attributes#attribute` +[⬆️ Back to Top](#table-of-contents-) +### `Micro::Attributes#attribute` + Use this method with a valid attribute name to get its value. ```ruby person = Person.new(age: 20) -person.attribute(:name) # John Doe person.attribute('age') # 20 +person.attribute(:name) # John Doe person.attribute('foo') # nil ``` If you pass a block, it will be executed only if the attribute was valid. @@ -127,22 +136,26 @@ person.attribute(:name) { |value| puts value } # John Doe person.attribute('age') { |value| puts value } # 20 person.attribute('foo') { |value| puts value } # !! Nothing happened, because of the attribute doesn't exist. ``` -#### `Micro::Attributes#attribute!` +[⬆️ Back to Top](#table-of-contents-) +### `Micro::Attributes#attribute!` + Works like the `#attribute` method, but it will raise an exception when the attribute doesn't exist. ```ruby -person.attribute!('foo') # NameError (undefined attribute `foo) +person.attribute!('foo') # NameError (undefined attribute `foo) person.attribute!('foo') { |value| value } # NameError (undefined attribute `foo) ``` -### How to define multiple attributes? +[⬆️ Back to Top](#table-of-contents-) +## How to define multiple attributes? + Use `.attributes` with a list of attribute names. ```ruby class Person include Micro::Attributes @@ -160,12 +173,14 @@ person.age # 32 ``` > **Note:** This method can't define default values. To do this, use the `#attribute()` method. -### `Micro::Attributes.with(:initialize)` +[⬆️ Back to Top](#table-of-contents-) +## `Micro::Attributes.with(:initialize)` + Use `Micro::Attributes.with(:initialize)` to define a constructor to assign the attributes. e.g. ```ruby class Person include Micro::Attributes.with(:initialize) @@ -174,46 +189,48 @@ attribute :name, default: 'John Doe' end person = Person.new(age: 18) -person.name # John Doe person.age # 18 +person.name # John Doe ``` This extension enables two methods for your objects. The `#with_attribute()` and `#with_attributes()`. -#### `#with_attribute()` +### `#with_attribute()` ```ruby another_person = person.with_attribute(:age, 21) -another_person.name # John Doe another_person.age # 21 +another_person.name # John Doe another_person.equal?(person) # false ``` -#### `#with_attributes()` +### `#with_attributes()` Use it to assign multiple attributes ```ruby other_person = person.with_attributes(name: 'Serradura', age: 32) -other_person.name # Serradura other_person.age # 32 +other_person.name # Serradura other_person.equal?(person) # false ``` If you pass a value different of a Hash, a Kind::Error will be raised. ```ruby -Person.new(1) # Kind::Error (1 must be a Hash) +Person.new(1) # Kind::Error (1 expected to be a kind of Hash) ``` -### Defining default values to the attributes +[⬆️ Back to Top](#table-of-contents-) +## Defining default values to the attributes + To do this, you only need make use of the `default:` keyword. e.g. ```ruby class Person include Micro::Attributes.with(:initialize) @@ -235,12 +252,14 @@ attribute :age, default: -> age { age&.to_i } attribute :name, default: -> name { String(name || 'John Doe').strip } end ``` -### Strict initializer +[⬆️ Back to Top](#table-of-contents-) +## The strict initializer + Use `.with(initialize: :strict)` to forbids an instantiation without all the attribute keywords. e.g. ```ruby class StrictPerson include Micro::Attributes.with(initialize: :strict) @@ -255,18 +274,20 @@ An attribute with a default value can be omitted. ``` ruby person_without_age = StrictPerson.new(age: nil) -person_without_age.name # 'John Doe' person_without_age.age # nil +person_without_age.name # 'John Doe' ``` > **Note:** Except for this validation the `.with(initialize: :strict)` method will works in the same ways of `.with(:initialize)`. -### Is it possible to inherit the attributes? +[⬆️ Back to Top](#table-of-contents-) +## Is it possible to inherit the attributes? + Yes. e.g. ```ruby class Person include Micro::Attributes.with(:initialize) @@ -284,12 +305,14 @@ instance.name # John Doe instance.respond_to?(:age) # true instance.respond_to?(:foo) # true ``` -#### .attribute!() +[⬆️ Back to Top](#table-of-contents-) +### `.attribute!()` + This method allows us to redefine the attributes default data that was defined in the parent class. e.g. ```ruby class AnotherSubclass < Person attribute! :name, default: 'Alfa' @@ -309,12 +332,14 @@ beta_person.name # 'Beta' beta_person.age # 0 ``` -### How to query the attributes? +[⬆️ Back to Top](#table-of-contents-) +## How to query the attributes? + ```ruby class Person include Micro::Attributes attribute :age @@ -370,81 +395,70 @@ person.attributes(:age) # {age: 20} person.attributes(:age, :name) # {age: 20, name: 'John Doe'} person.attributes('age', 'name') # {'age'=>20, 'name'=>'John Doe'} ``` -## Built-in extensions +[⬆️ Back to Top](#table-of-contents-) +# Built-in extensions + You can use the method `Micro::Attributes.with()` to combine and require only the features that better fit your needs. But, if you desire except one or more features, use the `Micro::Attributes.without()` method. +## Picking specific features + +### `Micro::Attributes.with` + ```ruby -#===========================# -# Loading specific features # -#===========================# +Micro::Attributes.with(:initialize) -class Job - include Micro::Attributes.with(:diff) +Micro::Attributes.with(initialize: :strict) - attribute :id - attribute :state, default: 'sleeping' +Micro::Attributes.with(:diff, :initialize) - def initialize(options) - self.attributes = options - end -end +Micro::Attributes.with(:diff, initialize: :strict) -#======================# -# Loading all features # -# --- # -#======================# +Micro::Attributes.with(:activemodel_validations) -class Job - include Micro::Attributes.with_all_features +Micro::Attributes.with(:activemodel_validations, :diff) - attribute :id - attribute :state, default: 'sleeping' -end +Micro::Attributes.with(:activemodel_validations, :diff, initialize: :strict) +``` -#----------------------------------------------------------------------------# -# Using the .with() method alias and adding the strict initialize extension. # -#----------------------------------------------------------------------------# -class Job - include Micro::Attributes.with(:diff, initialize: :strict) +The method `Micro::Attributes.with()` will raise an exception if no arguments/features were declared. - attribute :id - attribute :state, default: 'sleeping' +```ruby +class Job + include Micro::Attributes.with() # ArgumentError (Invalid feature name! Available options: :activemodel_validations, :diff, :initialize) end +``` -# Note: -# The method `Micro::Attributes.with()` will raise an exception if no arguments/features were declared. -# -# class Job -# include Micro::Attributes.with() # ArgumentError (Invalid feature name! Available options: diff, initialize, activemodel_validations) -# end +### `Micro::Attributes.without` -#=====================================# -# Loading except one or more features # -# ----- # -#=====================================# +Picking *except* one or more features -class Job - include Micro::Attributes.without(:diff) +```ruby +Micro::Attributes.without(:diff) # will load :activemodel_validations and initialize: :strict - attribute :id - attribute :state, default: 'sleeping' -end +Micro::Attributes.without(initialize: :strict) # will load :activemodel_validations and :diff +``` -# Note: -# The method `Micro::Attributes.without()` returns `Micro::Attributes` if all features extensions were used. +## Picking all the features + +```ruby +Micro::Attributes.with_all_features ``` -### ActiveModel::Validations extension +[⬆️ Back to Top](#table-of-contents-) -If your application uses ActiveModel as a dependency (like a regular Rails app). You will be enabled to use the `actimodel_validations` extension. +## Extensions +### `ActiveModel::Validation` extension + +If your application uses ActiveModel as a dependency (like a regular Rails app). You will be enabled to use the `activemodel_validations` extension. + ```ruby class Job include Micro::Attributes.with(:activemodel_validations) attribute :id @@ -459,29 +473,31 @@ job.id # 1 job.state # 'sleeping' ``` -#### Attribute options +#### `.attribute()` options You can use the `validate` or `validates` options to define your attributes. e.g. ```ruby class Job include Micro::Attributes.with(:activemodel_validations) attribute :id, validates: { presence: true } attribute :state, validate: :must_be_a_filled_string - def must_be_a_string + def must_be_a_filled_string return if state.is_a?(String) && state.present? errors.add(:state, 'must be a filled string') end end ``` +[⬆️ Back to Top](#table-of-contents-) + ### Diff extension Provides a way to track changes in your object attributes. ```ruby @@ -527,10 +543,12 @@ # #differences() # #----------------# job_changes.differences # {'state'=> {'from' => 'sleeping', 'to' => 'running'}} ``` +[⬆️ Back to Top](#table-of-contents-) + ### Initialize extension 1. Creates a constructor to assign the attributes. 2. Add methods to build new instances when some data was assigned. @@ -576,12 +594,14 @@ other_job.id # 2 other_job.state # killed other_job.equal?(job) # false ``` -### Strict initialize mode +[⬆️ Back to Top](#table-of-contents-) +#### Strict mode + 1. Creates a constructor to assign the attributes. 2. Adds methods to build new instances when some data was assigned. 3. **Forbids missing keywords**. ```ruby @@ -614,22 +634,24 @@ job.state # 'sleeping' ``` > **Note**: This extension works like the `initialize` extension. So, look at its section to understand all of the other features. -## Development +[⬆️ Back to Top](#table-of-contents-) +# Development + After 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. To 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). -## Contributing +# Contributing Bug 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. -## License +# License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). -## Code of Conduct +# Code of Conduct -Everyone 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/master/CODE_OF_CONDUCT.md). +Everyone 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).