stepford 0.12.2 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +78 -17
- data/lib/stepford/circular_ref_checker.rb +15 -2
- data/lib/stepford/common.rb +20 -1
- data/lib/stepford/factory_girl.rb +14 -6
- data/lib/stepford/factory_girl_generator.rb +27 -6
- data/lib/stepford/version.rb +1 -1
- metadata +1 -1
data/README.md
CHANGED
@@ -129,6 +129,67 @@ Then you can just use `deep_create`, `deep_create_list`, `deep_build`, `deep_bui
|
|
129
129
|
|
130
130
|
deep_create(:foo)
|
131
131
|
|
132
|
+
##### Forcing Attributes and Associations
|
133
|
+
|
134
|
+
If you want to force attributes and associations to be set, use the not_null configuration setting, or hand-edit the factories.rb:
|
135
|
+
|
136
|
+
# each entry looks like [:model_name, :association_or_column_name]
|
137
|
+
Stepford::FactoryGirl.not_null = [
|
138
|
+
[:bartender, :experience],
|
139
|
+
[:patron, :time_entered_bar],
|
140
|
+
]
|
141
|
+
|
142
|
+
##### Cleaning Up
|
143
|
+
|
144
|
+
If you just want to run rspec at command-line, want to be able to create in before hooks, and don't want to mess with database cleaner, here is some code that you can add to your spec_helper to remove all model instances.
|
145
|
+
|
146
|
+
THIS WILL DELETE ALL YOUR DATA! BE EXTREMELY CAREFUL:
|
147
|
+
|
148
|
+
raise "Do you really want to delete all #{Rails.env} data? I think not." unless Rails.env == 'test'
|
149
|
+
|
150
|
+
# ActiveRecord::Base.subclasses doesn't get everything
|
151
|
+
ALL_MODEL_CLASSES = Dir[File.join('app','models','*.rb').to_s].collect do |filename|
|
152
|
+
model_name = File.basename(filename).sub(/.rb$/, '')
|
153
|
+
load File.join('app','models',"#{model_name}.rb")
|
154
|
+
begin
|
155
|
+
model_class = model_name.camelize.constantize
|
156
|
+
rescue => e
|
157
|
+
puts "Problem in #{model_name.camelize}"
|
158
|
+
raise e
|
159
|
+
end
|
160
|
+
next unless model_class.ancestors.include?(ActiveRecord::Base)
|
161
|
+
model_class
|
162
|
+
end.compact
|
163
|
+
|
164
|
+
# can run rspec instead of rake test. FactoryGirl doesn't clean up everything, and DatabaseCleaner is either too slow (truncation) or too transaction-y (transaction).
|
165
|
+
RSpec::Runner.configure do |config|
|
166
|
+
config.before(:suite) do
|
167
|
+
ALL_MODEL_CLASSES.each do |m|
|
168
|
+
begin
|
169
|
+
m.delete_all
|
170
|
+
rescue
|
171
|
+
end
|
172
|
+
end
|
173
|
+
ALL_MODEL_CLASSES.each do |m|
|
174
|
+
count = m.count
|
175
|
+
raise "#{m} not all deleted (found #{count})" if count > 0
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
config.after(:all) do
|
180
|
+
ALL_MODEL_CLASSES.each do |m|
|
181
|
+
begin
|
182
|
+
m.delete_all
|
183
|
+
rescue
|
184
|
+
end
|
185
|
+
end
|
186
|
+
ALL_MODEL_CLASSES.each do |m|
|
187
|
+
count = m.count
|
188
|
+
raise "#{m} not all deleted (found #{count})" if count > 0
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
132
193
|
##### Debugging
|
133
194
|
|
134
195
|
Add somewhere after the require:
|
@@ -139,7 +200,7 @@ Add somewhere after the require:
|
|
139
200
|
|
140
201
|
Stepford has a CLI with a circular reference checker and a generator to automatically create your factories file(s).
|
141
202
|
|
142
|
-
#####
|
203
|
+
##### Circular
|
143
204
|
|
144
205
|
Check ActiveRecord circular dependencies find circular chains of dependencies where foreign keys that are not primary keys of the models are all not nullable in the schema or not nullable because of ActiveRecord presence validation:
|
145
206
|
|
@@ -205,7 +266,7 @@ Specify `--models` and a comma-delimited list of models to only output the model
|
|
205
266
|
|
206
267
|
If you use Stepford::FactoryGirl (or deep_* methods in rspec) to automatically generate factories, you may not need to generate associations, because that sets them for you. If you do choose to use associations, note that these will likely create factories with interdependence issues. 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". Later versions of FactoryGirl may make this easier, and be sure to see notes from Josh down in the troubleshooting section.
|
207
268
|
|
208
|
-
If you are ready to
|
269
|
+
If you are ready to edit factories, copy and paste stuff, rename things, etc. instead of just using Stepford::FactoryGirl or deep_* methods in rspec, then keep reading.
|
209
270
|
|
210
271
|
####### Include Required Assocations
|
211
272
|
|
@@ -249,11 +310,22 @@ Uniqueness constraints on the model are handled by the following being generated
|
|
249
310
|
|
250
311
|
If you have a formatting constraint, some other constraint, or don't like the format of the data in the factories, see the [Factory Girl][factory_girl] documentation to find out how to customize your factories.
|
251
312
|
|
313
|
+
###### Table Sequences
|
314
|
+
|
315
|
+
If a table has no sequence, each primary key will get a FactoryGirl sequence, e.g. if you had a tie table with two sequenceless primary key columns, 'a_id' and 'b_id', it will put this in the factory:
|
316
|
+
|
317
|
+
sequence(:a_id)
|
318
|
+
sequence(:b_id)
|
319
|
+
|
320
|
+
###### Composite Primary Keys
|
321
|
+
|
322
|
+
You can use the [composite_primary_keys][composite_primary_keys] gem and it should work fine.
|
323
|
+
|
252
324
|
##### Testing Factories
|
253
325
|
|
254
326
|
See [Testing all Factories (with RSpec)][test_factories] in the FactoryGirl wiki.
|
255
327
|
|
256
|
-
Here
|
328
|
+
Here is a version that tests the FactoryGirl factories and the Stepford deep_creates:
|
257
329
|
|
258
330
|
require 'spec_helper'
|
259
331
|
require 'stepford/factory_girl_rspec_helpers'
|
@@ -261,22 +333,10 @@ Here are a few rspecs that test the FactoryGirl factories and the Stepford deep_
|
|
261
333
|
describe 'validate factories build' do
|
262
334
|
FactoryGirl.factories.each do |factory|
|
263
335
|
context "with factory for :#{factory.name}" do
|
264
|
-
subject {
|
265
|
-
|
266
|
-
it "is valid" do
|
267
|
-
subject.valid?.should be, subject.errors.full_messages
|
268
|
-
end
|
269
|
-
end
|
270
|
-
end
|
271
|
-
end
|
272
|
-
|
273
|
-
describe 'validate factories deep build' do
|
274
|
-
FactoryGirl.factories.each do |factory|
|
275
|
-
context "with factory for :#{factory.name}" do
|
276
|
-
subject { deep_build(factory.name) }
|
336
|
+
subject { deep_create(factory.name) }
|
277
337
|
|
278
338
|
it "is valid" do
|
279
|
-
subject.valid?.should be, subject.errors.full_messages
|
339
|
+
subject.valid?.should be, subject.errors.full_messages.join(',')
|
280
340
|
end
|
281
341
|
end
|
282
342
|
end
|
@@ -347,6 +407,7 @@ or referring to created objects through associations, though he said multiple ne
|
|
347
407
|
|
348
408
|
Copyright (c) 2012 Gary S. Weaver, released under the [MIT license][lic].
|
349
409
|
|
410
|
+
[composite_primary_keys]: https://github.com/drnic/composite_primary_keys
|
350
411
|
[test_factories]: https://github.com/thoughtbot/factory_girl/wiki/Testing-all-Factories-%28with-RSpec%29
|
351
412
|
[factory_girl]: https://github.com/thoughtbot/factory_girl/
|
352
413
|
[lic]: http://github.com/garysweaver/stepford/blob/master/LICENSE
|
@@ -15,7 +15,14 @@ module Stepford
|
|
15
15
|
model_name = File.basename(filename).sub(/.rb$/, '')
|
16
16
|
next if included_models && !included_models.include?(model_name)
|
17
17
|
load File.join('app','models',"#{model_name}.rb")
|
18
|
-
|
18
|
+
|
19
|
+
begin
|
20
|
+
model_class = model_name.camelize.constantize
|
21
|
+
rescue => e
|
22
|
+
puts "Problem in #{model_name.camelize}"
|
23
|
+
raise e
|
24
|
+
end
|
25
|
+
|
19
26
|
next unless model_class.ancestors.include?(ActiveRecord::Base)
|
20
27
|
models << model_class
|
21
28
|
end
|
@@ -70,7 +77,13 @@ module Stepford
|
|
70
77
|
puts "warning: #{model_class}'s association #{reflection.name}'s foreign_key was nil. can't check." unless reflection.foreign_key
|
71
78
|
assc_sym = reflection.name.to_sym
|
72
79
|
clas_sym = reflection.class_name.underscore.to_sym
|
73
|
-
|
80
|
+
|
81
|
+
begin
|
82
|
+
next_class = reflection.class_name.constantize
|
83
|
+
rescue => e
|
84
|
+
puts "Problem in #{model_class.name} with association: #{reflection.macro} #{assc_sym.inspect} which refers to class #{reflection.class_name}"
|
85
|
+
raise e
|
86
|
+
end
|
74
87
|
|
75
88
|
has_presence_validator = model_class.validators_on(assc_sym).collect{|v|v.class}.include?(ActiveModel::Validations::PresenceValidator)
|
76
89
|
required = false
|
data/lib/stepford/common.rb
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
module Stepford
|
2
2
|
class Common
|
3
|
+
def self.sequence_for(column)
|
4
|
+
case column.type
|
5
|
+
when :string, :text
|
6
|
+
if column.name.to_s['email']
|
7
|
+
# n evaluated at runtime, so pound escaped
|
8
|
+
"sequence(#{column.name.to_sym.inspect}) do |n|; \"person\#{n}@example.com\"; end"
|
9
|
+
else
|
10
|
+
# n evaluated at runtime, so pound escaped
|
11
|
+
"sequence(#{column.name.to_sym.inspect}) do |n|; \"Test #{column.name.titleize} \#{n}\"; end"
|
12
|
+
end
|
13
|
+
when :integer, :decimal, :float, :date, :datetime, :timestamp, :binary, :ts_vector, :boolean
|
14
|
+
"sequence(#{column.name.to_sym.inspect})"
|
15
|
+
when :xml
|
16
|
+
"sequence(#{column.name.to_sym.inspect}) do |n|; \"<test>Test #{column.name.titleize} \#{n}</test>\"; end"
|
17
|
+
else
|
18
|
+
puts "Stepford does not know how to generate a sequence value for column type #{column.type.to_sym}"
|
19
|
+
column.default.nil? ? 'nil' : column.default.to_s
|
20
|
+
end
|
21
|
+
end
|
3
22
|
def self.value_for(column)
|
4
23
|
case column.type
|
5
24
|
when :string, :text
|
@@ -24,7 +43,7 @@ module Stepford
|
|
24
43
|
when :ts_vector
|
25
44
|
column.default.nil? ? 'nil' : column.default.to_s
|
26
45
|
else
|
27
|
-
puts "Stepford does not know how to
|
46
|
+
puts "Stepford does not know how to generate a value for column type #{column.type.to_sym}"
|
28
47
|
column.default.nil? ? 'nil' : column.default.to_s
|
29
48
|
end
|
30
49
|
end
|
@@ -18,7 +18,8 @@ module Stepford
|
|
18
18
|
# end
|
19
19
|
module FactoryGirl
|
20
20
|
OPTIONS = [
|
21
|
-
:debug
|
21
|
+
:debug,
|
22
|
+
:not_null
|
22
23
|
]
|
23
24
|
|
24
25
|
class << self
|
@@ -29,7 +30,13 @@ module Stepford
|
|
29
30
|
|
30
31
|
if args && args.size > 0
|
31
32
|
# call Stepford::FactoryGirl.* on any not null associations recursively
|
32
|
-
|
33
|
+
model_name = args[0]
|
34
|
+
begin
|
35
|
+
model_class = model_name.to_s.camelize.constantize
|
36
|
+
rescue => e
|
37
|
+
puts "Problem in #{model_name.to_s.camelize}" if model_name
|
38
|
+
raise e
|
39
|
+
end
|
33
40
|
|
34
41
|
args = args.dup # need local version because we'll be dup'ing the options hash to add things to set prior to create/build
|
35
42
|
options = args.last
|
@@ -80,7 +87,7 @@ module Stepford
|
|
80
87
|
required = has_presence_validator
|
81
88
|
end
|
82
89
|
|
83
|
-
if required
|
90
|
+
if required || Array.wrap(::Stepford::FactoryGirl.not_null).compact.include?([model_name.to_sym, assc_sym])
|
84
91
|
breadcrumbs << ["a:#{assc_sym}"]
|
85
92
|
if orig_method_args_and_options
|
86
93
|
method_args_and_options = orig_method_args_and_options.dup
|
@@ -93,7 +100,7 @@ module Stepford
|
|
93
100
|
options[assc_sym] = ::FactoryGirl.__send__(*method_args_and_options)
|
94
101
|
end
|
95
102
|
to_reload << options[assc_sym]
|
96
|
-
rescue
|
103
|
+
rescue => e
|
97
104
|
puts "#{breadcrumbs.join('>')}: FactoryGirl.__send__(#{method_args_and_options.inspect}): #{e}#{::Stepford::FactoryGirl.debug? ? "\n#{e.backtrace.join("\n")}" : ''}"
|
98
105
|
raise e
|
99
106
|
end
|
@@ -125,8 +132,9 @@ module Stepford
|
|
125
132
|
end
|
126
133
|
|
127
134
|
begin
|
135
|
+
raise "#{breadcrumbs.join('>')} - Huh? args[0] was #{args[0]}. m=#{m.inspect}, args=#{args.inspect}" if args && args.size > 1 && !(args[0].is_a?(String) || args[0].is_a?(Symbol))
|
128
136
|
result = ::FactoryGirl.__send__(m, *args, &block)
|
129
|
-
rescue
|
137
|
+
rescue => e
|
130
138
|
puts "#{breadcrumbs.join('>')}: FactoryGirl.#{m}(#{args.inspect}): #{e}#{::Stepford::FactoryGirl.debug? ? "\n#{e.backtrace.join("\n")}" : ''}" if defined?(breadcrumbs)
|
131
139
|
raise e
|
132
140
|
end
|
@@ -138,7 +146,7 @@ module Stepford
|
|
138
146
|
orig_options[:to_reload] << result
|
139
147
|
else
|
140
148
|
# ready to return the initially requested instances, so reload children with their parents, in reverse order added
|
141
|
-
orig_options[:to_reload].
|
149
|
+
orig_options[:to_reload].each do |i|
|
142
150
|
begin
|
143
151
|
i.reload
|
144
152
|
rescue => e
|
@@ -10,18 +10,27 @@ module Stepford
|
|
10
10
|
model_name = File.basename(filename).sub(/.rb$/, '')
|
11
11
|
next if included_models && !included_models.include?(model_name)
|
12
12
|
load File.join('app','models',"#{model_name}.rb")
|
13
|
-
|
13
|
+
|
14
|
+
begin
|
15
|
+
model_class = model_name.camelize.constantize
|
16
|
+
rescue => e
|
17
|
+
puts "Problem in #{model_name.camelize}"
|
18
|
+
raise e
|
19
|
+
end
|
20
|
+
|
14
21
|
next unless model_class.ancestors.include?(ActiveRecord::Base)
|
15
22
|
factory = (factories[model_name.to_sym] ||= [])
|
16
|
-
|
23
|
+
pk_syms = Array.wrap(model_class.primary_key).collect{|pk|pk.to_sym}
|
24
|
+
excluded_attributes = pk_syms + [:updated_at, :created_at, :object_id]
|
17
25
|
model_class.reflections.collect {|association_name, reflection|
|
18
26
|
(expected[reflection.class_name.underscore.to_sym] ||= []) << model_name
|
19
|
-
|
27
|
+
fkey_sym = reflection.foreign_key.try(:to_sym)
|
28
|
+
excluded_attributes << fkey_sym if reflection.foreign_key && !(excluded_attributes.include?(fkey_sym))
|
20
29
|
assc_sym = reflection.name.to_sym
|
21
30
|
clas_sym = reflection.class_name.underscore.to_sym
|
22
31
|
# 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
32
|
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 ==
|
33
|
+
required = reflection.foreign_key ? (has_presence_validator || model_class.columns.any?{|c| !c.null && c.name.to_sym == fkey_sym}) : false
|
25
34
|
should_be_trait = !(options[:associations] || (options[:include_required_associations] && required)) && options[:association_traits]
|
26
35
|
if options[:associations] || (options[:include_required_associations] && required) || should_be_trait
|
27
36
|
if reflection.macro == :has_many
|
@@ -39,12 +48,24 @@ module Stepford
|
|
39
48
|
nil
|
40
49
|
end
|
41
50
|
}.compact.sort.each {|l|factory << l}
|
51
|
+
|
52
|
+
sequenceless_table = false
|
53
|
+
begin
|
54
|
+
sequenceless_table = true unless m.sequence_name
|
55
|
+
rescue => e
|
56
|
+
# bug in Rails 3.2.8, at least: undefined method `split' for nil:NilClass in activerecord-3.2.8/lib/active_record/connection_adapters/postgresql_adapter.rb:911:in `default_sequence_name'
|
57
|
+
sequenceless_table = true
|
58
|
+
end
|
59
|
+
|
42
60
|
model_class.columns.sort_by {|c|[c.name]}.each {|c|
|
43
|
-
|
61
|
+
# intentional not checking excluded_attributes/exclude_all_ids when sequenceless. it needs these for create to work.
|
62
|
+
if sequenceless_table && pk_syms.include?(c.name.to_sym)
|
63
|
+
factory << Stepford::Common.sequence_for(c)
|
64
|
+
elsif !excluded_attributes.include?(c.name.to_sym) && !(c.name.to_s.downcase.end_with?('_id') && options[:exclude_all_ids]) && (options[:attributes] || !c.null)
|
44
65
|
has_uniqueness_validator = model_class.validators_on(c.name.to_sym).collect{|v|v.class}.include?(ActiveRecord::Validations::UniquenessValidator)
|
45
66
|
if has_uniqueness_validator
|
46
67
|
#TODO: specialize for different data types
|
47
|
-
factory <<
|
68
|
+
factory << Stepford::Common.sequence_for(c)
|
48
69
|
else
|
49
70
|
factory << "#{is_reserved?(c.name) ? 'self.' : ''}#{c.name} #{Stepford::Common.value_for(c)}"
|
50
71
|
end
|
data/lib/stepford/version.rb
CHANGED