Skip to content

Commit

Permalink
206 - add endpoint to return list of schools with cache (#211)
Browse files Browse the repository at this point in the history
* 206 - Add endpoint to fetch schools

* 206 - Add School Model

* 206 - Add endpoint to sync schools

* add cache

---------

Co-authored-by: Edimo <[email protected]>
  • Loading branch information
edimossilva and Edimo authored Jul 27, 2024
1 parent 3eb1386 commit 575af6f
Show file tree
Hide file tree
Showing 13 changed files with 141 additions and 125 deletions.
3 changes: 2 additions & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
REDIS_URL=redis://redis:6379/0
REDIS_CACHE_URL=redis://redis:6379/1
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_HOST=database
ELASTICSEARCH_HOST=http://elasticsearch:9200
COLLEGE_SCORE_CARD_API_KEY=
COLLEGE_SCORE_CARD_API_KEY= find_it_here_https://github.com/RTICWDT/open-data-maker/blob/master/API.md
3 changes: 3 additions & 0 deletions backend/.rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ RSpec/AnyInstance:
RSpec/ImplicitSubject:
EnforcedStyle: single_statement_only

RSpec/VerifiedDoubles:
Enabled: false

Style/Documentation:
Enabled: false

Expand Down
4 changes: 2 additions & 2 deletions backend/app/actors/fetch_schools.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
class FetchSchools < Actor
FETCH_SCHOOLS_URL = "https://api.data.gov/ed/collegescorecard/v1/schools"

input :school_name_like
input :school_index_contract

output :data

Expand All @@ -26,7 +26,7 @@ def params
api_key:,
fields: "id,school.name,location",
page: 0,
"school.name" => school_name_like
"school.name" => school_index_contract[:school_name_like]
}
end

Expand Down
9 changes: 9 additions & 0 deletions backend/app/contracts/school_contracts/index.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module SchoolContracts
class Index < ApplicationContract
params do
required(:school_name_like).filled(:string)
end
end
end
27 changes: 27 additions & 0 deletions backend/app/controllers/api/v1/schools_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module Api
module V1
class SchoolsController < Api::V1::ApiController
skip_before_action :authenticate_devise_api_token!
skip_after_action :verify_authorized

def index
schools = Rails.cache.fetch(school_index_contract.to_json) do
FetchSchools.result(school_index_contract:).data
end
render json: schools, status: :ok
end

private

def school_index_contract
@school_index_contract ||= SchoolContracts::Index.call(permitted_params(:school_name_like))
end

def school_name_like
school_index_contract[:school_name_like]
end
end
end
end
2 changes: 2 additions & 0 deletions backend/config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
config.public_file_server.headers = {
"Cache-Control" => "public, max-age=#{2.days.to_i}"
}
elsif ENV["REDIS_CACHE_URL"]
config.cache_store = :redis_cache_store, { url: ENV["REDIS_CACHE_URL"] }
else
config.action_controller.perform_caching = false

Expand Down
9 changes: 7 additions & 2 deletions backend/config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,13 @@

# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.cache_store = :null_store
config.cache_store = if ENV["REDIS_CACHE_URL"]
config.action_controller.perform_caching = true
[:redis_cache_store, { url: ENV["REDIS_CACHE_URL"] }]
else
config.action_controller.perform_caching = false
:null_store
end

# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = :none
Expand Down
2 changes: 2 additions & 0 deletions backend/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :users, only: %i[index create]
resources :schools, only: %i[index]
end
end

devise_scope :user do
post "/api/v1/tokens", to: "devise/api/tokens#sign_in", as: "api_v1_sign_in_user_token"
end
Expand Down
81 changes: 0 additions & 81 deletions backend/db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 0 additions & 12 deletions backend/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,6 @@ services:
interval: 1s
timeout: 3s
retries: 30
redis_cache:
image: docker.io/redis:latest
hostname: redis
command: redis-server
ports:
- "6380:6379"
healthcheck:
test: redis-cli ping
interval: 1s
timeout: 3s
retries: 30

volumes:
backend_data:
database-data:
Expand Down
53 changes: 26 additions & 27 deletions backend/spec/actors/fetch_schools_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,33 @@
RSpec.describe FetchSchools, type: :actor do
describe ".call" do
let(:school_name) { "The best school" }
let(:valid_params) do
{
api_key:,
fields: "id,school.name,location",
page: 0,
"school.name" => school_name
}
end

context "when setup is valid" do
let(:valid_params) do
{
api_key:,
fields: "id,school.name,location",
page: 0,
"school.name" => school_name
}
end
let(:school_index_contract) { SchoolContracts::Index.call({ school_name_like: school_name }) }

let(:success_response) { { results: schools }.to_json }
let(:success_response) { { results: schools }.to_json }

let(:schools) do
[
{ "id" => "1",
"school.name" => school_name,
"location.lat" => 42.374471,
"location.lon" => -71.118313 }
]
end
let(:schools) do
[
{ "id" => "1",
"school.name" => school_name,
"location.lat" => 42.374471,
"location.lon" => -71.118313 }
]
end

let(:api_key) { "secret_api_key" }

let(:api_key) { "secret_api_key" }
let(:call) { described_class.result(school_index_contract:) }

context "when setup is valid" do
before do
allow_any_instance_of(described_class).to receive(:api_key).and_return(api_key)
stub_request(:get, described_class::FETCH_SCHOOLS_URL)
Expand All @@ -37,13 +40,11 @@
end

it "is successful" do
result = described_class.result(school_name_like: school_name)
expect(result.success?).to be true
expect(call.success?).to be true
end

it "returns schools list" do
result = described_class.result(school_name_like: school_name)
expect(result.data).to eq(schools)
expect(call.data).to eq(schools)
end
end

Expand All @@ -54,14 +55,12 @@
end

it "is failure" do
result = described_class.result(school_name_like: school_name)
expect(result.failure?).to be true
expect(call.failure?).to be true
end
end

it "returns error object" do
result = described_class.result(school_name_like: school_name)
expect(result.error).to eq(:missing_college_score_card_api_key)
expect(call.error).to eq(:missing_college_score_card_api_key)
end
end
end
Expand Down
54 changes: 54 additions & 0 deletions backend/spec/requests/api/v1/schools_request_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe "Schools" do
describe "GET /api/v1/schools" do
let(:do_request) { get "/api/v1/schools", params: }
let(:result) { double("Result", data: schools) }
let(:school_name_like) { "Harvard" }
let(:schools) do
[
{ "id" => "1",
"school.name" => school_name_like,
"location.lat" => 42.374471,
"location.lon" => -71.118313 }
]
end
let(:params) do
{
school_name_like:
}
end
let(:school_index_contract) { SchoolContracts::Index.call(params) }

context "when schools are not cached" do
before do
allow(FetchSchools).to receive(:result).with(school_index_contract:).and_return(result)
do_request
end

it { expect(response.parsed_body).to match(schools) }
end

context "when schools are cached" do
let(:school_index_contract) { SchoolContracts::Index.call(params) }

before do
Rails.cache.fetch(school_index_contract.to_json) do
schools
end
end

it "does not calls FetchSchools.result" do
expect(FetchSchools).not_to receive(:result)
do_request
end

it "returns schools from cache" do
do_request
expect(response.parsed_body).to match(schools)
end
end
end
end
7 changes: 7 additions & 0 deletions backend/spec/support/cache.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

RSpec.configure do |config|
config.before(:suite) do
Rails.cache.clear
end
end

0 comments on commit 575af6f

Please sign in to comment.