刚开始在Rails 4项目中使用STI。假设我有User
和Blog
, User
可以将他的非公开博客作为editors
或normal viewers
分享给其他用户。
对我来说,把type
列放在users
表中是没有意义的,因为在项目中,用户不仅与blogs
相关联,还与posts
相关联。(这里的博客更像是一个平台,帖子是文章。这里只是一个想法,可能是其他两件事)。
所以我使用另一个模型BlogUserAssociation
来管理上述共享关系。基本上这个模型包含一个type
列,我从它继承了BlogEditorAssociation
和BlogViewerAssociation
。(名字有点笨拙)第一个问题,这是处理"共享"情况的推荐方法吗?
有了以上的想法,我有:
# 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