The ioki client is made to conveniently use all of Ioki's several APIs and parse the results into ruby objects to be used in other ruby code.
Add this line to your application's Gemfile:
gem 'ioki-ruby'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install ioki-ruby
It's recommended to configure using shared git hooks from .git-hooks
by running:
git config core.hooksPath .git-hooks
Basic usage of a Ioki::Client
platform_client = Ioki::PlatformClient.new
providers = platform_client.providers
# returns a list of Ioki::Model::Platform::Provider instances
products = platform_client.products
# returns a list of Ioki::Model::Platform::Product instances
stations = platform_client.stations(products.first, paginate: true)
# stations are a scoped endpoint within products, so the interface requires
# either a product or a product id as the first parameter.
# This call will then fetch the index of stations. Use `paginate: true` to
# receive pagination information in the response. Otherwise, only the data of
# the first page is returned.
unless stations.meta.last_page
next_page = stations.meta.page + 1
stations = platform_client.stations(products.first, params: {page: next_page}, paginate: true)
end
new_station = Ioki::Model::Platform::Station.new(
location_name: 'Test',
station_type: 'virtual',
lat: stations.data.first.lat,
lng: stations.data.first.lng
)
created_station = platform_client.create_station(products.first, new_station)
# created_station has an id. it is a new model instance and has nothing to do
# with the new_station that got passed in - it is simply parsed back from
# the response body internally.
platform_client.delete_station(products.first, created_station)
# will delete the formerly created station
stations = platform_client.stations(products.first, auto_paginate: true)
# This example shows auto_pagination, which keeps calling the index until the
# last page was fetched. Bear in mind, that auto_pagination might be extremely
# expensive.
first_station = stations.first
See spec/ioki/examples
for more examples.
If a project requires only a specific client setup, one can set the defaults on
the config (Ioki.config) via calling Ioki.configure
; for example within a Rails
initializer you could do it like this:
# frozen_string_literal: true
Rails.application.reloader.to_prepare do
Ioki.configure do |cfg|
cfg.http_adapter = Rails.env.test? ? :test : :faraday
cfg.verbose_output = Rails.env.development? || Rails.env.test?
cfg.logger = Rails.logger
cfg.api_base_url = 'https://demo.io.ki/api/'
cfg.api_client_identifier = 'com.ioki.example.client'
cfg.api_client_version = '0.0.1'
cfg.api_bleeding_edge = false
# As you should not put secrets into your source code, you probably want
# to NOT use these:
# cfg.api_client_secret = ...
# cfg.api_token = ...
end
end
The Ioki::Configuration
will also try to load defaults from the ENV, so one can
use ENV variables, to inject data - this is especially useful for the api_token
and api_client_secret. You can setup these ENV variables:
ENV['IOKI_HTTP_ADAPTER']
ENV['IOKI_VERBOSE_OUTPUT']
ENV['IOKI_API_BASE_URL']
ENV['IOKI_API_VERSION']
ENV['IOKI_API_CLIENT_IDENTIFIER']
ENV['IOKI_API_CLIENT_SECRET']
ENV['IOKI_API_CLIENT_VERSION']
ENV['IOKI_API_TOKEN']
ENV['IOKI_API_BLEEDING_EDGE']
ENV['IOKI_OAUTH_APP_ID']
ENV['IOKI_OAUTH_APP_SECRET']
ENV['IOKI_OAUTH_APP_URL']
ENV['IOKI_RETRY_COUNT']
ENV['IOKI_RETRY_SLEEP_SECONDS']
ENV['IOKI_IGNORE_DEPRECATED_ATTRIBUTES']
ENV['IOKI_PROXY_URL']
ENV['IOKI_VERIFY_SSL']
or define them for one of the three supported apis with a prefix:
ENV['IOKI_PLATFORM_API_CLIENT_IDENTIFIER']
ENV['IOKI_PASSENGER_API_VERSION']
...
Values passed directly into Ioki::Client.new
constructor take precedence over
the values in Ioki.config
. Ioki.config will respect the values set up by
Ioki.configure
. If not set, it will take the ENV value and finally fall back
for most configurable attributes to a baked in default.
ioki-ruby's main task is to serialize models to JSON and send the serialized representation to the API. All attributes changes are tracked and only changed attributes are serialized and sent to the API.
Make sure that all required attributes are properly set on the model, as ioki-ruby currently does not validate the serialized data before sending it to the API.
Please note that in certain cases sending an attribute with the value nil
results in a different behavior than not sending the attribute at all. You therefore have to ensure to only set the attributes which are meant to be sent to the API.
Example:
irb(main):002> line = Ioki::Model::Operator::Line.new(slug: 'my-line')
=> Ioki::Model::Operator::Line:3400 @attributes={:slug=>"my-line"}
irb(main):003> line.serialize
=> {:slug=>"my-line"}
irb(main):004> line.changed_attributes
=> {:slug=>"my-line"}
irb(main):005> line.attributes
=>
{:type=>nil,
:id=>nil,
:created_at=>nil,
:updated_at=>nil,
:name=>nil,
:mode=>nil,
:route_number=>nil,
:skip_time_window_check=>nil,
:slug=>"my-line",
:variant=>nil,
:line_stops=>nil}
# Set attributes to `nil` if you want to explicitly send `nil` to the API.
irb(main):007> line.slug = nil
=> nil
irb(main):008> line.serialize
=> {:slug=>nil}
# If you do not want to send an attribute to the API, but already have set it
# before, use `clear_myattribute_change`. Replace `myattribute` with the name
# of the attribute.
irb(main):009> line.clear_slug_change
=> nil
irb(main):010> line.serialize
=> {}
Webhooks are a way to receive updates about changes when they happen on the backend servers. They are pushed to your application, where the other APIs are pulled from the backend. In this regard they are very different from the Platform/Driver/Passenger/Operator API. What they have in common, is that the data is serialized in a specific manner, which ioki-ruby can turn into a model in the Ioki::Model::Webhooks module.
When you have the requests POST body in a params
hash, you can convert that
to an Event
instance, which has a model
attribute which is the deserialized
webhook data
as a model:
@event = Ioki::Webhooks::Event.new params
@model = @event.model
# The event also gives you access to other attributes:
puts @event.provider_id
puts @event.event_type
puts @event.created_at
Because the data is pushed to your application via the internet to a public endpoint in your application, the webhook data is signed by the ioki webhooks API to authenticate it using a preshared secret, which you need to provide.
# Set ENV['IOKI_WEBHOOK_SIGNATURE_KEY'] first...
# Then run the validation on the request
Ioki::Webhooks::SignatureValidator.new(
body: request.body.read,
signature: request.headers['X-Signature'],
signature_key: ENV.fetch('IOKI_WEBHOOK_SIGNATURE_KEY', nil)
).call
The logger can be set to any object that implements the ruby Logger
contract,
i.e. responding to #debug and alike. If no logger is set up no logging occurs.
A simple logger to standard out looks like this:
require 'logger'
Ioki::Configuration.new(
logger: Logger.new(STDOUT),
logger_options: { headers: true, bodies: false, log_level: :info }
)
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. If you have access to the open API definitions of iokis APIs you can add driver_api.json
, passenger_api.json
and/or driver_api.json
to fixtures/open_api_definitions
and the specs will check if the models are compatible with the current API version. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
You can use rake c
to start an interactive console. If you make code changes entering reload!
in the console will load the current files again.
Change the version number in 'lib/ioki/version.rb' to 1.2.3
. We use semantic versioning: https://semver.org/
Once this version number has been pushed to github, you have to tag that commit with the same version number. This can be done locally or on github.com when creating the release.
Tag the commit in which you changed the version number and push it to github
git tag -a '1.2.3' 0000COMMITHASHID00000000COMMITHASHID0000 -m 'Releasing Version 1.2.3'
git push --atomic origin main '1.2.3'
How to create the release, with or without creating a tag on github is described here:
https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository
Please do make use of the release notes feature to automatically create a changelog.
ioki-ruby uses Faraday under the hood to make requests to the API.
If you would like to make those request with fast predictable results in you test suite, you can pass in your own Faraday::Connection stub.
let(:client) { Ioki::Client.new(config, Ioki::PlatformApi) }
let(:config) { Ioki::Configuration.new http_adapter: http_adapter }
let(:http_adapter) { Faraday.new('https://example.com/api') { |f| f.adapter :test, stubs } }
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
it 'can retrieve provider data from the providers endpoint' do
stubs.get("/api/platform/providers") do |env|
[ 200, {}, { 'data' => [{ 'id' => '123', 'city' => 'Somewhere'}] } ]
end
expect(client.providers.first).to be_a Ioki::Model::Platform::Provider
expect(client.providers.first.city).to eq('Somewhere')
end
If you're allowed to access the OpenAPI-specifications for the three APIs you can place them in spec/fixtures/open_api_definitions
. open_api_spec
will then compare theses specs with the matching models. You can define specification_scope
on the model to set the prefix the specs are looking for in the OpenApi-schema file and schema_path
if the name of the schema is not the snake_case version of the class name and define unvalidated true
to mark that no specification exists for a model.
Sometimes it's also helpful to disable the check for specific attributes. You can pass unvalidated: true
as an option to the attribute
definition.
If ioki-ruby is integrated into a Rails app, a mailer is provided which can be used to send emails.
To activate it globally in your app, add the following to your environment configuration files (e.g. config/environments/development.rb
):
Rails.application.configure do
config.action_mailer.delivery_method = :ioki
end
When sending an email, you need to pass a provider
and a platform_client
:
class MyMailer < ApplicationMailer
def welcome_message
mail(
subject: 'Welcome to our app!'
).tap do |message|
message.ioki_options = {
provider_id: my_provider_api_id, # required
platform_client: my_ioki_platform_client, # required
delivery_context: 'standard', # optional
user_id: my_user_id # optional
}
end
end
end