在我的项目中,我有一些场景,在控制器操作中我需要做很多依赖的东西,这些东西无法直接移动到模型中。为了保持控制器的瘦,我最终将该代码移动到帮助器。这是一个好习惯吗?
例如,在控制器操作中,我需要先验证请求的校验和,然后在两个表中进行输入,然后调用一些外部api并根据结果更新一些值。我最终将调用外部api转移给了帮助者。
答案 0 :(得分:2)
这是一个好习惯吗?
<强>都能跟得上即可。助手仅适用于特定于视图的内容。货币格式,日期格式,包装特定<div>
中的东西,这种东西。默认情况下,它们甚至不能在控制器中使用。你必须明确要求它们。
协调多个运动部件的合作不应该是帮手。是的,控制器也不是一个好地方。我,我通常将这种逻辑放在ServiceObject或其他东西中。所以我的控制器通常看起来像这样:
class ProjectsController < AppicationController
def create
# this will create the project, create associated objects, push notifications to
# whatever needs to be notified, etc.
::ServiceObjects::Project::Create.new(project_params)
# render something
end
...
end
这样,您的控制器仍然很瘦。另外,服务对象更容易测试。
答案 1 :(得分:1)
良好的做法?当然不!它不仅会使代码维护成为一项艰巨的任务,而且还会使测试变得困难。
从来没有一种情况下无法从控制器移动逻辑。通常,对于任何面向对象的语言,项目应至少基于MVC模式,以将业务逻辑与表示分开。
根据您的示例,简单的事情可以轻松地将个人责任委派给专家类,从而从控制器中删除逻辑。
class FooController < ApplicationController
def create
handler = FooHandler.new(params[:foos])
foo = handler.process_foo
if foo[:result]
flash[:success] = 'Foo was successful'
redirect_to foo_path
else
flash[:error] = foo[:errors]
redirect_to foo_path
end
end
end
class FooHandler
delegate :valid_checksum?, to: :checksum_klass
delegate :create_foos, to: :foo_klass
delegate :call_foo_api, to: :foo_api_klass
def initialize(params)
@params = params
end
def process_foo
return {result: false, errors: 'failed checksum'} unless valiid_checksum?
return {result: false, errors: 'failed to create the foos' unless create_foos
return {result: false, errors: 'api errors'} unless call_foo_api
{result: true}
end
private
attr_accessor :params
def checksum_klass
@checksum_klass ||= ChecksumChecker.new(params[:checksum])
end
def checksum_klass
@foo_klass ||= FooCreator.new(params[:foo_objects])
end
def checksum_klass
@foo_api_klass ||= FooApiHandler.new(params[:foo_objects])
end
end
上述实现方式是一个示例,说明如何开始将每个单独的进程拆分为自己的类,该类运行单个FooHandler类,将所有逻辑与控制器分离。在这个例子中,所有控制器都关心的是该过程是否成功。
我的示例基于每个动作都包含在其自己的类中,因此FooHandler可以在需要时将责任委派给该类,传递一些数据并清洗任何责任。它所关心的只是结果。
这一切都归结为你对模式的理解。我使用的两个最有用的模式(除了Rubys duck typing和delegation)是观察者模式和装饰模式。
观察者模式允许您将依赖进程与可观察类的状态相关联。因此,对于您的示例,如果API调用依赖于正在创建的对象,请尝试设置具有FooApiHandler作为订阅侦听器的FooCreationHandler。如果处理程序成功创建记录,则它可以通知FooApiHandler可以调用外部API,从而解除依赖关系。
装饰器之类的模式允许您在需要时“装饰”具有特定行为的对象,而不是对其他类具有大的,错综复杂的if语句和“知识”。同样,这是一个很好的模式,允许您使用抽象行为创建专家类,而不是包含一个类或控制器的逻辑。
希望这个简短的例子有所帮助。