From d391321938de8b52a880594f72d3947f6a948a74 Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Fri, 1 Sep 2023 11:22:40 -0400 Subject: [PATCH] Document integration with 2.7 and 3.0 syntactic sugars Ruby 2.7 introduced [numbered block parameters][], and 3.0 introduced [Hash literal value omission][], so document how they integrate with Factory Bot. [numbered block parameters]: https://ruby-doc.org/core-2.7.1/Proc.html#class-Proc-label-Numbered+parameters [Hash literal value omission]: https://docs.ruby-lang.org/en/3.1/syntax/literals_rdoc.html#label-Hash+Literals --- GETTING_STARTED.md | 45 +++++++++++++++++-- .../attributes_for_destructuring.rb | 22 +++++++++ spec/acceptance/attributes_for_spec.rb | 4 ++ 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 spec/acceptance/attributes_for_destructuring.rb diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 984b4564..2ddc66cb 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -285,6 +285,9 @@ user = create(:user) # Returns a hash of attributes that can be used to build a User instance attrs = attributes_for(:user) +# Integrates with Ruby 3.0's support for pattern matching assignment +attributes_for(:user) => {email:, name:, **attrs} + # Returns an object with all defined attributes stubbed out stub = build_stubbed(:user) @@ -297,7 +300,7 @@ end ### Attribute overrides No matter which strategy is used, it's possible to override the defined -attributes by passing a hash: +attributes by passing a Hash: ```ruby # Build a User instance and override the first_name property @@ -306,6 +309,29 @@ user.first_name # => "Joe" ``` +Overriding associations is also supported: + +```ruby +account = build(:account, :deluxe) +friends = build_list(:user, 2) + +user = build(:user, account: account, friends: friends) +``` + +Ruby 3.1's support for [omitting values][] from `Hash` literals dovetails +attribute overrides and provides an opportunity to limit the repetition of +variable names: + +```ruby +account = build(:account, :deluxe) +friends = build_list(:user, 2) + +# The keyword arguments correspond to local variable names, so omit their values +user = build(:user, account:, friends:) +``` + +[omitting values]: https://docs.ruby-lang.org/en/3.1/syntax/literals_rdoc.html#label-Hash+Literals + ### `build_stubbed` and `Marshal.dump` Note that objects created with `build_stubbed` cannot be serialized with @@ -945,7 +971,7 @@ end Note that this approach works with `build`, `build_stubbed`, and `create`, but the associations will return `nil` when using `attributes_for`. -Also, note that if you assign any attributes inside a custom `initialize_with` +Also, note that if you assign any attributes inside a custom `initialize_with` (e.g. `initialize_with { new(**attributes) }`), those attributes should not refer to `instance`, since it will be `nil`. @@ -1008,6 +1034,17 @@ factory :user do end ``` +With Ruby 2.7's support for [numbered parameters][], inline definitions can be +even more abbreviated: + +```ruby +factory :user do + sequence(:email) { "person#{_1}@example.com" } +end +``` + +[numbered parameters]: https://ruby-doc.org/core-2.7.1/Proc.html#class-Proc-label-Numbered+parameters + ### Initial value You can override the initial value. Any value that responds to the `#next` @@ -1222,11 +1259,11 @@ FactoryBot.define do created_at { 8.days.ago } updated_at { 4.days.ago } end - + factory :user, traits: [:timestamps] do username { "john_doe" } end - + factory :post do timestamps title { "Traits rock" } diff --git a/spec/acceptance/attributes_for_destructuring.rb b/spec/acceptance/attributes_for_destructuring.rb new file mode 100644 index 00000000..2eb2b962 --- /dev/null +++ b/spec/acceptance/attributes_for_destructuring.rb @@ -0,0 +1,22 @@ +describe "Ruby 3.0: attributes_for destructuring syntax" do + include FactoryBot::Syntax::Methods + + before do + define_model("User", name: :string) + + FactoryBot.define do + factory :user do + sequence(:email) { "email_#{_1}@example.com" } + name { "John Doe" } + end + end + end + + it "supports being destructured" do + attributes_for(:user) => {name:, **attributes} + + expect(name).to eq("John Doe") + expect(attributes.keys).to eq([:email]) + expect(attributes.fetch(:email)).to match(/email_\d+@example.com/) + end +end diff --git a/spec/acceptance/attributes_for_spec.rb b/spec/acceptance/attributes_for_spec.rb index ba44efc4..b9a87c18 100644 --- a/spec/acceptance/attributes_for_spec.rb +++ b/spec/acceptance/attributes_for_spec.rb @@ -1,3 +1,7 @@ +if RUBY_VERSION >= "3.0" + require_relative "./attributes_for_destructuring" +end + describe "a generated attributes hash" do include FactoryBot::Syntax::Methods