Rails将记录复制到另一个表

时间:2013-11-12 00:25:08

标签: ruby-on-rails

我有一个名为jobplans的表和一个名为workorders的表。 它们有许多相似的字段(列)。

我想有一个按钮,可以调用代码创建一个新的workorder并复制jobplan中的类似字段。

我在另一个问题中发现了这样的代码:

@jobplan = Jobplan.find(params[:id]) # find original object
@workorder = Workorder.create(@jobplan.attributes)

但是 -

该代码是否有效?如果是的话,它会去哪里?可以进入有按钮的视图页面吗?控制器?模型?

感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

我会创建一个控制器操作,比如copy_to_workorder并将此按钮绑定到该控制器操作。我认为这个行动生活在JobplanController上是有意义的。

所以,在高层:

  1. 您的观点会有一个表单,可以向POST提交请求(可能是jobplan/:id/copy_to_workorder)。你也可以创建这条路线。

  2. 您的JobplanController会有一个copy_to_workorder操作,基本上会执行上面的代码,尽管最好对此进行一些额外的验证。

  3. copy_to_workorder操作会执行某些操作......也许会重定向到JobplanController#index操作?

  4. 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,则会映射到createindexshowupdatedestroy操作。当应用直接投放HTML网页时,我们也可以添加editnew操作,以便为createupdate端点提供接口。任何时候我们超越这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的描述。