{"id":13482805,"url":"https://github.com/remi/her","last_synced_at":"2025-04-14T01:00:27.948Z","repository":{"id":2953694,"uuid":"3967647","full_name":"remi/her","owner":"remi","description":"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 instead of a database.","archived":false,"fork":false,"pushed_at":"2024-05-17T10:59:56.000Z","size":1144,"stargazers_count":2051,"open_issues_count":117,"forks_count":322,"subscribers_count":44,"default_branch":"master","last_synced_at":"2024-10-29T14:15:15.507Z","etag":null,"topics":["json-data","orm","rails","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/remi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2012-04-08T23:34:17.000Z","updated_at":"2024-10-26T01:15:38.000Z","dependencies_parsed_at":"2024-06-18T11:28:36.898Z","dependency_job_id":null,"html_url":"https://github.com/remi/her","commit_stats":{"total_commits":792,"total_committers":91,"mean_commits":8.703296703296703,"dds":"0.43813131313131315","last_synced_commit":"b59a05aa12ac9da44d4d51dcfbbfb7c67a4a156a"},"previous_names":["remiprev/her"],"tags_count":66,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/remi%2Fher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/remi%2Fher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/remi%2Fher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/remi%2Fher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/remi","download_url":"https://codeload.github.com/remi/her/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248804790,"owners_count":21164132,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["json-data","orm","rails","ruby"],"created_at":"2024-07-31T17:01:05.654Z","updated_at":"2025-04-14T01:00:27.889Z","avatar_url":"https://github.com/remi.png","language":"Ruby","readme":"\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/remiprev/her\"\u003e\n    \u003cimg src=\"http://i.imgur.com/43KEchq.png\" alt=\"Her\" /\u003e\n  \u003c/a\u003e\n  \u003cbr /\u003e\n  Her is an ORM (Object Relational Mapper) that maps REST resources to Ruby objects.\u003cbr /\u003e It is designed to build applications that are powered by a RESTful API instead of a database.\n  \u003cbr /\u003e\u003cbr /\u003e\n  \u003ca href=\"https://rubygems.org/gems/her\"\u003e\u003cimg src=\"http://img.shields.io/gem/v/her.svg\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://codeclimate.com/github/remiprev/her\"\u003e\u003cimg src=\"http://img.shields.io/codeclimate/github/remiprev/her.svg\" /\u003e\u003c/a\u003e\n  \u003ca href='https://gemnasium.com/remiprev/her'\u003e\u003cimg src=\"http://img.shields.io/gemnasium/remiprev/her.svg\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://travis-ci.org/remiprev/her\"\u003e\u003cimg src=\"http://img.shields.io/travis/remiprev/her/master.svg\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://gitter.im/her-orm/Lobby\"\u003e\u003cimg src=\"https://badges.gitter.im/her-orm/Lobby.png\" alt=\"Gitter chat\" title=\"\" data-pin-nopin=\"true\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Installation\n\nIn your Gemfile, add:\n\n```ruby\ngem \"her\"\n```\n\nThat’s it!\n\n## Usage\n\n_For a complete reference of all the methods you can use, check out [the documentation](http://rdoc.info/github/remiprev/her)._\n\nFirst, 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 these lines:\n\n```ruby\n# config/initializers/her.rb\nHer::API.setup url: \"https://api.example.com\" do |c|\n  # Request\n  c.use Faraday::Request::UrlEncoded\n\n  # Response\n  c.use Her::Middleware::DefaultParseJSON\n\n  # Adapter\n  c.use Faraday::Adapter::NetHttp\nend\n```\n\nAnd then to add the ORM behavior to a class, you just have to include `Her::Model` in it:\n\n```ruby\nclass User\n  include Her::Model\nend\n```\n\nAfter that, using Her is very similar to many ActiveRecord-like ORMs:\n\n```ruby\nUser.all\n# GET \"https://api.example.com/users\" and return an array of User objects\n\nUser.find(1)\n# GET \"https://api.example.com/users/1\" and return a User object\n\n@user = User.create(fullname: \"Tobias Fünke\")\n# POST \"https://api.example.com/users\" with `fullname=Tobias+Fünke` and return the saved User object\n\n@user = User.new(fullname: \"Tobias Fünke\")\n@user.occupation = \"actor\"\n@user.save\n# POST \"https://api.example.com/users\" with `fullname=Tobias+Fünke\u0026occupation=actor` and return the saved User object\n\n@user = User.find(1)\n@user.fullname = \"Lindsay Fünke\"\n@user.save\n# PUT \"https://api.example.com/users/1\" with `fullname=Lindsay+Fünke` and return the updated User object\n```\n\n### ActiveRecord-like methods\n\nThese are the basic ActiveRecord-like methods you can use with your models:\n\n```ruby\nclass User\n  include Her::Model\nend\n\n# Update a fetched resource\nuser = User.find(1)\nuser.fullname = \"Lindsay Fünke\" # OR user.assign_attributes(fullname: \"Lindsay Fünke\")\nuser.save # returns false if it fails, errors in user.response_errors array\n# PUT \"/users/1\" with `fullname=Lindsay+Fünke`\n\nuser.update_attributes(fullname: \"Maeby Fünke\")\n# PUT \"/users/1\" with `fullname=Maeby+Fünke`\n\n# =\u003e PUT /users/1 { \"id\": 1, \"name\": \"new new name\" }\n# Update a resource without fetching it\nUser.save_existing(1, fullname: \"Lindsay Fünke\")\n# PUT \"/users/1\" with `fullname=Lindsay+Fünke`\n\n# Destroy a fetched resource\nuser = User.find(1)\nuser.destroy\n# DELETE \"/users/1\"\n\n# Destroy a resource without fetching it\nUser.destroy_existing(1)\n# DELETE \"/users/1\"\n\n# Fetching a collection of resources\nUser.all\n# GET \"/users\"\nUser.where(moderator: 1).all\n# GET \"/users?moderator=1\"\n\n# Create a new resource\nUser.create(fullname: \"Maeby Fünke\")\n# POST \"/users\" with `fullname=Maeby+Fünke`\n\n# Save a new resource\nuser = User.new(fullname: \"Maeby Fünke\")\nuser.save! # raises Her::Errors::ResourceInvalid if it fails\n# POST \"/users\" with `fullname=Maeby+Fünke`\n```\n\nYou can look into the [`her-example`](https://github.com/remiprev/her-example) repository for a sample application using Her.\n\n## Middleware\n\nSince Her relies on [Faraday](https://github.com/lostisland/faraday) to send HTTP requests, you can choose the middleware used to handle requests and responses. Using the block in the `setup` call, you have access to Faraday’s `connection` object and are able to customize the middleware stack used on each request and response.\n\n### Authentication\n\nHer doesn’t support authentication by default. However, it’s easy to implement one with request middleware. Using the `setup` block, we can add it to the middleware stack.\n\nFor example, to add a token header to your API requests in a Rails application, you could use the excellent [`request_store`](https://rubygems.org/gems/request_store) gem like this:\n\n```ruby\n# app/controllers/application_controller.rb\nclass ApplicationController \u003c ActionController::Base\n  before_filter :set_user_api_token\n\n  protected\n  def set_user_api_token\n    RequestStore.store[:my_api_token] = current_user.api_token # or something similar based on `session`\n  end\nend\n\n# lib/my_token_authentication.rb\nclass MyTokenAuthentication \u003c Faraday::Middleware\n  def call(env)\n    env[:request_headers][\"X-API-Token\"] = RequestStore.store[:my_api_token]\n    @app.call(env)\n  end\nend\n\n# config/initializers/her.rb\nrequire \"lib/my_token_authentication\"\n\nHer::API.setup url: \"https://api.example.com\" do |c|\n  # Request\n  c.use MyTokenAuthentication\n  c.use Faraday::Request::UrlEncoded\n\n  # Response\n  c.use Her::Middleware::DefaultParseJSON\n\n  # Adapter\n  c.use Faraday::Adapter::NetHttp\nend\n```\n\nNow, each HTTP request made by Her will have the `X-API-Token` header.\n\n### Basic Http Authentication\nHer can use basic http auth by adding a line to your initializer\n\n```ruby\n# config/initializers/her.rb\nHer::API.setup url: \"https://api.example.com\" do |c|\n  # Request\n  c.use Faraday::Request::BasicAuthentication, 'myusername', 'mypassword'\n  c.use Faraday::Request::UrlEncoded\n\n  # Response\n  c.use Her::Middleware::DefaultParseJSON\n\n  # Adapter\n  c.use Faraday::Adapter::NetHttp\nend\n```\n\n### OAuth\n\nUsing the `faraday_middleware` and `simple_oauth` gems, it’s fairly easy to use OAuth authentication with Her.\n\nIn your Gemfile:\n\n```ruby\ngem \"her\"\ngem \"faraday_middleware\"\ngem \"simple_oauth\"\n```\n\nIn your Ruby code:\n\n```ruby\n# Create an application on `https://dev.twitter.com/apps` to set these values\nTWITTER_CREDENTIALS = {\n  consumer_key: \"\",\n  consumer_secret: \"\",\n  token: \"\",\n  token_secret: \"\"\n}\n\nHer::API.setup url: \"https://api.twitter.com/1/\" do |c|\n  # Request\n  c.use FaradayMiddleware::OAuth, TWITTER_CREDENTIALS\n\n  # Response\n  c.use Her::Middleware::DefaultParseJSON\n\n  # Adapter\n  c.use Faraday::Adapter::NetHttp\nend\n\nclass Tweet\n  include Her::Model\nend\n\n@tweets = Tweet.get(\"/statuses/home_timeline.json\")\n```\n\nSee the [*Authentication middleware section*](#authentication) for an example of how to pass different credentials based on the current user.\n\n### Parsing JSON data\n\nBy default, Her handles JSON data. It expects the resource/collection data to be returned at the first level.\n\n```javascript\n// The response of GET /users/1\n{ \"id\" : 1, \"name\" : \"Tobias Fünke\" }\n\n// The response of GET /users\n[{ \"id\" : 1, \"name\" : \"Tobias Fünke\" }]\n```\n\nHowever, if you want Her to be able to parse the data from a single root element (usually based on the model name), you’ll have to use the `parse_root_in_json` method (See the [**JSON attributes-wrapping**](#json-attributes-wrapping) section).\n\nAlso, you can define your own parsing method using a response middleware. The middleware should set `env[:body]` to a hash with three symbol keys: `:data`, `:errors` and `:metadata`. The following code uses a custom middleware to parse the JSON data:\n\n```ruby\n# Expects responses like:\n#\n#     {\n#       \"result\": { \"id\": 1, \"name\": \"Tobias Fünke\" },\n#       \"errors\": []\n#     }\n#\nclass MyCustomParser \u003c Faraday::Response::Middleware\n  def on_complete(env)\n    json = MultiJson.load(env[:body], symbolize_keys: true)\n    env[:body] = {\n      data: json[:result],\n      errors: json[:errors],\n      metadata: json[:metadata]\n    }\n  end\nend\n\nHer::API.setup url: \"https://api.example.com\" do |c|\n  # Response\n  c.use MyCustomParser\n\n  # Adapter\n  c.use Faraday::Adapter::NetHttp\nend\n```\n\n### Caching\n\nAgain, using the `faraday_middleware` and `memcached` gems makes it very easy to cache requests and responses.\n\nIn your Gemfile:\n\n```ruby\ngem \"her\"\ngem \"faraday_middleware\"\ngem \"memcached\"\n```\n\nIn your Ruby code:\n\n```ruby\nHer::API.setup url: \"https://api.example.com\" do |c|\n  # Request\n  c.use FaradayMiddleware::Caching, Memcached::Rails.new('127.0.0.1:11211')\n\n  # Response\n  c.use Her::Middleware::DefaultParseJSON\n\n  # Adapter\n  c.use Faraday::Adapter::NetHttp\nend\n\nclass User\n  include Her::Model\nend\n\n@user = User.find(1)\n# GET \"/users/1\"\n\n@user = User.find(1)\n# This request will be fetched from memcached\n```\n\n## Advanced Features\n\nHere’s a list of several useful features available in Her.\n\n### Associations\n\nExamples use this code:\n\n```ruby\nclass User\n  include Her::Model\n  has_many :comments\n  has_one :role\n  belongs_to :organization\nend\n\nclass Comment\n  include Her::Model\nend\n\nclass Role\n  include Her::Model\nend\n\nclass Organization\n  include Her::Model\nend\n```\n\n#### Fetching data\n\nYou can define `has_many`, `has_one` and `belongs_to` associations in your models. The association data is handled in two different ways.\n\n1. If Her finds association data when parsing a resource, that data will be used to create the associated model objects on the resource.\n2. If no association data was included when parsing a resource, calling a method with the same name as the association will fetch the data (providing there’s an HTTP request available for it in the API).\n\nFor example, if there’s association data in the resource, no extra HTTP request is made when calling the `#comments` method and an array of resources is returned:\n\n```ruby\n@user = User.find(1)\n# GET \"/users/1\", response is:\n# {\n#   \"id\": 1,\n#   \"name\": \"George Michael Bluth\",\n#   \"comments\": [\n#     { \"id\": 1, \"text\": \"Foo\" },\n#     { \"id\": 2, \"text\": \"Bar\" }\n#   ],\n#   \"role\": { \"id\": 1, \"name\": \"Admin\" },\n#   \"organization\": { \"id\": 2, \"name\": \"Bluth Company\" }\n# }\n\n@user.comments\n# =\u003e [#\u003cComment id=1 text=\"Foo\"\u003e, #\u003cComment id=2 text=\"Bar\"\u003e]\n\n@user.role\n# =\u003e #\u003cRole id=1 name=\"Admin\"\u003e\n\n@user.organization\n# =\u003e #\u003cOrganization id=2 name=\"Bluth Company\"\u003e\n```\n\nIf there’s no association data in the resource, Her makes a HTTP request to retrieve the data.\n\n```ruby\n@user = User.find(1)\n# GET \"/users/1\", response is { \"id\": 1, \"name\": \"George Michael Bluth\", \"organization_id\": 2 }\n\n# has_many association:\n@user.comments\n# GET \"/users/1/comments\"\n# =\u003e [#\u003cComment id=1\u003e, #\u003cComment id=2\u003e]\n\n@user.comments.where(approved: 1)\n# GET \"/users/1/comments?approved=1\"\n# =\u003e [#\u003cComment id=1\u003e]\n\n# has_one association:\n@user.role\n# GET \"/users/1/role\"\n# =\u003e #\u003cRole id=1\u003e\n\n# belongs_to association:\n@user.organization\n# (the organization id comes from :organization_id, by default)\n# GET \"/organizations/2\"\n# =\u003e #\u003cOrganization id=2\u003e\n```\n\nSubsequent calls to `#comments`, `#role` and `#organization` will not trigger extra HTTP requests and will return the cached objects.\n\n#### Creating data\n\nYou can use the association methods to build new objects and save them.\n\n```ruby\n@user = User.find(1)\n@user.comments.build(body: \"Just a draft\")\n# =\u003e [#\u003cComment body=\"Just a draft\" user_id=1\u003e]\n\n@user.comments.create(body: \"Hello world.\", user_id: 1)\n# POST \"/comments\" with `body=Hello+world.\u0026user_id=1`\n# =\u003e [#\u003cComment id=3 body=\"Hello world.\" user_id=1\u003e]\n```\n\nYou can also explicitly request a new object via the API when using ``build``. This is useful if you're dealing with default attributes.\n\n```ruby\nclass Comment\n  include Her::Model\n  request_new_object_on_build true\nend\n\n@user = User.find(1)\n@user.comments.build(body: \"Just a draft\")\n# GET \"/users/1/comments/new\" with `body=Just+a+draft.`\n# =\u003e [#\u003cComment id=nil body=\"Just a draft\" archived=false user_id=1\u003e]\n```\n\n#### Notes about paths\n\nResources must always have all the required attributes to build their complete path. For example, if you have these models:\n\n```ruby\nclass User\n  include Her::Model\n  collection_path \"organizations/:organization_id/users\"\nend\n\nclass Organization\n  include Her::Model\n  has_many :users\nend\n```\n\nHer expects all `User` resources to have an `:organization_id` (or `:_organization_id`) attribute. Otherwise, calling mostly all methods, like `User.all`, will throw an exception like this one:\n\n```ruby\nHer::Errors::PathError: Missing :_organization_id parameter to build the request path. Path is `organizations/:organization_id/users`. Parameters are `{ … }`.\n```\n\n#### Associations with custom attributes\n\nAssociations can also be made using custom attributes:\n\n```ruby\nclass User\n  include Her::Model\n  belongs_to :owns, class_name: \"Organization\"\nend\n\nclass Organization\n  include Her::Model\n  has_many :owners, class_name: \"User\"\nend\n```\n\n### Validations\n\nHer includes `ActiveModel::Validations` so you can declare validations the same way you do in Rails.\n\nHowever, validations must be triggered manually — they are not run, for example, when calling `#save` on an object, or `#create` on a model class.\n\n```ruby\nclass User\n  include Her::Model\n\n  attributes :fullname, :email\n  validates :fullname, presence: true\n  validates :email, presence: true\nend\n\n@user = User.new(fullname: \"Tobias Fünke\")\n@user.valid? # =\u003e false\n\n@user.save\n# POST \"/users\" with `fullname=Tobias+Fünke` will still be called, even if the user is not valid\n```\n\n### Dirty attributes\n\nHer includes `ActiveModel::Dirty` so you can keep track of the attributes that have changed in an object.\n\n```ruby\nclass User\n  include Her::Model\n\n  attributes :fullname, :email\nend\n\n@user = User.new(fullname: \"Tobias Fünke\")\n@user.fullname_changed? # =\u003e true\n@user.changes # =\u003e { :fullname =\u003e [nil, \"Tobias Fünke\"] }\n\n@user.save\n# POST \"/users\" with `fullname=Tobias+Fünke`\n\n@user.fullname_changed? # =\u003e false\n@user.changes # =\u003e {}\n```\n\nTo update only the modified attributes specify `:send_only_modified_attributes =\u003e true` in the setup.\n\n### Callbacks\n\nYou can add *before* and *after* callbacks to your models that are triggered on specific actions. You can use symbols or blocks.\n\n```ruby\nclass User\n  include Her::Model\n  before_save :set_internal_id\n  after_find { |u| u.fullname.upcase! }\n\n  def set_internal_id\n    self.internal_id = 42 # Will be passed in the HTTP request\n  end\nend\n\n@user = User.create(fullname: \"Tobias Fünke\")\n# POST \"/users\" with `fullname=Tobias+Fünke\u0026internal_id=42`\n\n@user = User.find(1)\n@user.fullname # =\u003e \"TOBIAS FUNKE\"\n```\n\nThe available callbacks are:\n\n* `before_save`\n* `before_create`\n* `before_update`\n* `before_destroy`\n* `after_save`\n* `after_create`\n* `after_update`\n* `after_destroy`\n* `after_find`\n* `after_initialize`\n\n### JSON attributes-wrapping\n\nHer supports *sending* and *parsing* JSON data wrapped in a root element (to be compatible with Rails’ `include_root_in_json` setting), like so:\n\n#### Sending\n\nIf you want to send all data to your API wrapped in a *root* element based on the model name.\n\n```ruby\nclass User\n  include Her::Model\n  include_root_in_json true\nend\n\nclass Article\n  include Her::Model\n  include_root_in_json :post\nend\n\nUser.create(fullname: \"Tobias Fünke\")\n# POST \"/users\" with `user[fullname]=Tobias+Fünke`\n\nArticle.create(title: \"Hello world.\")\n# POST \"/articles\" with `post[title]=Hello+world`\n```\n\n#### Parsing\n\nIf the API returns data wrapped in a *root* element based on the model name.\n\n```ruby\nclass User\n  include Her::Model\n  parse_root_in_json true\nend\n\nclass Article\n  include Her::Model\n  parse_root_in_json :post\nend\n\nuser = User.create(fullname: \"Tobias Fünke\")\n# POST \"/users\" with `fullname=Tobias+Fünke`, response is { \"user\": { \"fullname\": \"Tobias Fünke\" } }\nuser.fullname # =\u003e \"Tobias Fünke\"\n\narticle = Article.create(title: \"Hello world.\")\n# POST \"/articles\" with `title=Hello+world.`, response is { \"post\": { \"title\": \"Hello world.\" } }\narticle.title # =\u003e \"Hello world.\"\n```\n\nOf course, you can use both `include_root_in_json` and `parse_root_in_json` at the same time.\n\n#### ActiveModel::Serializers support\n\nIf the API returns data in the default format used by the\n[ActiveModel::Serializers](https://github.com/rails-api/active_model_serializers)\nproject you need to configure Her as follows:\n\n```ruby\nclass User\n  include Her::Model\n  parse_root_in_json true, format: :active_model_serializers\nend\n\nuser = Users.find(1)\n# GET \"/users/1\", response is { \"user\": { \"id\": 1, \"fullname\": \"Lindsay Fünke\" } }\n\nusers = Users.all\n# GET \"/users\", response is { \"users\": [{ \"id\": 1, \"fullname\": \"Lindsay Fünke\" }, { \"id\": 1, \"fullname\": \"Tobias Fünke\" }] }\n```\n\n#### JSON API support\n\nTo consume a JSON API 1.0 compliant service, it must return data in accordance with the [JSON API spec](http://jsonapi.org/). The general format\nof the data is as follows:\n\n```json\n{ \"data\": {\n  \"type\": \"developers\",\n  \"id\": \"6ab79c8c-ec5a-4426-ad38-8763bbede5a7\",\n  \"attributes\": {\n    \"language\": \"ruby\",\n    \"name\": \"avdi grimm\",\n  }\n}\n```\n\nThen to setup your models:\n\n```ruby\nclass Contributor\n  include Her::JsonApi::Model\n\n  # defaults to demodulized, pluralized class name, e.g. contributors\n  type :developers\nend\n```\n\nFinally, you'll need to use the included JsonApiParser Her middleware:\n\n```ruby\nHer::API.setup url: 'https://my_awesome_json_api_service' do |c|\n  # Request\n  c.use FaradayMiddleware::EncodeJson\n\n  # Response\n  c.use Her::Middleware::JsonApiParser\n\n  # Adapter\n  c.use Faraday::Adapter::NetHttp\nend\n```\n\n### Custom requests\n\nYou can easily define custom requests for your models using `custom_get`, `custom_post`, etc.\n\n```ruby\nclass User\n  include Her::Model\n\n  custom_get :popular, :unpopular\n  custom_post :from_default, :activate\nend\n\nUser.popular\n# GET \"/users/popular\"\n# =\u003e [#\u003cUser id=1\u003e, #\u003cUser id=2\u003e]\n\nUser.unpopular\n# GET \"/users/unpopular\"\n# =\u003e [#\u003cUser id=3\u003e, #\u003cUser id=4\u003e]\n\nUser.from_default(name: \"Maeby Fünke\")\n# POST \"/users/from_default\" with `name=Maeby+Fünke`\n# =\u003e #\u003cUser id=5 name=\"Maeby Fünke\"\u003e\n\nUser.activate(id: 6)\n# POST \"/users/6/activate\"\n# =\u003e #\u003cUser id=6\u003e\n```\n\nYou can also use `get`, `post`, `put` or `delete` (which maps the returned data to either a collection or a resource).\n\n```ruby\nclass User\n  include Her::Model\nend\n\nUser.get(:popular)\n# GET \"/users/popular\"\n# =\u003e [#\u003cUser id=1\u003e, #\u003cUser id=2\u003e]\n\nUser.get(:single_best)\n# GET \"/users/single_best\"\n# =\u003e #\u003cUser id=1\u003e\n```\n\nYou can also use `get_raw` which yields the parsed data and the raw response from the HTTP request. Other HTTP methods are supported (`post_raw`, `put_raw`, etc.).\n\n```ruby\nclass User\n  include Her::Model\n\n  def self.total\n    get_raw(:stats) do |parsed_data, response|\n      parsed_data[:data][:total_users]\n    end\n  end\nend\n\nUser.total\n# GET \"/users/stats\"\n# =\u003e 42\n```\n\nYou can also use full request paths (with strings instead of symbols).\n\n```ruby\nclass User\n  include Her::Model\nend\n\nUser.get(\"/users/popular\")\n# GET \"/users/popular\"\n# =\u003e [#\u003cUser id=1\u003e, #\u003cUser id=2\u003e]\n```\n\n### Custom paths\n\nYou can define custom HTTP paths for your models:\n\n```ruby\nclass User\n  include Her::Model\n  collection_path \"/hello_users/:id\"\nend\n\n@user = User.find(1)\n# GET \"/hello_users/1\"\n```\n\nYou can also include custom variables in your paths:\n\n```ruby\nclass User\n  include Her::Model\n  collection_path \"/organizations/:organization_id/users\"\nend\n\n@user = User.find(1, _organization_id: 2)\n# GET \"/organizations/2/users/1\"\n\n@user = User.all(_organization_id: 2)\n# GET \"/organizations/2/users\"\n\n@user = User.new(fullname: \"Tobias Fünke\", organization_id: 2)\n@user.save\n# POST \"/organizations/2/users\" with `fullname=Tobias+Fünke`\n```\n\n### Custom primary keys\n\nIf your record uses an attribute other than `:id` to identify itself, specify it using the `primary_key` method:\n\n```ruby\nclass User\n  include Her::Model\n  primary_key :_id\nend\n\nuser = User.find(\"4fd89a42ff204b03a905c535\")\n# GET \"/users/4fd89a42ff204b03a905c535\", response is { \"_id\": \"4fd89a42ff204b03a905c535\", \"name\": \"Tobias\" }\n\nuser.destroy\n# DELETE \"/users/4fd89a42ff204b03a905c535\"\n```\n\n### Inheritance\n\nIf all your models share the same settings, you might want to make them children of a class and only include `Her::Model` in that class. However, there are a few settings that don’t get passed to the children classes:\n\n* `root_element`\n* `collection_path` and `resource_path`\n\nThose settings are based on the class name, so you don’t have to redefine them each time you create a new children class (but you still can). Every other setting is inherited from the parent (associations, scopes, JSON settings, etc.).\n\n```ruby\nmodule MyAPI\n  class Model\n    include Her::Model\n\n    parse_root_in_json true\n    include_root_in_json true\n  end\nend\n\nclass User \u003c MyAPI::Model\nend\n\nUser.find(1)\n# GET \"/users/1\"\n```\n\n### Scopes\n\nJust like with ActiveRecord, you can define named scopes for your models. Scopes are chainable and can be used within other scopes.\n\n```ruby\nclass User\n  include Her::Model\n\n  scope :by_role, -\u003e(role) { where(role: role) }\n  scope :admins, -\u003e { by_role('admin') }\n  scope :active, -\u003e { where(active: 1) }\nend\n\n@admins = User.admins\n# GET \"/users?role=admin\"\n\n@moderators = User.by_role('moderator')\n# GET \"/users?role=moderator\"\n\n@active_admins = User.active.admins # @admins.active would have worked here too\n# GET \"/users?role=admin\u0026active=1\"\n```\n\nA neat trick you can do with scopes is interact with complex paths.\n\n```ruby\nclass User\n  include Her::Model\n\n  collection_path \"organizations/:organization_id/users\"\n  scope :for_organization, -\u003e(id) { where(organization_id: id) }\nend\n\n@user = User.for_organization(3).find(2)\n# GET \"/organizations/3/users/2\"\n\n@user = User.for_organization(3).create(fullname: \"Tobias Fünke\")\n# POST \"/organizations/3\" with `fullname=Tobias+Fünke`\n```\n\n### Multiple APIs\n\nIt is possible to use different APIs for different models. Instead of calling `Her::API.setup`, you can create instances of `Her::API`:\n\n```ruby\n# config/initializers/her.rb\nMY_API = Her::API.new\nMY_API.setup url: \"https://my-api.example.com\" do |c|\n  # Response\n  c.use Her::Middleware::DefaultParseJSON\n\n  # Adapter\n  c.use Faraday::Adapter::NetHttp\nend\n\nOTHER_API = Her::API.new\nOTHER_API.setup url: \"https://other-api.example.com\" do |c|\n  # Response\n  c.use Her::Middleware::DefaultParseJSON\n\n  # Adapter\n  c.use Faraday::Adapter::NetHttp\nend\n```\n\nYou can then define which API a model will use:\n\n```ruby\nclass User\n  include Her::Model\n  use_api MY_API\nend\n\nclass Category\n  include Her::Model\n  use_api OTHER_API\nend\n\nUser.all\n# GET \"https://my-api.example.com/users\"\n\nCategory.all\n# GET \"https://other-api.example.com/categories\"\n```\n\n### SSL\n\nWhen initializing `Her::API`, you can pass any parameter supported by `Faraday.new`. So [to use HTTPS](https://github.com/lostisland/faraday/wiki/Setting-up-SSL-certificates), you can use Faraday’s `:ssl` option.\n\n```ruby\nssl_options = { ca_path: \"/usr/lib/ssl/certs\" }\nHer::API.setup url: \"https://api.example.com\", ssl: ssl_options do |c|\n  # Response\n  c.use Her::Middleware::DefaultParseJSON\n\n  # Adapter\n  c.use Faraday::Adapter::NetHttp\nend\n```\n\n## Testing\n\nSuppose we have these two models bound to your API:\n\n```ruby\n# app/models/user.rb\nclass User\n  include Her::Model\n  custom_get :popular\nend\n\n# app/models/post.rb\nclass Post\n  include Her::Model\n  custom_get :recent, :archived\nend\n```\n\nIn order to test them, we’ll have to stub the remote API requests. With [RSpec](https://github.com/rspec/rspec-core), we can do this like so:\n\n```ruby\n# spec/spec_helper.rb\nRSpec.configure do |config|\n  config.include(Module.new do\n    def stub_api_for(klass)\n      klass.use_api (api = Her::API.new)\n\n      # Here, you would customize this for your own API (URL, middleware, etc)\n      # like you have done in your application’s initializer\n      api.setup url: \"http://api.example.com\" do |c|\n        c.use Her::Middleware::FirstLevelParseJSON\n        c.adapter(:test) { |s| yield(s) }\n      end\n    end\n  end)\nend\n```\n\nThen, in your tests, we can specify what (fake) HTTP requests will return:\n\n```ruby\n# spec/models/user.rb\ndescribe User do\n  before do\n    stub_api_for(User) do |stub|\n      stub.get(\"/users/popular\") { |env| [200, {}, [{ id: 1, name: \"Tobias Fünke\" }, { id: 2, name: \"Lindsay Fünke\" }].to_json] }\n    end\n  end\n\n  describe :popular do\n    subject { User.popular }\n    its(:length) { should == 2 }\n    its(:errors) { should be_empty }\n  end\nend\n```\n\nWe can redefine the API for a model as many times as we want, like for more complex tests:\n\n```ruby\n# spec/models/user.rb\ndescribe Post do\n  describe :recent do\n    before do\n      stub_api_for(Post) do |stub|\n        stub.get(\"/posts/recent\") { |env| [200, {}, [{ id: 1 }, { id: 2 }].to_json] }\n      end\n    end\n\n    subject { Post.recent }\n    its(:length) { should == 2 }\n    its(:errors) { should be_empty }\n  end\n\n  describe :archived do\n    before do\n      stub_api_for(Post) do |stub|\n        stub.get(\"/posts/archived\") { |env| [200, {}, [{ id: 1 }, { id: 2 }].to_json] }\n      end\n    end\n\n    subject { Post.archived }\n    its(:length) { should == 2 }\n    its(:errors) { should be_empty }\n  end\nend\n```\n\n## Upgrade\n\nSee the [UPGRADE.md](https://github.com/remiprev/her/blob/master/UPGRADE.md) for backward compatibility issues.\n\n## Her IRL\n\nMost projects I know that use Her are internal or private projects but here’s a list of public ones:\n\n* [tumbz](https://github.com/remiprev/tumbz)\n* [zoho-ruby](https://github.com/errorstudio/zoho-ruby)\n* [crowdher](https://github.com/simonprev/crowdher)\n* [vodka](https://github.com/magnolia-fan/vodka)\n* [webistrano_cli](https://github.com/chytreg/webistrano_cli)\n* [ASMALLWORLD](https://www.asmallworld.com)\n\n## History\n\nI told myself a few months ago that it would be great to build a gem to replace Rails’ [ActiveResource](http://api.rubyonrails.org/classes/ActiveResource/Base.html) since it was barely maintained (and now removed from Rails 4.0), lacking features and hard to extend/customize. I had built a few of these REST-powered ORMs for client projects before but I decided I wanted to write one for myself that I could release as an open-source project.\n\nMost of Her’s core concepts were written on a Saturday morning of April 2012 ([first commit](https://github.com/remiprev/her/commit/689d8e88916dc2ad258e69a2a91a283f061cbef2) at 7am!).\n\n## Maintainers\nThe gem is currently maintained by [@zacharywelch](https://github.com/zacharywelch) and [@edtjones](https://github.com/edtjones).\n\n## Contribute\n\nYes please! Feel free to contribute and submit issues/pull requests [on GitHub](https://github.com/remiprev/her/issues). There’s no such thing as a bad pull request — even if it’s for a typo, a small improvement to the code or the documentation!\n\nSee [CONTRIBUTING.md](https://github.com/remiprev/her/blob/master/CONTRIBUTING.md) for best practices.\n\n### Contributors\n\nThese [fine folks](https://github.com/remiprev/her/contributors) helped with Her:\n\n* [@jfcixmedia](https://github.com/jfcixmedia)\n* [@EtienneLem](https://github.com/EtienneLem)\n* [@rafaelss](https://github.com/rafaelss)\n* [@tysontate](https://github.com/tysontate)\n* [@nfo](https://github.com/nfo)\n* [@simonprevost](https://github.com/simonprevost)\n* [@jmlacroix](https://github.com/jmlacroix)\n* [@thomsbg](https://github.com/thomsbg)\n* [@calmyournerves](https://github.com/calmyournerves)\n* [@luflux](https://github.com/luxflux)\n* [@simonc](https://github.com/simonc)\n* [@pencil](https://github.com/pencil)\n* [@joanniclaborde](https://github.com/joanniclaborde)\n* [@seanreads](https://github.com/seanreads)\n* [@jonkarna](https://github.com/jonkarna)\n* [@aclevy](https://github.com/aclevy)\n* [@stevschmid](https://github.com/stevschmid)\n* [@prognostikos](https://github.com/prognostikos)\n* [@dturnerTS](https://github.com/dturnerTS)\n* [@kritik](https://github.com/kritik)\n\n## License\n\nHer is © 2012-2013 [Rémi Prévost](http://exomel.com) and may be freely distributed under the [MIT license](https://github.com/remiprev/her/blob/master/LICENSE). See the `LICENSE` file.\n","funding_links":[],"categories":["API Builder and Discovery","Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fremi%2Fher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fremi%2Fher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fremi%2Fher/lists"}