From d4278a2f7f4c6771b7667e9f53c246de487f6dee Mon Sep 17 00:00:00 2001 From: Lara Ferroni Date: Thu, 22 Jul 2021 17:31:27 -0700 Subject: [PATCH] add updated_since to v3 search api --- app/controllers/api/v3/search.rb | 1 + app/models/concerns/bike_searchable.rb | 15 ++++++++++++++- spec/factories/bikes.rb | 7 +++++++ spec/models/stolen_bike_listing_spec.rb | 8 ++++++++ spec/requests/api/v3/search_request_spec.rb | 20 ++++++++++++++++++++ 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v3/search.rb b/app/controllers/api/v3/search.rb index 5c425e4b9e..6fd401bd7a 100644 --- a/app/controllers/api/v3/search.rb +++ b/app/controllers/api/v3/search.rb @@ -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 diff --git a/app/models/concerns/bike_searchable.rb b/app/models/concerns/bike_searchable.rb index 4f9be772a5..8cb3d780a4 100644 --- a/app/models/concerns/bike_searchable.rb +++ b/app/models/concerns/bike_searchable.rb @@ -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' @@ -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 @@ -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]} : {}) @@ -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_/) } @@ -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 diff --git a/spec/factories/bikes.rb b/spec/factories/bikes.rb index 53b783d189..567d723879 100644 --- a/spec/factories/bikes.rb +++ b/spec/factories/bikes.rb @@ -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 diff --git a/spec/models/stolen_bike_listing_spec.rb b/spec/models/stolen_bike_listing_spec.rb index cc0a2a5b54..c3dae9e760 100644 --- a/spec/models/stolen_bike_listing_spec.rb +++ b/spec/models/stolen_bike_listing_spec.rb @@ -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, @@ -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 diff --git a/spec/requests/api/v3/search_request_spec.rb b/spec/requests/api/v3/search_request_spec.rb index ea63393e65..f3c4e3fc40 100644 --- a/spec/requests/api/v3/search_request_spec.rb +++ b/spec/requests/api/v3/search_request_spec.rb @@ -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"} }