ignorar acentos ruby-on-rails activerecord case-insensitive

ruby on rails - acentos - Búsqueda de mayúsculas y minúsculas en el modelo Rails



ignorar acentos postgresql (17)

Mi modelo de producto contiene algunos artículos

Product.first => #<Product id: 10, name: "Blue jeans" >

Ahora estoy importando algunos parámetros del producto de otro conjunto de datos, pero hay inconsistencias en la ortografía de los nombres. Por ejemplo, en el otro conjunto de datos, Blue jeans podría deletrear Blue Jeans .

Quería Product.find_or_create_by_name("Blue Jeans") , pero esto creará un nuevo producto, casi idéntico al primero. ¿Cuáles son mis opciones si quiero encontrar y comparar el nombre en minúsculas?

Los problemas de rendimiento no son realmente importantes aquí: solo hay 100-200 productos, y quiero ejecutar esto como una migración que importa los datos.

¿Algunas ideas?


Algunas personas muestran usando LIKE o ILIKE, pero esas permiten búsquedas de expresiones regulares. Además, no es necesario bajar en Ruby. Puedes dejar que la base de datos lo haga por ti. Creo que puede ser más rápido. También se puede usar first_or_create después de where .

# app/models/product.rb class Product < ActiveRecord::Base # case insensitive name def self.ci_name(text) where("lower(name) = lower(?)", text) end end # first_or_create can be used after a where clause Product.ci_name("Blue Jeans").first_or_create # Product Load (1.2ms) SELECT "products".* FROM "products" WHERE (lower(name) = lower(''Blue Jeans'')) ORDER BY "products"."id" ASC LIMIT 1 # => #<Product id: 1, name: "Blue jeans", created_at: "2016-03-27 01:41:45", updated_at: "2016-03-27 01:41:45">


Citando de la documentación de SQLite :

Cualquier otro carácter coincide con sí mismo o con su equivalente en mayúsculas / minúsculas (es decir, coincidencia que no distingue mayúsculas y minúsculas)

... que no sabía. Pero funciona:

sqlite> create table products (name string); sqlite> insert into products values ("Blue jeans"); sqlite> select * from products where name = ''Blue Jeans''; sqlite> select * from products where name like ''Blue Jeans''; Blue jeans

Así que podrías hacer algo como esto:

name = ''Blue jeans'' if prod = Product.find(:conditions => [''name LIKE ?'', name]) # update product or whatever else prod = Product.create(:name => name) end

No #find_or_create , lo sé, y puede que no sea muy #find_or_create varias bases de datos, pero vale la pena verlo.


En postgres:

user = User.find(:first, :conditions => [''username ~* ?'', "regedarek"])



Esta es una configuración completa en Rails, para mi propia referencia. Estoy feliz si te ayuda también.

la consulta:

Product.where("lower(name) = ?", name.downcase).first

el validador

validates :name, presence: true, uniqueness: {case_sensitive: false}

el índice (respuesta del índice único insensible a mayúsculas en Rails / ActiveRecord? ):

execute "CREATE UNIQUE INDEX index_products_on_lower_name ON products USING btree (lower(name));"

Me gustaría que hubiera una forma más bella de hacer la primera y la última, pero nuevamente, Rails y ActiveRecord son de código abierto, no deberíamos quejarnos, podemos implementarlo nosotros mismos y enviar una solicitud de extracción.


Find_or_create ahora está en desuso, debe usar una relación AR en lugar más first_or_create, así:

TombolaEntry.where("lower(name) = ?", self.name.downcase).first_or_create(name: self.name)

Esto devolverá el primer objeto coincidente, o creará uno para usted si no existe ninguno.


Hasta ahora, hice una solución usando Ruby. Coloque esto dentro del modelo del producto:

#return first of matching products (id only to minimize memory consumption) def self.custom_find_by_name(product_name) @@product_names ||= Product.all(:select=>''id, name'') @@product_names.select{|p| p.name.downcase == product_name.downcase}.first end #remember a way to flush finder cache in case you run this from console def self.flush_custom_finder_cache! @@product_names = nil end

Esto me dará el primer producto donde coinciden los nombres. O nada.

>> Product.create(:name => "Blue jeans") => #<Product id: 303, name: "Blue jeans"> >> Product.custom_find_by_name("Blue Jeans") => nil >> Product.flush_custom_finder_cache! => nil >> Product.custom_find_by_name("Blue Jeans") => #<Product id: 303, name: "Blue jeans"> >> >> #SUCCESS! I found you :)


Hay muchas respuestas geniales aquí, particularmente las de @ oma. Pero otra cosa que podría intentar es usar la serialización de columnas personalizada. Si no le importa que todo esté almacenado en minúsculas en su base de datos, puede crear:

# lib/serializers/downcasing_string_serializer.rb module Serializers class DowncasingStringSerializer def self.load(value) value end def self.dump(value) value.downcase end end end

Luego en tu modelo:

# app/models/my_model.rb serialize :name, Serializers::DowncasingStringSerializer validates_uniqueness_of :name, :case_sensitive => false

El beneficio de este enfoque es que aún puede usar todos los buscadores regulares (incluido find_or_create_by ) sin usar ámbitos personalizados, funciones, o tener lower(name) = ? en sus consultas.

El inconveniente es que se pierde información de la carcasa en la base de datos.



Las letras mayúsculas y minúsculas difieren solo en un bit: la forma más eficiente de buscarlas es ignorar este bit, no convertir las letras inferiores o superiores, etc. Consulte las palabras clave COLLATION para MS SQL, consulte NLS_SORT = BINARY_CI si usa Oracle. etc.


Otro enfoque que nadie ha mencionado es agregar buscadores que no distinguen entre mayúsculas y minúsculas en ActiveRecord :: Base. Los detalles se pueden encontrar here . La ventaja de este enfoque es que no tiene que modificar todos los modelos, y no tiene que agregar la cláusula lower() a todas las consultas que no distinguen entre mayúsculas y minúsculas, sino que simplemente utiliza un método de búsqueda diferente.


Probablemente tengas que ser más detallado aquí.

name = "Blue Jeans" model = Product.where(''lower(name) = ?'', name.downcase).first model ||= Product.create(:name => name)


Si está utilizando Postegres y Rails 4+, entonces tiene la opción de usar el tipo de columna CITEXT, que permitirá consultas que no distinguen entre mayúsculas y minúsculas sin tener que escribir la lógica de consulta.

La migración:

def change enable_extension :citext change_column :products, :name, :citext add_index :products, :name, unique: true # If you want to index the product names end

Y para probarlo debes esperar lo siguiente:

Product.create! name: ''jOgGers'' => #<Product id: 1, name: "jOgGers"> Product.find_by(name: ''joggers'') => #<Product id: 1, name: "jOgGers"> Product.find_by(name: ''JOGGERS'') => #<Product id: 1, name: "jOgGers">



También puede usar ámbitos como este a continuación y ponerlos en una preocupación e incluirlos en los modelos que pueda necesitar:

scope :ci_find, lambda { |column, value| where("lower(#{column}) = ?", value.downcase).first }

Luego, use así: Model.ci_find(''column'', ''value'')


Varios comentarios se refieren a Arel, sin dar un ejemplo.

Aquí hay un ejemplo de Arel de una búsqueda que no distingue entre mayúsculas y minúsculas:

Product.where(Product.arel_table[:name].matches(''Blue Jeans''))

La ventaja de este tipo de solución es que es ILIKE de la base de datos: utilizará los comandos SQL correctos para su adaptador actual (las matches usarán ILIKE para Postgres y LIKE para todo lo demás).


user = Product.where(email: /^#{email}$/i).first