25

Ho due tipi di classi:Cambiare tipo di ActiveRecord classe in Rails con Single Table Inheritance

BaseUser < ActiveRecord::Base 

e

User < BaseUser 

che acts_as_authentic che utilizzano il sistema di autenticazione di Authlogic. Questa ereditarietà è implementata utilizzando l'ereditarietà della tabella singola

Se un nuovo utente si registra, lo registro come utente. Tuttavia, se ho già un BaseUser con la stessa email, mi piacerebbe cambiare BaseUser in un Utente nel database senza semplicemente copiare tutti i dati sull'Utente da BaseUser e creare un nuovo Utente (cioè con un nuovo id). È possibile? Grazie.

risposta

18

È possibile impostare il campo del tipo su "Utente" e salvare il record. L'oggetto in memoria mostrerà ancora come un BaseUser, ma la prossima volta che ricaricare l'oggetto in memoria sarà un utente

>> b=BaseUser.new 
>> b.class # = BaseUser 

# Set the Type. In-Memory object is still a BaseUser 
>> b.type='User' 
>> b.class # = BaseUser 
>> b.save 

# Retrieve the records through both models (Each has the same class) 

>> User.find(1).class # = User 
>> BaseUser.find(1).class # User 
+3

la risposta sta lavorando bene per me, ma è non eseguendo le convalide della classe 'User', puoi aiutarmi con quello? – abhas

72

opere risposta di Steve, ma dal momento che l'istanza è di classe BaseUser quando save viene chiamato, convalide e le callback definite in User non verranno eseguite. Probabilmente si vorrà convertire l'istanza utilizzando il metodo becomes:

user = BaseUser.where(email: "[email protected]").first_or_initialize 
user = user.becomes(User) # convert to instance from BaseUser to User 
user.type = "User" 
user.save! 
+0

Sperimentando con 'rails c' in Rails 3.2.18, non ho avuto bisogno di chiamare' user.becomes' per far funzionare le validazioni della nuova classe. Sei sicuro che sia corretto? – Kevin

+19

Puoi usare 'user = user.becomes! (Utente)' (con un '!') E omettere la riga 'user.type =" User "'. –

+1

Questo è fantastico. Mai sentito parlare di questo metodo ovunque – urmurmur

4

Sulla base delle altre risposte, mi aspettavo questo lavoro in Rails 4.1:

def update 
    @company = Company.find(params[:id]) 
    # This separate step is required to change Single Table Inheritance types 
    new_type = params[:company][:type] 
    if new_type != @company.type && Company::COMPANY_TYPES.include?(new_type) 
     @company.becomes!(new_type.constantize) 
     @company.type = new_type 
     @company.save! 
    end 

    @company.update(company_params) 
    respond_with(@company) 
    end 

Essa non ha, come il tipo il cambiamento non persisterebbe. Invece, sono andato con questo approccio meno elegante, che funziona correttamente:

def update 
    @company = Company.find(params[:id]) 
    # This separate step is required to change Single Table Inheritance types 
    new_type = params[:company][:type] 
    if new_type != @company.type && Company::COMPANY_TYPES.include?(new_type) 
     @company.update_column :type, new_type 
    end 

    @company.update(company_params) 
    respond_with(@company) 
    end 

Ed ecco le prove del controller che ho usato per confermare la soluzione:

describe 'Single Table Inheritance (STI)' do 

    class String 
     def articleize 
     %w(a e i o u).include?(self[0].to_s.downcase) ? "an #{self}" : "a #{self}" 
     end 
    end 

    Company::COMPANY_TYPES.each do |sti_type| 
     it "a newly assigned Company of type #{sti_type} " \ 
     "should be #{sti_type.articleize}" do 
     post :create, { company: attributes_for(:company, type: sti_type) }, 
      valid_session 
     expect(assigns(:company)).to be_a(sti_type.constantize) 
     end 
    end 

    Company::COMPANY_TYPES.each_index do |i| 
     sti_type, next_sti_type = Company::COMPANY_TYPES[i - 1], 
           Company::COMPANY_TYPES[i] 
     it "#{sti_type.articleize} changed to type #{next_sti_type} " \ 
     "should be #{next_sti_type.articleize}" do 
     company = Company.create! attributes_for(:company, type: sti_type) 
     put :update, { id: company.to_param, company: { type: next_sti_type } }, 
      valid_session 
     reloaded_company = Company.find(company.to_param) 
     expect(reloaded_company).to be_a(next_sti_type.constantize) 
     end 
    end 
    end 
+0

Grazie, questo sembra funzionare, con un piccolo cambiamento: 'new_type = params [: messa in vendita di]? [: Tipo] se new_type = @ listing.type && Listing :: LISTING_TYPES.include (new_type) # noi 're cambiando tipi di classe qui, quindi bisogna stare attenti @ listing.update_column: tipo, new_type @listing = new_type.constantize.find (@ listing.id) FINE' avevo bisogno di ricaricare l'istanza manualmente (chiamando "ricaricare" non ha funzionato nel mio caso) – elsurudo