{"id":26091669,"url":"https://github.com/intercom/requisite","last_synced_at":"2025-08-23T01:06:01.468Z","repository":{"id":22923126,"uuid":"26272085","full_name":"intercom/requisite","owner":"intercom","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-16T16:08:20.000Z","size":41,"stargazers_count":19,"open_issues_count":2,"forks_count":1,"subscribers_count":195,"default_branch":"master","last_synced_at":"2025-06-16T17:38:23.354Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/intercom.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":null,"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,"zenodo":null}},"created_at":"2014-11-06T13:53:25.000Z","updated_at":"2025-06-16T16:08:24.000Z","dependencies_parsed_at":"2025-04-12T06:43:04.222Z","dependency_job_id":"5c148e20-b674-4f4d-afc9-ac3fc639aa0e","html_url":"https://github.com/intercom/requisite","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/intercom/requisite","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/intercom%2Frequisite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/intercom%2Frequisite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/intercom%2Frequisite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/intercom%2Frequisite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/intercom","download_url":"https://codeload.github.com/intercom/requisite/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/intercom%2Frequisite/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271727502,"owners_count":24810561,"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-08-22T02:00:08.480Z","response_time":65,"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-03-09T10:22:52.185Z","updated_at":"2025-08-23T01:06:01.439Z","avatar_url":"https://github.com/intercom.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Requisite\n\nRequisite is an elegant way of strongly defining request and response models for serialization. How nice would it be if you could do:\n\n```ruby\ndef create\n  api_user = ApiRequestUser.new(params)\n  user = User.create(api_user.to_hash)\n  render json: ApiResponseUser.new(user).to_json\nend\n```\n\nWithout worrying about strong parameters, type safety and keeping a consistent API?\n\n## Usage\n\n```ruby\nrequire 'requisite'\n```\n\n## ApiModel\n\nApiModels are the primary way of using Requisite, they represent a model defined as part of an API. Attributes can be listed within a `serialized_attributes` block, with the format `\u003cattribute-type\u003e \u003cattribute-name\u003e \u003coptions\u003e`.\n\n|   method   |                                                                                                               behaviour                                                                                                               |\n| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| attribute  | The attribute with the given name will be looked up on the model, nil if not found. If a method with the same name exists on the UserResponse object it will be called for a value instead. Can take several options. Aliased to `a`. |\n| attribute! | as attribute, but raises an error if not found on model. Aliased to `a!`.                                                                                                                                                             |\n\nApiModels can be constructed from other objects, or from Hashes (like those you might find in _params_). The helper method `attribute_from_model(:attribute_name)` gives access that will work with either.\n\nThese objects have methods to access, and can be serialized back to a Hash (post-transformation; with non-listed parameters removed), or directly to json.\n\n```ruby\nclass UserApiModel \u003c Requisite::ApiModel\n  serialized_attributes do\n    attribute! :id\n    attribute! :username\n    attribute :real_name\n  end\n\n  # method with the name of of an attribute will be called to calculate the mapped value\n  def real_name\n    \"#{attribute_from_model(:first_name)} #{attribute_from_model(:last_name)}\"\n  end\nend\n\ncurrent_user = User.new(:id =\u003e 5, :first_name =\u003e 'Jamie', :last_name =\u003e 'Osler', :username =\u003e 'josler')\nuser = UserApiModel.new(current_user)\nuser.username\n# =\u003e 'josler'\nuser.real_name\n# =\u003e 'Jamie Osler'\nuser.to_hash\n# =\u003e { :id =\u003e 5, :real_name =\u003e 'Jamie Osler', :username =\u003e 'josler' }\nuser.to_json\n# =\u003e \"{\\\"id\\\":5,\\\"real_name\\\":\\\"Jamie Osler\\\",\\\"username\\\":\\\"josler\\\"}\"\n```\n\n`nil` values are not returned in the response, unless `to_hash(show_nil: true)` or `to_json(show_nil: true)` are requested.\n\nErrors are thrown when a required attribute is not present:\n\n```ruby\nUserApiModel.new({:id =\u003e 5, :first_name =\u003e 'Jamie', :last_name =\u003e 'Osler'}).to_hash\n# =\u003e Requisite::NotImplementedError: 'username' not found on model\n```\n\n#### Options\n\nThere are several options that can be used with ApiModel attributes:\n\n|    option   |                                                               behaviour                                                               |\n| ----------- | ------------------------------------------------------------------------------------------------------------------------------------- |\n| default     | `value` will be used as a default if the attribute is not found. Not available for `attribute!`                                       |\n| stringify   | `.to_s` will be called on `value`                                                                                                     |\n| rename      | The returned value will be sourced from the model's `value` attribute                                                                 |\n| type        | Raises error if value does not match given type. Works on the model's value prior to stringification and renaming. Nils are excluded. |\n| scalar_hash | Attribute is a hash with only scalar values permitted - Numeric, String, TrueClass and FalseClass types.                                                                               |\n| typed_hash  | Attribute is a typed hash, with `value` a hash specifying a mapping of sub-attribute to types.                                        |\n| typed_array | Attribute is a typed array, with `value` specifying the type of elements within the array                                             |\n\nThey can also be combined.\n\nExample:\n\n```ruby\nclass UserApiModel \u003c Requisite::ApiModel\n  serialized_attributes do\n    attribute :id, stringify: true\n    attribute :custom_attributes, rename: :custom_data\n    attribute :is_awesome, default: true\n    attribute :awesome_score, rename: :score, stringify: true, default: 9001\n    attribute :age, type: Integer,\n    attribute :tired, type: Requisite::Boolean\n  end\nend\n\ncurrent_user = User.new(:id =\u003e 5, :custom_data =\u003e [ {:number_events =\u003e 4} ], :age =\u003e 26)\nUserApiModel.new(current_user).to_json\n# =\u003e \"{\\\"id\\\":\\\"5\\\",\\\"custom_attributes\\\":[{\\\"number_events\\\":4}],\\\"is_awesome\\\":true,\\\"awesome_score\\\":\\\"9001\\\",\\\"age\\\":26}\"\n```\n\nThe `Requisite::Boolean` type will match `TrueClass` and `FalseClass`.\n\n#### Nested Structure Support\n\nNested structure support only applies one level deep; beyond that we recommend you use a nested ApiModel that's well structured.\n\n##### Hashes\n\nApiModels support nested hashes in two forms; specifying that a Hash should contain only Scalar (Numeric, String and Boolean) values, or a nested hash of a typed attributes.\n\nWith scalar hashes, any scalar value is permitted:\n\n```ruby\nclass UserApiModel \u003c Requisite::ApiModel\n  serialized_attributes do\n    attribute :data, scalar_hash: true\n  end\nend\n\nUserApiModel.new(:data =\u003e {:is_awesome =\u003e true, :score =\u003e 9001, :name =\u003e 'Jamie'}).to_hash\n# =\u003e { :data =\u003e {:is_awesome =\u003e true, :score =\u003e 9001, :name =\u003e 'Jamie'} }\n```\n\nNon-scalar values will raise a `Requisite::BadTypeError`. Empty scalar hash attributes are returned as `{}`.\n\nWith typed hashes, only values specified with a type are permitted:\n\n```ruby\nclass UserApiModel \u003c Requisite::ApiModel\n  serialized_attributes do\n    attribute :data, typed_hash: { is_awesome: Requisite::Boolean, score: Integer, name: String  }\n  end\nend\n\nUserApiModel.new(:data =\u003e {:is_awesome =\u003e true, :score =\u003e 9001, :name =\u003e 'Jamie'}).to_hash\n# =\u003e { :data =\u003e {:is_awesome =\u003e true, :score =\u003e 9001, :name =\u003e 'Jamie'} }\n```\n\nNote that setting the type to the provided `Requisite::Boolean` permits `TrueClass` and `FalseClass` values.\n\nFields within a fixed hash that are not listed as permitted will be omitted (even with attribute! their presence will not raise an error).\n\nFields with the wrong data type will result in a `Requisite::BadTypeError` being raised. Empty typed hash attributes are returned as `{}`.\n\n##### Arrays\n\nTyped arrays are supported; arrays must be all of one type:\n\n```ruby\nclass UserApiModel \u003c Requisite::ApiModel\n  serialized_attributes do\n    attribute :ids, typed_array: String\n  end\nend\n\nUserApiModel.new(:ids =\u003e ['x123D', 'u71d', '96yD']).to_hash\n# =\u003e { :ids =\u003e ['x123D', 'u71d', '96yD'] }\n```\n\nArray values not corresponding to the correct type will raise a `Requisite::BadTypeError`. Empty Array attributes will be returned as `[]`.\n\n#### Advanced Nested Structures\n\nTo work with advanced nested structures, we recommend you create a method with the attribute name that will be called, and use another ApiModel to perform validation, for example:\n\n```ruby\nclass ApiUser \u003c Requisite::ApiModel\n  serialized_attributes do\n    attribute :id, type: String\n    attribute :company\n  end\n\n  # ApiCompany object handles its' own validation\n  def company\n    ApiCompany.new(attribute_from_model(:company)).to_hash\n  end\nend\n```\n\n#### Preprocess Request\n\nA `preprocess_model` method can be defined to carry out any required steps before the model is processed, e.g.:\n\n```ruby\nclass ApiUser \u003c Requisite::ApiModel\n  serialized_attributes do\n    attribute :id, type: String\n    attribute :email, type: String\n  end\n\n  # preprocess to check we have an identifier for the user\n  def preprocess_model\n    identifier = attribute_from_model(:id)\n    identifier ||= attribute_from_model(:email)\n    raise IdentifierNotFoundError unless identifier\n  end\nend\n```\n\n#### Around each attribute\n\nAn `around_each_attribute` method can be defined to wrap each attribute fetch in a block. This can be useful for instrumenting processing on a per attribute basis.\n\n```ruby\nclass ApiUser \u003c Requisite::ApiModel\n  serialized_attributes do\n    attribute :id, type: String\n    attribute :email, type: String\n  end\n\n  def around_each_attribute(name, \u0026block)\n    start = Time.now\n    yield\n    end = Time.now\n    puts \"Fetching #{name} took #{end - start}\"\n  end\nend\n```\n\n#### Thanks\n\nStrongly inspired by the work done in the [mutations gem](https://github.com/cypriss/mutations), and with [restpack_serializer](https://github.com/RestPack/restpack_serializer), as well as some of the patterns laid out in Robert Martin's demonstrations of [clean architecture](http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintercom%2Frequisite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fintercom%2Frequisite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintercom%2Frequisite/lists"}