factory_girl 2.2.0 → 2.3.0

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