Skip to content

Commit

Permalink
Add an API-first Provider model
Browse files Browse the repository at this point in the history
This integrates with the Provider API in an API-first way – e.g. by
calling out to the API rather than relying on an overnight "ETL" sync
job to cache Provider records in a local Active Record model.

However, I'm putting this work on hold for now because it's become
increasingly clear that an overnight ETL would grant us more flexibility
in how we interact with Provider data. It currently seems to be the more
pragmatic approach, so I'm putting this API-first implementation on ice
for now.
  • Loading branch information
ollietreend committed Jan 4, 2024
1 parent 3835ce3 commit c0a863d
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 0 deletions.
40 changes: 40 additions & 0 deletions app/lib/teacher_training_courses/api_client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require "net/http"

module TeacherTrainingCourses
class ApiClient
BASE_URL =
"https://api.publish-teacher-training-courses.service.gov.uk/api/public/v1".freeze

class << self
def get(endpoint)
request(method: :get, endpoint:)
end

def get_all_pages(endpoint)
results = []
is_last_page = false

until is_last_page
response = request(method: :get, endpoint:)
results.concat response.fetch("data")

if (next_page = response.dig("links", "next"))
endpoint = next_page.delete_prefix(BASE_URL)
else
is_last_page = true
end
end

results
end

private

def request(method:, endpoint:)
url = URI("#{BASE_URL}#{endpoint}")
response = Net::HTTP.public_send(method, url)
JSON.parse(response)
end
end
end
end
51 changes: 51 additions & 0 deletions app/lib/teacher_training_courses/provider.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
module TeacherTrainingCourses
class Provider
ATTRIBUTES = %i[code name email website ukprn accredited_body].freeze

attr_reader(*ATTRIBUTES)

def initialize(_code, attributes)
ATTRIBUTES.each do |attribute|
attribute = attribute.to_s
instance_variable_set("@#{attribute}", attributes[attribute])
end
end

class << self
def all
providers_from_api.map { |code, attributes| new(code, attributes) }
end

def find(code)
if (attributes = providers_from_api[code])
new(code, attributes)
else
raise ProviderNotFound, code
end
end

private

def providers_from_api
@providers_from_api ||= Rails.cache.fetch("providers_from_api", expires_in: 1.hour) do
# Fetch all providers from the Teacher Training Courses API
providers = ApiClient.get_all_pages("/recruitment_cycles/2024/providers")

# Drop attributes we don't need
providers = providers.map do |provider|
provider["attributes"].select { |key, _value| ATTRIBUTES.include?(key.to_sym) }
end

# Transform to a Hash indexed by provider code
providers.index_by { |provider| provider["code"] }
end
end
end

class ProviderNotFound < StandardError
def initialize(code)
super("Couldn't find provider with code '#{code}'")
end
end
end
end
11 changes: 11 additions & 0 deletions spec/lib/teacher_training_courses/api_client_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require "rails_helper"

RSpec.describe TeacherTrainingCourses::ApiClient do
describe ".get" do
it "makes a GET request to the API" do
stub_providers
debugger
expect(described_class.get("/recruitment_cycles/2024/providers")).to eq("Hello")
end
end
end
24 changes: 24 additions & 0 deletions spec/lib/teacher_training_courses/provider_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require "rails_helper"

RSpec.describe TeacherTrainingCourses::Provider do
describe ".all" do
it "makes a GET request to the API" do
stub_providers
expect(described_class.all).to eq []
end
end

describe ".find" do
it "gets a specific provider by its code" do
stub_providers([
build(:teacher_training_courses_provider, code: "T92", name: "Example provider")
])

provider = described_class.find("T92")

expect(provider).to be_a TeacherTrainingCourses::Provider
expect(provider.code).to eq "T92"
expect(provider.name).to eq "Example provider"
end
end
end

0 comments on commit c0a863d

Please sign in to comment.