轨道:实现共享博客;has_many 与 STI:undefined 方法 'klass' for nil:NilClass



刚开始在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

我相信这是因为我在User中没有说has_many blog_editor_associations。但我想既然blog_editor_associations继承自blog_viewer_associations,我就不必再为子模型说has_many了。那么是否有不自动绑定has_many到子模型的原因?

在这种情况下,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

最新更新