简体   繁体   English

Rails将记录复制到另一个表

[英]Rails copy a record to another table

I have a table called jobplans and one called workorders . 我有一张叫工作jobplans的表,一个叫工作workorders的表。 They have many similar fields (columns). 他们有许多相似的字段(列)。

I would like to have a button that would invoke code create a new workorder and copy the similar fields from the jobplan . 我想有一个按钮,就会调用代码创建一个新的workorder ,并从复制类似领域jobplan

I found this code like this in another question: 我在另一个问题中找到了这样的代码:

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

But - 但是-

Will that code work? 该代码可以工作吗? If yes, where does it go? 如果是,它将去哪里? Can it go on the view page that has the button? 它可以在带有按钮的视图页面上显示吗? Controller? 控制器? Model? 模型?

Thanks for your help! 谢谢你的帮助!

I'd create a controller action, say, copy_to_workorder and bind this button to that controller action. 我将创建一个控制器动作,例如copy_to_workorder然后将此按钮绑定到该控制器动作。 I think it makes sense for this action to live on the JobplanController . 我认为将此动作JobplanControllerJobplanController上是JobplanController

So, at a high-level: 因此,从高层次看:

  1. Your view would have a form that submits a request (likely a POST ) to jobplan/:id/copy_to_workorder . 您的视图将具有一个向jobplan/:id/copy_to_workorder提交请求的表单(可能是POST )。 You'd create this route as well. 您还将创建此路线。

  2. Your JobplanController would have a copy_to_workorder action which would essentially do your code above, although it would be a good idea to have some additional validations on this. 您的JobplanController将具有一个copy_to_workorder操作,该操作基本上可以在上面完成您的代码,尽管对此进行一些附加验证是一个好主意。

  3. The copy_to_workorder action would then do something ... Maybe redirect to the JobplanController#index action? 然后copy_to_workorder操作将执行某些操作...也许重定向到JobplanController#index操作?

It may also be cleaner to create an instance method on Jobplan , maybe say create_workorder! Jobplan上创建实例方法也可能更干净一些,比如Jobplan create_workorder! which would do the two lines of code above. 这将执行上面的两行代码。

At a glance it looks to me like the code you posted would work. 乍一看,我觉得您发布的代码可以正常工作。 Where it belongs is a more interesting question. 它属于哪里是一个更有趣的问题。

My first thought is to consider if these model classes are doing a good job of modelling the problem domain your application exists to serve. 我的第一个想法是考虑这些模型类是否可以很好地对您的应用程序要服务的问题域进行建模。 Multiple models with near-identical sets of fields seems like a warning sign that those common attributes should live on one model and the different stages of your workflow should be represented by a different class. 具有几乎相同的字段集的多个模型似乎是一个警告信号,这些共同的属性应该存在于一个模型上,并且工作流的不同阶段应该由不同的类表示。

Guessing at domain terms here. 在这里猜测域名。 Would it be a better fit to have some Plan which has a JobStatus which tracks those changes in workflow state? 有一些计划具有一个JobStatus来跟踪工作流程状态的那些变化是否更合适? Could Jobplan and Workorder both have one Blueprint (or whatever captures these shared attributes)? Jobplan和Workorder都可以有一个蓝图(或任何捕获这些共享属性的东西)吗?


Regardless, let's assume these models are a good fit for the way you talk about and work with the core concepts in this domain. 无论如何,让我们假设这些模型非常适合您讨论和使用该领域核心概念的方式。 Where then should this behavior live and how do we invoke it? 那么该行为应该在哪里存在,我们如何调用它?

We can start by describing the behavior we want to user to see, regardless of how we implement it. 无论我们如何实现,我们都可以从描述我们希望用户看到的行为开始。 For example if the app uses Capybara feature specs we might write something like: 例如,如果应用程序使用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

This test will not pass yet but we'll get there. 该测试尚未通过,但我们会到达那里。

I find it helpful to try to restrict Rails controllers to just the basic CRUD (Create, Read, Update, Delete) actions. 我发现尝试将Rails控制器限制为基本的CRUD(创建,读取,更新,删除)操作很有帮助。 If the app just exposes an API those map to the create , index or show , update , and destroy actions. 如果应用仅公开API,则这些映射将映射到createindexshowupdatedestroy动作。 When the app is serving HTML pages directly then we can add the edit and new actions as well to serve the interfaces for the create and update endpoints. 当应用程序直接提供HTML页面时,我们还可以添加editnew动作,以提供用于createupdate端点的界面。 Any time we expand beyond those 7 actions it is a warning that we have perhaps not done a good job modelling the problem and our controller is taking on too many responsibilities. 每当我们扩展到这7个动作之外时,就警告我们可能没有很好地对问题进行建模,并且我们的控制器承担了太多的责任。

In this case we know we want to create a new Workorder . 在这种情况下,我们知道我们要创建一个新的Workorder Creating Workorder s already has a well defined home; 创建Workorder已有一个定义明确的主目录; the create action of the WorkordersController so let's try to implement this behavior there. WorkordersControllercreate动作,因此让我们尝试在WorkordersController实现此行为。

To create a Workorder we need a bunch of attributes. 要创建工作Workorder我们需要一堆属性。 We could have the client include them in the POST request to this create action but that leaves them under the client's control. 我们可以让客户端将它们包含在此create操作的POST请求中,但这将它们置于客户端的控制之下。 If the client is not supposed to change these values from whatever they were on the corresponding Jobplan then that seems like a bad idea. 如果不希望客户从相应的Jobplan上更改它们的值,那么这似乎是个坏主意。 Instead it seems preferable to receive just the Jobplan 's id , as already shown in the question. 相反,似乎最好只接收Jobplanid ,如问题中已经显示的那样。

We might just pass the Jobplan id as a POST param but I think there is a more elegant solution. 我们可能只是将Jobplan id作为POST参数传递,但我认为有一个更优雅的解决方案。 By declaring Workorder as a nested resource under Jobplan we can express the relationship between these models in our URLs. 通过声明Workorder的下一个嵌套的资源Jobplan我们可以表达我们的网址,这些模型之间的关系。 This leads to some very natural responses, like a 404 response making sense if you try to create a Workorder with a Jobplan id which doesn't exist. 这会导致一些非常自然的响应,例如,如果您尝试使用不存在的Jobplan ID创建工作Workorder ,则404响应Workorder意义。

We can of course describe the specific behavior we want from our controller in a test as well: 我们当然也可以在测试中描述我们希望控制器提供的特定行为:

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

This will fail since the route we are trying to reach is undefined so we can start there. 这将失败,因为我们尝试到达的路线是不确定的,因此我们可以从此处开始。

resources :jobplans do
  resources :workorders, only: [:create]
end
resources :workorders, only: [:show]

Will allow us to POST to /jobplans/<jobplan_id>/workorders to create a new workorder. 将使我们能够发布到/jobplans/<jobplan_id>/workorders以创建新的工作订单。 Note that here we have declared our routes such that it is appropriate to create a workorder under a jobplan but all other allowed actions still use the toplevel workorders routes. 请注意,这里我们声明了路线,这样适合在工作计划下创建工作单,但是所有其他允许的操作仍然使用顶层工作单路线。 In the future we might come back and expand these options; 将来,我们可能会回来扩展这些选项。 perhaps we will want to be able to GET /jobplans/<jobplan_id>/workorders for a list of all workorders under a specific jobplan while /workorders returns every workorder in the system. 也许我们将希望能够获取/jobplans/<jobplan_id>/workorders以获得特定工作计划下所有工作单的列表,而/workorders返回系统中的每个工作单。

Now we can implement the controller action. 现在我们可以执行控制器动作。

def create
  jobplan = Jobplan.find(params[:jobplan_id])
  @workorder = Workorder.create(@jobplan.attributes)
  redirect_to workorder_path(@workorder)
end

As a final thought we probably do not want to lose track of which Jobplan was used to create a given Workorder so we might add that relationship to our models and clean up a few things. 最终的想法是,我们可能不想丢失使用哪个Jobplan来创建给定的Workorder因此我们可以将该关系添加到模型中并清理一些东西。 Creating workorders from jobplans is getting complicated so we are probably asking the controller to do too much. 从工作计划创建工作单变得越来越复杂,因此我们可能要求控制器做太多事情。 We might move the responsibility for creating a workorder into the model or a service object if it is going to have side effects like needing to send off email announcements of the change or update billing. 如果可能会有副作用,例如需要发送变更通知或更新账单,我们可能会将创建工作订单的责任转移到模型或服务对象中。 Ultimately our controller action might look something like: 最终,我们的控制器动作可能类似于:

def create
  jobplan = Jobplan.find(params[:jobplan_id])
  @workorder = Workorder.create_from_jobplan(jobplan)
  redirect_to workorder_path(@workorder)
end

Hopefully that seems like a reasonable path and place to end up. 希望这看起来是一条合理的道路和最终解决之道。 I've made a number of assumptions about what your app needs and what tools you might be using as well as not actually run any of the code above and left out many of the other tests I might normally write but I trust you can decide to apply it or not as appropriate. 我对您的应用程序需要什么以及您可能使用的工具做了很多假设,并且并未实际运行上面的任何代码,并且忽略了我通常可能编写的许多其他测试,但是我相信您可以决定是否适当地应用它。 I don't want to make this answer any longer by ranting about tests but if the content above is unfamiliar I like Jared's description of outside in testing Rails development . 我不想再通过测试测试来回答这个问题,但是如果上面的内容不熟悉,我会喜欢Jared 在测试Rails开发中外部的描述。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM