Как моделировать людей и компании с наследованием одной таблицы в Rails?

В моем приложении для выставления счетов invoices можно отправить либо company, либо person. Насколько я понимаю, это хороший пример использования наследования одной таблицы Rails (STI). Поскольку оба типа имеют ряд общих атрибутов и функций, я решил, что суперкласс Recipient может быть хорошим вариантом:

class Recipient < ActiveRecord::Base 
end

class Company < Recipient
  has_many :people
end

class Person < Recipient
  belongs_to :company
end

Я также понимаю, что мне нужен атрибут type в модели Recipient.

Единственное, что меня беспокоит, это то, что person может (а может и не принадлежать) company. Как это можно смоделировать в Rails? Обычно я просто добавляю еще одно поле базы данных company_id в таблицу people. Но здесь только одна таблица (recipients). Итак, как это можно сделать?

Спасибо за любую помощь.


person Tintin81    schedule 04.08.2017    source источник
comment
Также стоит упомянуть заботы о том, чтобы полностью разделить модели Company и Person, и включить заботу, называемую (например) Billable или Invoicable, или что-то в этом роде, для сохранения логики счетов. Однако STI также является отличным вариантом использования здесь. Позвольте мне через секунду написать ответ для STI-решения.   -  person Frederik Spang    schedule 04.08.2017


Ответы (2)


Структура может выглядеть так:

class Recipient < ActiveRecord::Base 
  has_many :invoices
end

class Company < Recipient
  has_many :people
end

class Person < Recipient
  belongs_to :company
end

class Invoice < ActiveRecord::Base
  belongs_to :recipients
end

# Schema
create_table "invoices", force: :cascade do |t|
  t.integer "recipient_id"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["recipient_id"], name: "index_invoices_on_recipient_id"
end

create_table "recipients", force: :cascade do |t|
  t.integer "company_id"
  t.string "type"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

Я только что попробовал в консоли:

> Recipient.all
 => [#<Company:0x007fd55d797220
  id: 1,
  company_id: nil,
  type: "Company",
  created_at: Fri, 04 Aug 2017 10:57:41 UTC +00:00,
  updated_at: Fri, 04 Aug 2017 10:57:41 UTC +00:00>,
 #<Person:0x007fd55d796730
  id: 2,
  company_id: 1,
  type: "Person",
  created_at: Fri, 04 Aug 2017 10:57:41 UTC +00:00,
  updated_at: Fri, 04 Aug 2017 10:57:41 UTC +00:00>,
 #<Person:0x007fd55d796208
  id: 3,
  company_id: nil,
  type: "Person",
  created_at: Fri, 04 Aug 2017 10:57:41 UTC +00:00,
  updated_at: Fri, 04 Aug 2017 10:57:41 UTC +00:00>]

> Person.last.company
  Person Load (0.2ms)  SELECT  "recipients".* FROM "recipients" WHERE "recipients"."type" IN ('Person') ORDER BY "recipients"."id" DESC LIMIT ?  [["LIMIT", 1]]
 => nil

> Person.first.company
  Person Load (0.2ms)  SELECT  "recipients".* FROM "recipients" WHERE "recipients"."type" IN ('Person') ORDER BY "recipients"."id" ASC LIMIT ?  [["LIMIT", 1]]
  Company Load (0.2ms)  SELECT  "recipients".* FROM "recipients" WHERE "recipients"."type" IN ('Company') AND "recipients"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 => #<Company id: 1, company_id: nil, type: "Company", created_at: "2017-08-04 10:57:41", updated_at: "2017-08-04 10:57:41">
person Frederik Spang    schedule 04.08.2017
comment
Примерно это я и имел в виду. Спасибо за разъяснение этого. - person Tintin81; 04.08.2017
comment
@ Tintin81 Отлично! Что-нибудь еще вам нужно? Или вы могли бы принять ответ? - person Frederik Spang; 04.08.2017

class Recipient < ActiveRecord::Base 
end

class Company < Recipient
  has_many :people, class_name: "Recipient", foreign_key: 'parent_id' 
end

class Person < Recipient
  belongs_to :company, class_name: "Recipient", foreign_key: 'parent_id'
end

Просто добавьте parent_id к миграции получателя. И то это просто и быстро и вы получаете то что хотите одну модель две STI и has_many и belongs_to между company и person.

person Sedad Kosovac    schedule 04.08.2017
comment
Большое спасибо. Является ли class_name: "Recipient" строго необходимым, чтобы заставить это работать? - person Tintin81; 04.08.2017
comment
@ Tintin81 нет, но мне нравится использовать больше информации лучше :) - person Sedad Kosovac; 04.08.2017
comment
Спасибо, я взял на себя смелость отметить ответ Фредерика как правильный, потому что он вдавался в подробности, но я мог бы также отметить ваш. Вы оба очень помогли мне здесь. - person Tintin81; 04.08.2017