Skip to content


Subversion checkout URL

You can clone with
Download ZIP
A simple Representational State Transfer-based Hypertext Transfer Protocol-powered Object Relational Mapper. Her?
Pull request Compare This branch is 654 commits behind remiprev:master.
Fetching latest commit...
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


Build Status

Her is an ORM (Object Relational Mapper) that maps REST resources to Ruby objects. It is designed to build applications that are powered by a RESTful API and no database.


In your Gemfile, add:

gem "her"

That’s it!


First, you have to define which API your models will be bound to. For example, with Rails, you would create a new config/initializers/her.rb file with this line:

# config/initializers/her.rb
Her::API.setup :base_uri => ""

And then to add the ORM behavior to a class, you just have to include Her::Model in it:

class User
  include Her::Model

After that, using Her is very similar to many ActiveModel-like ORMs:

# GET and return an array of User objects

# GET and return a User object

@user = User.create(:fullname => "Tobias Fünke")
# POST "" with the data and return a User object

@user = => "Tobias Fünke")
@user.occupation = "actor"
# POST with the data and return a User object

@user = User.find(1)
@user.fullname = "Lindsay Fünke"
# PUT with the data and return+update the User object

Parsing data

By default, Her handles JSON data. It expects the data to be formatted in a certain structure. The default is this:

// The response of GET /users/1
  "data" : {
    "id" : 1,
    "name" : "Tobias Fünke"

// The response of GET /users
  "data" : [
      "id" : 1,
      "name" : "Tobias Fünke"
      "id" : 2,
      "name" : "Lindsay Fünke"
  "metadata" : {
    "page" : 1,
    "per_page" : 10

However, you can define your own parsing method, with Her::API.parse_with. The parse_with method takes a block which will be executed each time data from an HTTP response needs to be parsed. The block is expected to return a hash with three keys: data, errors and metadata. The following code enables parsing JSON data and treating this data as first-level properties:

Her::API.setup :base_uri => ""
Her::API.parse_with |response|
  json = JSON.parse(response.body, :symbolize_names => true)
  errors = json.delete(:errors)
    :data => json,
    :errors => errors || [],
    :metadata => {}

# User.find(1) will now expect "" to return something like '{ "id": 1, "name": "Tobias Fünke" }'

This feature is not stable and might change in the future, probably by using a middleware through Faraday.


You can define has_many, has_one and belongs_to relationships in your models. The relationship data is handled in two different ways. When parsing a resource from JSON data, if there’s a relationship data included, it will be used to create new Ruby objects.

If no relationship data was included when parsing a resource, calling a method with the same name as the relationship will fetch the data (providing there’s an HTTP request available for it in the API).

For example, with this setup:

class User
  include Her::Model
  has_many :comments
  has_one :role
  belongs_to :organization

class Comment
  include Her::Model

class Role
  include Her::Model

class Organization
  include Her::Model

If there’s relationship data in the resource, no extra HTTP request is made when calling the #comments method and an array of resources are returned:

@user = User.find(1) # { :data => { :id => 1, :name => "George Michael Bluth", :comments => [{ :id => 1, :text => "Foo" }, { :id => 2, :text => "Bar" }], :role => { :id => 1, :name => "Admin" }, :organization => { :id => 2, :name => "Bluth Company" } }}
@user.comments # => [#<Comment id=1>, #<Comment id=2>] fetched directly from @user
@user.role # => #<Role id=1> fetched directly from @user
@user.organization # => #<Organization id=2> fetched directly from @user

If there’s no relationship data in the resource, an extra HTTP request (to GET /users/1/comments) is made when calling the #comments method:

@user = User.find(1) # { :data => { :id => 1, :name => "George Michael Bluth" }}
@user.comments # => [#<Comment id=1>, #<Comment id=2>] fetched from /users/1/comments

For has_one relationship, an extra HTTP request (to GET /users/1/role) is made when calling the #role method:

@user = User.find(1) # { :data => { :id => 1, :name => "George Michael Bluth" }}
@user.role # => #<Role id=1> fetched from /users/1/role

For belongs_to relationship, an extra HTTP request (to GET /organizations/2) is made when calling the #organization method:

@user = User.find(1) # { :data => { :id => 1, :name => "George Michael Bluth", :organization_id => 2 }}
@user.organization # => #<Organization id=2> fetched from /organizations/2

However, subsequent calls to #comments or #role will not trigger the extra HTTP request.

Custom requests

You can easily add custom methods for your models. You can either use get_collection (which maps the returned data to a collection of resources), get_resource (which maps the returned data to a single resource) or get_raw (which yields the parsed data return from the HTTP request). Other HTTP methods are supported (post_raw, put_resource, etc.)

class User
  include Her::Model

  def self.popular

    get_raw("/users/stats") do |parsed_data|

User.popular  # => [#<User id=1>, #<User id=2>]    # => 42

Multiple APIs

It is possible to use different APIs for different models. Instead of calling Her::API.setup, you can create instances of Her::API:

# config/initializers/her.rb
$my_api =
$my_api.setup :base_uri => ""

$other_api =
$other_api.setup :base_uri => ""

You can then define which API a model will use:

class User
  include Her::Model
  uses_api $my_api

class Category
  include Her::Model
  uses_api $other_api



Things to be done

  • Deleting resources
  • Support for Faraday middleware to handle caching, alternative formats, etc.
  • Hooks before save, update, create, destroy, etc.
  • Better error handling
  • Better introspection for debug
  • Better documentation


Feel free to contribute and submit issues/pull requests on GitHub.

Take a look at the spec folder before you do, and make sure bundle exec rake spec passes after your modifications :)


Her is © 2012 Rémi Prévost and may be freely distributed under the LITL license. See the LICENSE file.

Something went wrong with that request. Please try again.