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
 Dependencies

Runtime

~> 2.10
~> 0.6
 Project Readme

EverythingRB

Gem Version Ruby Version Tests

Super handy extensions to Ruby core classes that make your code more expressive, readable, and fun to write. Stop writing boilerplate and start writing code that actually matters!

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

Because life's too short to write all that boilerplate!

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 (or you're a minimalist at heart):

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 (to_deep_h, 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 (to_h, to_ostruct, to_camelcase, etc.)
  • struct: Struct extensions (to_deep_h, 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

Stop writing complicated parsers and nested transformations:

# 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"

With EverythingRB, it's one elegant step:

# AFTER
'{"user":{"name":"Alice","roles":["admin"]}}'.to_ostruct.user.name  # => "Alice"

Methods used: to_ostruct

Convert between data structures with ease:

# 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

Deep conversion to plain hashes is just as easy:

# BEFORE
data = OpenStruct.new(user: Data.define(:name).new(name: "Bob"))
result = {
  user: {
    name: data.user.name
  }
}
# AFTER
data = OpenStruct.new(user: Data.define(:name).new(name: "Bob"))
data.to_deep_h  # => {user: {name: "Bob"}}

Methods used: to_deep_h

Extensions: to_struct, to_ostruct, to_istruct, to_h, to_deep_h

Collection Processing

Extract and transform data with elegant, expressive code:

# BEFORE
users = [{ name: "Alice", roles: ["admin"] }, { name: "Bob", roles: ["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 operations 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

Group elements with natural syntax:

# 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

Create natural language 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

Hash Convenience

Work with hashes more intuitively:

# BEFORE
stats = {}
stats[:server] ||= {}
stats[:server][:region] ||= {}
stats[:server][:region][:us_east] ||= {}
stats[:server][:region][:us_east][:errors] ||= []
stats[:server][:region][:us_east][:errors] << "Connection timeout"
# AFTER
stats = Hash.new_nested_hash(depth: 3)
(stats[:server][:region][:us_east][:errors] ||= []) << "Connection timeout"
# No need to initialize each level first!

Methods used: new_nested_hash

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.values_where { |_k, v| v[:role] == "admin" }
# => [{name: "Alice", role: "admin"}, {name: "Charlie", role: "admin"}]

Methods used: values_where

Just want the first match? Even simpler:

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

Methods used: value_where

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
      :key
    when :timeout
      :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

Filter hash by values:

# BEFORE
result = {a: 1, b: nil, c: 2}.select { |_k, v| v.is_a?(Integer) && v > 1 }
# => {c: 2}
# AFTER
{a: 1, b: nil, c: 2}.select_values { |v| v.is_a?(Integer) && v > 1 }
# => {c: 2}

Methods used: select_values

Conditionally merge hashes with clear intent:

# BEFORE
user_params = {name: "Alice", role: "user"}
filtered = {verified: true, admin: true}.select { |k, v| v == true && k == :verified }
user_params.merge(filtered)
# => {name: "Alice", role: "user", verified: true}
# AFTER
user_params = {name: "Alice", role: "user"}
user_params.merge_if(verified: true, admin: true) { |k, v| v == true && k == :verified }
# => {name: "Alice", role: "user", verified: true}

Methods used: merge_if

The nil-filtering pattern we've all written dozens of times:

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

Methods used: merge_compact

Extensions: new_nested_hash, transform_values(with_key: true), value_where, values_where, rename_key, rename_keys, select_values, reject_values, merge_if, merge_if!, merge_if_values, merge_if_values!, merge_compact, merge_compact!

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 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 strings and other values consistently:

# 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 with ease:

# 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 all kinds of input consistently
"please-WAIT while_loading...".to_camelcase  # => "PleaseWaitWhileLoading"

Methods used: to_camelcase

Extensions: in_quotes, with_quotes (alias), to_camelcase

Boolean Methods

Create predicate methods with minimal code:

# 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

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

Chain transformations with a more descriptive syntax:

# 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

Looking for a Software Engineer?

I'm currently looking for opportunities where I can tackle meaningful problems and help build reliable software while mentoring the next generation of developers. If you're looking for a senior engineer with full-stack Rails expertise and a passion for clean, maintainable code, let's talk!

bryan@itsthedevman.com