Rails 使用单个 class 设计 belongs_to 和 has_many 的 ActiveModel
Rails ActiveModel designing belongs_to and has_many with single class
我需要一些帮助来建模我的模型和控制器。这是我想要实现的目标:
我想要一个名为 User 的设计用户(像往常一样)和一个名为 Project 的第二个模型。一个项目应该属于一个用户,同时应该有很多参与者。项目的参与者也应该是用户(具有设计 registration/login),但创建项目的用户不应该能够参与。
到目前为止,一切都很好。棘手的部分来了:在我的控制器中,我希望能够写:
def participate
p = Project.find(id: params[:id])
p.participants << current_user unless p.participants.includes?(current_user) && !p.user_id.equal(current_user.id)
if p.save
redirect_back
else
render :project
end
end
这不起作用,因为 p.participants 不是数组,而且查询(我在 rails 控制台中尝试过)没有检查我的 n:m table。
这是我当前的模型设置:
class Project < ApplicationRecord
before_validation :set_uuid, on: :create
validates :id, presence: true
belongs_to :user
has_and_belongs_to_many :participants, class_name: "User"
end
class User < ApplicationRecord
before_validation :set_uuid, on: :create
validates :id, presence: true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_and_belongs_to_many :projects
end
最后是我的迁移:
class CreateProjects < ActiveRecord::Migration[6.0]
def change
create_table :projects, id: false do |t|
t.string :id, limit: 36, primary_key: true
t.string :title
t.belongs_to :user, index: true, foreign_key: true, type: :uuid
t.datetime :published_at
t.timestamps
end
end
end
class CreateJoinTableProjectsUsers < ActiveRecord::Migration[6.0]
def change
create_join_table :users, :projects do |t|
t.index :project_id
t.index :user_id
end
end
end
最好使用has_many: through 而不是has_and_belongs_to_many。这使您可以编写更清晰的验证代码。
- 从用户和项目模型中删除 has_and_belongs_to_many
添加 has_many :through 到用户和项目模型
rails g model UserProject user:references project:references
rails db:migrate
class User < ApplicationRecord
..
has_many :user_projects
has_many :projects, through: :user_projects
..
end
class Project < ApplicationRecord
..
has_many :user_projects
has_many :participants, through: :user_projects, source: 'user'
..
end
class UserProject < ApplicationRecord
belongs_to :user
belongs_to :project
end
向 UserProject 模型添加验证
class UserProject < ApplicationRecord
belongs_to :user
belongs_to :project
validate :check_participant
private
def check_participant
return if project.participants.pluck(:id).exclude?(user.id) && project.user != user
errors.add(:base, 'You cannot be participant')
end
end
更新参与方式
def participate
p = Project.find(id: params[:id])
begin
p.participants << current_user
redirect_back
rescue ActiveRecord::RecordInvalid => invalid
puts invalid.record.errors
render :project
end
end
我需要一些帮助来建模我的模型和控制器。这是我想要实现的目标:
我想要一个名为 User 的设计用户(像往常一样)和一个名为 Project 的第二个模型。一个项目应该属于一个用户,同时应该有很多参与者。项目的参与者也应该是用户(具有设计 registration/login),但创建项目的用户不应该能够参与。 到目前为止,一切都很好。棘手的部分来了:在我的控制器中,我希望能够写:
def participate
p = Project.find(id: params[:id])
p.participants << current_user unless p.participants.includes?(current_user) && !p.user_id.equal(current_user.id)
if p.save
redirect_back
else
render :project
end
end
这不起作用,因为 p.participants 不是数组,而且查询(我在 rails 控制台中尝试过)没有检查我的 n:m table。 这是我当前的模型设置:
class Project < ApplicationRecord
before_validation :set_uuid, on: :create
validates :id, presence: true
belongs_to :user
has_and_belongs_to_many :participants, class_name: "User"
end
class User < ApplicationRecord
before_validation :set_uuid, on: :create
validates :id, presence: true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_and_belongs_to_many :projects
end
最后是我的迁移:
class CreateProjects < ActiveRecord::Migration[6.0]
def change
create_table :projects, id: false do |t|
t.string :id, limit: 36, primary_key: true
t.string :title
t.belongs_to :user, index: true, foreign_key: true, type: :uuid
t.datetime :published_at
t.timestamps
end
end
end
class CreateJoinTableProjectsUsers < ActiveRecord::Migration[6.0]
def change
create_join_table :users, :projects do |t|
t.index :project_id
t.index :user_id
end
end
end
最好使用has_many: through 而不是has_and_belongs_to_many。这使您可以编写更清晰的验证代码。
- 从用户和项目模型中删除 has_and_belongs_to_many
添加 has_many :through 到用户和项目模型
rails g model UserProject user:references project:references rails db:migrate class User < ApplicationRecord .. has_many :user_projects has_many :projects, through: :user_projects .. end class Project < ApplicationRecord .. has_many :user_projects has_many :participants, through: :user_projects, source: 'user' .. end class UserProject < ApplicationRecord belongs_to :user belongs_to :project end
向 UserProject 模型添加验证
class UserProject < ApplicationRecord belongs_to :user belongs_to :project validate :check_participant private def check_participant return if project.participants.pluck(:id).exclude?(user.id) && project.user != user errors.add(:base, 'You cannot be participant') end end
更新参与方式
def participate p = Project.find(id: params[:id]) begin p.participants << current_user redirect_back rescue ActiveRecord::RecordInvalid => invalid puts invalid.record.errors render :project end end