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 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
- ##### Refs
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 hand-edit to fix things, then copy paste stuff, rename it, etc. instead of just using Stepford::FactoryGirl (or deep_* methods in rspec), then keep reading.
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 are a few rspecs that test the FactoryGirl factories and the Stepford deep_builds:
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 { build(factory.name) }
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
- model_class = model_name.camelize.constantize
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
- next_class = clas_sym.to_s.camelize.constantize
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
@@ -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 handle type #{column.type.to_sym}"
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
- model_class = args[0].to_s.camelize.constantize
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 ActiveRecord::RecordInvalid => e
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 ActiveRecord::RecordInvalid => e
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].reverse.each do |i|
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
- model_class = model_name.camelize.constantize
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
- excluded_attributes = Array.wrap(model_class.primary_key).collect{|pk|pk.to_sym} + [:updated_at, :created_at, :object_id]
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
- excluded_attributes << reflection.foreign_key.to_sym if reflection.foreign_key
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 == reflection.foreign_key.to_sym}) : false
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
- if !excluded_attributes.include?(c.name.to_sym) && !(c.name.downcase.end_with?('_id') && options[:exclude_all_ids]) && (options[:attributes] || !c.null)
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 << "sequence(#{c.name.to_sym.inspect})"
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
@@ -1,3 +1,3 @@
1
1
  module Stepford
2
- VERSION = '0.12.2'
2
+ VERSION = '0.13.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.12.2
4
+ version: 0.13.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: