Búsqueda insensible a mayúsculas / minúsculas en el modelo Rails

Mi modelo de producto contiene algunos artículos

Product.first => # 

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úscula?

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

¿Algunas ideas?

Probablemente tendrás que ser más prolijo aquí

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

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 / minúsculas en Rails / ActiveRecord? ):

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

Ojalá hubiera una manera más hermosa de hacer la primera y la última, pero, de nuevo, Rails y ActiveRecord es de código abierto, no deberíamos quejarnos, podemos implementarlo nosotros mismos y enviar una solicitud de extracción.

Es posible que desee utilizar lo siguiente:

 validates_uniqueness_of :name, :case_sensitive => false 

Tenga en cuenta que, de forma predeterminada, la configuración es: case_sensitive => false, por lo que no necesita escribir esta opción si no ha cambiado otras formas.

Encuentre más en: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of

Si está utilizando Postegres y Rails 4+, entonces tiene la opción de utilizar el tipo de columna CITEXT, que permitirá consultas sin distinción entre mayúsculas y minúsculas sin tener que escribir la lógica de la 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, debe esperar lo siguiente:

 Product.create! name: 'jOgGers' => # Product.find_by(name: 'joggers') => # Product.find_by(name: 'JOGGERS') => # 

En postgres:

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

Citando de la documentación de SQLite :

Cualquier otro carácter se empareja a sí mismo o su equivalente inferior / superior (es decir, coincidencia insensible a 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 

Entonces 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 amigable con las bases de datos cruzadas, ¿pero vale la pena mirarlo?

Las letras mayúsculas y minúsculas difieren solo en un solo bit; la forma más eficiente de buscarlas es ignorar este bit, no convertirlo en inferior o superior, etc. Ver COLLATION de palabras clave para MS SQL, ver NLS_SORT = BINARY_CI si se usa Oracle, etc.

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

Aquí hay un ejemplo de Arel de una búsqueda insensible a 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).

Otro enfoque que nadie ha mencionado es agregar localizadores insensibles a mayúsculas y minúsculas en ActiveRecord :: Base. Los detalles se pueden encontrar aquí . 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 sus consultas que no distinguen entre mayúsculas y minúsculas, sino que simplemente utiliza un método de buscador diferente.

Find_or_create ahora está en desuso, debe usar una relación de AR en su 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.

La búsqueda insensible a mayúsculas viene integrada con Rails. Representa las diferencias en las implementaciones de bases de datos. Utilice la biblioteca Arel incorporada o una gem como Squeel .

Aquí hay muchas respuestas excelentes, especialmente @ oma’s. Pero otra cosa que podría intentar es usar la serialización de columnas personalizadas. Si no te importa que todo esté almacenado en minúsculas en tu base de datos, puedes 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 

Entonces 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 todavía puede usar todos los buscadores regulares (incluido find_or_create_by ) sin usar ámbitos personalizados, funciones o tener lower(name) = ? en tus consultas.

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

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 esto: Model.ci_find('column', 'value')

Suponiendo que use mysql, podría usar campos que no distinguen entre mayúsculas y minúsculas: http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html

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

Algunas personas muestran que usan LIKE o ILIKE, pero permiten búsquedas de expresiones regulares. Además, no es necesario esconderse en Ruby. Puede dejar que la base de datos lo haga por usted. Creo que puede ser más rápido. También first_or_create se puede usar 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 # => # 

Hasta ahora, hice una solución usando Ruby. Coloque esto dentro del modelo de 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 los nombres coinciden. O nil.

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