EverythingRB
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
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!