【问题标题】:Ruby Rails Nested RecordsRuby Rails 嵌套记录
【发布时间】:2009-11-28 11:42:26
【问题描述】:

我不确定我什至尝试做的事情是否可行,但这里就是这样。

我有一个定义了以下表的 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));

如您所见,“客户”表定义了与地址的一对两个关系(一个用于帐单地址,一个用于邮政/送货地址)。这里的想法有两个:

  1. 使用与地址表的关系将重复的地址字段保存在客户表中。
  2. 稍后我可以使用地址 ID 轻松填写“订单”的送货地址。

现在我想使用带有 Rails 的 Active Record 对此进行建模。到目前为止,我有以下内容:

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) 地址模型(默认):

class Address < ActiveRecord::Base
end

3) 客户控制器(仅显示相关方法,即新建和创建):

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) 我用于创建新客户的嵌套表单还有帐单地址。

<% 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 %>

现在解决问题。当我填写此表格并继续创建新记录时,我收到以下错误:

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', '')

据此,我了解到帐单地址不是在客户之前创建的。我认为(可能非常天真)活动记录会识别客户和地址记录之间的关系并执行正确的操作来创建新记录。显然不是这样的。

我怎样才能做到这一点?我假设逻辑需要在客户控制器中首先保存地址记录,然后获取该记录的 ID 以在客户控制器中使用。全部在交易中?或者也许我只是以一种糟糕的方式建模了我的数据库?

希望所有这些代码不要太多,但我想提供尽可能多的上下文。

【问题讨论】:

    标签: sql ruby-on-rails database nested-forms


    【解决方案1】:

    第二轮:

    好的,我希望现在这对您有所帮助。您在一张表中实现双地址的方式并不完全是“rails”方式。总是这样,如果你想做某事,你必须像 DHH 那样去做。因此,rails 有 STI (Single Table Inheritance),您可以在其中拥有一个超类,其中有许多类继承自该超类。

    在您的情况下,(我希望)移动这个范式不应该做太多工作。

    第 1 步:在盒子上挖一个洞

    第 2 步:更新您的迁移文件。您希望地址表具有对应客户的键。然后取出 Customer 表中的 billing_address_id 和 shipping_address_id 列,因为我们不再需要它们了。

    您还想添加一个名为type 的字段(如果类型已被采用,则可以解决此问题)。像这样的:

    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
    

    第 3 步:更新模型。将您的客户类别更改为如下所示:

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

    然后您需要在模型目录中创建两个新文件:billing_address.rb 和 postal_address.rb。它们应该如下所示:

    class BillingAddress < Address
      belongs_to :customer
    end
    
    class PostalAddress < Address
      belongs_to :customer
    end
    

    第 4 步:更新控制器。现在,您在问题中显示的唯一控制器是 customer_controller.rb 但是,仅供参考,这实际上可以适用于任何地方。您想将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
    

    希望这确实有效,它弥补了我之前糟糕的尝试;)

    【讨论】:

    • 除非 OP 编辑​​了他的问题,否则他已经在其 Customer 类中指定了accepts_nested_atrributes_for。
    • 啊哈,我的错误,我下次可能应该更彻底地阅读这个问题......我刚刚更新了我的答案,希望这更有意义。
    • 感谢 vrish 的深入回复。这是我怀疑需要做的事情。反转关系,以便地址表引用客户。不过,我希望有一种优雅的方式来解决这个问题,因为我想在与订单表的关系中使用相同的地址表(即客户有两个对地址 [billing and postal] 的引用,而一个订单对一个地址 [postal]。这对我来说会很好也很方便,但似乎 Active Records 还没有那么聪明:(除非其他人有更好的答案,否则我会和你一起去 :) 谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多