{"id":30876349,"url":"https://github.com/howdoicomputer/crib","last_synced_at":"2025-09-08T02:09:15.363Z","repository":{"id":78209484,"uuid":"48289403","full_name":"howdoicomputer/crib","owner":"howdoicomputer","description":":package: A dynamic way of exploring and packaging REST APIs.","archived":false,"fork":false,"pushed_at":"2015-02-24T15:57:59.000Z","size":416,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2023-08-15T10:35:50.601Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://rubygems.org/gems/crib","language":"Ruby","has_issues":false,"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/howdoicomputer.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}},"created_at":"2015-12-19T16:36:57.000Z","updated_at":"2020-09-24T16:52:18.000Z","dependencies_parsed_at":"2023-02-24T11:30:36.720Z","dependency_job_id":null,"html_url":"https://github.com/howdoicomputer/crib","commit_stats":null,"previous_names":[],"tags_count":8,"template":null,"template_full_name":null,"purl":"pkg:github/howdoicomputer/crib","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/howdoicomputer%2Fcrib","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/howdoicomputer%2Fcrib/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/howdoicomputer%2Fcrib/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/howdoicomputer%2Fcrib/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/howdoicomputer","download_url":"https://codeload.github.com/howdoicomputer/crib/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/howdoicomputer%2Fcrib/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274121957,"owners_count":25225801,"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","status":"online","status_checked_at":"2025-09-08T02:00:09.813Z","response_time":121,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2025-09-08T02:09:09.742Z","updated_at":"2025-09-08T02:09:15.331Z","avatar_url":"https://github.com/howdoicomputer.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Crib [![Build Status](https://travis-ci.org/rafalchmiel/crib.svg?branch=master)](https://travis-ci.org/rafalchmiel/crib) [![Coverage Status](https://coveralls.io/repos/rafalchmiel/crib/badge.svg?branch=master)](https://coveralls.io/r/rafalchmiel/crib?branch=master) [![Documentation Status](http://inch-ci.org/github/rafalchmiel/crib.svg?branch=master)](http://inch-ci.org/github/rafalchmiel/crib) [![Code Climate](https://codeclimate.com/github/rafalchmiel/crib/badges/gpa.svg)](https://codeclimate.com/github/rafalchmiel/crib) [![Gem Version](https://badge.fury.io/rb/crib.svg)](http://badge.fury.io/rb/crib)\n**Crib** allows you to dynamically explore and package most REST APIs using an intuitive syntax that resembles a HTTP URI path. It uses [Sawyer](https://github.com/lostisland/sawyer) under the hood so things like authentication and passing certain headers to every request are simple.\n\nIn the below example, we are using the Dribbble API to [get the name of the currently authenticated user](http://developer.dribbble.com/v1/users/#get-the-authenticated-user), then just a single user in order to demonstrate arguments:\n\n```ruby\ndribbble = Crib::API.new('https://api.dribbble.com/v1') do |http|\n  http.authorization 'Bearer', '1aea05cfdbb92294be2fcf63ee11b412fd88c65051bd3144302c30ae8ba18896'\nend\n\nme = dribbble.user\nme._get.name\n # =\u003e \"Rafal Chmiel\"\n\ndan = dribbble.users('simplebits')\ndan._get.name\n # =\u003e \"Dan Cederholm\"\n```\n\nIf you're interested in building a REST API client you can use the `Crib::Resource` class which provides a DSL. Here's an interesting example of this functionality demonstrated using the [GitHub API](https://developer.github.com/v3/):\n\n```ruby\nclass GitHub \u003c Crib::Resource\n  define 'https://api.github.com' do |http|\n    http.headers[:user_agent] = 'crib'\n  end\n\n  action :user do |user|\n    get api.users(user)\n  end\n\n  action :issues do |repo, options = {}|\n    get api.repos(*repo.split('/')).issues, options\n  end\nend\n\ngithub = GitHub.new\n\nme = github.user('rafalchmiel')\nme.name\n # =\u003e \"Rafal Chmiel\"\n\nrails_issues = github.issues('rails/rails', per_page: 2)\nrails_issues.count\n # =\u003e 2\n```\n\n## Philosophy\nThe aim of this project is to be able to explore and package most REST APIs using a straightforward syntax and an intuitive DSL. **Crib** uses Sawyer to simplify requests and easily read responses, which means there's no need to overcomplicate response handling and middleware.\n\n### Inspiration\n**Crib** takes a lot of its inspiration from [Blanket](https://github.com/inf0rmer/blanket). The aim of this project is not to compete with Blanket, but to produce a very different flavour of it. Some parts of the documentation and code are borrowed from [Octokit](https://github.com/octokit/octokit.rb) and [Resource Kit](https://github.com/digitalocean/resource_kit).\n\n## Quick Start\nInstall the latest stable version of **Crib** via RubyGems:\n\n```bash\n$ gem install crib\n```\n\nAlternatively add it to your `Gemfile` and run `bundle install`:\n\n```ruby\ngem 'crib'\n```\n\nAt the top of your code, require the library:\n\n```ruby\nrequire 'crib'\n```\n\n### Defining an API\nIn order to define a REST API in **Crib** you need to initialise a new `Crib::API` class passing it the endpoint, any optional Sawyer options, and an optional block for Sawyer to yield connection options to.\n\nAs Sawyer is built on top of [Faraday](https://github.com/lostisland/faraday), the options in this block are yielded to a `Faraday::Connection` which means you can set headers, authentication stuff, and middleware like this:\n\n```ruby\ndribbble = Crib::API.new('https://api.dribbble.com/v1') do |http|\n  http.headers[:user_agent] = 'crib'\n  http.authorization 'Bearer', '1aea05cfdbb92294be2fcf63ee11b412fd88c65051bd3144302c30ae8ba18896'\n  http.response :logger # take note of this, it logs all requests in the following examples\nend\n\ndribbble.users('simplebits')._get.id\n # I, [2015-01-02T14:59:20.183319 #6990]  INFO -- : get https://api.dribbble.com/v1/users/simplebits\n # D, [2015-01-02T14:59:20.183436 #6990] DEBUG -- request: User-Agent: \"crib\"\n # Authorization: \"Bearer 1aea05cfdbb92294be2fcf63ee11b412fd88c65051bd3144302c30ae8ba18896\"\n # I, [2015-01-02T14:59:20.183742 #6990]  INFO -- Status: 200\n # D, [2015-01-02T14:59:20.183890 #6990] DEBUG -- response: server: \"nginx\"\n # date: \"Fri, 02 Jan 2015 14:59:20 GMT\"\n # content-type: \"application/json; charset=utf-8\"\n # (...)\n #\n # =\u003e 1\n```\n\nDefining an API using the DSL is almost the same: just replace `Crib::API.new` with `define` (the definition will be accessible via `#api`):\n\n```ruby\nclass Dribbble \u003c Crib::Resource\n  define 'https://api.dribbble.com/v1' do |http|\n    http.headers[:user_agent] = 'crib'\n    http.authorization 'Bearer', '1aea05cfdbb92294be2fcf63ee11b412fd88c65051bd3144302c30ae8ba18896'\n    http.response :logger\n  end\n\n  # ...\nend\n```\n\n### Constructing Requests\n**Crib** uses `#method_missing` for `Crib::API` and `Crib::Request` instances. This means when you call, for example, `#users('rafalchmiel')` on your API a new instance of `Crib::Request` is created and returned (with the private instance variable `@uri` set to `\"users/rafalchmiel\"`). This allows you to chain methods together, because these new instances create new instances of their own class, each time joining together their URIs. For example:\n\n```ruby\n# GET /users/:user/followers\ndribbble.users('rafalchmiel').followers\n # =\u003e \u003cCrib::Request @api=#\u003cCrib::API @_agent=\u003cSawyer::Agent https://api.dribbble.com/v1\u003e, @_last_response=#\u003cSawyer::Response 200 @rels={} @data={...}\u003e, @uri=\"users/rafalchmiel/followers\"\u003e\n```\n\nAlternatively you can use `#send` passing it a String containing a path. For example the above can be written as:\n\n```ruby\ndribbble.send('users/rafalchmiel/followers')\n # =\u003e \u003cCrib::Request @api=#\u003cCrib::API @_agent=\u003cSawyer::Agent https://api.dribbble.com/v1\u003e, @_last_response=#\u003cSawyer::Response 200 @rels={} @data={...}\u003e, @uri=\"users/rafalchmiel/followers\"\u003e\n```\n\nConstructing requests when using the DSL is exactly the same. After you defined your API, your instance of `Crib::API` will be stored in the class that inherits `Crib::Resource`. You can get that instance using `#api`:\n\n```ruby\nclass Dribbble \u003c Crib::Resource\n  # ...\n\n  action :user do\n    get api.user # currently authenticated user\n  end\nend\n```\n\n### Consuming Resources\nSo far we have only *constructed* a request. It's time to execute the request with a HTTP verb of your choice. You can call any of the HTTP verbs (prefixed with an underscore to keep the namespace unpoluted) on any `Crib::Request` instance you constructed. All these HTTP verb methods take an optional parameter and header Hash also (pass parameters in `:query` Hash and headers in `:headers` Hash). For example:\n\n```ruby\nfollowers = dribbble.users('rafalchmiel').followers\nfollowers._get\n # I, [2015-01-02T15:54:00.196740 #6990]  INFO -- : get https://api.dribbble.com/v1/users/rafalchmiel/followers\n # (...)\n #\n # =\u003e [{:id=\u003e3178237 (...)}]\n\ndribbble.shots._get(query: { sort: :comments })\n # I, [2015-01-02T16:44:00.989936 #8284]  INFO -- : get https://api.dribbble.com/v1/shots?sort=comments\n # (...)\n #\n # =\u003e [{:id=\u003e1865147 (...)}]\n```\n\nAll responses are `Sawyer::Resource` objects which provide dot notation and `[]` access for fields returned in the API response:\n\n```ruby\nme = dribbble.users('rafalchmiel')._get\nme.name\n # =\u003e \"Rafal Chmiel\"\nme.fields\n # =\u003e #\u003cSet: {:id, :name, :username, :html_url, :avatar_url, :bio, :location, :links, :buckets_count, :followers_count, :followings_count, :likes_count, :projects_count, :shots_count, :teams_count, :type, :pro, :buckets_url, :followers_url, :following_url, :likes_url, :projects_url, :shots_url, :teams_url, :created_at, :updated_at}\u003e\nme[:username]\n # =\u003e \"RafalChmiel\"\nme.rels[:followers].href\n # =\u003e \"https://api.dribbble.com/v1/users/97203/followers\"\n```\n\n*Note:* URL fields are culled into a separate `.rels` collection for easier [Hypermedia](#hypermedia-agent) support.\n\nConsuming requests is similar when using the DSL. You define *actions* which have a name and a block. They are then created as instance methods of the class that inherited `Crib::Resource`. The return value of the block you pass to `.action` is the return value of the method it defines, most likely a `Sawyer::Response`.\n\nIn order to keep your DSL code as clean as possible, Crib provides HTTP verb methods so that you don't have to call `#_get`, `#_post`, etc. every single time you define an action and want to request a resource. The DSL methods include: `.get`, `.post`, `.put`, `.patch`, `.delete`, and `.head`. They take a `Crib::Request` object and an optional *options* Hash (just like the verb methods prefixed with an underscore).\n\nA great example of all this can be presented using this example:\n\n```ruby\nclass GitHub \u003c Crib::Resource\n  # ...\n\n  action :issues do |repo, options = {}|\n    get api.repos(*repo.split('/')).issues, options\n  end\nend\n\ngithub = GitHub.new\ngithub.issues('rails/rails', per_page: 2)\n # =\u003e ...\n```\n\nIf you need to, you can override the API definition set in the class with `.define` and instead pass your own, when initialising your class:\n\n```ruby\ncustom_github = GitHub.new(Crib::API.new('https://api.github.dev'))\n```\n\n#### Accessing HTTP Responses\nWhile all HTTP verb methods (`#_get`, `#_post`, etc.) return a `Sawyer::Resource` object, sometimes you may need access to the raw HTTP response headers. You can access the last HTTP response like this:\n\n```ruby\nlast_response = dribbble._last_response\n # =\u003e #\u003cSawyer::Response 200 @rels={} @data={:id=\u003e97203 (...)}\u003e\nlast_response.headers[:last_modified]\n # =\u003e \"Fri, 02 Jan 2015 14:35:08 GMT\"\n```\n\nWhen using the DSL, you can access the most recent response using `#last_response`:\n\n```ruby\nclass Dribbble \u003c Crib::Resource\n  # ...\nend\n\ndribbble = Dribbble.new\ndribbble.user\n # =\u003e ...\n\ndribbble.last_response\n # =\u003e #\u003cSawyer::Response 200 @rels={} @data={:id=\u003e97203 (...)}\u003e\n```\n\n#### Hypermedia Agent\n**Crib** is [hypermedia](http://en.wikipedia.org/wiki/Hypermedia)-enabled. Under the hood, **Crib** uses Sawyer, a hypermedia client built on Faraday.\n\nResources returned by the underscore-prefixed HTTP methods contain not only data but hypermedia link relations:\n\n```ruby\nme = dribbble.users('rafalchmiel')._get\n\nme.rels\n # =\u003e {:html_url=\u003e\"https://dribbble.com/RafalChmiel\",\n #   :avatar_url=\u003e\n #   \"https://d13yacurqjgara.cloudfront.net/users/97203/avatars/normal/profile-icon-margin-transparent.png?1385898916\",\n #   :buckets_url=\u003e\"https://api.dribbble.com/v1/users/97203/buckets\",\n #   :followers_url=\u003e\"https://api.dribbble.com/v1/users/97203/followers\",\n #   :following_url=\u003e\"https://api.dribbble.com/v1/users/97203/following\",\n #   :likes_url=\u003e\"https://api.dribbble.com/v1/users/97203/likes\",\n #   :projects_url=\u003e\"https://api.dribbble.com/v1/users/97203/projects\",\n #   :shots_url=\u003e\"https://api.dribbble.com/v1/users/97203/shots\",\n #   :teams_url=\u003e\"https://api.dribbble.com/v1/users/97203/teams\"}\n\n# Get the followers 'rel', returned from the API as 'followers_url'\nme.rels[:followers].href\n # =\u003e \"https://api.dribbble.com/v1/users/97203/followers\"\n\nfollowers = me.rels[:followers].get.data\nfollowers.first.follower.name\n # =\u003e \"Kevin Halley\"\n```\n\nWhen processing API responses, all `*_url` attributes are culled in to the link relations collection. Any `url` attribute becomes `.rels[:self]`.\n\n## Supported Ruby Versions\n**Crib** aims to support and is [tested against](https://travis-ci.org/rafalchmiel/crib) the following Ruby implementations:\n\n  - 2.2.0\n  - 2.1.5\n  - 2.0.0-p598\n  - 1.9.3-p551\n\nIf something doesn't work on one of these Ruby versions, it's a bug. **Crib** may inadvertently work (or seem to work) on other Ruby implementations, however support will only be provided for the versions listed above.\n\nIf you would like **Crib** to support another Ruby version, you may volunteer to be a maintainer. Being a maintainer entails making sure all tests run and pass on that implementation. When something breaks on your implementation, you will be responsible for providing patches in a timely fashion. If critical issues for a particular implementation exist at the time of a major release, support for that Ruby version may be dropped.\n\n## Development\nIf you want to hack on **Crib** locally, we try to make [bootstrapping the project](http://wynnnetherland.com/linked/2013012801/bootstrapping-consistency) as painless as possible. Just clone and run:\n\n```bash\n$ script/bootstrap\n```\n\nThis will install project dependencies and get you up and running. If you want to run a Pry console to poke on **Crib**, you can crank one up with:\n\n```bash\n$ script/console\n```\n\nUsing the scripts in `./script` instead of `bundle exec rspec`, `bundle console`, etc. ensures your dependencies are up-to-date.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhowdoicomputer%2Fcrib","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhowdoicomputer%2Fcrib","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhowdoicomputer%2Fcrib/lists"}