Rails: Where mit Hash-Bedingung bei Joins

Posted on: Dezember 27th, 2012 by crille No Comments

Viele Wege führen nach Rom. So auch bei Abfragen in Rails. Es gibt 3 verschiedene Wege, um Konditionen zu formulieren:

  1. String
  2. Array
  3. Hash
class User < ActiveRecord::Base
  def self.authenticate_unsafely(user_name, password)
    where("user_name = '#{user_name}' AND password = '#{password}'").first
  end

  def self.authenticate_safely(user_name, password)
    where("user_name = ? AND password = ?", user_name, password).first
  end

  def self.authenticate_safely_simply(user_name, password)
    where(:user_name => user_name, :password => password).first
  end
end

Die String-Methode ist unsicher, da die Daten nicht bereinigt werden und so SQL-Injections möglich sind.

Die Array-Methode ist sicher und erinnert syntaktisch stark an Prepared Statements.

Die Hash-Methode ist ebenfalls sicher, aber typisch Rails, weil einfach: lediglich ein Hash wird übergeben. Der Schlüssel (key) als Symbol entspricht der Spaltenbezeichnungen der Tabelle und der Wert (value) definiert die Bedingung.
Das Schöne an der Hash-Methode ist neben der Sicherheit, dass das Hash wesentlich einfacher dynamisch berechnet werden kann (als die Array-Methode):

def all_by_employee_or_manager(employee_id=nil, manager_id=nil)
  condition = Hash.new
  condition[:employee_id] = employee_id unless employee_id.blank?
  condition[:manager_id] = manager_id unless manager_id.blank?
  where(condition)
end

Das Problem: NoMethodError

Sobald jedoch in der Abfrage gejoint wird, bekommt man bei einer Hash-Bedingung wie

joins(:employees).where(:employees.name => name)

folgenden Fehler:

NoMethodError: undefined method `name' for :employees:Symbol

Das Problem ist, dass das Symbol :employees.name nicht komplett als Symbol interpretiert wird, sondern nur bis zum Punkt.

Bei der Internetrecherche habe ich für gejointe Abfragen nur Konditionen im String- oder Array-Format finden können, was in meinem Anwendungsfall aber nicht praktikabel war, denn die Konditionen mussten dynamisch berechnet werden.

Die Lösung

Um Konditionen in gejointen Abfragen mit der Hash-Methode anzugeben, muss Ruby klar gemacht werden, dass employees.name komplett als Symbol verstanden werden soll. Das geht so:

joins(:employees).where(:"employees.name" => name)

Einfach, wenn gewusst wie!

Leave a Reply