cucumber_factory 2.0.2 → 2.1.0

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: eeed19e5c878629c3f87a038821a955401c40c19ca323577d7e84e3ae019f764
4
- data.tar.gz: 2d3b57e9b739be6e69d28240c7553eec3882b2e200ca64c402a8cc646bcdc169
3
+ metadata.gz: 3153238ae9f4c30fd21e6dd493621e361d4bc6b508521effc93fbb4cb4be8af2
4
+ data.tar.gz: c152087fd0231027a94abf15f2abff5c3b3857c714212f3c1eefa67902b32b3e
5
5
  SHA512:
6
- metadata.gz: 752a2cbe68fa53254741a792770ab0252f320a218fb3c39fe85679bcf6d8e1289fdee354042bb342d96789266c6f8ac02819393ee375d628def84679de281cad
7
- data.tar.gz: aa2eb84a898e7d52f10b7272ad8f7c04a9d1dea4096d0a7fab04d9158a887f46839e7d36b83f9aec945b13b18f0b4550de19fe9df0897ccc1235f22f990b2b59
6
+ metadata.gz: 48b39f6ebaedb5bca521d07579384cbcffea01eb4638787de026ce683b1db2abc70fd91227f272cd88a75cb3a15cd8e780989b99a3429313aa6f9e0f6a8575c9
7
+ data.tar.gz: 5b16cb2d4e73c1fde061b12e31a23944a3d6889316002805cd0f0bb7d0355cf520d01cb587c5a0ab58648633f83189ee2bba9597b2bda7217315db5fc48ca218
@@ -13,6 +13,19 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html
13
13
 
14
14
  ### Compatible changes
15
15
 
16
+ -
17
+
18
+
19
+ ## 2.1.0 - 2020-03-09
20
+
21
+ ### Compatible changes
22
+
23
+ - Allow associations to be set for [transient attributes](https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#transient-attributes) if they are named after the model. For example, when there is a `Role` model and the`user` factory has a transient attribute `role`, the following steps are now valid:
24
+ ```
25
+ Given there is a role
26
+ And there is a user with the role above
27
+ ```
28
+
16
29
  ## 2.0.2 - 2020-03-26
17
30
 
18
31
  ### Compatible changes
@@ -12,5 +12,8 @@ gem 'rake', '>=10.0.4'
12
12
  gem 'database_cleaner', '~>1.0.0'
13
13
  gem 'gemika'
14
14
 
15
+ # Test dependencies
16
+ gem 'factory_bot', '< 5'
17
+
15
18
  # Gem under test
16
19
  gem 'cucumber_factory', :path => '.'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cucumber_factory (2.0.2)
4
+ cucumber_factory (2.1.0)
5
5
  activerecord
6
6
  activesupport
7
7
  cucumber
@@ -35,6 +35,8 @@ GEM
35
35
  cucumber
36
36
  database_cleaner (1.0.1)
37
37
  diff-lcs (1.3)
38
+ factory_bot (4.11.1)
39
+ activesupport (>= 3.0.0)
38
40
  gemika (0.4.0)
39
41
  gherkin (2.12.2)
40
42
  multi_json (~> 1.3)
@@ -71,6 +73,7 @@ DEPENDENCIES
71
73
  cucumber (~> 1.3.20)
72
74
  cucumber_factory!
73
75
  database_cleaner (~> 1.0.0)
76
+ factory_bot (< 5)
74
77
  gemika
75
78
  mysql2
76
79
  rake (>= 10.0.4)
@@ -12,5 +12,8 @@ gem 'rake'
12
12
  gem 'database_cleaner'
13
13
  gem 'gemika'
14
14
 
15
+ # Test dependencies
16
+ gem 'factory_bot', '< 5' # 5.0 requires Ruby >= 2.3
17
+
15
18
  # Gem under test
16
19
  gem 'cucumber_factory', :path => '.'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cucumber_factory (2.0.2)
4
+ cucumber_factory (2.1.0)
5
5
  activerecord
6
6
  activesupport
7
7
  cucumber
@@ -40,6 +40,8 @@ GEM
40
40
  cucumber
41
41
  database_cleaner (1.6.2)
42
42
  diff-lcs (1.3)
43
+ factory_bot (4.11.1)
44
+ activesupport (>= 3.0.0)
43
45
  gemika (0.3.2)
44
46
  gherkin (4.1.3)
45
47
  i18n (0.9.5)
@@ -75,6 +77,7 @@ DEPENDENCIES
75
77
  cucumber (~> 2.4.0)
76
78
  cucumber_factory!
77
79
  database_cleaner
80
+ factory_bot (< 5)
78
81
  gemika
79
82
  mysql2
80
83
  rake
@@ -12,5 +12,8 @@ gem 'rake'
12
12
  gem 'database_cleaner'
13
13
  gem 'gemika'
14
14
 
15
+ # Test dependencies
16
+ gem 'factory_bot'
17
+
15
18
  # Gem under test
16
19
  gem 'cucumber_factory', :path => '.'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cucumber_factory (2.0.2)
4
+ cucumber_factory (2.1.0)
5
5
  activerecord
6
6
  activesupport
7
7
  cucumber
@@ -45,6 +45,8 @@ GEM
45
45
  cucumber
46
46
  database_cleaner (1.6.2)
47
47
  diff-lcs (1.3)
48
+ factory_bot (5.1.1)
49
+ activesupport (>= 4.2.0)
48
50
  gemika (0.3.2)
49
51
  gherkin (4.1.3)
50
52
  i18n (0.9.5)
@@ -80,6 +82,7 @@ DEPENDENCIES
80
82
  cucumber (~> 3.0.0)
81
83
  cucumber_factory!
82
84
  database_cleaner
85
+ factory_bot
83
86
  gemika
84
87
  mysql2
85
88
  rake
@@ -12,5 +12,8 @@ gem 'rake'
12
12
  gem 'database_cleaner'
13
13
  gem 'gemika'
14
14
 
15
+ # Test dependencies
16
+ gem 'factory_bot'
17
+
15
18
  # Gem under test
16
19
  gem 'cucumber_factory', :path => '.'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cucumber_factory (2.0.2)
4
+ cucumber_factory (2.1.0)
5
5
  activerecord
6
6
  activesupport
7
7
  cucumber
@@ -45,6 +45,8 @@ GEM
45
45
  cucumber
46
46
  database_cleaner (1.6.2)
47
47
  diff-lcs (1.3)
48
+ factory_bot (5.1.1)
49
+ activesupport (>= 4.2.0)
48
50
  gemika (0.3.2)
49
51
  gherkin (5.0.0)
50
52
  i18n (0.9.5)
@@ -80,6 +82,7 @@ DEPENDENCIES
80
82
  cucumber (~> 3.1.0)
81
83
  cucumber_factory!
82
84
  database_cleaner
85
+ factory_bot
83
86
  gemika
84
87
  mysql2
85
88
  rake
data/README.md CHANGED
@@ -65,7 +65,7 @@ Given there is a movie with these attributes:
65
65
  Setting associations
66
66
  --------------------
67
67
 
68
- You can set `belongs_to` associations by referring to the last created record of as `above`:
68
+ You can set `belongs_to` and `transient` associations by referring to the last created record of as `above`:
69
69
 
70
70
  ```cucumber
71
71
  Given there is a movie with the title "Before Sunrise"
@@ -7,79 +7,110 @@ module CucumberFactory
7
7
  class << self
8
8
 
9
9
  def from_prose(model_prose, variant_prose)
10
- # don't use \w which depends on the system locale
11
- underscored_model_name = model_prose.gsub(/[^A-Za-z0-9_\/]+/, "_")
12
- if variant_prose.present?
13
- variants = /\((.*?)\)/.match(variant_prose)[1].split(/\s*,\s*/)
14
- variants = variants.collect { |variant| variant.downcase.gsub(" ", "_") }
10
+ variants = variants_from_prose(variant_prose)
11
+ factory = factory_bot_factory(model_prose, variants)
12
+
13
+ if factory
14
+ strategy = factory_bot_strategy(factory, model_prose, variants)
15
+ transient_attributes = factory_bot_transient_attributes(factory, variants)
15
16
  else
16
- variants = []
17
+ strategy = alternative_strategy(model_prose, variants)
18
+ transient_attributes = []
17
19
  end
18
20
 
19
- if factory_bot_strategy = factory_bot_strategy(underscored_model_name, variants)
20
- factory_bot_strategy
21
+ [strategy, transient_attributes]
22
+ end
23
+
24
+ private
25
+
26
+ def variants_from_prose(variant_prose)
27
+ if variant_prose.present?
28
+ variants = /\((.*?)\)/.match(variant_prose)[1].split(/\s*,\s*/)
29
+ variants.collect { |variant| variant.downcase.gsub(" ", "_").to_sym }
21
30
  else
22
- model_class = underscored_model_name.camelize.constantize
23
- machinist_strategy(model_class, variants) ||
24
- active_record_strategy(model_class) ||
25
- ruby_object_strategy(model_class)
31
+ []
26
32
  end
27
33
  end
28
34
 
29
- private
35
+ def factory_bot_factory(model_prose, variants)
36
+ return unless factory_bot_class
30
37
 
31
- def factory_bot_strategy(factory_name, variants)
32
- factory_class = ::FactoryBot if defined?(FactoryBot)
33
- factory_class ||= ::FactoryGirl if defined?(FactoryGirl)
34
- return unless factory_class
38
+ factory_name = factory_name_from_prose(model_prose)
39
+ factory = factory_bot_class.factories[factory_name]
35
40
 
36
- variants = variants.map(&:to_sym)
37
- factory_name = factory_name.to_s.underscore.gsub('/', '_').to_sym
41
+ if factory.nil? && variants.present?
42
+ factory = factory_bot_class.factories[variants[0]]
43
+ end
44
+
45
+ factory
46
+ end
38
47
 
39
- factory = factory_class.factories[factory_name]
48
+ def factory_bot_strategy(factory, model_prose, variants)
49
+ return unless factory
40
50
 
41
- if factory.nil? && variants.present? && factory = factory_class.factories[variants[0]]
51
+ factory_name = factory_name_from_prose(model_prose)
52
+ if factory_bot_class.factories[factory_name].nil? && variants.present?
42
53
  factory_name, *variants = variants
43
54
  end
44
55
 
45
- if factory
46
- new(factory.build_class) do |attributes|
47
- # Cannot have additional scalar args after a varargs
48
- # argument in Ruby 1.8 and 1.9
49
- args = []
50
- args += variants
51
- args << attributes
52
- factory_class.create(factory_name, *args)
53
- end
56
+ new(factory.build_class) do |attributes|
57
+ # Cannot have additional scalar args after a varargs
58
+ # argument in Ruby 1.8 and 1.9
59
+ args = []
60
+ args += variants
61
+ args << attributes
62
+ factory_bot_class.create(factory_name, *args)
63
+ end
64
+ end
65
+
66
+ def factory_bot_transient_attributes(factory, variants)
67
+ return [] unless factory
68
+
69
+ factory_attributes = factory_bot_attributes(factory, variants)
70
+ class_attributes = factory.build_class.attribute_names.map(&:to_sym)
71
+
72
+ factory_attributes - class_attributes
73
+ end
74
+
75
+ def factory_bot_attributes(factory, variants)
76
+ traits = factory_bot_traits(factory, variants)
77
+ factory.with_traits(traits.map(&:name)).definition.attributes.names
78
+ end
79
+
80
+ def factory_bot_traits(factory, variants)
81
+ factory.definition.defined_traits.select do |trait|
82
+ variants.include?(trait.name.to_sym)
54
83
  end
84
+ end
55
85
 
86
+ def alternative_strategy(model_prose, variants)
87
+ model_class = underscored_model_name(model_prose).camelize.constantize
88
+ machinist_strategy(model_class, variants) ||
89
+ active_record_strategy(model_class) ||
90
+ ruby_object_strategy(model_class)
56
91
  end
57
92
 
58
93
  def machinist_strategy(model_class, variants)
59
- if model_class.respond_to?(:make)
60
-
61
- new(model_class) do |attributes|
62
- if variants.present?
63
- variants.size == 1 or raise 'Machinist only supports a single variant per blueprint'
64
- model_class.make(variants.first.to_sym, attributes)
65
- else
66
- model_class.make(attributes)
67
- end
68
- end
94
+ return unless model_class.respond_to?(:make)
69
95
 
96
+ new(model_class) do |attributes|
97
+ if variants.present?
98
+ variants.size == 1 or raise 'Machinist only supports a single variant per blueprint'
99
+ model_class.make(variants.first, attributes)
100
+ else
101
+ model_class.make(attributes)
102
+ end
70
103
  end
71
104
  end
72
105
 
73
106
  def active_record_strategy(model_class)
74
- if model_class.respond_to?(:create!)
75
-
76
- new(model_class) do |attributes|
77
- model = model_class.new
78
- CucumberFactory::Switcher.assign_attributes(model, attributes)
79
- model.save!
80
- model
81
- end
107
+ return unless model_class.respond_to?(:create!)
82
108
 
109
+ new(model_class) do |attributes|
110
+ model = model_class.new
111
+ CucumberFactory::Switcher.assign_attributes(model, attributes)
112
+ model.save!
113
+ model
83
114
  end
84
115
  end
85
116
 
@@ -89,6 +120,21 @@ module CucumberFactory
89
120
  end
90
121
  end
91
122
 
123
+ def factory_bot_class
124
+ factory_class = ::FactoryBot if defined?(FactoryBot)
125
+ factory_class ||= ::FactoryGirl if defined?(FactoryGirl)
126
+ factory_class
127
+ end
128
+
129
+ def factory_name_from_prose(model_prose)
130
+ underscored_model_name(model_prose).to_s.underscore.gsub('/', '_').to_sym
131
+ end
132
+
133
+ def underscored_model_name(model_prose)
134
+ # don't use \w which depends on the system locale
135
+ model_prose.gsub(/[^A-Za-z0-9_\/]+/, "_")
136
+ end
137
+
92
138
  end
93
139
 
94
140
 
@@ -100,7 +100,7 @@ module CucumberFactory
100
100
  end
101
101
 
102
102
  def parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil)
103
- build_strategy = BuildStrategy.from_prose(raw_model, raw_variant)
103
+ build_strategy, transient_attributes = BuildStrategy.from_prose(raw_model, raw_variant)
104
104
  model_class = build_strategy.model_class
105
105
  attributes = {}
106
106
  if raw_attributes.try(:strip).present?
@@ -108,7 +108,7 @@ module CucumberFactory
108
108
  raw_attributes.scan(raw_attribute_fragment_regex).each do |fragment|
109
109
  attribute = attribute_name_from_prose(fragment[0])
110
110
  value = fragment[1]
111
- attributes[attribute] = attribute_value(world, model_class, attribute, value)
111
+ attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value)
112
112
  end
113
113
  unused_raw_attributes = raw_attributes.gsub(raw_attribute_fragment_regex, '')
114
114
  if unused_raw_attributes.present?
@@ -129,14 +129,14 @@ module CucumberFactory
129
129
  raw_attribute, value = fragment.split(': ')
130
130
  attribute = attribute_name_from_prose(raw_attribute)
131
131
  value = "\"#{value}\"" unless matches_fully?(value, VALUE_ARRAY)
132
- attributes[attribute] = attribute_value(world, model_class, attribute, value)
132
+ attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value)
133
133
  end
134
134
  # DataTable e.g. in raw [["first name", "Jane"], ["last name", "Jenny"]]
135
135
  else
136
136
  raw_multiline_attributes.raw.each do |raw_attribute, value|
137
137
  attribute = attribute_name_from_prose(raw_attribute)
138
138
  value = "\"#{value}\"" unless matches_fully?(value, VALUE_ARRAY)
139
- attributes[attribute] = attribute_value(world, model_class, attribute, value)
139
+ attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value)
140
140
  end
141
141
  end
142
142
  end
@@ -145,15 +145,15 @@ module CucumberFactory
145
145
  record
146
146
  end
147
147
 
148
- def attribute_value(world, model_class, attribute, value)
149
- association = model_class.respond_to?(:reflect_on_association) ? model_class.reflect_on_association(attribute) : nil
148
+ def attribute_value(world, model_class, transient_attributes, attribute, value)
149
+ association_class = resolve_association_class(attribute, model_class, transient_attributes)
150
150
 
151
151
  if matches_fully?(value, VALUE_ARRAY)
152
152
  elements_str = unquote(value)
153
- value = elements_str.scan(VALUE_SCALAR).map { |v| attribute_value(world, model_class, attribute, v) }
154
- elsif association.present?
153
+ value = elements_str.scan(VALUE_SCALAR).map { |v| attribute_value(world, model_class, transient_attributes, attribute, v) }
154
+ elsif association_class.present?
155
155
  if matches_fully?(value, VALUE_LAST_RECORD)
156
- value = CucumberFactory::Switcher.find_last(association.klass) or raise Error, "There is no last #{attribute}"
156
+ value = CucumberFactory::Switcher.find_last(association_class) or raise Error, "There is no last #{attribute}"
157
157
  elsif matches_fully?(value, VALUE_STRING)
158
158
  value = unquote(value)
159
159
  value = get_named_record(world, value) || transform_value(world, value)
@@ -169,6 +169,19 @@ module CucumberFactory
169
169
  value
170
170
  end
171
171
 
172
+ def resolve_association_class(attribute, model_class, transient_attributes)
173
+ return unless model_class.respond_to?(:reflect_on_association)
174
+
175
+ klass = if model_class.reflect_on_association(attribute)
176
+ model_class.reflect_on_association(attribute).klass
177
+ elsif transient_attributes.include?(attribute.to_sym)
178
+ klass_name = attribute.to_s.camelize
179
+ klass_name.constantize if Object.const_defined?(klass_name)
180
+ else
181
+ nil
182
+ end
183
+ end
184
+
172
185
  def resolve_scalar_value(world, model_class, attribute, value)
173
186
  if matches_fully?(value, VALUE_STRING)
174
187
  value = unquote(value)
@@ -1,3 +1,3 @@
1
1
  module CucumberFactory
2
- VERSION = '2.0.2'
2
+ VERSION = '2.1.0'
3
3
  end
@@ -2,47 +2,45 @@ require 'spec_helper'
2
2
 
3
3
  describe CucumberFactory::BuildStrategy do
4
4
 
5
- subject { CucumberFactory::BuildStrategy }
6
-
7
5
  # most of the behaviour is integration tested in steps_spec.rb
8
6
 
9
7
  describe '.from_prose' do
10
8
 
11
9
  context 'when describing a factory_bot factory' do
12
-
13
- it 'returns a strategy corresponding to the factories model' do
14
- FactoryBot.stub_factories :job_offer => JobOffer
15
- strategy = subject.from_prose('job offer', nil)
10
+ it 'returns a strategy and transient attributes corresponding to the factories model' do
11
+ strategy, transient_attributes = described_class.from_prose('job offer', nil)
16
12
 
17
13
  strategy.should be_a(described_class)
18
14
  strategy.model_class.should == JobOffer
15
+ transient_attributes.should == [:my_transient_attribute]
19
16
  end
20
17
 
21
18
  it 'uses the variant for the factory name if present' do
22
- FactoryBot.stub_factories :job_offer => JobOffer
23
- strategy = subject.from_prose('foo', '(job offer)')
19
+ strategy, transient_attributes = described_class.from_prose('job offer', '(tempting_job_offer)')
24
20
 
25
21
  strategy.should be_a(described_class)
26
22
  strategy.model_class.should == JobOffer
23
+ transient_attributes.should == [:my_transient_attribute, :other_transient_attribute]
27
24
  end
28
-
29
25
  end
30
26
 
31
27
  context 'when describing a non factory_bot model' do
28
+ before do
29
+ hide_const("FactoryBot")
30
+ end
32
31
 
33
32
  it "should return a strategy for the class matching a natural language expression" do
34
- subject.from_prose("movie", nil).model_class.should == Movie
35
- subject.from_prose("job offer", nil).model_class.should == JobOffer
33
+ described_class.from_prose("movie", nil).first.model_class.should == Movie
34
+ described_class.from_prose("job offer", nil).first.model_class.should == JobOffer
36
35
  end
37
36
 
38
37
  it "should ignore variants for the class name" do
39
- subject.from_prose("movie", "(job offer)").model_class.should == Movie
38
+ described_class.from_prose("movie", "(job offer)").first.model_class.should == Movie
40
39
  end
41
40
 
42
41
  it "should allow namespaced models" do
43
- subject.from_prose("people/actor", nil).model_class.should == People::Actor
42
+ described_class.from_prose("people/actor", nil).first.model_class.should == People::Actor
44
43
  end
45
-
46
44
  end
47
45
 
48
46
  end