【问题标题】:Single Table Inheritance Errors - ActiveRecord::SubclassNotFound单表继承错误 - ActiveRecord::SubclassNotFound
【发布时间】:2018-01-13 13:14:02
【问题描述】:

我的意图是实施两种类型的 STI:工作人员和临床医生。我之前的实现是使用带枚举的角色,并且在尽我所能遵循类似问题的答案之后,取出测试等中的所有引用以枚举角色并替换为对类型的引用,当我收到以下错误的许多版本时运行我的测试套件:

ERROR["test_valid_signup_information_with_account_activation", UsersSignupTest, 1.01794000000001]
 test_valid_signup_information_with_account_activation#UsersSignupTest (1.02s)
ActiveRecord::SubclassNotFound:         ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'Staff'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class or overwrite User.inheritance_column to use another column for that information.
            app/controllers/users_controller.rb:19:in `create'
            test/integration/users_signup_test.rb:27:in `block (2 levels) in <class:UsersSignupTest>'
            test/integration/users_signup_test.rb:26:in `block in <class:UsersSignupTest>'

以下是我感到困惑的几个可能隐藏问题的领域:

在我的用户模型 user.rb 中,我认为我正确定义了子类(Staff 和 Clinician),但我不确定我是否正确包装了所有内容。所有其他代码是否必须包含在这些类之一中?我误用了“结束”吗?

class User < ApplicationRecord
end

class Staff < User
end

class Clinician < User
end

belongs_to :university
has_many :referral_requests




  attr_accessor :remember_token, :activation_token, :reset_token
  before_save   :downcase_email
  before_create :create_activation_digest
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  validates :type, presence: true
  validates :university_id, presence: true, if: lambda { self.type == 'Staff' }

  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }, allow_nil: true






  # Returns the hash digest of the given string.
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  # Returns a random token.
  def User.new_token
    SecureRandom.urlsafe_base64
  end

  # Remembers a user in the database for use in persistent sessions.
  def remember
    self.remember_token = User.new_token
    update_attribute(:remember_digest, User.digest(remember_token))
  end

  # Returns true if the given token matches the digest.
  def authenticated?(remember_token)
        return false if remember_digest.nil?
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end


  # Forgets a user.
  def forget
    update_attribute(:remember_digest, nil)
  end

    # Returns true if the given token matches the digest.
  def authenticated?(attribute, token)
    digest = send("#{attribute}_digest")
    return false if digest.nil?
    BCrypt::Password.new(digest).is_password?(token)
  end

  # Activates an account.
  def activate
    update_attribute(:activated,    true)
    update_attribute(:activated_at, Time.zone.now)
  end

  # Sends activation email.
  def send_activation_email
    UserMailer.account_activation(self).deliver_now
  end

 # Sets the password reset attributes.
  def create_reset_digest
    self.reset_token = User.new_token
    update_attribute(:reset_digest,  User.digest(reset_token))
    update_attribute(:reset_sent_at, Time.zone.now)
  end

  # Sends password reset email.
  def send_password_reset_email
    UserMailer.password_reset(self).deliver_now
  end

   # Returns true if a password reset has expired.
  def password_reset_expired?
    reset_sent_at < 2.hours.ago
  end

 def feed
    ReferralRequest.where("user_id = ?", id)
  end


 private

    # Converts email to all lower-case.
    def downcase_email
    self.email = email.downcase
    end

    # Creates and assigns the activation token and digest.
    def create_activation_digest
      self.activation_token  = User.new_token
      self.activation_digest = User.digest(activation_token)
    end
end

这是失败的特定测试代码(测试套件中许多失败的代码之一 - 但所有用户参数的定义都类似)。我是否适当地传递了人员参数?

 test "valid signup information with account activation" do
    get signup_path
    assert_difference 'User.count', 1 do
      post users_path, params: { user: { name:  "Example User",
                                         email: "user@example.com",
                                         university_id: 1 ,
                                         type: "Staff",
                                         password:              "password",
                                         password_confirmation: "password" } }

这是我的用户表架构:

create_table "users", force: :cascade do |t|
    t.string   "name"
    t.string   "email"
    t.datetime "created_at",                        null: false
    t.datetime "updated_at",                        null: false
    t.string   "password_digest"
    t.string   "remember_digest"
    t.string   "activation_digest"
    t.boolean  "activated",         default: false
    t.datetime "activated_at"
    t.string   "reset_digest"
    t.datetime "reset_sent_at"
    t.integer  "university_id"
    t.integer  "role"
    t.string   "type"
    t.index ["email"], name: "index_users_on_email", unique: true
  end

非常感谢您的任何想法!我在这里问了很多问题,但只是在尝试了很长时间类似的答案之后。

【问题讨论】:

    标签: ruby-on-rails single-table-inheritance


    【解决方案1】:

    假设上面的代码示例是准确的,您会看到此错误,因为 user.rb 文件是无效的 Ruby 并且无法解析。您还应该看到一个解释器错误。

    class User < ApplicationRecord
      belongs_to :university
      has_many :referral_requests
    
      attr_accessor :remember_token, :activation_token, :reset_token
      before_save   :downcase_email
      before_create :create_activation_digest
      validates :name,  presence: true, length: { maximum: 50 }
      VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
      validates :email, presence: true, length: { maximum: 255 },
                        format: { with: VALID_EMAIL_REGEX },
                        uniqueness: { case_sensitive: false }
      validates :type, presence: true
    
      has_secure_password
      validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
      # etc...
    end
    
    class Staff < User
      validates :university_id, presence: true
    end
    
    class Clinician < User
    end
    

    标准类继承实践适用,因此如果其中有仅适用于特定子类的代码,它应该移到那里(例如,university_id 验证移到 Staff)。

    # Returns the hash digest of the given string.
    def User.digest(string)
      cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
      BCrypt::Password.create(string, cost: cost)
    end
    
    # Returns a random token.
    def User.new_token
      SecureRandom.urlsafe_base64
    end
    

    这些应该写成

    def self.digest(string)
      # ...
    end
    
    def self.new_token
      # ...
    end
    

    或者,或者,

    class << self
      def digest(string)
        # ...
      end
    
      def new_token
        # ...
      end
    end
    

    【讨论】:

    • 代码示例是正确的,因为这就是我编写的方式。根据您的有用反馈,这显然是错误的。我会实施。谢谢!
    • 一个问题:为什么我需要在切换到 STI 时将诸如摘要之类的方法更改为新语法?我相信你,我当然可以这样写,但我不知道为什么会改变。
    • @mike9182 与 STI 无关,这正是 Rubyists 编写的方式。我认为你所拥有的东西是可行的,但我给出的例子是其他 Ruby 人希望看到的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-21
    • 2012-10-13
    • 1970-01-01
    • 2012-12-18
    • 1970-01-01
    相关资源
    最近更新 更多