DEV Community

Rails Designer
Rails Designer

Posted on • Originally published at railsdesigner.com

String Inflectors: bring a bit of Rails into JavaScript

The code from this article, originally published on Rails Designer, was taken from the book JavaScript for Rails Developers. Get your copy today! πŸ§‘β€πŸ’»


Ruby developers working with JavaScript often miss the convenience of Ruby's string manipulation methods. While Ruby (Rails) spoils us with elegant transformations like "user_name".camelize, JavaScript requires you to roll our own helpers or reach for external dependencies.

This article explores creating a lightweight collection of JavaScript string helpers, inspired by Rails' ActiveSupport inflectors, instead of adding yet another package to your project. Let's look at how to add these helpers in JavaScript and use them in your Rails app.

From snake_case to camelCase

In Rails, you can easily convert strings with String#camelize. Let's implement a JavaScript equivalent:

// app/javascript/helpers/stringInflectors.js
export const camelize = (text) => {
  return text
    .replace(/[-_\s]+(.)?/g, (_, character) => character ? character.toUpperCase() : "")
    .replace(/^[A-Z]/, character => character.toLowerCase())
}
Enter fullscreen mode Exit fullscreen mode

This function transforms underscored or hyphenated strings into camelCase format. The first regex replaces any dash, underscore, or space followed by a character with the uppercase version of that character. The second regex ensures the first character is lowercase, giving us proper camelCase.

Small aside: The _ in (_, character) denotes an unused parameter. This is a common JavaScript convention for parameters that must be included in the function signature but aren't used in the implementation.

Now how to use it?

// app/javascript/controllers/form_controller.js
import { Controller } from "@hotwired/stimulus"
import { camelize } from "../helpers/stringInflectors"

export default class extends Controller {
  connect() {
    const propertyName = camelize("user_name") // => "userName"

    // Use the camelized property name
    this.element.dataset[propertyName] = "value"
  }
}
Enter fullscreen mode Exit fullscreen mode

ordinalize: human-friendly numbers

Let's look at a more specialized helper: ordinalize, which adds the appropriate suffix to numbers (1st, 2nd, 3rd, etc.).

In Rails, this functionality is provided by ActiveSupport::Inflector.ordinalize or the Integer#ordinalize method. The implementation logic in JavaScript is nearly identical:

export const ordinalize = (number) => {
  const mod100 = number % 100

  if (mod100 >= 11 && mod100 <= 13) return `${number}th`

  switch (number % 10) {
    case 1: return `${number}st`
    case 2: return `${number}nd`
    case 3: return `${number}rd`
    default: return `${number}th`
  }
}
Enter fullscreen mode Exit fullscreen mode

This function handles the special cases for numbers ending in 11, 12, and 13 (which all use th), then applies the appropriate suffix based on the last digit.

Another small aside: The % (modulo) operator returns the remainder after division. Using number % 100 extracts just the last two digits of any number, which is perfect for handling ordinal suffix rules.

For a practical example using this helper in a Stimulus controller:

// app/javascript/controllers/leaderboard_controller.js
import { Controller } from "@hotwired/stimulus"
import { ordinalize } from "../helpers/stringInflectors"

export default class extends Controller {
  static targets = ["rank"]

  displayRank(position) {
    this.rankTarget.textContent = ordinalize(position)
  }
}
Enter fullscreen mode Exit fullscreen mode

Add even more helpers

This is the complete module, that comes with the Pro package of the book. It includes several other useful transformations:

  • underscore: Converts camelCase to snake_case (Rails: String#underscore)
  • dasherize: Converts underscores to dashes (Rails: String#dasherize)
  • humanize: Capitalizes first word and converts underscores to spaces (Rails: String#humanize)
  • titleize: Capitalizes all words (Rails: String#titleize)
  • downcase and upcase: Simple case converters (Rails: String#downcase, String#upcase)
  • parameterize: Makes strings URL-friendly (Rails: String#parameterize)
// app/javascript/helpers/stringInflectors.js

export const camelize = (text) => {
  return text
    .replace(/[-_\s]+(.)?/g, (_, character) => character ? character.toUpperCase() : "")
    .replace(/^[A-Z]/, character => character.toLowerCase())
}

export const underscore = (text) => {
  return text
    .replace(/([A-Z])/g, "_$1")
    .replace(/[-\s]+/g, "_")
    .toLowerCase()
    .replace(/^_/, "")
}

export const dasherize = (text) => {
  return text
    .replace(/([A-Z])/g, "-$1")
    .replace(/[_\s]+/g, "-")
    .toLowerCase()
    .replace(/^-/, "")
}

export const humanize = (text) => {
  return underscore(text)
    .replace(/_id$/, "")
    .replace(/_/g, " ")
    .replace(/^[a-z]/, character => character.toUpperCase())
}

export const titleize = (text) => {
  return humanize(text).replace(/\b[a-z]/g, character => character.toUpperCase())
}

export const downcase = (text) => text.toLowerCase()
export const upcase = (text) => text.toUpperCase()

export const parameterize = (text) => {
  return text
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-|-$/g, "")
}

export const ordinalize = (number) => {
  const mod100 = number % 100

  if (mod100 >= 11 && mod100 <= 13) return `${number}th`

  switch (number % 10) {
    case 1: return `${number}st`
    case 2: return `${number}nd`
    case 3: return `${number}rd`
    default: return `${number}th`
  }
}

export default {
  camelize,
  underscore,
  dasherize,
  humanize,
  titleize,
  downcase,
  upcase,
  parameterize,
  ordinalize
}
Enter fullscreen mode Exit fullscreen mode

To use these helpers in your Rails application, you can import specific helpers:

import { camelize, ordinalize } from "../helpers/stringInflectors"
// Usage: camelize("user_name")
Enter fullscreen mode Exit fullscreen mode

Or import everything as a namespace:

import * as inflector from "../helpers/stringInflectors"
// Usage: inflector.camelize("user_name")
Enter fullscreen mode Exit fullscreen mode

If you're using importmap-rails, add this to your config/importmap.rb:

pin_all_from "app/javascript/helpers", under: "helpers"
Enter fullscreen mode Exit fullscreen mode

Interested in writing and understanding code like this yourself? I recently wrote the book JavaScript for Rails Developers. It is already read by hundreds of (Rails) developers like you. 😊 This code snippet was included with it along with many other practical, code examples and a practical step by step explanation of writing beautiful, modern JavaScript code.

Top comments (0)