Rails 3中单个记录的多个外键?

我正在开发一个应用程序来管理注册课程的学生。 该应用程序将拥有可以登录和操作学生的用户。 用户还可以对学生发表评论。 因此,我们的三个主要课程是学生,用户和评论。 问题是我需要将个别评论与其他两个模型相关联:用户和学生。 所以我开始使用这样的基本代码……

class Student < ActiveRecord::Base has_many :comments end class User < ActiveRecord::Base has_many :comments end class Comment < ActiveRecord::Base belongs_to :student belongs_to :user attr_accessible :comment end 

因此,在注释表中,单个记录将包含以下字段:

 id comment student_id user_id created_at updated_at 

这提出了几个问题。 首先,用于创建关联对象的漂亮Rails语法分解。 如果我想发表新评论,我必须在外键之间进行选择。 所以…

 User.comments.create(attributes={}) 

要么

 Student.comments.create(attributes={}) 

另一个选择是任意选择一个外键,并手动将其添加到attrs哈希。 所以…

 User.comments.create(:comment => "Lorem ipsum", :student_id => 1) 

这个选项的问题是我必须在我的评论模型中列出attr_accessible下的student_id。 但我的理解是,这会带来安全风险,因为有人可以在技术上出现,并使用群发作业与不同的学生重新关联评论。

这通常使用Rails导致关于数据建模的进一步问题。 我目前在Rails中构建的应用程序是我几年前在PHP / MySQL中编写的应用程序。 当我第一次学习SQL时,非常重视规范化的想法。 因此,例如,如果您有一个存储名称和地址的联系人表,您将使用许多外键关系来避免重复数据。 如果您有状态列,则不希望直接列出状态。 否则,您可能有数千行都包含字符串值,如“Texas”。 有一个单独的状态表并使用外键关系将其与您的联系人表关联更好。 我对好的SQL理论的理解是,任何可以重复的值都应该分成它们自己的表。 当然,为了完全规范化数据库,您可能会在联系人表中找到相当多的外键。 (state_id,gender_id等)

那么如何用“Rails方式”来解决这个问题呢?

为了澄清(对不起,我知道这已经很久了)我考虑了另外两种常见方法:“has_many:through =>”和多态关联。 我能说的最好,也没有解决上述问题。 原因如下:

“has_many:through =>”在像博客这样的情况下运行良好。 所以我们有评论,文章和用户模型。 用户通过文章有很多评论。 (这样的例子出现在Apress的Beginning Rails 3中。顺便说一句好书。)问题在于,为了使其工作(如果我没有记错),每篇文章都必须属于特定用户。 在我的情况下(我的学生模型在这里类似于文章)没有一个用户拥有学生。 所以我不能说用户通过学生有很多评论。 可能有多个用户评论同一个学生。

最后,我们有多态关联。 这适用于多个外键,假设没有一个记录需要属于多个外来类。 在RailsCasts第154集中,Ryan Bates给出了一个例子,其中评论可以属于文章或照片或事件。 但是如果单个评论需要属于多个评论呢?

总而言之,我可以通过手动分配一个或两个外键来使我的用户,学生,注释场景工作,但这并不能解决attr_accessible的问题。

提前感谢您的任何建议!

当我开始使用rails时,我有一个确切的问题。 如何在create方法中整齐地设置两个关联,同时确保关联_ID受到保护。

Wukerplank是正确的 – 您不能通过质量分配设置第二个关联,但您仍然可以直接在新行中分配association_id。

这种类型的关联分配非常常见,并且在我的代码中遍布,因为在许多情况下,一个对象具有多个关联。

另外,要明确:多态关联和has_many:through根本不会解决你的情况。 您有两个单独的关联(评论的“所有者”和评论的“主题”) – 它们不能合理化为一个。

编辑:这是你应该怎么做:

 @student = Student.find_by_id(params[:id]) @comment = @student.comments.build(params[:comment]) #First association is set here @comment.user = current_user #Second association is set here if @comment.save # ... else # ... end 

通过使用Object.associations.build,Rails会自动创建一个新的“关联”对象,并在保存时将其与Object关联。

我认为多态关联是要走的路。 我建议使用插件而不是“自己滚动”。 我使用ActsAsCommentable ( 在Github上 )取得了很好的成绩。

至于你的attr_accessible问题:你是对的,这样做更安全。 但它并没有抑制你想要做的事情。

在我的示例current_user ,我假设您拥有保存当前用户的内容

 @student = Student.find(params[:id]) @comment = Comment.new(params[:comment]) # <= mass assignment @comment.student = @student # <= no mass assignment @comment.user = current_user # <= no mass assignment if @comment.save # ... else # ... end 

attr_accessible保护你免受某人偷偷使用params[:comment][:student_id] ,但它不会阻止该属性被另一种方式设置。

您仍然可以通过has_many :comments关联获取用户的所有评论,但您还可以通过belongs_to :user关联显示评论学生的人:

 

<%= @student.name %>

Comments

<%- @student.comments.each do |comment| -%>

<%= comment.text %>
by <%= comment.user.name %>

<%- end -%>

加:

不要过度设计你的应用程序。 拥有一个state:string字段是完全正常的,除非你想用State对象做一些有意义的事情,比如存储所有的区和县。 但是,如果您需要了解学生的所有状态,那么文本字段就完全可以了。 性别等也是如此。

好吧,第一部分。 如果我正确理解你的问题,我认为,由于评论列在学生控制器中,你应该通过学生模型将它们联系起来(这对我来说似乎合乎逻辑)。 但是,为了防止分配错误的用户ID,您可以执行类似的操作

 @student = Student.find params[:id] @student.comments.create :user => current_user 

current_user可能是执行User.find session[:user_id]或其他类似操作的帮助程序。

has_many:通过关联在这里没有意义。 您可以使用它通过评论关联用户和学生,但不能通过学生使用用户和评论

希望有所帮助。