Rails:实现共享博客;有STI的has_many:未定义的方法' klass'为零:NilClass

时间:2015-09-06 01:50:30

标签: ruby-on-rails activerecord rspec

刚开始使用带有Rails 4项目的STI。假设我有UserBlog,并且User可以将其非公开博客与editorsnormal viewers分享给其他用户。

type列放在users表中对我来说没有意义,因为在项目中,用户不仅与blogs相关联,还与事物相关联比如posts。 (这里的博客更像是一个平台,帖子是文章。这里只是一个想法,可能是其他两件事。)

所以我使用另一个名为BlogUserAssociation的模型来管理上述共享关系。基本上这个模型包含type列,我从中继承了BlogEditorAssociationBlogViewerAssociation。 (名称有点笨拙。)第一个问题,这是一种推荐的方式来处理"共享"情况?

根据上述想法,我有:

# blog.rb
class Blog < ActiveRecord::Base
    ...
    has_many :blog_user_associations, dependent: :destroy
    has_many :editors, through: :blog_editor_associations, source: :user
    has_many :allowed_viewers, through: :blog_viewer_associations, source: :user # STI
    ...

# user.rb
class User < ActiveRecord::Base
    ...
    has_many :blog_user_associations, dependent: :destroy
    has_many :editable_blogs, through: :blog_editor_associations, source: :blog
    has_many :blogs_shared_for_view, through: :blog_viewer_associations, source: :blog
    ...

但是当我尝试用Rspec测试时,

it { should have_many(:editors).through(:blog_editor_associations).source(:user) }

我收到错误undefined method 'klass' for nil:NilClass

我相信这是因为我在has_many blog_editor_associations中没有说User。但我认为blog_editor_associations继承自blog_viewer_associations,我不必再为子模型说has_many。那么是否有理由不自动将has_many绑定到子模型?

1 个答案:

答案 0 :(得分:1)

对于这种情况,STI似乎有点矫枉过正。我更喜欢向关联模型添加属性,并使用范围来检索集合,具体取决于属性的值。例如,您可以将关联模型命名为BlogUser,并添加布尔can_edit列。值true表示用户可以编辑关联的博客。

然后模型看起来像这样:

class Blog < ActiveRecord::Base
  has_many :blog_users
  has_many :users, through: :blog_users
  scope :editable, -> { where(blog_users: {can_edit: true}) }
end

 class BlogUser < ActiveRecord::Base
   belongs_to :blog
   belongs_to :user
 end

 class User < ActiveRecord::Base
   has_many :blog_users
   has_many :blogs, through: :blog_users
   scope :editors, -> { where(blog_users: {can_edit: true}) }
 end

因此user.blogs检索与该用户关联的所有博客,user.blogs.editable检索用户可以编辑的所有博客。 blog.users检索与博客关联的所有用户,blog.users.editors检索可以编辑博客的所有用户。

一些测试证明:

require 'rails_helper'

RSpec.describe User, type: :model do
  describe "A user with no associated blogs" do
    let(:user) { User.create! }
    it "has no blogs" do
      expect(user.blogs.empty?).to be true
      expect(user.blogs.editable.empty?).to be true
    end
  end

  describe "A user with a non-editable blog association" do
    let(:user) { User.create! }
    let(:blog) { Blog.create! }
    before do
      user.blogs << blog
    end
    it "has one blog" do
      expect(user.blogs.count).to eq 1
    end
    it "has no editable blogs" do
      expect(user.blogs.editable.empty?).to be true
    end
  end

  describe "A user with an editable blog association" do
    let(:user) { User.create! }
    let(:blog) { Blog.create! }
    before do
      user.blog_users << BlogUser.new(blog: blog, user: user, can_edit: true)
    end
    it "has one blog" do
      expect(user.blogs.count).to eq 1
    end
    it "has one editable blog" do
      expect(user.blogs.editable.count).to eq 1
    end
  end
end