Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add updated_since to v3 search api #2023

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions app/controllers/api/v3/search.rb
Expand Up @@ -11,6 +11,7 @@ class Search < API::Base
optional :location, type: String, desc: "Location for proximity search", default: "IP"
optional :distance, type: String, desc: "Distance in miles from `location` for proximity search", default: 10
optional :stolenness, type: String, values: %w[non stolen proximity all] + [""], default: "stolen"
optional :updated_since, type: Integer, desc: "Date last updated in unicode date"
optional :query_items, type: Array, desc: "Our Fancy select query items, DO NOT USE, may change without notice", documentation: {hidden: true}
end
params :search do
Expand Down
15 changes: 14 additions & 1 deletion app/models/concerns/bike_searchable.rb
Expand Up @@ -10,6 +10,7 @@ module ClassMethods
# colors: array of colors, friendly found, faster if integers. Overrides query_items if passed explicitly
# manufacturer: friendly found, faster if integer. Overrides query_items if passed explicitly.
# stolenness: can be 'all', 'non', 'stolen', 'found', 'proximity'. Defaults to 'stolen'
# updated_since: can be a date
# location: location for proximity search. Only for stolenness == 'proximity'. 'ip'/'you' uses IP geocoding and returns location object
# distance: distance in miles for matches. Only for stolenness == 'proximity'
# bounding_box: bounding box generated by geocoder. Only for stolenness == 'proximity'
Expand All @@ -20,11 +21,11 @@ def searchable_interpreted_params(query_params, ip: nil)
params[:serial] = SerialNormalizer.new(serial: query_params[:serial]).normalized
params[:raw_serial] = query_params[:serial]
end

params
.merge(searchable_query_items_query(query_params)) # query if present
.merge(searchable_query_items_manufacturer(query_params)) # manufacturer if present
.merge(searchable_query_items_colors(query_params)) # color if present
.merge(searchable_query_items_updated_since(query_params)) # updated_since if present
.merge(searchable_query_stolenness(query_params, ip))
.to_h
end
Expand Down Expand Up @@ -77,6 +78,7 @@ def non_serial_matches(interpreted_params)
# For each of the of the colors, call searching_matching_color_ids with the color_id on the previous ;)
(interpreted_params[:colors] || [nil])
.reduce(self) { |matches, c_id| matches.search_matching_color_ids(c_id) }
.search_matching_update_since(interpreted_params[:updated_since])
.search_matching_stolenness(interpreted_params)
.search_matching_query(interpreted_params[:query])
.where(interpreted_params[:manufacturer] ? {manufacturer_id: interpreted_params[:manufacturer]} : {})
Expand Down Expand Up @@ -125,6 +127,12 @@ def searchable_query_stolenness(query_params, ip)
end
end

def searchable_query_items_updated_since(query_params)
#ignore anything that isn't a unicode integer date
return {updated_since: Time.at(query_params[:updated_since])} if query_params[:updated_since].is_a? Integer
return {}
end

def extracted_query_items_manufacturer_id(query_params)
return query_params[:manufacturer] if query_params[:manufacturer].present?
manufacturer_id = query_params[:query_items]&.select { |i| i.start_with?(/m_/) }
Expand Down Expand Up @@ -170,6 +178,11 @@ def search_matching_color_ids(color_id)
where("primary_frame_color_id=? OR secondary_frame_color_id=? OR tertiary_frame_color_id =?", color_id, color_id, color_id)
end

def search_matching_update_since(updated_since)
return all unless updated_since # So we can chain this if we don't have an updated since date
where("bikes.updated_at >= ?", updated_since)
end

def search_matching_query(query)
query.presence && pg_search(query) || all
end
Expand Down
7 changes: 7 additions & 0 deletions spec/factories/bikes.rb
Expand Up @@ -122,6 +122,13 @@
bike.reload
end
end
factory :older_stolen_bike, traits: [:stolen_trait] do
after(:create) do |bike|
create(:stolen_record, :in_chicago, bike: bike)
bike.update_attributes(:updated_at => Time.now - 1.year)
bike.reload
end
end

trait :organized_bikes do # don't use this trait, use the factories it's included with
transient do
Expand Down
8 changes: 8 additions & 0 deletions spec/models/stolen_bike_listing_spec.rb
Expand Up @@ -21,6 +21,7 @@
let(:stolen_bike_listing1) { FactoryBot.create(:stolen_bike_listing, primary_frame_color: color, listed_at: Time.current - 3.months) }
let(:stolen_bike_listing2) { FactoryBot.create(:stolen_bike_listing, secondary_frame_color: color, tertiary_frame_color: color2, listed_at: Time.current - 2.weeks) }
let(:stolen_bike_listing3) { FactoryBot.create(:stolen_bike_listing, tertiary_frame_color: color, manufacturer: manufacturer) }
let(:stolen_bike_listing4) { FactoryBot.create(:stolen_bike_listing, updated_at: Date.today - 1.year)}
let(:all_color_ids) do
[
stolen_bike_listing1.primary_frame_color_id,
Expand Down Expand Up @@ -57,6 +58,13 @@
expect(StolenBikeListing.search(interpreted_params).pluck(:id)).to eq([stolen_bike_listing3.id])
end
end
context "updated_since" do
let(:query_params) { {updated_since: Date.today - 1.week, stolenness: "all"} }
it "matches just the bikes updated in the past week" do
expect(StolenBikeListing.search(interpreted_params).count === 3)
expect(StolenBikeListing.search(interpreted_params).pluck(:id)).to eq([stolen_bike_listing3.id, stolen_bike_listing2.id, stolen_bike_listing1.id])
end
end
end
end

Expand Down
20 changes: 20 additions & 0 deletions spec/requests/api/v3/search_request_spec.rb
Expand Up @@ -20,6 +20,26 @@
end
end

describe "/" do
let!(:bike) { FactoryBot.create(:bike, updated_at: Time.now - 1.day) }
let!(:bike_old) { FactoryBot.create(:bike, updated_at: Time.now - 1.year) }
let!(:bike_stolen_old) { FactoryBot.create(:older_stolen_bike) }
let!(:bike_stolen) { FactoryBot.create(:stolen_bike, updated_at: Time.now - 1.day) }
let(:query_params) { {updated_since: (Time.now - 1.week).to_i, stolenness: "all"} }
context "with per_page" do
it "returns all matching bikes" do
expect(Bike.count).to eq 4
get "/api/v3/search", params: query_params.merge(format: :json)
expect(response.header["Total"]).to eq("2")
result = JSON.parse(response.body)
expect(result["bikes"][0]["id"]).to eq bike_stolen.id
expect(result["bikes"][1]["id"]).to eq bike.id
expect(response.headers["Access-Control-Allow-Origin"]).to eq("*")
expect(response.headers["Access-Control-Request-Method"]).to eq("*")
end
end
end

describe "/close_serials" do
let!(:bike) { FactoryBot.create(:bike, manufacturer: manufacturer, serial_number: "something") }
let(:query_params) { {serial: "somethind", stolenness: "non"} }
Expand Down