victor-am/rails-ranger

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# Rails Ranger
### Exploring the routes and paths of Ruby on Rails APIs

#### [Github Repository](https://github.com/victor-am/rails-ranger) | [Documentation](https://victor-am.github.io/rails-ranger)

[![npm version](https://badge.fury.io/js/rails-ranger.svg)](https://badge.fury.io/js/rails-ranger)
[![Travis build status](http://img.shields.io/travis/victor-am/rails-ranger.svg?style=flat)](https://travis-ci.org/victor-am/rails-ranger)
[![Test Coverage](https://codeclimate.com/github/victor-am/rails-ranger/badges/coverage.svg)](https://codeclimate.com/github/victor-am/rails-ranger)
[![Dependency Status](https://david-dm.org/victor-am/rails-ranger.svg)](https://david-dm.org/victor-am/rails-ranger)
[![devDependency Status](https://david-dm.org/victor-am/rails-ranger/dev-status.svg)](https://david-dm.org/victor-am/rails-ranger#info=devDependencies)

Rails Ranger is a thin layer on top of [Axios](https://github.com/mzabriskie/axios), which gives you an opinionated interface to query APIs built with Ruby on Rails.

## Main features
- URL building following Ruby on Rails routes conventions
- Automatic transformation of camelCase into snake_case and back to camelCase when exchanging data between the front-end and the API


## Installation
```bash
npm install --save rails-ranger
```

**or**

```bash
yarn add rails-ranger
```
<br>

## Getting started
If you prefer a blog post, checkout our [getting started guide here](https://alligator.io/js/rails-ranger/).

The following example illustrates a simple usage of the library:

```javascript
// api-client.js
import RailsRanger from 'rails-ranger'

const config = {
  axios: {
    baseURL: 'http://api.myapp.com',
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    }
  }
}

export default new RailsRanger(config)
```

```javascript
// some-front-end-component.js
import api from 'api-client'

api.list('users').then((response) => {
  const users = response.data
})
```

The `list` function makes a request to the **index** path of the **users** resource, following Rails routing conventions. This means a `GET` request to the `/users` path.

Also we converted the *snake_cased* JSON generated by Ruby on Rails automatically to *camelCase*, as preferred in Javascript.

> **Observation:** you can use `api.index('users')` as well. The `list` function is just an alias for it.
<br>

### Important notice for non api-only Ruby on Rails servers
You must setup the headers correctly, passing down the content type and accept keys as `application/json` (as shown in the example above) for Rails to serve the endpoint in the json format instead of presuming the HTTP default.

### A slightly more advanced example:

```javascript
api.resource(users, 1).list('blogPosts', { someParameter: false })
// => GET request to /users/1/blog_posts?some_parameter=false
```
<br>

### Build your own client object
You can build your own client object to centralize the API routes used by your front-end app.

This is indeed **recommended** for non-trivial applications, to avoid duplication, allow manipulating the parameters before performing the request and make your life easier in the event of removal/replacement of this dependency from your project.

Below is an example of such implementation:

```javascript
// api-client.js
import RailsRanger from 'rails-ranger'

const client = new RailsRanger

export default {
  users: {
    list(params) {
      return client.list('users', params)
    }
  }

  blogPosts: {
    list(params) {
      return client.list('blogPosts', params)
    }
  }
}
```

```javascript
// some-front-end-component.js
import api from 'api-client'

api.users.list({ limit: 3 }).then((response) => {
  const users = response.data
})
```
<br>

## Options
As the first argument when creating a new instance of Rails Ranger you can pass an object of options to customize the behavior of the client.

### dataTransform
**default: true**

By default RailsRanger will convert camelCased keys in your jsons to snake_case when sending a request to Rails, and will convert the Rails response back from snake_case to camelCase for better usage within your javascript code.

You can disable this behavior by setting `dataTransform` to false:

```javascript
const api = new RailsRanger({ dataTransform: false })
```

---
### axios
**default: {}**

Any object passed to the `axios` option will be handled to **Axios**.
Here an example using the `baseUrl` configuration of Axios:

```javascript
const api = new RailsRanger({ axios: { baseUrl: 'http://myapp.com/api/v1' } })

api.list('users')
// => GET request to http://myapp.com/api/users
```

#### See more configuration options in the [Axios documentation](https://github.com/mzabriskie/axios#request-config)
<br>

## Use Rails Ranger just for path building
You don't need to use Rails Ranger as an ajax client if you don't want to. It can also be used just to generate the resource routes and then make the request with another tool. The following is an example of this usage:

```javascript
import { RouteBuilder } from RailsRanger
const routes = new RouteBuilder

routes.create('users', { name: 'John' })
// => { path: '/users', params: { name: 'John' }, method: 'post' }

routes.show('users', { id: 1, hidePassword: true })
// => { path: '/users/1?hide_password=true', params: {}, method: 'get' }

routes.get('/:api/documentation', { api: 'v1', page: 3 })
// => { path: 'v1/documentation?page=3', params: {}, method: 'get' }
```
<br>

## Nested resources
You can access your nested resources by using the `.resource` function:

```javascript
api.resource('users').list('blogPosts')
//=> GET request to /users/blog_posts


api.resource('users', 1).list('blogPosts')
//=> GET request to /users/1/blog_posts
```
<br>

## Namespaced routes
The `.namespace` function can help you to build a path nested within a Rails namespace:

```javascript
api.namespace('users').list('blogPosts')
//=> GET request to /users/blog_posts


api.namespace('admin_roles/:type', { type: 1 }).list('blogPosts')
//=> GET request to /admin_roles/1/blog_posts
```
<br>

## Available actions

### List/Index
```javascript
api.list('users', { limit: 3 })
// => GET request to /users?limit=3

api.index('users', { limit: 3 })
// => GET request to /users?limit=3
```

### Show
```javascript
api.show('users', { id: 1 })
// => GET request to /users/1
```

### New
```javascript
api.new('users')
// => GET request to /users/new
```

### Create
```javascript
api.create('users', { email: 'john@doe.com' })
// => POST request to /users
```

### Edit
```javascript
api.edit('users', { id: 1 })
// => GET request to /users/1/edit
```

### Update
```javascript
api.update('users', { id: 1, name: 'John Doe' })
// => PATCH request to /users/1
```

### Destroy
```javascript
api.destroy('users', { id: 1 })
// => DELETE request to /users/1
```
<br>

## Available HTTP methods

### GET
```javascript
api.get('users/:id', { id: 1, hidePassword: true })
// => GET request to users/1&hide_password=true
```

### POST
```javascript
api.post('users/:id', { id: 1, name: 'John' })
// => POST request to users/1 with a JSON payload containing: { "name": "John" }
```

### PATCH
```javascript
api.patch('users/:id', { id: 1, name: 'John' })
// => PATCH request to users/1 with a JSON payload containing: { "name": "John" }
```

### PUT
```javascript
api.put('users/:id', { id: 1, name: 'John' })
// => PUT request to users/1 with a JSON payload containing: { "name": "John" }
```

### DELETE
```javascript
api.delete('users/:id', { id: 1, hidePassword: true })
// => DELETE request to users/1&hide_password=true
```
<br>

## Request Cancellation
Since rails-ranger is built on top of Axios, [request cancellation works the same way](https://github.com/axios/axios#cancellation).
```javascript
import api from 'api-client'

import axios from 'axios';
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

const request = api.get('/users/:id', {id: 1}, {cancelToken: source.token})
request.cancel = (optionalMessage) => source.cancel(optionalMessage);
```