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"])
Es posible que desee utilizar lo siguiente:
validates_uniqueness_of :name, :case_sensitive => false
Tenga en cuenta que, de manera predeterminada, la configuración es: case_sensitive => false, por lo que ni siquiera necesita escribir esta opción si no ha cambiado de otra forma.
Encuentre más en: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of
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.
La búsqueda sin distinción de mayúsculas viene incorporada con Rails. Cuenta las diferencias en las implementaciones de bases de datos. Utilice la biblioteca Arel incorporada o una gema como Squeel .
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">
Suponiendo que usa mysql, puede usar campos que no distinguen entre mayúsculas y minúsculas: http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html
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