A collection of functions that operate on principles found in Rails,
specifically with regard to figuring out if a given value is blank
or present
.
import { strict: assert } from "assert";
import { isPresent, isBlank } from "railsish";
// It treats empty or invalid values as blank...
assert(isBlank([]), "an empty array is blank");
assert(isBlank({}), "an empty object is blank");
assert(isBlank(new Date("2020-03-32")), "an invalid date is blank");
assert(isBlank(NaN), "NaN is blank");
assert(isBlank("\n\t "), "a string containing only whitespace is blank");
// But there's a special case: 0 is present!
assert(isPresent(0), "0 is present");
Related to the above concepts, railsish
includes some helper methods
that can be used to extract values from nested objects, perform some
checks, and do what you want them to.
import { strict: assert } from "assert";
import { getPresent } from "railsish";
assert.equal(getPresent({ foo: { bar: { baz: null } } }, "foo.bar.baz", "quux"), "quux", "it will return a default value");
import { strict: assert } from "assert";
import { getPresentNumber, getPresentString } from "railsish";
assert.equal(getPresentNumber({ foo: "-3.14" }, "foo"), -3.14, "fetching a numeric string will cast it");
assert.equal(getPresentString({ foo: "\t" }, "foo", "default"), "default", "whitespace-only strings are not present");
Sometimes you want to work with boolean values directly, distinct from blankness and presence,
and so there’s also the booleanize
function. It works a little differently, in that it does
treat 0, and certain string values as falsey. It’s intended for use with API responses or
other inconsistent data containing booleanesque values that aren’t true
or false
.
import { booleanize } from "railsish";
booleanize(0) === false
booleanize("0") == false
// an empty object
booleanize({}) === false
// an empty array
booleanize([]) === false
booleanize(new Int8Array()) === false
// an empty string
booleanize("") === false
// a string that's only whitespace
booleanize(" ") === false
// special string handling: the following are case-insensitive
booleanize("F") === false
booleanize("n") === false
booleanize("no") === false
booleanize("nil") === false
booleanize("NULL") === false
booleanize("FALSE") === false
There’s a helper method for this one, too, that uses its logic instead:
import { strict: assert } from "assert";
import { getBoolean } from "railsish";
assert.fail(getBoolean({ foo: [] }, "foo"), "it will booleanize the value at a given path");
- Core functionality
- isPresent
- isBlank
- booleanize
- Accessors
- getPresent
- getBoolean
- getFunction
- getPresentNumber
- getPresentString
- getArray
- getPresentArray
- Utility
- isPresentObject
- isBlankObject
- respondTo
- respondToPath
- Jest Matchers
- install
- matchers
- jestMatcherResult
isPresent
and isBlank
trace their inspiration to Rails and ActiveSupport’s Object#blank?
logic.
booleanize
is loosely inspired by how ActiveRecord
converts values from forms to booleans,
but has been expanded for handling other edge cases I’ve come across when developing.
- See: Object#present? from Rails
Check to see if a given value is “present”, translated from Rails’ standards into JavaScript.
It is a boolean complement of {@linkcode module:railsish/presence.isBlank isBlank}. Simply put, if an object is not blank, it is considered present.
value
any
Returns boolean
- See: Object#blank? from Rails
Check to see if a given value is “blank”, translated from Rails’ standards into JavaScript.
Certain values are truthy in JS that are considered blank by this function, namely:
- A string containing only whitespace, e.g.
"\n\t \r\n"
- An empty array
- An empty plain object, e.g.
{}
- An empty
Map
orSet
- An invalid
Date
object, e.g.new Date("2020-03-32")
Additionally, 0
is considered false but is not considered blank,
as it is a finite number.
value
any
Returns boolean
Convert a provided value to a boolean with some handy logic.
This is used to handle cases where the value should be a
boolean, but might be coming from an API or some other representation
that is not explicitly true
/ false
value
any something to convert
booleanize(0) === false
booleanize("0") == false
// an empty object
booleanize({}) === false
// an empty array
booleanize([]) === false
booleanize(new Int8Array()) === false
// an empty string
booleanize("") === false
// a string that's only whitespace
booleanize(" ") === false
// special string handling: the following are case-insensitive
booleanize("F") === false
booleanize("n") === false
booleanize("no") === false
booleanize("nil") === false
booleanize("NULL") === false
booleanize("FALSE") === false
Returns boolean the booleanized value
Methods used to fetch various deeply-nested values based on presence or booleanization from an object.
Process an API response, elasticsearch document, JSON document, or anything else and remove the need for complex code like:
import { getPresentArray }
const response = {
foo: {
bar: {
baz: {
quux: []
}
}
}
};
// instead of this:
function extractDeepValue(value) {
if (value && value.foo) {
if (value.foo.bar) {
if (value.foo.bar.baz) {
if (Array.isArray(value.foo.bar.baz.quux) && value.foo.bar.baz.quux.length > 0) {
return value;
}
}
}
}
return ["default"];
};
extractDeepValue(response, ["default"]);
// you can just:
getPresentArray(response, "foo.bar.baz.quux", ["default"]);
While all of the get___
accessors accept a defaultValue
as their third parameter,
no type-checking or related validation is performed on it: it’s returned as is. This
is by design, so that you can handle unset values at a higher level.
import { getPresentArray } from "railsish";
const NO_TAGS_SELECTED = Symbol("NO_TAGS");
const getSelectedTags = (config) => getPresentArray(config, "user.selected.tags", NO_TAGS_SELECTED);
// A contrived express handler
export async function renderSelectedTags(req, res) {
const tags = getSelectedTags(req.user.config);
if (tags === NO_TAGS_SELECTED) {
// take the user to a
return res.json({
error: "User needs to select tags",
code: "SELECT_TAGS_FIRST"
}).status(400);
}
const tagResponse = await myDB.getTags(tags);
res.json(tagResponse);
}
Get a property from object
at path
that
isPresent.
Returns object?
- See: booleanize
Get a boolean value from object
at path
.
// It'll booleanize a deeply-nested value
getBoolean({ foo: { bar: [] } }, "foo.bar") === false;
Returns boolean
Returns function?
Returns number?
Retrieve a present string from an object
at path
.
getPresentString({ foo: "\t" }, "foo", "default") === "default";
Returns string?
Extract an Array
-typed value from object
at path
.
Unlike getPresentArray, the array can be blank.
Returns array?
Extract an Array
-typed value from object
at path
.
If the value is blank, it will return defaultValue
instead.
Returns array?
Some additional lower-level helper methods that get used elsewhere
Check if a provided value
is a plain object with at least 1 key set.
value
any
isPresentObject({}) === false
isPresentObject({ foo: "bar" }) === true
isPresentObject([]) === false
isPresentObject(new Foo()) === false
isPresentObject(Object.create(null)) === false
Returns boolean
Test if a provided object is a blank, plain object.
value
object
isBlankObject({}) === true
isBlankObject({ foo: "bar" }) === false
isBlankObject([]) === false
isBlankObject(new Foo()) === false
isBlankObject(Object.create(null)) === false
Returns boolean
- See: Object#respond_to? from Ruby
A duck-typing method to check if a property on object
named name
is a function.
object
any an objectname
string a function name
Returns boolean whether typeof object[name] === "function"
- See: Object#respond_to? from Ruby
A duck-typing method to check if a property on object
at the provided path
is a function.
Similar to respondTo
, but allows you to use an arbitrarily nested path.
object
any an objectpath
(string | array<number, string>) a path to a possible function à lalodash.get
Returns boolean whether typeof get(object, path) === "function"
Some helpers for jest ship with this library that you can install into your test environment.
import { install as installRailsishMatchers } from "railsish/jest-matchers"
installRailsishMatchers(expect)
describe("some tests", () => {
it("returns a present response", () => {
expect(myLibrary.doSomething()).toBePresent();
});
it("returns something blank", () => {
expect(anonymousRequest.getCurrentUser()).toBeBlank();
});
it("implements foo", () => {
expect({ foo: () => "bar" }).toRespondTo("foo");
});
});
- See: expect.extend
Install the associated matchers
into your
jest environment to have presence, blankness, booleanization, and response helpers.
expect
object jest’s globalexpect
methodexpect.extend
function the function that will accept the jest matchers
// in a file called by "setupFilesAfterEnv":
import { install } from "railsish/jest-matchers"
install(expect);
Returns void
- See: expect.extend
A collection of matchers for use with jest.
Type: object
received
any something to check for presenceexpected
expect(0).toBePresent();
expect([]).not.toBePresent();
expect(false).not.toBePresent();
expect("some string").toBePresent();
expect({}).not.toBePresent();
expect({ foo: "bar" }).toBePresent();
expect
Returns jestMatcherResult
received
any something to check for blanknessexpected
expect(0).not.toBeBlank();
expect([]).toBeBlank();
expect({}).toBeBlank();
expect(NaN).toBeBlank();
expect(Infinity).not.toBeBlank();
expect("\t\n\t").toBeBlank();
Returns jestMatcherResult
- See: booleanize
Test a value to see how it booleanizes.
received
any something tobooleanize
expected
boolean the value thatbooleanize
should evaluate to
expect("some text").toBooleanizeAs(true);
expect([]).toBooleanizeAs(false);
Returns jestMatcherResult
- See: booleanize
A wrapper around matchers.toBooleanizeAs with expected
set to false
.
received
any something tobooleanize
expected
expect("some text").toBooleanizeTrue();
expect([]).not.toBooleanizeTrue();
Returns jestMatcherResult
- See: booleanize
A wrapper around matchers.toBooleanizeAs with expected
set to false
.
received
any something tobooleanize
expected
expect("some text").not.toBooleanizeFalse();
expect([]).toBooleanizeFalse();
Returns jestMatcherResult
- See: respondTo
received
any something that might have a functionexpected
string a function name
expect(new Date()).toRespondTo("toISOString");
Returns jestMatcherResult
- See: respondToPath
received
any something that might have a functionexpected
(string | Array<string>) a path to a function
expect({ foo: { bar: { baz: () => "quux" } } }).toRespondToPath("foo.bar.baz");
Returns jestMatcherResult
An interface returned from a jest matcher.