Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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
  • geocoder gem available
  • Admin setting popular_zone_distance_limit_max_x_km configured

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.