Railbus

Do you want to use autogenerated set of Rails application routes in your JavaScript files?

Do you want to make requests to your routes from JS with human-readable names like create_news, update_message?

Here comes Railbus to you.

Installation

Add this line to your application's Gemfile:

gem 'railbus'

And then execute:

$ bundle install

You may call this to generate example file and install JavaScript dependencies:

$ rails g railbus:install

Or do it manually:

  1. Run yarn add @crosspath/yambus. See its README for usage info.
  2. Recommended step: add adapter for Axios or fetch function. Default: add adapter for Axios as yarn add @crosspath/yambus-axios.
  3. Create *.js.erb file in app/javascript (e.g. lib/routes.js.erb) to use it with Webpack:
/* rails-erb-loader-dependencies ../config/routes */
<%= Railbus.generate %>

And the last step, require this file in your .js files where you want to use application routes.

import Routes from 'lib/routes'

Usage

In short:

  1. See output of rails routes, it includes route names. You may use these names in JS as functions for requests.
  2. Append _path to a route name to get its URL.

Route names in JS depend on route names in Rails:

Action name HTTP method Request function Path function
index GET plural_name plural_name_path
show GET singular_name singular_name_path
new GET new_singular_name new_singular_name_path
edit GET edit_singular_name edit_singular_name_path
create POST create_singular_name create_singular_name_path
update PUT/PATCH update_singular_name update_singular_name_path
destroy DELETE delete_singular_name delete_singular_name_path

Configuration

By default Railbus generates functions for all routes defined in Rails.application, and these functions use library axios.

You may change it:

<%=
  Railbus.generate(
    # Here `YourApp::Engine` inherits `Rails::Engine`.
    # Default: `Rails.application`.
    app:    YourApp::Engine,

    # 'axios', 'fetch' or the name of function defined or imported in this file.
    # Appropriate NPM package for adapter should be installed.
    # Default: 'axios'.
    client: 'request_api',

    # Include only these route paths that start with '/api'.
    # Empty array: include all routes (default).
    include: [%r{^/api}],

    # Exclude all route paths matching regexpes.
    # Empty array: do not exclude any routes (default).
    exclude: [/edit|new/],

    # 'null' or the name of function defined or imported in this file.
    #
    # This function will be called before doing requests. It accepts
    # `url`, `options`, `route`, `params` and should return `options`.
    #
    # You may use it to add options before performing request or for logging
    # requests.
    #
    # Default: 'null' (no function).
    set_options: 'null'
  )
%>
import axios from 'axios'

// For example, pass all requests to subdomain `api`.
const axios_api = axios.create({
  baseURL: `${document.location.protocol}//api.${document.location.host}/`
})

function request_api(route, params, set_options) {
  let options = {
    url:    params.path,
    method: route.verb,
    params: params.url_options,
    data:   params.data
  }

  if (typeof set_options === 'function')
    options = set_options(params.path, options, route, params)

  return axios_api.request(options)
}

Examples

Let's take these routes as an example:

resources :news do
  resources :images, only: %w[index show create update destroy]
end
resources :messages, only: %w[new create]
namespace :my do
  resources :favourites, only: %w[index show create update destroy]
end

Assume you have generated object with routes in JS file lib/routes.

You may do this in JS to call requests:

import Routes from 'lib/routes'

// Request all favourites and print to console. Note: using namespace `my`.
// It will call `My::FavouritesController#index`.
// Path: '/my/favourites'.
Routes.my_favourites().then(resp => console.log(resp.data))

// Request all news and print to console.
// String `index` appended to distinguish `index` and `show` actions.
// It will call `NewsController#index`.
// Path: '/news'.
Routes.news_index().then(resp => console.log(resp.data))

// Request all news filtered by date, and print to console.
// `NewsController#index` with param `date` in query string (GET).
// Path: '/news' with query string '?date=2020-12-31'.
Routes.news_index({date: '2020-12-31'}).then(resp => console.log(resp.data))

// Request one news and print to console.
// `NewsController#show` with param `id` = 327 (GET).
// Path: '/news/327'.
Routes.news(327).then(resp => console.log(resp.data))
// Or
Routes.news({id: 327}).then(resp => console.log(resp.data))

// Specify format (for example, '.json').
// `NewsController#show` with param `id` = 327 (GET) and `format` = 'json'.
// Path: '/news/327.json'.
Routes.news(327, {format: 'json'}).then(resp => console.log(resp.data))
// Or
Routes.news({id: 327, format: 'json'}).then(resp => console.log(resp.data))

// Create news with given title.
// `NewsController#create` with param `title` (POST).
Routes.create_news({title: 'Bears showed up in Tomsk'}).then(resp => {
  console.log(resp.data)
})

// Create message with given title. Note: `message`, singular form.
// `MessagesController#create` with params `body`, `to` (POST).
Routes.create_message({body: 'Fantastic!', to: 23}).then(resp => {
  console.log(resp.data)
})

// Create message with given title. Note: using namespace `my`.
// `My::FavouritesController#create` with param `url` (POST).
Routes.create_my_favourite({url: 'https://best.site'}).then(resp => {
  console.log(resp.data)
})

// Attach an image to the news.
// `ImageController#create` with params `news_id` = 41 (GET),
// `my_attachment` (POST).
Routes.create_news_image(41, {my_attachment: file})

// Get URL for `create` action.
Routes.create_my_favourite_path() // => 'my/favourites'

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

TODO:

:white_check_mark: Support fetch — done!\ :black_square_button: Tests

Contributing

Bug reports and pull requests are welcome on GitHub at github.com/crosspath/railbus.

Please do not change version number in pull requests.

Alternative solutions

  1. rswag + swagger-js + your integration specs (tests)
  2. railsware/js-routes
  3. mtrpcic/js-routes
  4. less-js-routes
  5. js_named_routes

License

The gem is available as open source under the terms of the MIT License.