factory_bot 6.0.2 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53cbce42f9c9fcf243a4081288b73cfce66a6b75a323f6882d6be4b4f60fcd18
4
- data.tar.gz: 68df68b5a4501caa97f8e2538e3d1afb6e8ae619add80a52fff7bef2f8bda24b
3
+ metadata.gz: 3bcbbd8bee01abdb25a5cfdbbbbca29396f587236f0c20f8300adbf6e32f783c
4
+ data.tar.gz: 94d8e73507ae61edba7fb3129ee74c4fe9998a8fb78614ecba9966199561c8e4
5
5
  SHA512:
6
- metadata.gz: '049104e865666570343adea5fd54e3025d5c4b550c7a364b22e4f9af8f24c8055e2fc21a168e752976569617bd0e4d02504904eb7274d7c7819d32936c164610'
7
- data.tar.gz: 2bf0447a30a2e536f9eeaaccc724528c839a576ce0df7490f68ec86e683273a50e58ae5f7171c4e001c323adb8fe793427106dbcdc68987dc6ff28d3a4608d6f
6
+ metadata.gz: e97d9530810f0de00f6e0129df37db52722ee8d430f7b118ffdcebdc0eecc1e1532356b7c8e01cc85265c04f18eb174d3d5ff09fe235c5f30ceb7b5e16432508
7
+ data.tar.gz: 71b77c30b16c89588d6946505be547374321597e8e16f89673766cda94b22188dc28070a34144ae126dad93042de0173e1d92173237ae1e929a3131e85569a51
data/CONTRIBUTING.md CHANGED
@@ -51,6 +51,12 @@ Here are some ways *you* can contribute:
51
51
  asking for help. We love helping!
52
52
  * Please don't update the Gem version.
53
53
 
54
+ ## Setting up
55
+
56
+ ```sh
57
+ bundle install
58
+ ```
59
+
54
60
  ## Running the test suite
55
61
 
56
62
  The default rake task will run the full test suite and [standard]:
data/GETTING_STARTED.md CHANGED
@@ -37,12 +37,15 @@ Getting Started
37
37
  * [Associations](#associations)
38
38
  + [Implicit definition](#implicit-definition)
39
39
  + [Explicit definition](#explicit-definition)
40
+ + [Inline definition](#inline-definition)
40
41
  + [Specifying the factory](#specifying-the-factory)
41
42
  + [Overriding attributes](#overriding-attributes)
43
+ + [Association overrides](#association-overrides)
42
44
  + [Build strategies](#build-strategies-1)
43
45
  + [`has_many` associations](#has_many-associations)
44
46
  + [`has_and_belongs_to_many` associations](#has_and_belongs_to_many-associations)
45
47
  + [Polymorphic associations](#polymorphic-associations)
48
+ + [Interconnected associations](#interconnected-associations)
46
49
  * [Sequences](#sequences)
47
50
  + [Global sequences](#global-sequences)
48
51
  + [With dynamic attributes](#with-dynamic-attributes)
@@ -52,6 +55,7 @@ Getting Started
52
55
  + [Without a block](#without-a-block)
53
56
  + [Aliases](#aliases-1)
54
57
  + [Rewinding](#rewinding)
58
+ + [Uniqueness](#uniqueness)
55
59
  * [Traits](#traits)
56
60
  + [Defining traits](#defining-traits)
57
61
  + [As implicit attributes](#as-implicit-attributes-1)
@@ -196,17 +200,15 @@ It is also possible to explicitly specify the class:
196
200
 
197
201
  ```ruby
198
202
  # This will use the User class (otherwise Admin would have been guessed)
199
- factory :admin, class: User
203
+ factory :admin, class: "User"
200
204
  ```
201
205
 
202
- If the constant is not available
203
- (if you are using a Rails engine that waits to load models, for example),
204
- you can also pass a symbol or string,
205
- which factory\_bot will constantize later, once you start building objects:
206
+ You can pass a constant as well, if the constant is available (note that this
207
+ can cause test performance problems in large Rails applications, since
208
+ referring to the constant will cause it to be eagerly loaded).
206
209
 
207
210
  ```ruby
208
- # It's OK if Doorkeeper::AccessToken isn't loaded yet
209
- factory :access_token, class: "Doorkeeper::AccessToken"
211
+ factory :access_token, class: User
210
212
  ```
211
213
 
212
214
  ### Hash attributes
@@ -311,17 +313,17 @@ factory :user, aliases: [:author, :commenter] do
311
313
  end
312
314
 
313
315
  factory :post do
314
- author
315
- # instead of
316
+ # The alias allows us to write author instead of
316
317
  # association :author, factory: :user
318
+ author
317
319
  title { "How to read a book effectively" }
318
320
  body { "There are five steps involved." }
319
321
  end
320
322
 
321
323
  factory :comment do
322
- commenter
323
- # instead of
324
+ # The alias allows us to write commenter instead of
324
325
  # association :commenter, factory: :user
326
+ commenter
325
327
  body { "Great article!" }
326
328
  end
327
329
  ```
@@ -345,6 +347,7 @@ create(:user, last_name: "Doe").email
345
347
 
346
348
  Transient Attributes
347
349
  --------------------
350
+ Transient attributes are attributes only available within the factory definition, and not set on the object being built. This allows for more complex logic inside factories.
348
351
 
349
352
  ### With other attributes
350
353
 
@@ -504,6 +507,19 @@ factory :post do
504
507
  end
505
508
  ```
506
509
 
510
+ ### Inline definition
511
+
512
+ You can also define associations inline within regular attributes,
513
+ but note that the value will be `nil`
514
+ when using the `attributes_for` strategy.
515
+
516
+ ```ruby
517
+ factory :post do
518
+ # ...
519
+ author { association :author }
520
+ end
521
+ ```
522
+
507
523
  ### Specifying the factory
508
524
 
509
525
  You can specify a different factory (although [Aliases](#aliases) might also
@@ -527,6 +543,15 @@ factory :post do
527
543
  end
528
544
  ```
529
545
 
546
+ Inline:
547
+
548
+ ```ruby
549
+ factory :post do
550
+ # ...
551
+ author { association :user }
552
+ end
553
+ ```
554
+
530
555
  ### Overriding attributes
531
556
 
532
557
  You can also override attributes.
@@ -550,6 +575,35 @@ factory :post do
550
575
  end
551
576
  ```
552
577
 
578
+ Or inline using attributes from the factory:
579
+
580
+ ```rb
581
+ factory :post do
582
+ # ...
583
+ author_last_name { "Writely" }
584
+ author { association :author, last_name: author_last_name }
585
+ end
586
+ ```
587
+
588
+ ### Association overrides
589
+
590
+ Attribute overrides can be used to link associated objects:
591
+
592
+ ```ruby
593
+ FactoryBot.define do
594
+ factory :author do
595
+ name { 'Taylor' }
596
+ end
597
+
598
+ factory :post do
599
+ author
600
+ end
601
+ end
602
+
603
+ eunji = build(:author, name: 'Eunji')
604
+ post = build(:post, author: eunji)
605
+ ```
606
+
553
607
  ### Build strategies
554
608
 
555
609
  In factory\_bot 5, associations default to using the same build strategy as
@@ -619,27 +673,51 @@ factory :post do
619
673
 
620
674
  ### `has_many` associations
621
675
 
622
- Generating data for a `has_many` relationship is a bit more involved,
623
- depending on the amount of flexibility desired, but here's a surefire example
624
- of generating associated data.
676
+ There are a few ways to generate data for a `has_many` relationship. The
677
+ simplest approach is to write a helper method in plain Ruby to tie together the
678
+ different records:
625
679
 
626
680
  ```ruby
627
681
  FactoryBot.define do
682
+ factory :post do
683
+ title { "Through the Looking Glass" }
684
+ user
685
+ end
628
686
 
629
- # post factory with a `belongs_to` association for the user
687
+ factory :user do
688
+ name { "Rachel Sanchez" }
689
+ end
690
+ end
691
+
692
+ def user_with_posts(posts_count: 5)
693
+ FactoryBot.create(:user) do |user|
694
+ FactoryBot.create_list(:post, posts_count, user: user)
695
+ end
696
+ end
697
+
698
+ create(:user).posts.length # 0
699
+ user_with_posts.posts.length # 5
700
+ user_with_posts(posts_count: 15).posts.length # 15
701
+ ```
702
+
703
+ If you prefer to keep the object creation fully within factory\_bot, you can
704
+ build the posts in an `after(:create)` callback.
705
+
706
+
707
+ ```ruby
708
+ FactoryBot.define do
630
709
  factory :post do
631
710
  title { "Through the Looking Glass" }
632
711
  user
633
712
  end
634
713
 
635
- # user factory without associated posts
636
714
  factory :user do
637
715
  name { "John Doe" }
638
716
 
639
717
  # user_with_posts will create post data after the user has been created
640
718
  factory :user_with_posts do
641
- # posts_count is declared as a transient attribute and available in
642
- # attributes on the factory, as well as the callback via the evaluator
719
+ # posts_count is declared as a transient attribute available in the
720
+ # callback via the evaluator
643
721
  transient do
644
722
  posts_count { 5 }
645
723
  end
@@ -650,71 +728,122 @@ FactoryBot.define do
650
728
  # to create and we make sure the user is associated properly to the post
651
729
  after(:create) do |user, evaluator|
652
730
  create_list(:post, evaluator.posts_count, user: user)
731
+
732
+ # You may need to reload the record here, depending on your application
733
+ user.reload
653
734
  end
654
735
  end
655
736
  end
656
737
  end
738
+
739
+ create(:user).posts.length # 0
740
+ create(:user_with_posts).posts.length # 5
741
+ create(:user_with_posts, posts_count: 15).posts.length # 15
657
742
  ```
658
743
 
659
- This allows us to do:
744
+ Or, for a solution that works with `build`, `build_stubbed`, and `create`
745
+ (although it doesn't work well with `attributes_for`), you can use inline
746
+ associations:
660
747
 
661
748
  ```ruby
749
+ FactoryBot.define do
750
+ factory :post do
751
+ title { "Through the Looking Glass" }
752
+ user
753
+ end
754
+
755
+ factory :user do
756
+ name { "Taylor Kim" }
757
+
758
+ factory :user_with_posts do
759
+ posts { [association(:post)] }
760
+ end
761
+ end
762
+ end
763
+
662
764
  create(:user).posts.length # 0
765
+ create(:user_with_posts).posts.length # 1
766
+ build(:user_with_posts).posts.length # 1
767
+ build_stubbed(:user_with_posts).posts.length # 1
768
+ ```
769
+
770
+ For more flexibility you can combine this with the `posts_count` transient
771
+ attribute from the callback example:
772
+
773
+ ```ruby
774
+ FactoryBot.define do
775
+ factory :post do
776
+ title { "Through the Looking Glass" }
777
+ user
778
+ end
779
+
780
+ factory :user do
781
+ name { "Adiza Kumato" }
782
+
783
+ factory :user_with_posts do
784
+ transient do
785
+ posts_count { 5 }
786
+ end
787
+
788
+ posts do
789
+ Array.new(posts_count) { association(:post) }
790
+ end
791
+ end
792
+ end
793
+ end
794
+
663
795
  create(:user_with_posts).posts.length # 5
664
796
  create(:user_with_posts, posts_count: 15).posts.length # 15
797
+ build(:user_with_posts, posts_count: 15).posts.length # 15
798
+ build_stubbed(:user_with_posts, posts_count: 15).posts.length # 15
665
799
  ```
666
800
 
667
801
  ### `has_and_belongs_to_many` associations
668
802
 
669
803
  Generating data for a `has_and_belongs_to_many` relationship is very similar
670
- to the above `has_many` relationship, with a small change, you need to pass an
804
+ to the above `has_many` relationship, with a small change: you need to pass an
671
805
  array of objects to the model's pluralized attribute name rather than a single
672
806
  object to the singular version of the attribute name.
673
807
 
674
- Here's an example with two models that are related via
675
- `has_and_belongs_to_many`:
676
808
 
677
809
  ```ruby
678
- FactoryBot.define do
679
-
680
- # language factory with a `belongs_to` association for the profile
681
- factory :language do
682
- title { "Through the Looking Glass" }
683
- profile
810
+ def profile_with_languages(languages_count: 2)
811
+ FactoryBot.create(:profile) do |profile|
812
+ FactoryBot.create_list(:language, languages_count, profiles: [profile])
684
813
  end
814
+ end
815
+ ```
685
816
 
686
- # profile factory without associated languages
687
- factory :profile do
688
- name { "John Doe" }
817
+ Or with the callback approach:
689
818
 
690
- # profile_with_languages will create language data after the profile has
691
- # been created
692
- factory :profile_with_languages do
693
- # languages_count is declared as an ignored attribute and available in
694
- # attributes on the factory, as well as the callback via the evaluator
695
- transient do
696
- languages_count { 5 }
697
- end
819
+ ```ruby
820
+ factory :profile_with_languages do
821
+ transient do
822
+ languages_count { 2 }
823
+ end
698
824
 
699
- # the after(:create) yields two values; the profile instance itself and
700
- # the evaluator, which stores all values from the factory, including
701
- # ignored attributes; `create_list`'s second argument is the number of
702
- # records to create and we make sure the profile is associated properly
703
- # to the language
704
- after(:create) do |profile, evaluator|
705
- create_list(:language, evaluator.languages_count, profiles: [profile])
706
- end
707
- end
825
+ after(:create) do |profile, evaluator|
826
+ create_list(:language, evaluator.languages_count, profiles: [profile])
827
+ profile.reload
708
828
  end
709
829
  end
710
830
  ```
711
831
 
712
- This allows us to do:
832
+ Or the inline association approach (note the use of the `instance` method here
833
+ to refer to the profile being built):
713
834
 
714
835
  ```ruby
715
- create(:profile).languages.length # 0
716
- create(:profile_with_languages).languages.length # 5
717
- create(:profile_with_languages, languages_count: 15).languages.length # 15
836
+ factory :profile_with_languages do
837
+ transient do
838
+ languages_count { 2 }
839
+ end
840
+
841
+ languages do
842
+ Array.new(languages_count) do
843
+ association(:language, profiles: [instance])
844
+ end
845
+ end
846
+ end
718
847
  ```
719
848
 
720
849
  ### Polymorphic associations
@@ -748,6 +877,61 @@ create(:comment, :for_video)
748
877
  create(:comment, :for_photo)
749
878
  ```
750
879
 
880
+ ### Interconnected associations
881
+
882
+ There are limitless ways objects might be interconnected, and
883
+ factory\_bot may not always be suited to handle those relationships. In some
884
+ cases it makes sense to use factory\_bot to build each individual object, and
885
+ then to write helper methods in plain Ruby to tie those objects together.
886
+
887
+ That said, some more complex, interconnected relationships can be built in factory\_bot
888
+ using inline associations with reference to the `instance` being built.
889
+
890
+ Let's say your models look like this, where an associated `Student` and
891
+ `Profile` should both belong to the same `School`:
892
+
893
+ ```ruby
894
+ class Student < ApplicationRecord
895
+ belongs_to :school
896
+ has_one :profile
897
+ end
898
+
899
+ class Profile < ApplicationRecord
900
+ belongs_to :school
901
+ belongs_to :student
902
+ end
903
+
904
+ class School < ApplicationRecord
905
+ has_many :students
906
+ has_many :profiles
907
+ end
908
+ ```
909
+
910
+ We can ensure the student and profile are connected to each other and to the
911
+ same school with a factory like this:
912
+
913
+ ```ruby
914
+ FactoryBot.define do
915
+ factory :student do
916
+ school
917
+ profile { association :profile, student: instance, school: school }
918
+ end
919
+
920
+ factory :profile do
921
+ school
922
+ student { association :student, profile: instance, school: school }
923
+ end
924
+
925
+ factory :school
926
+ end
927
+ ```
928
+
929
+ Note that this approach works with `build`, `build_stubbed`, and `create`, but
930
+ the associations will return `nil` when using `attributes_for`.
931
+
932
+ Also, note that if you assign any attributes inside a custom `initialize_with`
933
+ (e.g. `initialize_with { new(**attributes) }`), those attributes should not refer to `instance`,
934
+ since it will be `nil`.
751
935
 
752
936
  Sequences
753
937
  ---------
@@ -810,7 +994,7 @@ end
810
994
 
811
995
  ### Initial value
812
996
 
813
- You can override the initial value. Any value that response to the `#next`
997
+ You can override the initial value. Any value that responds to the `#next`
814
998
  method will work (e.g. 1, 2, 3, 'a', 'b', 'c')
815
999
 
816
1000
  ```ruby
@@ -829,6 +1013,15 @@ factory :post do
829
1013
  end
830
1014
  ```
831
1015
 
1016
+ Please note, that the value for the sequence could be any Enumerable instance,
1017
+ as long as it responds to `#next`:
1018
+
1019
+ ```ruby
1020
+ factory :task do
1021
+ sequence :priority, %i[low medium high urgent].cycle
1022
+ end
1023
+ ```
1024
+
832
1025
  ### Aliases
833
1026
 
834
1027
  Sequences can also have aliases. The sequence aliases share the same counter:
@@ -878,6 +1071,22 @@ generate(:email) # "person1@example.com"
878
1071
 
879
1072
  This rewinds all registered sequences.
880
1073
 
1074
+ ### Uniqueness
1075
+
1076
+ When working with uniqueness constraints, be careful not to pass in override values that will conflict with the generated sequence values.
1077
+
1078
+ In this example the email will be the same for both users. If email must be unique, this code will error:
1079
+
1080
+ ```rb
1081
+ factory :user do
1082
+ sequence(:email) { |n| "person#{n}@example.com" }
1083
+ end
1084
+
1085
+ FactoryBot.create(:user, email: "person1@example.com")
1086
+ FactoryBot.create(:user)
1087
+ ```
1088
+
1089
+
881
1090
  Traits
882
1091
  ------
883
1092
 
@@ -943,16 +1152,16 @@ factory :user do
943
1152
  name { "Friendly User" }
944
1153
  login { name }
945
1154
 
946
- trait :male do
1155
+ trait :active do
947
1156
  name { "John Doe" }
948
- gender { "Male" }
949
- login { "#{name} (M)" }
1157
+ status { :active }
1158
+ login { "#{name} (active)" }
950
1159
  end
951
1160
 
952
- trait :female do
1161
+ trait :inactive do
953
1162
  name { "Jane Doe" }
954
- gender { "Female" }
955
- login { "#{name} (F)" }
1163
+ status { :inactive }
1164
+ login { "#{name} (inactive)" }
956
1165
  end
957
1166
 
958
1167
  trait :admin do
@@ -960,8 +1169,8 @@ factory :user do
960
1169
  login { "admin-#{name}" }
961
1170
  end
962
1171
 
963
- factory :male_admin, traits: [:male, :admin] # login will be "admin-John Doe"
964
- factory :female_admin, traits: [:admin, :female] # login will be "Jane Doe (F)"
1172
+ factory :active_admin, traits: [:active, :admin] # login will be "admin-John Doe"
1173
+ factory :inactive_admin, traits: [:admin, :inactive] # login will be "Jane Doe (inactive)"
965
1174
  end
966
1175
  ```
967
1176
 
@@ -974,19 +1183,41 @@ factory :user do
974
1183
  name { "Friendly User" }
975
1184
  login { name }
976
1185
 
977
- trait :male do
1186
+ trait :active do
978
1187
  name { "John Doe" }
979
- gender { "Male" }
1188
+ status { :active }
980
1189
  login { "#{name} (M)" }
981
1190
  end
982
1191
 
983
1192
  factory :brandon do
984
- male
1193
+ active
985
1194
  name { "Brandon" }
986
1195
  end
987
1196
  end
988
1197
  ```
989
1198
 
1199
+ ### As mixins
1200
+
1201
+ Traits can be defined outside of factories and used as mixins to compose shared attributes
1202
+
1203
+ ```ruby
1204
+ FactoryBot.define do
1205
+ trait :timestamps do
1206
+ created_at { 8.days.ago }
1207
+ updated_at { 4.days.ago }
1208
+ end
1209
+
1210
+ factory :user, traits: [:timestamps] do
1211
+ username { "john_doe" }
1212
+ end
1213
+
1214
+ factory :post do
1215
+ timestamps
1216
+ title { "Traits rock" }
1217
+ end
1218
+ end
1219
+ ```
1220
+
990
1221
  ### Using traits
991
1222
 
992
1223
  Traits can also be passed in as a list of symbols when you construct an instance
@@ -996,9 +1227,9 @@ from factory\_bot.
996
1227
  factory :user do
997
1228
  name { "Friendly User" }
998
1229
 
999
- trait :male do
1230
+ trait :active do
1000
1231
  name { "John Doe" }
1001
- gender { "Male" }
1232
+ status { :active }
1002
1233
  end
1003
1234
 
1004
1235
  trait :admin do
@@ -1006,8 +1237,8 @@ factory :user do
1006
1237
  end
1007
1238
  end
1008
1239
 
1009
- # creates an admin user with gender "Male" and name "Jon Snow"
1010
- create(:user, :admin, :male, name: "Jon Snow")
1240
+ # creates an admin user with :active status and name "Jon Snow"
1241
+ create(:user, :admin, :active, name: "Jon Snow")
1011
1242
  ```
1012
1243
 
1013
1244
  This ability works with `build`, `build_stubbed`, `attributes_for`, and `create`.
@@ -1025,8 +1256,8 @@ factory :user do
1025
1256
  end
1026
1257
  end
1027
1258
 
1028
- # creates 3 admin users with gender "Male" and name "Jon Snow"
1029
- create_list(:user, 3, :admin, :male, name: "Jon Snow")
1259
+ # creates 3 admin users with :active status and name "Jon Snow"
1260
+ create_list(:user, 3, :admin, :active, name: "Jon Snow")
1030
1261
  ```
1031
1262
 
1032
1263
  ### With associations
@@ -1325,7 +1556,6 @@ FactoryBot.define do
1325
1556
  factory :application_user, parent: :user do
1326
1557
  full_name { "Jane Doe" }
1327
1558
  date_of_birth { 21.years.ago }
1328
- gender { "Female" }
1329
1559
  health { 90 }
1330
1560
  end
1331
1561
  end
@@ -1338,7 +1568,6 @@ FactoryBot.modify do
1338
1568
  factory :user do
1339
1569
  full_name { "Jane Doe" }
1340
1570
  date_of_birth { 21.years.ago }
1341
- gender { "Female" }
1342
1571
  health { 90 }
1343
1572
  end
1344
1573
  end
@@ -1376,6 +1605,15 @@ twenty_somethings = build_list(:user, 10) do |user, i|
1376
1605
  end
1377
1606
  ```
1378
1607
 
1608
+ `create_list` passes saved instances into the block. If you modify the instance, you must save it again:
1609
+
1610
+ ```ruby
1611
+ twenty_somethings = create_list(:user, 10) do |user, i|
1612
+ user.date_of_birth = (20 + i).years.ago
1613
+ user.save!
1614
+ end
1615
+ ```
1616
+
1379
1617
  `build_stubbed_list` will give you fully stubbed out instances:
1380
1618
 
1381
1619
  ```ruby
@@ -1550,7 +1788,7 @@ factory :user do
1550
1788
 
1551
1789
  name "John Doe"
1552
1790
 
1553
- initialize_with { new(attributes) }
1791
+ initialize_with { new(**attributes) }
1554
1792
  end
1555
1793
  ```
1556
1794
 
data/NEWS.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # News
2
2
 
3
+ ## 6.2.1 (March 8, 2022)
4
+ * Added: CI testing against truffleruby
5
+ * Changed: Documentation improvements for sequences and traits
6
+ * Fixed: ActiveSupport::Notifications reporting strategy through associations now report as symbols
7
+ * Fixed: `add_attribute` with reserved keywords assigns values correctly
8
+
9
+ ## 6.2.0 (May 7, 2021)
10
+ * Added: support for Ruby 3.0
11
+ * Changed: Include factory or trait name in error messages for missing traits. d05a9a3c
12
+ * Changed: Switched from Travis CI to GitHub Actions
13
+ * Fixed: More Ruby 2.7 kwarg deprecation warnings
14
+
15
+ ## 6.1.0 (July 8, 2020)
16
+ * Added: public reader for the evaluation instance, helpful for building interrelated associations
17
+ * Changed: raise a more helpful error when passing an invalid argument to an association
18
+ * Fixed: Ruby 2.7 kwarg deprecation warnings
19
+
3
20
  ## 6.0.2 (June 19, 2020)
4
21
  * Fixed: bug causing traits to consume more memory each time they were used
5
22
 
@@ -8,10 +25,14 @@
8
25
 
9
26
  ## 6.0.0 (June 18, 2020)
10
27
  * Added: automatic definition of traits for Active Record enum attributes, enabled by default
28
+ (Note that this required changing where factory_bot constantizes the build
29
+ class, which may affect applications that were using abstract factories for
30
+ inheritance. See issue #1409.)
11
31
  * Added: `traits_for_enum` method to define traits for non-Active Record enums
12
32
  * Added: `build_stubbed_starting_id=` option to define the starting id for `build_stubbed`
13
33
  * Removed: deprecated methods on the top-level `FactoryBot` module meant only for internal use
14
34
  * Removed: support for EOL versions of Ruby (2.3, 2.4) and Rails (4.2)
35
+ * Removed: support for "abstract" factories with no associated class; use traits instead.
15
36
 
16
37
  ## 5.2.0 (April 24, 2020)
17
38
  * Added: Pass index to block for `*_list` methods
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # factory_bot [![Build Status][ci-image]][ci] [![Code Climate][grade-image]][grade] [![Gem Version][version-image]][version] [![Reviewed by Hound][hound-badge-image]][hound]
1
+ # factory_bot [![Build Status][ci-image]][ci] [![Code Climate][grade-image]][grade] [![Gem Version][version-image]][version]
2
2
 
3
3
  factory_bot is a fixtures replacement with a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute hashes, and stubbed objects), and support for multiple factories for the same class (user, admin_user, and so on), including factory inheritance.
4
4
 
@@ -43,13 +43,7 @@ gem install factory_bot
43
43
  Supported Ruby versions
44
44
  -----------------------
45
45
 
46
- The factory_bot 5.x series supports MRI Ruby 2.3+.
47
-
48
- The factory_bot 3.x+ series supports MRI Ruby 1.9. Additionally, factory_bot
49
- 3.6+ supports JRuby 1.6.7.2+ while running in 1.9 mode. See [GETTING_STARTED]
50
- for more information on configuring the JRuby environment.
51
-
52
- For versions of Ruby prior to 1.9, please use factory_bot 2.x.
46
+ Supported Ruby versions are listed in [`.github/workflows/build.yml`](https://github.com/thoughtbot/factory_bot/blob/master/.github/workflows/build.yml)
53
47
 
54
48
  More Information
55
49
  ----------------
@@ -100,8 +94,8 @@ See [our other projects][community] or
100
94
 
101
95
  [community]: https://thoughtbot.com/community?utm_source=github
102
96
  [hire]: https://thoughtbot.com/hire-us?utm_source=github
103
- [ci-image]: https://travis-ci.org/thoughtbot/factory_bot.svg
104
- [ci]: https://travis-ci.org/thoughtbot/factory_bot?branch=master
97
+ [ci-image]: https://github.com/thoughtbot/factory_bot/actions/workflows/build.yml/badge.svg
98
+ [ci]: https://github.com/thoughtbot/factory_bot/actions?query=workflow%3A.github%2Fworkflows%2Fbuild.yml+branch%3Amaster++
105
99
  [grade-image]: https://codeclimate.com/github/thoughtbot/factory_bot/badges/gpa.svg
106
100
  [grade]: https://codeclimate.com/github/thoughtbot/factory_bot
107
101
  [version-image]: https://badge.fury.io/rb/factory_bot.svg
@@ -10,7 +10,7 @@ module FactoryBot
10
10
 
11
11
  def self.aliases_for(attribute)
12
12
  aliases.map { |(pattern, replace)|
13
- if pattern.match(attribute.to_s)
13
+ if pattern.match?(attribute)
14
14
  attribute.to_s.sub(pattern, replace).to_sym
15
15
  end
16
16
  }.compact << attribute
@@ -12,7 +12,7 @@ module FactoryBot
12
12
 
13
13
  -> {
14
14
  value = case block.arity
15
- when 1, -1 then instance_exec(self, &block)
15
+ when 1, -1, -2 then instance_exec(self, &block)
16
16
  else instance_exec(&block)
17
17
  end
18
18
  raise SequenceAbuseError if FactoryBot::Sequence === value
@@ -37,8 +37,9 @@ module FactoryBot
37
37
  end
38
38
 
39
39
  def decorated_evaluator
40
- Decorator::InvocationTracker.new(
41
- Decorator::NewConstructor.new(@evaluator, @build_class)
40
+ Decorator::NewConstructor.new(
41
+ Decorator::InvocationTracker.new(@evaluator),
42
+ @build_class
42
43
  )
43
44
  end
44
45
 
@@ -9,7 +9,7 @@ module FactoryBot
9
9
 
10
10
  def run(instance, evaluator)
11
11
  case block.arity
12
- when 1, -1 then syntax_runner.instance_exec(instance, &block)
12
+ when 1, -1, -2 then syntax_runner.instance_exec(instance, &block)
13
13
  when 2 then syntax_runner.instance_exec(instance, evaluator, &block)
14
14
  else syntax_runner.instance_exec(&block)
15
15
  end
@@ -6,6 +6,7 @@ module FactoryBot
6
6
  super(name, false)
7
7
  @options = options.dup
8
8
  @overrides = options.extract_options!
9
+ @factory_name = @overrides.delete(:factory) || name
9
10
  @traits = options
10
11
  end
11
12
 
@@ -21,20 +22,36 @@ module FactoryBot
21
22
 
22
23
  private
23
24
 
25
+ attr_reader :factory_name, :overrides, :traits
26
+
24
27
  def build
25
- ensure_factory_is_not_a_declaration!
28
+ raise_if_arguments_are_declarations!
26
29
 
27
- factory_name = @overrides[:factory] || name
28
- [Attribute::Association.new(name, factory_name, [@traits, @overrides.except(:factory)].flatten)]
30
+ [
31
+ Attribute::Association.new(
32
+ name,
33
+ factory_name,
34
+ [traits, overrides].flatten
35
+ )
36
+ ]
29
37
  end
30
38
 
31
- def ensure_factory_is_not_a_declaration!
32
- if @overrides[:factory].is_a?(Declaration)
39
+ def raise_if_arguments_are_declarations!
40
+ if factory_name.is_a?(Declaration)
33
41
  raise ArgumentError.new(<<~MSG)
34
42
  Association '#{name}' received an invalid factory argument.
35
- Did you mean? 'factory: :#{@overrides[:factory].name}'
43
+ Did you mean? 'factory: :#{factory_name.name}'
36
44
  MSG
37
45
  end
46
+
47
+ overrides.each do |attribute, value|
48
+ if value.is_a?(Declaration)
49
+ raise ArgumentError.new(<<~MSG)
50
+ Association '#{name}' received an invalid attribute override.
51
+ Did you mean? '#{attribute}: :#{value.name}'
52
+ MSG
53
+ end
54
+ end
38
55
  end
39
56
  end
40
57
  end
@@ -10,6 +10,7 @@ module FactoryBot
10
10
  @invoked_methods << name
11
11
  super
12
12
  end
13
+ ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
13
14
 
14
15
  def __invoked_methods__
15
16
  @invoked_methods.uniq
@@ -6,18 +6,30 @@ module FactoryBot
6
6
  @component = component
7
7
  end
8
8
 
9
- def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissingSuper
10
- @component.send(name, *args, &block)
9
+ if ::Gem::Version.new(::RUBY_VERSION) >= ::Gem::Version.new("2.7")
10
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
11
+ def method_missing(...) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
12
+ @component.send(...)
13
+ end
14
+
15
+ def send(...)
16
+ __send__(...)
17
+ end
18
+ RUBY
19
+ else
20
+ def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing
21
+ @component.send(name, *args, &block)
22
+ end
23
+
24
+ def send(symbol, *args, &block)
25
+ __send__(symbol, *args, &block)
26
+ end
11
27
  end
12
28
 
13
29
  def respond_to_missing?(name, include_private = false)
14
30
  @component.respond_to?(name, true) || super
15
31
  end
16
32
 
17
- def send(symbol, *args, &block)
18
- __send__(symbol, *args, &block)
19
- end
20
-
21
33
  def self.const_missing(name)
22
34
  ::Object.const_get(name)
23
35
  end
@@ -30,7 +30,7 @@ module FactoryBot
30
30
  end
31
31
 
32
32
  def to_create(&block)
33
- if block_given?
33
+ if block
34
34
  @to_create = block
35
35
  else
36
36
  aggregate_from_traits_and_self(:to_create) { @to_create }.last
@@ -111,6 +111,20 @@ module FactoryBot
111
111
 
112
112
  def base_traits
113
113
  @base_traits.map { |name| trait_by_name(name) }
114
+ rescue KeyError => error
115
+ raise error_with_definition_name(error)
116
+ end
117
+
118
+ def error_with_definition_name(error)
119
+ message = error.message
120
+ message.insert(
121
+ message.index("\nDid you mean?") || message.length,
122
+ " referenced within \"#{name}\" definition"
123
+ )
124
+
125
+ error.class.new(message).tap do |new_error|
126
+ new_error.set_backtrace(error.backtrace)
127
+ end
114
128
  end
115
129
 
116
130
  def additional_traits
@@ -88,7 +88,7 @@ module FactoryBot
88
88
  # end
89
89
  #
90
90
  # are equivalent.
91
- def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing, Style/MethodMissingSuper
91
+ def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing
92
92
  association_options = args.first
93
93
 
94
94
  if association_options.nil?
@@ -24,7 +24,7 @@ module FactoryBot
24
24
  def association(factory_name, *traits_and_overrides)
25
25
  overrides = traits_and_overrides.extract_options!
26
26
  strategy_override = overrides.fetch(:strategy) {
27
- FactoryBot.use_parent_strategy ? @build_strategy.class : :create
27
+ FactoryBot.use_parent_strategy ? @build_strategy.to_sym : :create
28
28
  }
29
29
 
30
30
  traits_and_overrides += [overrides.except(:strategy)]
@@ -33,15 +33,16 @@ module FactoryBot
33
33
  @build_strategy.association(runner)
34
34
  end
35
35
 
36
- attr_writer :instance
36
+ attr_accessor :instance
37
37
 
38
- def method_missing(method_name, *args, &block) # rubocop:disable Style/MethodMissingSuper
38
+ def method_missing(method_name, *args, &block)
39
39
  if @instance.respond_to?(method_name)
40
40
  @instance.send(method_name, *args, &block)
41
41
  else
42
42
  SyntaxRunner.new.send(method_name, *args, &block)
43
43
  end
44
44
  end
45
+ ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
45
46
 
46
47
  def respond_to_missing?(method_name, _include_private = false)
47
48
  @instance.respond_to?(method_name) || SyntaxRunner.new.respond_to?(method_name)
@@ -25,7 +25,7 @@ module FactoryBot
25
25
  raise key_error_with_custom_message(e)
26
26
  end
27
27
 
28
- alias [] find
28
+ alias_method :[], :find
29
29
 
30
30
  def register(name, item)
31
31
  @items[name] = item
@@ -8,6 +8,10 @@ module FactoryBot
8
8
  def result(evaluation)
9
9
  evaluation.hash
10
10
  end
11
+
12
+ def to_sym
13
+ :attributes_for
14
+ end
11
15
  end
12
16
  end
13
17
  end
@@ -10,6 +10,10 @@ module FactoryBot
10
10
  evaluation.notify(:after_build, instance)
11
11
  end
12
12
  end
13
+
14
+ def to_sym
15
+ :build
16
+ end
13
17
  end
14
18
  end
15
19
  end
@@ -13,6 +13,10 @@ module FactoryBot
13
13
  evaluation.notify(:after_create, instance)
14
14
  end
15
15
  end
16
+
17
+ def to_sym
18
+ :create
19
+ end
16
20
  end
17
21
  end
18
22
  end
@@ -6,6 +6,10 @@ module FactoryBot
6
6
 
7
7
  def result(evaluation)
8
8
  end
9
+
10
+ def to_sym
11
+ :null
12
+ end
9
13
  end
10
14
  end
11
15
  end
@@ -41,6 +41,10 @@ module FactoryBot
41
41
  end
42
42
  end
43
43
 
44
+ def to_sym
45
+ :stub
46
+ end
47
+
44
48
  private
45
49
 
46
50
  def next_id
@@ -15,7 +15,7 @@ module FactoryBot
15
15
  def factory(name, options = {}, &block)
16
16
  factory = Factory.new(name, options)
17
17
  proxy = FactoryBot::DefinitionProxy.new(factory.definition)
18
- proxy.instance_eval(&block) if block_given?
18
+ proxy.instance_eval(&block) if block
19
19
 
20
20
  Internal.register_factory(factory)
21
21
 
@@ -9,7 +9,7 @@ module FactoryBot
9
9
  @definition = Definition.new(@name)
10
10
  proxy = FactoryBot::DefinitionProxy.new(@definition)
11
11
 
12
- if block_given?
12
+ if block
13
13
  proxy.instance_eval(&@block)
14
14
  end
15
15
  end
@@ -1,3 +1,3 @@
1
1
  module FactoryBot
2
- VERSION = "6.0.2".freeze
2
+ VERSION = "6.2.1".freeze
3
3
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factory_bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.2
4
+ version: 6.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Clayton
8
8
  - Joe Ferris
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-06-20 00:00:00.000000000 Z
12
+ date: 2022-03-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -237,7 +237,7 @@ homepage: https://github.com/thoughtbot/factory_bot
237
237
  licenses:
238
238
  - MIT
239
239
  metadata: {}
240
- post_install_message:
240
+ post_install_message:
241
241
  rdoc_options: []
242
242
  require_paths:
243
243
  - lib
@@ -252,8 +252,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
252
252
  - !ruby/object:Gem::Version
253
253
  version: '0'
254
254
  requirements: []
255
- rubygems_version: 3.1.2
256
- signing_key:
255
+ rubygems_version: 3.1.6
256
+ signing_key:
257
257
  specification_version: 4
258
258
  summary: factory_bot provides a framework and DSL for defining and using model instance
259
259
  factories.