2015-01-25 12 views
9

konfiguracji: Szyny 3.2.18, PostgreSQLSzyny: Powrót tylko obiekty w którym wszystkie stowarzyszenia spełniają warunek

Mam dwa obiekty, przez wzgląd na przykład, nazwać je Author i Article o następującej konfiguracji:

class Author 
    has_many :articles 

    ... 
end 

class Article 
    belongs_to :author 

    class << self 

    def published_over_one_year_ago 
     where(arel_table[:published_at].lt(1.year.ago)) 
    end 

    end 
end 

Próbuję znaleźć wszystkie rekordy Author, gdzie wszystkie powiązane powiązane są z rekordami Article ponad rok temu. Ten kod:

Author.joins(:article).merge(Article.published_over_one_year_ago) 

... zwraca Author obiektów gdzie przynajmniej jeden związane Article została opublikowana ponad rok temu, ale muszę tylkoAuthor rekordy gdzie wszystkie związane Article rekordy są w ciągu jednego roku stary. Czy jest to możliwe z Arel/ActiveRecord, czy też muszę używać surowego SQL?

Potencjalnie "Working" SQL Rozwiązanie:

query = ERB.new(<<-SQL_END).result(binding) 
    'author'.id in (
    select art.author_id 
    from articles art 
    group by art.author_id 
    having sum(case (art.published_at::date >= current_date) when true then 1 else 0 end) = 0 AND 
     sum(case(art.published_at::date < current_date) when true then 1 else 0 end) > 0 
) 
SQL_END 


# The following now appears to do what I want it to do. 
Author.includes(:articles).where(query) 

UPDATE widzę tam już sugeruje odpowiedź za pomocą Arel, który jest znakomita. Czy ktoś ma sugestię użycia prostego ActiveRecord, jeśli to możliwe?

+0

Ciekawe! Czy masz na myśli surowe rozwiązanie SQL? –

+0

Tak, wygląda na to, że działa. Zobacz edycję. – CDub

Odpowiedz

10

Stosując Arel i wspólny Expression tabeli:

articles = Article.arel_table 
authors = Author.arel_table 
cte_table = Arel::Table.new(:cte_table) 
composed_cte = 
    Arel::Nodes::As.new(cte_table, 
         articles.project(articles[:author_id], 
              articles[:published_at].maximum.as("max_published_at")) 
           .group(articles[:author_id]) 
         ) 

następnie:

authors 
    .join(cte_table).on(cte_table[:author_id].eq(authors[:id])) 
    .with(composed_cte) 
    .where(cte_table[:max_published_at].lt(1.year.ago)) 
    .project(authors[:id]) 
    .to_sql  

odzyski (sformatowaną dla jasności):

WITH "cte_table" AS (
    SELECT articles.author_id, 
      MAX(articles.published_at) AS max_published_at 
    FROM articles 
    GROUP BY articles.author_id 
) 
SELECT authors.id 
FROM authors 
INNER JOIN cte_table 
ON cte_table.author_id = authors.id 
WHERE cte_tble.max_published_at < '2014-01-25 23:04:16.532796' 

używa:

Od

[2] test» Article.pluck(:author_id, :published_at)                                  
D, [2015-01-26T00:10:06.763197 #21897] DEBUG -- : (0.6ms) SELECT "articles"."author_id", "articles"."published_at" FROM "articles" 
=> [ 
    [0] [ 
    [0] 1, 
    [1] Sun, 25 Jan 2015 23:31:58 UTC +00:00 
    ], 
    [1] [ 
    [0] 1, 
    [1] Fri, 01 Jan 1999 00:00:00 UTC +00:00 
    ], 
    [2] [ 
    [0] 2, 
    [1] Fri, 01 Jan 1999 00:00:00 UTC +00:00 
    ] 

powraca zapytań:

[2015-01-26T00:10:25.279587 #21897] DEBUG -- : (0.9ms) WITH "cte_table" AS (SELECT "articles"."author_id", MAX("articles"."published_at") AS max_published_at FROM "articles" GROUP BY "articles"."author_id") SELECT "authors"."id" FROM "authors" INNER JOIN "cte_table" ON "cte_table"."author_id" = "authors"."id" WHERE "cte_table"."max_published_at" < '2014-01-25 23:10:25.277902' 
=> [ 
    [0] { 
    "id" => "2" 
    } 
] 

EDIT

bez użycia Arel:

Author.joins(:articles) 
     .where("authors.id != ANY (?)", 
       Article.select('author_id') 
        .where("published_at > ?", 1.year.ago) 
      ) 

Jeśli dodać .published_last_year do artykułu:

class Article < ActiveRecord::Base 
    belongs_to :author 

    def self.published_last_year 
    where("published_at > ?", 1.year.ago) 
    end 
end 

Następnie

Author.joins(:articles) 
     .where("authors.id != ANY (?)", 
       Article.published_last_year.select('author_id')) 
wersja

Rails4

Author.joins(:articles) 
     .where.not(id: Article.published_last_year.select('author_id')) 
+0

Chwalebna odpowiedź. Jakieś sztuczki w rękawie dla czystej odpowiedzi ActiveRecord? – CDub

+0

@CDub: Zmieniono moją odpowiedź. Niezupełnie czysty AR, ale nie mogę w żaden sposób wyrazić słowa "NOT ANY" w AR3. – Marth