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 // yesterdaynpm install rubymonkeyThen 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 )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:
[1,2,3].map($`next`)Checks if the number is even.
(4).even; // true
(5).even; // falseChecks if the number is odd.
(3).odd; // true
(10).odd; // falseConverts the number to a string.
(42).to_s; // "42"Returns the next integer.
(10).next; // 11Rounds the number to the nearest integer.
(4.7).round; // 5
(4.2).round; // 4Returns the smallest integer greater than or equal to the number.
(4.2).ceil; // 5Returns the largest integer less than or equal to the number.
(4.9).floor; // 4Returns an array of the digits of the number.
(123).digits; // [1, 2, 3]Returns an array of all factors of the number.
(12).factors; // [1, 2, 3, 4, 6, 12]Checks if the number is prime.
(7).prime; // true
(9).prime; // falseChecks if the number is an integer.
(10.5).integer; // false
(10).integer; // trueChecks if the number is positive.
(5).positive; // true
(-3).positive; // falseChecks if the number is negative.
(-10).negative; // true
(5).negative; // falseChecks if the number is zero.
(0).zero; // true
(1).zero; // falseReturns the square of the number.
(4).squared; // 16Returns the cube of the number.
(3).cubed; // 27Returns the ordinal suffix of the number.
(1).ordinal; // "st"
(2).ordinal; // "nd"
(3).ordinal; // "rd"
(4).ordinal; // "th"
(11).ordinal; // "th"Returns the number as an ordinal string.
(1).ordinalize; // "1st"
(2).ordinalize; // "2nd"
(3).ordinalize; // "3rd"
(4).ordinalize; // "4th"
(11).ordinalize; // "11th"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]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: 2Returns the remainder of the number divided by n.
(10).mod(3); // 1Returns an array containing the quotient and remainder of division by n.
(10).divmod(3); // [3, 1]Computes the greatest common divisor (GCD) of the number and n.
(48).gcd(18); // 6Computes the least common multiple (LCM) of the number and n.
(4).lcm(6); // 12Checks if the number is between a and b (inclusive).
(5).between(1, 10); // true
(15).between(1, 10); // falseChecks if the number is strictly equal to n.
(5).eql(5); // true
(5).eql(3); // falseChecks 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); // falseChecks 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); // falseReturns the string reversed.
"hello".reverse; // "olleh"Returns the length of the string.
"hello".size; // 5Converts the string to an integer, returning 0 if conversion fails.
"123".to_i; // 123
"abc".to_i; // 0Converts the string to a float, returning 0 if conversion fails.
"12.34".to_f; // 12.34
"abc".to_f; // 0Returns the string in lowercase.
"Hello".downcase; // "hello"Returns the string in uppercase.
"hello".upcase; // "HELLO"Capitalizes only the first character of the string.
"hello world".upcase_first; // "Hello world"Lowercases only the first character of the string.
"Hello World".downcase_first; // "hello World"
Removes leading, trailing, and multiple consecutive spaces.
" Hello world ".squish; // "Hello world"Checks if the string is empty or contains only whitespace.
" ".blank; // true
"hello".blank; // falseChecks if the string is completely empty (not even whitespace).
"".empty; // true
" ".empty; // falseRemoves _id from the end (if present) and replaces underscores with spaces, capitalizing the first letter.
"user_name".humanize; // "User name"
"post_id".humanize; // "Post"Capitalizes each word in the string.
"hello world".titleize; // "Hello World"Converts the string into a URL-friendly format (lowercase, hyphenated).
"Hello, World!".parameterize; // "hello-world"Returns an array of individual characters.
"hello".chars; // ["h", "e", "l", "l", "o"]Returns the number of times substring appears in the string.
"hello world".count("l"); // 3Checks if the string starts with the given substring.
"hello world".starts_with("hello"); // trueChecks if the string ends with the given substring.
"hello world".ends_with("world"); // trueReturns the first character of the string.
"hello".first; // "h"Returns the first n characters of the string.
"hello".first(2); // "he"
"hello".first(5); // "hello"Returns the last character of the string.
"hello".last; // "o"Returns the last n characters of the string. If n is omitted, returns the last character.
"hello".last_(2); // "lo"
"hello".last_(5); // "hello"Checks if the string is strictly equal to str.
("hello").eql("hello"); // true
("Hello!").eql("hello"); // falseReturns the first element of the array.
[1, 2, 3].first; // 1
[].first; // undefinedReturns the second, third, fourth, or fifth element of the array.
[10, 20, 30].second; // 20
[10].third; // undefinedReturns 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; // undefinedReturns true if the array is empty, false otherwise.
[].empty; // true
[1].empty; // falseClears all elements from the array.
let arr = [1, 2, 3];
arr.clear;
console.log(arr); // []Returns the length of the array.
[1, 2, 3].size; // 3Returns the smallest or largest number in the array.
[5, 3, 9].min; // 3
[5, 3, 9].max; // 9
[].min; // undefinedReturns a new array with duplicate elements removed.
[1, 2, 2, 3].uniq; // [1, 2, 3]Converts the array into a human-readable sentence.
["a", "b", "c"].to_sentence; // "a, b and c"Returns a new array with null and undefined values removed.
[1, null, 2, undefined, 3].compact; // [1, 2, 3]Converts the array into a string joined by /.
["users", 42, "edit"].to_param; // "users/42/edit"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(); // falseReturns true if exactly one element satisfies func.
[1, 2, 3].one(x => x > 2); // true
[1, 2, 3, 4].one(x => x > 2); // falseReturns the sum of all elements, or applies func before summing.
[1, 2, 3].sum; // 6Returns a new array without elements matching func.
[1, 2, 3, 4].reject(x => x % 2 === 0); // [1, 3]
Splits the array into two: one matching func, one not.
[1, 2, 3, 4].partition(x => x % 2 === 0); // [[2, 4], [1, 3]]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(); // 3Extracts values of the given property from an array of objects.
[{id: 1}, {id: 2}].pluck("id"); // [1, 2]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]]Returns all possible combinations of n elements.
[1, 2, 3].combination(2); // [[1,2], [1,3], [2,3]]Counts occurrences of each unique element.
["a", "b", "a"].tally(); // { a: 2, b: 1 }Returns overlapping subarrays of size n.
[1, 2, 3, 4].each_cons(2); // [[1,2], [2,3], [3,4]]Returns a rotated array by n places.
[1, 2, 3].rotate(); // [2, 3, 1]Returns n random elements.
[1, 2, 3, 4].sample(2); // Random subsetZips two arrays together.
[1, 2, 3].zip(["a", "b", "c"]); // [[1, "a"], [2, "b"], [3, "c"]]Returns a merged array without duplicates.
[1, 2].union([2, 3], [3, 4]); // [1, 2, 3, 4]
## `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.
```javascript
[ { a: { b: 10 } } ].dig(0, "a", "b"); // 10
[ { a: {} } ].dig(0, "a", "c"); // undefinedDeep-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"]); // falseMaps the array with func and removes null/undefined values (.compact).
[1, 2, 3].filter_map(n => (n % 2 === 0 ? n * 2 : null));
// [4]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]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]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]collect → map
all → every
select → filter
each → forEach
detect → find
inject → reduce
delete_if → reject
Checks if an object has no keys.
({}).empty; // true
({ a: 1 }).empty; // falseReturns the number of keys in the object.
({ a: 1, b: 2 }).size; // 2`
Returns an array of the object's values.
({ a: 1, b: 2 }).values; // [1, 2]Returns an array of the object's keys.
({ a: 1, b: 2 }).keys; // ["a", "b"]Returns an array of [key, value] pairs.
({ a: 1, b: 2 }).entries; // [["a", 1], ["b", 2]]Removes all properties from an object (mutates it).
const obj = { a: 1, b: 2 }; obj.clear console.log(obj); // {}
Returns a new object with null and undefined values removed.
({ a: 1, b: null, c: undefined }).compact; // { a: 1 }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 }Alias for select.
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 }Alias for reject.
Checks if an object has a given key.
({ a: 1 }).has_key("a"); // true
({ a: 1 }).has_key("b"); // falseChecks if an object contains a given value.
({ a: 1, b: 2 }).has_value(2); // true
({ a: 1 }).has_value(3); // falseReturns the first key where the value matches, or undefined if not found.
({ a: 1, b: 2 }).key(2); // "b"
({ a: 1 }).key(3); // undefinedIf 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); // trueReturns a new object excluding specified keys.
({ a: 1, b: 2, c: 3 }).except("b", "c"); // { a: 1 }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.)
Returns true if the date falls inside the range (inclusive).
range.includes(Date.today); // trueIterates 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 daysAlias for .each.
Iterates through days in the range.
range.each_day(d => console.log(d));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 daysIterates month-to-month, preserving the original day when possible (and adjusting for month length differences automatically).
range.each_month(d => console.log(d));Iterates in increments of 3 months.
range.each_quarter(d => console.log(d));Iterates year-to-year through the range.
range.each_year(d => console.log(d));Creates a duration object.
const d = new Duration({ days: 3, hours: 5 });Shifts backward from Date.current.
(3).days.ago; // 3 days before nowMoves forward from the given date.
(2).weeks.since(Date.today);Moves backward from the given date.
(1).month.before(Date.today);Alias for duration.since.
(6).hours.after(Date.current);Returns the date minus the duration.
(10).days.until(Date.today);Shifts forward from Date.current.
(30).minutes.from_now;Applies all duration components to the given date.
new Duration({ days: 1, months: 1 }).advance_from(Date.today);All numbers gain convenience getters for generating a Duration:
second, seconds
minute, minutes
hour, hours
day, days
week, weeks
month, months
year, yearsExample usage:
(3).days; // Duration { days: 3 }
(1).year; // Duration { years: 1 }
(2).weeks.from_now;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;date.isYesterday, date.isToday, date.isTomorrowChecks if the date falls within the corresponding day.
Date.today.isToday; // trueDate.currentReturns the current UTC date with full time precision.
Date.current;
Date.todayReturns today's UTC date at midnight.
Date.today;
Date.yesterday
Date.tomorrowUTC midnight versions of today, yesterday and tomorrow.
Returns the previous day at the beginning of day.
Date.current.yesterday;Returns the next day at the beginning of day.
Date.current.tomorrow;Returns the number of days in the current month.
new Date(Date.UTC(2024, 1, 1)).days_in_month; // 29Returns a new date advanced by the given duration.
Date.today.advance({ weeks: 1 });Returns a new date with selected UTC fields replaced.
Date.current.change({ hour: 0, minute: 0 });