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


Mi modelo de producto contiene algunos elementos

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

Ahora estoy importando algunos parámetros de 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 escribirse 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.

¿Alguna idea?

Author: Jesper Rønn-Jensen, 2010-02-08

17 answers

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

name = "Blue Jeans"
model = Product.where('lower(name) = ?', name.downcase).first 
model ||= Product.create(:name => name)
 327
Author: alex.zherdev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-13 20:41:05

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

La consulta:

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

El validador:

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

El índice (respuesta desde Índice único insensible a mayúsculas y minú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 hermosa de hacer el primero y el último, 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.

 92
Author: oma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:36

Es posible que desee utilizar lo siguiente:

validates_uniqueness_of :name, :case_sensitive => false

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

Más información en: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of

 21
Author: Sohan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-29 20:01:25

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 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 usted debe 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">
 19
Author: Viet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-18 20:07:19

En postgres:

 user = User.find(:first, :conditions => ['username ~* ?', "regedarek"])
 13
Author: tomekfranek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-04 18:44:45

Citando la documentación de SQLite :

Cualquier otro carácter coincide consigo mismo o su equivalente en mayúsculas/minúsculas (i. e. coincidencia insensible a mayúsculas y minúsculas)

...lo cual 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 amigable con las bases de datos cruzadas, pero vale la pena mirar?

 9
Author: Mike Woodhouse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-08 10:04:01

Varios comentarios se refieren a Arel, sin proporcionar 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 independiente de la base de datos: usará los comandos SQL correctos para su adaptador actual (matches usará ILIKE para Postgres, y LIKE para todo lo demás).

 7
Author: Brad Werth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-21 01:14:38

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 inferior o superior, etc.. Vea LA RECOPILACIÓN de palabras clave para MS SQL, vea NLS_SORT = BINARY_CI si usa Oracle, etc..

 6
Author: Dean Radcliffe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-29 18:32:21

Otro enfoque que nadie ha mencionado es agregar buscadores 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 insensibles a mayúsculas y minúsculas, solo usa un método finder diferente en su lugar.

 5
Author: Alex Korban,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-24 22:13:19

Find_or_create ahora está obsoleto, deberías usar una relación 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.

 4
Author: superluminary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-15 17:09:28

La búsqueda insensible a mayúsculas y minúsculas viene incorporada con Rails. Tiene en cuenta las diferencias en las implementaciones de bases de datos. Utilice la biblioteca Arel incorporada, o una gema como Squeel.

 2
Author: Dogweather,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-06 20:53:26

Hay muchas respuestas geniales aquí, particularmente las de @oma. Pero otra cosa que podrías intentar es usar la serialización de columnas personalizadas. Si no le importa que todo esté almacenado en minúsculas en su base de datos, entonces podría 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.

La desventaja es que se pierde la cubierta información en la base de datos.

 2
Author: Nate Murray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-09 17:14:43

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

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

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

 1
Author: Damian Simon Peter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-12 00:39:39

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

 0
Author: marcgg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-08 09:37:55
user = Product.where(email: /^#{email}$/i).first
 0
Author: shilovk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-02 16:56:12

Algunas personas muestran el uso de LIKE o ILIKE, pero que permiten búsquedas regex. Además, no es necesario descargar 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"> 
 0
Author: 6ft Dan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-27 01:52:38

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 los nombres coinciden. 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 :)
 -7
Author: Jesper Rønn-Jensen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-08 09:38:36