You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

City-state Ruby Gem结合JavaScript动态更新城市功能失效求助

Let's troubleshoot this step by step — I’ve worked with the city-state gem before, so I know a few common pitfalls here. First, let’s break down why your dynamic city update isn’t working, then fix it with clear code examples.

First, Fix Your State Select HTML

Looking at your existing state dropdown code, you’re setting the option value to the state name (<%= value %>), but the city-state gem requires the state abbreviation to fetch cities. Also, you’ll need a dedicated select element for cities that we can update dynamically. Let’s adjust that:

<select id="states-of-country" name="project[state]">
  <option id="no-state" value="" selected>Select Your State</option>
  <% CS.states(:us).each do |abbr, name| %>
    <option value="<%= abbr %>"><%= name %></option>
  <% end %>
</select>

<!-- Add this city select element -->
<select id="cities-of-state" name="project[city]">
  <option value="" selected>Select Your City</option>
</select>

Next, Choose an Approach: Preloaded Data (No AJAX) or AJAX Request

You can’t call Ruby’s CS.cities directly from JavaScript (since Ruby runs server-side and JS runs client-side). Here are two reliable solutions:

Option 1: Preload City Data to the Frontend (Fast, No API Calls)

This works well if you only need US cities — we’ll render all state-city data into a JavaScript object when the page loads, then use that to update the dropdown.

Add this script tag in your view (below the form):

<script>
  // Preload US state-city data into a JS object
  const usCityData = <%= CS.states(:us).transform_values { |name| CS.cities(:us, CS.states(:us).key(name)) }.to_json.html_safe %>;
</script>

Then add this JavaScript to handle the state change:

document.addEventListener('DOMContentLoaded', function() {
  const stateSelect = document.getElementById('states-of-country');
  const citySelect = document.getElementById('cities-of-state');

  // Listen for when the selected state changes
  stateSelect.addEventListener('change', function() {
    const selectedStateAbbr = this.value;
    // Reset city dropdown to default
    citySelect.innerHTML = '<option value="" selected>Select Your City</option>';

    if (selectedStateAbbr) {
      const cities = usCityData[selectedStateAbbr];
      if (cities && cities.length > 0) {
        // Add each city as an option
        cities.forEach(city => {
          const option = document.createElement('option');
          option.value = city;
          option.textContent = city;
          citySelect.appendChild(option);
        });
      } else {
        // Handle states with no city data
        const noCitiesOption = document.createElement('option');
        noCitiesOption.value = "";
        noCitiesOption.textContent = "No cities available";
        citySelect.appendChild(noCitiesOption);
      }
    }
  });
});

Option 2: Fetch Cities via AJAX (Better for Large Datasets)

If you want to avoid loading all city data upfront, create a simple backend endpoint to fetch cities for a selected state.

  1. Add a route in config/routes.rb:
get '/cities/:state_abbr', to: 'projects#cities', as: :cities
  1. Add a controller action in app/controllers/projects_controller.rb:
def cities
  state_abbr = params[:state_abbr]
  cities = CS.cities(:us, state_abbr.to_sym)
  render json: cities
end
  1. Add the JavaScript to handle the AJAX request:
document.addEventListener('DOMContentLoaded', function() {
  const stateSelect = document.getElementById('states-of-country');
  const citySelect = document.getElementById('cities-of-state');

  stateSelect.addEventListener('change', function() {
    const selectedStateAbbr = this.value;
    citySelect.innerHTML = '<option value="" selected>Loading...</option>';

    // Reset if no state is selected
    if (!selectedStateAbbr) {
      citySelect.innerHTML = '<option value="" selected>Select Your City</option>';
      return;
    }

    // Fetch cities from the backend
    fetch(`/cities/${selectedStateAbbr}`)
      .then(response => {
        if (!response.ok) throw new Error('Failed to load cities');
        return response.json();
      })
      .then(cities => {
        citySelect.innerHTML = '<option value="" selected>Select Your City</option>';
        if (cities.length > 0) {
          cities.forEach(city => {
            const option = document.createElement('option');
            option.value = city;
            option.textContent = city;
            citySelect.appendChild(option);
          });
        } else {
          const noCitiesOption = document.createElement('option');
          noCitiesOption.value = "";
          noCitiesOption.textContent = "No cities available";
          citySelect.appendChild(noCitiesOption);
        }
      })
      .catch(error => {
        console.error('Error:', error);
        citySelect.innerHTML = '<option value="" selected>Error loading cities</option>';
      });
  });
});

Common Mistakes to Avoid

  • Using state names instead of abbreviations: The city-state gem’s cities method requires the state abbreviation (e.g., :ny) not the full name.
  • Trying to call Ruby code from JS: Remember, Ruby runs on the server — you can’t directly invoke CS.cities in client-side JavaScript. You need to either preload the data or use AJAX.
  • Missing the city select element: Without a dedicated dropdown for cities, there’s nothing to update!

内容的提问来源于stack exchange,提问作者Sana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:03:22