factory_girl 2.2.0 → 2.3.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.
Files changed (58) hide show
  1. data/.autotest +1 -1
  2. data/CONTRIBUTION_GUIDELINES.md +1 -1
  3. data/Changelog +16 -3
  4. data/GETTING_STARTED.md +22 -2
  5. data/Gemfile.lock +1 -1
  6. data/Rakefile +11 -15
  7. data/gemfiles/2.1.gemfile.lock +1 -1
  8. data/gemfiles/2.3.gemfile.lock +1 -1
  9. data/gemfiles/3.0.gemfile.lock +1 -1
  10. data/gemfiles/3.1.gemfile.lock +1 -1
  11. data/lib/factory_girl.rb +8 -3
  12. data/lib/factory_girl/attribute.rb +8 -0
  13. data/lib/factory_girl/attribute/dynamic.rb +1 -5
  14. data/lib/factory_girl/attribute/sequence.rb +1 -5
  15. data/lib/factory_girl/attribute/static.rb +1 -5
  16. data/lib/factory_girl/attribute_list.rb +18 -44
  17. data/lib/factory_girl/callback.rb +5 -0
  18. data/lib/factory_girl/declaration.rb +3 -0
  19. data/lib/factory_girl/declaration/association.rb +8 -0
  20. data/lib/factory_girl/declaration/dynamic.rb +9 -0
  21. data/lib/factory_girl/declaration/implicit.rb +11 -2
  22. data/lib/factory_girl/declaration/static.rb +9 -0
  23. data/lib/factory_girl/declaration_list.rb +48 -0
  24. data/lib/factory_girl/definition.rb +62 -0
  25. data/lib/factory_girl/definition_proxy.rb +11 -11
  26. data/lib/factory_girl/factory.rb +100 -111
  27. data/lib/factory_girl/null_factory.rb +15 -0
  28. data/lib/factory_girl/proxy.rb +14 -9
  29. data/lib/factory_girl/proxy/attributes_for.rb +2 -3
  30. data/lib/factory_girl/proxy/build.rb +12 -20
  31. data/lib/factory_girl/proxy/create.rb +0 -6
  32. data/lib/factory_girl/proxy/stub.rb +4 -10
  33. data/lib/factory_girl/registry.rb +4 -3
  34. data/lib/factory_girl/step_definitions.rb +1 -1
  35. data/lib/factory_girl/syntax/default.rb +3 -4
  36. data/lib/factory_girl/syntax/methods.rb +38 -16
  37. data/lib/factory_girl/trait.rb +13 -21
  38. data/lib/factory_girl/version.rb +1 -1
  39. data/spec/acceptance/modify_factories_spec.rb +1 -1
  40. data/spec/acceptance/traits_spec.rb +87 -1
  41. data/spec/factory_girl/attribute/dynamic_spec.rb +1 -1
  42. data/spec/factory_girl/attribute_list_spec.rb +9 -58
  43. data/spec/factory_girl/declaration_list_spec.rb +71 -0
  44. data/spec/factory_girl/definition_proxy_spec.rb +135 -139
  45. data/spec/factory_girl/definition_spec.rb +81 -0
  46. data/spec/factory_girl/factory_spec.rb +42 -17
  47. data/spec/factory_girl/null_factory_spec.rb +12 -0
  48. data/spec/factory_girl/proxy/build_spec.rb +1 -24
  49. data/spec/factory_girl/proxy/create_spec.rb +14 -11
  50. data/spec/factory_girl/proxy_spec.rb +23 -40
  51. data/spec/factory_girl/registry_spec.rb +4 -3
  52. data/spec/spec_helper.rb +2 -0
  53. data/spec/support/matchers/callback.rb +9 -0
  54. data/spec/support/matchers/declaration.rb +71 -0
  55. data/spec/support/matchers/delegate.rb +44 -0
  56. data/spec/support/matchers/trait.rb +9 -0
  57. data/spec/support/shared_examples/proxy.rb +4 -5
  58. metadata +191 -115
@@ -0,0 +1,15 @@
1
+ module FactoryGirl
2
+ class NullFactory
3
+ attr_reader :definition
4
+
5
+ def initialize
6
+ @definition = Definition.new
7
+ end
8
+
9
+ delegate :defined_traits, :callbacks, :attributes, :to => :definition
10
+
11
+ def compile; end
12
+ def default_strategy; end
13
+ def class_name; end
14
+ end
15
+ end
@@ -1,10 +1,14 @@
1
+ require "active_support/core_ext/hash/except"
2
+
1
3
  module FactoryGirl
2
4
  class Proxy #:nodoc:
5
+ def initialize(klass, callbacks = [])
6
+ @callbacks = callbacks.inject({}) do |result, callback|
7
+ result[callback.name] ||= []
8
+ result[callback.name] << callback
9
+ result
10
+ end
3
11
 
4
- attr_reader :callbacks
5
-
6
- def initialize(klass)
7
- @callbacks = {}
8
12
  @ignored_attributes = {}
9
13
  end
10
14
 
@@ -21,11 +25,6 @@ module FactoryGirl
21
25
  def associate(name, factory, attributes)
22
26
  end
23
27
 
24
- def add_callback(callback)
25
- @callbacks[callback.name] ||= []
26
- @callbacks[callback.name] << callback
27
- end
28
-
29
28
  def run_callbacks(name)
30
29
  if @callbacks[name]
31
30
  @callbacks[name].each do |callback|
@@ -76,5 +75,11 @@ module FactoryGirl
76
75
  def result(to_create)
77
76
  raise NotImplementedError, "Strategies must return a result"
78
77
  end
78
+
79
+ def self.ensure_strategy_exists!(strategy)
80
+ unless Proxy.const_defined? strategy.to_s.camelize
81
+ raise ArgumentError, "Unknown strategy: #{strategy}"
82
+ end
83
+ end
79
84
  end
80
85
  end
@@ -1,10 +1,9 @@
1
1
  module FactoryGirl
2
2
  class Proxy #:nodoc:
3
3
  class AttributesFor < Proxy #:nodoc:
4
- def initialize(klass)
5
- super(klass)
4
+ def initialize(klass, callbacks = [])
5
+ super
6
6
  @hash = {}
7
- @ignored_attributes = {}
8
7
  end
9
8
 
10
9
  def get(attribute)
@@ -1,8 +1,8 @@
1
1
  module FactoryGirl
2
2
  class Proxy #:nodoc:
3
3
  class Build < Proxy #:nodoc:
4
- def initialize(klass)
5
- super(klass)
4
+ def initialize(klass, callbacks = [])
5
+ super
6
6
  @instance = klass.new
7
7
  end
8
8
 
@@ -19,19 +19,13 @@ module FactoryGirl
19
19
  end
20
20
 
21
21
  def associate(name, factory_name, overrides)
22
- method = get_method(overrides[:method])
23
- factory = FactoryGirl.factory_by_name(factory_name)
24
- set(name, factory.run(method, remove_method(overrides)))
22
+ set(name, association(factory_name, overrides))
25
23
  end
26
24
 
27
25
  def association(factory_name, overrides = {})
28
26
  method = get_method(overrides[:method])
29
27
  factory = FactoryGirl.factory_by_name(factory_name)
30
- factory.run(method, remove_method(overrides))
31
- end
32
-
33
- def remove_method(overrides)
34
- overrides.dup.delete_if {|key, value| key == :method}
28
+ factory.run(method, overrides.except(:method))
35
29
  end
36
30
 
37
31
  def result(to_create)
@@ -39,18 +33,16 @@ module FactoryGirl
39
33
  @instance
40
34
  end
41
35
 
42
- def parse_method(method)
43
- method ||= :create
44
- if :build == method
45
- return Proxy::Build
46
- elsif :create == method
47
- return Proxy::Create
48
- else
49
- raise "unrecognized method #{method}"
36
+ private
37
+
38
+ def get_method(method)
39
+ case method
40
+ when :build then Proxy::Build
41
+ when :create then Proxy::Create
42
+ when nil then Proxy::Create
43
+ else raise "unrecognized method #{method}"
50
44
  end
51
45
  end
52
-
53
- alias_method :get_method, :parse_method
54
46
  end
55
47
  end
56
48
  end
@@ -11,12 +11,6 @@ module FactoryGirl
11
11
  run_callbacks(:after_create)
12
12
  @instance
13
13
  end
14
-
15
- def get_method(method_string)
16
- # Leaving this as Proxy::Build in the :method => :build case
17
- # is a bit strange, but does it have any user-visible behaviors?
18
- parse_method(method_string)
19
- end
20
14
  end
21
15
  end
22
16
  end
@@ -3,10 +3,9 @@ module FactoryGirl
3
3
  class Stub < Proxy #:nodoc:
4
4
  @@next_id = 1000
5
5
 
6
- def initialize(klass)
7
- super(klass)
6
+ def initialize(klass, callbacks = [])
7
+ super
8
8
  @instance = klass.new
9
- @ignored_attributes = {}
10
9
  @instance.id = next_id
11
10
  @instance.instance_eval do
12
11
  def persisted?
@@ -60,17 +59,12 @@ module FactoryGirl
60
59
  end
61
60
 
62
61
  def associate(name, factory_name, overrides)
63
- factory = FactoryGirl.factory_by_name(factory_name)
64
- set(name, factory.run(Proxy::Stub, remove_method(overrides)))
62
+ set(name, association(factory_name, overrides))
65
63
  end
66
64
 
67
65
  def association(factory_name, overrides = {})
68
66
  factory = FactoryGirl.factory_by_name(factory_name)
69
- factory.run(Proxy::Stub, remove_method(overrides))
70
- end
71
-
72
- def remove_method(overrides)
73
- overrides.dup.delete_if {|key, value| key == :method}
67
+ factory.run(Proxy::Stub, overrides.except(:method))
74
68
  end
75
69
 
76
70
  def result(to_create)
@@ -2,7 +2,8 @@ module FactoryGirl
2
2
  class Registry
3
3
  include Enumerable
4
4
 
5
- def initialize
5
+ def initialize(name)
6
+ @name = name
6
7
  @items = {}
7
8
  end
8
9
 
@@ -12,7 +13,7 @@ module FactoryGirl
12
13
  end
13
14
 
14
15
  def find(name)
15
- @items[name.to_sym] or raise ArgumentError.new("Not registered: #{name.to_s}")
16
+ @items[name.to_sym] or raise ArgumentError.new("#{@name} not registered: #{name.to_s}")
16
17
  end
17
18
 
18
19
  def each(&block)
@@ -35,7 +36,7 @@ module FactoryGirl
35
36
 
36
37
  def add_as(name, item)
37
38
  if registered?(name)
38
- raise DuplicateDefinitionError, "Already defined: #{name}"
39
+ raise DuplicateDefinitionError, "#{@name} already registered: #{name}"
39
40
  else
40
41
  @items[name.to_sym] = item
41
42
  end
@@ -95,7 +95,7 @@ end
95
95
  World(FactoryGirlStepHelpers)
96
96
 
97
97
  FactoryGirl.factories.each do |factory|
98
- factory.ensure_compiled
98
+ factory.compile
99
99
  factory.human_names.each do |human_name|
100
100
  Given /^the following (?:#{human_name}|#{human_name.pluralize}) exists?:$/i do |table|
101
101
  table.hashes.each do |human_hash|
@@ -18,7 +18,7 @@ module FactoryGirl
18
18
 
19
19
  def factory(name, options = {}, &block)
20
20
  factory = Factory.new(name, options)
21
- proxy = FactoryGirl::DefinitionProxy.new(factory)
21
+ proxy = FactoryGirl::DefinitionProxy.new(factory.definition)
22
22
  proxy.instance_eval(&block) if block_given?
23
23
 
24
24
  FactoryGirl.register_factory(factory)
@@ -43,10 +43,9 @@ module FactoryGirl
43
43
  end
44
44
 
45
45
  def factory(name, options = {}, &block)
46
- factory = FactoryGirl.factory_by_name(name).allow_overrides
47
- proxy = FactoryGirl::DefinitionProxy.new(factory)
46
+ factory = FactoryGirl.factory_by_name(name)
47
+ proxy = FactoryGirl::DefinitionProxy.new(factory.definition.overridable)
48
48
  proxy.instance_eval(&block)
49
- factory.ensure_compiled
50
49
  end
51
50
  end
52
51
  end
@@ -8,16 +8,17 @@ module FactoryGirl
8
8
  # Arguments:
9
9
  # * name: +Symbol+ or +String+
10
10
  # The name of the factory that should be used.
11
- # * overrides: +Hash+
12
- # Attributes to overwrite for this set.
11
+ # * traits_and_overrides: +Array+
12
+ # [+*Array+] Traits to be applied
13
+ # [+Hash+] Attributes to overwrite for this set.
13
14
  # * block:
14
15
  # Yields the hash of attributes.
15
16
  #
16
17
  # Returns: +Hash+
17
18
  # A set of attributes that can be used to build an instance of the class
18
19
  # this factory generates.
19
- def attributes_for(name, overrides = {}, &block)
20
- FactoryGirl.factory_by_name(name).run(Proxy::AttributesFor, overrides, &block)
20
+ def attributes_for(name, *traits_and_overrides, &block)
21
+ run_factory_girl_proxy(name, traits_and_overrides, Proxy::AttributesFor, &block)
21
22
  end
22
23
 
23
24
  # Generates and returns an instance from this factory. Attributes can be
@@ -26,16 +27,17 @@ module FactoryGirl
26
27
  # Arguments:
27
28
  # * name: +Symbol+ or +String+
28
29
  # The name of the factory that should be used.
29
- # * overrides: +Hash+
30
- # Attributes to overwrite for this instance.
30
+ # * traits_and_overrides: +Array+
31
+ # [+*Array+] Traits to be applied
32
+ # [+Hash+] Attributes to overwrite for this instance.
31
33
  # * block:
32
34
  # Yields the built instance.
33
35
  #
34
36
  # Returns: +Object+
35
37
  # An instance of the class this factory generates, with generated attributes
36
38
  # assigned.
37
- def build(name, overrides = {}, &block)
38
- FactoryGirl.factory_by_name(name).run(Proxy::Build, overrides, &block)
39
+ def build(name, *traits_and_overrides, &block)
40
+ run_factory_girl_proxy(name, traits_and_overrides, Proxy::Build, &block)
39
41
  end
40
42
 
41
43
  # Generates, saves, and returns an instance from this factory. Attributes can
@@ -48,16 +50,17 @@ module FactoryGirl
48
50
  # Arguments:
49
51
  # * name: +Symbol+ or +String+
50
52
  # The name of the factory that should be used.
51
- # * overrides: +Hash+
52
- # Attributes to overwrite for this instance.
53
+ # * traits_and_overrides: +Array+
54
+ # [+*Array+] Traits to be applied
55
+ # [+Hash+] Attributes to overwrite for this instance.
53
56
  # * block:
54
57
  # Yields the created instance.
55
58
  #
56
59
  # Returns: +Object+
57
60
  # A saved instance of the class this factory generates, with generated
58
61
  # attributes assigned.
59
- def create(name, overrides = {}, &block)
60
- FactoryGirl.factory_by_name(name).run(Proxy::Create, overrides, &block)
62
+ def create(name, *traits_and_overrides, &block)
63
+ run_factory_girl_proxy(name, traits_and_overrides, Proxy::Create, &block)
61
64
  end
62
65
 
63
66
  # Generates and returns an object with all attributes from this factory
@@ -67,15 +70,16 @@ module FactoryGirl
67
70
  # Arguments:
68
71
  # * name: +Symbol+ or +String+
69
72
  # The name of the factory that should be used.
70
- # * overrides: +Hash+
71
- # Attributes to overwrite for this instance.
73
+ # * traits_and_overrides: +Array+
74
+ # [+*Array+] Traits to be applied
75
+ # [+Hash+] Attributes to overwrite for this instance.
72
76
  # * block
73
77
  # Yields the stubbed object.
74
78
  #
75
79
  # Returns: +Object+
76
80
  # An object with generated attributes stubbed out.
77
- def build_stubbed(name, overrides = {}, &block)
78
- FactoryGirl.factory_by_name(name).run(Proxy::Stub, overrides, &block)
81
+ def build_stubbed(name, *traits_and_overrides, &block)
82
+ run_factory_girl_proxy(name, traits_and_overrides, Proxy::Stub, &block)
79
83
  end
80
84
 
81
85
  # Builds and returns multiple instances from this factory as an array. Attributes can be
@@ -125,6 +129,24 @@ module FactoryGirl
125
129
  def generate(name)
126
130
  FactoryGirl.sequence_by_name(name).next
127
131
  end
132
+
133
+ private
134
+
135
+ def run_factory_girl_proxy(name, traits_and_overrides, proxy, &block)
136
+ overrides = if traits_and_overrides.last.respond_to?(:has_key?)
137
+ traits_and_overrides.pop
138
+ else
139
+ {}
140
+ end
141
+
142
+ factory = FactoryGirl.factory_by_name(name)
143
+
144
+ if traits_and_overrides.any?
145
+ factory = factory.with_traits(traits_and_overrides)
146
+ end
147
+
148
+ factory.run(proxy, overrides, &block)
149
+ end
128
150
  end
129
151
  end
130
152
  end
@@ -4,34 +4,26 @@ module FactoryGirl
4
4
 
5
5
  def initialize(name, &block) #:nodoc:
6
6
  @name = name
7
- @attribute_list = AttributeList.new
7
+ @block = block
8
+ @definition = Definition.new
8
9
 
9
- proxy = FactoryGirl::DefinitionProxy.new(self)
10
- proxy.instance_eval(&block) if block_given?
10
+ proxy = FactoryGirl::DefinitionProxy.new(@definition)
11
+ proxy.instance_eval(&@block) if block_given?
11
12
  end
12
13
 
13
- def declare_attribute(declaration)
14
- @attribute_list.declare_attribute(declaration)
15
- declaration
16
- end
14
+ delegate :add_callback, :declare_attribute, :to_create, :define_trait,
15
+ :callbacks, :attributes, :to => :@definition
17
16
 
18
- def add_callback(name, &block)
19
- @attribute_list.add_callback(Callback.new(name, block))
17
+ def names
18
+ [@name]
20
19
  end
21
20
 
22
- def attributes
23
- AttributeList.new.tap do |list|
24
- @attribute_list.declarations.each do |declaration|
25
- declaration.to_attributes.each do |attribute|
26
- list.define_attribute(attribute)
27
- end
28
- end
29
- list.apply_attributes @attribute_list
30
- end
21
+ def ==(other)
22
+ name == other.name &&
23
+ block == other.block
31
24
  end
32
25
 
33
- def names
34
- [@name]
35
- end
26
+ protected
27
+ attr_reader :block
36
28
  end
37
29
  end
@@ -1,4 +1,4 @@
1
1
  module FactoryGirl
2
- VERSION = "2.2.0"
2
+ VERSION = "2.3.0"
3
3
  end
4
4
 
@@ -39,7 +39,7 @@ describe "modifying factories" do
39
39
  it "doesn't allow the factory to be subsequently defined" do
40
40
  expect do
41
41
  FactoryGirl.define { factory :user }
42
- end.to raise_error(FactoryGirl::DuplicateDefinitionError)
42
+ end.to raise_error(FactoryGirl::DuplicateDefinitionError, "Factory already registered: user")
43
43
  end
44
44
 
45
45
  it "does allow the factory to be subsequently modified" do
@@ -165,6 +165,92 @@ describe "an instance generated by a factory with multiple traits" do
165
165
 
166
166
  context "factory outside of scope" do
167
167
  subject { FactoryGirl.create(:user_without_admin_scoping) }
168
- it { expect { subject }.to raise_error(ArgumentError, "Not registered: admin_trait") }
168
+ it { expect { subject }.to raise_error(ArgumentError, "Trait not registered: admin_trait") }
169
+ end
170
+ end
171
+
172
+ describe "traits with callbacks" do
173
+ before do
174
+ define_model("User", :name => :string)
175
+
176
+ FactoryGirl.define do
177
+ factory :user do
178
+ name "John"
179
+
180
+ trait :great do
181
+ after_create {|user| user.name.upcase! }
182
+ end
183
+
184
+ factory :caps_user, :traits => [:great]
185
+
186
+ factory :caps_user_implicit_trait do
187
+ great
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ context "when the factory has a trait passed via arguments" do
194
+ subject { FactoryGirl.create(:caps_user) }
195
+ its(:name) { should == "JOHN" }
196
+ end
197
+
198
+ context "when the factory has an implicit trait" do
199
+ subject { FactoryGirl.create(:caps_user_implicit_trait) }
200
+ its(:name) { should == "JOHN" }
201
+ end
202
+ end
203
+
204
+ describe "traits added via proxy" do
205
+ before do
206
+ define_model("User", :name => :string, :admin => :boolean)
207
+
208
+ FactoryGirl.define do
209
+ factory :user do
210
+ name "John"
211
+
212
+ trait :admin do
213
+ admin true
214
+ end
215
+
216
+ trait :great do
217
+ after_create {|user| user.name.upcase! }
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+ context "adding traits in create" do
224
+ subject { FactoryGirl.create(:user, :admin, :great, :name => "Joe") }
225
+
226
+ its(:admin) { should be_true }
227
+ its(:name) { should == "JOE" }
228
+
229
+ it "doesn't modify the user factory" do
230
+ subject
231
+ FactoryGirl.create(:user).should_not be_admin
232
+ FactoryGirl.create(:user).name.should == "John"
233
+ end
234
+ end
235
+
236
+ context "adding traits in build" do
237
+ subject { FactoryGirl.build(:user, :admin, :great, :name => "Joe") }
238
+
239
+ its(:admin) { should be_true }
240
+ its(:name) { should == "Joe" }
241
+ end
242
+
243
+ context "adding traits in attributes_for" do
244
+ subject { FactoryGirl.attributes_for(:user, :admin, :great) }
245
+
246
+ its([:admin]) { should be_true }
247
+ its([:name]) { should == "John" }
248
+ end
249
+
250
+ context "adding traits in build_stubbed" do
251
+ subject { FactoryGirl.build_stubbed(:user, :admin, :great, :name => "Jack") }
252
+
253
+ its(:admin) { should be_true }
254
+ its(:name) { should == "Jack" }
169
255
  end
170
256
  end