Generating Restaurant Popular Zones
Purpose
This script recalculates restaurant-to-popular-zone relationships based on distance. Use it when location tag mappings are incorrect or stale.
Prerequisites
- Rails environment loaded
geocodergem available- Admin setting
popular_zone_distance_limit_max_x_kmconfigured
Execution
rails c --production
# paste and run script
Idempotency and rerun behavior
The script can be rerun. It removes and recalculates matching tag mappings. Always test on a subset before running globally.
Tracking arrays
missing: restaurants where nearest popular zone could not be updated.missing_tags: restaurants with no tags in allowed distance range.
Script
missing = []
missing_tags = []
def find_distance_between2(coordinate1, coordinate2)
Geocoder::Calculations.distance_between(coordinate1, coordinate2)
end
def delete_restaurant_tag_restaurant(restaurant, tags_distance)
ActiveRecord::Base.transaction do
tag_ids = tags_distance.select { |_k, v| v }.keys
restaurant.with_lock do
restaurant.restaurant_tags_restaurants.where(restaurant_tag_id: tag_ids).delete_all
end
end
end
def filter_tags_by_distance(restaurant, distance_in_kms)
tags_with_location = RestaurantTag.where.not(lat: nil, lng: nil).pluck(:id, :lat, :lng).to_h { |id, lat, lng| [id, [lat, lng]] }
return tags_with_location.keys if tags_with_location.empty?
tags_distance = tags_with_location.transform_values { |location| find_distance_between2(restaurant.to_coordinates, location) }
delete_restaurant_tag_restaurant(restaurant, tags_distance)
tags_distance.select { |_tag_id, distance| distance <= distance_in_kms }.keys
end
def find_distance_between(tag_lat, tag_lng, restaurant_lat, restaurant_lng)
return 1000 if tag_lat.blank? || tag_lng.blank? || restaurant_lat.blank? || restaurant_lng.blank?
Geocoder::Calculations.distance_between([tag_lat, tag_lng], [restaurant_lat, restaurant_lng])
end
def find_nearest_popular_zone_id(restaurant)
popular_zones = restaurant.restaurant_tags.where_category('PopularZone')
return if popular_zones.blank?
distances = popular_zones.map do |tag|
{ tag_id: tag.id, distance: find_distance_between(tag.lat, tag.lng, restaurant.lat, restaurant.lng) }
end
distances.min_by { |h| h[:distance] }[:tag_id]
end
distance_in_kms = AdminSetting.popular_zone_distance_limit_max_x_km.to_i
Restaurant.find_each do |restaurant|
ActiveRecord::Base.transaction do
filtered_tags = filter_tags_by_distance(restaurant, distance_in_kms)
if filtered_tags.empty?
missing_tags << restaurant.id
next
end
filtered_tags.each do |restaurant_tag_id|
begin
RestaurantTag.find(restaurant_tag_id).restaurants << restaurant
rescue StandardError
next
end
end
primary_location = restaurant.primary_tag_by_category('PopularZone')
nearest_popular_zone_id = find_nearest_popular_zone_id(restaurant)
if nearest_popular_zone_id == primary_location&.restaurant_tag_id || nearest_popular_zone_id.blank?
missing << restaurant.id
next
end
begin
restaurant.primary_tags.build(restaurant_tag_id: nearest_popular_zone_id).save
rescue StandardError
# ignore duplicate/validation errors and continue
end
CleanCompactRestaurantCacheWorker.perform_async(restaurant.id)
end
end
puts "Missing nearest updates: #{missing}"
puts "Missing tags in distance: #{missing_tags}"
Validation
- Sample check restaurants in admin to verify primary popular zone.
- Confirm compact cache refresh jobs run successfully.