ActiveResource.js
A JavaScript class that acts like ActiveResource for Rails. The quest for a common interface to our API based resources among languages, apps, and projects.
About The Author
I am passionate about problem solving, fanatic about efficiency, and obsessed with bringing new ideas to life. I love solving problems and doing it efficiently. I am always looking for ways to DRY up code, and invent new optimizations for next time.
Another thing to note is that I am a full-stack developer who started as a designer (and still is one). So, my approach is usually different than that of a typical computer scientist. My goal is generally to get things functional enough to generate the UI/UX, and then loop back to validations, specs, indexes, etc. I draw great satisfaction from rapid prototyping, MVPs, and creating data driven experiences.
The Problem
How simple can we make the process of creating new resources in a Rails app and interacting with them in our React/React-Native or other JS framework app?
Can we make it simple, elegant, easy to work with, and not restricting?
Can we make our client side JavaScript feel as intuitive to work with as our Rails ActiveRecord or ActiveResource models?
If you've used Rails for any length of time, you've come to enjoy the simplicity of generating a new model, adding a few relations, generating scaffolding, and some specs.
It's all so dang easy!
Then we go to the client side... where we really just want to start interacting with our new record set as simply as with ActiveRecord. Let me get all, find, update, destroy. I want to get my UI framed up and not have to spend a lot of time configuring my JS models. I'll add the layers I need as I go, but initially, I want the basics out of the box quickly.
The Dream
Interact with my remote services more consistently in JS and Ruby, client side and server side. Create JS classes that feel like the server side equivalent ActiveResource
- Simple configuration
- Attribute getter overrides
- Overridable CRUD API
- OOP style Class and Instance methods
The alternative
I looked into React-Native-models, but they didn't give me quite what I wanted. There are many other alternatives, most of which are framework specific, so I wanted to build something that could be used in any JS project.
The Tools
- Rails
- React Native
- Inherited Resources
- ActiveResource.js
This was initially built inside a React Native app. React or React Native are not requirements, but ES6 is.
I'm a big fan of Inherited Resources, which generates a whole CRUD interface for a Rails controller with the ability to override or monkey patch any action you want. So with simple resource routes, an inherited resource controller, and an ActiveResource.js class, we can very quickly start interacting with our new model.
The Goal
We want to start interacting with a new Rails model from my front-end app as quickly as possible with all of the default behavior just working out of the box. We want our interaction with resources to feel very similar in Ruby and in JS. We want as little configuration as possible, without giving up flexibility later.
The Code
Here are some examples of how you can integrate ActiveResource.js into your project. Let's use a simple example of a blog post model. We'll generate the model and controller, configure the resource routes, set up your JS resource class, and play with it a bit.
Add this to Gemfile in your Rails app:
gem 'inherited_resources'
Then run bundle install in the console:
bundle install
Now, let's generate your model and controller in your Rails app.
rails g model Post title:string body:text
rails g controller posts
This will generate a controller that inherits ActionController::Base or ApplicationController. We are going to want to swap that out for InheritedResources::Base so we can get all of those handy automagical CRUD actions that respond to JSON and XML without writing them all by hand. If we need to later, we can override as needed.
class PostsController < InheritedResources::Base
end
A controller is only as useful as the routes to it. We'll take advantage of Rails resources to make one line generate our standard seven CRUD routes for us.
- Index
- Create
- New
- Edit
- Show
- Update
- Destroy
config/routes.rb
resources :posts
Now, let's get into the client side. We are slipping in another class here called RemoteService that hasn't been mentioned. It's included in the gist below if you want to see what it's all about, but in short, it's a class that provide RESTful methods Rails style from routes to http methods with callbacks.
You just need to point it to your server, and configure a few defaults params like an api_key, token, or user_id if needed (something that will be sent along with every request), and it's ready to go.
Create your new resource class inheriting ActiveResource.
import RemoteService from 'RemoteService';
import ActiveResource from 'ActiveResource';
var MyService = new RemoteService("https://myservice.com", { api_key: 'top-secret' });
export default class Post extends ActiveResource {
static remote = MyService;
static base_route = '/api/v1/posts';
// attribute getter overrides
// zero pad all ids
get id(){
return _.lpad(this.attributes.id, 8, '0');
}
}
Post.all((post) => {
console.log(post.id); // zero padded ids
})
// crud api with attr getter overrides
var post = Post.create({ title: 'new' });
var post = Post.find(post.id);
post.update('title','test');
post.save();
post.destroy();
The Gist
https://gist.github.com/gabecoyne/da8d23d5c31be2d5aacbe6ed5b78ebd8
TODO
Now, this could be taken even further to create a new extension of ActiveResource so that server side models and client side models handle everything even more consistently. ActiveResource::Base is great out of the box, but it may not handle InheritedResource errors perfectly.
class ApiResource < ActiveResource::Base
# overrides & customizations
end
class Post < ApiResource
# overrides & methods
end
You could also create a new extension of InherittedResources::Base to get things just how you like them. Then you could have all your API controllers inherit that new class. Then you can keep refining that class to add global functionality.
class ApiController < InherittedResources::Base
# overrides & customizations
end
class PostsController < ApiController
# overrides & actions
end