From 6146f71f957e5309caaa54f0647e5ca711e839eb Mon Sep 17 00:00:00 2001 From: gangelo Date: Fri, 9 Aug 2024 20:12:26 -0400 Subject: [PATCH] Update README.md --- README.md | 54 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 9f7406c..c71b467 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # DecoLite + [![Ruby](https://github.com/gangelo/deco_lite/actions/workflows/ruby.yml/badge.svg)](https://github.com/gangelo/deco_lite/actions/workflows/ruby.yml) -[![GitHub version](http://badge.fury.io/gh/gangelo%2Fdeco_lite.svg?refresh=6)](https://badge.fury.io/gh/gangelo%2Fdeco_lite) -[![Gem Version](https://badge.fury.io/rb/deco_lite.svg?refresh=6)](https://badge.fury.io/rb/deco_lite) +[![GitHub version](http://badge.fury.io/gh/gangelo%2Fdeco_lite.svg?refresh=7)](https://badge.fury.io/gh/gangelo%2Fdeco_lite) +[![Gem Version](https://badge.fury.io/rb/deco_lite.svg?refresh=7)](https://badge.fury.io/rb/deco_lite) [![](http://ruby-gem-downloads-badge.herokuapp.com/deco_lite?type=total)](http://www.rubydoc.info/gems/deco_lite/) [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/deco_lite/) [![Report Issues](https://img.shields.io/badge/report-issues-red.svg)](https://github.com/gangelo/deco_lite/issues) @@ -9,11 +10,11 @@ ## Introduction -*DecoLite* is a little gem that allows you to use the provided `DecoLite::Model` class to dynamically create Decorator class objects. Use the `DecoLite::Model` class directly, or inherit from the `DecoLite::Model` class to create your own unique subclasses with custom functionality. `DecoLite::Model` includes `ActiveModel::Model`, so validation can be applied using [ActiveModel validation helpers](https://api.rubyonrails.org/v6.1.3/classes/ActiveModel/Validations/HelperMethods.html) you're familiar with; or, you can roll your own - just like any other `ActiveModel`. +_DecoLite_ is a little gem that allows you to use the provided `DecoLite::Model` class to dynamically create Decorator class objects. Use the `DecoLite::Model` class directly, or inherit from the `DecoLite::Model` class to create your own unique subclasses with custom functionality. `DecoLite::Model` includes `ActiveModel::Model`, so validation can be applied using [ActiveModel validation helpers](https://api.rubyonrails.org/v6.1.3/classes/ActiveModel/Validations/HelperMethods.html) you're familiar with; or, you can roll your own - just like any other `ActiveModel`. -`DecoLite::Model` allows you to consume a Ruby `Hash` that you supply via the initializer (`DecoLite::Model#new`) or via the `DecoLite::Model#load!` method. Any number of Ruby `Hashes` can be consumed. Your supplied Ruby Hashes are used to create `attr_accessor` attributes (or *"fields"*) on the model. Each attribute created is then assigned the value from the Hash that was loaded. Again, any number of hashes can be consumed using the `DecoLite::Model#load!` method. +`DecoLite::Model` allows you to consume a Ruby `Hash` that you supply via the initializer (`DecoLite::Model#new`) or via the `DecoLite::Model#load!` method. Any number of Ruby `Hashes` can be consumed. Your supplied Ruby Hashes are used to create `attr_accessor` attributes (or _"fields"_) on the model. Each attribute created is then assigned the value from the Hash that was loaded. Again, any number of hashes can be consumed using the `DecoLite::Model#load!` method. -`attr_accessors` created during initialization, or by calling `DecoLite::Model#load!`, are *mangled* to include namespacing. This allows `DecoLite` to create *unique* attribute names for nested Hashes that may have non-unique key names. For example: +`attr_accessors` created during initialization, or by calling `DecoLite::Model#load!`, are _mangled_ to include namespacing. This allows `DecoLite` to create _unique_ attribute names for nested Hashes that may have non-unique key names. For example: ```ruby # NOTE: keys :name and :age are not unique across this Hash. @@ -28,7 +29,8 @@ family = { } } ``` -Given the above example, `DecoLite` will produce the following *unique* `attr_accessors` on the `DecoLite::Model` object, and assign the values: + +Given the above example, `DecoLite` will produce the following _unique_ `attr_accessors` on the `DecoLite::Model` object, and assign the values: ```ruby # Instead of the below, you can also use DecoLite::Model.new.load!(hash: family) @@ -41,11 +43,11 @@ model.wife_name #=> 'Mary Doe' model.wife_age #=> 30 ``` -In the above example, notice how `DecoLite` *mangles* attributes `:wife_name` and `:wife_age` using the `:wife` `Hash` key name to make them unique. +In the above example, notice how `DecoLite` _mangles_ attributes `:wife_name` and `:wife_age` using the `:wife` `Hash` key name to make them unique. -`DecoLite::Model#load!` can be called *multiple times*, on the same model using different `Hashes`. This could potentially cause `attr_accessor` name clashes. In order to ensure unique `attr_accessor` names, a *"namespace"* may be *explicitly* provided to ensure attribute name uniqueness. +`DecoLite::Model#load!` can be called _multiple times_, on the same model using different `Hashes`. This could potentially cause `attr_accessor` name clashes. In order to ensure unique `attr_accessor` names, a _"namespace"_ may be _explicitly_ provided to ensure attribute name uniqueness. -For example, **continuing from the previous example,** if we were to call `DecoLite::Model#load!` a *second time* with the following `Hash`, this would produce `attr_accessor` name clashes which would raise errors, because `:name` and `:age` attributes already exist on the `DecoLite::Model` in question: +For example, **continuing from the previous example,** if we were to call `DecoLite::Model#load!` a _second time_ with the following `Hash`, this would produce `attr_accessor` name clashes which would raise errors, because `:name` and `:age` attributes already exist on the `DecoLite::Model` in question: ```ruby grandpa = { @@ -76,10 +78,12 @@ For more examples and usage, see the [Examples and usage](#examples-and-usage) a ## Use cases ### Generally Speaking -`DecoLite` would *most likely* thrive where the structure of the `Hashe(s)` consumed are (of course) known, relatively small to moderate in size, and not *terribly* deep nested-hash-wise. This is because of the way `DecoLite` mangles loaded Hash key names to create unique `attr_accessors` on the model (see the Introduction section). However, I'm sure there are some geniuses out there that would find other contexts where `DecoLite` may thrive. Assuming the former is the case, `DecoLite` would be ideal to consume Model attributes, Webservice JSON results (converted to Ruby `Hash`), JSON Web Token (JWT) payloads, etc. to create a cohesive data model to be used in any scenario. + +`DecoLite` would _most likely_ thrive where the structure of the `Hashe(s)` consumed are (of course) known, relatively small to moderate in size, and not _terribly_ deep nested-hash-wise. This is because of the way `DecoLite` mangles loaded Hash key names to create unique `attr_accessors` on the model (see the Introduction section). However, I'm sure there are some geniuses out there that would find other contexts where `DecoLite` may thrive. Assuming the former is the case, `DecoLite` would be ideal to consume Model attributes, Webservice JSON results (converted to Ruby `Hash`), JSON Web Token (JWT) payloads, etc. to create a cohesive data model to be used in any scenario. ### Rails -Because `DecoLite::Model` includes `ActiveModel::Model`, it could also be ideal for use as a model in Rails applications, where a *decorator pattern* might be used, and decorator methods provided for use in Rails views; for example: + +Because `DecoLite::Model` includes `ActiveModel::Model`, it could also be ideal for use as a model in Rails applications, where a _decorator pattern_ might be used, and decorator methods provided for use in Rails views; for example: ```ruby class ViewModel < DecoLite::Model @@ -105,9 +109,10 @@ view_model.full_name view_model.salutation => "Hello John Doe, welcome back!" ``` + ### Etc., etc., etc. -Get creative. Please pop me an email and let me know how *you're* using `DecoLite`. +Get creative. Please pop me an email and let me know how _you're_ using `DecoLite`. ## Examples and usage @@ -166,13 +171,14 @@ model.wife_name #=> Amy Doe model.wife_info_age #=> 20 model.wife_info_address #=> 1 street, boonton, nj 07005 ``` + ## More examples and usage ### I want to... #### Add validators to my model -Simply add your `ActiveModel` validators just like you would any other `ActiveModel::Model` validator, with one caveat noted below. It is important to note that any attribute (field) having an *explicit validation* associated with it, will automatically cause `DecoLite` to create an `attr_accessor` for that field; this is to avoid `NoMethodErrors` when validating the model (e.g. `#valid?`, `#validate`, etc.) *before* the data is loaded. Why does `DecoLite` need to do this? Typically, `DecoLite` dynamically creates `attr_accessors` using the keys from the `Hash` loaded into the model. If the `Hash` loaded into your `DecoLite` model _does not_ include a `Hash` key for the attribute referenced by any validators on your model, `DecoLite` will not create an `attr_accessor` for it; consequently, calling any validation method (e.g. `#valid?`, `#validate`, etc.) on your model will result in a `NoMethodError` for that attribute. +Simply add your `ActiveModel` validators just like you would any other `ActiveModel::Model` validator, with one caveat noted below. It is important to note that any attribute (field) having an _explicit validation_ associated with it, will automatically cause `DecoLite` to create an `attr_accessor` for that field; this is to avoid `NoMethodErrors` when validating the model (e.g. `#valid?`, `#validate`, etc.) _before_ the data is loaded. Why does `DecoLite` need to do this? Typically, `DecoLite` dynamically creates `attr_accessors` using the keys from the `Hash` loaded into the model. If the `Hash` loaded into your `DecoLite` model _does not_ include a `Hash` key for the attribute referenced by any validators on your model, `DecoLite` will not create an `attr_accessor` for it; consequently, calling any validation method (e.g. `#valid?`, `#validate`, etc.) on your model will result in a `NoMethodError` for that attribute. One caveat to note is when using Rails custom validators with `validates_with`. When using Rails custom validators via `validates_with`, you should pass the attribute names being validated to your custom validator via the `#options` `Hash` with a key of either `:attributes` or `:fields`. This is so that `DecoLite` can create dynamic `attr_accessors` for these attributes and avoid the aformentioned `NoMethodError` (see above): @@ -205,12 +211,13 @@ model.valid? #### Validate whether or not certain fields were loaded -To be clear, this example does not validate the *data* associated with the fields loaded; rather, this example validates whether or not the *fields themselves* were loaded into your model, and as a result, `attr_accessors` created *for* them on the model. If you only want to validate the *data* loaded into your model, simply use `ActiveModel` validations, just like you would any other `ActiveModel` model (see the [Add validators to my model](#add-validators-to-my-model) section). +To be clear, this example does not validate the _data_ associated with the fields loaded; rather, this example validates whether or not the _fields themselves_ were loaded into your model, and as a result, `attr_accessors` created _for_ them on the model. If you only want to validate the _data_ loaded into your model, simply use `ActiveModel` validations, just like you would any other `ActiveModel` model (see the [Add validators to my model](#add-validators-to-my-model) section). -If you want to validate whether or not particular *fields* were loaded into your model, as a result of `#load!`ing data into your model, you need to add the required field names to the `DecoLite::Model#required_fields` attribute, or use inheritance: - - Create a `DecoLite::Model` subclass. - - Override the `DecoLite::Model#required_fields` method. - - Return an Array of `Symbols` that represent the fields you want to validate (e.g. `%i[first last ssn]`). +If you want to validate whether or not particular _fields_ were loaded into your model, as a result of `#load!`ing data into your model, you need to add the required field names to the `DecoLite::Model#required_fields` attribute, or use inheritance: + +- Create a `DecoLite::Model` subclass. +- Override the `DecoLite::Model#required_fields` method. +- Return an Array of `Symbols` that represent the fields you want to validate (e.g. `%i[first last ssn]`). For example: @@ -234,7 +241,7 @@ model.errors.full_messages #=> ["First field is missing", "Last field is missing", "Address field is missing"] ``` -If we load data that includes :first, :last, and :address Hash keys, even with nil data, our `": field is missing"` errors would go away; in this scenario, we only wish to validate the *presence of the FIELDS,* not the data associated with these fields! +If we load data that includes :first, :last, and :address Hash keys, even with nil data, our `": field is missing"` errors would go away; in this scenario, we only wish to validate the _presence of the FIELDS,_ not the data associated with these fields! ```ruby model.load!(hash: { first: nil, last: nil, address: nil }) @@ -260,11 +267,12 @@ model.validate model.errors.full_messages #=> ["Age is not a number"] ``` -#### Validate whether or not certain fields were loaded *and* validate the data associated with these same fields -If you simply want to validate the *data* loaded into your model, simply add `ActiveModel` validation, just like you would any other `ActiveModel` model (see the [Add validators to my model](#add-validators-to-my-model) section). +#### Validate whether or not certain fields were loaded _and_ validate the data associated with these same fields + +If you simply want to validate the _data_ loaded into your model, simply add `ActiveModel` validation, just like you would any other `ActiveModel` model (see the [Add validators to my model](#add-validators-to-my-model) section). -If you want to validate whether or not particular fields were loaded *and* the field data associated with those same fields, you simply need to return the required fields from the `DecoLite#required_fields` method and add the appropriate validation(s); for example: +If you want to validate whether or not particular fields were loaded _and_ the field data associated with those same fields, you simply need to return the required fields from the `DecoLite#required_fields` method and add the appropriate validation(s); for example: ```ruby class Model < DecoLite::Model @@ -294,7 +302,7 @@ model.errors.full_messages #### Manually define attributes (fields) on my model -Manually defining attributes on your subclass is possible, although there doesn't seem a valid reason to do so, since you can just use `DecoLite::Model#load!` to wire all this up for you automatically. However, if there *were* a need to do this, you must add your `attr_reader` to the `DecoLite::Model@field_names` array, or an error will be raised _provided_ there are any conflicting field names being loaded using `DecoLite::Model#load!`. Note that the aforementioned error will be raised regardless of whether or not you set `options: { fields: :merge }`. This is because `DecoLite` considers any existing model attributes *not* added to the model via `load!`*to be native to the model object,* and therefore will not allow you to create `attr_accessors` and assign values to existing model attributes because this can potentially be dangerous. +Manually defining attributes on your subclass is possible, although there doesn't seem a valid reason to do so, since you can just use `DecoLite::Model#load!` to wire all this up for you automatically. However, if there _were_ a need to do this, you must add your `attr_reader` to the `DecoLite::Model@field_names` array, or an error will be raised _provided_ there are any conflicting field names being loaded using `DecoLite::Model#load!`. Note that the aforementioned error will be raised regardless of whether or not you set `options: { fields: :merge }`. This is because `DecoLite` considers any existing model attributes _not_ added to the model via `load!`_to be native to the model object,_ and therefore will not allow you to create `attr_accessors` and assign values to existing model attributes because this can potentially be dangerous. To avoid errors when manually defining model attributes that could potentially conflict with fields loaded using `DecoLite::Model#load!`, you could do the following: