README.md in attr-gather-1.2.0 vs README.md in attr-gather-1.2.1

- old
+ new

@@ -1,11 +1,149 @@ # attr-gather -[![Actions Status](https://github.com/ianks/attr-gather/workflows/.github/workflows/ruby.yml/badge.svg)](https://github.com/ianks/attr-gather/actions) +![Actions Status](https://github.com/ianks/attr-gather/workflows/Build%20+%20Test%20+%20Lint/badge.svg) +[![Maintainability](https://api.codeclimate.com/v1/badges/b825a3bc37ad6a76e005/maintainability)](https://codeclimate.com/github/ianks/attr-gather/maintainability) -A gem for creating simple workflows to enhance entities with extra attributes. -At a high level, `attr-gather` provides a process to sync attributes from many -sources (third party APIs, legacy databases, etc). +A gem for creating workflows that "enhance" entities with extra attributes. At a high level, [attr-gather](https://github.com/ianks/attr-gather) provides a process to fetch information from many data sources (such as third party APIs, legacy databases, etc.) in a fully parallelized fashion. + +## Usage + +### Defining your workflow + +```ruby +# define a workflow +class EnhanceProfile + include Attr::Gather::Workflow + + # contains all the task implementations + container TasksContainer + + # filter out invalid data using a Dry::Validation::Contract + # anything that doesn't match this schema will be filtered out + filter_with_contract do + params do + required(:user_id).filled(:integer) + + optional(:user).hash do + optional(:name).filled(:string) + optional(:email).filled(:string) + optional(:gravatar).filled(:string) + optional(:email_info).hash do + optional(:deliverable).filled(:bool?) + optional(:free).filled(:bool?) + end + end + end + end + + # each task returns a hash of data that will be merged into the result + task :fetch_post do |t| + t.depends_on = [] + end + + # will run in parallel + task :fetch_user do |t| + t.depends_on = [:fetch_post] + end + + # will run in parallel + task :fetch_email_info do |t| + t.depends_on = [:fetch_user] + end +end +``` + +### Defining some tasks + +```ruby +class PostFetcher + def call(attrs) + res = HTTP.get("https://jsonplaceholder.typicode.com/posts/#{attrs[:id]}") + post = JSON.parse(res.to_s, symbolize_names: true) + + { title: post[:title], user_id: post[:userId], body: post[:body] } + end +end +``` + +```ruby +class UserFetcher + # will have access to the PostFetcher attributes here + def call(attrs) + res = HTTP.get("https://jsonplaceholder.typicode.com/users/#{attrs[:user_id]}") + user = JSON.parse(res.to_s, symbolize_names: true) + + { user: { name: user[:name], email: user[:email] } } + end +end +``` + +```ruby +class EmailInfoFetcher + # will have access to the PostFetcher attributes here + def call(user:) + res = HTTP.timeout(3).get("https://api.trumail.io/v2/lookups/json?email=#{user[:email]}") + info = JSON.parse(res.to_s, symbolize_names: true) + + # will deep merge with the final result + { user: { email_info: { deliverable: info[:deliverable], free: info[:free] } } } + end +end +``` + +### Registering your tasks + +```ruby +class MyContainer + extend Dry::Container::Mixin + + register :fetch_post, PostFetcher + register :fetch_user, UserFetcher + register :fetch_email_info, EmailInfoFetcher +end +``` + +### Run it! + +```ruby +enhancer = EnhanceUserProfile.new +enhancer.call(id: 12).value! +``` + +And this is the result... + +```ruby +{ + :id => 12, + :user_id => 2, + :user => { + :email => "Shanna@melissa.tv", + :name => "Ervin Howell", + :email_info => { :deliverable => true, :free => true }, + :gravatar => "https://www.gravatar.com/avatar/241af7d19a0a7438794aef21e4e19b79" + } +} +``` + +You can even preview it as an SVG! + +```ruby +enhancer.to_dot(preview: true) # requires graphviz (brew install graphviz) +``` + +## Features + +- Offers DSL for defining workflows and merging the results from each task +- Execution engine optimally parallelizes the execution of the workflow dependency graph using [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby) Promises +- Very easy to unit test +- Ability to filter out bad/junky data using [dry-validation](https://dry-rb.org/gems/dry-validation) contracts + +## What are the main difference between this Ruby project and similar ones? + +- Operates on a single entity rather than a list, so easily adoptable in existing systems +- Focuses on the "fetching" and filtering of data solely, and not transformation or storage +- Focuses on having a clean PORO interface to make testing simple +- Provides a declarative interface for merging results from many sources (APIs, legacy databases, etc.) which allows for prioritization ## Links - [API Documentation](https://www.rubydoc.info/gems/attr-gather)