0.0
A long-lived project that still receives updates
EverythingRB extends Ruby core classes with useful methods for combining operations (join_map), converting data structures (to_struct, to_ostruct, to_istruct), and handling JSON with nested parsing support.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 2.10
~> 0.6
 Project Readme

EverythingRB

Gem Version Ruby Version Tests

Practical extensions to Ruby core classes that let your code say what it means.

Express Your Intent, Not Your Logic

We've all been there - writing the same tedious patterns over and over:

# BEFORE
users = [
  { name: "Alice", role: "admin" },
  { name: "Bob", role: "user" },
  { name: "Charlie", role: "admin" }
]
admin_users = users.select { |u| u[:role] == "admin" }
admin_names = admin_users.map { |u| u[:name] }
result = admin_names.join(", ")
# => "Alice, Charlie"

With EverythingRB, you can write code that actually says what you mean:

# AFTER
users.join_map(", ") { |u| u[:name] if u[:role] == "admin" }
# => "Alice, Charlie"

Methods used: join_map

Installation

# In your Gemfile
gem "everythingrb"

# Or install manually
gem install everythingrb

Usage

There are two ways to use EverythingRB:

Standard Ruby Projects

Load Everything (Default)

The simplest approach - just require and go:

require "everythingrb"

# Now you have access to all extensions!
users = [{name: "Alice"}, {name: "Bob"}]
users.key_map(:name).join(", ")  # => "Alice, Bob"

config = {server: {port: 443}}.to_ostruct
config.server.port  # => 443

Cherry-Pick Extensions

If you only need specific extensions:

require "everythingrb/prelude"  # Required base module
require "everythingrb/array"    # Just Array extensions
require "everythingrb/string"   # Just String extensions

# Now you have access to only the extensions you loaded
["a", "b"].join_map(" | ") { |s| s.upcase }  # => "A | B"
'{"name": "Alice"}'.to_ostruct.name   # => "Alice"

# But Hash extensions aren't loaded yet
{}.to_ostruct  # => NoMethodError

Available modules:

  • array: Array extensions (join_map, key_map, etc.)
  • boolean: Boolean extensions (in_quotes, with_quotes)
  • data: Data extensions (in_quotes)
  • date: Date and DateTime extensions (in_quotes)
  • enumerable: Enumerable extensions (join_map, group_by_key)
  • hash: Hash extensions (to_ostruct, transform_values(with_key: true), etc.)
  • kernel: Kernel extensions (morph alias for then)
  • module: Extensions like attr_predicate
  • nil: NilClass extensions (in_quotes)
  • numeric: Numeric extensions (in_quotes)
  • ostruct: OpenStruct extensions (map, join_map, etc.)
  • range: Range extensions (in_quotes)
  • regexp: Regexp extensions (in_quotes)
  • string: String extensions (parse_json, to_ostruct, to_camelcase, etc.)
  • struct: Struct extensions (in_quotes)
  • symbol: Symbol extensions (with_quotes)
  • time: Time extensions (in_quotes)

Rails Applications

EverythingRB works out of the box with Rails. Just add it to your Gemfile and you're all set.

If you only want specific extensions, configure them in an initializer:

# In config/initializers/everythingrb.rb
Rails.application.configure do
  config.everythingrb.extensions = [:array, :string, :hash]
end

By default (when config.everythingrb.extensions is not set), all extensions are loaded. Setting this to an empty array would effectively disable the gem.

What's Included

Data Structure Conversions

# BEFORE
json_string = '{"user":{"name":"Alice","roles":["admin"]}}'
parsed = JSON.parse(json_string)
result = OpenStruct.new(
  user: OpenStruct.new(
    name: parsed["user"]["name"],
    roles: parsed["user"]["roles"]
  )
)
result.user.name  # => "Alice"
# AFTER
'{"user":{"name":"Alice","roles":["admin"]}}'.to_ostruct.user.name  # => "Alice"

Methods used: to_ostruct

Convert between data structures:

# BEFORE
config_hash = { server: { host: "example.com", port: 443 } }
ServerConfig = Struct.new(:host, :port)
Config = Struct.new(:server)
config = Config.new(ServerConfig.new(config_hash[:server][:host], config_hash[:server][:port]))
# AFTER
config = { server: { host: "example.com", port: 443 } }.to_struct
config.server.host  # => "example.com"

Methods used: to_struct

Extensions: to_struct, to_ostruct, to_istruct, parse_json

Collection Processing

Extract and transform data:

# BEFORE
users = [{ name: "Alice", role: "admin" }, { name: "Bob", role: "user" }]
names = users.map { |user| user[:name] }
# => ["Alice", "Bob"]
# AFTER
users.key_map(:name)  # => ["Alice", "Bob"]

Methods used: key_map

Simplify nested data extraction:

# BEFORE
users = [
  {user: {profile: {name: "Alice"}}},
  {user: {profile: {name: "Bob"}}}
]
names = users.map { |u| u.dig(:user, :profile, :name) }
# => ["Alice", "Bob"]
# AFTER
users.dig_map(:user, :profile, :name)  # => ["Alice", "Bob"]

Methods used: dig_map

Combine filter, map, and join in one step:

# BEFORE
data = [1, 2, nil, 3, 4]
result = data.compact.filter_map { |n| "Item #{n}" if n.odd? }.join(" | ")
# => "Item 1 | Item 3"
# AFTER
[1, 2, nil, 3, 4].join_map(" | ") { |n| "Item #{n}" if n&.odd? }
# => "Item 1 | Item 3"

Methods used: join_map

Both Array and Hash support with_index when you need position-aware processing:

# BEFORE
users = {alice: "Alice", bob: "Bob", charlie: "Charlie"}
users.filter_map.with_index { |(k, v), i| "#{i + 1}. #{v}" }.join(", ")
# => "1. Alice, 2. Bob, 3. Charlie"
# AFTER
users.join_map(", ", with_index: true) { |(k, v), i| "#{i + 1}. #{v}" }
# => "1. Alice, 2. Bob, 3. Charlie"

Methods used: join_map

Group by a nested key path directly:

# BEFORE
users = [
  {name: "Alice", department: {name: "Engineering"}},
  {name: "Bob", department: {name: "Sales"}},
  {name: "Charlie", department: {name: "Engineering"}}
]
users.group_by { |user| user[:department][:name] }
# => {"Engineering"=>[{name: "Alice",...}, {name: "Charlie",...}], "Sales"=>[{name: "Bob",...}]}
# AFTER
users.group_by_key(:department, :name)
# => {"Engineering"=>[{name: "Alice",...}, {name: "Charlie",...}], "Sales"=>[{name: "Bob",...}]}

Methods used: group_by_key

Build 'or'-joined lists:

# BEFORE
options = ["red", "blue", "green"]
# The default to_sentence uses "and"
options.to_sentence  # => "red, blue, and green"

# Need "or" instead? Time for string surgery
if options.size <= 2
  options.to_sentence(words_connector: " or ")
else
  # Replace the last "and" with "or" - careful with i18n!
  options.to_sentence.sub(/,?\s+and\s+/, ", or ")
end
# => "red, blue, or green"
# AFTER
["red", "blue", "green"].to_or_sentence  # => "red, blue, or green"

Methods used: to_or_sentence

Extensions: join_map, key_map, dig_map, to_or_sentence, group_by_key

Here's just that section with the fixes:


Hash Convenience

Transform values with access to their keys:

# BEFORE
users = {alice: {name: "Alice"}, bob: {name: "Bob"}}
result = {}
users.each do |key, value|
  result[key] = "User #{key}: #{value[:name]}"
end
# => {alice: "User alice: Alice", bob: "User bob: Bob"}
# AFTER
users.transform_values(with_key: true) { |v, k| "User #{k}: #{v[:name]}" }
# => {alice: "User alice: Alice", bob: "User bob: Bob"}

Methods used: transform_values(with_key: true)

Find values based on conditions:

# BEFORE
users = {
  alice: {name: "Alice", role: "admin"},
  bob: {name: "Bob", role: "user"},
  charlie: {name: "Charlie", role: "admin"}
}
admins = users.select { |_k, v| v[:role] == "admin" }.values
# => [{name: "Alice", role: "admin"}, {name: "Charlie", role: "admin"}]
# AFTER
users.select_values { |_k, v| v[:role] == "admin" }
# => [{name: "Alice", role: "admin"}, {name: "Charlie", role: "admin"}]

Methods used: select_values

Just want the first match?

# BEFORE
users.find { |_k, v| v[:role] == "admin" }&.last
# => {name: "Alice", role: "admin"}
# AFTER
users.find_value { |_k, v| v[:role] == "admin" }
# => {name: "Alice", role: "admin"}

Methods used: find_value

Rename keys while preserving order:

# BEFORE
config = {api_key: "secret", timeout: 30}
new_config = config.each_with_object({}) do |(key, value), hash|
  new_key =
    case key
    when :api_key then :key
    when :timeout then :request_timeout
    else key
    end
  hash[new_key] = value
end
# => {key: "secret", request_timeout: 30}
# AFTER
config = {api_key: "secret", timeout: 30}
config.rename_keys(api_key: :key, timeout: :request_timeout)
# => {key: "secret", request_timeout: 30}

Methods used: rename_keys

Merge while dropping nils:

# BEFORE
params = {sort: "created_at"}
search_params = {filter: "active", search: nil}.compact
params.merge(search_params)
# => {sort: "created_at", filter: "active"}
# AFTER
search_params = {filter: "active", search: nil}
params.compact_merge(search_params)
# => {sort: "created_at", filter: "active"}

Methods used: compact_merge

Merge while dropping nils and blank values (requires ActiveSupport):

# BEFORE
params = {sort: "created_at"}
search_params = {filter: "active", search: nil, query: ""}.reject { |_k, v| v.blank? }
params.merge(search_params)
# => {sort: "created_at", filter: "active"}
# AFTER
search_params = {filter: "active", search: nil, query: ""}
params.compact_blank_merge(search_params)
# => {sort: "created_at", filter: "active"}

Methods used: compact_blank_merge

Extensions: transform_values(with_key: true), find_value, select_values, rename_key, rename_keys, merge_if, merge_if!, merge_if_values, merge_if_values!, compact_merge, compact_merge!, compact_blank_merge, compact_blank_merge!

Array Cleaning

Clean up array boundaries while preserving internal structure:

# BEFORE
data = [nil, nil, 1, nil, 2, nil, nil]
data.drop_while(&:nil?).reverse.drop_while(&:nil?).reverse
# => [1, nil, 2]
# AFTER
[nil, nil, 1, nil, 2, nil, nil].trim_nils  # => [1, nil, 2]

Methods used: trim_nils

With ActiveSupport, remove blank values from the edges too:

# BEFORE
data = [nil, "", 1, "", 2, nil, ""]
data.drop_while(&:blank?).reverse.drop_while(&:blank?).reverse
# => [1, "", 2]
# AFTER
[nil, "", 1, "", 2, nil, ""].trim_blanks  # => [1, "", 2]

Methods used: trim_blanks

Extensions: trim_nils, compact_prefix, compact_suffix, trim_blanks (with ActiveSupport)

String Formatting

Format values consistently without a helper method:

# BEFORE
def format_value(value)
  case value
  when String
    "\"#{value}\""
  when Symbol
    "\"#{value}\""
  when Numeric
    "\"#{value}\""
  when NilClass
    "\"nil\""
  when Array, Hash
    "\"#{value.inspect}\""
  else
    "\"#{value}\""
  end
end

selection = nil
message = "You selected #{format_value(selection)}"
# AFTER
"hello".in_quotes      # => "\"hello\""
42.in_quotes           # => "\"42\""
nil.in_quotes          # => "\"nil\""
:symbol.in_quotes      # => "\"symbol\""
[1, 2].in_quotes       # => "\"[1, 2]\""
Time.now.in_quotes     # => "\"2025-05-04 12:34:56 +0000\""

message = "You selected #{selection.in_quotes}"

Methods used: in_quotes, with_quotes

Convert strings to camelCase:

# BEFORE
name = "user_profile_settings"
pascal_case = name.gsub(/[-_\s]+([a-z])/i) { $1.upcase }.gsub(/[-_\s]/, '')
pascal_case[0].upcase!
pascal_case
# => "UserProfileSettings"

camel_case = name.gsub(/[-_\s]+([a-z])/i) { $1.upcase }.gsub(/[-_\s]/, '')
camel_case[0].downcase!
camel_case
# => "userProfileSettings"
# AFTER
"user_profile_settings".to_camelcase       # => "UserProfileSettings"
"user_profile_settings".to_camelcase(:lower)  # => "userProfileSettings"

# Handles mixed input consistently
"please-WAIT while_loading...".to_camelcase  # => "PleaseWaitWhileLoading"

Methods used: to_camelcase

Extensions: in_quotes, with_quotes (alias), to_camelcase

Boolean Methods

Define predicate methods from any attribute:

# BEFORE
class User
  attr_accessor :admin

  def admin?
    !!@admin
  end
end

user = User.new
user.admin = true
user.admin?  # => true
# AFTER
class User
  attr_accessor :admin
  attr_predicate :admin
end

user = User.new
user.admin = true
user.admin?  # => true

Methods used: attr_predicate

Map predicates to differently-named sources with from::

# BEFORE
class Task
  attr_accessor :started_at, :stopped_at

  def started?
    !@started_at.nil?
  end

  def finished?
    !@stopped_at.nil?
  end
end
# AFTER
class Task
  attr_accessor :started_at, :stopped_at
  attr_predicate :started, from: :@started_at
  attr_predicate :finished, from: :@stopped_at
end

task = Task.new
task.started?      # => false
task.started_at = Time.now
task.started?      # => true

Methods used: attr_predicate

Works with Data objects too:

# BEFORE
Person = Data.define(:active) do
  def active?
    !!active
  end
end

# AFTER
Person = Data.define(:active)
Person.attr_predicate(:active)

person = Person.new(active: false)
person.active? # => false

Methods used: attr_predicate

Extensions: attr_predicate

Value Transformation

An alias for then/yield_self that reads more naturally in transformation chains:

# BEFORE
result = value.then { |v| transform_it(v) }
# AFTER
result = value.morph { |v| transform_it(v) }

Methods used: morph

Extensions: morph (alias for then/yield_self)

Full Documentation

For complete method listings, examples, and detailed usage, see the API Documentation.

Requirements

  • Ruby 3.2 or higher

Contributing

Bug reports and pull requests are welcome! This project is intended to be a safe, welcoming space for collaboration.

License

MIT License