假设有这样两个模型,并如此创建模型
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 方法了
#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: :tags
。inverse_of
的参数是 article 中关联 tag 的那个名字。比如has_many :tags
中的tags
。
has_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 之后会变成一个数组的处理。
has_many
?如果是用中间一个 model(同时belongs_to
tag 和 article)的话,那就应该在这个中间的 model 里加。你可以看看 api,上面的例子几乎和你的一样,他是 Post 和 tag。注意inverse_of
是不支持through
的。所以这种情况,你的validates
应该也放在中间那个 model 里。#8 楼 @upsilon2gamma 嗯,中间 model 当时就是我的方案,但是当初没知道可以用 inverse_of,而且当时试下来是,只有中间 model 和关联 model 可以互相创建对象,但是 3 个模型不能一次性创建,就比如,article,tag,article_tag 三个模型,创建 article 的时候可以把 article 和 article_tag 两个模型创建,但是不能把相关的 tag 创建进 tag 的表,当时想过重写 initialize 构造函数,然后觉得太麻烦了,一个个人站点,没必要弄得这么麻烦,性能上也不会有什么瓶颈,现在这样写对于 tag 搜索 article 也不会有什么麻烦的。
#12 楼 @Rei @Ryan tag 这种常用的功能肯定有 gem,但我个人不是特别推荐直接给新手推荐 gem 来完成某个功能。因为这样很容易把问题掩盖起来。 自己实现虽然很有可能“笨拙”,但相当于自己对业务逻辑的做了分析和抽象。能学到很多东西。而很多 gem 都是"best practice"的结果。有些设定初学者根本无法理解。很容易把简单问题复杂化。出了问题也不知道到底是自己使用有问题,还是 gem 有 bug。 我自己在做一个项目的认证功能的时候,一开始考虑用 devise,但看了文档,发现功能实在“太强大”,我只是要一个非常简单的用户名密码的功能,却要设置很多东西(实际也不算多,但自己确实有点懒)。后来就自己写了几个 method,几个 helper 就完了。目前来说很“粗糙”,但是以后功能多了,复杂了,自然而然就会用 devise。我相信那时对其中的设置应该有更深的理解。 再说点题外话。我一个同事之前也想学 rails,我自己没什么“教学”的经验(本身就不是学计算机的,又不是在 it 公司工作,都是自己自学摸索的。),“自以为是”的给了他很多“经验”,以为帮他把一些坑填了他能更快学好。但现在发觉他虽然写个应用没什么问题(他都跳槽到软件公司去了),却还是会问我一些比较基础的东西,让我觉得很惊讶。我现在就在反省是不是应该在他学“游泳”的时候给他机会“呛几口水”。 以我个人的看法,楼主的问题本身就值得推敲。
article_id
设置在validates_presence_of
里已经是比较“初级”的问题了(我没有恶意)。因为article
都还没有保存的时候,不可能有这个值。如果知道validates_presence_of
本身实际就是调用blank?
,那#1 楼的方案应该也能想到。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,倒是没真的使用过。