Preenchendo ComboBox Com Cidades De Acordo Com O Estado Selecionado Em Rails

Vamos criar nossa aplicação e para ir mais rapido o scaffold de city, state e user.

rails new cities_by_state
rails g scaffold state name:string
rails g scaffold city name:string state:references
rails g scaffold user name:string city:references
rake db:migrate
rm public/index.html

Abra o arquivo config/routes.rb e defina a pagina de user como inicial:

root :to => 'users#index'

Agora vamos starta a aplicação lol:

rails s

Abra agora o arquivo db/seeds.rb e adicione as seguinte linhas, para popular nosso banco:

1
2
3
4
5
6
7
states = State.create([{:name => 'Piauí'}, {:name => 'Maranhão'}, {:name => 'Pernambuco'}])

City.create([
              {:name => 'Ribeiro Gonçalves', :state => states.first},
              {:name => 'Uruçuí', :state => states.first},
              {:name => 'Teresina', :state => states.first}
          ])

Execute o seguinte comando no terminal:

rake db:seed

Agora vamos abrir o arquivo _form.html.erb de user e colocar o combobox de state e city:

1
2
3
4
5
6
7
8
9
<div class="field">
    <%= f.label :state %><br />
    <%= collection_select(:state ,:id, State.all, :id, :name, :include_blank => 'SELECT A STATE') %>
  </div>

<div class="field">
   <%= f.label :city %><br />
   <%= f.collection_select :city_id, City.all, :id, :name, :include_blank => 'SELECT A CITY' %>
</div>

Ainda não esta como queremos, então vamos fazer o metodo que irá buscar as cidades quando o combobox de estados for modificado, abrimos então o controller cities e adicionamos o seguinte código:

1
2
3
4
5
6
7
8
9
  def cities_by_state
    state_id = params[:id].to_i
    cities = City.where(:state_id => state_id)
    cty = []
    cities.each do |city|
      cty << {:id => city.id, :n => city.name}
    end
    render :json => {:cty => cty.compact}.as_json
  end

Uma pequena modificação no arquivo config/routes.rb é necessária, pois temos que adicionar a rota para nosso metodo criado:

match "/cities_by_state" => "cities#cities_by_state"

O metodo cities_by_state simplesmente recebe um parametro enviado via js e faz uma busca de todas as cidades em relação ao id do estado que foi enviado, retornando um arquivo json com o id e nome das cidades.

Quase finalizando criaremos agora nossos metodos js para fazer a busca e preenchimento ao modificar o combobox de estado. Adicione no final do arquivo _form.html.erb de user:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script type="text/javascript">
  $(document).ready(function() {
   $("#state_id").change(function() {
      getCitiesByState("id="+$("#state_id").val());
    });
  });

  function getCitiesByState(id) {
    $.getJSON("/cities_by_state", id, function(j) {
      var options = '<option value="">SELECT A CITY</option>';
      $.each(j.cty, function(i, item) {
        options += '<option value="' + item.id + '">' + item.n + '</option>';
      });
      $("#user_city_id").html(options);
    });
  }
</script>

O primeiro metodo chama o segundo se o estado do combobox for modificado. O segundo metodo retorna as cidades referente ao estado selecionado no combobox e preenche o combobox com suas cidades.

Agora finalizando vamos modificar o combobox de cities para não exibir as cidades ao entar no form de usuarios, esperando o usuario selecionar primeiro um estado. O combobox deve ficar assim:

1
2
3
4
<div class="field">
   <%= f.label :city %><br />
   <%= f.collection_select :city_id, [], :id, :name, :include_blank => 'SELECT FIRST STATE' %>
</div>

Simples e fácil lol.

Comments