The primary objective of this Ruby on Rails API template is to provide a starting point to integrate your app with Jobber.
- What is this App for?
- OAuth flow
- How it works
- Getting started
- Making GraphQL requests
- Deployment
- Learn More
- Need help of have an idea?
- License
This Ruby on Rails API Template is meant to be a quick and easy way to get you up to speed using Jobber's GraphQL API. This API is to be consumed by the React App Template and handles authentication through Jobber's Developer Center and a few GraphQL example queries.
The authentication flow is done by both apps, the frontend is responsable to receive the code
returned from Jobber's GraphQL API once the users goes through the oauth and allow the app to connect to they jobber account.
On this App you will find a /request_access_token
endpoint that will authenticate the user upon recieving a valid code, creating a record for a JobberAccount, generating an HttpOnly Cookie and sending it to the frontend in order to mantain the session.
Note: An App needs to be created on Jobber's Developer Center, and the environment variables described in
.env.sample
need to be configured in order to make the oauth redirection.
When you run both apps together, you should see a list of the clients from your Jobber account on the frontend:
A sample query is available for getting paginated clients, you can try increasing the CLIENTS_LIMIT
constant to get more clients per request which could trigger a Throttling error or decreasing it, which would then generate many more and smaller requests. Finding a good balance is key to have an app with good performance.
Read more on Jobber's API Rate Limits.
# app/services/queries/clients.rb
module Graphql
module Queries
module Clients
CLIENTS_LIMIT = 50
def variables
{
limit: CLIENTS_LIMIT,
cursor: nil,
filter: nil,
}
end
ClientsQuery = JobberAppTemplateRailsApi::Client.parse(<<~'GRAPHQL')
fragment PageInfoFragment on PageInfo {
endCursor
hasNextPage
}
query(
$limit: Int,
$cursor: String,
$filter: ClientFilterAttributes,
) {
clients(first: $limit, after: $cursor, filter: $filter) {
nodes {
id
name
}
pageInfo {
...PageInfoFragment
}
}
}
GRAPHQL
end
end
end
We use execute_query
to make a simple request and make sure it won't cause any issues with result_has_errors?
and sleep_before_throttling
.
execute_paginated_query
is a recursive method that will call execute_query
until has_next_page
is false, meaning we've reached the end of our query. This is where the CLIENTS_LIMIT
constant in the ClientsQuery comes into play.
If for any reason the query returns an error, it will be raised by result_has_errors?
.
Finally, sleep_before_throttling
makes sure your query won't go over the Maximum Available Limit by taking the cost of the previous request as the expected_cost
of the next request and comparing it against the currently available points.
# app/services/jobber_service.rb
class JobberService
def execute_query(token, query, variables = {}, expected_cost: nil)
context = { Authorization: "Bearer #{token}" }
result = JobberAppTemplateRailsApi::Client.query(query, variables: variables, context: context)
result = result.original_hash
result_has_errors?(result)
sleep_before_throttling(result, expected_cost)
result
end
def execute_paginated_query(token, query, variables, resource_names, paginated_results = [], expected_cost: nil)
result = execute_query(token, query, variables, expected_cost: expected_cost)
result = result["data"]
resource_names.each do |resource|
result = result[resource].deep_dup
end
paginated_results << result["nodes"]
page_info = result["pageInfo"]
has_next_page = page_info["hasNextPage"]
if has_next_page
variables[:cursor] = page_info["endCursor"]
execute_paginated_query(token, query, variables, resource_names, paginated_results, expected_cost: expected_cost)
end
paginated_results.flatten
end
private
def result_has_errors?(result)
return false if result["errors"].nil?
raise Exceptions::GraphQLQueryError, result["errors"].first["message"]
end
def sleep_before_throttling(result, expected_cost = nil)
throttle_status = result["extensions"]["cost"]["throttleStatus"]
currently_available = throttle_status["currentlyAvailable"].to_i
max_available = throttle_status["maximumAvailable"].to_i
restore_rate = throttle_status["restoreRate"].to_i
sleep_time = 0
if expected_cost.blank?
expected_cost = max_available * 0.6
end
if currently_available <= expected_cost
sleep_time = ((max_available - currently_available) / restore_rate).ceil
sleep(sleep_time)
end
sleep_time
end
end
clients_controller#index
retrieves the account's access token to use as a parameter for the execute_paginated_query
method along with the ClientsQuery
and its variables
and pass ["clients"]
as the paginated_result
param. We don't pass an expected cost for this example, meaning sleep_before_throttling
will be our default 60% of the Maximum Available Limit.
# app/controllers/clients_controller.rb
class ClientsController < AuthController
include Graphql::Queries::Clients
def index
token = @jobber_account.jobber_access_token
clients = jobber_service.execute_paginated_query(token, ClientsQuery, variables, ["clients"])
render(json: { clients: clients }, status: :ok)
rescue Exceptions::GraphQLQueryError => error
render(json: { message: "#{error.class}: #{error.message}" }, status: :internal_server_error)
end
end
You should expect clients_controller#index
to return a json similar to this:
{
"clients": [
{
"id": "ABC1DEFgHIE=",
"name": "Anakin Skywalker"
},
{
"id": "ABC1DEFgHIY=",
"name": "Paddy's Pub"
},
{
"id": "ABC1DEFgHIM=",
"name": "Maximus Decimus Meridius"
},
{
"id": "ABC1DEFgHIM=",
"name": "Tom Bombadil"
}
]
}
Which should look something like this on the frontend:
-
Ruby 3.0.1
-
rvm install "ruby-3.0.1"
-
rvm use "ruby-3.0.1"
-
-
Postgres database
This project is configured to use the postgres database from the
docker-compose.yml
file. -
Jobber App
- Create a developer account:
- Create new app:
- Follow the docs to get started: https://developer.getjobber.com/docs
- Your app must have (as a minimum) read access to Clients and Users under the Scopes section, in order for this template to work:
-
Install gems
bundle install
-
Create postgres and redis docker container
docker compose up -d
-
Setup environment variables
-
cp .env.sample .env
Make sure to have the correct env values
-
-
Create database and migrations
-
rails db:create
-
rails db:migrate
-
-
Update the GraphQL schema
rake schema:update
rails s -p 4000
- Learn more about Jobber's GraphQL API:
This template comes with a Procfile
configured so you can easily deploy on Heroku, however, you can deploy this API on the platform of your choice.
-
Install the Heroku CLI.
-
Log in to you Heroku account:
heroku login
-
Create a new Heroku app, this can be done from your browser or using Heroku's CLI in your terminal:
heroku create <name-of-your-app>
-
Verify the git remote was added with
git config --list --local | grep heroku
or add the heroku remote yourself:git remote add heroku <heroku-app-url>
-
Deploy
git push heroku main
To learn more about deploying on Heroku:
Checkout Jobber's API documentation for more details on its setup and usage.
You can learn more about Ruby on Rails API mode in the documentation.
For more information on Heroku, visit the Heroku Dev Center or the Getting started on Heroku with Rails 6 for more specific content on Rails.
Please follow one of these issue templates if you'd like to submit a bug or request a feature.
The template is available as open source under the terms of the MIT License.