2013-04-05 10 views
13

Ho il seguente modello:Rails Associazioni has_one ultimo disco

class Section < ActiveRecord::Base 
    belongs_to :page 
    has_many :revisions, :class_name => 'SectionRevision', :foreign_key => 'section_id' 
    has_many :references 

    has_many :revisions, :class_name => 'SectionRevision', 
         :foreign_key => 'section_id' 

    delegate :position, to: :current_revision 

    def current_revision 
    self.revisions.order('created_at DESC').first 
    end 
end 

Dove current_revision è la più recente creazione revision. È possibile trasformare current_revision in un'associazione in modo da poter eseguire query come Section.where("current_revision.parent_section_id = '1'")? O dovrei aggiungere una colonna current_revision al mio database invece di provare a crearlo virtualmente o tramite associazioni?

risposta

2

Capisco che desideri ottenere le sezioni in cui l'ultima revisione di ogni sezione ha un parent_section_id = 1;

Ho una situazione simile, in primo luogo, questo è l'SQL (si prega di pensare le categorie come sezioni per voi, post come revisioni e user_id come parent_seection_id -sorry se non lo spostamento il codice per il vostro bisogno, ma devo andare):

SELECT categories.*, MAX(posts.id) as M 
FROM `categories` 
INNER JOIN `posts` 
ON `posts`.`category_id` = `categories`.`id` 
WHERE `posts`.`user_id` = 1 
GROUP BY posts.user_id 
having M = (select id from posts where category_id=categories.id order by id desc limit 1) 

E questa è la query in Rails:

Category.select("categories.*, MAX(posts.id) as M").joins(:posts).where(:posts => {:user_id => 1}).group("posts.user_id").having("M = (select id from posts where category_id=categories.id order by id desc limit 1)") 

questo funziona, è brutto, penso che il modo migliore è quello di "tagliare" la query, ma se si dispone di troppo molte sezioni che sarebbero un problema durante il looping attraverso di esse; puoi anche inserire questa query in un metodo statico e inoltre, la tua prima idea, avere un revision_id all'interno della tua tabella delle sezioni ti aiuterà a ottimizzare la query, ma lascerà cadere la normalizzazione (a volte è necessario), e dovrai essere aggiornando questo campo quando viene creata una nuova revisione per quella sezione (quindi se si stanno facendo molte revisioni in un enorme database potrebbe essere una cattiva idea se si ha un server lento ...)

UPDATE sono tornato hehe, stavo facendo alcuni test, e controllare questo fuori:

def last_revision 
    revisions.last 
end 

def self.last_sections_for(parent_section_id) 
    ids = Section.includes(:revisions).collect{ |c| c.last_revision.id rescue nil }.delete_if {|x| x == nil} 

    Section.select("sections.*, MAX(revisions.id) as M") 
     .joins(:revisions) 
     .where(:revisions => {:parent_section_id => parent_section_id}) 
     .group("revisions.parent_section_id") 
     .having("M IN (?)", ids) 
end 

ho fatto questa domanda e ha lavorato con i miei tavoli (spero chiamato bene la params, è la stessa query di Rails di prima, ma cambio la query nell'avere per l'ottimizzazione); fai attenzione al gruppo; la include lo rende ottimale in grandi dataset, e mi spiace di non aver trovato un modo per creare una relazione con has_one, ma vorrei farlo, ma anche riconsiderare il campo che hai menzionato all'inizio.

+0

Impressionante, grazie! –

14

È possibile modificarlo in associazione, ma in genere, l'ordine per l'associazione has_one o belongs_to viene sempre interpretato erroneamente quando utilizzato su query. Nella tua domanda, quando si accende che in un'associazione, che sarebbe

has_one :current_revision, class_name: 'SectionRevision', foreign_key: :section_id, order: 'created_at DESC' 

Il problema di questo è che quando si tenta di combinare questo con altre query, normalmente vi darà il record errato.

>> record.current_revision 
    # gives you the last revision 
>> record.joins(:current_revision).where(section_revisions: { id: 1 }) 
    # searches for the revision where the id is 1 ordered by created_at DESC 

Quindi vi suggerisco di aggiungere un current_revision_id invece.

+0

Bello questo è esattamente quello che stavo cercando! –

+0

Se si dispone di un vincolo null su 'section_revisions.section_id', assicurati di non usare l'helper has_one 'record.create_current_revision' (o' record.build_current_revision') o otterrai 'Impossibile rimuovere il current_revision associato esistente. Il record non è riuscito a salvare dopo che la sua chiave esterna è stata impostata su zero. Invece, usa sempre 'record.section_revisions.create', che sarà il nuovo' current_revision' –

21

Per ottenere l'ultima su un has_many, si vorrebbe fare qualcosa di simile a @jvnill, tranne aggiungere un ambito con un ordinamento per l'associazione:

has_one :current_revision, -> { order created_at: :desc }, 
    class_name: 'SectionRevision', foreign_key: :section_id 

Questo farà sì che si ottiene la versione più recente dal database.