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 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 these you could more likely run into association interdependency problems (circular references). To fix those you could hand-edit the factories, or write methods to create more complex structures. 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!
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
@@ -1,3 +1,3 @@
1
1
  module Stepford
2
- VERSION = '0.14.1'
2
+ VERSION = '0.15.0'
3
3
  end
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.14.1
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-11-15 00:00:00.000000000 Z
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: Has deep_* methods for automating FactoryGirl creation with required
47
- association trees and small tweaks, ActiveRecord circular required reference checker
48
- CLI, and a nice flexible FactoryGirl factories code generator CLI.
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