问题描述
已经有关于如何在同一控制器的方法之间传递参数的线程。
例如,有时您有一篇文章,当您对其调用#update动作时,您还希望同时通过TagsController的#update动作来更新与其关联的标签。
这个习惯用法就像在ArticlesController#actions中实例化TagsController,然后在执行tags_controller_instance.send(:update)
时传递一个新的Rack :: Response实例,仅将整个params-hash的:tags部分传递给它。 / p>
您将只需要发送ArticlesController收到的params-hash的特殊部分,因为TagsController将具有不同的StrongParameters!
我认为这可以归结为以下问题:如何创建一个Rack :: Request来复制一方面的RequestsController以及如何不将整个params-hash传递给它,而仅将其中有意义的部分传递给它。 >
然后应该可以在ArticlesController#update中使用this.tags
来请求更新的标记列表,对吗?
谢谢
冯·斯波茨
解决方法
这个习惯用法就像在ArticlesController#actions中实例化TagsController,然后在执行tags_controller_instance.send(:update)时将仅带有整个参数哈希的:tags部分的Rack :: Response新实例传递给它。
请不要那样做!很难理解和维护。您可能甚至还没有想到其他副作用。
问题是,您不想在TagsController#update
中复制的ArticlesController
是什么?如果是复杂的逻辑,我认为您应该对此进行抽象,例如在(service) object中,然后调用它。
类似这样的东西:
class UpdateTags
def self.run(params)
new(params).run
end
def initialize(params)
@params = params
end
def run
# copy logic from TagsController
end
end
然后您可以在控制器中使用/重复使用此服务
class TagsController
def update
UpdateTags.run(params)
end
end
class ArticlesController
def update
# update Article or move this in a dedicated service too
UpdateTags.run(params)
end
end
另一种方法可能是让Article
和nested attributes接受Tags
的属性。
编辑
详细说明为什么实例化另一个控制器不是一个好主意。
- 过滤器之前呢? (再次)执行它们可以吗?
- 如何渲染视图?您显然不想渲染视图,因此这是额外的工作,并且可能会有副作用吗?
- 其他副作用,例如缓存,日志记录,数据分析。
- 实例化控制器不是公共API,因此可以在Rails版本之间进行更改,从而导致更新困难。
- 这不是常见的模式,因此很难理解
我只是再次强调,这不是一个好主意。复制代码比错误的抽象要好。
,这听起来像是疯狂的Wile E. Coyote解决方案,对于用nested attributes解决的问题却微不足道。
在Rails中,控制器的唯一公共方法应该是控制器的 action ,它们是响应HTTP请求的方法,并且这些只能通过http调用来调用 >。
任何其他方法should be private/protected。没有有效的方案,您需要从另一个控制器上调用TagsController#update
来更新标签,因为该方法为should do only one thing-更新标签以响应PATCH /tags/:id
。
如果要在一个请求中更新文章及其标签,请使用accepts_nested_attributes_for :tags
:
class Article < ApplicationRecord
has_many :tags
accepts_nested_attibutes_for :tags
end
这将创建一个tags_attributes=
设置器,您可以使用该设置器与父级一起更新嵌套记录。
如果您想在不适合经典继承的类之间共享功能,请使用水平继承。在Ruby中,这意味着模块:
module Taggable
private
def tag_attributes
[:foo,:bar,:baz]
end
end
class TagsController < ApplicationController
include Taggable
# PATCH /tags/:id
def update
@tag = Tag.find(params[:id])
if @tag.update(tag_params)
redirect_to @tag,success: 'Tag updated'
else
render :edit
end
end
private
def tag_params
params.require(:tag).permit(*tag_attibutes)
end
end
class ArticlesController < ApplicationController
include Taggable
def update
if @article.update(article_params)
redirect_to @article,success: 'Article updated'
else
render :edit
end
end
private
def article_params
params.require(:article).permit(:title,:body,tags_attributes: tag_attibutes)
end
end
<%= form_with(model: @article) do |f| %>
...
<%= f.fields_for :tags do |tag| %>
<div class="field">
<%= tag.label :foo %>
<%= tag.text_field :foo %>
</div>
<% end %>
<% f.submit %>
<% end %>
在某些情况下,您可能会选择使用AJAX来通过发送确实调用另一个控制器的异步HTTP请求(而不是在同一请求中)来“即时”创建/更新/删除嵌套资源。但是,这确实超出了这个问题的范围。