简体   繁体   English

Ruby Rails嵌套记录

[英]Ruby Rails Nested Records

I'm not sure if what I'm even trying to do is possible but here goes. 我不确定我什至试图做的事是否可行,但这是可行的。

I have an SQL database with the following tables defined (showing only relevant tables in SQL): 我有一个定义了以下表的SQL数据库(仅显示SQL中的相关表):

CREATE TABLE customers(
    id integer NOT NULL UNIQUE,
    name vachar(25) NOT NULL,
    surname vachar(25) NOT NULL,
    password vachar(20) NOT NULL,
    email_address vachar(1024) NOT NULL,
    home_phone vachar(15),
    mobile_phone vachar(15),
    office_phone vachar(15),
    billing_address_id integer NOT NULL,
    postal_address_id integer,
    FOREIGN KEY (billing_address_id) REFERENCES addresses(id),
    FOREIGN KEY (postal_address_id) REFERENCES addresses(id),
    PRIMARY KEY (id));

CREATE TABLE addresses(
    id integer NOT NULL UNIQUE,
    line1 vachar(100) NOT NULL,
    line2 vachar(100),
    state vachar(30) NOT NULL,
    postcode vachar(10) NOT NULL,
    country_id vachar(3) NOT NULL,
    PRIMARY KEY (id));

CREATE TABLE orders(
    id integer NOT NULL UNIQUE,
    customer_id integer NOT NULL UNIQUE,
    order_date date NOT NULL,
    postal_address_id integer NOT NULL UNIQUE,
    FOREIGN KEY (customer_id) REFERENCES customers(id),
    PRIMARY KEY (id));

As you can see, the "customers" table defines a one-to- two relationship with addresses (one for billing address and one for postal/shipping address). 正如你所看到的,“客户”表中定义了地址(一个计费地址和一个邮政/送货地址)一个一对一2间的关系。 The idea here being two fold: 这里的想法有两个方面:

  1. Saves duplicating address fields in the customers table by using relationships to address table. 通过使用与地址表的关系,将重复的地址字段保存在客户表中。
  2. Later I can use the address ID to easily fill out the shipping address for an "order". 稍后,我可以使用地址ID轻松填写“订单”的送货地址。

Now I want to model this using Active Records with rails. 现在,我想使用带轨道的Active Records对此建模。 So far I have the following: 到目前为止,我有以下内容:

1) The "Customer" model: 1)“客户”模型:

class Customer < ActiveRecord::Base
    has_one :postal_address, :class_name => 'Address', :foreign_key => :postal_address_id
    has_one :billing_address, :class_name => 'Address', :foreign_key => :billing_address_id
    accepts_nested_attributes_for :postal_address, :billing_address, :allow_destroy => true
end

2) The address model (default): 2)地址模型(默认):

class Address < ActiveRecord::Base
end

3) The customer controller (only relevant methods shown, ie new & create): 3)客户控制者(仅显示相关方法,即new和create):

class CustomersController < ApplicationController

  # GET /customers/new
  # GET /customers/new.xml
  def new
    @customer = Customer.new
    @customer.postal_address = Address.new
    @customer.billing_address = Address.new

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @customer }
    end
  end

  # POST /customers
  # POST /customers.xml
  def create
    @customer = Customer.new(params[:customer])

    respond_to do |format|
      if @customer.save
        flash[:notice] = 'Customer was successfully created.'
        format.html { redirect_to(@customer) }
        format.xml  { render :xml => @customer, :status => :created, :location => @customer }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @customer.errors, :status => :unprocessable_entity }
      end
    end
  end

end

3) My nested form for creating a new customer with billing address as well . 3)我的嵌套表格也用于创建具有帐单地址的新客户。

<% form_for(@customer) do |f| %>
  <%= f.error_messages %>

  <%= f.label :name, 'Name:' %>
  <%= f.text_field :name %>

  <%= f.label :surname, 'Surname:' %>
  <%= f.text_field :surname %>

  <br>

  <%= f.label :email_address, 'Email:' %>
  <%= f.text_field :email_address %>

  <%= f.label :confirm_email_address, 'Confirm Email:' %>
  <input id="confirm_email_address" type="text" />

  <br>

  <%= f.label :password, 'Password:' %>
  <%= f.text_field :password %>
  <%= f.label :confirm_password, 'Confirm Password:' %>
  <input id="confirm_password" type="password" %>

  <br>

  <%= f.label :home_phone, 'Home Phone:' %> 
  <%= f.text_field :home_phone %>

  <%= f.label :mobile_phone, 'Mobile Phone:' %>
  <%= f.text_field :mobile_phone %>

  <%= f.label :office_phone, 'Office Phone:' %>
  <%= f.text_field :office_phone %>

  <br>

  <% f.fields_for :billing_address do |billing_form| %>

    <%= billing_form.label :line1, 'Billing Address:' %>
    <%= billing_form.text_field :line1 %>

    <br>

    <%= billing_form.text_field :line2 %>

    <br>

    <%= billing_form.label :state, 'State / Province / Region:' %>
    <%= billing_form.text_field :state %>

    <br>

    <%= billing_form.label :postcode, 'Postcode / ZIP:' %>
    <%= billing_form.text_field :postcode %>

    <br>

    <%= billing_form.label :country_id, 'Country:' %>
    <%= billing_form.text_field :country_id %>

  <% end %>

  <p>
    <%= f.submit 'Create' %>
  </p>
<% end %>

Now to the problem. 现在解决问题。 When I fill out this form and proceed to creating the new record I get the following error: 当我填写此表格并继续创建新记录时,出现以下错误:

SQLite3::SQLException: customers.billing_address_id may not be NULL: INSERT INTO "customers" ("name", "office_phone", "billing_address_id", "postal_address_id", "home_phone", "surname", "password", "email_address", "mobile_phone") VALUES('Michael', '', NULL, NULL, '93062145', 'Fazio', '9npn4zicr', 'michael.fazio@me.com', '')

From this I understand that the billing address is not being created before the customer. 由此可知,帐单地址不是在客户之前创建的。 I thought (probably very naively) that active record would recognize the relationship between a customer and address record and do the correct operation to create the new records. 我认为(可能很天真),该活动记录将识别客户和地址记录之间的关系,并做了正确的操作来创建新的记录。 This is obviously not the case. 显然不是这样。

How can I make this so? 我怎样才能让这样呢? I'm assuming logic needs to be in the customer controller to save the address record first then get the ID for that record to use in the customer controller. 我假设逻辑需要放在客户控制器中,以便首先保存地址记录,然后获取该记录的ID,以便在客户控制器中使用。 All within a transaction? 全部都在交易中吗? Or maybe I have just modeled my DB in a bad way? 还是我刚刚以一种不好的方式对数据库建模?

Hope that all this code was not too much but I wanted to give as much context as possible. 希望所有这些代码不要太多,但是我想提供尽可能多的上下文。

Round 2: 第二回合:

Ok, so I hope this will now help you. 好的,希望现在对您有所帮助。 The way that you are implementing the dual address in one table isn't exactly the "rails" way. 在一个表中实现双重地址的方式并不完全是“轨道”方式。 It always goes that if you want to do something you've got to do it like DHH. 总的来说,如果您想做某些事情,例如DHH,就必须要做。 So rails has STI ( Single Table Inheritance ) where you can have one super class with many classes inheriting from that. 因此,rails具有STI( 单表继承 ),您可以在其中拥有一个超类,并从中继承许多类。

In your case, it shouldn't be too much work (I hope) to move this paradigm. 在您的情况下,移动这种范例不应做太多工作(我希望)。

Step 1: Cut a hole in a box 步骤1:在盒子上切一个洞

Step 2: Update your migration files. 步骤2:更新您的迁移文件。 You want the addresses table to have the key to it corresponding Customer. 您希望地址表具有对应于客户的键。 Then take out the billing_address_id and shipping_address_id columns in the Customer table because we don't need these anymore. 然后删除“客户”表中的billing_address_id和shipping_address_id列,因为我们不再需要这些列。

You also want to add a field named type (if type is already taken there is a work around). 您还想添加一个名为type的字段(如果已经使用type,则可以解决)。 Something like this: 像这样:

create_table :addresses do |t|
  t.string :line1
  t.string :line2
  t.string :state
  t.integer :postcode
  t.integer :country_id
  t.integer :customer_id
  t.integer :type

  t.timestamps

Step 3: Update your models. 步骤3:更新模型。 Change your customer class to look like so: 更改您的客户类别,如下所示:

class Customer < ActiveRecord::Base
  has_one :postal_address
  has_one :billing_address
  accepts_nested_attributes_for :postal_address, :billing_address, :allow_destroy => true

Then you'll want to create two new files in the models directory: billing_address.rb and postal_address.rb. 然后,您将要在models目录中创建两个新文件:billing_address.rb和postal_address.rb。 They should look like this: 它们应如下所示:

class BillingAddress < Address
  belongs_to :customer
end

class PostalAddress < Address
  belongs_to :customer
end

Step 4: Update Controllers. 步骤4:更新控制器。 Now the only controller you showed in your question was customer_controller.rb but, fyi, this can apply for really anywhere. 现在,您在问题中显示的唯一控制器是customer_controller.rb,但是,可以在任何地方使用。 You want to replace Address.new with a call to instantiate either Shipping or Billing Addresses. 您要用实例化“送货地址”或“帐单地址”的调用替换Address.new

def new
  @customer = Customer.new
  @customer.postal_address = PostalAddress.new
  @customer.billing_address = BillingAddress.new

  respond_to do |format|
    format.html # new.html.erb
    format.xml  { render :xml => @customer }
  end
end

Hopefully this actually works and it makes up for my abysmal attempt earlier ;) 希望这确实有效,并且可以弥补我之前的糟糕尝试;)

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

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