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




acentos postgresql (15)

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?


Answers

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.



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.


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.



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.


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">

En postgres:

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

Probablemente tengas que ser más detallado aquí.

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

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 :)

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).


Otra opción es usar el método de búsqueda de la siguiente manera:

if (referrer.search(new RegExp("Ral", "i")) == -1) { ...

Parece más elegante que la conversión de toda la cadena a minúsculas y puede ser más eficiente.
Con toLowerCase() el código tiene dos pasadas sobre la cadena, una pasada está en toda la cadena para convertirla en minúsculas y otra es buscar el índice deseado.
Con RegExp el código tiene una pasada sobre la cadena que parece coincidir con el índice deseado.

Por lo tanto, en cadenas largas recomiendo usar la versión RegExp (aunque supongo que en cadenas cortas, esta eficiencia viene de la cuenta de la creación RegExp objeto RegExp )





ruby-on-rails activerecord case-insensitive