乍一看,在我看来,您发布的代码会起作用。它属于哪里是一个更有趣的问题。
我的第一个想法是考虑这些模型类是否在对应用程序存在的问题域建模方面做得很好。具有几乎相同字段集的多个模型似乎是一个警告信号,表明这些公共属性应该存在于一个模型上,并且工作流的不同阶段应该由不同的类表示。
在这里猜测领域术语。拥有一些具有 JobStatus 跟踪工作流状态变化的计划会更合适吗?Jobplan 和 Workorder 是否都具有一个蓝图(或任何捕获这些共享属性的东西)?
无论如何,让我们假设这些模型非常适合您谈论和使用该领域的核心概念的方式。那么这种行为应该存在于哪里,我们如何调用它呢?
我们可以从描述我们希望用户看到的行为开始,不管我们如何实现它。例如,如果应用程序使用 Capybara 功能规范,我们可能会编写如下内容:
feature 'the job plan show page' do
given(:user) { FactoryGirl.create :job_supervisor }
given(:jobplan) { FactoryGirl.create :jobplan }
background do
login user
end
# ...
scenario 'creating a work order from the job plan' do
visit jobplan_path(jobplan)
click_link 'Create work order'
expect(page).to have_content 'Work order created'
expect(current_path).to match /\/workorders\/\d/
# ...
end
end
这个测试还不会通过,但我们会到达那里。
我发现尝试将 Rails 控制器限制为基本的 CRUD(创建、读取、更新、删除)操作很有帮助。如果应用程序只是公开一个 API,则这些映射到create
、index
或show
、update
和destroy
操作。当应用程序直接为 HTML 页面提供服务时,我们也可以添加edit
和new
操作来为create
和update
端点提供接口。每当我们超出这 7 项操作时,这都是一个警告,我们可能没有很好地建模问题并且我们的控制器承担了太多责任。
在这种情况下,我们知道我们想要创建一个新的Workorder
. Creating Workorder
s 已经有了一个明确的家;所以让我们尝试在那里实现这个行为create
。WorkordersController
要创建一个Workorder
,我们需要一堆属性。我们可以让客户端将它们包含在此create
操作的 POST 请求中,但这会将它们置于客户端的控制之下。如果客户端不应该从相应的值更改这些值,Jobplan
那么这似乎是个坏主意。相反,正如问题中已经显示的那样,似乎最好只接收Jobplan
's 。id
我们可能只是将Jobplan
id
POST 作为 POST 参数传递,但我认为有一个更优雅的解决方案。通过声明Workorder
为嵌套资源,Jobplan
我们可以在我们的 URL 中表达这些模型之间的关系。这会导致一些非常自然的响应,例如如果您尝试使用不存在Workorder
的id 创建一个 404 响应是有意义的。Jobplan
我们当然也可以在测试中描述我们希望控制器的特定行为:
describe WorkorderController do
let(:jobplan) { FactoryGirl.create :jobplan }
describe '#create' do
context 'given a valid jobplan id' do
it 'creates a new workorder' do
expect { post jobplan_id: jobplan.id }.to change {Workorder.count}.by(1)
end
it 'redirects to the new workorder' do
post jobplan_id: jobplan.id
expect(response).to be_redirect
end
it 'copies the jobplan attributes to the new workorder' do
post jobplan_id: jobplan.id
expect(assigns(:workorder).location).to eq jobplan.location
# ...
end
end
end
end
这将失败,因为我们试图到达的路线是未定义的,所以我们可以从那里开始。
resources :jobplans do
resources :workorders, only: [:create]
end
resources :workorders, only: [:show]
将允许我们/jobplans/<jobplan_id>/workorders
发布以创建新的工单。请注意,这里我们已经声明了我们的路线,以便在工作计划下创建工作订单是合适的,但所有其他允许的操作仍然使用顶级工作订单路线。将来我们可能会回来扩展这些选项;也许我们希望能够获取/jobplans/<jobplan_id>/workorders
特定工作计划下所有工作单的列表,同时/workorders
返回系统中的每个工作单。
现在我们可以实现控制器动作了。
def create
jobplan = Jobplan.find(params[:jobplan_id])
@workorder = Workorder.create(@jobplan.attributes)
redirect_to workorder_path(@workorder)
end
作为最后的想法,我们可能不想忘记使用哪个Jobplan
来创建给定的Workorder
,因此我们可以将这种关系添加到我们的模型中并清理一些东西。从工作计划创建工作订单变得越来越复杂,因此我们可能要求控制器做太多事情。如果创建工作订单会产生副作用,例如需要发送更改或更新账单的电子邮件公告,我们可能会将创建工作订单的责任转移到模型或服务对象中。最终我们的控制器动作可能看起来像:
def create
jobplan = Jobplan.find(params[:jobplan_id])
@workorder = Workorder.create_from_jobplan(jobplan)
redirect_to workorder_path(@workorder)
end
希望这似乎是一个合理的路径和最终的地方。我已经对您的应用程序需要什么以及您可能使用的工具以及实际上没有运行上面的任何代码做出了许多假设,并遗漏了我通常可能编写的许多其他测试,但我相信您可以决定酌情应用或不应用。我不想再通过咆哮测试来回答这个问题,但如果对上面的内容不熟悉,我喜欢 Jared在 testing Rails development 中对外部的描述。