# Terrain [](https://travis-ci.org/scttnlsn/terrain) Opinionated toolkit for building CRUD APIs with Rails ## Install Add Terrain to your Gemfile: ```ruby gem 'terrain' ``` ## Usage * [Error handling](#error-handling) * [Resources](#resources) * [Authorization](#authorization) * [Serialization](#serialization) * [Querying](#querying) * [Filtering](#filtering) * [Ordering](#ordering) * [Pagination](#pagination) * [Relationships](#relationships) * [CRUD operations](#crud-operations) * [Config](#config) ### Error handling ```ruby class ExampleController < ApplicationController include Terrain::Errors end ``` Rescues the following errors: * `ActiveRecord::AssociationNotFoundError` (400) * `Pundit::NotAuthorizedError` (403) * `ActiveRecord::RecordNotFound` (404) * `ActionController::RoutingError` (404) * `ActiveRecord::RecordInvalid` (422) JSON responses are of the form: ```json { "error": { "key": "type_of_error", "message": "Localized error message", "details": "Optional details" } } ``` To rescue a custom error with a similar response: ```ruby class ExampleController < ApplicationController include Terrain::Errors rescue_from MyError, with: :my_error private def my_error error_response(:type_of_error, 500, { some: :details }) end end ``` ### Resources Suppose you have an `Example` model with `foo`, `bar`, and `baz` columns. ```ruby class ExampleController < ApplicationController include Terrain::Resource resource Example, permit: [:foo, :bar, :baz] end ``` This sets up the typical resourceful Rails controller actions. Note that **you'll still need to setup corresponding routes**. #### Authorization Authorization is handled by [Pundit](https://github.com/elabs/pundit). If the policy class for a given resource exists, each controller action calls the policy before proceeding with the operation. Authorization expects a `current_user` controller method to exist (otherwise `nil` is used as the `pundit_user`). #### Serialization via [ActiveModelSerializers](https://github.com/rails-api/active_model_serializers) #### Querying Records of a given resource are queried by requesting the `index` action. ##### Filtering Queries are scoped to the results returned from the `resource_scope` method. By default this returns all records, however, you can override it to further filter the results (i.e. based on query params, nested route params, etc.): ```ruby class ExampleController < ApplicationController include Terrain::Resource resource Example, permit: [:foo, :bar, :baz] private def resource_scope scope = super scope = scope.where(foo: params[:foo]) if params[:foo].present? scope end end ``` ##### Ordering You can pass an `order` param to reorder the response records. Specify a comma-separated list of fields and prefix the field with a `-` for descending order: ```ruby # corresponds to Example.order('foo', 'bar desc') get :index, order: 'foo,-bar' ``` ##### Pagination To request a range of records, specify the range in an HTTP header: ```ruby # Request the first 10 records get :index, {}, { 'Range' => '0-9' } ``` All responses include a `Content-Range` header that specifies the exact range returned as well as a total count of records. i.e. ``` Content-Range: 0-9/100 ``` You can also pass open ended ranges such as `10-` (i.e. skip the first 10 records). ##### Relationships No model relationships are serialized in the response by default. To specify the set of relationships to be embedded in the response, pass a comma-separated list of relationships in the `include` param. As an example, suppose we're querying for posts which each have many tags and belong to an author. We could embed those relationships with the following `include` param: ```ruby get :index, include: 'author,tags' ``` Suppose now that the author also has a profile relationship. We could include the author, author profile and tags by passing: ```ruby get :index, include: 'author.profile,tags' ``` Included relationships are automatically preloaded via the ActiveRecord `includes` method. The `include` param is also supported in `show` actions. #### CRUD operations You may need an action to perform additional steps beyond simple persistence. There are methods for performing CRUD operations that can be overridden (shown below with their default implementation): ```ruby class ExampleController < ApplicationController include Terrain::Resource resource Example, permit: [:foo, :bar, :baz] private def create_record resource.create!(permitted_params) end def update_record(record) record.update_attributes!(permitted_params) record end def destroy_record(record) record.delete end end ``` ### Config ```ruby Terrain.configure do |config| # Maximum number of records returned config.max_records = Float::INFINITY end ```