我在尝试更新具有has_and_belongs_to_many
关联的模型时遇到问题。
假设Post
has_and_belongs_to_many
Tag
和Post
验证了标题和Tags
的存在。
如果我更新Post
,删除其标题和代码,我会在title
和tags
中收到验证错误,确定。
但ActiveAdmin
已删除了Post
和Tag
之间关联的记录,因此,如果我离开Post
编辑页面,则post
在数据库中无效,没有tags
。
这是我的模特:
class Tag < ActiveRecord::Base
attr_accessible :label
has_and_belongs_to_many :posts
end
class Post < ActiveRecord::Base
attr_accessible :content, :title, :tag_ids
has_and_belongs_to_many :tags
validates_presence_of :content, :title, :tags
end
ActiveAdmin.register Post do
form do |f|
f.inputs do
f.input :title
f.input :content
f.input :image
f.input :tags
end
f.buttons
end
end
我使用了chosen-rails gem,它允许用户取消选择帖子的所有标签。
总结一下,我的问题是:ActiveAdmin在执行模型验证之前更新数据库上的关系。
有这种行为的解决方案还是我做错了什么?
修改
这里是我尝试更新没有标题和标签的帖子时的请求日志:
Started PUT "/admin/posts/8" for 127.0.0.1 at 2013-04-01 10:32:07 -0300
Processing by Admin::PostsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"amSbLlP/rgDrNn/N8lgq/KEaRXK1fMPShZDwpZ0QIJ4=", "post"=>{"title"=>"", "content"=>"content", "tag_ids"=>["", ""]}, "commit"=>"Update Post", "id"=>"8"}
AdminUser Load (0.2ms) SELECT `admin_users`.* FROM `admin_users` WHERE `admin_users`.`id` = 1 LIMIT 1
Post Load (0.2ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 8 LIMIT 1
Tag Load (0.2ms) SELECT `tags`.* FROM `tags` INNER JOIN `posts_tags` ON `tags`.`id` = `posts_tags`.`tag_id` WHERE `posts_tags`.`post_id` = 8
(0.1ms) BEGIN
SQL (12.3ms) DELETE FROM `posts_tags` WHERE `posts_tags`.`post_id` = 8 AND `posts_tags`.`tag_id` IN (1, 2)
(49.6ms) COMMIT
(0.1ms) BEGIN
(0.2ms) ROLLBACK
Post Load (0.3ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 8 LIMIT 1
Tag Load (0.2ms) SELECT `tags`.* FROM `tags`
Rendered /home/rodrigo/.rvm/gems/ruby-1.9.3-p125@blog/gems/activeadmin-0.5.1/app/views/active_admin/resource/edit.html.arb (192.3ms)
Completed 200 OK in 276ms (Views: 194.8ms | ActiveRecord: 63.3ms)
编辑2:
好的,我确定ActiveAdmin有这个bug。
查看ActiveRecord行为,我认为仅使用模型类就会破坏验证流程。见这个例子:
1.9.3p125 :064 > post = Post.find(8)
Post Load (0.3ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 8 LIMIT 1
=> #<Post id: 8, title: "title", content: "content", created_at: "2013-03-27 13:13:20", updated_at: "2013-03-27 13:13:20", image: "extrato.bmp">
1.9.3p125 :065 > post.tags
Tag Load (0.2ms) SELECT `tags`.* FROM `tags` INNER JOIN `posts_tags` ON `tags`.`id` = `posts_tags`.`tag_id` WHERE `posts_tags`.`post_id` = 8
=> [#<Tag id: 1, label: "tag", created_at: "2013-02-25 18:32:45", updated_at: "2013-02-25 18:32:45">, #<Tag id: 2, label: "new", created_at: "2013-02-25 18:32:50", updated_at: "2013-02-25 18:32:50">]
1.9.3p125 :066 > post.title = ""
=> ""
1.9.3p125 :067 > post.save #<<<<<<< It's invalid on title
=> false
1.9.3p125 :068 > post.tags = [] #<<<<<<< This shouldnt trigger database update
(0.3ms) BEGIN
SQL (0.5ms) DELETE FROM `posts_tags` WHERE `posts_tags`.`post_id` = 8 AND `posts_tags`.`tag_id` IN (1, 2)
(55.5ms) COMMIT
=> []
1.9.3p125 :069 > post.save #<<<<<<< It's invalid on title AND TAGS
(0.2ms) BEGIN
(0.2ms) ROLLBACK
=> false
1.9.3p125 :070 > post.reload
Post Load (0.2ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 8 LIMIT 1
=> #<Post id: 8, title: "title", content: "content", created_at: "2013-03-27 13:13:20", updated_at: "2013-03-27 13:13:20", image: "extrato.bmp">
1.9.3p125 :071 > post.valid? #<<<<<<< Now, I have this model in invalid state
Tag Load (0.6ms) SELECT `tags`.* FROM `tags` INNER JOIN `posts_tags` ON `tags`.`id` = `posts_tags`.`tag_id` WHERE `posts_tags`.`post_id` = 8
=> false
在进行任何数据库更新之前,有没有办法更新帖子属性(包括标签)并验证模型?
答案 0 :(得分:2)
您可以使用此gem:https://github.com/MartinKoerner/deferred_associations
延迟关联将修复此错误。
答案 1 :(得分:1)
@Rodrigo我能够在没有主动管理员的情况下在本地重现您的问题,问题实际上是使用HABTM关系时的默认操作之一,如果您看到[here] [1]
集合=对象
Replaces the collection’s content by deleting and adding objects as appropriate.
显然你需要覆盖这个操作
以下是一个例子:
让我知道我该如何帮助你
答案 2 :(得分:0)
我建议添加一个tmp变量并在其中存储值。然后,如果验证通过,则应将它们移动到数据库。
以下是一个例子:
您的models/article.rb
:
class Article < ActiveRecord::Base
validate :author_presence
has_and_belongs_to_many :authors
attr_writer :tmp_author_ids
def tmp_author_ids
@tmp_author_ids || author_ids
end
def author_presence
if tmp_author_ids.reject(&:blank?).empty?
errors.add(:tmp_author_ids, 'Author is missing')
else
self.author_ids = tmp_author_ids
end
end
end
您的admin/article.rb
,阻止form
:
f.input :tmp_author_ids, as: :select, multiple: true, collection: Author.all, label: 'Authors'
就是这样
答案 3 :(得分:0)
因此,对于遇到相同问题的任何人,我都找到了一种解决方法: 您可以在has_and_belongs_to_many关联中定义一个before_remove,并引发一个异常,然后可以在ActiveAdmin中捕获该异常。
为此,
class Post < ActiveRecord::Base
attr_accessible :content, :title, :tag_ids
has_and_belongs_to_many :tags, before_remove: :check_something
validates_presence_of :content, :title, :tags
end
def check_something(agent)
if self.tags.size == 1
raise ActiveModel::MissingAttributeError.new 'something'
end
end
更多信息:https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
答案 4 :(得分:-1)
如果需要,您可以在active_admin中重新定义更新操作,以防止保存空标记,如此样式
ActiveAdmin.register Post do
controller do
def update
if params[:post][:tag_ids] == ["", ""]
flash.now[:alert] = "You can't remove all tags"
render :edit
else
super
end
end
end
...
end
我认为模型中的这些东西可以删除
attr_accessor :new_tag_ids
validate :validate_new_tags_ids
after_save :update_tags
def update_tags
self.tag_ids = @new_tag_ids if defined?(@new_tag_ids)
@new_tag_ids = nil
end
private
def validate_new_tags_ids
errors[:tags] << "can't be blank (2)" if @new_tag_ids.blank?
end