Skip to content

Commit

Permalink
Add Factory.build and Factory.create (#92)
Browse files Browse the repository at this point in the history
Adds two methods to the `Factories` instances:

* `Factory.create` as an alias of `Factory.[]`
* `Factory.build` as a delgator to `Factory.structs#[]`

These are more commonly expected methods for interacting with a Factory, 
and are slightly more expressive when encountering thier usage.

ref: https://discourse.hanamirb.org/t/hanami-hack-day-at-rubyconf-2024/1051

This also closes a connection leak when running the test suite.
  • Loading branch information
cflipse authored Nov 14, 2024
1 parent d707579 commit 07b5bcc
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 31 deletions.
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ gem "faker", "~> 3.0"
gem "rspec", "~> 3.0"

git "https://github.com/rom-rb/rom.git", branch: "release-5.3" do
gem "rom-core"
gem "rom"
gem "rom-changeset"
gem "rom-core"
gem "rom-repository"
gem "rom"
end

group :test do
Expand Down
10 changes: 7 additions & 3 deletions docsite/source/index.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,15 @@ end

```ruby
# Create in-memory object
Factory.structs[:user]
Factory.build(:user)

# Persist struct in database
Factory[:user]
Factory.create(:user)

# Override attributes
Factory[:user, age: 24]
Factory.create(:user, age: 24)

# Build and Create via #[] accessors
Factory.structs[:user]
Factory[:user]
```
20 changes: 20 additions & 0 deletions lib/rom/factory/factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ def define(spec, opts = EMPTY_HASH, &block)
def [](name, *traits, **attrs)
registry[name].struct_namespace(struct_namespace).persistable.create(*traits, **attrs)
end
alias_method :create, :[]

# Return in-memory struct builder
#
Expand All @@ -180,6 +181,25 @@ def structs
@__structs__ ||= Structs.new(registry, struct_namespace)
end

# Return a new, non-persisted struct
#
# @example create a struct with default attributes
# MyFactory.build(:user)
#
# @example create a struct with some attributes overridden
# MyFactory.build(:uesr, name: "Jane")
#
# @param [Symbol] name The name of the registered factory
# @param [Array<Symbol>] traits List of traits to apply
# @param [Hash] attrs optional attributes to override the defaults
#
# @return [ROM::Struct]
#
# @api public
def build(name, *traits, **attrs)
structs[name, *traits, **attrs]
end

# Get factories with a custom struct namespace
#
# @example
Expand Down
89 changes: 63 additions & 26 deletions spec/integration/rom/factory_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
end
end

describe ".structs" do
shared_examples_for "it builds a plain struct" do
it "returns a plain struct builder" do
factories.define(:user) do |f|
f.first_name "Jane"
Expand All @@ -30,8 +30,8 @@
f.timestamps
end

user1 = factories.structs[:user]
user2 = factories.structs[:user]
user1 = build(:user)
user2 = build(:user)

expect(user1.id).to_not be(nil)
expect(user1.first_name).to eql("Jane")
Expand Down Expand Up @@ -63,7 +63,7 @@
end

it "works when building parent" do
user_with_tasks = factories.structs[:user]
user_with_tasks = build(:user)

expect(user_with_tasks.tasks.length).to eql(2)
expect(relations[:tasks].count).to be_zero
Expand All @@ -73,27 +73,27 @@
end

it "sets a value explicitly" do
user = factories.structs[:user, tasks: []]
user = build(:user, tasks: [])

expect(user.tasks).to be_empty
end

it "sets a value explicitly when persisting" do
user = factories[:user, tasks: []]
user = build(:user, tasks: [])

expect(user.tasks).to be_empty
end

it "does not create records when building child" do
factories.structs[:task]
build(:task)

expect(relations[:tasks].count).to be_zero
expect(relations[:users].count).to be_zero
end

it "does not pass provided attributes into associations" do
expect {
factories.structs[:user, email: "[email protected]"]
build(:user, email: "[email protected]")
}.not_to raise_error
end
end
Expand All @@ -117,14 +117,14 @@
end

it "creates a struct with associated parent" do
task = factories.structs[:task, title: "Bar"]
task = build(:task, title: "Bar")

expect(task.title).to eql("Bar")
expect(task.user.first_name).to eql("Jane")
end

it "does not build associated struct if it's set to nil explicitly" do
task = factories.structs[:task, user: nil]
task = build(:task, user: nil)

expect(task.user).to be(nil)
end
Expand Down Expand Up @@ -160,14 +160,14 @@
end

it "creates a struct with associated parent" do
task = factories.structs[:task, title: "Bar"]
task = build(:task, title: "Bar")

expect(task.title).to eql("Bar")
expect(task.author.first_name).to eql("Jane")
end

it "does not build associated struct if it's set to nil explicitly" do
task = factories.structs[:task, author: nil]
task = build(:task, author: nil)

expect(task.author).to be_nil
end
Expand Down Expand Up @@ -241,14 +241,14 @@

context "when building a struct" do
it "persists the relation properly with pre-existing assoc record" do
address = factories.structs[:address]
user = factories.structs[:user, address: address]
address = build(:address)
user = build(:user, address: address)

expect(user.address).to have_attributes(full_address: "123 Elm St.")
end

it "persists the relation properly without pre-existing assoc record" do
user = factories.structs[:user]
user = build(:user)

expect(user.address).to have_attributes(full_address: "123 Elm St.")
end
Expand Down Expand Up @@ -301,23 +301,23 @@
end

it "works with one to one relationships with parent" do
user = factories.structs[:basic_user]
user = build(:basic_user)

expect(relations[:basic_accounts].count).to be_zero
expect(relations[:basic_users].count).to be_zero
expect(user.basic_account).to have_attributes(basic_user_id: user.id)
end

it "does not persist when building a child struct" do
factories.structs[:basic_account]
build(:basic_account)

expect(relations[:basic_accounts].count).to be_zero
expect(relations[:basic_users].count).to be_zero
end

it "does not pass provided attributes into associations" do
expect {
factories.structs[:basic_account, created_at: Time.now]
build(:basic_account, created_at: Time.now)
}.not_to raise_error
end
end
Expand All @@ -336,7 +336,7 @@
end

it "still allows building the parent struct" do
basic_user = factories.structs[:basic_user]
basic_user = build(:basic_user)

expect(basic_user.basic_account).to respond_to(:id)
end
Expand All @@ -363,7 +363,7 @@
end

it "does not build the related record" do
user = factories.structs[:basic_user]
user = build(:basic_user)

expect(user.basic_account).to be_nil
end
Expand All @@ -388,6 +388,22 @@
end
end

describe ".structs" do
it_behaves_like "it builds a plain struct"

def build(factory, *traits, **attrs)
factories.structs[factory, *traits, **attrs]
end
end

describe ".build" do
it_behaves_like "it builds a plain struct"

def build(factory, *traits, **attrs)
factories.build(factory, *traits, **attrs)
end
end

describe "factories builder DSL" do
it "infers relation from the name" do
factories.define(:user) do |f|
Expand All @@ -412,7 +428,7 @@
end
end

context "creation of records" do
shared_examples_for "it creates records" do
it "creates a record based on defined factories" do
factories.define(:user, relation: :users) do |f|
f.first_name "Janis"
Expand All @@ -422,8 +438,6 @@
f.updated_at Time.now
end

user = factories[:user]

expect(user.email).not_to be_empty
expect(user.first_name).not_to be_empty
expect(user.last_name).not_to be_empty
Expand All @@ -438,8 +452,6 @@
f.updated_at { Time.now }
end

user = factories[:user]

expect(user.email).not_to be_empty
expect(user.first_name).not_to be_empty
expect(user.last_name).not_to be_empty
Expand All @@ -456,11 +468,22 @@
f.updated_at { Time.now }
end

user = factories[:user]
expect(user.email).to match(/\d{1,3}/)
end
end

context "creation of records via .[]" do
let(:user) { factories[:user] }

it_should_behave_like "it creates records"
end

context "creation of records via .create" do
let(:user) { factories.create(:user) }

it_should_behave_like "it creates records"
end

context "changing values" do
it "supports overwriting of values" do
factories.define(:user, relation: :users) do |f|
Expand All @@ -475,6 +498,20 @@

expect(user.email).to eq("[email protected]")
end

it "supports overwriting create values" do
factories.define(:user, relation: :users) do |f|
f.first_name "Janis"
f.last_name "Miezitis"
f.email "[email protected]"
f.created_at Time.now
f.updated_at Time.now
end

user = factories.create(:user, email: "[email protected]")

expect(user.email).to eq("[email protected]")
end
end

context "dependant attributes" do
Expand Down
4 changes: 4 additions & 0 deletions spec/shared/database.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
conf.gateways[:default].connection
end

after do
conn.disconnect
end

let(:relations) do
rom.relations
end
Expand Down

0 comments on commit 07b5bcc

Please sign in to comment.