Muitos para muitos no Rails: Marcando com Select2

Select2 é uma excelente ferramenta para personalizar caixas de seleção.
Os optionelementos podem ser filtrados na hora, categorizados e estilizados lindamente.

Além disso, ao lidar com a relação muitos para muitos, e especialmente com a relação HABTM, selecionar o valor para um campo pode ser um pé no saco, dependendo do número de opções diferentes que você pode escolher. Ter uma lista que se estende muito para baixo na página não é estético nem prático.

Para mim, o Select2 leva o prêmio entre outras opções de plugin Jquery como Chosen e Token Input . Essas duas opções são abordadas na versão revisada do episódio 258 do Railscasts . Pessoalmente, a documentação do Select2 não é o documento amigável e de implementação em uma etapa que eu escreveria, então esta dica se concentrará nisso.

Primeiramente, nossos dois modelos: Thing e Tag. Este guia assume que você já instalou Select2 em seu aplicativo Rails (veja a gem select2-rails)

class Thing < ActiveRecord::Base
has_and_belongs_to_many
:tags
attr_reader
:tag_tokens

def tag_tokens=(tokens)
self.tag_ids = Tag.ids_from_tokens(tokens)
end
end

class Tag < ActiveRecord::Base
has_and_belongs_to_many
:things

def self.ids_from_tokens(tokens)
tokens
.gsub!(/<<<(.+?)>>>/) { create!(name: $1.capitalize).id }
tokens
.split(',')
end
end

Explicação do código

  • Em Thing.rb

O attr_readerpermite-me brincar com o atributo virtual tag_tokens. Dizemos que é um atributo virtual porque não é realmente parte do modelo, ele não aparece no Esquema.
A função é um método setter para atributo virtual de tokens de tag ou (mais corretamente) o atributo `tag ids`.tag_tokens=

  • Em Tag.rb

Explicarei essa função mais tarde. Por enquanto, vamos fazer uma divisão em uma string com base no caractere de vírgula.


A visualização quando estarei usando Select2 se parece com:

#textos/show.html.erb
<%= form_for(@thing) do |f| %>
<%= f.text_field :name %>
<%= f.text_field :tag_tokens %>
<% end %>

Então, deixe-me dizer o que eu gostaria de fazer . Gostaria de selecionar para me mostrar todas as tags que já possuo, filtrá-las enquanto escrevo, mas também, se eu não conseguir encontrar uma tag, devo ser capaz de criá-la a partir daí.

O objeto select2 é então inicializado:

#textos.js.coffee
$
("#thing_tag_tokens").select2
multiple
: true
data
: window.tags
createSearchChoice
: (term, data) ->
if $(data).filter(->
@text.localeCompare(term) is 0
).length is 0
id
: "<<<" + term + ">>>"
text
: term

Explicação do código

Todas as opções podem ser encontradas na documentação do Select2, mas provavelmente devo falar sobre o objeto.window.tags

Inicializo os dados do objeto Select2 com as instâncias do Tag que já possuo. Eu executo uma chamada Ajax para recuperar todas as Tags, mas com um detalhe. Select2 espera objetos com os campos ide text, assim como um array de objetos com esses campos, onde contém o nome da tag. Vou deixar isso para você descobrir.window.tagstext

O createSearchChoice cria uma nova tag dentro do elemento Select2, ou seja, algo como

{ id: "<<<term>>>", text: "term" }

Mas ainda não está em nosso banco de dados. Precisamos capturar esse tag não criado no controlador e, de alguma forma, criá-lo em nossa tabela de tags. E é exatamente isso que a primeira linha da função, em Tag.rb, faz.self.ids_from_tokens


Finalmente, se você estiver usando Rails 4, você deve permitir o campo tag_tokensem seuthing_params

Avise-me se encontrar algo estranho!