2017-02-21 2 views
1

Ich habe das folgende ModellWie benutze ich find_by_sql richtig?

class Backup < ActiveRecord::Base 
    belongs_to :component 
    belongs_to :backup_medium 

    def self.search(value) 
    join_tables = "backups, components, backup_media" 
    joins = "backups.backup_medium_id = backup_media.id and components.id = backups.component_id" 
    c = Backup.find_by_sql "select * from #{join_tables} where components.name like '%#{value}%' and #{joins}" 
    b = Backup.find_by_sql "select * from #{join_tables} where backup_media.name like '%#{value}%' and #{joins}" 
    c.count > 0 ? c : b 
    end 
end 

In hebeln, wenn ich Backup.all.class laufen, bekomme ich

=> Backup::ActiveRecord_Relation 

aber wenn ich Backup.search ('xxx'). Klasse laufen, ich bekomme

=> Array 

Da die Suche eine Teilmenge aller zurückgeben sollte, ich glaube, ich brauche eine Active Record_Relation zurückzukehren. Was vermisse ich?

+0

Sie sollten versuchen, das ActiveRecord-Abfrage-Interface zu verwenden, anstatt sich auf 'find_by_sql' zu verlassen, also hätten Sie etwas wie' Backup.joins (: Komponenten,: backup_media) .where ("components.name like '% # { Wert}% ') 'für die erste Abfrage, die eine' Backup :: ActiveRecord_Relation' zurückgeben soll. 'find_by_sql' ist für komplexere Abfragen gedacht als das. –

+0

Mögliches Duplikat von [find \ _by \ _sql rendert ein Array] (http://stackoverflow.com/questions/11115785/find-by-sql-renders-an-array) –

Antwort

1

find_by_sql gibt ein Array von Objekten zurück, kein Relation. Wenn Sie Beziehung zurückkehren wollen für Konsistenz versuchen Sie Ihre Suche neu zu schreiben ActiveRecord api verwenden:

def self.search(value) 
    query = Backup.includes(:component, :backup_medium) 
    by_component_name = query.where("components.name like ?", "'%#{value}%'") 
    by_media_name = query.where("backup_media.name like ?", "'%#{value}%'") 
    by_component_name.any? ? by_component_name : by_media_name 
    end 

oder, wenn Sie noch SQL verwenden möchten, können Sie versuchen, Rekord-IDs zu holen und dann eine zweite Abfrage machen:

def self.search(value) 
    # ... 
    c = Backup.find_by_sql "select id from #{join_tables} where components.name like '%#{value}%' and #{joins}" 
    b = Backup.find_by_sql "select id from #{join_tables} where backup_media.name like '%#{value}%' and #{joins}" 
    ids = c.count > 0 ? c : b 
    Backup.where(id: ids) 
    end 
+0

Ich mag Ihren ersten Vorschlag, aber ich bekomme Beschwerden, weil components.name ist in der Komponenten-Tabelle, aber nicht in Die Backup-Tabelle Backups hat nur die IDs in den anderen beiden Tabellen. –

+0

@PH das Problem war wahrscheinlich in der 'Backup.includes (...)' Bitte werfen Sie einen Blick auf Update-Antwort, sollte es 'Backu sein p.includes (: Komponente,: backup_medium) ' –

1

Aus der Dokumentation:

Führt eine benutzerdefinierte SQL-Abfrage für die Datenbank und gibt die alle Ergebnisse. Die Ergebnisse werden als Array mit den Spalten zurückgegeben, die als Attribute des Modells, das Sie diese Methode aufrufen, als gekapselt angefordert werden. Wenn Sie Product.find_by_sql aufrufen, wird das Ergebnis in einem Product-Objekt mit den Attributen zurückgegeben, die Sie in der SQL-Abfrage angegeben haben.

So erhalten Sie eine Reihe von Backup-Instanzen.

Beachten Sie, dass Sie es wahrscheinlich nicht so machen sollten. Die Verwendung der String-Interpolation in einer Abfrage öffnet Sie für SQL-Injection-Angriffe und bringt Ihnen nichts. Außerdem können Sie mit ActiveRecord-Bereichen eine größere Flexibilität erzielen.

def self.my_includes 
    includes(:components, :backup_media) 
end 

def self.by_component_name(name) 
    media_includes.where("components.name like ?", "'%#{name}%'") 
end 

def self.by_media_name(name) 
    media_includes.where("backup_media.name like ?", "'%#{value}%'") 
end 

def self.search(name) 
    by_component(name).any? ? by_component_name : by_media_name 
end 

Sie können dann rufen

Backup.search(name) 

sowie

Backup.by_component_name(name) 

oder

Backup.by_media_name(name) 
+0

Ich werde versuchen, eine Beispielanwendung mit dieser Lösung neu zu erstellen, und ich werde die Ergebnisse veröffentlichen. –

0

So kann ich die Syntax das Richtige für die media_includes bekommen, aber Inspiriert von Ihrer Lösung ist mir das gelungen mit Joins.

Ich habe ein kleines Demo-Projekt erstellt, das nur den Suchcode anzeigt. Sie können einen Blick auf https://github.com/pamh09/rails-search-demo werfen. Wenn Sie an einer Lösung mitarbeiten möchten, wäre dies effizienter als der Versuch, den gesamten Code hier einzufügen. Das heißt, ich habe eine funktionierende Lösung, wenn Sie lieber nicht stören. Aber ich würde gerne sehen, was die richtige Syntax ist.

Unten ist der Modellcode. Es ist sehr gut möglich, dass ich nur eine Art von syntaktischer Diskrepanz habe, da ich nicht sehr vertraut bin mit dem, wie Schienen seine Datenbank (offensichtlich) magisch machen.

class Backup < ApplicationRecord 
    belongs_to :component 
    belongs_to :backup_medium 


#---- code below does not work --- 
# in pry 
# pry(Backup):1> by_media('bak').any? 
# (0.0ms) SELECT COUNT(*) FROM "backups" WHERE (backup_media = 'bak') 
# ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: backup_media.name: SELECT COUNT(*) FROM "backups" WHERE (backup_media.name = 'bak') 

def self.my_includes 
     includes(:component, :backup_medium) 
    end 

    def self.by_component(name) 
     my_includes.where("components.name = ?", name) 
    end 

    def self.by_media(name) 
     my_includes.where("backup_media.name = ?", name) 
    end 

    def self.search_by(name) 
     by_component(name).any? ? by_component_name : by_media_name 
    end 

# ----- code below works ... call search('string') ----- 
# I was unable to get the like query to work without using #{name} 

    def self.by_component_like(name) 
    # Note: joins (singular).where (plural.column ...) 
     joins(:component).where("components.name like '%#{name}%'") 
    end 

    def self.by_media_like(name) 
     joins(:backup_medium).where("backup_media.name like '%#{name}%'") 
    end 

    def self.search(name) 
     by_component_like(name).any? ? by_component_like(name) : by_media_like(name) 
    end 

end 

Und wie im Code erwähnt. Ich konnte dich nicht verstehen, wie man das benutzt? mit LIKE, da die Abfrage als LIKE '%' xxx '%' anstelle von '% xxx%' eingefügt wird.

+0

Ich bin mir nicht sicher, wie ich user7623429 anstelle von P.H. ??? – user7623429