Simple REST API Provider implements an abstract which allows you to make a requests to REST API, get a response and map it to your class object (RestApiProvider::Resource).
This gem was not published to RubyGems.org so far, so in your Gemfile use:
gem 'rest_api_provider', git: 'git@github.com:madeinussr/rest_api_provider.git' bundle install
then either create a proper config file (in /config/initializers folder if you use Ruby on Rails) or just call that code:
RestApiProvider.configure do |config|
config.api_root = 'https://api.example.com'
# if you need to include an Authorization header in every request
config.auth_token = 'Basic XxXxxXxx='
endYou can define a simple Resource like this:
class TestResource < RestApiProvider::Resource
resource_path '/test_resources/:slug'
# 'application/json' - default
content_type 'application/x-www-form-urlencoded'
# include only fields that you want to map to object
field :slug
field :title
field :description
endYou can define an explicit field type and a default value:
class TestResource < RestApiProvider::Resource
resource_path '/test_resources/:slug'
field :slug
field :title, type: String, default: 'Hello World!'
field :description
endSupported field types:
- String
- Integer
- Fixnum
- Bignum
- Float
- Date
- Time
- Array
- Hash
and...
- your custom classes
Consider slug as an identifier of the particular Resource item. You can define included Resources like this:
class SubResource < RestApiProvider::Resource
resource_path '/test_resources/:resource_slug/sub_resources/:subresource_slug'
field :resource_slug
field :subresource_slug
field :title
field :description
endThere is the list of supported (so far) ActiveRecord-like validations:
- presence
- length
- numericality
- minimum
- maximum
- &block
class TestResource < RestApiProvider::Resource
resource_path '/test_resources/:slug'
field :slug
field :number
field :description
validates :slug, presence: true
validates :number, numericality: true
endYou can define relations between Resources. Relations implementation based on the HATEOAS principle. Supported relations:
- belongs_to
- has_one
- has_many
Before using relations you might configure HATEOAS attributes in config (if it is differ from default 'links'):
RestApiProvider.configure do |config|
config.api_root = 'https://api.test.com:9999'
config.verify_ssl = false
config.auth_token = "Basic #{Rails.application.secrets.basic_auth_token}"
config.hateoas_links = 'refs'
endYou can define a relation in Rails-like way:
class Book < RestApiProvider::Resource
belongs_to :author, rel: 'lib:author'
end
class Author < RestApiProvider::Resource
has_many :books, rel: 'lib:books'
endby default, rel is equal to relation name
class Book < RestApiProvider::Resource
belongs_to :author
end
Book.relations
>> {:type=>:one2one, :rel=>"author"}If you need to use relation name differ from resource class name, you can specify target type:
class Book < RestApiProvider::Resource
belongs_to :author, rel: 'lib:author'
end
class Author < RestApiProvider::Resource
has_many :writings, rel: 'lib:books', type: Book
endSometimes it worth to specify a source data path (for example: rel href guide you to paged data: {data:[obj, obj, obj...], paging:{...}}
In this case you can define a data path like this:
class Book < RestApiProvider::Resource
belongs_to :author, rel: 'lib:author'
end
class Author < RestApiProvider::Resource
has_many :books, rel: 'lib:books', data_path: '/data'
endBy default, related resources requested from API each time, but you can switch on the ability to store (cache) previous request result, then provide 'true' attribute to fetch related resource from cache (if you sure that resource was not modified):
class Book < RestApiProvider::Resource
enable_relations_caching
belongs_to :author, rel: 'lib:author'
end
class Author < RestApiProvider::Resource
has_many :books, rel: 'lib:books', data_path: '/data'
end
book = Book.new
Book.relations_caching_enabled? #true
book.author(true) #will be requested from API, because cache is empty at the moment
book.author #will be requested from API, because attribute wasn't provided
book.author(true) #will be fetched from cacheEach relation calling without 'true' attribute will refresh cache data.
There are some pre-defined request methods (name=>http_verb): API_METHODS = {all: 'get', grouped: 'get', find: 'get', create: 'post', update: 'put', destroy: 'delete'} You can use them in this way (slugs are based on a Resource path)
TestResource.find slugs:{slug_1: 'qwe', slug_2: 'rty'}, params: {}, body: {}, headers: {} - params: {} - url querystring-params
- body: {} - request's body
- headers: {} - request's headers
Default methods has some limitations:
- their target element (response source element) always root
- .all result - Array object
- .grouped result - Hash object
- .find, .create, .update, .destroy result - itself object
You always can declare your own custom methods http_verb method_name, custom_path
class TestResource < RestApiProvider::Resource
resource_path '/test_resources/:slug'
field :slug
field :number
field :description
get :custom_get, '/some_resource/:some_slug/some_method'
post :custom_post, '/some_resource/:some_slug/some_method'
put :custom_put, '/some_resource/:some_slug/some_method'
delete :custom_delete, '/some_resource/:some_slug/some_method'
endYou can specify mapping result's class (Hash or Array, by default - your Resource class itself):
class TestResource < RestApiProvider::Resource
resource_path '/test_resources/:slug'
field :slug
field :number
get :custom_get, '/some_resource/:some_slug/some_method', result: Hash
endYou're free to specify data source element path for mapping (by default - root) if your Resource data is not defined in the response's root:
class TestResource < RestApiProvider::Resource
resource_path '/test_resources/:slug'
field :slug
field :number
get :custom_get, '/some_resource/:some_slug/some_method', result: Hash, data_path: '/data/sub_data'
endThe same usage:
TestResource.custom_get slugs:{some_slug: 'qwe'}, params: {}, body: {}, headers: {}.resource_path defines the default Resource path. But you always can declare a custom path for a particular method.
class TestResource < RestApiProvider::Resource
resource_path '/test_resources/:slug'
field :slug
field :number
field :description
get :custom_method, '/some_resource/:some_slug/some_method'
endIf you've not specified the resource_path it would be set as /resource_name_pluralised/:slug
This gem awaits that request is a JSON. Actually, methods .all, .find etc. and custom methods returns a mapped result. So far you can get a mapping JSON to:
- object
- Array
- Hash
If a response body is blank, then ::Mapper provides ::ApiResponse class object.
- escape/unescape HTML fields
- async requests
- cache