Skip to content

Commit 9cb1ca0

Browse files
committed
feat: Refactor Airline model and controllers, and optimize N1QL queries
1 parent fdfa9d0 commit 9cb1ca0

File tree

3 files changed

+176
-144
lines changed

3 files changed

+176
-144
lines changed

app/controllers/api/v1/airlines_controller.rb

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -79,49 +79,55 @@ def index
7979
country = params[:country]
8080
page = params[:page].to_i || 1
8181
per_page = params[:per_page].to_i || 10
82-
8382
offset = (page - 1) * per_page
8483

85-
airlines = if country.present?
86-
Airline.where(country:)
87-
else
88-
Airline.all
89-
end
90-
.pluck(:callsign, :country, :iata, :icao, :id, :name)
91-
# .order(:name)
92-
# .offset(offset)
93-
# .limit(per_page)
84+
begin
85+
airlines = if country.present?
86+
Airline.list_by_country(country, limit: per_page, offset:)
87+
else
88+
Airline.all
89+
.pluck(:callsign, :country, :iata, :icao, :id, :name)
90+
# .limit(per_page).offset(offset)
91+
end
92+
93+
formatted_airlines = airlines.map do |airline|
94+
{
95+
callsign: airline[0],
96+
country: airline[1],
97+
iata: airline[2],
98+
icao: airline[3],
99+
id: airline[4],
100+
name: airline[5]
101+
}
102+
end
94103

95-
formatted_airlines = airlines.map do |airline|
96-
{
97-
callsign: airline[0],
98-
country: airline[1],
99-
iata: airline[2],
100-
icao: airline[3],
101-
id: airline[4],
102-
name: airline[5]
103-
}
104+
render json: formatted_airlines
105+
rescue ArgumentError => e
106+
render json: { error: 'Invalid request', message: e.message }, status: :bad_request
107+
rescue StandardError => e
108+
render json: { error: 'Internal server error', message: e.message }, status: :internal_server_error
104109
end
105-
106-
render json: formatted_airlines
107110
end
108111

109112
# GET /api/v1/airlines/to-airport
110113
def to_airport
111-
raise ArgumentError, 'Destination airport is missing' unless params[:destination_airport].present?
114+
raise ArgumentError, 'Destination airport is missing' unless params[:destinationAirportCode].present?
112115

113-
destination_airport = params[:destination_airport]
116+
destination_airport = params[:destinationAirportCode]
114117
page = params[:page].to_i || 1
115118
per_page = params[:per_page].to_i || 10
116119

117120
offset = (page - 1) * per_page
118121

119-
airline_ids = Route.where(destinationairport: destination_airport)
120-
# .distinct(:airlineid)
121-
.pluck(:airlineid)
122+
# airline_ids = Route.where(destinationairport: destination_airport)
123+
# # .distinct(:airlineid)
124+
# .pluck(:airlineid)
125+
126+
# airlines = Airline.where(id: airline_ids)
127+
# .pluck(:callsign, :country, :iata, :icao, :id, :name)
128+
129+
airlines = Airline.to_airport(destination_airport, limit: per_page, offset:)
122130

123-
airlines = Airline.where(id: airline_ids)
124-
.pluck(:callsign, :country, :iata, :icao, :id, :name)
125131
# .offset(offset)
126132
# .limit(per_page)
127133

app/models/airline.rb

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
# frozen_string_literal: true
2+
13
require 'couchbase-orm'
24

5+
# Airline model
36
class Airline < CouchbaseOrm::Base
47
attribute :name, :string
58
attribute :callsign, :string
@@ -14,9 +17,31 @@ class Airline < CouchbaseOrm::Base
1417
validates :country, presence: true
1518

1619
# Custom N1QL query to list airlines by country with pagination
17-
n1ql :by_country, emit_key: :country, query_fn: proc { |bucket, values, options|
18-
offset = options.delete(:offset) || 0
19-
limit = options.delete(:limit) || 10
20-
cluster.query("SELECT * FROM `#{bucket.name}` WHERE type = 'airline' AND country = #{quote(values[0])} ORDER BY name ASC LIMIT #{limit} OFFSET #{offset}")
20+
n1ql :list_by_country, emit_key: [:country], query_fn: proc { |bucket, values, options|
21+
limit = options[:limit] || 10
22+
offset = options[:offset] || 0
23+
cluster.query("SELECT airline.callsign, airline.country, airline.iata, airline.icao, META(airline).id AS id, airline.name, airline.type FROM `#{bucket.name}` AS airline WHERE airline.country = $1 LIMIT $2 OFFSET $3", values[0], limit, offset)
24+
}
25+
# n1ql :by_country,
26+
# "SELECT * FROM `#{bucket.name}` WHERE type = 'airline' AND country = $1 ORDER BY name ASC LIMIT $2 OFFSET $3"
27+
28+
# SELECT
29+
# air.callsign,
30+
# air.country,
31+
# air.iata,
32+
# air.icao,
33+
# air.id,
34+
# air.name,
35+
# air.type
36+
# FROM (
37+
# SELECT DISTINCT META(airline).id AS airlineId
38+
# FROM route
39+
# JOIN airline ON route.airlineid = META(airline).id
40+
# WHERE route.destinationairport = ?
41+
# ) AS subquery
42+
# JOIN airline AS air ON META(air).id = subquery.airlineId;
43+
44+
n1ql :to_airport, emit_key: :icao, query_fn: proc { |_bucket, values, _options|
45+
cluster.query("SELECT air.callsign, air.country, air.iata, air.icao, META(air).id, air.name, air.type FROM (SELECT DISTINCT META(airline).id AS airlineId FROM route JOIN airline ON route.airlineid = META(airline).id WHERE route.destinationairport = #{quote(values[0])}) AS subquery JOIN airline AS air ON META(air).id = subquery.airlineId")
2146
}
2247
end

test/integration/airlines_spec.rb

Lines changed: 113 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -154,123 +154,124 @@
154154
end
155155
end
156156

157-
# describe 'GET /api/v1/airlines/list' do
158-
# let(:country) { 'United States' }
159-
# let(:limit) { '10' }
160-
# let(:offset) { '0' }
161-
# let(:expected_airlines) do
162-
# [
163-
# { 'name' => '40-Mile Air', 'iata' => 'Q5', 'icao' => 'MLA', 'callsign' => 'MILE-AIR',
164-
# 'country' => 'United States' },
165-
# { 'name' => 'Texas Wings', 'iata' => 'TQ', 'icao' => 'TXW', 'callsign' => 'TXW', 'country' => 'United States' },
166-
# { 'name' => 'Atifly', 'iata' => 'A1', 'icao' => 'A1F', 'callsign' => 'atifly', 'country' => 'United States' },
167-
# { 'name' => 'Locair', 'iata' => 'ZQ', 'icao' => 'LOC', 'callsign' => 'LOCAIR', 'country' => 'United States' },
168-
# { 'name' => 'SeaPort Airlines', 'iata' => 'K5', 'icao' => 'SQH', 'callsign' => 'SASQUATCH',
169-
# 'country' => 'United States' },
170-
# { 'name' => 'Alaska Central Express', 'iata' => 'KO', 'icao' => 'AER', 'callsign' => 'ACE AIR',
171-
# 'country' => 'United States' },
172-
# { 'name' => 'AirTran Airways', 'iata' => 'FL', 'icao' => 'TRS', 'callsign' => 'CITRUS',
173-
# 'country' => 'United States' },
174-
# { 'name' => 'U.S. Air', 'iata' => '-+', 'icao' => '--+', 'callsign' => nil, 'country' => 'United States' },
175-
# { 'name' => 'PanAm World Airways', 'iata' => 'WQ', 'icao' => 'PQW', 'callsign' => nil,
176-
# 'country' => 'United States' },
177-
# { 'name' => 'Bemidji Airlines', 'iata' => 'CH', 'icao' => 'BMJ', 'callsign' => 'BEMIDJI',
178-
# 'country' => 'United States' }
179-
# ]
180-
# end
157+
describe 'GET /api/v1/airlines/list' do
158+
let(:country) { 'United States' }
159+
let(:limit) { '10' }
160+
let(:offset) { '0' }
161+
let(:expected_airlines) do
162+
[
163+
{ 'name' => '40-Mile Air', 'iata' => 'Q5', 'icao' => 'MLA', 'callsign' => 'MILE-AIR',
164+
'country' => 'United States' },
165+
{ 'name' => 'Texas Wings', 'iata' => 'TQ', 'icao' => 'TXW', 'callsign' => 'TXW', 'country' => 'United States' },
166+
{ 'name' => 'Atifly', 'iata' => 'A1', 'icao' => 'A1F', 'callsign' => 'atifly', 'country' => 'United States' },
167+
{ 'name' => 'Locair', 'iata' => 'ZQ', 'icao' => 'LOC', 'callsign' => 'LOCAIR', 'country' => 'United States' },
168+
{ 'name' => 'SeaPort Airlines', 'iata' => 'K5', 'icao' => 'SQH', 'callsign' => 'SASQUATCH',
169+
'country' => 'United States' },
170+
{ 'name' => 'Alaska Central Express', 'iata' => 'KO', 'icao' => 'AER', 'callsign' => 'ACE AIR',
171+
'country' => 'United States' },
172+
{ 'name' => 'AirTran Airways', 'iata' => 'FL', 'icao' => 'TRS', 'callsign' => 'CITRUS',
173+
'country' => 'United States' },
174+
{ 'name' => 'U.S. Air', 'iata' => '-+', 'icao' => '--+', 'callsign' => nil, 'country' => 'United States' },
175+
{ 'name' => 'PanAm World Airways', 'iata' => 'WQ', 'icao' => 'PQW', 'callsign' => nil,
176+
'country' => 'United States' },
177+
{ 'name' => 'Bemidji Airlines', 'iata' => 'CH', 'icao' => 'BMJ', 'callsign' => 'BEMIDJI',
178+
'country' => 'United States' }
179+
]
180+
end
181181

182-
# it 'returns a list of airlines for a given country' do
183-
# get '/api/v1/airlines/list', params: { country:, limit:, offset: }
182+
it 'returns a list of airlines for a given country' do
183+
get '/api/v1/airlines/list', params: { country:, limit:, offset: }
184184

185-
# expect(response).to have_http_status(:ok)
186-
# expect(response.content_type).to eq('application/json; charset=utf-8')
187-
# expect(JSON.parse(response.body)).to eq(expected_airlines)
188-
# end
189-
# end
185+
expect(response).to have_http_status(:ok)
186+
expect(response.content_type).to eq('application/json; charset=utf-8')
187+
expect(JSON.parse(response.body)).to eq(expected_airlines)
188+
end
189+
end
190190

191-
# describe 'GET /api/v1/airlines/to-airport' do
192-
# let(:destination_airport_code) { 'MRS' }
193-
# let(:limit) { '10' }
194-
# let(:offset) { '0' }
195-
# let(:expected_airlines) do
196-
# [
197-
# {
198-
# 'callsign' => 'AIRFRANS',
199-
# 'country' => 'France',
200-
# 'iata' => 'AF',
201-
# 'icao' => 'AFR',
202-
# 'name' => 'Air France'
203-
# },
204-
# {
205-
# 'callsign' => 'SPEEDBIRD',
206-
# 'country' => 'United Kingdom',
207-
# 'iata' => 'BA',
208-
# 'icao' => 'BAW',
209-
# 'name' => 'British Airways'
210-
# },
211-
# {
212-
# 'callsign' => 'AIRLINAIR',
213-
# 'country' => 'France',
214-
# 'iata' => 'A5',
215-
# 'icao' => 'RLA',
216-
# 'name' => 'Airlinair'
217-
# },
218-
# {
219-
# 'callsign' => 'STARWAY',
220-
# 'country' => 'France',
221-
# 'iata' => 'SE',
222-
# 'icao' => 'SEU',
223-
# 'name' => 'XL Airways France'
224-
# },
225-
# {
226-
# 'callsign' => 'TWINJET',
227-
# 'country' => 'France',
228-
# 'iata' => 'T7',
229-
# 'icao' => 'TJT',
230-
# 'name' => 'Twin Jet'
231-
# },
232-
# {
233-
# 'callsign' => 'EASY',
234-
# 'country' => 'United Kingdom',
235-
# 'iata' => 'U2',
236-
# 'icao' => 'EZY',
237-
# 'name' => 'easyJet'
238-
# },
239-
# {
240-
# 'callsign' => 'AMERICAN',
241-
# 'country' => 'United States',
242-
# 'iata' => 'AA',
243-
# 'icao' => 'AAL',
244-
# 'name' => 'American Airlines'
245-
# },
246-
# {
247-
# 'callsign' => 'CORSICA',
248-
# 'country' => 'France',
249-
# 'iata' => 'XK',
250-
# 'icao' => 'CCM',
251-
# 'name' => 'Corse-Mediterranee'
252-
# }
253-
# ]
254-
# end
191+
describe 'GET /api/v1/airlines/to-airport' do
192+
let(:destination_airport_code) { 'MRS' }
193+
let(:limit) { '10' }
194+
let(:offset) { '0' }
195+
let(:expected_airlines) do
196+
[
197+
{
198+
'callsign' => 'AIRFRANS',
199+
'country' => 'France',
200+
'iata' => 'AF',
201+
'icao' => 'AFR',
202+
'name' => 'Air France'
203+
},
204+
{
205+
'callsign' => 'SPEEDBIRD',
206+
'country' => 'United Kingdom',
207+
'iata' => 'BA',
208+
'icao' => 'BAW',
209+
'name' => 'British Airways'
210+
},
211+
{
212+
'callsign' => 'AIRLINAIR',
213+
'country' => 'France',
214+
'iata' => 'A5',
215+
'icao' => 'RLA',
216+
'name' => 'Airlinair'
217+
},
218+
{
219+
'callsign' => 'STARWAY',
220+
'country' => 'France',
221+
'iata' => 'SE',
222+
'icao' => 'SEU',
223+
'name' => 'XL Airways France'
224+
},
225+
{
226+
'callsign' => 'TWINJET',
227+
'country' => 'France',
228+
'iata' => 'T7',
229+
'icao' => 'TJT',
230+
'name' => 'Twin Jet'
231+
},
232+
{
233+
'callsign' => 'EASY',
234+
'country' => 'United Kingdom',
235+
'iata' => 'U2',
236+
'icao' => 'EZY',
237+
'name' => 'easyJet'
238+
},
239+
{
240+
'callsign' => 'AMERICAN',
241+
'country' => 'United States',
242+
'iata' => 'AA',
243+
'icao' => 'AAL',
244+
'name' => 'American Airlines'
245+
},
246+
{
247+
'callsign' => 'CORSICA',
248+
'country' => 'France',
249+
'iata' => 'XK',
250+
'icao' => 'CCM',
251+
'name' => 'Corse-Mediterranee'
252+
}
253+
]
254+
end
255255

256-
# context 'when destinationAirportCode is provided' do
257-
# it 'returns a list of airlines flying to the destination airport' do
258-
# get '/api/v1/airlines/to-airport',
259-
# params: { destinationAirportCode: destination_airport_code, limit:, offset: }
256+
context 'when destinationAirportCode is provided' do
257+
it 'returns a list of airlines flying to the destination airport' do
258+
get '/api/v1/airlines/to-airport',
259+
params: { destinationAirportCode: destination_airport_code, limit:, offset: }
260260

261-
# expect(response).to have_http_status(:ok)
262-
# expect(response.content_type).to eq('application/json; charset=utf-8')
263-
# expect(JSON.parse(response.body)).to eq(expected_airlines)
264-
# end
265-
# end
261+
expect(response).to have_http_status(:ok)
262+
expect(response.content_type).to eq('application/json; charset=utf-8')
263+
expect(JSON.parse(response.body)).to eq(expected_airlines)
264+
end
265+
end
266266

267-
# context 'when destinationAirportCode is not provided' do
268-
# it 'returns a bad request error' do
269-
# get '/api/v1/airlines/to-airport', params: { limit:, offset: }
267+
context 'when destinationAirportCode is not provided' do
268+
it 'returns a bad request error' do
269+
get '/api/v1/airlines/to-airport', params: { limit:, offset: }
270270

271-
# expect(response).to have_http_status(:bad_request)
272-
# expect(JSON.parse(response.body)).to eq({ 'message' => 'Destination airport code is required' })
273-
# end
274-
# end
275-
# end
271+
expect(response).to have_http_status(:bad_request)
272+
expect(JSON.parse(response.body)).to eq({ 'error' => 'Invalid request',
273+
'message' => 'Destination airport is missing' })
274+
end
275+
end
276+
end
276277
end

0 commit comments

Comments
 (0)