रेल-मॉडल में केस-असंवेदनशील खोज


211

मेरे उत्पाद मॉडल में कुछ आइटम हैं

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

अब मैं कुछ उत्पाद मापदंडों को किसी अन्य डेटासेट से आयात कर रहा हूं, लेकिन नामों की वर्तनी में विसंगतियां हैं। उदाहरण के लिए, अन्य डेटासेट में,Blue jeans वर्तनी की जा सकती है Blue Jeans

मैं चाहता था Product.find_or_create_by_name("Blue Jeans"), लेकिन यह एक नया उत्पाद बनाएगा, जो पहले के समान है। मेरे विकल्प क्या हैं यदि मैं नीचे के नाम को ढूंढना और तुलना करना चाहता हूं।

प्रदर्शन के मुद्दे वास्तव में यहां महत्वपूर्ण नहीं हैं: केवल 100-200 उत्पाद हैं, और मैं इसे एक माइग्रेशन के रूप में चलाना चाहता हूं जो डेटा आयात करता है।

कोई विचार?

जवाबों:


368

आपको शायद यहाँ और अधिक क्रिया करनी होगी

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

5
@ बॉटबोट की टिप्पणी उपयोगकर्ता इनपुट से तार पर लागू नहीं होती है। "# $ $" रूबी स्ट्रिंग प्रक्षेप के साथ वैश्विक चर से बचने के लिए एक अल्पज्ञात शॉर्टकट है। यह "# {$ $}" के बराबर है। लेकिन स्ट्रिंग प्रक्षेप उपयोगकर्ता-इनपुट स्ट्रिंग्स के लिए नहीं होता है। अंतर देखने के लिए इरब में ये आज़माएं: "$##"और '$##'। पहला इंटरपोल (डबल-कोट्स) है। दूसरा नहीं है। उपयोगकर्ता इनपुट कभी भी प्रक्षेपित नहीं होता है।
ब्रायन मोरारी

5
बस ध्यान देना है कि find(:first)पदावनत किया गया है, और अब विकल्प का उपयोग करना है #first। इस प्रकार,Product.first(conditions: [ "lower(name) = ?", name.downcase ])
लुइ रामालो

2
आपको यह सब काम करने की आवश्यकता नहीं है। का प्रयोग करें निर्मित अरेल पुस्तकालय या Squeel
Dogweather

17
रेल 4 में अब आप कर सकते हैंmodel = Product.where('lower(name) = ?', name.downcase).first_or_create
डेरेक लुकास

1
@DerekLucas रेल 4 में ऐसा करने के लिए संभव है, इस विधि एक अप्रत्याशित व्यवहार का कारण हो सकता है। मान लीजिए कि हमारे पास मॉडल after_createमें कॉलबैक है Productऔर कॉलबैक के अंदर, हमारे पास whereक्लॉज़ है, जैसे products = Product.where(country: 'us')। इस मामले में, whereखंड को गुंजाइश के संदर्भ में कॉलबैक निष्पादित के रूप में जंजीर किया जाता है। सिर्फ आपकी जानकारी के लिए।
एलक्विमिस्टा

100

यह मेरे स्वयं के संदर्भ के लिए रेल में एक पूर्ण सेटअप है। मुझे खुशी है अगर यह आपकी भी मदद करे।

पूछताछ:

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

सत्यापनकर्ता:

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

अनुक्रमणिका (उत्तर -असंवेदनशील अद्वितीय अनुक्रमणिका से Rails / ActiveRecord में उत्तर? ):

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

काश पहले और आखिरी करने के लिए एक और अधिक सुंदर तरीका होता, लेकिन फिर से, रेल और एक्टिवकार्ड खुला स्रोत है, हमें शिकायत नहीं करनी चाहिए - हम इसे स्वयं लागू कर सकते हैं और पुल अनुरोध भेज सकते हैं।


6
PostgreSQL में केस-असंवेदनशील इंडेक्स बनाने के लिए क्रेडिट के लिए धन्यवाद। आपको यह दिखाने के लिए कि रेल में इसका उपयोग कैसे करें! एक अतिरिक्त नोट: यदि आप एक मानक खोजक का उपयोग करते हैं, उदाहरण के लिए find_by_name, यह अभी भी एक सटीक मिलान करता है। आपको अपनी "क्वेरी" पंक्ति के समान कस्टम खोजक लिखना होगा, यदि आप चाहते हैं कि आपकी खोज केस-असंवेदनशील हो।
मार्क बेरी

यह देखते हुए कि find(:first, ...)अब पदावनत कर दिया गया है, मुझे लगता है कि यह सबसे उचित उत्तर है।
उपयोगकर्ता

name.downcase की आवश्यकता है? यह साथ काम करने लगता हैProduct.where("lower(name) = ?", name).first
जॉर्डन

1
@ जोर्डन आपने कोशिश की है कि बड़े अक्षरों वाले नामों के साथ?
oma

1
@ जोर्डन, शायद बहुत महत्वपूर्ण नहीं है, लेकिन हमें SO पर सटीकता के लिए प्रयास करना चाहिए क्योंकि हम दूसरों की मदद कर रहे हैं :)
oma

28

यदि आप Postegres और Rails 4+ का उपयोग कर रहे हैं, तो आपके पास कॉलम प्रकार CITEXT का उपयोग करने का विकल्प है, जो क्वेरी तर्क को लिखने के बिना मामले को असंवेदनशील प्रश्नों की अनुमति देगा।

प्रवास:

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

और इसका परीक्षण करने के लिए आपको निम्नलिखित की अपेक्षा करनी चाहिए:

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

21

आप निम्नलिखित का उपयोग करना चाह सकते हैं:

validates_uniqueness_of :name, :case_sensitive => false

कृपया ध्यान दें कि डिफ़ॉल्ट रूप से सेटिंग है: case_sensitive => झूठी, इसलिए आपको इस विकल्प को लिखने की आवश्यकता नहीं है यदि आपने कई तरीके नहीं बदले हैं।

और अधिक जानकारी प्राप्त करें: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of


5
मेरे अनुभव में, प्रलेखन के विपरीत, case_sensitive डिफ़ॉल्ट रूप से सत्य है। मैंने देखा है कि postgresql में व्यवहार और अन्य लोगों ने mysql में समान रिपोर्ट की है।
ट्रॉय

1
इसलिए मैं पोस्टग्रेज के साथ यह कोशिश कर रहा हूं, और यह काम नहीं करता है। find_by_x केस सेंसिटिव होने के बावजूद ...
Louis Sayers

यह मान्यता केवल मॉडल बनाते समय है। इसलिए यदि आपके डेटाबेस में 'HAML' है, और आप 'haml' जोड़ने का प्रयास करते हैं, तो यह सत्यापन पास नहीं करेगा।
Dudo

14

पोस्टग्रेज में:

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

1
हरकू पर रेल, इसलिए पोस्टग्रेज का उपयोग कर ... ILIKE शानदार है। धन्यवाद!
FeifanZ

निश्चित रूप से PostgreSQL पर ILIKE का उपयोग कर रहा है।
डोम

12

कई टिप्पणियाँ उदाहरण के बिना, Arel का उल्लेख करती हैं।

यहाँ केस-असंवेदनशील खोज का एक उदाहरण दिया गया है:

Product.where(Product.arel_table[:name].matches('Blue Jeans'))

इस प्रकार के समाधान का लाभ यह है कि यह डेटाबेस-अज्ञेयवादी है - यह आपके वर्तमान एडेप्टर के लिए सही SQL कमांड matchesका उपयोग करेगा ( ILIKEपोस्टग्रेज के लिए उपयोग करेगा , और LIKEबाकी सब के लिए)।


9

SQLite प्रलेखन से उद्धरण :

कोई भी अन्य पात्र स्वयं या उसके निचले / ऊपरी मामले के बराबर (यानी केस-असंवेदनशील मिलान) से मेल खाता है

... जो मुझे नहीं पता था। लेकिन यह काम करता है:

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

तो आप ऐसा कुछ कर सकते हैं:

name = 'Blue jeans'
if prod = Product.find(:conditions => ['name LIKE ?', name])
    # update product or whatever
else
    prod = Product.create(:name => name)
end

नहीं #find_or_create, मुझे पता है, और यह बहुत क्रॉस-डेटाबेस के अनुकूल नहीं हो सकता है, लेकिन देखने लायक है?


1
जैसे mysql में केस संवेदी है, लेकिन postgresql में नहीं। मैं Oracle या DB2 के बारे में निश्चित नहीं हूँ। इस बिंदु पर, आप इस पर भरोसा नहीं कर सकते हैं और यदि आप इसका उपयोग करते हैं और आपके बॉस ने आपके अंतर्निहित db को बदल दिया है तो आप एक स्पष्ट कारण के बिना "लापता" रिकॉर्ड रखना शुरू कर देंगे। @ न्यूट्रिनो का निचला (नाम) सुझाव संभवतः इसे संबोधित करने का सबसे अच्छा तरीका है।
मसुकोमी

6

एक और दृष्टिकोण जिसका किसी ने उल्लेख नहीं किया है, वह केस असंवेदनशील खोजकर्ताओं को ActiveRecord :: Base में जोड़ना है। विवरण यहाँ पाया जा सकता है । इस दृष्टिकोण का लाभ यह है कि आपको प्रत्येक मॉडल को संशोधित करने की आवश्यकता नहीं है, और आपको lower()अपने सभी असंवेदनशील प्रश्नों के लिए खंड जोड़ने की आवश्यकता नहीं है , आप इसके बजाय केवल एक अलग खोजक विधि का उपयोग करते हैं।


जब आप जिस पेज को लिंक करते हैं वह मर जाता है, तो आपका जवाब होता है।
एंथनी

जैसा कि @Anthony ने भविष्यवाणी की है, इसलिए यह पारित होने के लिए आया है। लिंक डेड।
XP84

3
@ XP84 मुझे नहीं पता कि यह अब कितना प्रासंगिक है, लेकिन मैंने लिंक को ठीक कर दिया है।
एलेक्स कोरबान

6

ऊपरी और निचले मामले के अक्षर केवल एक बिट से भिन्न होते हैं। उन्हें खोजने के लिए सबसे प्रभावी तरीका इस बिट को अनदेखा करना है, निचले या ऊपरी को बदलना नहीं है, आदि COLLATIONMSSQL के लिए कीवर्ड देखें , देखें NLS_SORT=BINARY_CIकि क्या ओरेकल है, आदि।


4

Find_or_create अब पदावनत हो गया है, आपको इसके बजाय AR संबंध का उपयोग करना चाहिए।

TombolaEntry.where("lower(name) = ?", self.name.downcase).first_or_create(name: self.name)

यह पहली मिलान की गई वस्तु को लौटाएगा, या यदि कोई मौजूद नहीं है तो आपके लिए एक बना देगा।



2

यहाँ बहुत सारे शानदार उत्तर हैं, खासकर @ ओमा के। लेकिन एक और चीज जो आप कोशिश कर सकते हैं वह है कस्टम कॉलम क्रमांकन का उपयोग करना। अगर आपको अपने डीबी में सब कुछ स्टोर किए जाने से कोई दिक्कत नहीं है तो आप बना सकते हैं:

# 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

फिर अपने मॉडल में:

# app/models/my_model.rb
serialize :name, Serializers::DowncasingStringSerializer
validates_uniqueness_of :name, :case_sensitive => false

इस दृष्टिकोण का लाभ यह है कि आप अभी भी सभी नियमित खोजकर्ताओं (सहित find_or_create_by) का उपयोग कस्टम स्कोप, फ़ंक्शंस का उपयोग किए बिना या lower(name) = ?अपने प्रश्नों में कर सकते हैं।

नकारात्मक पक्ष यह है कि आप डेटाबेस में केसिंग जानकारी खो देते हैं।


2

एंड्रयूज के समान है जो # 1 है:

मेरे लिए कुछ काम किया है:

name = "Blue Jeans"
Product.find_by("lower(name) = ?", name.downcase)

यह एक ही क्वेरी में ए #whereऔर करने की आवश्यकता को समाप्त करता है #first। उम्मीद है की यह मदद करेगा!


1

आप नीचे दिए गए स्कोप का भी उपयोग कर सकते हैं और उन्हें एक चिंता में डाल सकते हैं और उन मॉडलों में शामिल कर सकते हैं जिनकी आपको आवश्यकता हो सकती है:

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

तो इस तरह का उपयोग करें: Model.ci_find('column', 'value')


0

यह मानते हुए कि आप mysql का उपयोग करते हैं, आप उन फ़ील्ड्स का उपयोग कर सकते हैं जो संवेदनशील नहीं हैं: http://dev.mysql.com/doc/refman/5.0/en/case-s संवेदनशीलता. html


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

TypeError: Cannot visit Regexp
डोरियन

@ शिलोव धन्यवाद यही वह है जिसकी तलाश में मैं हूं। और यह स्वीकार किए जाते हैं जवाब की तुलना में बेहतर देखा stackoverflow.com/a/2220595/1380867
MZaragoza

मुझे यह समाधान पसंद है, लेकिन आपको "रिवेंजएक्स पर नहीं जा सकता" त्रुटि कैसे मिली? मैं वह भी देख रहा हूं।
गेल

0

कुछ लोग LIKE या ILIKE का उपयोग करके दिखाते हैं, लेकिन वे regex खोजों की अनुमति देते हैं। इसके अलावा आपको रूबी में नीचे जाने की जरूरत नहीं है। आप डेटाबेस को आपके लिए कर सकते हैं। मुझे लगता है कि यह तेज हो सकता है। इसके first_or_createबाद भी इस्तेमाल किया जा सकता है 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"> 


-9

अब तक, मैंने रूबी का उपयोग करके एक समाधान बनाया। इसे उत्पाद मॉडल के अंदर रखें:

  #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

इससे मुझे पहला उत्पाद मिलेगा जहां नाम मेल खाते हैं। या नील।

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

2
यह एक बड़े डेटा सेट के लिए बेहद अक्षम है, क्योंकि इसमें पूरी चीज़ को मेमोरी में लोड करना होता है। जबकि केवल कुछ सौ प्रविष्टियों के साथ आपके लिए कोई समस्या नहीं है, यह अच्छा अभ्यास नहीं है।
लैम्ब्शैनी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.