Rails 一个关于 rails 关联模型的创建问题

ryan · 2013年06月14日 · 最后由 Ryan 回复于 2013年06月17日 · 4091 次阅读

假设有这样两个模型,并如此创建模型

class Article
    has_many tags
end

class Tag
    attr_accessible :name
    validates_presence_of :name,:article_id
end

tags=['a','b','c','d','e']
ar1=Article.new
tags.each{ |t|
    ar1.tags.build(name:t)
}

ar1.save!

如此创建一个 article 和关联的 tag,会爆这样的错误tag invalid 我通过命令行发现,build方法后,article 的 id 依然是 null,尚未赋值,因此在 tags 中的 article_id 也是 null,所以 tag 创建的时候会报错,因为 article_id 必须非空。

针对以上问题,是否有其他的写法可以避免? 我暂时是先save,然后设定 tags,然后再save

改成校验 article 试试看:

belongs_to :article
validates_presence_of :name, :article

实在不行的话,只能自己写 validate 方法了

直接 ar1=Article.create 可以么

#2 楼 @guyanbiao 不可以在设定好 tag 之后直接 create,一定要先 create article,然后设定 tag,再 save,原理是一样的,你可以试试全部设定好之后,用 article.create! 。会爆一个 validate error。

#1 楼 @quakewang 不行,错误是一样的。而且多加一条belongs_to是不对的,这里其实是个多对多的关系。article 和 tag 是多对多的。

#4 楼 @Ryan 1 楼的验证 article 而不是 article_id 的方案是可行的。只是要在 belongs_to 加参数 inverse_of。 比如belongs_to :article, inverse_of: :tagsinverse_of的参数是 article 中关联 tag 的那个名字。比如has_many :tags中的tagshas_many也可以加。具体可以看 rails 的 api。 简单的说就是不加的话,在没有保存 article 之前,tag 是"看不到"关联的 article 的。因为必须靠 foreign_key,也就是 article_id 才能找到对应的 model。使用 inverse_of 就“类似于缓存”了关联的对象,即使没有保存(不使用 foreign_key)也能找到。 不知道解释清楚没有。 PS:“类似于缓存”这个说法只是我观察到的效果,没有看源代码的实现,所以不确定是不是真的这么做的。

#5 楼 @upsilon2gamma 我尝试了下,不仅仅对于 tag 要加inverse_of,article 也要加,两边都加上才能满足我之前说到的需求,不过这里也有个问题,因为 tag 和 article 是多对多的关系,所以不能用 belongs_to,其实两者都是 has_many,不知道 inverse_of 是否可以行

#5 楼 @upsilon2gamma 我发现在这个情况下,不能对两个模型同时加 has_many,不然 save 时候会爆undefined method each for <Article.....>。看来 has_many 之后会变成一个数组的处理。

#6 楼 @Ryan

  1. 我记得自己当时做的时候只要加一边就可以了。查了 api,有这句for belongs_to associations has_many inverse associations are ignored.
  2. 你说是多对多,怎么会直接用has_many?如果是用中间一个 model(同时belongs_to tag 和 article)的话,那就应该在这个中间的 model 里加。你可以看看 api,上面的例子几乎和你的一样,他是 Post 和 tag。注意inverse_of是不支持through的。所以这种情况,你的validates应该也放在中间那个 model 里。

不是有相关的 gem 吗?不过自己写也好

#8 楼 @upsilon2gamma 嗯,中间 model 当时就是我的方案,但是当初没知道可以用 inverse_of,而且当时试下来是,只有中间 model 和关联 model 可以互相创建对象,但是 3 个模型不能一次性创建,就比如,article,tag,article_tag 三个模型,创建 article 的时候可以把 article 和 article_tag 两个模型创建,但是不能把相关的 tag 创建进 tag 的表,当时想过重写 initialize 构造函数,然后觉得太麻烦了,一个个人站点,没必要弄得这么麻烦,性能上也不会有什么瓶颈,现在这样写对于 tag 搜索 article 也不会有什么麻烦的。

#9 楼 @Levan 什么!有第三方 gem?叫啥?我不清楚啊。。。

#12 楼 @Rei @Ryan tag 这种常用的功能肯定有 gem,但我个人不是特别推荐直接给新手推荐 gem 来完成某个功能。因为这样很容易把问题掩盖起来。 自己实现虽然很有可能“笨拙”,但相当于自己对业务逻辑的做了分析和抽象。能学到很多东西。而很多 gem 都是"best practice"的结果。有些设定初学者根本无法理解。很容易把简单问题复杂化。出了问题也不知道到底是自己使用有问题,还是 gem 有 bug。 我自己在做一个项目的认证功能的时候,一开始考虑用 devise,但看了文档,发现功能实在“太强大”,我只是要一个非常简单的用户名密码的功能,却要设置很多东西(实际也不算多,但自己确实有点懒)。后来就自己写了几个 method,几个 helper 就完了。目前来说很“粗糙”,但是以后功能多了,复杂了,自然而然就会用 devise。我相信那时对其中的设置应该有更深的理解。 再说点题外话。我一个同事之前也想学 rails,我自己没什么“教学”的经验(本身就不是学计算机的,又不是在 it 公司工作,都是自己自学摸索的。),“自以为是”的给了他很多“经验”,以为帮他把一些坑填了他能更快学好。但现在发觉他虽然写个应用没什么问题(他都跳槽到软件公司去了),却还是会问我一些比较基础的东西,让我觉得很惊讶。我现在就在反省是不是应该在他学“游泳”的时候给他机会“呛几口水”。 以我个人的看法,楼主的问题本身就值得推敲。

  1. article_id设置在validates_presence_of里已经是比较“初级”的问题了(我没有恶意)。因为article都还没有保存的时候,不可能有这个值。如果知道validates_presence_of本身实际就是调用blank?,那#1 楼的方案应该也能想到。
  2. 而且实际上 rails 也可以直接在new的时候把关联的 model 设置好。accepts_nested_attributes_for不知道楼主有没有用过。 当然以上 2 点你做到了,不知道inverse_of,你还是会提这个问题,但是你对问题的理解肯定比现在的要好。 说了很多题外话....总之希望楼主有帮助吧。:-)

#14 楼 @upsilon2gamma 看来我一开始想法就偏差了。validates_presence_of 给了 article_id 是认为 tag 的创建是必须得和某个 article 关联,只是没想到这样一来即便是article.build也会出问题。后面的accepts_nested_attributes_for倒是没用过,我用的大部分函数都只是在看教程时候记下来的,显得有点孤陋寡闻了。

#16 楼 @Ryan 你的想法很有意思。既然本意就是“要 tag 必须和某个 article 关联”,为什么不直接用validates_presence_of :article呢?而是“跳过”这个概念,用更加“底层”的外键来验证。 至于accepts_nested_attributes_for,老实说,我也很少用..... -_-b 只是知道这个方法。有时候还是喜欢在 action 里预处理一下。这方面没有什么大问题,时间长了你自然也会知道的。:-) 我的本意只是希望给你一个建议:在发现一个问题的时候,稍微深挖一下。用 gem 当然是可行的方案(甚至也是最终的方案),但有可能就少学一些基础的东西了。 个人观点,仅供参考。:-)

#17 楼 @upsilon2gamma 验证其实我也没想太多,只是在开发的过程中用了 has_many 然后企图用 build,结果报错信息会说,missing article_id,那我就按照出错信息来给一个 id。更抽象的使用 article,倒是没真的使用过。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号