2010-07-16 22 views
25

Tôi có hai loại lớp:Thay đổi loại ActiveRecord Class trong Rails với đơn Table Inheritance

BaseUser < ActiveRecord::Base 

User < BaseUser 

mà acts_as_authentic sử dụng hệ thống xác thực Authlogic của. Thừa kế này được triển khai bằng cách sử dụng Bảng thừa kế đơn

Nếu người dùng mới đăng ký, tôi đăng ký người dùng đó làm Người dùng. Tuy nhiên, nếu tôi đã có một BaseUser với cùng một email, tôi muốn thay đổi BaseUser đó thành một User trong cơ sở dữ liệu mà không cần sao chép tất cả dữ liệu sang User từ BaseUser và tạo một User mới (tức là với một new ID). Điều này có thể không? Cảm ơn.

Trả lời

18

Bạn chỉ có thể đặt trường loại thành 'Người dùng' và lưu bản ghi. Các đối tượng trong bộ nhớ vẫn sẽ hiển thị như một BaseUser nhưng lần sau khi bạn tải lại các đối tượng trong bộ nhớ sẽ là một tài

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

câu trả lời của bạn đang làm việc tốt cho tôi nhưng nó là không chạy các xác nhận hợp lệ của lớp 'User', bạn có thể vui lòng giúp tôi với điều đó không? – abhas

72

công trình câu trả lời của Steve nhưng kể từ khi dụ là của lớp BaseUser khi save được gọi, kiểm chứng thực và các cuộc gọi lại được xác định trong User sẽ không chạy. Có thể bạn sẽ muốn chuyển đổi các trường hợp sử dụng phương pháp 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

Thử nghiệm với 'rails c' trong Rails 3.2.18, tôi không cần phải gọi' user.becomes' để kiểm tra tính hợp lệ của lớp mới. Bạn có chắc chắn điều này là chính xác? – Kevin

+19

Bạn có thể sử dụng 'user = user.becomes! (Người dùng)' (với một '!') Và bỏ qua dòng 'user.type =" User "'. –

+1

Điều này thật tuyệt vời. Không bao giờ nghe nói về phương pháp đó bất cứ nơi nào – urmurmur

4

Dựa trên câu trả lời khác, tôi mong đợi này để làm việc trong 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 

Nó không, như các loại thay đổi sẽ không tồn tại. Thay vào đó, tôi đã đi với cách tiếp cận này ít thanh lịch, hoạt động một cách chính xác:

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 

Và đây là các bài kiểm tra điều khiển tôi sử dụng để xác nhận các giải pháp:

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

Cảm ơn, điều này dường như hoạt động, với một thay đổi nhỏ: 'new_type = params [: listing] [: type] if new_type! = @ Listing.type && Listing :: LISTING_TYPES.include? (New_type) # we đang thay đổi loại lớp ở đây, vì vậy cần phải cẩn thận @ listing.update_column: type, new_type @listing = new_type.constantize.find (@ listing.id) end' Tôi cần tải lại bản sao theo cách thủ công (gọi điện thoại "tải lại" không hoạt động trong trường hợp của tôi) – elsurudo

Các vấn đề liên quan