No commit activity in last 3 years
No release in over 3 years
Support classes for Repository::Base and subclasses.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.16
>= 1.16.2
>= 12.3.0, ~> 12.3
~> 3.7
>= 0.52.1, ~> 0.52
>= 0.15.1, ~> 0.15
>= 0.9.12

Runtime

>= 4.2.3, ~> 4.2
 Project Readme

Repository::Support

Gem Version Code Climate Codeship Status for jdickey/repository-support security Dependency Status

Contents

  • Overview
  • IMPORTANT LEGACY NOTICE
  • Installation
  • Usage
    • StoreResult
      • StoreResult::Success
      • StoreResult::Failure
    • ErrorFactory
    • TestAttributeContainer
    • A Note on Parameters
  • Contributing
  • Version History
  • Legal

Overview

This Gem provides several support classes and modules for Repository::Base and its user-created subclasses, which implement the Repository layer of a typical Data Mapper pattern architecture.

These classes and modules are:

  • ErrorFactory provides a single class method, .create which, when supplied with an ActiveModel::Errors-quacking object as a parameter, produces an Array of Hashes containing JSON-compatible error information;
  • ResultBuilder is a Command-pattern class whose #initialize method takes one parameter and whose #build method evaluates that value. If it is truthy, then #build returns a StoreResult::Success (see below) with that value as its "paylaaod". If the value is falsy, then #build returns a StoreResult#Failure, yielding the value to a block that returns the payload for the StoreResult.
  • StoreResult is a simple value object with three accessors for values passed in to the #initialize method: namely #entity (some value object); #success (a Boolean, aliased as #success?); and #errors an Array of error records as created by ErrorFactory.create. It has two subclasses: StoreResult::Success fills in a StoreResult using its single parameter (the entity) and defaults for the other fields; and StoreResult::Failure, which does likewise initialised with an array of error hashes.
  • TestAttributeContainer is a module that, when used to extend a class, adds an attributes Hash property (reader and writer) to the extending class. While attributes is initially empty, it may be added to either by defining a single key, or by mass-assigning a Hash to attributes. Once an individual "attribute" is defined for a class instance, it can be read from or written to using a direct method on that instance. See the discussion in "Usage" below for more details.

IMPORTANT LEGACY NOTICE

NOTICE! This Gem was created to support a solo, ad-hoc, early learning experience in what is now known as Clean Architecture. It was part of our first attempt to build an alternative to the ActiveRecord/ActiveModel scheme native to Ruby on Rails.

As such, it has been superseded and far outshone by other, team efforts, notably ROM as used with Hanami and Trailblazer. You are strongly advised to examine these and other tools rather than to use this for any new development. The Gem is being republished as an 0.1.0 release purely for internal archaeologigical purposes.

Installation

Add this line to your application's Gemfile:

gem 'repository-support'

And then execute:

$ bundle

Or install it yourself as:

$ gem install repository-support

Usage

StoreResult

StoreReult is used as the return value from all Repository::Base instance methods (actions) except #all.

If the action implemented by the method was successful, it returns a StoreResult where

  • the entity attribute is an entity matching the state of the record persisted or accessed by the action;
  • the success attribute (or #success? method) is true; and
  • the errors attribute is an empty Array.

If the action was unsuccessful, the repository method returns a StoreResult where

  • the entity attribute is nil;
  • the success attribute (or #success? method) is false; and
  • the errors attribute contains one error Hash for each error preventing the action from succeeding.

StoreResult::Success

This subclass of StoreResult is a convenience for initialising a successful StoreResult. Its #initialize method takes a single parameter, the entity to be used in the result, with the other fields set as described above for a successful result.

StoreResult::Failure

This subclass of StoreResult is a convenience for initialising an unsuccessful StoreResult. Its #initialize method takes a single parameter, the Array of error hashes to be used in the result, with the other fields set as described above for an unsuccessful result.

ErrorFactory

This class has a single class method, .create. Given a parameter value that quacks as anActiveModel::Errors instance, it returns an Array where each item is a Hash derived from each error reported by the parameter object, or an empty Array if there are no errors. Each Hash in the Array has two fields:

  1. field, which contains the attribute passed to ActiveModel::Errors#add as a string; and
  2. message, which contains the message as passed into the same #add call.

So, given an ActiveModel::Errors object that resulted from the following code:

  errors = ActiveModel::Errors.new self
  # ...
  errors.add :frobulator, 'does not frob'
  errors.add :frobulator, `is busted'
  errors.add :foo, 'is :foo'
  # ...
  error_data = ErrorFactory.create errors
  @logger.log error_data

the value of error_data written to the log would be (formatted for clarity)

[
  {field: 'frobulator', message: 'does not frob'},
  {field: 'frobulator', message: 'is busted'},
  {field: 'foo', 'is :foo'}
]

Note that no guarantees are made for ordering, just as seems to be the case for ActiveModel::Errors.

TestAttributeContainer

This module implements support for attributes in a way that can be thought of as "halfway between a Struct and an OpenStruct or FancyOpenStruct."

By extending a class with the module and invoking the init_empty_attribute_container class method within that class, a Hash is added as the attributes attribute of each instance of that class. It can be assigned to directly; once having done so, individual "attributes" may be accessed or modified through a method call using the name of the attribute.

For example:

class Foo
  extend Repository::Support::TestAttributeContainer

  init_empty_attribute_container
end

# interactively
foo = Foo.new
# => #<Foo:0x007fd2b4b9da28>
foo.attributes
# => {}
foo.attributes = { foo: true, bar: 42 }
# => {:foo=>true, :bar=>42}
foo.foo
# => true
foo.foo = :whatever_you_want
# => :whatever_you_want
foo.attributes
# => {:foo=>:whatever_you_want, :bar=>42}
foo.quux
# => NoMethodError: undefined method `quux' # ...
foo.attributes[:quux] = 'hello'
# => "hello"
foo.quux
# => "hello"

To create a new attribute after the container has been set up, assign to a new key in the attributes property Hash. As demonstrated above, the "attribute" can then be accessed or modified by using its name as a reader or writer method name. Without explicitly assigning to attributes, however, undefined methods raise errors as usual.

A Note on Parameters

All public methods having multiple arguments (including #initialize) in each of the classes defined above use the keyword-argument specification introduced in Ruby 2.0. By removing order dependency of arguments, inadvertent-reordering errors are no longer a hunt-the-typo exercise. This rule does not apply to single-parameter methods, nor to private methods.

Contributing

  1. Fork it ( https://github.com/jdickey/repository-support/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Ensure that your changes are completely covered by passing specs, and comply with the Ruby Style Guide as enforced by RuboCop. To verify this, run bundle exec rake, noting and repairing any lapses in coverage or style violations;
  4. Commit your changes (git commit -a). Please do not use a single-line commit message (git commit -am "some message"). A good commit message notes what was changed and why in sufficient detail that a relative newcomer to the code can understand your reasoning and your code;
  5. Push to the branch (git push origin my-new-feature)
  6. Create a new Pull Request. Describe at some length the rationale for your new feature; your implementation strategy at a higher level than each individual commit message; anything future maintainers should be aware of; and so on. If this is a modification to existing code, reference the open issue being addressed.
  7. Don't be discouraged if the PR generates a discussion that leads to further refinement of your PR through additional commits. These should generally be discussed in comments on the PR itself; discussion in the Gitter room (see below) may also be useful;
  8. If you've comments, questions, or just want to talk through your ideas, don't hesitate to hang out in the Repository::Base room on Gitter. Ask away!

Version History

Version Date Notes
v0.1.0 2 February 2018 Changed MRI supported version from 2.2.2 to 2.5.0; published legacy notice
v0.0.4 9 March 2015 Added experimental, one-off JRuby 9000 support
v0.0.3 21 February 2015 Completed initial feature development
v0.0.2 18 February 2015 Internal; incremental feature development
v0.0.1 18 February 2015 Internal; incremental feature development

Legal

This document and the accompanying code are Copyright © 2015-2018 by Jeff Dickey/Seven Sigma Agility, and are released under the terms of the MIT License.