Skip to content

Commit

Permalink
Implemented Version 2.0.0 using Ruby Refinements
Browse files Browse the repository at this point in the history
  • Loading branch information
AndyObtiva committed May 29, 2020
1 parent 674d24e commit 5ecddcb
Show file tree
Hide file tree
Showing 16 changed files with 137 additions and 377 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ pkg

# For rubinius:
#*.rbc

# Glimmer Editor
.gladiator
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.4.0
2.7.1
23 changes: 4 additions & 19 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,7 @@
language: ruby
rvm:
- 2.7.1
- 2.6.6
- 2.5.8
- 2.4.0
- 2.3.3
- 2.2.3
- 2.1.5
- 2.0.0
gemfile:
- Gemfile
- ruby200-215.Gemfile
matrix:
exclude:
- rvm: 2.4.0
gemfile: ruby200-215.Gemfile
- rvm: 2.3.3
gemfile: ruby200-215.Gemfile
- rvm: 2.2.3
gemfile: ruby200-215.Gemfile
- rvm: 2.1.5
gemfile: Gemfile
- rvm: 2.0.0
gemfile: Gemfile
- jruby-9.2.11.1
2 changes: 0 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
source "http://rubygems.org"

gem 'super_module', '1.2.2'

group :development do
gem 'jeweler', '~> 2.3.3'
gem "coveralls", '~> 0.8.19', require: false
Expand Down
45 changes: 26 additions & 19 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ GEM
remote: http://rubygems.org/
specs:
addressable (2.4.0)
builder (3.2.3)
builder (3.2.4)
coveralls (0.8.23)
json (>= 1.8, < 3)
simplecov (~> 0.16.1)
Expand All @@ -15,16 +15,18 @@ GEM
docile (1.3.2)
faraday (0.9.2)
multipart-post (>= 1.2, < 3)
git (1.5.0)
git (1.7.0)
rchardet (~> 1.8)
github_api (0.16.0)
addressable (~> 2.4.0)
descendants_tracker (~> 0.0.4)
faraday (~> 0.8, < 0.10)
hashie (>= 3.4)
mime-types (>= 1.16, < 3.0)
oauth2 (~> 1.0)
hashie (3.6.0)
highline (2.0.2)
hashie (4.1.0)
highline (2.0.3)
jar-dependencies (0.4.0)
jeweler (2.3.9)
builder
bundler
Expand All @@ -36,26 +38,30 @@ GEM
rake
rdoc
semver2
json (2.2.0)
json (2.3.0)
json (2.3.0-java)
jwt (2.2.1)
method_source (0.9.2)
mime-types (2.99.3)
mini_portile2 (2.4.0)
multi_json (1.13.1)
multi_json (1.14.1)
multi_xml (0.6.0)
multipart-post (2.1.1)
nokogiri (1.10.3)
nokogiri (1.10.9)
mini_portile2 (~> 2.4.0)
oauth2 (1.4.1)
faraday (>= 0.8, < 0.16.0)
nokogiri (1.10.9-java)
oauth2 (1.4.4)
faraday (>= 0.8, < 2.0)
jwt (>= 1.0, < 3.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
psych (3.1.0)
rack (2.0.7)
rake (12.3.3)
rdoc (6.1.1)
psych (3.1.0-java)
jar-dependencies (>= 0.1.7)
rack (2.2.2)
rake (13.0.1)
rchardet (1.8.0)
rdoc (6.2.1)
rspec (3.5.0)
rspec-core (~> 3.5.0)
rspec-expectations (~> 3.5.0)
Expand All @@ -75,22 +81,23 @@ GEM
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
super_module (1.2.2)
method_source (>= 0.8.2)
sync (0.5.0)
term-ansicolor (1.7.1)
tins (~> 1.0)
thor (0.20.3)
thor (1.0.1)
thread_safe (0.3.6)
tins (1.21.1)
thread_safe (0.3.6-java)
tins (1.25.0)
sync

PLATFORMS
java
ruby

DEPENDENCIES
coveralls (~> 0.8.19)
jeweler (~> 2.3.3)
rspec (~> 3.5.0)
super_module (= 1.2.2)

BUNDLED WITH
1.14.4
2.1.4
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2017 Andy Maleh
Copyright (c) 2017-2020 Andy Maleh

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
112 changes: 36 additions & 76 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
# `to_collection`
# ToCollection 2.0.0 Ruby Refinement
[![Gem Version](https://badge.fury.io/rb/to_collection.svg)](http://badge.fury.io/rb/to_collection)
[![Build Status](https://travis-ci.org/AndyObtiva/to_collection.svg?branch=master)](https://travis-ci.org/AndyObtiva/to_collection)
[![Coverage Status](https://coveralls.io/repos/github/AndyObtiva/to_collection/badge.svg?branch=master)](https://coveralls.io/github/AndyObtiva/to_collection?branch=master)

Treat an array of objects and a singular object uniformly as a collection of objects.
Especially useful in processing REST Web Service API JSON responses in a functional approach.

Especially useful in processing REST Web Service API JSON responses in a uniform functional approach.

`ToCollection` is a Ruby Refinement, so it may be safely enabled via `using ToCollection` where needed only.

## Introduction

Canonicalize data to treat uniformly whether it comes in as a single object or an array of objects, dropping `nils` out automatically.

API: `object.to_collection(compact)` where `compact` is a boolean for whether to compact collection or not. It is true by default.
API: `object#to_collection(compact=true)` where `compact` is a boolean for whether to compact collection or not. It is true by default.

Example:

Expand Down Expand Up @@ -39,17 +42,24 @@ end

## Instructions

### Bundler / Rails
### Bundler

`gem 'to_collection', '~> 1.0.1'`
- Add `gem 'to_collection', '~> 2.0.0'` to Gemfile
- Run `bundle`
- Require `to_collection` ruby gem in code (e.g. via `Bundler.require(:default)` or `require 'bundler/setup'` & `require 'to_collection'`)
- Add `using ToCollection` to the top of the Ruby file you would like to refine `Object` in with `#to_collection` method.

### Plain Ruby
### Manual Gem Install

`require 'to_collection'`
- Run `gem install to_collection -v2.0.0`
- Add `require 'to_collection'` to code
- Add `using ToCollection` to the top of the Ruby file you would like to refine `Object` in with `#to_collection` method.

### Note

Code above enables `#to_collection` method on all classes inheriting from `Object`. See [options](#options) below in case you prefer to **manually** include in certain classes only.
If '#to_collection' was already defined on `Object` in a project, requiring the `to_collection` library will print a warning.

It is still safe to require as it does not overwrite `Object#to_collection` except in Ruby files where `using ToCollection` is added.

## Background

Expand All @@ -63,7 +73,7 @@ GET /people
<= 1 person
JSON Response:
```JSON
{"first_name":"karim","last_name":"akram","city":"Dubai"}
{"first_name":"John","last_name":"Barber","city":"Chicago"}
```

HTTP Request: =>
Expand All @@ -75,7 +85,7 @@ GET /people
JSON Response:

```JSON
[{"first_name":"karim","last_name":"akram","city":"Dubai"}, {"first_name":"muhsen","last_name":"asaad","city":"Amman"}, {"first_name":"assaf","last_name":"munir","city":"Qatar"}]
[{"first_name":"John","last_name":"Barber","city":"Chicago"}, {"first_name":"Mark","last_name":"Jones","city":"New York"}, {"first_name":"Josh","last_name":"Beeswax","city":"Denver"}]
```

How do you work with the varied JSON responses in Ruby?
Expand Down Expand Up @@ -198,7 +208,7 @@ Example:
How about go one step further and bake this into all objects using our previous approach of object-oriented polymorphism and Ruby open-classes? That way, we don't just collapse the difference between dealing with arrays of hashes vs hashes but also arrays of objects vs singular objects by adding. Note the use of flatten(1) below to prevent arrays or arrays from collapsing more than one level.

```ruby
Object.class_eval do
class Object
def to_collection
[self].flatten(1).compact
end
Expand All @@ -218,7 +228,7 @@ end
A refactored version including optional compacting would be:

```ruby
Object.class_eval do
class Object
def to_collection(compact=true)
collection = [self].flatten(1)
compact ? collection.compact : collection
Expand All @@ -241,77 +251,27 @@ people_http_request.to_collection(false).each do |person|
end
```

You asked for "Elegant" didn't you? I hope that was what you were looking for.

## How It Works

A [super_module](https://github.com/AndyObtiva/super_module) called `ToCollection`
contains the `#to_collection` method and is included (mixed) into `Object`, providing
`#to_collection` method to inheriting classes.

## Options

### `Object.to_collection_already_implemented_strategy`
Possible Values: `"raise_error"` (default), `"keep"`, `"overwrite"`

Setting this option allows developer to configure handling of the case when
`Object#to_collection` already exists before loading `to_collection` library.

#### `"raise_error"` (default)

For safety reasons, the library will raise AlreadyImplementedError by default to
alert the developer and provide information about the other options.
This prevents later surprises and puts control in the hand of the developer to
responsibly decide what option to pick next.

#### `"keep"`

This keeps existing `to_collection` untouched, disabling this library.

#### `"overwrite"`

This overwrites existing `to_collection` method, fully enabling this library.

### `ENV['TO_COLLECTION_ALREADY_IMPLEMENTED_STRATEGY']`
Possible Values: `"raise_error"` (default), `"keep"`, `"overwrite"`

Same function as `Object.to_collection_already_implemented_strategy`.
Environment variable takes precedence over class accessor variable though.

### `ENV['TO_COLLECTION_OBJECT_INCLUDE']`
Possible Values: `"true"` (default), `"false"`

Must be set before requiring/loading library. When using bundler, ensure `require` option is set to `false` or `nil`.

`ToCollection` [super_module](https://github.com/AndyObtiva/super_module) is automatically included in `Object` except when `ENV['TO_COLLECTION_OBJECT_INCLUDE']` is set to `"false"`, providing developer with the option to **manually** include (mix in) `ToCollection` [super_module](https://github.com/AndyObtiva/super_module) into classes that need it.

Example:

Bundler would have gem require option as false:
Of course, in Ruby 2+, you may use Ruby Refinements, so simply include `Object#to_collection` via this line instead:

```ruby
gem 'to_collection', require: false
using ToCollection
```

Ruby code would then set that environment variable **manually** before requiring library:
You asked for "Elegant" didn't you? I hope that was what you were looking for.

```ruby
ENV['TO_COLLECTION_OBJECT_INCLUDE'] = false
require 'to_collection'
Hash.instance_eval do
include ToCollection #enables to_collection method
end
Array.instance_eval do
include ToCollection #enables to_collection method
end
response_data = people_http_request #returns single hash or array of hashes
response_data.to_collection.each do |person_hash|
# do some work
end
```
## How It Works

A Ruby Refinement is activated via `using ToCollection` adding/overwriting the `#to_collection` method in `Object`, which
is the ancestor of all Ruby objects.

## Release Notes

### v2.0.0

- Revamped API using Ruby Refinements (safer than monkey-patching)
- Removed `super_module` gem dependency
- Dropped safety options since Ruby Refinements already handle things safely

### v1.0.1

- Updated `super_module` gem version to relax indirect `method_source` gem version dependency
Expand All @@ -330,5 +290,5 @@ end

## Copyright

Copyright (c) 2017 Andy Maleh. See LICENSE.txt for
Copyright (c) 2017-2020 Andy Maleh. See LICENSE.txt for
further details.
35 changes: 2 additions & 33 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,8 @@ Jeweler::Tasks.new do |gem|
gem.name = "to_collection"
gem.homepage = "http://github.com/AndyObtiva/to_collection"
gem.license = "MIT"
gem.summary = %Q{Treat an array of objects and a singular object uniformly as a collection of objects}
gem.description = %Q{
Treat an array of objects and a singular object uniformly as a collection of objects. Especially useful in processing REST Web Service API JSON responses in a functional approach.
Canonicalize data to treat uniformly whether it comes in as a single object or an array of objects, dropping `nils` out automatically.
API: `object.to_collection(compact)` where `compact` is a boolean for whether to compact collection or not. It is true by default.
Example:
```ruby
city_counts = {}
people_http_request.to_collection.each do |person|
city_counts[person["city"]] ||= 0
city_counts[person["city"]] += 1
end
```
Wanna keep `nil` values? No problem! Just pass `false` as an argument:
```ruby
bad_people_count = 0
city_counts = {}
people_http_request.to_collection(false).each do |person|
if person.nil?
bad_people_count += 1
else
city_counts[person["city"]] ||= 0
city_counts[person["city"]] += 1
end
end
```
}
gem.summary = %Q{ToCollection Ruby Refinement - Treat an array of objects and a singular object uniformly as a collection of objects}
gem.description = %Q{ToCollection Ruby Refinement - Treat an array of objects and a singular object uniformly as a collection of objects}
gem.email = "andy.am@gmail.com"
gem.authors = ["Andy Maleh"]
gem.files = Dir['lib/**/*.rb']
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.1
2.0.0
6 changes: 0 additions & 6 deletions lib/ext/object.rb

This file was deleted.

0 comments on commit 5ecddcb

Please sign in to comment.