Skip to content

Commit

Permalink
add tests for readme example
Browse files Browse the repository at this point in the history
  • Loading branch information
ezekg committed Aug 12, 2024
1 parent 9dc9290 commit 1879336
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 7 deletions.
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,31 +64,30 @@ class Book < ActiveRecord::Base

# foreword writers for the book via a join table
has_many :forewords
has_many :foreword_writers, through: :forewords, source: :user
has_many :prefacers, through: :forewords, source: :user

# union association for all contributors to the book
has_many :contributors, union_of: %i[
has_many :contributors, class_name: 'User', union_of: %i[
author
coauthors
editors
illustrators
foreword_writers
prefacers
]
end

```

Here's a quick example of what's possible:

```ruby
# contributors to the book
primary_author = User.create(name: 'Isaac Asimov')
author = User.create(name: 'Isaac Asimov')
editor = User.create(name: 'John W. Campbell')
illustrator = User.create(name: 'Frank Kelly Freas')
writer = User.create(name: 'Ray Bradbury')

# create book by the author
book = Book.create(title: 'I, Robot', primary_author:)
book = Book.create(title: 'I, Robot', author:)

# assign an editor
Edit.create(user: editor, book:)
Expand All @@ -99,7 +98,7 @@ Illustration.create(user: illustrator, book:)
# assign a foreword writer
Foreword.create(user: writer, book:)

# access all contributors (primary author, editor, illustrator, foreword writer)
# access all contributors (author, editor, illustrator, etc.)
book.contributors.to_a
# => [#<User id=1, name="Isaac Asimov">, #<User id=2, name="John W. Campbell">, #<User id=3, name="Frank Kelly Freas">, #<User id=4, name="Ray Bradbury">]

Expand Down
194 changes: 194 additions & 0 deletions spec/union_of_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1309,4 +1309,198 @@ def owned = where(owner: proxy_association.owner)

# TODO(ezekg) Add exhaustive tests for all association macros, e.g.
# belongs_to, has_many, etc.

describe 'README' do
temporary_table :users, force: true do |t|
t.string :name
end

temporary_table :books do |t|
t.integer :author_id
t.string :title
end

temporary_table :coauthorships do |t|
t.integer :book_id
t.integer :user_id
end

temporary_table :edits do |t|
t.integer :book_id
t.integer :user_id
end

temporary_table :illustrations do |t|
t.integer :book_id
t.integer :user_id
end

temporary_table :forewords do |t|
t.integer :book_id
t.integer :user_id
end

temporary_model :user do
has_many :books
has_many :coauthorships
has_many :edits
has_many :illustrations
has_many :forewords
end

temporary_model :coauthorship do
belongs_to :book
belongs_to :user
end

temporary_model :edit do
belongs_to :book
belongs_to :user
end

temporary_model :illustration do
belongs_to :book
belongs_to :user
end

temporary_model :foreword do
belongs_to :book
belongs_to :user
end

temporary_model :book do
# the primary author of the book
belongs_to :author, class_name: 'User', foreign_key: 'author_id', optional: true

# coauthors of the book via a join table
has_many :coauthorships
has_many :coauthors, through: :coauthorships, source: :user

# editors for the book via a join table
has_many :edits
has_many :editors, through: :edits, source: :user

# illustrators for the book via a join table
has_many :illustrations
has_many :illustrators, through: :illustrations, source: :user

# foreword writers for the book via a join table
has_many :forewords
has_many :prefacers, through: :forewords, source: :user

# union association for all contributors to the book
has_many :contributors, class_name: 'User', union_of: %i[
author
coauthors
editors
illustrators
prefacers
]
end

let(:author) { User.create(name: 'Isaac Asimov') }
let(:editor) { User.create(name: 'John W. Campbell') }
let(:illustrator) { User.create(name: 'Frank Kelly Freas') }
let(:writer) { User.create(name: 'Ray Bradbury') }
let(:book) {
book = Book.create(title: 'I, Robot', author:)

Edit.create(user: editor, book:)
Illustration.create(user: illustrator, book:)
Foreword.create(user: writer, book:)

book
}

it 'should return contributors' do
expect(book.contributors).to satisfy { _1.to_a in [author, editor, illustrator, writer] }
end

it 'should use limit' do
expect(book.contributors.order(:name).limit(3)).to satisfy { _1.to_a in [author, editor, illustrator] }
end

it 'should use predicate' do
expect(book.contributors.where(id: editor.id)).to satisfy { _1.to_a in [editor] }
end

it 'should use UNION' do
expect(book.contributors.to_sql).to match_sql <<~SQL.squish
SELECT
users.*
FROM
users
WHERE
users.id IN (
SELECT
users.id
FROM
(
(
SELECT
users.id
FROM
users
WHERE
users.id = 1
LIMIT
1
)
UNION
(
SELECT
users.id
FROM
users INNER JOIN coauthorships ON users.id = coauthorships.user_id
WHERE
coauthorships.book_id = 1
)
UNION
(
SELECT
users.id
FROM
users INNER JOIN edits ON users.id = edits.user_id
WHERE
edits.book_id = 1
)
UNION
(
SELECT
users.id
FROM
users INNER JOIN illustrations ON users.id = illustrations.user_id
WHERE
illustrations.book_id = 1
)
UNION
(
SELECT
users.id
FROM
users INNER JOIN forewords ON users.id = forewords.user_id
WHERE
forewords.book_id = 1
)
) users
)
SQL
end

it 'should support joins' do
expect { Book.joins(:contributors).where(contributors: { user_id: author.id }) }.to_not raise_error
end

it 'should support preloading' do
expect { Book.preload(:contributors) }.to_not raise_error
end

it 'should support eager loading' do
expect { Book.eager_load(:contributors) }.to_not raise_error
end

it 'should support includes' do
expect { Book.includes(:contributors) }.to_not raise_error
end
end
end

0 comments on commit 1879336

Please sign in to comment.