2015-12-07 13 views
7

Sto creando fluidi utilizzandorotto creare, salvare, aggiornare e distruggere il Join record quando utilizza Postgres UUID in Rails

create_table :users, { id: false } do |t| 
    t.uuid :uid, default: 'uuid_generate_v4()' 
    ... other columns 

e impostando self.primary_key = :uid nei modelli.

In generale questo funziona correttamente con ActiveRecord e scrivo le associazioni has_many e belongs_to. Tuttavia, quando si attraversa una tabella di join (cioè has_many ... through:, ho bisogno di scrivere SQL personalizzato per ottenere i record.

ho capito che posso in generale farlo scrivendo SQL personalizzata, vale a dire SELECT * FROM main_table JOIN join_table ON main_table.uid = cast(join_table.uid AS uuid) WHERE condition=true)

I' ve appena realizzato che ActiveRecord di create, destroy, save e update non lavoro sul modello di unirsi.

ho rattoppato i quattro metodi in modo da lavorare, ma è troppo complesso una sequenza per i miei gusti e probabilmente unoptimal. Qui sono i miei patch:

A volte, save emette una ROLLBACK la prima volta senza spiegazione. Quindi funziona la seconda volta. In altre situazioni (non sono sicuro del motivo, probabilmente durante l'aggiornamento), la prima volta che viene eseguita correttamente, ma se richiamata una seconda volta genera un errore TypeError. Vedere here per un'altra domanda su questo errore che non ha alcuna risposta su come salvare un join quando si utilizza uid anziché id. Ecco le mie altre patch (di lavoro).

def create(*args) 
    attrs = args[0] 
    raise(ArgumentError, "invalid args to bucket list create") unless attrs.is_a?(Hash) 
    bucket_list_photo = self.class.new(
     attrs 
    ) 
    bucket_list_photo.save 
    bucket_list_photo = BucketListPhoto.find_by(
     bucket_list_uid: bucket_list_photo.bucket_list_uid, 
     photo_uid: bucket_list_photo.photo_uid 
    ) 
    return bucket_list_photo 
end 

def update(*args) 
    # similar bug to save 
    attrs = args[0] 
    raise(ArgumentError, "invalid args to bucket list update") unless attrs.is_a?(Hash) 
    bucket_list_uid = self.bucket_list_uid 
    photo_uid = self.photo_uid 
    due_at = self.due_at 
    self.destroy 
    bucket_list_photo = self.class.new(
     { 
      bucket_list_uid: bucket_list_uid, 
      photo_uid: photo_uid, 
      due_at: due_at 
     }.merge(attrs) 
    ) 
    bucket_list_photo.save 
    bucket_list_photo = self.class.find_by(
     photo_uid: photo_uid, 
     bucket_list_uid: bucket_list_uid 
    ) 
    return bucket_list_photo # phew 
end 

def destroy 
    # patching to fix an error on #destroy, #destroy_all etc. 
    # the problem was apparently caused by custom primary keys (uids) 
    # see https://stackoverflow.com/a/26029997/2981429 
    # however a custom fix is implemented here 
    deleted_uids = ActiveRecord::Base.connection.execute(
     "DELETE FROM bucket_list_photos WHERE uid='#{uid}' RETURNING uid" 
    ).to_a.map { |record| record['uid'] } 
    raise "BucketListPhoto not deleted" unless (
     (deleted_uids.length == 1) && (deleted_uids.first == uid) 
    ) 
    ActiveRecord::Base.connection.query_cache.clear 
    # since, the cache isnt updated when using ActiveRecord::Base.connection.execute, 
    # reset the cache to ensure accurate values, i.e. counts and associations. 
end 

Ho persino assicurato che self.primary_key = :uid in tutti i miei modelli.

Ho anche provato a sostituire uid con id ovunque e verificato che tutte le specifiche stavano passando (anche se ho lasciato nella patch). Tuttavia, ha ancora fallito quando ho rimosso la patch (rinominare le colonne uid su id non è stato risolto).

EDIT

In risposta ad alcuni commenti che ho provato il activeuuid gemma (dove mi sono bloccato sulla an error) e ha deciso di passare totalmente oltre agli ID. Questo è fondamentalmente per semplicità visto che ho voglia di lanciare questa app al più presto.

Tuttavia, anche con questa correzione sono necessario patch save, create e update. In realtà la patch delete non funziona più e ho dovuto rimuoverla (basandosi sull'originale). Vorrei assolutamente evitare di dover fare queste patch e sto mantenendo la bounty aperta per questo motivo.

+1

Le gemme come https://github.com/jashmenn/activeuuid o https://github.com/madpilot/has_uuid risolvono il problema? –

+1

Approccio alternativo: perché non combinare entrambe le colonne 'id' e' uuid'? 'Id', che è un numero incrementale, è molto più efficiente da indicizzare e garantire l'integrità referenziale. E usare l'uuid per la presentazione rivolta verso l'esterno. E in questo modo tutto il codice delle rotaie continuerà a funzionare :) – nathanvda

+0

@twairball ringrazia per queste raccomandazioni. Ho provato activeuuid ma non è stato in grado di correggere questo errore. has_uuid sembra interessante ma il suo readme dice che "non è abbastanza ben testato per essere rilasciato come una gemma", quindi ho intenzione di saltarlo per questa app di produzione. grazie per il tuo consiglio nathanvda. Ho finito per passare agli ID dagli UID completamente per motivi di semplicità. Comunque sto ancora usando le stesse patch (tranne che per distruggere). –

risposta

6

Ci sono pro e contro nel mantenere sia id sia uuid. Per le API JSON che espongono uuid, utilizzare Concerns sarebbe un'implementazione Rails-ish.

app/models/user.rb

class User < ActiveRecord::Base 

    include UserConcerns::Uuidable 

end 

app/modelli/preoccupazioni/user_concerns/uuidable.rb

module UserConcerns::Uuidable 
    extend ActiveSupport::Concern 

    included do 
     before_save :ensure_uuid! 
    end 


    def ensure_uuid! 
     self.uuid = generate_uuid if uuid.blank? 
    end 

    def generate_uuid 
     # your uuid using uuid_generate_v4() here 
    end 

    def to_param 
     uuid 
    end 
end 

Sopra implementazione lascia fuori la generazione uuid ma penso sopra risposta ha un link a questo.

+0

questo è uno snippet utile. Non sono ancora sicuro di cosa stia causando il mio CRUD rotto (dal momento che ho già passato a ID numerici), ma forse è un problema derivante da un altro contesto che sto trascurando. –

3

Ho fornito 1 soluzione per generare UUID, sapevo che ora sei tornato all'ID. find link for UUID

Sì Accetto che non è possibile eseguire CRUD sulla tabella dei join perché non si utilizzano le relazioni dei record attivi per eseguire le operazioni CRUD.

La cosa importante da capire è la convenzione con cui Rails implementa le relazioni tramite ActiveRecord.Un libro ha molti caratteri, ed ogni carattere appartiene a un libro, quindi:

class Book < ActiveRecordBase 
    has_many :characters 
end 

class Character < ActiveRecordBase 
    belongs_to :book 
end 

rotaie assume ora che la tabella dei caratteri avrà una chiave esterna chiamato libro_id, che si riferisce alla tabella libri. Per creare un personaggio appartenente ad un libro:

@book = Book.new(:name=>"Book name") 
@character = @book.characters.build(:name=>"Character name") 

Ora, quando @book viene salvato (supponendo che sia @book e @character sono validi), una riga viene creata in entrambi i libri e le tabelle personaggi, con la riga di carattere collegata tramite book_id.

per dimostrare che un personaggio appartiene anche ad un utente, si potrebbe aggiungere che il rapporto al modello Carattere:

class Character < ActiveRecordBase 
    belongs_to :book 
    belongs_to :user 
end 

Così Rails si aspetta ora i personaggi di avere anche chiave esterna chiamata user_id, che punta a un utente tabella (che necessita anche di un modello utente). Per specificare l'utente durante la creazione del personaggio:

@book = Book.new(:name=>"Book name") 
@character = @book.characters.build(:name=>"Character name",:user=>current_user) 

È possibile anche assegnare la chiave esterna chiamando il metodo corrispondente sull'oggetto:

@character.user = current_user 

Tutto questo funziona perché segue le convenzioni Rails per la denominazione modelli e tabelle.

+0

in quali situazioni è impossibile eseguire CRUD sui record di join? Funziona in altri posti nella mia app. Non sono sicuro di cosa ci sia di diverso in questo caso. –

+0

Si prega di trovare la mia risposta aggiornata, spero che risolverà il tuo problema. è tutto sulla convenzione di rotaie. –

+0

Intendo CRUD su modelli associati che seguono l'integrazione referenziale in mezzo. –

Problemi correlati