stepford 0.14.1 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +24 -38
- data/lib/stepford/cli.rb +0 -10
- data/lib/stepford/factory_girl/generator.rb +6 -0
- data/lib/stepford/factory_girl/methods.rb +6 -1
- data/lib/stepford/version.rb +1 -1
- metadata +5 -6
- data/lib/stepford/circular_ref_checker.rb +0 -122
data/README.md
CHANGED
@@ -76,7 +76,7 @@ But you can have it autogenerate all attributes, associations, and traits:
|
|
76
76
|
|
77
77
|
end
|
78
78
|
|
79
|
-
However, without modification, if you use the CLI to generate
|
79
|
+
However, without modification, if you use the CLI to generate associations, you may run into association interdependency problems (circular references). To fix those you could hand-edit the factories, or write methods to create what is needed. Or, to keep it simple, just use the defaults for the factories CLI and then use the deep_* methods in your specs to automatically create dependencies as needed!
|
80
80
|
|
81
81
|
### Setup
|
82
82
|
|
@@ -212,40 +212,6 @@ Add somewhere after the require:
|
|
212
212
|
|
213
213
|
#### CLI
|
214
214
|
|
215
|
-
Stepford has a CLI with a circular reference checker and a generator to automatically create your factories file(s).
|
216
|
-
|
217
|
-
##### Circular
|
218
|
-
|
219
|
-
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:
|
220
|
-
|
221
|
-
bundle exec stepford circular
|
222
|
-
|
223
|
-
Example of output:
|
224
|
-
|
225
|
-
The following non-nullable foreign keys used in ActiveRecord model associations are involved in circular dependencies:
|
226
|
-
|
227
|
-
beers.waitress_id -> waitresses.bartender_id -> bartenders.beer_id -> beers.waitress_id
|
228
|
-
|
229
|
-
beers.waitress_id -> waitresses.bartender_id -> bartenders.order_id -> order.beer_id -> beers.waitress_id
|
230
|
-
|
231
|
-
|
232
|
-
Distinct foreign keys involved in a circular dependency:
|
233
|
-
|
234
|
-
beers.waitress_id
|
235
|
-
order.beer_id
|
236
|
-
bartenders.beer_id
|
237
|
-
bartenders.order_id
|
238
|
-
waitresses.bartender_id
|
239
|
-
|
240
|
-
|
241
|
-
Foreign keys by number of circular dependency chains involved with:
|
242
|
-
|
243
|
-
2 (out of 2): beers.waitress_id -> waitresses
|
244
|
-
2 (out of 2): waitresses.bartender_id -> bartenders
|
245
|
-
1 (out of 2): order.beer_id -> beers
|
246
|
-
1 (out of 2): bartenders.order_id -> order
|
247
|
-
1 (out of 2): bartenders.beer_id -> beers
|
248
|
-
|
249
215
|
##### Factories
|
250
216
|
|
251
217
|
###### Creating Factories
|
@@ -358,6 +324,8 @@ Here is a version that tests the FactoryGirl factories and the Stepford deep_cre
|
|
358
324
|
|
359
325
|
##### Troubleshooting
|
360
326
|
|
327
|
+
First, please use [modelist][modelist] to help test your models and the backing schema to ensure everything is kosher.
|
328
|
+
|
361
329
|
If you have duplicate factory definitions during Rails load, it may complain. Just move, rename, or remove the offending files and factories and retry.
|
362
330
|
|
363
331
|
The factories CLI produces factories that use Ruby 1.9 hash syntax. If you aren't using Ruby 1.9, it may not fail during generation, but it might later when loading the factories.
|
@@ -374,7 +342,25 @@ or maybe:
|
|
374
342
|
ActiveRecord::RecordInvalid:
|
375
343
|
Validation failed: Item The item is required., Pricer The pricer is required., Purchased by A purchaser is required.
|
376
344
|
|
377
|
-
then try to use the deep_* methods to build or create.
|
345
|
+
then try to use the deep_* methods to build or create. If still you get an error like:
|
346
|
+
|
347
|
+
ActiveRecord::StatementInvalid:
|
348
|
+
PG::Error: ERROR: null value in column "something_id" violates not-null constraint
|
349
|
+
: INSERT INTO "foobars"
|
350
|
+
|
351
|
+
ensure that the belongs_to association on the model (e.g. Foobar) is using the proper column name. It may need to explicitly set the `:foreign_key` option.
|
352
|
+
|
353
|
+
Stepford needs some help fixing factories for some validations, for example, if attribute foobar on SomeModel can only be "foo" or "bar", then you may get:
|
354
|
+
|
355
|
+
ActiveRecord::RecordInvalid:
|
356
|
+
Validation failed: SomeModel invalid foobar Test Foobar (...)
|
357
|
+
|
358
|
+
In which case you need to hand-edit the some_model factory to set the foobar attribute to "foo" or "bar". Keep in mind that if you have a default set for it in the schema, that will be set as part of stepford factories file generation. You may also want to set default values in your models like this if you can't set a default value in the column in the DB schema itself like the example in [this answer][set_default_values] in StackOverflow:
|
359
|
+
|
360
|
+
after_initialize :init
|
361
|
+
def init
|
362
|
+
self.foobar = 'foo'
|
363
|
+
end
|
378
364
|
|
379
365
|
If you get:
|
380
366
|
|
@@ -383,8 +369,6 @@ If you get:
|
|
383
369
|
|
384
370
|
then note that associations and traits can lead to circular dependencies. Trying generating factories without associations or traits (the default), and use the deep_* methods to create.
|
385
371
|
|
386
|
-
If you still see the 'stack level too deep' error, use the circular CLI to find interreferencing non-nullable foreign keys and fix them.
|
387
|
-
|
388
372
|
ThoughtBot's Josh Clayton provided some suggestions for this, including using methods to generate more complex object structures:
|
389
373
|
|
390
374
|
def post_containing_comment_by_author
|
@@ -421,6 +405,8 @@ or referring to created objects through associations, though he said multiple ne
|
|
421
405
|
|
422
406
|
Copyright (c) 2012 Gary S. Weaver, released under the [MIT license][lic].
|
423
407
|
|
408
|
+
[set_default_values]: http://stackoverflow.com/questions/328525/what-is-the-best-way-to-set-default-values-in-activerecord/5127684#5127684
|
409
|
+
[modelist]: https://github.com/garysweaver/modelist/
|
424
410
|
[column_meta]: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html#method-i-column
|
425
411
|
[composite_primary_keys]: https://github.com/drnic/composite_primary_keys
|
426
412
|
[test_factories]: https://github.com/thoughtbot/factory_girl/wiki/Testing-all-Factories-%28with-RSpec%29
|
data/lib/stepford/cli.rb
CHANGED
@@ -20,16 +20,6 @@ module Stepford
|
|
20
20
|
require 'stepford/factory_girl/generator'
|
21
21
|
exit ::Stepford::FactoryGirl::Generator.generate_factories(options) ? 0 : 1
|
22
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
|
33
23
|
end
|
34
24
|
end
|
35
25
|
|
@@ -29,6 +29,12 @@ module Stepford
|
|
29
29
|
excluded_attribute_syms = [:updated_at, :created_at, :object_id]
|
30
30
|
excluded_attribute_syms_and_pkeys = pkey_syms + [:updated_at, :created_at, :object_id]
|
31
31
|
model_class.reflections.collect {|association_name, reflection|
|
32
|
+
begin
|
33
|
+
reflection.class_name
|
34
|
+
rescue
|
35
|
+
puts "#{model_class.name}.#{association_name} failed when attempting to call reflection's class_name method, so skipped association"
|
36
|
+
next
|
37
|
+
end
|
32
38
|
(expected[reflection.class_name.underscore.to_sym] ||= []) << model_name_sym
|
33
39
|
fkey_sym = reflection.foreign_key.try(:to_sym)
|
34
40
|
excluded_attribute_syms_and_pkeys << fkey_sym if reflection.foreign_key && !(excluded_attribute_syms_and_pkeys.include?(fkey_sym))
|
@@ -61,7 +61,12 @@ module Stepford
|
|
61
61
|
model_class.reflections.each do |association_name, reflection|
|
62
62
|
assc_sym = reflection.name.to_sym
|
63
63
|
next if options[assc_sym] || options[reflection.foreign_key.to_sym] # || reflection.macro != :belongs_to
|
64
|
-
|
64
|
+
begin
|
65
|
+
reflection.class_name
|
66
|
+
rescue
|
67
|
+
puts "#{model_class.name}.#{association_name} failed when attempting to call reflection's class_name method, so skipped association"
|
68
|
+
next
|
69
|
+
end
|
65
70
|
clas_sym = reflection.class_name.underscore.to_sym
|
66
71
|
has_presence_validator = model_class.validators_on(assc_sym).collect{|v|v.class}.include?(::ActiveModel::Validations::PresenceValidator)
|
67
72
|
required = reflection.foreign_key ? (has_presence_validator || model_class.columns.any?{|c| !c.null && c.name.to_sym == reflection.foreign_key.to_sym}) : false
|
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.15.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-
|
12
|
+
date: 2012-12-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: thor
|
@@ -43,9 +43,9 @@ dependencies:
|
|
43
43
|
- - ! '>='
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
|
-
description:
|
47
|
-
|
48
|
-
|
46
|
+
description: Automates FactoryGirl deep creation of models and their required associations
|
47
|
+
avoiding circulars and provides a generator for FactoryGirl factories that reflects
|
48
|
+
on models.
|
49
49
|
email:
|
50
50
|
- garysweaver@gmail.com
|
51
51
|
executables:
|
@@ -53,7 +53,6 @@ executables:
|
|
53
53
|
extensions: []
|
54
54
|
extra_rdoc_files: []
|
55
55
|
files:
|
56
|
-
- lib/stepford/circular_ref_checker.rb
|
57
56
|
- lib/stepford/cli.rb
|
58
57
|
- lib/stepford/column_representation.rb
|
59
58
|
- lib/stepford/common.rb
|
@@ -1,122 +0,0 @@
|
|
1
|
-
module Stepford
|
2
|
-
class CircularRefChecker
|
3
|
-
|
4
|
-
@@offenders = []
|
5
|
-
@@circles_sorted = []
|
6
|
-
@@circles = []
|
7
|
-
@@selected_offenders = []
|
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
|
-
|
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
|
-
|
26
|
-
next unless model_class.ancestors.include?(ActiveRecord::Base)
|
27
|
-
models << model_class
|
28
|
-
end
|
29
|
-
|
30
|
-
models.each do |model_class|
|
31
|
-
check_associations(model_class)
|
32
|
-
end
|
33
|
-
|
34
|
-
if @@circles.size == 0
|
35
|
-
puts
|
36
|
-
puts "No circular dependencies."
|
37
|
-
puts
|
38
|
-
return true
|
39
|
-
end
|
40
|
-
|
41
|
-
puts "The following non-nullable foreign keys used in ActiveRecord model associations are involved in circular dependencies:"
|
42
|
-
@@circles.sort.each do |c|
|
43
|
-
puts
|
44
|
-
puts "#{c}"
|
45
|
-
end
|
46
|
-
puts
|
47
|
-
puts
|
48
|
-
puts "Distinct foreign keys involved in a circular dependency:"
|
49
|
-
puts
|
50
|
-
@@offenders.sort.each do |c|
|
51
|
-
puts "#{c[0]}.#{c[1]}"
|
52
|
-
end
|
53
|
-
|
54
|
-
totals = {}
|
55
|
-
@@circles_sorted.each do |arr|
|
56
|
-
arr.each do |key|
|
57
|
-
totals[key] = 0 unless totals[key]
|
58
|
-
totals[key] = totals[key] + 1
|
59
|
-
end
|
60
|
-
end
|
61
|
-
puts
|
62
|
-
puts
|
63
|
-
puts "Foreign keys by number of circular dependency chains involved with:"
|
64
|
-
puts
|
65
|
-
totals.sort_by {|k,v| v}.reverse.each do |arr|
|
66
|
-
c = arr[0]
|
67
|
-
t = arr[1]
|
68
|
-
puts "#{t} (out of #{@@circles_sorted.size}): #{c[0]}.#{c[1]} -> #{c[2]}"
|
69
|
-
end
|
70
|
-
puts
|
71
|
-
|
72
|
-
return false
|
73
|
-
end
|
74
|
-
|
75
|
-
def self.check_associations(model_class, model_and_association_names = [])
|
76
|
-
model_class.reflections.collect {|association_name, reflection|
|
77
|
-
puts "warning: #{model_class}'s association #{reflection.name}'s foreign_key was nil. can't check." unless reflection.foreign_key
|
78
|
-
assc_sym = reflection.name.to_sym
|
79
|
-
|
80
|
-
begin
|
81
|
-
next_class = reflection.class_name.constantize
|
82
|
-
rescue => e
|
83
|
-
puts "Problem in #{model_class.name} with association: #{reflection.macro} #{assc_sym.inspect} which refers to class #{reflection.class_name}"
|
84
|
-
raise e
|
85
|
-
end
|
86
|
-
|
87
|
-
has_presence_validator = model_class.validators_on(assc_sym).collect{|v|v.class}.include?(ActiveModel::Validations::PresenceValidator)
|
88
|
-
required = false
|
89
|
-
if reflection.macro == :belongs_to
|
90
|
-
# note: supports composite_primary_keys gem which stores primary_key as an array
|
91
|
-
foreign_key_is_also_primary_key = Array.wrap(model_class.primary_key).collect{|pk|pk.to_sym}.include?(reflection.foreign_key.to_sym)
|
92
|
-
is_not_null_fkey_that_is_not_primary_key = model_class.columns.any?{|c| !c.null && c.name.to_sym == reflection.foreign_key.to_sym && !foreign_key_is_also_primary_key}
|
93
|
-
required = is_not_null_fkey_that_is_not_primary_key || has_presence_validator
|
94
|
-
else
|
95
|
-
# no nullable metadata on column if no foreign key in this table. we'd figure out the null requirement on the column if inspecting the child model
|
96
|
-
required = has_presence_validator
|
97
|
-
end
|
98
|
-
|
99
|
-
if required
|
100
|
-
key = [model_class.table_name.to_sym, reflection.foreign_key.to_sym, next_class.table_name.to_sym]
|
101
|
-
if model_and_association_names.include?(key)
|
102
|
-
@@offenders << model_and_association_names.last unless @@offenders.include?(model_and_association_names.last)
|
103
|
-
short = model_and_association_names.dup
|
104
|
-
# drop all preceding keys that have nothing to do with the circle
|
105
|
-
(short.index(key)).times {short.delete_at(0)}
|
106
|
-
sorted = short.sort
|
107
|
-
unless @@circles_sorted.include?(sorted)
|
108
|
-
@@circles_sorted << sorted
|
109
|
-
@@circles << "#{(short + [key]).collect{|b|"#{b[0]}.#{b[1]}"}.join(' -> ')}".to_sym
|
110
|
-
end
|
111
|
-
else
|
112
|
-
model_and_association_names << key
|
113
|
-
check_associations(next_class, model_and_association_names)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
}
|
117
|
-
|
118
|
-
model_and_association_names.pop
|
119
|
-
model_and_association_names
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|