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 CHANGED
@@ -1,7 +1,26 @@
1
1
  Stepford
2
2
  =====
3
3
 
4
- Stepford is a CLI to create starter [Factory Girl][factory_girl] factories for all of your Rails models, e.g.
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
- ##### Singleton Values
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/factory_girl'
21
- exit Stepford::FactoryGirl.generate_factories(options) ? 0 : 1
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 'stepford/common'
1
+ require 'factory_girl'
2
2
 
3
3
  module Stepford
4
- class FactoryGirl
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]
17
- association_lines = 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] || required) && options[:association_traits]
26
- if options[:associations] || required || should_be_trait
27
- if options[:cache_associations]
28
- if reflection.macro == :has_many
29
- #
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}#{clas_sym == assc_sym ? '' : ", #{assc_sym.inspect}"}, 2); end#{should_be_trait ? '; end' : ''}"
31
- else
32
- "#{should_be_trait ? "trait #{"with_#{assc_sym}".to_sym.inspect} do; " : ''}before(:build) do |user, evaluator|; #{is_reserved?(assc_sym) ? 'self.' : ''}#{assc_sym} = FactoryGirlCache.create(#{clas_sym.inspect}#{clas_sym == assc_sym ? '' : ", #{assc_sym.inspect}"}); end#{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 << "#{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
- private
124
-
125
- def self.is_reserved?(s)
126
- # 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
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
- 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]
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
- def self.write_header(f, options)
147
- f.puts 'require \'factory_girl_rails\''
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
@@ -1,3 +1,3 @@
1
1
  module Stepford
2
- VERSION = '0.6.0'
2
+ VERSION = '0.7.0'
3
3
  end
data/lib/stepford.rb CHANGED
@@ -1,3 +1 @@
1
1
  require 'stepford/version'
2
- require 'stepford/common'
3
-
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.6.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-06 00:00:00.000000000 Z
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