使用单表继承更改Rails中的ActiveRecord类的类型

Wei*_*Wei 25 ruby activerecord ruby-on-rails single-table-inheritance

我有两种类:

BaseUser < ActiveRecord::Base 
Run Code Online (Sandbox Code Playgroud)

User < BaseUser
Run Code Online (Sandbox Code Playgroud)

其中acts_as_authentic使用Authlogic的身份验证系统.此继承是使用单表继承实现的

如果新用户注册,我将他注册为用户.但是,如果我已经有一个具有相同电子邮件的BaseUser,我想将该BaseUser更改为数据库中的用户,而不是简单地将所有数据从BaseUser复制到用户并创建新用户(即使用新用户) ID).这可能吗?谢谢.

bal*_*and 83

史蒂夫的答案是有效的,但由于实例BaseUsersave调用时属于类,因此定义的验证和回调User将不会运行.您可能希望使用become方法转换实例:

user = BaseUser.where(email: "user@example.com").first_or_initialize
user = user.becomes(User) # convert to instance from BaseUser to User
user.type = "User"
user.save!
Run Code Online (Sandbox Code Playgroud)

  • 您可以使用`user = user.becomes!(User)`(带有`!`)并省略`user.type ="User"`行. (26认同)
  • 这太棒了。从来没有听说过这种方法 (2认同)
  • 真棒!那应该是公认的答案. (2认同)

Ste*_*eet 17

您只需将类型字段设置为"用户"并保存记录即可.内存中对象仍将显示为BaseUser,但下次重新加载内存中对象时将是User

>> 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
Run Code Online (Sandbox Code Playgroud)

  • 你的答案对我来说很好,但它没有运行"用户"课程的验证,你能帮助我吗? (3认同)

Dan*_*ohn 5

根据其他答案,我希望这能在 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
Run Code Online (Sandbox Code Playgroud)

它没有,因为类型更改不会持续。相反,我采用了这种不太优雅的方法,它可以正常工作:

  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
Run Code Online (Sandbox Code Playgroud)

以下是我用来确认解决方案的控制器测试:

  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
Run Code Online (Sandbox Code Playgroud)