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
@@ -6,6 +6,15 @@ module FactoryGirl
6
6
  @block = block
7
7
  end
8
8
 
9
+ def ==(other)
10
+ name == other.name &&
11
+ ignored == other.ignored &&
12
+ block == other.block
13
+ end
14
+
15
+ protected
16
+ attr_reader :block
17
+
9
18
  private
10
19
 
11
20
  def build
@@ -6,6 +6,15 @@ module FactoryGirl
6
6
  @factory = factory
7
7
  end
8
8
 
9
+ def ==(other)
10
+ name == other.name &&
11
+ factory == other.factory &&
12
+ ignored == other.ignored
13
+ end
14
+
15
+ protected
16
+ attr_reader :factory
17
+
9
18
  private
10
19
 
11
20
  def build
@@ -14,8 +23,8 @@ module FactoryGirl
14
23
  elsif FactoryGirl.sequences.registered?(name)
15
24
  [Attribute::Sequence.new(name, name, @ignored)]
16
25
  else
17
- trait_root = @factory || FactoryGirl
18
- trait_root.trait_by_name(name).attributes.to_a
26
+ @factory.inherit_traits([name])
27
+ []
19
28
  end
20
29
  end
21
30
  end
@@ -6,6 +6,15 @@ module FactoryGirl
6
6
  @value = value
7
7
  end
8
8
 
9
+ def ==(other)
10
+ name == other.name &&
11
+ value == other.value &&
12
+ ignored == other.ignored
13
+ end
14
+
15
+ protected
16
+ attr_reader :value
17
+
9
18
  private
10
19
 
11
20
  def build
@@ -0,0 +1,48 @@
1
+ module FactoryGirl
2
+ class DeclarationList
3
+ include Enumerable
4
+
5
+ def initialize(name = nil)
6
+ @declarations = []
7
+ @name = name
8
+ @overridable = false
9
+ end
10
+
11
+ def declare_attribute(declaration)
12
+ delete_declaration(declaration) if overridable?
13
+
14
+ @declarations << declaration
15
+ declaration
16
+ end
17
+
18
+ def overridable
19
+ @overridable = true
20
+ end
21
+
22
+ def attribute_list
23
+ AttributeList.new(@name).tap do |list|
24
+ to_attributes.each do |attribute|
25
+ list.define_attribute(attribute)
26
+ end
27
+ end
28
+ end
29
+
30
+ def each(&block)
31
+ @declarations.each(&block)
32
+ end
33
+
34
+ private
35
+
36
+ def delete_declaration(declaration)
37
+ @declarations.delete_if {|decl| decl.name == declaration.name }
38
+ end
39
+
40
+ def to_attributes
41
+ @declarations.inject([]) {|result, declaration| result += declaration.to_attributes }
42
+ end
43
+
44
+ def overridable?
45
+ @overridable
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,62 @@
1
+ module FactoryGirl
2
+ class Definition
3
+ attr_reader :callbacks, :defined_traits, :declarations
4
+
5
+ def initialize(name = nil)
6
+ @declarations = DeclarationList.new(name)
7
+ @callbacks = []
8
+ @defined_traits = []
9
+ @to_create = nil
10
+ @traits = []
11
+ end
12
+
13
+ delegate :declare_attribute, :to => :declarations
14
+
15
+ def attributes
16
+ @attributes ||= declarations.attribute_list
17
+ end
18
+
19
+ def compile
20
+ attributes
21
+ end
22
+
23
+ def overridable
24
+ declarations.overridable
25
+ self
26
+ end
27
+
28
+ def traits
29
+ @traits.reverse.map { |name| trait_by_name(name) }
30
+ end
31
+
32
+ def inherit_traits(new_traits)
33
+ @traits += new_traits
34
+ end
35
+
36
+ def add_callback(callback)
37
+ @callbacks << callback
38
+ end
39
+
40
+ def to_create(&block)
41
+ if block_given?
42
+ @to_create = block
43
+ else
44
+ @to_create
45
+ end
46
+ end
47
+
48
+ def define_trait(trait)
49
+ @defined_traits << trait
50
+ end
51
+
52
+ private
53
+
54
+ def trait_by_name(name)
55
+ trait_for(name) || FactoryGirl.trait_by_name(name)
56
+ end
57
+
58
+ def trait_for(name)
59
+ defined_traits.detect {|trait| trait.name == name }
60
+ end
61
+ end
62
+ end
@@ -8,8 +8,8 @@ module FactoryGirl
8
8
 
9
9
  attr_reader :child_factories
10
10
 
11
- def initialize(factory, ignore = false)
12
- @factory = factory
11
+ def initialize(definition, ignore = false)
12
+ @definition = definition
13
13
  @ignore = ignore
14
14
  @child_factories = []
15
15
  end
@@ -41,11 +41,11 @@ module FactoryGirl
41
41
  Declaration::Static.new(name, value, @ignore)
42
42
  end
43
43
 
44
- @factory.declare_attribute(declaration)
44
+ @definition.declare_attribute(declaration)
45
45
  end
46
46
 
47
47
  def ignore(&block)
48
- proxy = DefinitionProxy.new(@factory, true)
48
+ proxy = DefinitionProxy.new(@definition, true)
49
49
  proxy.instance_eval(&block)
50
50
  end
51
51
 
@@ -82,7 +82,7 @@ module FactoryGirl
82
82
  # are equivalent.
83
83
  def method_missing(name, *args, &block)
84
84
  if args.empty? && block.nil?
85
- @factory.declare_attribute(Declaration::Implicit.new(name, @factory, @ignore))
85
+ @definition.declare_attribute(Declaration::Implicit.new(name, @definition, @ignore))
86
86
  elsif args.first.is_a?(Hash) && args.first.has_key?(:factory)
87
87
  association(name, *args)
88
88
  else
@@ -135,23 +135,23 @@ module FactoryGirl
135
135
  # name of the factory. For example, a "user" association will by
136
136
  # default use the "user" factory.
137
137
  def association(name, options = {})
138
- @factory.declare_attribute(Declaration::Association.new(name, options))
138
+ @definition.declare_attribute(Declaration::Association.new(name, options))
139
139
  end
140
140
 
141
141
  def after_build(&block)
142
- @factory.add_callback(:after_build, &block)
142
+ @definition.add_callback(Callback.new(:after_build, block))
143
143
  end
144
144
 
145
145
  def after_create(&block)
146
- @factory.add_callback(:after_create, &block)
146
+ @definition.add_callback(Callback.new(:after_create, block))
147
147
  end
148
148
 
149
149
  def after_stub(&block)
150
- @factory.add_callback(:after_stub, &block)
150
+ @definition.add_callback(Callback.new(:after_stub, block))
151
151
  end
152
152
 
153
153
  def to_create(&block)
154
- @factory.to_create(&block)
154
+ @definition.to_create(&block)
155
155
  end
156
156
 
157
157
  def factory(name, options = {}, &block)
@@ -159,7 +159,7 @@ module FactoryGirl
159
159
  end
160
160
 
161
161
  def trait(name, &block)
162
- @factory.define_trait(Trait.new(name, &block))
162
+ @definition.define_trait(Trait.new(name, &block))
163
163
  end
164
164
  end
165
165
  end
@@ -3,76 +3,46 @@ require "active_support/inflector"
3
3
 
4
4
  module FactoryGirl
5
5
  class Factory
6
- attr_reader :name #:nodoc:
6
+ attr_reader :name, :definition #:nodoc:
7
7
 
8
8
  def initialize(name, options = {}) #:nodoc:
9
9
  assert_valid_options(options)
10
- @name = name.to_s.underscore.to_sym
10
+ @name = name.is_a?(Symbol) ? name : name.to_s.underscore.to_sym
11
11
  @parent = options[:parent]
12
12
  @aliases = options[:aliases] || []
13
- @traits = options[:traits] || []
14
13
  @class_name = options[:class]
15
14
  @default_strategy = options[:default_strategy]
16
- @defined_traits = []
17
- @attribute_list = AttributeList.new
18
- @compiled = false
15
+ @definition = Definition.new(@name)
16
+
17
+ inherit_traits(options[:traits] || [])
19
18
  end
20
19
 
20
+ delegate :add_callback, :declare_attribute, :to_create, :define_trait,
21
+ :defined_traits, :traits, :inherit_traits, :to => :@definition
22
+
21
23
  def factory_name
22
24
  $stderr.puts "DEPRECATION WARNING: factory.factory_name is deprecated; use factory.name instead."
23
25
  name
24
26
  end
25
27
 
26
28
  def build_class #:nodoc:
27
- @build_class ||= class_name.to_s.camelize.constantize
29
+ class_name.to_s.camelize.constantize
28
30
  end
29
31
 
30
32
  def default_strategy #:nodoc:
31
- @default_strategy || (parent && parent.default_strategy) || :create
32
- end
33
-
34
- def allow_overrides
35
- @compiled = false
36
- @attribute_list.overridable
37
- self
38
- end
39
-
40
- def allow_overrides?
41
- @attribute_list.overridable?
42
- end
43
-
44
- def define_trait(trait)
45
- @defined_traits << trait
46
- end
47
-
48
- def add_callback(name, &block)
49
- @attribute_list.add_callback(Callback.new(name, block))
33
+ @default_strategy || parent.default_strategy || :create
50
34
  end
51
35
 
52
36
  def run(proxy_class, overrides, &block) #:nodoc:
53
- ensure_compiled
54
- proxy = proxy_class.new(build_class)
55
- callbacks.each { |callback| proxy.add_callback(callback) }
56
- overrides = overrides.symbolize_keys
57
-
58
- attributes.each do |attribute|
59
- factory_overrides = overrides.select { |attr, val| attribute.aliases_for?(attr) }
60
- if factory_overrides.empty?
61
- attribute.add_to(proxy)
62
- else
63
- factory_overrides.each do |attr, val|
64
- if attribute.ignored
65
- proxy.set_ignored(attr, val)
66
- else
67
- proxy.set(attr, val)
68
- end
69
-
70
- overrides.delete(attr)
71
- end
72
- end
73
- end
74
- overrides.each { |attr, val| proxy.set(attr, val) }
75
- result = proxy.result(@to_create_block)
37
+ runner_options = {
38
+ :attributes => attributes,
39
+ :callbacks => callbacks,
40
+ :to_create => to_create,
41
+ :build_class => build_class,
42
+ :proxy_class => proxy_class
43
+ }
44
+
45
+ result = Runner.new(runner_options).run(overrides)
76
46
 
77
47
  block ? block.call(result) : result
78
48
  end
@@ -85,16 +55,6 @@ module FactoryGirl
85
55
  attributes.select {|attribute| attribute.association? }
86
56
  end
87
57
 
88
- def trait_by_name(name)
89
- if existing_attribute = trait_for(name)
90
- existing_attribute
91
- elsif parent
92
- parent.trait_by_name(name)
93
- else
94
- FactoryGirl.trait_by_name(name)
95
- end
96
- end
97
-
98
58
  # Names for this factory, including aliases.
99
59
  #
100
60
  # Example:
@@ -124,94 +84,123 @@ module FactoryGirl
124
84
  [name] + @aliases
125
85
  end
126
86
 
127
- def to_create(&block)
128
- @to_create_block = block
129
- end
130
-
131
- def ensure_compiled
132
- compile unless @compiled
87
+ def compile
88
+ parent.defined_traits.each {|trait| define_trait(trait) }
89
+ parent.compile
90
+ @definition.compile
133
91
  end
134
92
 
135
- def declare_attribute(declaration)
136
- @attribute_list.declare_attribute(declaration)
93
+ def with_traits(traits)
94
+ self.clone.tap do |factory_with_traits|
95
+ factory_with_traits.inherit_traits traits
96
+ end
137
97
  end
138
98
 
139
99
  protected
140
100
 
141
101
  def class_name #:nodoc:
142
- @class_name || (parent && parent.class_name) || name
102
+ @class_name || parent.class_name || name
143
103
  end
144
104
 
145
105
  def attributes
146
- ensure_compiled
147
- AttributeList.new.tap do |list|
148
- @traits.reverse.map { |name| trait_by_name(name) }.each do |trait|
106
+ compile
107
+ AttributeList.new(@name).tap do |list|
108
+ traits.each do |trait|
149
109
  list.apply_attributes(trait.attributes)
150
110
  end
151
111
 
152
- list.apply_attributes(@attribute_list)
153
- list.apply_attributes(parent.attributes) if parent
112
+ list.apply_attributes(@definition.attributes)
113
+ list.apply_attributes(parent.attributes)
154
114
  end
155
115
  end
156
116
 
157
- private
158
-
159
117
  def callbacks
160
- attributes.callbacks
118
+ [parent.callbacks, traits.map(&:callbacks), @definition.callbacks].flatten
161
119
  end
162
120
 
163
- def compile
164
- inherit_factory(parent) if parent
121
+ private
165
122
 
166
- declarations.each do |declaration|
167
- declaration.to_attributes.each do |attribute|
168
- define_attribute(attribute)
169
- end
170
- end
123
+ def assert_valid_options(options)
124
+ options.assert_valid_keys(:class, :parent, :default_strategy, :aliases, :traits)
171
125
 
172
- @compiled = true
126
+ if options[:default_strategy]
127
+ Proxy.ensure_strategy_exists!(options[:default_strategy])
128
+ $stderr.puts "DEPRECATION WARNING: default_strategy is deprecated."
129
+ $stderr.puts "Override to_create if you need to prevent a call to #save!."
130
+ end
173
131
  end
174
132
 
175
- def inherit_factory(parent) #:nodoc:
176
- parent.ensure_compiled
177
- allow_overrides if parent.allow_overrides?
133
+ def parent
134
+ if @parent
135
+ FactoryGirl.factory_by_name(@parent)
136
+ else
137
+ NullFactory.new
138
+ end
178
139
  end
179
140
 
180
- def declarations
181
- @attribute_list.declarations
141
+ def initialize_copy(source)
142
+ super
143
+ @definition = @definition.clone
182
144
  end
183
145
 
184
- def define_attribute(attribute)
185
- if attribute.respond_to?(:factory) && attribute.factory == self.name
186
- raise AssociationDefinitionError, "Self-referencing association '#{attribute.name}' in factory '#{self.name}'"
146
+ class Runner
147
+ def initialize(options = {})
148
+ @attributes = options[:attributes]
149
+ @callbacks = options[:callbacks]
150
+ @to_create = options[:to_create]
151
+ @build_class = options[:build_class]
152
+ @proxy_class = options[:proxy_class]
153
+
154
+ @overrides = {}
187
155
  end
188
156
 
189
- @attribute_list.define_attribute(attribute)
190
- end
157
+ def run(overrides = {})
158
+ @overrides = overrides.symbolize_keys
191
159
 
192
- def assert_valid_options(options)
193
- options.assert_valid_keys(:class, :parent, :default_strategy, :aliases, :traits)
160
+ apply_attributes
161
+ apply_remaining_overrides
194
162
 
195
- if options[:default_strategy]
196
- assert_valid_strategy(options[:default_strategy])
197
- $stderr.puts "DEPRECATION WARNING: default_strategy is deprecated."
198
- $stderr.puts "Override to_create if you need to prevent a call to #save!."
163
+ proxy.result(@to_create)
199
164
  end
200
- end
201
165
 
202
- def assert_valid_strategy(strategy)
203
- unless Proxy.const_defined? strategy.to_s.camelize
204
- raise ArgumentError, "Unknown strategy: #{strategy}"
166
+ private
167
+
168
+ def apply_attributes
169
+ @attributes.each do |attribute|
170
+ if overrides_for_attribute(attribute).any?
171
+ handle_attribute_with_overrides(attribute)
172
+ else
173
+ handle_attribute_without_overrides(attribute)
174
+ end
175
+ end
205
176
  end
206
- end
207
177
 
208
- def trait_for(name)
209
- @defined_traits.detect {|trait| trait.name == name }
210
- end
178
+ def apply_remaining_overrides
179
+ @overrides.each { |attr, val| add_static_attribute(attr, val) }
180
+ end
211
181
 
212
- def parent
213
- return unless @parent
214
- FactoryGirl.factory_by_name(@parent)
182
+ def overrides_for_attribute(attribute)
183
+ @overrides.select { |attr, val| attribute.aliases_for?(attr) }
184
+ end
185
+
186
+ def handle_attribute_with_overrides(attribute)
187
+ overrides_for_attribute(attribute).each do |attr, val|
188
+ add_static_attribute(attr, val, attribute.ignored)
189
+ @overrides.delete(attr)
190
+ end
191
+ end
192
+
193
+ def add_static_attribute(attr, val, ignored = false)
194
+ Attribute::Static.new(attr, val, ignored).add_to(proxy)
195
+ end
196
+
197
+ def handle_attribute_without_overrides(attribute)
198
+ attribute.add_to(proxy)
199
+ end
200
+
201
+ def proxy
202
+ @proxy ||= @proxy_class.new(@build_class, @callbacks)
203
+ end
215
204
  end
216
205
  end
217
206
  end