stepford 0.8.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,24 +1,30 @@
1
1
  Stepford
2
2
  =====
3
3
 
4
- Stepford is a wrapper and generator for [FactoryGirl][factory_girl] and [FactoryGirl Cache][factory_girl-cache] to automate stuff and make it easier to use.
4
+ Stepford is an automatic required (non-null or presence validated) association resolving (creating/building/stubbing) wrapper and/or factory generator for [FactoryGirl][factory_girl].
5
5
 
6
- `Stepford::FactoryGirl` (and there is a short rspec helper) automatically recursively creates/builds/stubbed factories for null=false and/or presence validated associations. It also lets you specify method name and arguments/options to FactoryGirl for associations.
6
+ For example, with the rspec helper:
7
7
 
8
- e.g. if the following is required:
9
- * Bar has a required association called house_special which uses the :beer factory, and we have a block we want to send into it
10
- * Beer has specials that you want to build as a list of 3 using the :tuesday_special_offer factory
8
+ create(:foo)
9
+
10
+ Would take a :foo FactoryGirl factory that has no associations defined in it (only required attributes) and will travel the dependency tree of the models to create and create_list as needed. Or, you can build the same way:
11
+
12
+ build(:foo)
13
+
14
+ Need to just customize a few things? You can use the normal FactoryGirl behavior (args, options, block) but also specify options for each factory.
11
15
 
12
- you could set that up like this:
16
+ e.g. maybe Bar has a required association called house_special which uses the :beer factory, and we have a block we want to send into it; oh, and Beer has specials that you want to build as a list of 3 using the :tuesday_special_offer factory. Just set it up like:
13
17
 
14
18
  Stepford::FactoryGirl.create_list(:bar, with_factory_options: {
15
19
  house_special: [:create, :beer, {blk: ->(beer) do; beer.bubbles.create(attributes_for(:bubbles)); end}],
16
20
  specials: [:build_list, :tuesday_special_offer, 3]
17
21
  }) do
18
- # any block you would send to FactoryGirl.create_list(:foo) would go here
22
+ # any block you would send to FactoryGirl.create_list(:bar) would go here
19
23
  end
20
24
 
21
- The Stepford CLI command will generate your factories.rb or multiple factory files for you.
25
+ What if you have an existing schema, and you want to use FactoryGirl, but don't have any factories yet?
26
+
27
+ The Stepford CLI allows you to generate your factories.rb or multiple factory files for you.
22
28
 
23
29
  e.g. maybe one of your models is called post, then you could generate a factory for post and all of the other models with a one-liner, maybe with the following in the `some/path/factories/post.rb` file:
24
30
 
@@ -58,13 +64,17 @@ Then run:
58
64
 
59
65
  ### Usage
60
66
 
61
- #### Factory Girl Wrapper
67
+ #### Require
68
+
69
+ Put this in your `test/spec_helper.rb`, `spec/spec_helper.rb`, or some other file used by your tests:
70
+
71
+ require 'stepford/factory_girl'
62
72
 
63
- ##### Standard
73
+ #### Stepford::FactoryGirl
64
74
 
65
75
  Stepford::FactoryGirl acts just like FactoryGirl, but it goes through all the null=false associations in the factory and/or its presence validated associations and attempts to create/build/build_stub depending on what you called originally, but also lets you pass in an `:with_factory_options` that can contain a hash of factory name symbols to the arguments and block you'd pass to it. You specify the block using a `:blk` option with a proc/lambda (probably a lambda) to use in that method.
66
76
 
67
- If you don't specify options, it's easy. If Foo requires Bar and Bar requires a list of Foobars and a Barfoo, and you have factories for each of those, you'd only have to do:
77
+ If you don't specify options, it's easy (note: it is even easier with the rspec helper- see below). If Foo requires Bar and Bar requires a list of Foobars and a Barfoo, and you have factories for each of those, you'd only have to do:
68
78
 
69
79
  Stepford::FactoryGirl.create_list(:foo, 5)
70
80
 
@@ -79,45 +89,45 @@ But, you might want to specify traits, and certain attributes or associations or
79
89
  # any block you would send to FactoryGirl.create_list(:foo) would go here
80
90
  end
81
91
 
82
- ##### Cached
83
-
84
- In your Gemfile:
85
-
86
- require 'factory_girl-cache'
87
-
88
- Then:
89
-
90
- bundle install
91
-
92
- In your `spec/spec_helper.rb`, add:
93
-
94
- require 'stepford/factory_girl_cache'
95
-
96
- It works just about the same as `Stepford::FactoryGirl` except it is called `Stepford::FactoryGirlCache` and acts more like `FactoryGirlCache`.
97
-
98
92
  ##### RSpec Helpers
99
93
 
100
94
  Put this in your `spec/spec_helper.rb`:
101
95
 
102
96
  require 'stepford/factory_girl_rspec_helpers'
103
97
 
104
- Then you can just use `create`, `create_list`, `build`, `build_list`, or `build_stubbed` in your rspec tests (`create` becomes a shortcut for `::Stepford::FactoryGirl.create`, etc.):
98
+ Then you can just use `create`, `create_list`, `build`, `build_list`, or `build_stubbed` in your rspec tests (`create` becomes a shortcut for `::Stepford::FactoryGirl.create`, etc.), e.g.:
105
99
 
106
100
  create(:foo)
107
101
 
108
- For the cached version, add this to `spec/spec_helper.rb`:
102
+ ##### Stopping Circular References
109
103
 
110
- require 'stepford/factory_girl_cache_rspec_helpers'
104
+ If you have a circular reference (A has NOT NULL foreign key to B that has NOT NULL foreign key to C that has NOT NULL foreign key to A) in the
105
+ schema, there is a workaround. First, prepopulate one of the models involved in the interdependency chain in the database as part of test setup,
106
+ or if the ids are NOT NULL but are not foreign key constrained (i.e. if you can enter an invalid ID into the foreign key column, which implies possible
107
+ referential integrity issues) then you may be able to set them with an invalid id. Take that foreign id and then use the following to ensure
108
+ that it will set that foreign id or instance. This is done at a global level which may not work for you, but it makes it convenient to put into
109
+ your spec/spec_helper.rb, etc. For example, let's say your bar has NOT NULL on bartender_id and waiter_id, and in turn bartender and waiter
110
+ both have a NOT NULL bar_id, and neither enforce foreign keys. Maybe you have preloaded data for waiter somehow as the id '123', but want to set bartender to
111
+ just use an invalid id '-1', and you want to do it when they are on their second loop. You could use:
111
112
 
112
- Then you can just use `cache_create`, `cache_create_list`, `cache_build`, `cache_build_list`, or `cache_build_stubbed` in your rspec tests (`cache_create` becomes a shortcut for `::Stepford::FactoryGirlCache.create`, etc.):
113
-
114
- cache_create(:foo)
113
+ Stepford::FactoryGirl.stop_circular_refs = {
114
+ [:bartender, :bar] => {on_loop: 2, set_foreign_key_to: -1},
115
+ [:waiter, :bar] => {on_loop: 2, set_to: Waiter.find(123)},
116
+ }
115
117
 
116
118
  #### CLI
117
119
 
118
- Stepford has a CLI generator to automatically create your factories file(s).
120
+ Stepford has a CLI with a circular reference checker and a generator to automatically create your factories file(s).
121
+
122
+ ##### Refs
123
+
124
+ Check ActiveRecord circular dependencies:
125
+
126
+ bundle exec stepford circular
127
+
128
+ ##### Factories
119
129
 
120
- ##### Creating Factories
130
+ ###### Creating Factories
121
131
 
122
132
  Autogenerate `test/factories.rb` from all model files in `app/models`:
123
133
 
@@ -127,7 +137,7 @@ If you want one file per model, use `--multiple`. The default path is `test/fact
127
137
 
128
138
  bundle exec stepford factories --path spec/factories --multiple
129
139
 
130
- ##### RSpec
140
+ ###### RSpec
131
141
 
132
142
  To put all of your factories into `spec/factories.rb`:
133
143
 
@@ -137,7 +147,7 @@ This also works:
137
147
 
138
148
  bundle exec stepford factories --path spec/support/factories.rb
139
149
 
140
- ##### Specifying Models
150
+ ###### Specifying Models
141
151
 
142
152
  By default, Stepford processes all models found in `app/models`.
143
153
 
@@ -145,7 +155,7 @@ Specify `--models` and a comma-delimited list of models to only output the model
145
155
 
146
156
  bundle exec stepford factories --path spec/support/put_into_factories.rb --models foo,bar,foo_bar
147
157
 
148
- ##### Traits
158
+ ###### Traits
149
159
 
150
160
  To generate traits for each attribute that would be included with `--attributes`, but isn't because `--attributes` is not specified:
151
161
 
@@ -155,47 +165,35 @@ To generate traits for each association that would be included with `--associati
155
165
 
156
166
  bundle exec stepford factories --association-traits
157
167
 
158
- ##### Associations
168
+ ###### Associations
159
169
 
160
170
  If you use the (cache) wrapper to automatically generate factories, you may not need to generate associations. We had interdependence issues with factories. When there are NOT NULLs on foreign keys and/or presence validations, etc. you can't just use `after(:create)` or `after(:build)` to set associations, and without those you can have issues with "Trait not registered" or "Factory not registered" with interdependent factory associations.
161
171
 
162
172
  However, if you don't have anything that complex or don't mind hand-editing the factories to try to fix issues, these might help.
163
173
 
164
- ###### Include Required Assocations
174
+ ####### Include Required Assocations
165
175
 
166
176
  To include NOT NULL foreign key associations or presence validated associations:
167
177
 
168
178
  bundle exec stepford factories --include-required-associations
169
179
 
170
- ###### Include All Associations
180
+ ####### Include All Associations
171
181
 
172
182
  To include all associations even if they aren't deemed to be required by not null ActiveRecord constraints defined in the model:
173
183
 
174
184
  bundle exec stepford factories --associations
175
185
 
176
- ###### Checking Model Associations
186
+ ####### Checking Model Associations
177
187
 
178
188
  If `--associations` or `--validate-associations` is specified, Stepford first loads Rails and attempts to check your models for broken associations.
179
189
 
180
190
  If associations are deemed broken, it will output proposed changes.
181
191
 
182
- ###### Caching Associations
183
-
184
- Use `--cache-associations` will use the [factory_girl-cache][factory_girl-cache] gem in the autogenerated factories, so you will need to include it also in your Gemfile:
185
-
186
- gem 'factory_girl-cache'
187
-
188
- and
189
-
190
- bundle install
191
-
192
- See the Factory Girl Wrapper section for a possibly more useful way to use FactoryGirlCache.
193
-
194
- ##### No IDs
192
+ ###### No IDs
195
193
 
196
194
  If working with a legacy schema, you may have models with foreign_key columns that you don't have associations defined for in the model. If that is the case, we don't want to assign arbitrary integers to them and try to create a record. If that is the case, try `--exclude-all-ids`, which will exclude those ids as attributes defined in the factories and you can add associations as needed to get things working.
197
195
 
198
- ##### How NOT NULL, and Other Database Constraints and Active Record Validations Are Handled
196
+ ###### How NOT NULL, and Other Database Constraints and Active Record Validations Are Handled
199
197
 
200
198
  If the ActiveRecord column `null` property for the attribute is true for the attribute or foreign key for the association, or if there is a presence validator for an attribute or foreign key for the association, then that attribute or association will be defined by the default factory.
201
199
 
@@ -203,7 +201,7 @@ Currently uniqueness constraints are ignored and must be handled by FactoryGirl
203
201
 
204
202
  sequence(:username) {|n| "user#{n}" }
205
203
 
206
- ##### Testing Factories
204
+ ###### Testing Factories
207
205
 
208
206
  See [Testing all Factories (with RSpec)][test_factories] in the FG wiki.
209
207
 
@@ -268,7 +266,6 @@ This is the reason we wrote the Stepford Factory Girl Wrapper (see above). It au
268
266
 
269
267
  Copyright (c) 2012 Gary S. Weaver, released under the [MIT license][lic].
270
268
 
271
- [factory_girl-cache]: https://github.com/garysweaver/factory_girl-cache
272
269
  [test_factories]: https://github.com/thoughtbot/factory_girl/wiki/Testing-all-Factories-%28with-RSpec%29
273
270
  [factory_girl]: https://github.com/thoughtbot/factory_girl/
274
271
  [lic]: http://github.com/garysweaver/stepford/blob/master/LICENSE
@@ -0,0 +1,76 @@
1
+ module Stepford
2
+ class CircularRefChecker
3
+
4
+ @@model_and_association_names = []
5
+ @@level = 0
6
+ @@offenders = []
7
+ @@circles = []
8
+
9
+ # Check refs on all models or models specified in comma delimited list in options like:
10
+ # Stepford.CircularRefChecker.check_refs models: 'user, post, comment'
11
+ def self.check_refs(options={})
12
+ models = []
13
+ included_models = options[:models] ? options[:models].split(',').collect{|s|s.strip}.compact : nil
14
+ Dir[File.join('app','models','*.rb').to_s].each do |filename|
15
+ model_name = File.basename(filename).sub(/.rb$/, '')
16
+ next if included_models && !included_models.include?(model_name)
17
+ load File.join('app','models',"#{model_name}.rb")
18
+ model_class = model_name.camelize.constantize
19
+ next unless model_class.ancestors.include?(ActiveRecord::Base)
20
+ models << model_class
21
+ end
22
+
23
+ models.each do |model_class|
24
+ check_associations(model_class)
25
+ end
26
+
27
+ puts "Circles of shame:"
28
+ @@circles.sort.each do |c|
29
+ puts
30
+ puts "#{c}"
31
+ end
32
+ puts
33
+ puts
34
+ puts "Possible offenders. Either set nullable to true on these fields, or see if any of the fields above can be nullable:"
35
+ puts
36
+ @@offenders.sort.each do |c|
37
+ puts "#{c[0]}.#{c[1]}"
38
+ end
39
+
40
+ return (@@offenders.size == 0)
41
+ end
42
+
43
+ def self.check_associations(model_class)
44
+ @@level += 1
45
+
46
+ model_class.reflections.collect {|association_name, reflection|
47
+ @@model_and_association_names = [] if @@level == 1
48
+ assc_sym = reflection.name.to_sym
49
+ clas_sym = reflection.class_name.underscore.to_sym
50
+
51
+ # if has a foreign key, then if NOT NULL or is a presence validate, the association is required and should be output. unfortunately this could mean a circular reference that will have to be manually fixed
52
+ has_presence_validator = model_class.validators_on(assc_sym).collect{|v|v.class}.include?(ActiveModel::Validations::PresenceValidator)
53
+ required = reflection.foreign_key ? (has_presence_validator || model_class.columns.any?{|c| !c.null && c.name.to_sym == reflection.foreign_key.to_sym}) : false
54
+ if required
55
+ key = [model_class.to_s.underscore.to_sym, assc_sym]
56
+ if @@model_and_association_names.include?(key)
57
+ @@offenders << @@model_and_association_names.last unless @@offenders.include?(@@model_and_association_names.last)
58
+ # add to end
59
+ @@model_and_association_names << key
60
+ short = @@model_and_association_names.dup
61
+ # drop all preceding keys that have nothing to do with the circle
62
+ (short.index(key)).times {short.delete_at(0)}
63
+ string_representation = "#{short.collect{|b|"#{b[0]}.#{b[1]}"}.join(' -> ')}"
64
+ #puts string_representation
65
+ @@circles << string_representation.to_sym unless @@circles.include?(string_representation.to_sym)
66
+ else
67
+ @@model_and_association_names << key
68
+ check_associations(reflection.class_name.constantize)
69
+ end
70
+ end
71
+ }
72
+
73
+ @@level -= 1
74
+ end
75
+ end
76
+ end
data/lib/stepford/cli.rb CHANGED
@@ -12,7 +12,6 @@ module Stepford
12
12
  method_option :attributes, :desc => "Include all attributes except foreign keys and primary keys, not just those that are required due to ActiveRecord presence validation or column not null restriction", :type => :boolean
13
13
  method_option :attribute_traits, :desc => "Include traits for attributes that would be output with --attributes that wouldn't be otherwise when --attributes is not specified", :type => :boolean
14
14
  method_option :association_traits, :desc => "Include traits for associations that would be output with --associations that wouldn't be otherwise when --associations is not specified", :type => :boolean
15
- method_option :cache_associations, :desc => "Use singleton values to avoid 'stack level too deep' circular reference(s)", :type => :boolean
16
15
  method_option :include_required_associations, :desc => "Include NOT NULL foreign key associations or presence validated associations by default", :type => :boolean
17
16
  def factories()
18
17
  # load Rails environment
@@ -21,6 +20,16 @@ module Stepford
21
20
  require 'stepford/factory_girl_generator'
22
21
  exit Stepford::FactoryGirlGenerator.generate_factories(options) ? 0 : 1
23
22
  end
23
+
24
+ desc "circular", "check for circular refs"
25
+ method_option :models, :desc => "A comma delimited list of only the models you want to include"
26
+ def circular()
27
+ # load Rails environment
28
+ require './config/environment'
29
+ # load FactoryGirl and generate factories
30
+ require 'stepford/circular_ref_checker'
31
+ exit Stepford::CircularRefChecker.check_refs(options) ? 0 : 1
32
+ end
24
33
  end
25
34
  end
26
35
 
@@ -15,38 +15,88 @@ module Stepford
15
15
  # }) do
16
16
  # # the block you would send to FactoryGirl.create_list(:foo) would go here
17
17
  # end
18
+ #
19
+ # If you have a circular reference (A has NOT NULL foreign key to B that has NOT NULL foreign key to C that has NOT NULL foreign key to A) in the
20
+ # schema, there is a workaround. First, prepopulate one of the models involved in the interdependency chain in the database as part of test setup,
21
+ # or if the ids are NOT NULL but are not foreign key constrained (i.e. if you can enter an invalid ID into the foreign key column, which implies possible
22
+ # referential integrity issues) then you may be able to set them with an invalid id. Take that foreign id and then use the following to ensure
23
+ # that it will set that foreign id or instance. This is done at a global level which may not work for you, but it makes it convenient to put into
24
+ # your spec/spec_helper.rb, etc. For example, let's say your bar has NOT NULL on bartender_id and waiter_id, and in turn bartender and waiter
25
+ # both have a NOT NULL bar_id, and neither enforce foreign keys. Maybe you have preloaded data for waiter somehow as the id '123', but want to set bartender to
26
+ # just use an invalid id '-1', and you want to do it when they are on their second loop. You could use:
27
+ #
28
+ # Stepford::FactoryGirl.stop_circular_refs = {
29
+ # [:bartender, :bar] => {on_loop: 2, set_foreign_key_to: -1},
30
+ # [:waiter, :bar] => {on_loop: 2, set_to: Waiter.find(123)},
31
+ # }
18
32
  module FactoryGirl
33
+ OPTIONS = [
34
+ :stop_circular_refs
35
+ ]
36
+
19
37
  class << self
38
+ OPTIONS.each{|o|attr_accessor o; define_method("#{o}?".to_sym){!!send("#{o}")}}
39
+ def configure(&blk); class_eval(&blk); end
40
+
20
41
  def method_missing(m, *args, &block)
21
- puts "Stepford::FactoryGirl.#{m}(#{args.inspect})"
22
- stepford_command_options = {}
23
- args = args.dup # need local version because we'll be dup'ing the options hash to add things to set prior to create/build
24
- options = args.last
25
- if options.is_a?(Hash)
26
- options = options.dup
27
- args[(args.size - 1)] = options # need to set the dup'd options
28
- with_factory_options = options.delete(:with_factory_options)
29
- stepford_command_options = (with_factory_options ? {with_factory_options: with_factory_options} : {})
30
- else
31
- options = {}
32
- args << options # need to add options to set associations
33
- end
42
+ puts "starting Stepford::FactoryGirl.#{m}(#{args.inspect})"
34
43
 
35
44
  if [:build, :build_list, :build_stubbed, :create, :create_list].include?(m) && args && args.size > 0
36
- # key in the with_factory_options is association class symbol OR [association class symbol, association name symbol]
37
- # value is *args (array and possible hash)
38
- key_to_method_args_and_options = stepford_command_options[:with_factory_options]
39
-
40
45
  # call Stepford::FactoryGirl.* on any not null associations recursively
46
+ model_sym = args[0].to_sym
41
47
  model_class = args[0].to_s.camelize.constantize
48
+
49
+ args = args.dup # need local version because we'll be dup'ing the options hash to add things to set prior to create/build
50
+ options = args.last
51
+ if options.is_a?(Hash)
52
+ options = options.dup
53
+ args[(args.size - 1)] = options # need to set the dup'd options
54
+ else
55
+ options = {}
56
+ args << options # need to add options to set associations
57
+ end
58
+
59
+ options[:with_factory_options] = {} unless options[:with_factory_options]
60
+ with_factory_options = options[:with_factory_options]
61
+ with_factory_options[:circular_ref_counts] = {} unless with_factory_options[:circular_ref_counts]
42
62
  model_class.reflections.each do |association_name, reflection|
43
63
  assc_sym = reflection.name.to_sym
44
- next if options[assc_sym]
64
+ next if options[assc_sym] || options[reflection.foreign_key.to_sym]
45
65
  clas_sym = reflection.class_name.underscore.to_sym
46
66
  has_presence_validator = model_class.validators_on(assc_sym).collect{|v|v.class}.include?(::ActiveModel::Validations::PresenceValidator)
47
67
  required = reflection.foreign_key ? (has_presence_validator || model_class.columns.any?{|c| !c.null && c.name.to_sym == reflection.foreign_key.to_sym}) : false
48
- orig_method_args_and_options = key_to_method_args_and_options ? (key_to_method_args_and_options[[clas_sym, assc_sym]] || key_to_method_args_and_options[clas_sym]) : nil
68
+ orig_method_args_and_options = with_factory_options ? (with_factory_options[[clas_sym, assc_sym]] || with_factory_options[clas_sym]) : nil
49
69
  if required || orig_method_args_and_options
70
+ circular_ref_key = [model_sym, assc_sym]
71
+ all_opts = ::Stepford::FactoryGirl.stop_circular_refs
72
+ if all_opts.is_a?(Hash) && all_opts.size > 0
73
+ circ_options = all_opts[circular_ref_key]
74
+ if circ_options
75
+ #puts "::Stepford::FactoryGirl.stop_circular_refs[circular_ref_key]=#{circ_options.inspect}"
76
+ count = with_factory_options[:circular_ref_counts][circular_ref_key]
77
+ if count
78
+ count += 1
79
+ else
80
+ count = 0
81
+ end
82
+ with_factory_options[:circular_ref_counts][circular_ref_key] = count
83
+ if count > 100
84
+ puts "over 100 loops. need to add this to Stepford::FactoryGirl.stop_circular_refs: {#{circular_ref_key.inspect} => {on_loop: 2, set_foreign_key_to: -1}}"
85
+ end
86
+
87
+ if count >= (circ_options[:on_loop] || 1)
88
+ if circ_options.has_key?(:set_to)
89
+ puts "Circular dependency override enabled. Returning :set_to instance to set as #{model_sym}.#{assc_sym}. instance was #{circ_options[:set_to]}"
90
+ return circ_options[:set_to]
91
+ elsif circ_options.has_key?(:set_foreign_key_to)
92
+ # (CHILD) return hash to set on parent
93
+ puts "Circular dependency override enabled. Returning :set_foreign_key_to to set as #{model_sym}.#{reflection.foreign_key}. value was '#{circ_options[:set_foreign_key_to]}'"
94
+ return {reflection.foreign_key.to_sym => circ_options[:set_foreign_key_to]}
95
+ end
96
+ end
97
+ end
98
+ end
99
+
50
100
  if orig_method_args_and_options
51
101
  method_args_and_options = orig_method_args_and_options.dup
52
102
  method_options = args.last
@@ -62,21 +112,28 @@ module Stepford
62
112
  if reflection.macro == :has_many
63
113
  case m
64
114
  when :create, :create_list
65
- options[assc_sym] = ::Stepford::FactoryGirl.create_list(*[clas_sym, 2, stepford_command_options])
115
+ options[assc_sym] = ::Stepford::FactoryGirl.create_list(clas_sym, 2, options)
66
116
  when :build, :build_list
67
- options[assc_sym] = ::Stepford::FactoryGirl.build_list(*[clas_sym, 2, stepford_command_options])
117
+ options[assc_sym] = ::Stepford::FactoryGirl.build_list(clas_sym, 2, options)
68
118
  when :build_stubbed
69
119
  #TODO: need to test building something stubbed that has a PresenceValidator on a has_many
70
- options[assc_sym] = ::Stepford::FactoryGirl.build_stubbed(*[clas_sym, stepford_command_options])
120
+ options[assc_sym] = ::Stepford::FactoryGirl.build_stubbed(clas_sym, options)
71
121
  end
72
122
  else
73
123
  case m
74
124
  when :create, :create_list
75
- options[assc_sym] = ::Stepford::FactoryGirl.create(*[clas_sym, stepford_command_options])
125
+ options[assc_sym] = ::Stepford::FactoryGirl.create(clas_sym, options)
76
126
  when :build, :build_list
77
- options[assc_sym] = ::Stepford::FactoryGirl.build(*[clas_sym, stepford_command_options])
127
+ options[assc_sym] = ::Stepford::FactoryGirl.build(clas_sym, options)
78
128
  when :build_stubbed
79
- options[assc_sym] = ::Stepford::FactoryGirl.build_stubbed(*[clas_sym, stepford_command_options])
129
+ options[assc_sym] = ::Stepford::FactoryGirl.build_stubbed(clas_sym, options)
130
+ end
131
+
132
+ # (PARENT) we passed this back as a hash which means that the child model needed to set foreign key on the parent model
133
+ if options[assc_sym].is_a?(Hash)
134
+ value = options.delete(assc_sym)
135
+ options.merge!()
136
+ puts "Overrode foreign key #{model_sym}.#{assc_sym} = #{value}"
80
137
  end
81
138
  end
82
139
  end
@@ -84,7 +141,10 @@ module Stepford
84
141
  end
85
142
  end
86
143
 
87
- puts "FactoryGirl.__send__(#{([m] + Array.wrap(args)).inspect}, &block)"
144
+ # clean-up before sending to FactoryGirl
145
+ (args.last).delete(:with_factory_options) if args.last.is_a?(Hash)
146
+
147
+ puts "FactoryGirl.#{m}(#{args.inspect}) (via __send__)"
88
148
  ::FactoryGirl.__send__(m, *args, &block)
89
149
  end
90
150
  end
@@ -24,25 +24,16 @@ module Stepford
24
24
  required = reflection.foreign_key ? (has_presence_validator || model_class.columns.any?{|c| !c.null && c.name.to_sym == reflection.foreign_key.to_sym}) : false
25
25
  should_be_trait = !(options[:associations] || (options[:include_required_associations] && required)) && options[:association_traits]
26
26
  if options[:associations] || (options[:include_required_associations] && required) || should_be_trait
27
- if options[:cache_associations]
28
- if reflection.macro == :has_many
29
- is_default_plural_association_name = (clas_sym.to_s.pluralize.to_sym == assc_sym)
30
- "#{should_be_trait ? "trait #{"with_#{assc_sym}".to_sym.inspect} do; " : ''}after(:create) do |user, evaluator|; #{is_reserved?(assc_sym) ? 'self.' : ''}#{assc_sym} = FactoryGirlCache.create_list(#{clas_sym.inspect}#{is_default_plural_association_name ? '' : ", #{assc_sym.inspect}"}, 2); end#{should_be_trait ? '; end' : ''}"
31
- else
32
- "#{should_be_trait ? "trait #{"with_#{assc_sym}".to_sym.inspect} do; " : ''}#{is_reserved?(assc_sym) ? 'self.' : ''}#{assc_sym} = FactoryGirlCache.create(#{clas_sym.inspect}#{clas_sym == assc_sym ? '' : ", #{assc_sym.inspect}"})#{should_be_trait ? '; end' : ''}"
33
- end
27
+ if reflection.macro == :has_many
28
+ # In factory girl v4.1.0:
29
+ # create_list must be done in an after(:create) or you get Trait not registered or Factory not registered errors.
30
+ # this means that validators that verify presence or size > 0 in a association list will not work with this method, and you'll need to
31
+ # use build, not create: http://stackoverflow.com/questions/11209347/has-many-with-at-least-two-entries
32
+ "#{should_be_trait ? "trait #{"with_#{assc_sym}".to_sym.inspect} do; " : ''}#{should_be_trait || has_presence_validator ? '' : '#'}after(:create) do |user, evaluator|; FactoryGirl.create_list #{clas_sym.inspect}, 2; end#{should_be_trait ? '; end' : ''}#{should_be_trait ? '' : ' # commented to avoid circular reference'}"
33
+ elsif assc_sym != clas_sym
34
+ "#{should_be_trait ? "trait #{"with_#{assc_sym}".to_sym.inspect} do; " : ''}#{should_be_trait || reflection.macro == :belongs_to || has_presence_validator ? '' : '#'}association #{assc_sym.inspect}#{assc_sym != clas_sym ? ", factory: #{clas_sym.inspect}" : ''}#{should_be_trait ? '; end' : ''}#{should_be_trait || reflection.macro == :belongs_to ? '' : ' # commented to avoid circular reference'}"
34
35
  else
35
- if reflection.macro == :has_many
36
- # In factory girl v4.1.0:
37
- # create_list must be done in an after(:create) or you get Trait not registered or Factory not registered errors.
38
- # this means that validators that verify presence or size > 0 in a association list will not work with this method, and you'll need to
39
- # use build, not create: http://stackoverflow.com/questions/11209347/has-many-with-at-least-two-entries
40
- "#{should_be_trait ? "trait #{"with_#{assc_sym}".to_sym.inspect} do; " : ''}#{should_be_trait || has_presence_validator ? '' : '#'}after(:create) do |user, evaluator|; FactoryGirl.create_list #{clas_sym.inspect}, 2; end#{should_be_trait ? '; end' : ''}#{should_be_trait ? '' : ' # commented to avoid circular reference'}"
41
- elsif assc_sym != clas_sym
42
- "#{should_be_trait ? "trait #{"with_#{assc_sym}".to_sym.inspect} do; " : ''}#{should_be_trait || reflection.macro == :belongs_to || has_presence_validator ? '' : '#'}association #{assc_sym.inspect}#{assc_sym != clas_sym ? ", factory: #{clas_sym.inspect}" : ''}#{should_be_trait ? '; end' : ''}#{should_be_trait || reflection.macro == :belongs_to ? '' : ' # commented to avoid circular reference'}"
43
- else
44
- "#{should_be_trait ? "trait #{"with_#{assc_sym}".to_sym.inspect} do; " : ''}#{should_be_trait || reflection.macro == :belongs_to || has_presence_validator ? '' : '#'}#{is_reserved?(assc_sym) ? 'self.' : ''}#{assc_sym}#{should_be_trait ? '; end' : ''}#{should_be_trait || reflection.macro == :belongs_to ? '' : ' # commented to avoid circular reference'}"
45
- end
36
+ "#{should_be_trait ? "trait #{"with_#{assc_sym}".to_sym.inspect} do; " : ''}#{should_be_trait || reflection.macro == :belongs_to || has_presence_validator ? '' : '#'}#{is_reserved?(assc_sym) ? 'self.' : ''}#{assc_sym}#{should_be_trait ? '; end' : ''}#{should_be_trait || reflection.macro == :belongs_to ? '' : ' # commented to avoid circular reference'}"
46
37
  end
47
38
  else
48
39
  nil
@@ -1,3 +1,3 @@
1
1
  module Stepford
2
- VERSION = '0.8.2'
2
+ VERSION = '0.9.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stepford
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.9.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-07 00:00:00.000000000 Z
12
+ date: 2012-11-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -51,11 +51,10 @@ executables:
51
51
  extensions: []
52
52
  extra_rdoc_files: []
53
53
  files:
54
+ - lib/stepford/circular_ref_checker.rb
54
55
  - lib/stepford/cli.rb
55
56
  - lib/stepford/common.rb
56
57
  - lib/stepford/factory_girl.rb
57
- - lib/stepford/factory_girl_cache.rb
58
- - lib/stepford/factory_girl_cache_rspec_helpers.rb
59
58
  - lib/stepford/factory_girl_generator.rb
60
59
  - lib/stepford/factory_girl_rspec_helpers.rb
61
60
  - lib/stepford/version.rb
@@ -1,92 +0,0 @@
1
- require 'factory_girl'
2
-
3
- module Stepford
4
- # A wrapper for FactoryGirl that automatically recursively creates/builds/stubbed factories for null=false and/or presence validated associations.
5
- #
6
- # Lets you specify method name and arguments/options to factory girl for associations.
7
- #
8
- # e.g. if the following is required:
9
- # * Bar has a required association called house_special which uses the :beer factory, and we have a block we want to send into it
10
- # * Beer has specials that you want to build as a list of 3 using the :tuesday_special_offer factory
11
- # then you could set that up like this:
12
- # Stepford::FactoryGirl.create_list(:bar, with_factory_options: {
13
- # house_special: [:create, :beer, {blk: ->(beer) do; beer.bubbles.create(attributes_for(:bubbles)); end}],
14
- # specials: [:build_list, :tuesday_special_offer, 3]
15
- # }) do
16
- # # the block you would send to FactoryGirl.create_list(:foo) would go here
17
- # end
18
- module FactoryGirlCache
19
- class << self
20
- def method_missing(m, *args, &block)
21
- puts "Stepford::FactoryGirlCache.#{m}(#{args.inspect})"
22
- stepford_command_options = {}
23
- args = args.dup # need local version because we'll be dup'ing the options hash to add things to set prior to create/build
24
- options = args.last
25
- if options.is_a?(Hash)
26
- options = options.dup
27
- args[(args.size - 1)] = options # need to set the dup'd options
28
- with_factory_options = options.delete(:with_factory_options)
29
- stepford_command_options = (with_factory_options ? {with_factory_options: with_factory_options} : {})
30
- else
31
- options = {}
32
- args << options # need to add options to set associations
33
- end
34
-
35
- if [:build, :build_list, :build_stubbed, :create, :create_list].include?(m) && args && args.size > 0
36
- # key in the with_factory_options is association class symbol OR [association class symbol, association name symbol]
37
- # value is *args (array and possible hash)
38
- key_to_method_args_and_options = stepford_command_options[:with_factory_options]
39
-
40
- # call Stepford::FactoryGirlCache.* on any not null associations recursively, and create an options hash with those as values, if not already passed in
41
- model_class = args[0].to_s.camelize.constantize
42
- model_class.reflections.each do |association_name, reflection|
43
- assc_sym = reflection.name.to_sym
44
- next if options[assc_sym]
45
- clas_sym = reflection.class_name.underscore.to_sym
46
- has_presence_validator = model_class.validators_on(assc_sym).collect{|v|v.class}.include?(::ActiveModel::Validations::PresenceValidator)
47
- required = reflection.foreign_key ? (has_presence_validator || model_class.columns.any?{|c| !c.null && c.name.to_sym == reflection.foreign_key.to_sym}) : false
48
- orig_method_args_and_options = key_to_method_args_and_options ? (key_to_method_args_and_options[[clas_sym, assc_sym]] || key_to_method_args_and_options[clas_sym]) : nil
49
- if required || orig_method_args_and_options
50
- if orig_method_args_and_options
51
- method_args_and_options = orig_method_args_and_options.dup
52
- method_options = args.last
53
- blk = method_options.is_a?(Hash) ? method_args_and_options.delete(:blk) : nil
54
- if blk
55
- puts "FactoryGirlCache.__send__(#{method_args_and_options.inspect}, &blk)"
56
- options[assc_sym] = ::FactoryGirlCache.__send__(*method_args_and_options, &blk)
57
- else
58
- puts "FactoryGirlCache.__send__(#{method_args_and_options.inspect})"
59
- options[assc_sym] = ::FactoryGirlCache.__send__(*method_args_and_options)
60
- end
61
- else
62
- if reflection.macro == :has_many
63
- case m
64
- when :create, :create_list
65
- options[assc_sym] = ::Stepford::FactoryGirlCache.create_list(*[clas_sym, 2, stepford_command_options])
66
- when :build, :build_list
67
- options[assc_sym] = ::Stepford::FactoryGirlCache.build_list(*[clas_sym, 2, stepford_command_options])
68
- when :build_stubbed
69
- #TODO: need to test building something stubbed that has a PresenceValidator on a has_many
70
- options[assc_sym] = ::Stepford::FactoryGirlCache.build_stubbed(*[clas_sym, stepford_command_options])
71
- end
72
- else
73
- case m
74
- when :create, :create_list
75
- options[assc_sym] = ::Stepford::FactoryGirlCache.create(*[clas_sym, stepford_command_options])
76
- when :build, :build_list
77
- options[assc_sym] = ::Stepford::FactoryGirlCache.build(*[clas_sym, stepford_command_options])
78
- when :build_stubbed
79
- options[assc_sym] = ::Stepford::FactoryGirlCache.build_stubbed(*[clas_sym, stepford_command_options])
80
- end
81
- end
82
- end
83
- end
84
- end
85
- end
86
-
87
- puts "FactoryGirlCache.__send__(#{([m] + Array.wrap(args)).inspect}, &block)"
88
- ::FactoryGirlCache.__send__(m, *args, &block)
89
- end
90
- end
91
- end
92
- end
@@ -1,17 +0,0 @@
1
- require 'stepford/factory_girl_cache'
2
-
3
- module Stepford
4
- module FactoryGirlCacheRspecHelpers
5
- [:create, :create_list, :build, :build_list, :build_stubbed].each do |s|
6
- class_eval %Q"
7
- def cache_#{s}(*args, &block)
8
- ::Stepford::FactoryGirlCache.#{s}(*args, &block)
9
- end
10
- "
11
- end
12
- end
13
- end
14
-
15
- RSpec.configure do |c|
16
- c.include ::Stepford::FactoryGirlCacheRspecHelpers
17
- end