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?
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)
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.
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
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">
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"])
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?
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).
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..
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.
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.
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.
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.
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')
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
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
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">
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 :)
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