Unashamedly monkey patching JavaScript to be more like Ruby.
Ruby (and Rails) has loads of really nice methods, now you can use them in JS as well!
Ruby Monkey helps to make your JavaScript code more code elegant, fun and productive!
Now you can write JS code like this:
[1,2,3].last // 3 [1,2,3].count // 3 (21).ordinalize // "21st" "RubyMonkey".downcase.reverse // "yeknomybur" [1,2,3].sum.squared // 36 ["A","A","C","A","B","A","B"].tally // {"A": 4, "C": 1, "B": 2} (1).day.ago // yesterday
Usage
Then just add either require "rubymonkey" or import "rubymonkey" to the top of any JS file and suddenly coding in JS becomes a lot more fun and productive!
In general, if you know the Ruby methods you should be able to use them in almost the same way, with a few slight changes:
- Blocks change to arrow functions
- JavaScript does not support appending symbols to the end of function names, so Boolean methods can't end in a
?, so these have 2 versions, one without the?at the end and another withisprepended to the beginning.
So for example, this Ruby:
[1,2,3].count{ |n| n.odd? }
Would be written in Ruby Monkey as either of the following:
[1,2,3].count( n => n.isOdd )
[1,2,3].count( n => n.odd )
Template to Func
Ruby has this really nice syntax to make calling methods on objects easier, so instead of [1,2,3].map { |n| n.next } you can just write [1,2,3].map(&:next)
JavaScript doesn't let you use & and doesn't have symbol literals, but you can use $ and it does have template literals and tag functions, so in Ruby Doo, you can do the same thing like this:
Number Methods
number.even & number.isEven
Checks if the number is even.
(4).even; // true (5).even; // false
number.odd & number.isOdd
Checks if the number is odd.
(3).odd; // true (10).odd; // false
number.to_s
Converts the number to a string.
number.next
Returns the next integer.
number.round
Rounds the number to the nearest integer.
(4.7).round; // 5 (4.2).round; // 4
number.ceil
Returns the smallest integer greater than or equal to the number.
number.floor
Returns the largest integer less than or equal to the number.
number.digits
Returns an array of the digits of the number.
(123).digits; // [1, 2, 3]
number.factors
Returns an array of all factors of the number.
(12).factors; // [1, 2, 3, 4, 6, 12]
number.prime
Checks if the number is prime.
(7).prime; // true (9).prime; // false
number.integer and number.isInteger
Checks if the number is an integer.
(10.5).integer; // false (10).integer; // true
number.positive & number.isPositive
Checks if the number is positive.
(5).positive; // true (-3).positive; // false
number.negative & number.isNegative
Checks if the number is negative.
(-10).negative; // true (5).negative; // false
number.zero & number.isZero
Checks if the number is zero.
(0).zero; // true (1).zero; // false
number.squared
Returns the square of the number.
number.cubed
Returns the cube of the number.
number.ordinal
Returns the ordinal suffix of the number.
(1).ordinal; // "st" (2).ordinal; // "nd" (3).ordinal; // "rd" (4).ordinal; // "th" (11).ordinal; // "th"
number.ordinalize
Returns the number as an ordinal string.
(1).ordinalize; // "1st" (2).ordinalize; // "2nd" (3).ordinalize; // "3rd" (4).ordinalize; // "4th" (11).ordinalize; // "11th"
number.upto(n, func?)
Iterates from the current number up to n, calling func if provided.
(3).upto(6, console.log); // Logs: 3, 4, 5, 6 (3).upto(6); // Returns: [3, 4, 5, 6]
number.times(func)
Executes func the given number of times, passing the index as an optional argument.
(3).times(_ => console.log("Ruby!")); // Logs: Ruby!Ruby!Ruby! (3).times(i => console.log(`Iteration: ${i}`)); // Logs: Iteration: 0, Iteration: 1, Iteration: 2
number.mod(n)
Returns the remainder of the number divided by n.
number.divmod(n)
Returns an array containing the quotient and remainder of division by n.
(10).divmod(3); // [3, 1]
number.gcd(n)
Computes the greatest common divisor (GCD) of the number and n.
number.lcm(n)
Computes the least common multiple (LCM) of the number and n.
number.between(a, b) & number.isBetween(a,b)
Checks if the number is between a and b (inclusive).
(5).between(1, 10); // true (15).between(1, 10); // false
number.eql(n)
Checks if the number is strictly equal to n.
(5).eql(5); // true (5).eql(3); // false
number.multiple_of(n) & number.divisble_by(n)
Checks if the number is a multiple of n.
(10).multiple_of(5); // true (10).multiple_of(3); // false
(10).divisible_by(5); // true (10).divisible_by(3); // false
number.divisor_of(n) & number.factor_of(n)
Checks if the number is a divisor (factor) of n.
(5).divisor_of(10); // true (3).divisor_of(10); // false
(5).factor_of(10); // true (3).factor_of(10); // false
number.pred
Returns the predecessor (this - 1).
(10).pred; // 9 (1).pred; // 0
number.downto(n, func?)
Iterates from the current number down to n, calling func if provided.
(6).downto(3, console.log); // Logs: 6, 5, 4, 3 (6).downto(3); // Returns: [6, 5, 4, 3]
number.div(n)
Returns the integer division result (floor of division).
(10).div(3); // 3 (7).div(2); // 3
number.modulo(n)
Returns the modulo of the number divided by n. Alias for mod.
(10).modulo(3); // 1 (-10).modulo(3); // 2
number.hcf(n)
Highest common factor. Alias for gcd.
number.nonzero & number.isNonzero
isNonzero returns true if the number is not zero. nonzero returns the number itself if non-zero, otherwise undefined.
(5).isNonzero; // true (0).isNonzero; // false (5).nonzero; // 5 (0).nonzero; // undefined
number.abs
Returns the absolute value of the number.
(-5).abs; // 5 (5).abs; // 5
number.add(n)
Returns the sum of the number and n.
number.subtract(n) & number.minus(n)
Returns the difference of the number and n.
(10).subtract(3); // 7 (10).minus(3); // 7
number.multiply(n)
Returns the product of the number and n.
number.divide(n)
Returns the quotient of the number divided by n.
(10).divide(2); // 5 (7).divide(2); // 3.5
String Methods
string.reverse
Returns the string reversed.
"hello".reverse; // "olleh"
string.size
Returns the length of the string.
string.to_i
Converts the string to an integer, returning 0 if conversion fails.
"123".to_i; // 123 "abc".to_i; // 0
string.to_f
Converts the string to a float, returning 0 if conversion fails.
"12.34".to_f; // 12.34 "abc".to_f; // 0
string.downcase
Returns the string in lowercase.
"Hello".downcase; // "hello"
string.upcase
Returns the string in uppercase.
"hello".upcase; // "HELLO"
string.upcase_first
Capitalizes only the first character of the string.
"hello world".upcase_first; // "Hello world"
string.downcase_first
Lowercases only the first character of the string.
"Hello World".downcase_first; // "hello World"
string.squish
Removes leading, trailing, and multiple consecutive spaces.
" Hello world ".squish; // "Hello world"
string.blank
Checks if the string is empty or contains only whitespace.
" ".blank; // true "hello".blank; // false
string.empty
Checks if the string is completely empty (not even whitespace).
"".empty; // true " ".empty; // false
string.humanize
Removes _id from the end (if present) and replaces underscores with spaces, capitalizing the first letter.
"user_name".humanize; // "User name" "post_id".humanize; // "Post"
string.titleize (Alias: titlecase)
Capitalizes each word in the string.
"hello world".titleize; // "Hello World"
string.parameterize
Converts the string into a URL-friendly format (lowercase, hyphenated).
"Hello, World!".parameterize; // "hello-world"
string.chars
Returns an array of individual characters.
"hello".chars; // ["h", "e", "l", "l", "o"]
string.count(substring)
Returns the number of times substring appears in the string.
"hello world".count("l"); // 3
string.starts_with(substring)
Checks if the string starts with the given substring.
"hello world".starts_with("hello"); // true
string.ends_with(substring)
Checks if the string ends with the given substring.
"hello world".ends_with("world"); // true
string.first
Returns the first character of the string.
string.first_(n)
Returns the first n characters of the string.
"hello".first(2); // "he" "hello".first(5); // "hello"
string.last
Returns the last character of the string.
string.last_(n)
Returns the last n characters of the string. If n is omitted, returns the last character.
"hello".last_(2); // "lo" "hello".last_(5); // "hello"
string.eql(str)
Checks if the string is strictly equal to str.
("hello").eql("hello"); // true ("Hello!").eql("hello"); // false
string.to_s
Returns the string itself.
string.isBlank & string.blank
isBlank and blank are aliases. Checks if the string is empty or contains only whitespace.
" ".isBlank; // true "hello".blank; // false
string.isEmpty & string.empty
isEmpty and empty are aliases. Checks if the string is completely empty (not even whitespace).
"".isEmpty; // true " ".empty; // false
Array Methods
Property Methods
array.first
Returns the first element of the array.
[1, 2, 3].first; // 1 [].first; // undefined
array.second, array.third, array.fourth, array.fifth
Returns the second, third, fourth, or fifth element of the array.
[10, 20, 30].second; // 20 [10].third; // undefined
array.forty_two
Returns the 42nd element (index 41) of the array.
Array(50).fill(0).map((_, i) => i + 1).forty_two; // 42
### array.third_to_last, array.second_to_last, array.last
Returns the third-to-last, second-to-last, or last element of the array.
[1, 2, 3, 4].second_to_last; // 3 [].last; // undefined
array.empty
Returns true if the array is empty, false otherwise.
[].empty; // true [1].empty; // false
array.clear
Clears all elements from the array.
let arr = [1, 2, 3]; arr.clear; console.log(arr); // []
array.size
Returns the length of the array.
array.min, array.max
Returns the smallest or largest number in the array.
[5, 3, 9].min; // 3 [5, 3, 9].max; // 9 [].min; // undefined
array.uniq
Returns a new array with duplicate elements removed.
[1, 2, 2, 3].uniq; // [1, 2, 3]
array.to_sentence
Converts the array into a human-readable sentence.
["a", "b", "c"].to_sentence; // "a, b and c"
array.compact
Returns a new array with null and undefined values removed.
[1, null, 2, undefined, 3].compact; // [1, 2, 3]
array.to_param
Converts the array into a string joined by /.
["users", 42, "edit"].to_param; // "users/42/edit"
Functional Methods
array.any(func?)
Returns true if at least one element satisfies func, or if the array is not empty.
[1, 2, 3].any(x => x > 2); // true [].any(); // false
array.one(func?)
Returns true if exactly one element satisfies func.
[1, 2, 3].one(x => x > 2); // true [1, 2, 3, 4].one(x => x > 2); // false
array.sum
Returns the sum of all elements, or applies func before summing.
array.reject(func)
Returns a new array without elements matching func.
[1, 2, 3, 4].reject(x => x % 2 === 0); // [1, 3]
array.partition(func)
Splits the array into two: one matching func, one not.
[1, 2, 3, 4].partition(x => x % 2 === 0); // [[2, 4], [1, 3]]
array.count(func?)
Returns the number of elements satisfying func, or the total length.
[1, 2, 3, 4].count(x => x % 2 === 0); // 2 [1, 2, 3].count(); // 3
array.pluck(prop)
Extracts values of the given property from an array of objects.
[{id: 1}, {id: 2}].pluck("id"); // [1, 2]
array.from(n)
Returns a new array starting from index n.
[10, 20, 30, 40].from(2); // [30, 40] ### `array.product(arr)` Returns all possible combinations of elements from both arrays. ```javascript [1, 2, 3].product([4,5]); // [[1,4], [1,5], [2,4],[2,5], [3,4], [3,5]]
array.combination(n)
Returns all possible combinations of n elements.
[1, 2, 3].combination(2); // [[1,2], [1,3], [2,3]]
array.tally()
Counts occurrences of each unique element.
["a", "b", "a"].tally(); // { a: 2, b: 1 }
array.each_cons(n)
Returns overlapping subarrays of size n.
[1, 2, 3, 4].each_cons(2); // [[1,2], [2,3], [3,4]]
array.rotate(n = 1)
Returns a rotated array by n places.
[1, 2, 3].rotate(); // [2, 3, 1]
array.sample(n = 1)
Returns n random elements.
[1, 2, 3, 4].sample(2); // Random subset
array.zip(arr)
Zips two arrays together.
[1, 2, 3].zip(["a", "b", "c"]); // [[1, "a"], [2, "b"], [3, "c"]]
array.union(...arrs)
Returns a merged array without duplicates.
[1, 2].union([2, 3], [3, 4]); // [1, 2, 3, 4]
array.intersection(...arrs)
Returns elements common to all arrays.
[1, 2, 3].intersection([2, 3, 4]); // [2, 3]
array.minmax
Returns a tuple of [min, max] values in the array.
[5, 1, 9, 3].minmax; // [1, 9]
array.multiply
Returns the product of all elements in the array.
[1, 2, 3, 4].multiply; // 24
array.shuffle
Returns a shuffled copy of the array.
[1, 2, 3].shuffle; // e.g., [3, 1, 2]
array.transpose
Transposes a 2D array (swaps rows and columns).
[[1, 2], [3, 4]].transpose; // [[1, 3], [2, 4]]
array.sort
Returns a sorted copy of the array (numeric sort).
[3, 1, 2].sort; // [1, 2, 3]
array.flatten
Flattens nested arrays by one level.
[[1, 2], [3, [4, 5]]].flatten; // [1, 2, 3, [4, 5]]
array.drop(n)
Alias for slice(n). Returns elements after dropping the first n.
[1, 2, 3, 4].drop(2); // [3, 4]
array.sort_by(func, order?)
Sorts the array by the value returned from func. Pass 'desc' as the second argument to sort in descending order.
// Sort strings by length ["apple", "fig", "banana"].sort_by(s => s.length); // ["fig", "apple", "banana"] // Sort strings by length descending ["apple", "fig", "banana"].sort_by(s => s.length, 'desc'); // ["banana", "apple", "fig"] // Sort strings by last character ["apple", "banana", "cherry"].sort_by(s => s.last); // ["banana", "apple", "cherry"] // Sort objects by property const users = [ { name: "Charlie", age: 35 }, { name: "Alice", age: 30 }, { name: "Bob", age: 25 } ]; users.sort_by(u => u.age); // [{ name: "Bob", age: 25 }, { name: "Alice", age: 30 }, { name: "Charlie", age: 35 }] // Sort by computed value descending const products = [ { name: "Widget", price: 25, quantity: 10 }, { name: "Gadget", price: 50, quantity: 3 }, { name: "Gizmo", price: 15, quantity: 20 } ]; products.sort_by(p => p.price * p.quantity, 'desc'); // [Gizmo (300), Widget (250), Gadget (150)]
array.dig(...indices)
Safely retrieves a nested value using a sequence of keys or indexes.
Returns undefined if any step in the chain is missing.
[ { a: { b: 10 } } ].dig(0, "a", "b"); // 10 [ { a: {} } ].dig(0, "a", "c"); // undefined
array.eql(arr)
Deep-compares two arrays for equality.
Uses an element’s custom .eql method if present, otherwise strict equality (===).
[1, 2, 3].eql([1, 2, 3]); // true [1, 2, 3].eql([1, 2, "3"]); // false
array.filter_map(func)
Maps the array with func and removes null/undefined values (.compact).
[1, 2, 3].filter_map(n => (n % 2 === 0 ? n * 2 : null)); // [4]
array.each_with_object(obj, func)
Iterates over the array and yields each element with the provided object.
const acc = []; [1, 2, 3].each_with_object(acc, (n, arr) => arr.push(n * 2)); acc; // [2, 4, 6]
array.first_(n)
Returns the first n elements of the array.
If n < 1, returns an empty array.
[1, 2, 3].first_(2); // [1,2] [1, 2, 3].first_(3); // [1, 2, 3]
array.last_(n)
Returns the last n elements of the array.
If n < 1, returns an empty array.
[1, 2, 3].last_(2); // [2, 3] [1, 2, 3].last_(3); // [1, 2, 3]
Aliases
collect → map
all → every
select → filter
each → forEach
detect → find
inject → reduce
delete_if → reject
flat_map → flatMap
drop → slice
empty → isEmpty
Object Methods
object.empty
Checks if an object has no keys.
({}).empty; // true ({ a: 1 }).empty; // false
object.size
Returns the number of keys in the object.
({ a: 1, b: 2 }).size; // 2
`
object.values
Returns an array of the object's values.
({ a: 1, b: 2 }).values; // [1, 2]
object.keys
Returns an array of the object's keys.
({ a: 1, b: 2 }).keys; // ["a", "b"]
object.entries
Returns an array of [key, value] pairs.
({ a: 1, b: 2 }).entries; // [["a", 1], ["b", 2]]
object.clear
Removes all properties from an object (mutates it).
const obj = { a: 1, b: 2 }; obj.clear console.log(obj); // {}
object.compact
Returns a new object with null and undefined values removed.
({ a: 1, b: null, c: undefined }).compact; // { a: 1 }
Methods
object.select(func)
Returns a new object with key-value pairs where func(key, value) is true.
({ a: 1, b: 2 }).select(([k, v]) => v > 1); // { b: 2 }
object.keep_if(func)
Alias for select.
object.reject(func)
Returns a new object with key-value pairs where func(key, value) is false.
({ a: 1, b: 2 }).reject(([k, v]) => v > 1); // { a: 1 }
object.delete_if(func)
Alias for reject.
object.has_key(key)
Checks if an object has a given key.
({ a: 1 }).has_key("a"); // true ({ a: 1 }).has_key("b"); // false
object.has_value(value)
Checks if an object contains a given value.
({ a: 1, b: 2 }).has_value(2); // true ({ a: 1 }).has_value(3); // false
object.key(value)
Returns the first key where the value matches, or undefined if not found.
({ a: 1, b: 2 }).key(2); // "b" ({ a: 1 }).key(3); // undefined
object.any([func])
If func is provided, checks if any key-value pair matches func(key, value). If func is omitted, returns true if the object is not empty.
({ a: 1, b: 2 }).any(); // true ({}).any(); // false ({ a: 1, b: 2 }).any(([k, v]) => v > 1); // true
object.except(...keys)
Returns a new object excluding specified keys.
({ a: 1, b: 2, c: 3 }).except("b", "c"); // { a: 1 }
object.transform_keys(func)
Returns a new object with keys transformed by func.
({ a: 1, b: 2 }).transform_keys(k => k.toUpperCase()); // { A: 1, B: 2 }
object.transform_values(func)
Returns a new object with values transformed by func.
({ a: 1, b: 2 }).transform_values(v => v * 10); // { a: 10, b: 20 }
object.dig(...keys)
Safely retrieves a nested value using a sequence of keys.
({ a: { b: { c: 42 } } }).dig("a", "b", "c"); // 42 ({ a: {} }).dig("a", "x"); // undefined
object.each(func)
Iterates over each [key, value] entry, calling func.
({ a: 1, b: 2 }).each(([k, v]) => console.log(k, v)); // Logs: "a" 1, "b" 2
object.each_key(func)
Iterates over each key, calling func.
({ a: 1, b: 2 }).each_key(k => console.log(k)); // Logs: "a", "b"
object.each_value(func)
Iterates over each value, calling func.
({ a: 1, b: 2 }).each_value(v => console.log(v)); // Logs: 1, 2
object.fetch(key)
Returns the value for the given key.
({ a: 1, b: 2 }).fetch("a"); // 1
object.fetch_values(...keys)
Returns an array of values for the given keys.
({ a: 1, b: 2, c: 3 }).fetch_values("a", "c"); // [1, 3]
object.eql(obj)
Deep equality check between objects.
({ a: 1, b: 2 }).eql({ a: 1, b: 2 }); // true ({ a: 1 }).eql({ a: "1" }); // false
object.to_s
Returns a string representation of the object.
({ a: 1, b: 2 }).to_s; // "{ a: 1, b: 2 }"
Aliases
each_pair → each
filter → select
includes → has_key
Date Utilities Library
A lightweight set of extensions for working with dates, durations, and date ranges based on the Rails helper methods. Includes:
- DateRange — iterate or inspect ranges of dates
- Duration — express time spans like (3).days or (2).months
- Prototype helpers on Date for navigation, ranges, and comparisons
- Convenience accessors like Date.today, Date.current, date.isToday
- Iterable ranges and unit-based iteration (each_day, each_month, etc.)
DateRange
range.includes(date)
Returns true if the date falls inside the range (inclusive).
range.includes(Date.today); // true
range.each(callback, step = 1)
Iterates day-by-day through the range, calling the callback for each date.
step controls the day increment.
range.each(d => console.log(d)); range.each(d => console.log(d), 2); // Every 2 days
range.each_day(callback, step = 1)
Alias for .each.
Iterates through days in the range.
range.each_day(d => console.log(d));
range.each_week(callback, step = 1)
Iterates through the range in weekly steps.
range.each_week(d => console.log(d)); // Every 7 days range.each_week(d => console.log(d), 2); // Every 14 days
range.each_month(callback, step = 1)
Iterates month-to-month, preserving the original day when possible (and adjusting for month length differences automatically).
range.each_month(d => console.log(d));
range.each_quarter(callback, step = 1)
Iterates in increments of 3 months.
range.each_quarter(d => console.log(d));
range.each_year(callback, step = 1)
Iterates year-to-year through the range.
range.each_year(d => console.log(d));
Duration
new Duration({ years, months, weeks, days, hours, minutes, seconds })
Creates a duration object.
const d = new Duration({ days: 3, hours: 5 });
duration.ago
Shifts backward from Date.current.
(3).days.ago; // 3 days before now
duration.since(date)
Moves forward from the given date.
(2).weeks.since(Date.today);
duration.before(date)
Moves backward from the given date.
(1).month.before(Date.today);
duration.after(date)
Alias for duration.since.
(6).hours.after(Date.current);
duration.until(date)
Returns the date minus the duration.
(10).days.until(Date.today);
duration.from_now
Shifts forward from Date.current.
duration.advance_from(date)
Applies all duration components to the given date.
new Duration({ days: 1, months: 1 }).advance_from(Date.today);
Number Duration Extensions
All numbers gain convenience getters for generating a Duration:
second, seconds minute, minutes hour, hours day, days week, weeks month, months year, years
Example usage:
(3).days; // Duration { days: 3 } (1).year; // Duration { years: 1 } (2).weeks.from_now;
Date Range Helpers
date.all_day, date.all_week, date.all_month, date.all_quarter, date.all_year
Returns a DateRange covering the full period.
Date.today.all_week.each(d => console.log(d));
date.at_beginning_of_day, date.at_beginning_of_month, ... date.at_end_of_day, date.at_end_of_month, ...
Convenient accessors for the start or end of a period.
Date.today.at_beginning_of_month; Date.today.at_end_of_year;
Named Day Predicates
date.isYesterday, date.isToday, date.isTomorrow
Checks if the date falls within the corresponding day.
Date.today.isToday; // true
Static Date Helpers
Returns the current UTC date with full time precision.
Returns today's UTC date at midnight.
Date.today; Date.yesterday Date.tomorrow
UTC midnight versions of today, yesterday and tomorrow.
Date Prototype Methods
date.yesterday
Returns the previous day at the beginning of day.
date.tomorrow
Returns the next day at the beginning of day.
date.days_in_month
Returns the number of days in the current month.
new Date(Date.UTC(2024, 1, 1)).days_in_month; // 29
date.advance(duration)
Returns a new date advanced by the given duration.
Date.today.advance({ weeks: 1 });
date.change({ year, month, day, hour, minute, second, ms })
Returns a new date with selected UTC fields replaced.
Date.current.change({ hour: 0, minute: 0 });