factory_bot 6.0.2 → 6.2.1

Sign up to get free protection for your applications and to get access to all the features.
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.