factory_girl 3.1.1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.simplecov +4 -0
  2. data/GETTING_STARTED.md +147 -3
  3. data/Gemfile.lock +1 -1
  4. data/NEWS +8 -0
  5. data/features/support/env.rb +0 -1
  6. data/gemfiles/3.0.gemfile.lock +1 -1
  7. data/gemfiles/3.1.gemfile.lock +1 -1
  8. data/gemfiles/3.2.gemfile.lock +1 -1
  9. data/lib/factory_girl.rb +50 -7
  10. data/lib/factory_girl/attribute.rb +1 -1
  11. data/lib/factory_girl/attribute/association.rb +1 -1
  12. data/lib/factory_girl/attribute/dynamic.rb +1 -1
  13. data/lib/factory_girl/attribute/sequence.rb +1 -1
  14. data/lib/factory_girl/attribute/static.rb +1 -1
  15. data/lib/factory_girl/attribute_assigner.rb +12 -3
  16. data/lib/factory_girl/callback.rb +4 -1
  17. data/lib/factory_girl/{callback_runner.rb → callbacks_observer.rb} +1 -1
  18. data/lib/factory_girl/definition.rb +1 -1
  19. data/lib/factory_girl/definition_proxy.rb +4 -0
  20. data/lib/factory_girl/disallows_duplicates_registry.rb +17 -0
  21. data/lib/factory_girl/evaluator.rb +14 -11
  22. data/lib/factory_girl/factory.rb +5 -5
  23. data/lib/factory_girl/null_object.rb +14 -2
  24. data/lib/factory_girl/registry.rb +15 -23
  25. data/lib/factory_girl/reload.rb +2 -0
  26. data/lib/factory_girl/strategy_calculator.rb +1 -5
  27. data/lib/factory_girl/syntax.rb +1 -0
  28. data/lib/factory_girl/syntax/blueprint.rb +1 -0
  29. data/lib/factory_girl/syntax/generate.rb +6 -3
  30. data/lib/factory_girl/syntax/make.rb +4 -2
  31. data/lib/factory_girl/syntax/methods.rb +0 -81
  32. data/lib/factory_girl/syntax/sham.rb +1 -0
  33. data/lib/factory_girl/syntax/vintage.rb +0 -2
  34. data/lib/factory_girl/syntax_runner.rb +5 -0
  35. data/lib/factory_girl/version.rb +1 -1
  36. data/spec/acceptance/activesupport_instrumentation_spec.rb +49 -0
  37. data/spec/acceptance/build_stubbed_spec.rb +6 -6
  38. data/spec/acceptance/initialize_with_spec.rb +26 -0
  39. data/spec/acceptance/modify_factories_spec.rb +2 -2
  40. data/spec/acceptance/register_strategies_spec.rb +120 -0
  41. data/spec/acceptance/skip_create_spec.rb +19 -0
  42. data/spec/acceptance/syntax/blueprint_spec.rb +2 -0
  43. data/spec/acceptance/syntax/generate_spec.rb +2 -0
  44. data/spec/acceptance/syntax/make_spec.rb +2 -0
  45. data/spec/acceptance/syntax/vintage_spec.rb +2 -2
  46. data/spec/acceptance/syntax_methods_within_dynamic_attributes_spec.rb +41 -0
  47. data/spec/factory_girl/attribute/dynamic_spec.rb +6 -6
  48. data/spec/factory_girl/attribute_list_spec.rb +4 -4
  49. data/spec/factory_girl/callback_spec.rb +7 -7
  50. data/spec/factory_girl/definition_proxy_spec.rb +6 -6
  51. data/spec/factory_girl/disallows_duplicates_registry_spec.rb +44 -0
  52. data/spec/factory_girl/evaluator_class_definer_spec.rb +5 -5
  53. data/spec/factory_girl/factory_spec.rb +3 -3
  54. data/spec/factory_girl/null_object_spec.rb +18 -4
  55. data/spec/factory_girl/registry_spec.rb +30 -72
  56. data/spec/factory_girl/sequence_spec.rb +3 -2
  57. data/spec/factory_girl/strategy_calculator_spec.rb +1 -1
  58. data/spec/spec_helper.rb +0 -1
  59. metadata +41 -28
data/.simplecov ADDED
@@ -0,0 +1,4 @@
1
+ SimpleCov.start do
2
+ add_filter "/spec/"
3
+ add_filter "/tmp/"
4
+ end
data/GETTING_STARTED.md CHANGED
@@ -130,6 +130,24 @@ factory :user do
130
130
  end
131
131
  ```
132
132
 
133
+ In addition to running other methods dynamically, you can use FactoryGirl's
134
+ syntax methods (like `build`, `create`, and `generate`) within dynamic
135
+ attributes without having to prefix the call with `FactoryGirl.`. This allows
136
+ you to do:
137
+
138
+ ```ruby
139
+ sequence(:random_string) {|n| LoremIpsum.generate }
140
+
141
+ factory :post do
142
+ title { generate(:random_string) } # instead of FactoryGirl.generate(:random_string)
143
+ end
144
+
145
+ factory :comment do
146
+ post
147
+ body { generate(:random_string) } # instead of FactoryGirl.generate(:random_string)
148
+ end
149
+ ```
150
+
133
151
  Aliases
134
152
  -------
135
153
 
@@ -372,7 +390,7 @@ Or in lazy attributes:
372
390
 
373
391
  ```ruby
374
392
  factory :invite do
375
- invitee { FactoryGirl.generate(:email) }
393
+ invitee { generate(:email) }
376
394
  end
377
395
  ```
378
396
 
@@ -684,7 +702,7 @@ Custom Construction
684
702
  If you want to use factory_girl to construct an object where some attributes
685
703
  are passed to `initialize` or if you want to do something other than simply
686
704
  calling `new` on your build class, you can override the default behavior by
687
- defining `to_initialize` on your factory. Example:
705
+ defining `initialize_with` on your factory. Example:
688
706
 
689
707
  ```ruby
690
708
  # user.rb
@@ -705,7 +723,7 @@ factory :user do
705
723
  end
706
724
 
707
725
  email
708
- initialize_with { User.new(name) }
726
+ initialize_with { new(name) }
709
727
  end
710
728
 
711
729
  FactoryGirl.build(:user).name # Bob Hope
@@ -727,6 +745,132 @@ You can override the initializer in order to:
727
745
  * Use a method other than `new` to instantiate the instance
728
746
  * Do crazy things like decorate the instance after it's built
729
747
 
748
+ When using `initialize_with`, you don't have to declare the class itself when
749
+ calling `new`; however, any other class methods you want to call will have to
750
+ be called on the class explicitly.
751
+
752
+ For example:
753
+
754
+ ```ruby
755
+ factory :user do
756
+ ignore do
757
+ name { Faker::Name.name }
758
+ end
759
+
760
+ initialize_with { User.build_with_name(name) }
761
+ end
762
+ ```
763
+
764
+ Custom Strategies
765
+ -----------------
766
+
767
+ There are times where you may want to extend behavior of factory\_girl by
768
+ adding a custom build strategy.
769
+
770
+ Strategies define two methods: `association` and `result`. `association`
771
+ receives a `FactoryGirl::FactoryRunner` instance, upon which you can call
772
+ `run`, overriding the strategy if you want. The second method, `result`,
773
+ receives a `FactoryGirl::Evaluation` instance. It provides a way to trigger
774
+ callbacks (with `notify`), `object` or `hash` (to get the result instance or a
775
+ hash based on the attributes defined in the factory), and `create`, which
776
+ executes the `to_create` callback defined on the factory.
777
+
778
+ To understand how factory\_girl uses strategies internally, it's probably
779
+ easiest to just view the source for each of the four default strategies.
780
+
781
+ Inheritance can occasionally be useful; here's an example of inheriting from
782
+ `FactoryGirl::Strategy::Create` to build a JSON representation of your model.
783
+
784
+ ```ruby
785
+ class JsonStrategy
786
+ def initialize
787
+ @strategy = FactoryGirl.strategy_by_name(:create).new
788
+ end
789
+
790
+ delegate :association, to: :@strategy
791
+
792
+ def result(evaluation)
793
+ @strategy.result(evaluation).to_json
794
+ end
795
+ end
796
+ ```
797
+
798
+ For factory\_girl to recognize the new strategy, you can register it:
799
+
800
+ ```ruby
801
+ FactoryGirl.register_strategy(:json, JsonStrategy)
802
+ ```
803
+
804
+ This allows you to call
805
+
806
+ ```ruby
807
+ FactoryGirl.json(:user)
808
+ ```
809
+
810
+ Finally, you can override factory\_girl's own strategies if you'd like by
811
+ registering a new object in place of the strategies.
812
+
813
+ Custom Methods to Persist Objects
814
+ ---------------------------------
815
+
816
+ By default, creating a record will call `save!` on the instance; since this
817
+ may not always be ideal, you can override that behavior by defining
818
+ `to_create` on the factory:
819
+
820
+ ```ruby
821
+ factory :different_orm_model do
822
+ to_create {|instance| instance.persist! }
823
+ end
824
+ ```
825
+
826
+ To disable the persistence method altogether on create, you can `skip_create`
827
+ for that factory:
828
+
829
+ ```ruby
830
+ factory :user_without_database do
831
+ skip_create
832
+ end
833
+ ```
834
+
835
+ ActiveSupport Instrumentation
836
+ -----------------------------
837
+
838
+ In order to track what factories are created (and with what build strategy),
839
+ `ActiveSupport::Notifications` are included to provide a way to subscribe to
840
+ factories being run. One example would be to track factories based on a
841
+ threshold of execution time.
842
+
843
+ ```ruby
844
+ ActiveSupport::Notifications.subscribe("factory_girl.run_factory") do |name, start, finish, id, payload|
845
+ execution_time_in_seconds = finish - start
846
+
847
+ if execution_time_in_seconds >= 0.5
848
+ $stderr.puts "Slow factory: #{payload[:name]} using strategy #{payload[:strategy]}"
849
+ end
850
+ end
851
+ ```
852
+
853
+ Another example would be tracking all factories and how they're used
854
+ throughout your test suite. If you're using RSpec, it's as simple as adding a
855
+ `before(:suite)` and `after(:suite)`:
856
+
857
+ ```ruby
858
+ config.before(:suite) do
859
+ @factory_girl_results = {}
860
+ ActiveSupport::Notifications.subscribe("factory_girl.run_factory") do |name, start, finish, id, payload|
861
+ factory_name = payload[:name]
862
+ strategy_name = payload[:strategy]
863
+ @factory_girl_results[factory_name] ||= {}
864
+ @factory_girl_results[factory_name][strategy_name] ||= 0
865
+ @factory_girl_results[factory_name][strategy_name] += 1
866
+ end
867
+ end
868
+
869
+ config.after(:suite) do
870
+ puts @factory_girl_results
871
+ end
872
+ ```
873
+
730
874
  Cucumber Integration
731
875
  --------------------
732
876
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- factory_girl (3.1.1)
4
+ factory_girl (3.2.0)
5
5
  activesupport (>= 3.0.0)
6
6
 
7
7
  GEM
data/NEWS CHANGED
@@ -1,3 +1,11 @@
1
+ 3.2.0 (April 24, 2012)
2
+ Use AS::Notifications for pub/sub to track running factories
3
+ Call new within initialize_with implicitly on the build class
4
+ Skip to_create with skip_create
5
+ Allow registration of custom strategies
6
+ Deprecate alternate syntaxes
7
+ Implicitly call factory_girl's syntax methods from dynamic attributes
8
+
1
9
  3.1.0 (April 6, 2012)
2
10
  Sequences support aliases, which reference the same block
3
11
  Update documentation
@@ -1,7 +1,6 @@
1
1
  PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
2
2
 
3
3
  require "simplecov"
4
- SimpleCov.start
5
4
 
6
5
  $: << File.join(PROJECT_ROOT, 'lib')
7
6
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: /Users/joshuaclayton/dev/gems/factory_girl
3
3
  specs:
4
- factory_girl (3.1.1)
4
+ factory_girl (3.2.0)
5
5
  activesupport (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: /Users/joshuaclayton/dev/gems/factory_girl
3
3
  specs:
4
- factory_girl (3.1.1)
4
+ factory_girl (3.2.0)
5
5
  activesupport (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: /Users/joshuaclayton/dev/gems/factory_girl
3
3
  specs:
4
- factory_girl (3.1.1)
4
+ factory_girl (3.2.0)
5
5
  activesupport (>= 3.0.0)
6
6
 
7
7
  GEM
data/lib/factory_girl.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "active_support/core_ext/module/delegation"
2
+ require "active_support/notifications"
2
3
 
3
4
  require 'factory_girl/errors'
4
5
  require 'factory_girl/factory_runner'
@@ -8,6 +9,7 @@ require "factory_girl/strategy/create"
8
9
  require "factory_girl/strategy/attributes_for"
9
10
  require "factory_girl/strategy/stub"
10
11
  require "factory_girl/strategy/null"
12
+ require 'factory_girl/disallows_duplicates_registry'
11
13
  require 'factory_girl/registry'
12
14
  require 'factory_girl/null_factory'
13
15
  require 'factory_girl/null_object'
@@ -18,7 +20,7 @@ require 'factory_girl/evaluator'
18
20
  require 'factory_girl/evaluator_class_definer'
19
21
  require 'factory_girl/attribute'
20
22
  require 'factory_girl/callback'
21
- require 'factory_girl/callback_runner'
23
+ require 'factory_girl/callbacks_observer'
22
24
  require 'factory_girl/declaration_list'
23
25
  require 'factory_girl/declaration'
24
26
  require 'factory_girl/sequence'
@@ -28,17 +30,21 @@ require 'factory_girl/aliases'
28
30
  require 'factory_girl/definition'
29
31
  require 'factory_girl/definition_proxy'
30
32
  require 'factory_girl/syntax'
33
+ require 'factory_girl/syntax_runner'
31
34
  require 'factory_girl/find_definitions'
32
35
  require 'factory_girl/reload'
33
36
  require 'factory_girl/version'
34
37
 
35
38
  module FactoryGirl
36
39
  def self.factories
37
- @factories ||= Registry.new("Factory")
40
+ @factories ||= DisallowsDuplicatesRegistry.new(Registry.new("Factory"))
38
41
  end
39
42
 
40
43
  def self.register_factory(factory)
41
- factories.add(factory)
44
+ factory.names.each do |name|
45
+ factories.register(name, factory)
46
+ end
47
+ factory
42
48
  end
43
49
 
44
50
  def self.factory_by_name(name)
@@ -46,11 +52,14 @@ module FactoryGirl
46
52
  end
47
53
 
48
54
  def self.sequences
49
- @sequences ||= Registry.new("Sequence")
55
+ @sequences ||= DisallowsDuplicatesRegistry.new(Registry.new("Sequence"))
50
56
  end
51
57
 
52
58
  def self.register_sequence(sequence)
53
- sequences.add(sequence)
59
+ sequence.names.each do |name|
60
+ sequences.register(name, sequence)
61
+ end
62
+ sequence
54
63
  end
55
64
 
56
65
  def self.sequence_by_name(name)
@@ -58,18 +67,52 @@ module FactoryGirl
58
67
  end
59
68
 
60
69
  def self.traits
61
- @traits ||= Registry.new("Trait")
70
+ @traits ||= DisallowsDuplicatesRegistry.new(Registry.new("Trait"))
62
71
  end
63
72
 
64
73
  def self.register_trait(trait)
65
- traits.add(trait)
74
+ trait.names.each do |name|
75
+ traits.register(name, trait)
76
+ end
77
+ trait
66
78
  end
67
79
 
68
80
  def self.trait_by_name(name)
69
81
  traits.find(name)
70
82
  end
71
83
 
84
+ def self.strategies
85
+ @strategies ||= Registry.new("Strategy")
86
+ end
87
+
88
+ def self.register_strategy(strategy_name, strategy_class)
89
+ strategies.register(strategy_name, strategy_class)
90
+
91
+ FactoryGirl::Syntax::Methods.module_exec do
92
+ define_method(strategy_name) do |name, *traits_and_overrides, &block|
93
+ instrumentation_payload = { name: name, strategy: strategy_name }
94
+
95
+ ActiveSupport::Notifications.instrument("factory_girl.run_factory", instrumentation_payload) do
96
+ FactoryRunner.new(name, strategy_class, traits_and_overrides).run(&block)
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ def self.strategy_by_name(name)
103
+ strategies.find(name)
104
+ end
105
+
106
+ def self.register_default_strategies
107
+ FactoryGirl.register_strategy(:build, FactoryGirl::Strategy::Build)
108
+ FactoryGirl.register_strategy(:create, FactoryGirl::Strategy::Create)
109
+ FactoryGirl.register_strategy(:attributes_for, FactoryGirl::Strategy::AttributesFor)
110
+ FactoryGirl.register_strategy(:build_stubbed, FactoryGirl::Strategy::Stub)
111
+ end
112
+
72
113
  def self.callback_names
73
114
  [:after_build, :after_create, :after_stub, :before_create].freeze
74
115
  end
75
116
  end
117
+
118
+ FactoryGirl.register_default_strategies
@@ -15,7 +15,7 @@ module FactoryGirl
15
15
  end
16
16
 
17
17
  def to_proc
18
- lambda { }
18
+ -> { }
19
19
  end
20
20
 
21
21
  def association?
@@ -12,7 +12,7 @@ module FactoryGirl
12
12
  def to_proc
13
13
  factory = @factory
14
14
  overrides = @overrides
15
- lambda { association(factory, overrides) }
15
+ -> { association(factory, overrides) }
16
16
  end
17
17
 
18
18
  def association?
@@ -9,7 +9,7 @@ module FactoryGirl
9
9
  def to_proc
10
10
  block = @block
11
11
 
12
- lambda {
12
+ -> {
13
13
  value = block.arity == 1 ? block.call(self) : instance_exec(&block)
14
14
  raise SequenceAbuseError if FactoryGirl::Sequence === value
15
15
  value
@@ -9,7 +9,7 @@ module FactoryGirl
9
9
 
10
10
  def to_proc
11
11
  sequence = @sequence
12
- lambda { FactoryGirl.generate(sequence) }
12
+ -> { FactoryGirl.generate(sequence) }
13
13
  end
14
14
  end
15
15
 
@@ -8,7 +8,7 @@ module FactoryGirl
8
8
 
9
9
  def to_proc
10
10
  value = @value
11
- lambda { value }
11
+ -> { value }
12
12
  end
13
13
  end
14
14
  end
@@ -1,6 +1,7 @@
1
1
  module FactoryGirl
2
2
  class AttributeAssigner
3
- def initialize(evaluator, &instance_builder)
3
+ def initialize(evaluator, build_class, &instance_builder)
4
+ @build_class = build_class
4
5
  @instance_builder = instance_builder
5
6
  @evaluator = evaluator
6
7
  @attribute_list = evaluator.class.attribute_list
@@ -18,7 +19,7 @@ module FactoryGirl
18
19
  end
19
20
 
20
21
  def hash
21
- @evaluator.instance = NullObject.new
22
+ @evaluator.instance = build_hash
22
23
 
23
24
  attributes_to_set_on_hash.inject({}) do |result, attribute|
24
25
  result[attribute] = get(attribute)
@@ -32,6 +33,10 @@ module FactoryGirl
32
33
  @build_class_instance ||= @evaluator.instance_exec(&@instance_builder)
33
34
  end
34
35
 
36
+ def build_hash
37
+ @build_hash ||= NullObject.new(hash_instance_methods_to_respond_to)
38
+ end
39
+
35
40
  def get(attribute_name)
36
41
  @evaluator.send(attribute_name)
37
42
  end
@@ -61,7 +66,11 @@ module FactoryGirl
61
66
  end
62
67
 
63
68
  def override_names
64
- @evaluator.__overrides.keys
69
+ @evaluator.__override_names__
70
+ end
71
+
72
+ def hash_instance_methods_to_respond_to
73
+ @attribute_list.map(&:name) + override_names + @build_class.instance_methods
65
74
  end
66
75
 
67
76
  def alias_names_to_ignore