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 +55 -58
- data/lib/stepford/circular_ref_checker.rb +76 -0
- data/lib/stepford/cli.rb +10 -1
- data/lib/stepford/factory_girl.rb +86 -26
- data/lib/stepford/factory_girl_generator.rb +9 -18
- data/lib/stepford/version.rb +1 -1
- metadata +3 -4
- data/lib/stepford/factory_girl_cache.rb +0 -92
- data/lib/stepford/factory_girl_cache_rspec_helpers.rb +0 -17
data/README.md
CHANGED
@@ -1,24 +1,30 @@
|
|
1
1
|
Stepford
|
2
2
|
=====
|
3
3
|
|
4
|
-
Stepford is
|
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
|
-
|
6
|
+
For example, with the rspec helper:
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
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(:
|
22
|
+
# any block you would send to FactoryGirl.create_list(:bar) would go here
|
19
23
|
end
|
20
24
|
|
21
|
-
|
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
|
-
####
|
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
|
-
|
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
|
-
|
102
|
+
##### Stopping Circular References
|
109
103
|
|
110
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
######
|
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
|
-
|
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
|
-
|
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 =
|
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(
|
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(
|
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(
|
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(
|
125
|
+
options[assc_sym] = ::Stepford::FactoryGirl.create(clas_sym, options)
|
76
126
|
when :build, :build_list
|
77
|
-
options[assc_sym] = ::Stepford::FactoryGirl.build(
|
127
|
+
options[assc_sym] = ::Stepford::FactoryGirl.build(clas_sym, options)
|
78
128
|
when :build_stubbed
|
79
|
-
options[assc_sym] = ::Stepford::FactoryGirl.build_stubbed(
|
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
|
-
|
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
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
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
|
data/lib/stepford/version.rb
CHANGED
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.
|
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-
|
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
|