Вложенные записи Ruby Rails - PullRequest
       5

Вложенные записи Ruby Rails

0 голосов
/ 28 ноября 2009

Я не уверен, что то, что я даже пытаюсь сделать, возможно, но здесь.

У меня есть база данных 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));

Как видите, в таблице «клиенты» определяется отношение «один к-* 1006» * два с адресами (один для платежного адреса и один для почтового / почтового адреса). Идея здесь состоит из двух частей:

  1. Сохраняет дублирующиеся поля адреса в таблице клиентов, используя связи с таблицей адресов.
  2. Позже я могу использовать идентификатор адреса, чтобы легко заполнить адрес доставки для «заказа».

Теперь я хочу смоделировать это, используя Active Records с рельсами. Пока у меня есть следующее:

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) Контроллер клиента (показаны только соответствующие методы, т.е. 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) Моя вложенная форма для создания нового клиента с адресом выставления счета .

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

Исходя из этого, я понимаю, что адрес для выставления счета не создается перед клиентом. Я думал (возможно, очень наивно), что активная запись распознает отношения между клиентом и адресной записью и выполнит правильную операцию для создания новых записей. Это явно не тот случай.

Как я могу сделать это так? Я предполагаю, что логика должна быть в контроллере клиента, чтобы сначала сохранить запись адреса, а затем получить идентификатор этой записи для использования в контроллере клиента. Все в рамках транзакции? Или, может быть, я просто плохо смоделировал свою БД?

Надеюсь, что весь этот код был не слишком большим, но я хотел дать как можно больше контекста.

1 Ответ

0 голосов
/ 28 ноября 2009

Раунд 2:

Хорошо, я надеюсь, это поможет вам. То, как вы реализуете двойной адрес в одной таблице, не совсем "рельсовый" способ. Всегда бывает так, что если вы хотите сделать что-то, вы должны сделать это, как DHH. Таким образом, у rails есть STI ( Single Table Inheritance ), где вы можете иметь один суперкласс с множеством классов, наследуемых от него.

В вашем случае, это не должно быть слишком много работы (я надеюсь), чтобы переместить эту парадигму.

Шаг 1: вырезать отверстие в коробке

Шаг 2. Обновите файлы миграции. Вы хотите, чтобы в таблице адресов был ключ к ней соответствующего клиента. Затем удалите столбцы billing_address_id и shipping_address_id в таблице Customer, поскольку они нам больше не нужны.

Вы также хотите добавить поле с именем 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

Надеюсь, это действительно сработает, и это восполнит мою безумную попытку ранее;)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...