2013-12-22 1 views
0

У меня есть два класса activerecords, User и UserSkill. UserSkill имеет squeel просеиватель, как показано ниже:ActiveRecords Присоединиться к условиям: как присоединиться к одной таблице с разными наборами условий с использованием squeel sifters?

class User < ActiveRecord::Base 
    has_many :user_skills 
end 

и

class UserSkill < ActiveRecord::Base 
    belongs_to :user 
    belongs_to :skill 

    sifter :with_skill_id_and_value_of_at_least do |params| 
    if params[1].present? 
     skill_id.eq(params[0]) & value.gteq(params[1]) 
    else 
     skill_id.eq(params[0]) 
    end 
    end 
end 

Я реализующий поиск в данный момент, и иметь это в контроллере:

def search 
    users = User.skip_user(current_user) 

    if params[:user_skills].present? 
     params[:user_skills].each do |user_skill| 
     #user_skill is an array where [0] is the id, and [1] the minimum value. 
     #if [1] is empty, it will default to 0 in the sifter 

     users = users.joins(:user_skills).where do 
     { 
      user_skills: sift(:with_skill_id_and_value_of_at_least, user_skill) 
     } 
     end 
     end 
    end 
    end 

моя проблема заключается в том, что в основном, когда применяется отношение, я заканчиваю следующим кодом:

SELECT "users".* FROM "users" 
    INNER JOIN "user_skills" ON "user_skills"."user_id" = "users"."id" 
    WHERE "user_skills"."skill_id" = 10 
    AND (("user_skills"."skill_id" = 11 AND "user_skills"."value" >= 2)) 

, который не то, что я хочу:

на самом деле, я хочу, чтобы пользователи, которые имеют user_skill с skill_id 10 и которые имеют также user_skill с skill_id 11 и значения выше, чем 2.

ActiveRecords, похоже, «цепочки» условий, и я всегда получаю пустое отношение, поскольку UserSkill не может иметь идентификатор 10 и 11 в одно и то же время!

Я хотел бы как-то различать первое и второе условие, чтобы получить пользователей с user_skills, которые удовлетворяют первому условию и второму условию в последовательности, а не для одной и той же строки user_skill, и я не конечно, как добиться этого!

Я думаю, что псевдонимы будут способом, но как я могу указать другой псевдоним для каждого из user_skill, с которым я хочу ссылаться, используя сиквел Squeel или ActiveRecord?

Я бы предположил, что мой вопрос очень похож на this one, однако, поскольку я использую цикл, я не думаю, что смогу использовать предлагаемое решение!

редактировать: Я также нашел это на squeel GitHub, но не знаете, как применить его к моему конкретному прецеденту: Need a way to alias joins for multiple joins on the same relation

Последнее редактирование:

Я написал решение в SQL, который работает , но, как вы можете видеть, что это очень некрасиво:

 params[:user_skills].each_with_index do |user_skill,index| 
     users = users.joins(",user_skills as u#{index}") 
         .where("u#{index}.skill_id= ? AND u#{index}.value >= ?" 
         ,user_skill[0].to_i, user_skill[1].to_i) 
     end 

ответ

0

Это тот случай, когда вам нужно взять пересечение множеств пользователей. Другими словами, вам нужно применить «И» к наборам пользователей, а не к условиям where. Когда вы связываете where условий, вы «устанавливаете» условия, поэтому вы видите результаты, которые видите.

Вы можете сделать это либо в Ruby, либо в SQL, используя основную технику, описанную в Intersection of two relations. Для простоты я проиллюстрирую оба здесь, используя два набора, и оставьте расширение более чем на два набора и попросите вас.

Во-первых, давайте определим два множества:

set1 = User.joins(:user_skills).where('user_skills.skill_id = 10') 
set2 = User.joins(:user_skills).where('user_skills.skill_id = 11 and user_skills.value >= 2') 

Примечание: Ваш пример текста сказал, что «значение выше, чем 2», но ваш код выражается «значение больше или равную 2». Я, очевидно, использовал последнюю интерпретацию выше.

Если вы хотите взять пересечение в Ruby, вы берете на себя пересечение результирующих массивов, используя оператор & следующим образом:

set1 & set2 

В этом случае, вы делаете два отдельных запросов SQL и взяв пересечение результатов.

Если вы хотите взять пересечение с помощью SQL, необходимо составить SQL, который может быть сделано с помощью методики, описанной в this answer from @MikDiet к вышеуказанному вопросу:

User.connection.unprepared_statement do 
    User.find_by_sql "#{set1.to_sql} INTERSECT #{set2.to_sql}" 
end 

В этом случае вы выполняете один запрос, хотя этот запрос, очевидно, делает «больше работы». Вы должны прочитать исходный ответ, чтобы понять причину использования unprepared_statement.

Я оставлю расширение на squeel и больше, чем два набора к вам, за исключением отметить, что вы можете использовать оператор SQL INTERSECT более двух операндов следующим образом:

query1 INTERSECT query2 INTERSECT query3 ... 
+0

Большое спасибо питер для очень подробные ответы - как вы можете видеть из моего последнего редактирования, генерация псевдонимов для соединения с использованием индекса цикла, похоже, работает, но выглядит немного дерьмовым - я думаю, что это может быть намного чище, если я использую пересечение в том, как вы описали, где set1 и set2 - просеиватели squeel - я пойду на это и дам вам знать, как это происходит! – mulus

 Смежные вопросы

  • Нет связанных вопросов^_^