1

我有一张叫做 的桌子jobplans和一张叫做workorders. 它们有许多相似的字段(列)。

我想要一个按钮来调用代码创建一个新的workorder并从jobplan.

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

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

但 -

该代码会起作用吗?如果是,它去哪儿了?它可以进入有按钮的视图页面吗?控制器?模型?

谢谢你的帮助!

4

2 回答 2

1

比如说,我会创建一个控制器动作,copy_to_workorder然后将此按钮绑定到该控制器动作。我认为这个动作在JobplanController.

所以,在高层次上:

  1. 您的视图将有一个POSTjobplan/:id/copy_to_workorder. 你也会创建这条路线。

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

  3. 然后copy_to_workorder动作会做一些事情......也许重定向到JobplanController#index动作?

在 上创建一个实例方法也可能更干净Jobplan,也许可以说create_workorder!上面的两行代码。

于 2013-11-12T00:31:51.617 回答
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. Creating Workorders 已经有了一个明确的家;所以让我们尝试在那里实现这个行为createWorkordersController

要创建一个Workorder,我们需要一堆属性。我们可以让客户端将它们包含在此create操作的 POST 请求中,但这会将它们置于客户端的控制之下。如果客户端不应该从相应的值更改这些值,Jobplan那么这似乎是个坏主意。相反,正如问题中已经显示的那样,似乎最好只接收Jobplan's 。id

我们可能只是将Jobplan idPOST 作为 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 中对外部的描述。

于 2013-11-12T06:56:45.793 回答