我有一个名为jobplans
的表和一个名为workorders
的表。
它们有许多相似的字段(列)。
我想有一个按钮,可以调用代码创建一个新的workorder
并复制jobplan
中的类似字段。
我在另一个问题中发现了这样的代码:
@jobplan = Jobplan.find(params[:id]) # find original object
@workorder = Workorder.create(@jobplan.attributes)
但是 -
该代码是否有效?如果是的话,它会去哪里?可以进入有按钮的视图页面吗?控制器?模型?
感谢您的帮助!
答案 0 :(得分:1)
我会创建一个控制器操作,比如copy_to_workorder
并将此按钮绑定到该控制器操作。我认为这个行动生活在JobplanController
上是有意义的。
所以,在高层:
您的观点会有一个表单,可以向POST
提交请求(可能是jobplan/:id/copy_to_workorder
)。你也可以创建这条路线。
您的JobplanController
会有一个copy_to_workorder
操作,基本上会执行上面的代码,尽管最好对此进行一些额外的验证。
copy_to_workorder
操作会执行某些操作......也许会重定向到JobplanController#index
操作?
在Jobplan
上创建实例方法也可能更清晰,也许可以说create_workorder!
可以执行上面两行代码。
答案 1 :(得分:1)
一眼就能看出你发布的代码会起作用。它所属的地方是一个更有趣的问题。
我的第一个想法是考虑这些模型类是否在为应用程序存在的问题域建模方面做得很好。具有几乎相同的字段集的多个模型似乎是一个警告标志,即这些公共属性应该存在于一个模型上,而工作流的不同阶段应该由不同的类表示。
在这里猜测域名。如果某个计划有一个跟踪工作流状态变化的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
。创建Workorder
已经有一个明确定义的家庭; create
的{{1}}操作,让我们尝试在那里实现此行为。
要创建WorkordersController
,我们需要一堆属性。我们可以让客户端在这个Workorder
操作的POST请求中包含它们,但这会使它们处于客户端的控制之下。如果客户端不应该从相应的create
上的任何内容更改这些值,那么这似乎是一个坏主意。相反,似乎最好只接收Jobplan
的{{1}},如问题所示。
我们可能只是将Jobplan
id
作为POST参数传递,但我认为有更优雅的解决方案。通过在Jobplan
下声明id
作为嵌套资源,我们可以在网址中表达这些模型之间的关系。这会导致一些非常自然的响应,例如,如果您尝试使用Workorder
id创建Jobplan
而不存在,那么404响应会有意义。
我们当然可以在测试中描述我们想要控制器的特定行为:
Workorder
这将失败,因为我们尝试到达的路线未定义,因此我们可以从那里开始。
Jobplan
允许我们POST到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
希望这似乎是一条合理的道路和地方。我已经做了一些关于你的应用程序需要什么以及你可能使用什么工具的假设,以及实际上没有运行上面的任何代码,并且遗漏了我通常会编写的许多其他测试,但我相信你可以决定是否适用。 我不想再通过对测试的咆哮来做出这个答案,但如果上面的内容不熟悉,我喜欢Jared对outside in testing Rails development的描述。