Skip to content

Commit

Permalink
Merge pull request #30 from bus-detective/scheduled-departures
Browse files Browse the repository at this point in the history
Scheduled departures
  • Loading branch information
geofflane committed Oct 1, 2015
2 parents e7284f8 + 4f495cd commit 83f7560
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 31 deletions.
22 changes: 21 additions & 1 deletion app/controllers/api/departures_controller.rb
@@ -1,6 +1,12 @@
class Api::DeparturesController < ApiController
def index
fetcher = departure_fetcher.new(agency, stop, time)
fetcher = departure_fetcher.new(
agency: agency,
stop: stop,
start_time: start_time,
end_time: end_time
)

if fetcher.valid?
render json: fetcher, serializer: DepartureFetcherSerializer
else
Expand Down Expand Up @@ -32,4 +38,18 @@ def time
# offset to work correctly I think.
params[:time] ? Time.zone.parse(params[:time]) : Time.current
end

def start_time
time - 10.minutes
end

def end_time
time + duration_in_hours
end

DEFAULT_DURATION_IN_HOURS = 1

def duration_in_hours
params.fetch(:duration, DEFAULT_DURATION_IN_HOURS).to_i.hours
end
end
30 changes: 17 additions & 13 deletions app/models/departure_fetcher.rb
@@ -1,40 +1,44 @@
class DepartureFetcher
include ActiveModel::SerializerSupport

attr_reader :agency, :stop, :time
def initialize(agency, stop, time, params = {})
attr_reader :agency, :stop, :start_time, :end_time
def initialize(agency:, stop:, start_time:, end_time:)
@agency = agency
@time = time.in_time_zone
@stop = stop
@start_time = start_time
@end_time = end_time
end

def departures
@departures ||= stop_times.map { |stop_time|
Departure.new(stop_time: stop_time, stop_time_update: nil)
}.sort_by(&:time)
}.sort_by(&:time).select { |d| active?(d) }
end

def stop_times
@stop_times ||= begin
agency.calculated_stop_times
.where(stop: stop)
.between(start_time, end_time)
.between(query_start_time, query_end_time)
.preload(:stop, :trip, :route)
end
end

def valid?
@agency.present? && @stop.present?
def query_start_time
start_time
end

protected
def query_end_time
end_time
end

def start_time
@time - 10.minutes
def valid?
@agency.present? && @stop.present?
end

def end_time
@time + 1.hour
private

def active?(departure)
true
end
end

20 changes: 8 additions & 12 deletions app/models/realtime_departure_fetcher.rb
@@ -1,30 +1,26 @@
class RealtimeDepartureFetcher < DepartureFetcher
include ActiveModel::SerializerSupport

def initialize(agency, stop, time, params = {})
super(agency, stop, time, params)

time_limit = params.fetch(:time_limit, 10).to_i
@active_duration = Interval.new((-1 * time_limit.minutes))
end

def departures
@departures ||= stop_times.map { |stop_time|
stop_time_update = realtime_updates.for_stop_time(stop_time) if realtime_updates
Departure.new(stop_time: stop_time, stop_time_update: stop_time_update)
}.sort_by(&:time).select { |d| active?(d) }
end

private

def start_time
@time - 1.hour
# Realtime updates may shift the departures into the time window we are
# asking for. Therefor we need to query further back in time in order capture
# all the applicable departures.
def query_start_time
start_time - 1.hour
end

def active?(departure)
departure.duration_from(@time) >= @active_duration
departure.time >= start_time && departure.time <= end_time
end

private

def realtime_updates
# Non-standard memoization because we want to allow nulls so we don't
# continually try and call the service
Expand Down
2 changes: 1 addition & 1 deletion spec/models/departure_fetcher_spec.rb
Expand Up @@ -6,7 +6,7 @@
let(:agency) { stop.agency }
let(:service) { create(:service, agency: agency, thursday: true) }
let(:trip) { create(:trip, agency: agency, service: service) }
subject { DepartureFetcher.new(agency, stop, now) }
subject { DepartureFetcher.new(agency: agency, stop: stop, start_time: now - 10.minutes, end_time: now + 1.hour) }

describe "#stop_times" do
let!(:applicable_stop_time) {
Expand Down
6 changes: 3 additions & 3 deletions spec/models/realtime_departure_fetcher_spec.rb
Expand Up @@ -5,7 +5,7 @@
let(:agency) { create(:agency, :with_rt_endpoint) }
let(:stop) { create(:stop, agency: agency) }
let(:trip) { create(:trip, agency: agency, service: create(:service, agency: agency, thursday: true)) }
subject { RealtimeDepartureFetcher.new(agency, stop, now) }
subject { RealtimeDepartureFetcher.new(agency: agency, stop: stop, start_time: now - 10.minutes, end_time: now + 1.hour) }

describe "#stop_times" do
let!(:applicable_stop_time) {
Expand Down Expand Up @@ -110,10 +110,10 @@
end

context "when a departure is more than an hour in the past" do
let(:departure_time) { now - 65.minutes }
let(:departure_time) { now - 75.minutes }

context "and realtime updates are available showing upcoming departure for the stop time" do
let(:fake_stop_time_update) { OpenStruct.new(departure_time: Interval.for_time(now + 5.minutes)) }
let(:fake_stop_time_update) { OpenStruct.new(departure_time: now + 5.minutes) }

it "it won't show that stop time" do
expect(subject.departures.size).to be_zero
Expand Down
15 changes: 14 additions & 1 deletion spec/requests/departures_request_spec.rb
Expand Up @@ -5,7 +5,13 @@
let(:now) { Time.zone.parse("2015-02-16 17:55:00 -0500") }
let!(:trip) { create(:trip, agency: agency, remote_id: 940135, service: create(:service, agency: agency, monday: true)) }
let!(:stop) { create(:stop, agency: agency, remote_id: "HAMBELi") }
let!(:stop_time) { create(:stop_time, agency: agency, stop: stop, trip: trip, departure_time: Interval.for_time(now + 10.minutes)) }
let!(:stop_times) {
[
create(:stop_time, agency: agency, stop: stop, trip: trip, departure_time: Interval.for_time(now + 10.minutes)),
create(:stop_time, agency: agency, stop: stop, trip: trip, departure_time: Interval.for_time(now + 2.hours)),
create(:stop_time, agency: agency, stop: stop, trip: trip, departure_time: Interval.for_time(now + 2.hours + 1.second)),
]
}

before do
fixture = File.read('spec/fixtures/realtime_updates.buf')
Expand All @@ -29,6 +35,13 @@
end
end

context "with a duration" do
it "returns stops with an end time N hours in the future" do
get "/api/departures", stop_id: stop.id, time: now, duration: 2
expect(json["data"]["departures"].length).to eq(2)
end
end

context "missing a stop id" do
it "responds with a bad request" do
get "/api/departures/?stop_id=&time=#{now}"
Expand Down

0 comments on commit 83f7560

Please sign in to comment.