25

que tiene dos tipos de clases:Cambiar tipo de ActiveRecord en Rails clase con una sola mesa Herencia

BaseUser < ActiveRecord::Base 

y

User < BaseUser 

cuales acts_as_authentic utilizando el sistema de autenticación de Authlogic. Esta herencia se implementa con herencia de tabla única

Si un nuevo usuario se registra, lo inscribo como usuario. Sin embargo, si ya tengo un Usuario Base con el mismo correo electrónico, me gustaría cambiar ese Usuario Base a un Usuario en la base de datos sin simplemente copiar todos los datos al Usuario desde el Usuario Base y crear un nuevo Usuario (es decir, con un nuevo Usuario). carné de identidad). es posible? Gracias.

Respuesta

18

Puede simplemente establecer el campo tipo en 'Usuario' y guardar el registro. El objeto en memoria aún se mostrará como un BaseUser pero la próxima vez que vuelva a cargar el objeto en memoria será un usuario

>> 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

su respuesta está trabajando muy bien para mí, pero es no ejecutando las validaciones de la clase 'User', ¿pueden ayudarme con eso? – abhas

72

obras respuesta de Steve, pero desde la instancia es de la clase BaseUser cuando save se llama, validaciones y las devoluciones de llamadas definidas en User no se ejecutarán. Es probable que desee para convertir la instancia utilizando el método de 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

Experimentando con 'rails c' en Rails 3.2.18, no tuve que llamar' user.becomes' para que se ejecutaran las validaciones de la nueva clase. ¿Estas seguro que esto es correcto? – Kevin

+19

Puede usar 'user = user.becomes! (User)' (con un '!') Y omite la línea 'user.type =" User "'. –

+1

Esto es excelente. Nunca he escuchado hablar de ese método en ningún lado – urmurmur

4

Sobre la base de las otras respuestas, que esperaba que esto funcione en 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 

No fue así, como el tipo de el cambio no persistiría. En su lugar, fui con este enfoque menos elegante, que funciona correctamente:

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 

Y aquí están las pruebas del controlador que utiliza para confirmar la solución:

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

Gracias, esto parece funcionar, con un pequeño cambio: 'NEW_TYPE = params [: lista]? [: Tipo] si NEW_TYPE = @ listing.type && inmueble :: LISTING_TYPES.include (NEW_TYPE) # nos 're cambiar los tipos de clases aquí, así que hay que tener cuidado @ listing.update_column: tipo, NEW_TYPE @listing = new_type.constantize.find (@ listing.id) end' que necesitaba para recargar la instancia manualmente (llamando "recargar" no funcionó en mi caso) – elsurudo

Cuestiones relacionadas