stepford 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +85 -3
- data/lib/stepford/cli.rb +4 -3
- data/lib/stepford/factory_girl.rb +80 -157
- data/lib/stepford/factory_girl_cache.rb +92 -0
- data/lib/stepford/factory_girl_cache_rspec_helpers.rb +17 -0
- data/lib/stepford/factory_girl_generator.rb +166 -0
- data/lib/stepford/factory_girl_rspec_helpers.rb +17 -0
- data/lib/stepford/version.rb +1 -1
- data/lib/stepford.rb +0 -2
- metadata +6 -2
data/README.md
CHANGED
@@ -1,7 +1,26 @@
|
|
1
1
|
Stepford
|
2
2
|
=====
|
3
3
|
|
4
|
-
Stepford is a
|
4
|
+
Stepford is a wrapper and generator for FactoryGirl to automate stuff and make it easier to use.
|
5
|
+
|
6
|
+
Stepford::FactoryGirl 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 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
|
+
|
12
|
+
then you could set that up like this:
|
13
|
+
|
14
|
+
Stepford::FactoryGirl.create_list(:bar, with_factory_options: {
|
15
|
+
house_special: [:create, :beer, {blk: ->(beer) do; beer.bubbles.create(attributes_for(:bubbles)); end}],
|
16
|
+
specials: [:build_list, :tuesday_special_offer, 3]
|
17
|
+
}) do
|
18
|
+
# any block you would send to FactoryGirl.create_list(:foo) would go here
|
19
|
+
end
|
20
|
+
|
21
|
+
The `Stepford` CLI command will generate your factories.rb or multiple factory files for you.
|
22
|
+
|
23
|
+
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:
|
5
24
|
|
6
25
|
require 'factory_girl_rails'
|
7
26
|
|
@@ -23,6 +42,8 @@ Stepford is a CLI to create starter [Factory Girl][factory_girl] factories for a
|
|
23
42
|
|
24
43
|
end
|
25
44
|
|
45
|
+
and also let
|
46
|
+
|
26
47
|
### Setup
|
27
48
|
|
28
49
|
In your Rails 3+ project, add this to your Gemfile:
|
@@ -39,7 +60,64 @@ Then run:
|
|
39
60
|
|
40
61
|
### Usage
|
41
62
|
|
42
|
-
#### Factory Girl
|
63
|
+
#### Factory Girl Wrapper
|
64
|
+
|
65
|
+
##### Standard
|
66
|
+
|
67
|
+
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.
|
68
|
+
|
69
|
+
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:
|
70
|
+
|
71
|
+
Stepford::FactoryGirl.create_list(:foo, 5)
|
72
|
+
|
73
|
+
and that would create a list of 5 Foos, that each have a Bar, where each Bar has a list of 2 Foobars and a Barfoo. Easy!
|
74
|
+
|
75
|
+
But, you might want to specify traits, and certain attributes or associations or a block or different methods to use. That's pretty easy, too. Let's say you only need to tweak bar and foobar on each item, but the rest gets created as it would with just `Stepford::FactoryGirl.create_list`, so if you wanted to create 5 with two traits `:fancy` and `:light` and only build the bar and build bar's foobar as a stub:
|
76
|
+
|
77
|
+
Stepford::FactoryGirl.create_list(:foo, 5, :fancy, :light, with_factory_options: {
|
78
|
+
bar: [:build, :bar],
|
79
|
+
foobar: [:build_stubbed, :foobar]
|
80
|
+
}) do
|
81
|
+
# any block you would send to FactoryGirl.create_list(:foo) would go here
|
82
|
+
end
|
83
|
+
|
84
|
+
##### Cached
|
85
|
+
|
86
|
+
In your Gemfile:
|
87
|
+
|
88
|
+
require 'factory_girl-cache'
|
89
|
+
|
90
|
+
Then:
|
91
|
+
|
92
|
+
bundle install
|
93
|
+
|
94
|
+
In your `spec/spec_helper.rb`, add:
|
95
|
+
|
96
|
+
require 'stepford/factory_girl_cache'
|
97
|
+
|
98
|
+
It works just about the same as `Stepford::FactoryGirl` except it is called `Stepford::FactoryGirlCache` and acts more like `FactoryGirlCache`.
|
99
|
+
|
100
|
+
##### RSpec Helpers
|
101
|
+
|
102
|
+
`Stepford::FactoryGirl` and `Stepford::FactoryGirlCache` are a lot to type.
|
103
|
+
|
104
|
+
Instead put this in your `spec/spec_helper.rb`:
|
105
|
+
|
106
|
+
require 'stepford/factory_girl_rspec_helpers'
|
107
|
+
|
108
|
+
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.):
|
109
|
+
|
110
|
+
create(:foo)
|
111
|
+
|
112
|
+
For the cached version, add this to `spec/spec_helper.rb`:
|
113
|
+
|
114
|
+
require 'stepford/factory_girl_cache_rspec_helpers'
|
115
|
+
|
116
|
+
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.):
|
117
|
+
|
118
|
+
cache_create(:foo)
|
119
|
+
|
120
|
+
#### Factory Girl CLI
|
43
121
|
|
44
122
|
##### How NOT NULL, and Other Database Constraints and Active Record Validations Are Handled
|
45
123
|
|
@@ -89,7 +167,11 @@ If associations are deemed broken, it will output proposed changes.
|
|
89
167
|
|
90
168
|
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.
|
91
169
|
|
92
|
-
#####
|
170
|
+
##### Ignored Required Assocations
|
171
|
+
|
172
|
+
With `--ignore-required-associations` it won't include NOT NULL foreign key associations or presence validated associations by default.
|
173
|
+
|
174
|
+
##### Cache Associations
|
93
175
|
|
94
176
|
Use `--cache-associations` to store and use factories to avoid 'stack level too deep' errors.
|
95
177
|
|
data/lib/stepford/cli.rb
CHANGED
@@ -12,13 +12,14 @@ 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
|
15
|
+
method_option :cache_associations, :desc => "Use singleton values to avoid 'stack level too deep' circular reference(s)", :type => :boolean
|
16
|
+
method_option :ignore_required_associations, :desc => "Won't include NOT NULL foreign key associations or presence validated associations by default", :type => :boolean
|
16
17
|
def factories()
|
17
18
|
# load Rails environment
|
18
19
|
require './config/environment'
|
19
20
|
# load FactoryGirl and generate factories
|
20
|
-
require 'stepford/
|
21
|
-
exit Stepford::
|
21
|
+
require 'stepford/factory_girl_generator'
|
22
|
+
exit Stepford::FactoryGirlGenerator.generate_factories(options) ? 0 : 1
|
22
23
|
end
|
23
24
|
end
|
24
25
|
end
|
@@ -1,169 +1,92 @@
|
|
1
|
-
require '
|
1
|
+
require 'factory_girl'
|
2
2
|
|
3
3
|
module Stepford
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
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
|
46
|
-
end
|
47
|
-
else
|
48
|
-
nil
|
49
|
-
end
|
50
|
-
}.compact.sort.each {|l|factory << l}
|
51
|
-
model_class.columns.sort_by {|c|[c.name]}.each {|c|
|
52
|
-
if !excluded_attributes.include?(c.name.to_sym) && !(c.name.downcase.end_with?('_id') && options[:exclude_all_ids]) && (options[:attributes] || !c.null)
|
53
|
-
factory << "#{c.name} #{Stepford::Common.value_for(c)}"
|
54
|
-
elsif options[:attribute_traits]
|
55
|
-
if c.type == :boolean
|
56
|
-
factory << "trait #{c.name.underscore.to_sym.inspect} do; #{c.name} true; end"
|
57
|
-
factory << "trait #{"not_#{c.name.underscore}".to_sym.inspect} do; #{c.name} false; end"
|
58
|
-
else
|
59
|
-
factory << "trait #{"with_#{c.name.underscore}".to_sym.inspect} do; #{c.name} #{Stepford::Common.value_for(c)}; end"
|
60
|
-
end
|
61
|
-
end
|
62
|
-
}
|
63
|
-
end
|
64
|
-
|
65
|
-
if options[:associations] || options[:validate_associations]
|
66
|
-
failed = false
|
67
|
-
model_to_fixes_required = {}
|
68
|
-
expected.keys.sort.each do |factory_name|
|
69
|
-
unless factories[factory_name.to_sym]
|
70
|
-
puts "#{File.join('app','models',"#{factory_name}.rb")} missing. Model(s) with associations to it: #{expected[factory_name].sort.join(', ')}"
|
71
|
-
expected[factory_name].each do |model_name|
|
72
|
-
(model_to_fixes_required[model_name.to_sym] ||= []) << factory_name.to_sym
|
73
|
-
end
|
74
|
-
failed = true
|
75
|
-
end
|
76
|
-
end
|
77
|
-
model_to_fixes_required.keys.each do |model_to_fix|
|
78
|
-
puts ""
|
79
|
-
puts "In #{model_to_fix}:"
|
80
|
-
model_to_fixes_required[model_to_fix].each do |fix|
|
81
|
-
puts "- comment/remove/fix broken association to #{fix}"
|
82
|
-
end
|
83
|
-
end
|
84
|
-
return false if failed
|
85
|
-
end
|
86
|
-
|
87
|
-
path = get_factories_rb_pathname(options)
|
88
|
-
|
89
|
-
if path.end_with?('.rb')
|
90
|
-
dirpath = File.dirname(path)
|
91
|
-
unless File.directory?(dirpath)
|
92
|
-
puts "Please create this directory first: #{dirpath}"
|
93
|
-
return false
|
94
|
-
end
|
95
|
-
|
96
|
-
File.open(path, "w") do |f|
|
97
|
-
write_header(f, options)
|
98
|
-
factories.keys.sort.each do |factory_name|
|
99
|
-
factory = factories[factory_name]
|
100
|
-
write_factory(factory_name, factory, f)
|
101
|
-
end
|
102
|
-
write_footer(f)
|
103
|
-
end
|
104
|
-
else
|
105
|
-
unless File.directory?(path)
|
106
|
-
puts "Please create this directory first: #{path}"
|
107
|
-
return false
|
108
|
-
end
|
109
|
-
|
110
|
-
factories.keys.sort.each do |factory_name|
|
111
|
-
factory = factories[factory_name]
|
112
|
-
File.open(File.join(path,"#{factory_name}.rb"), "w") do |f|
|
113
|
-
write_header(f, options)
|
114
|
-
write_factory(factory_name, factory, f)
|
115
|
-
write_footer(f)
|
116
|
-
end
|
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 FactoryGirl
|
19
|
+
class << self
|
20
|
+
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
|
117
33
|
end
|
118
|
-
end
|
119
|
-
|
120
|
-
return true
|
121
|
-
end
|
122
34
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
%w{__FILE__ __LINE__ alias and begin BEGIN break case class def defined? do else elsif end END ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield}.include? s.to_s
|
128
|
-
end
|
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]
|
129
39
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
40
|
+
# call Stepford::FactoryGirl.* on any not null associations recursively
|
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 "FactoryGirl.__send__(#{method_args_and_options.inspect}, &blk)"
|
56
|
+
options[assc_sym] = ::FactoryGirl.__send__(*method_args_and_options, &blk)
|
57
|
+
else
|
58
|
+
puts "FactoryGirl.__send__(#{method_args_and_options.inspect})"
|
59
|
+
options[assc_sym] = ::FactoryGirl.__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::FactoryGirl.create_list(*[clas_sym, 2, stepford_command_options])
|
66
|
+
when :build, :build_list
|
67
|
+
options[assc_sym] = ::Stepford::FactoryGirl.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::FactoryGirl.build_stubbed(*[clas_sym, stepford_command_options])
|
71
|
+
end
|
72
|
+
else
|
73
|
+
case m
|
74
|
+
when :create, :create_list
|
75
|
+
options[assc_sym] = ::Stepford::FactoryGirl.create(*[clas_sym, stepford_command_options])
|
76
|
+
when :build, :build_list
|
77
|
+
options[assc_sym] = ::Stepford::FactoryGirl.build(*[clas_sym, stepford_command_options])
|
78
|
+
when :build_stubbed
|
79
|
+
options[assc_sym] = ::Stepford::FactoryGirl.build_stubbed(*[clas_sym, stepford_command_options])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
140
84
|
end
|
141
85
|
end
|
142
|
-
end
|
143
|
-
path
|
144
|
-
end
|
145
86
|
|
146
|
-
|
147
|
-
|
148
|
-
f.puts 'require \'factory_girl-cache\'' if options[:cache_associations]
|
149
|
-
f.puts ''
|
150
|
-
f.puts '# original version autogenerated by Stepford: https://github.com/garysweaver/stepford'
|
151
|
-
f.puts ''
|
152
|
-
f.puts 'FactoryGirl.define do'
|
153
|
-
f.puts ' '
|
154
|
-
end
|
155
|
-
|
156
|
-
def self.write_factory(factory_name, factory, f)
|
157
|
-
f.puts " factory #{factory_name.inspect} do"
|
158
|
-
factory.each do |line|
|
159
|
-
f.puts " #{line}"
|
87
|
+
puts "FactoryGirl.__send__(#{([m] + Array.wrap(args)).inspect}, &block)"
|
88
|
+
::FactoryGirl.__send__(m, *args, &block)
|
160
89
|
end
|
161
|
-
f.puts ' end'
|
162
|
-
f.puts ' '
|
163
|
-
end
|
164
|
-
|
165
|
-
def self.write_footer(f)
|
166
|
-
f.puts 'end'
|
167
90
|
end
|
168
91
|
end
|
169
92
|
end
|
@@ -0,0 +1,92 @@
|
|
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
|
@@ -0,0 +1,17 @@
|
|
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
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'stepford/common'
|
2
|
+
|
3
|
+
module Stepford
|
4
|
+
class FactoryGirlGenerator
|
5
|
+
def self.generate_factories(options={})
|
6
|
+
factories = {}
|
7
|
+
expected = {}
|
8
|
+
included_models = options[:models] ? options[:models].split(',').collect{|s|s.strip}.compact : nil
|
9
|
+
Dir[File.join('app','models','*.rb').to_s].each do |filename|
|
10
|
+
model_name = File.basename(filename).sub(/.rb$/, '')
|
11
|
+
next if included_models && !included_models.include?(model_name)
|
12
|
+
load File.join('app','models',"#{model_name}.rb")
|
13
|
+
model_class = model_name.camelize.constantize
|
14
|
+
next unless model_class.ancestors.include?(ActiveRecord::Base)
|
15
|
+
factory = (factories[model_name.to_sym] ||= [])
|
16
|
+
excluded_attributes = Array.wrap(model_class.primary_key).collect{|pk|pk.to_sym} + [:updated_at, :created_at, :object_id]
|
17
|
+
model_class.reflections.collect {|association_name, reflection|
|
18
|
+
(expected[reflection.class_name.underscore.to_sym] ||= []) << model_name
|
19
|
+
excluded_attributes << reflection.foreign_key.to_sym if reflection.foreign_key
|
20
|
+
assc_sym = reflection.name.to_sym
|
21
|
+
clas_sym = reflection.class_name.underscore.to_sym
|
22
|
+
# 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
|
23
|
+
has_presence_validator = model_class.validators_on(assc_sym).collect{|v|v.class}.include?(ActiveModel::Validations::PresenceValidator)
|
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
|
+
should_be_trait = !(options[:associations] || (!options[:ignore_required] && required)) && options[:association_traits]
|
26
|
+
if options[:associations] || (!options[:ignore_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
|
34
|
+
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
|
46
|
+
end
|
47
|
+
else
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
}.compact.sort.each {|l|factory << l}
|
51
|
+
model_class.columns.sort_by {|c|[c.name]}.each {|c|
|
52
|
+
if !excluded_attributes.include?(c.name.to_sym) && !(c.name.downcase.end_with?('_id') && options[:exclude_all_ids]) && (options[:attributes] || !c.null)
|
53
|
+
factory << "#{is_reserved?(c.name) ? 'self.' : ''}#{c.name} #{Stepford::Common.value_for(c)}"
|
54
|
+
elsif options[:attribute_traits]
|
55
|
+
if c.type == :boolean
|
56
|
+
factory << "trait #{c.name.underscore.to_sym.inspect} do; #{is_reserved?(c.name) ? 'self.' : ''}#{c.name} true; end"
|
57
|
+
factory << "trait #{"not_#{c.name.underscore}".to_sym.inspect} do; #{is_reserved?(c.name) ? 'self.' : ''}#{c.name} false; end"
|
58
|
+
else
|
59
|
+
factory << "trait #{"with_#{c.name.underscore}".to_sym.inspect} do; #{is_reserved?(c.name) ? 'self.' : ''}#{c.name} #{Stepford::Common.value_for(c)}; end"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
if options[:associations] || options[:validate_associations]
|
66
|
+
failed = false
|
67
|
+
model_to_fixes_required = {}
|
68
|
+
expected.keys.sort.each do |factory_name|
|
69
|
+
unless factories[factory_name.to_sym]
|
70
|
+
puts "#{File.join('app','models',"#{factory_name}.rb")} missing. Model(s) with associations to it: #{expected[factory_name].sort.join(', ')}"
|
71
|
+
expected[factory_name].each do |model_name|
|
72
|
+
(model_to_fixes_required[model_name.to_sym] ||= []) << factory_name.to_sym
|
73
|
+
end
|
74
|
+
failed = true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
model_to_fixes_required.keys.each do |model_to_fix|
|
78
|
+
puts ""
|
79
|
+
puts "In #{model_to_fix}:"
|
80
|
+
model_to_fixes_required[model_to_fix].each do |fix|
|
81
|
+
puts "- comment/remove/fix broken association to #{fix}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
return false if failed
|
85
|
+
end
|
86
|
+
|
87
|
+
path = get_factories_rb_pathname(options)
|
88
|
+
|
89
|
+
if path.end_with?('.rb')
|
90
|
+
dirpath = File.dirname(path)
|
91
|
+
unless File.directory?(dirpath)
|
92
|
+
puts "Please create this directory first: #{dirpath}"
|
93
|
+
return false
|
94
|
+
end
|
95
|
+
|
96
|
+
File.open(path, "w") do |f|
|
97
|
+
write_header(f, options)
|
98
|
+
factories.keys.sort.each do |factory_name|
|
99
|
+
factory = factories[factory_name]
|
100
|
+
write_factory(factory_name, factory, f)
|
101
|
+
end
|
102
|
+
write_footer(f)
|
103
|
+
end
|
104
|
+
else
|
105
|
+
unless File.directory?(path)
|
106
|
+
puts "Please create this directory first: #{path}"
|
107
|
+
return false
|
108
|
+
end
|
109
|
+
|
110
|
+
factories.keys.sort.each do |factory_name|
|
111
|
+
factory = factories[factory_name]
|
112
|
+
File.open(File.join(path,"#{factory_name}.rb"), "w") do |f|
|
113
|
+
write_header(f, options)
|
114
|
+
write_factory(factory_name, factory, f)
|
115
|
+
write_footer(f)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
return true
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def self.is_reserved?(s)
|
126
|
+
# modified from http://stackoverflow.com/questions/6461303/built-in-way-to-determine-whether-a-string-is-a-ruby-reserved-word/6461673#6461673
|
127
|
+
%w{__FILE__ __LINE__ alias and begin BEGIN break case class def defined? do else elsif end END ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield}.include? s.to_s
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.get_factories_rb_pathname(options)
|
131
|
+
path = File.join('test','factories.rb')
|
132
|
+
if options[:path]
|
133
|
+
if options[:path].end_with?('.rb')
|
134
|
+
path = options[:path]
|
135
|
+
else
|
136
|
+
if options[:single]
|
137
|
+
path = File.join(options[:path],'factories.rb')
|
138
|
+
else
|
139
|
+
path = options[:path]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
path
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.write_header(f, options)
|
147
|
+
f.puts '# original version autogenerated by Stepford: https://github.com/garysweaver/stepford'
|
148
|
+
f.puts ''
|
149
|
+
f.puts 'FactoryGirl.define do'
|
150
|
+
f.puts ' '
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.write_factory(factory_name, factory, f)
|
154
|
+
f.puts " factory #{factory_name.inspect} do"
|
155
|
+
factory.each do |line|
|
156
|
+
f.puts " #{line}"
|
157
|
+
end
|
158
|
+
f.puts ' end'
|
159
|
+
f.puts ' '
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.write_footer(f)
|
163
|
+
f.puts 'end'
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'stepford/factory_girl'
|
2
|
+
|
3
|
+
module Stepford
|
4
|
+
module FactoryGirlRspecHelpers
|
5
|
+
[:create, :create_list, :build, :build_list, :build_stubbed].each do |s|
|
6
|
+
class_eval %Q"
|
7
|
+
def #{s}(*args, &block)
|
8
|
+
::Stepford::FactoryGirl.#{s}(*args, &block)
|
9
|
+
end
|
10
|
+
"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec.configure do |c|
|
16
|
+
c.include ::Stepford::FactoryGirlRspecHelpers
|
17
|
+
end
|
data/lib/stepford/version.rb
CHANGED
data/lib/stepford.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.7.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-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: thor
|
@@ -54,6 +54,10 @@ files:
|
|
54
54
|
- lib/stepford/cli.rb
|
55
55
|
- lib/stepford/common.rb
|
56
56
|
- lib/stepford/factory_girl.rb
|
57
|
+
- lib/stepford/factory_girl_cache.rb
|
58
|
+
- lib/stepford/factory_girl_cache_rspec_helpers.rb
|
59
|
+
- lib/stepford/factory_girl_generator.rb
|
60
|
+
- lib/stepford/factory_girl_rspec_helpers.rb
|
57
61
|
- lib/stepford/version.rb
|
58
62
|
- lib/stepford.rb
|
59
63
|
- Rakefile
|