Изменение типа класса ActiveRecord в Rails с наследованием одной таблицы

У меня есть два типа классов:

BaseUser < ActiveRecord::Base 

и

User < BaseUser

который действует_как_аутентичный, используя систему аутентификации Authlogic. Это наследование реализовано с использованием наследования одной таблицы.

Если регистрируется новый пользователь, я регистрирую его как пользователя. Однако, если у меня уже есть BaseUser с таким же адресом электронной почты, я хотел бы изменить этого BaseUser на пользователя в базе данных без простого копирования всех данных пользователю из BaseUser и создания нового пользователя (т.е. с новым я бы). Это возможно? Спасибо.


person Wei    schedule 16.07.2010    source источник


Ответы (3)


Вы можете просто установить в поле типа значение «Пользователь» и сохранить запись. Объект в памяти по-прежнему будет отображаться как BaseUser, но в следующий раз, когда вы перезагрузите объект в памяти, он будет пользователем.

>> 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
person Steve Weet    schedule 16.07.2010
comment
ваш ответ у меня работает нормально, но он не выполняет проверки класса User, не могли бы вы помочь мне с этим? - person abhas; 14.08.2012
comment
@abhas, если вы хотите проверить модель, вам нужно будет добавить '!' к концу. Это обеспечивает проверку и является общепринятым соглашением. то есть создать!, найти_по!, сохранить! - person Cody Elhard; 11.04.2019
comment
Смотрите лучший ответ от @balexand ниже - person Steve Weet; 13.08.2019

Ответ Стива работает, но поскольку экземпляр класса BaseUser при вызове save, проверки и обратные вызовы, определенные в User, не будут выполняться. Вероятно, вы захотите преобразовать экземпляр с помощью метода 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!
person balexand    schedule 24.12.2012
comment
Экспериментируя с rails c в Rails 3.2.18, мне не нужно было вызывать user.becomes для выполнения проверки нового класса. Вы уверены, что это правильно? - person Kevin; 15.07.2014
comment
Вы можете использовать user = user.becomes!(User)!) и опустить строку user.type = "User". - person Christoph Geschwind; 11.09.2014
comment
Это потрясающе. Нигде не слышал о таком методе - person urmurmur; 28.05.2015

Основываясь на других ответах, я ожидал, что это будет работать в 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

Этого не произошло, так как изменение типа не сохранялось. Вместо этого я выбрал менее элегантный подход, который работает правильно:

  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

И вот тесты контроллера, которые я использовал для подтверждения решения:

  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
person Dan Kohn    schedule 29.09.2014
comment
Спасибо, кажется, это работает, с одним небольшим изменением: new_type = params[:listing][:type] if new_type != @listing.type && Listing::LISTING_TYPES.include?(new_type) # we're changing class types here, so need to be careful @listing.update_column :type, new_type @listing = new_type.constantize.find(@listing.id) end мне нужно было перезагрузить экземпляр вручную (вызов перезагрузки не работал в моем случае) - person elsurudo; 15.06.2015