railsish

Rails-like blank and presence helpers, with related property access methods

Usage no npm install needed!

<script type="module">
  import railsish from 'https://cdn.skypack.dev/railsish';
</script>

README

railsish

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.

Example Usage

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");

Accessor helpers

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.

Get any present value

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");

Get a present number or string

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");

Converting values to booleans

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

Get a boolean value

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");

API Documentation

Table of Contents

Core functionality

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.

isPresent

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.

Parameters

  • value any

Returns boolean

isBlank

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 or Set
  • 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.

Parameters

  • value any

Returns boolean

booleanize

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

Parameters

  • value any something to convert

Examples

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

Accessors

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"]);

Caveat!

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);
}

getPresent

Get a property from object at path that isPresent.

Parameters

Returns object?

getBoolean

Get a boolean value from object at path.

Parameters

  • object any
  • path (string | Array<string>)
  • defaultValue any (optional, default false)

Examples

// It'll booleanize a deeply-nested value
getBoolean({ foo: { bar: [] } }, "foo.bar") === false;

Returns boolean

getFunction

Parameters

Returns function?

getPresentNumber

Parameters

Returns number?

getPresentString

Retrieve a present string from an object at path.

Parameters

Examples

getPresentString({ foo: "\t" }, "foo", "default") === "default";

Returns string?

getArray

Extract an Array-typed value from object at path.

Unlike getPresentArray, the array can be blank.

Parameters

Returns array?

getPresentArray

Extract an Array-typed value from object at path.

If the value is blank, it will return defaultValue instead.

Parameters

Returns array?

Utility

Some additional lower-level helper methods that get used elsewhere

isPresentObject

Check if a provided value is a plain object with at least 1 key set.

Parameters

  • value any

Examples

isPresentObject({}) === false
isPresentObject({ foo: "bar" }) === true
isPresentObject([]) === false
isPresentObject(new Foo()) === false
isPresentObject(Object.create(null)) === false

Returns boolean

isBlankObject

Test if a provided object is a blank, plain object.

Parameters

Examples

isBlankObject({}) === true
isBlankObject({ foo: "bar" }) === false
isBlankObject([]) === false
isBlankObject(new Foo()) === false
isBlankObject(Object.create(null)) === false

Returns boolean

respondTo

A duck-typing method to check if a property on object named name is a function.

Parameters

  • object any an object
  • name string a function name

Returns boolean whether typeof object[name] === "function"

respondToPath

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.

Parameters

Returns boolean whether typeof get(object, path) === "function"

Jest Matchers

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");
  });
});

install

Install the associated matchers into your jest environment to have presence, blankness, booleanization, and response helpers.

Parameters

  • expect object jest’s global expect method
    • expect.extend function the function that will accept the jest matchers

Examples

// in a file called by "setupFilesAfterEnv":
import { install } from "railsish/jest-matchers"

install(expect);

Returns void

matchers

A collection of matchers for use with jest.

Type: object

toBePresent

Parameters

  • received any something to check for presence
  • expected

Examples

expect(0).toBePresent();
expect([]).not.toBePresent();
expect(false).not.toBePresent();
expect("some string").toBePresent();
expect({}).not.toBePresent();
expect({ foo: "bar" }).toBePresent();
expect

Returns jestMatcherResult

toBeBlank

Parameters

  • received any something to check for blankness
  • expected

Examples

expect(0).not.toBeBlank();
expect([]).toBeBlank();
expect({}).toBeBlank();
expect(NaN).toBeBlank();
expect(Infinity).not.toBeBlank();
expect("\t\n\t").toBeBlank();

Returns jestMatcherResult

toBooleanizeAs

Test a value to see how it booleanizes.

Parameters

  • received any something to booleanize
  • expected boolean the value that booleanize should evaluate to

Examples

expect("some text").toBooleanizeAs(true);
expect([]).toBooleanizeAs(false);

Returns jestMatcherResult

toBooleanizeTrue

A wrapper around matchers.toBooleanizeAs with expected set to false.

Parameters

  • received any something to booleanize
  • expected

Examples

expect("some text").toBooleanizeTrue();
expect([]).not.toBooleanizeTrue();

Returns jestMatcherResult

toBooleanizeFalse

A wrapper around matchers.toBooleanizeAs with expected set to false.

Parameters

  • received any something to booleanize
  • expected

Examples

expect("some text").not.toBooleanizeFalse();
expect([]).toBooleanizeFalse();

Returns jestMatcherResult

toRespondTo

Parameters

  • received any something that might have a function
  • expected string a function name

Examples

expect(new Date()).toRespondTo("toISOString");

Returns jestMatcherResult

toRespondToPath

Parameters

  • received any something that might have a function
  • expected (string | Array<string>) a path to a function

Examples

expect({ foo: { bar: { baz: () => "quux" } } }).toRespondToPath("foo.bar.baz");

Returns jestMatcherResult

jestMatcherResult

An interface returned from a jest matcher.

Properties

  • pass boolean whether or not the matcher passed
  • message function (void): string a function that takes no args and returns a string representing the reason for the failure, if applicable