2009-11-28 3 views
0

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

У меня есть база данных 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. Позже я могу использовать идентификатор адреса, чтобы легко заполнить адрес доставки для «заказа».

Теперь я хочу смоделировать это с помощью активных записей с рельсами. До сих пор у меня есть следующие:

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

<% 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', '[email protected]', '') 

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

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

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

ответ

0

Round 2:

Хорошо, так что я надеюсь, что это теперь поможет вам. То, как вы реализуете двойной адрес в одной таблице, не является способом «рельсов». Всегда идет, что если вы хотите что-то сделать, вам нужно сделать это, как DHH. Таким образом, рельсы имеют 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: Обновите контроллеры. Теперь единственным вашим контроллером, который вы указали в своем вопросе, был client_controller.rb, но, fyi, это может применяться в любом месте. Вы хотите заменить 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 

Надеется, что это на самом деле работает, и это делает для меня ужасна попыткой раньше;)

+0

Если ОП редактировал свой вопрос, он уже имеет accepts_nested_atrributes_for указан в своем классе Customer. –

+0

Исправить. Это уже задано в объекте Customer. – S73417H

+0

aha, моя ошибка, я должен, вероятно, лучше прочитать вопрос в следующий раз ... Я только что обновил свой ответ и, надеюсь, это имеет немного больше смысла. – vrish88

Смежные вопросы