stepford 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +16 -27
- data/lib/stepford/circular_ref_checker.rb +25 -17
- data/lib/stepford/factory_girl.rb +7 -1
- data/lib/stepford/version.rb +1 -1
- metadata +1 -1
data/README.md
CHANGED
@@ -72,7 +72,7 @@ Put this in your `test/spec_helper.rb`, `spec/spec_helper.rb`, or some other fil
|
|
72
72
|
|
73
73
|
#### Stepford::FactoryGirl
|
74
74
|
|
75
|
-
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.
|
75
|
+
Stepford::FactoryGirl acts just like FactoryGirl, but it goes through all the null=false associations for foreign keys that aren't primary keys 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.
|
76
76
|
|
77
77
|
If you don't specify options, it's easy (note: it is even easier with the rspec helper- see below). 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:
|
78
78
|
|
@@ -101,14 +101,7 @@ Then you can just use `create`, `create_list`, `build`, `build_list`, or `build_
|
|
101
101
|
|
102
102
|
##### Stopping Circular References
|
103
103
|
|
104
|
-
If you have a circular reference (A has NOT NULL foreign key to B that has NOT NULL foreign key to C that has NOT NULL foreign key to A) in the
|
105
|
-
schema, there is a workaround. First, prepopulate one of the models involved in the interdependency chain in the database as part of test setup,
|
106
|
-
or if the ids are NOT NULL but are not foreign key constrained (i.e. if you can enter an invalid ID into the foreign key column, which implies possible
|
107
|
-
referential integrity issues) then you may be able to set them with an invalid id. Take that foreign id and then use the following to ensure
|
108
|
-
that it will set that foreign id or instance. This is done at a global level which may not work for you, but it makes it convenient to put into
|
109
|
-
your spec/spec_helper.rb, etc. For example, let's say your bar has NOT NULL on bartender_id and waiter_id, and in turn bartender and waiter
|
110
|
-
both have a NOT NULL bar_id, and neither enforce foreign keys. Maybe you have preloaded data for waiter somehow as the id '123', but want to set bartender to
|
111
|
-
just use an invalid id '-1', and you want to do it when they are on their second loop. You could use:
|
104
|
+
If you have a circular reference (A has NOT NULL foreign key to B that has NOT NULL foreign key to C that has NOT NULL foreign key to A) either via schema where the foreign key is not also a primary key of the model with the belongs_to, or there is an ActiveRecord presence validation), there is a workaround. First, prepopulate one of the models involved in the interdependency chain in the database as part of test setup, or if the ids are NOT NULL but are not foreign key constrained (i.e. if you can enter an invalid ID into the foreign key column, which implies possible referential integrity issues) then you may be able to set them with an invalid id. Take that foreign id and then use the following to ensure that it will set that foreign id or instance. This is done at a global level which may not work for you, but it makes it convenient to put into your spec/spec_helper.rb, etc. For example, let's say your bar has NOT NULL on bartender_id and waiter_id, and in turn bartender and waiter both have a NOT NULL bar_id, and neither enforce foreign keys. Maybe you have preloaded data for waiter somehow as the id '123', but want to set bartender to just use an invalid id '-1', and you want to do it when they are on their second loop. You could use:
|
112
105
|
|
113
106
|
Stepford::FactoryGirl.stop_circular_refs = {
|
114
107
|
[:bartender, :bar] => {on_loop: 2, set_foreign_key_to: -1},
|
@@ -121,7 +114,7 @@ Stepford has a CLI with a circular reference checker and a generator to automati
|
|
121
114
|
|
122
115
|
##### Refs
|
123
116
|
|
124
|
-
Check ActiveRecord circular dependencies:
|
117
|
+
Check ActiveRecord circular dependencies where the foreign key for a belongs_to is not also a primary key of the model, or there is an ActiveRecord presence validation keeping an association from being null:
|
125
118
|
|
126
119
|
bundle exec stepford circular
|
127
120
|
|
@@ -129,31 +122,27 @@ Then it outputs the circular dependencies, e.g.:
|
|
129
122
|
|
130
123
|
The following non-nullable foreign keys used in ActiveRecord model associations are involved in circular dependencies:
|
131
124
|
|
132
|
-
|
125
|
+
beers.waitress_id -> waitresses.bartender_id -> bartenders.beer_id -> beers.waitress_id
|
133
126
|
|
134
|
-
|
127
|
+
beers.waitress_id -> waitresses.bartender_id -> bartenders.order_id -> order.beer_id -> beers.waitress_id
|
135
128
|
|
136
|
-
waitress.waiter_id -> bar.waiter_id -> waiter.waitress_id
|
137
|
-
|
138
|
-
...
|
139
129
|
|
140
130
|
Distinct foreign keys involved in a circular dependency:
|
141
131
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
waitress.waiter_id
|
132
|
+
beers.waitress_id
|
133
|
+
order.beer_id
|
134
|
+
bartenders.beer_id
|
135
|
+
bartenders.order_id
|
136
|
+
waitresses.bartender_id
|
137
|
+
|
149
138
|
|
150
139
|
Foreign keys by number of circular dependency chains involved with:
|
151
140
|
|
152
|
-
|
153
|
-
2 (out of
|
154
|
-
1 (out of
|
155
|
-
1 (out of
|
156
|
-
|
141
|
+
2 (out of 2): beers.waitress_id -> waitresses
|
142
|
+
2 (out of 2): waitresses.bartender_id -> bartenders
|
143
|
+
1 (out of 2): order.beer_id -> beers
|
144
|
+
1 (out of 2): bartenders.order_id -> order
|
145
|
+
1 (out of 2): bartenders.beer_id -> beers
|
157
146
|
|
158
147
|
##### Factories
|
159
148
|
|
@@ -1,8 +1,6 @@
|
|
1
1
|
module Stepford
|
2
2
|
class CircularRefChecker
|
3
3
|
|
4
|
-
@@model_and_association_names = []
|
5
|
-
@@level = 0
|
6
4
|
@@offenders = []
|
7
5
|
@@circles_sorted = []
|
8
6
|
@@circles = []
|
@@ -26,6 +24,13 @@ module Stepford
|
|
26
24
|
check_associations(model_class)
|
27
25
|
end
|
28
26
|
|
27
|
+
if @@circles.size == 0
|
28
|
+
puts
|
29
|
+
puts "No circular dependencies."
|
30
|
+
puts
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
|
29
34
|
puts "The following non-nullable foreign keys used in ActiveRecord model associations are involved in circular dependencies:"
|
30
35
|
@@circles.sort.each do |c|
|
31
36
|
puts
|
@@ -55,43 +60,46 @@ module Stepford
|
|
55
60
|
t = arr[1]
|
56
61
|
puts "#{t} (out of #{@@circles_sorted.size}): #{c[0]}.#{c[1]} -> #{c[2]}"
|
57
62
|
end
|
63
|
+
puts
|
58
64
|
|
59
|
-
return
|
65
|
+
return false
|
60
66
|
end
|
61
67
|
|
62
|
-
def self.check_associations(model_class)
|
63
|
-
@@level += 1
|
64
|
-
|
68
|
+
def self.check_associations(model_class, model_and_association_names = [])
|
65
69
|
model_class.reflections.collect {|association_name, reflection|
|
66
|
-
@@model_and_association_names = [] if @@level == 1
|
67
70
|
next unless reflection.macro == :belongs_to
|
71
|
+
puts "warning: #{model_class}'s association #{reflection.name}'s foreign_key was nil. can't check." unless reflection.foreign_key
|
68
72
|
assc_sym = reflection.name.to_sym
|
69
73
|
clas_sym = reflection.class_name.underscore.to_sym
|
70
74
|
next_class = clas_sym.to_s.camelize.constantize
|
71
75
|
|
72
76
|
# 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
|
73
77
|
has_presence_validator = model_class.validators_on(assc_sym).collect{|v|v.class}.include?(ActiveModel::Validations::PresenceValidator)
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
78
|
+
# note: supports composite_primary_keys gem which stores primary_key as an array
|
79
|
+
foreign_key_is_also_primary_key = Array.wrap(model_class.primary_key).collect{|pk|pk.to_sym}.include?(reflection.foreign_key.to_sym)
|
80
|
+
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}
|
81
|
+
|
82
|
+
if is_not_null_fkey_that_is_not_primary_key || has_presence_validator
|
83
|
+
key = [model_class.table_name.to_sym, reflection.foreign_key.to_sym, next_class.table_name.to_sym]
|
84
|
+
if model_and_association_names.include?(key)
|
85
|
+
@@offenders << model_and_association_names.last unless @@offenders.include?(model_and_association_names.last)
|
86
|
+
short = model_and_association_names.dup
|
80
87
|
# drop all preceding keys that have nothing to do with the circle
|
81
88
|
(short.index(key)).times {short.delete_at(0)}
|
82
89
|
sorted = short.sort
|
83
90
|
unless @@circles_sorted.include?(sorted)
|
84
91
|
@@circles_sorted << sorted
|
85
|
-
@@circles << "#{(short
|
92
|
+
@@circles << "#{(short + [key]).collect{|b|"#{b[0]}.#{b[1]}"}.join(' -> ')}".to_sym
|
86
93
|
end
|
87
94
|
else
|
88
|
-
|
89
|
-
check_associations(next_class)
|
95
|
+
model_and_association_names << key
|
96
|
+
check_associations(next_class, model_and_association_names)
|
90
97
|
end
|
91
98
|
end
|
92
99
|
}
|
93
100
|
|
94
|
-
|
101
|
+
model_and_association_names.pop
|
102
|
+
model_and_association_names
|
95
103
|
end
|
96
104
|
end
|
97
105
|
end
|
@@ -66,7 +66,13 @@ module Stepford
|
|
66
66
|
has_presence_validator = model_class.validators_on(assc_sym).collect{|v|v.class}.include?(::ActiveModel::Validations::PresenceValidator)
|
67
67
|
required = reflection.foreign_key ? (has_presence_validator || model_class.columns.any?{|c| !c.null && c.name.to_sym == reflection.foreign_key.to_sym}) : false
|
68
68
|
orig_method_args_and_options = with_factory_options ? (with_factory_options[[clas_sym, assc_sym]] || with_factory_options[clas_sym]) : nil
|
69
|
-
if required
|
69
|
+
# 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
|
70
|
+
has_presence_validator = model_class.validators_on(assc_sym).collect{|v|v.class}.include?(ActiveModel::Validations::PresenceValidator)
|
71
|
+
# note: supports composite_primary_keys gem which stores primary_key as an array
|
72
|
+
foreign_key_is_also_primary_key = Array.wrap(model_class.primary_key).collect{|pk|pk.to_sym}.include?(reflection.foreign_key.to_sym)
|
73
|
+
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}
|
74
|
+
|
75
|
+
if is_not_null_fkey_that_is_not_primary_key || has_presence_validator
|
70
76
|
circular_ref_key = [model_sym, assc_sym]
|
71
77
|
all_opts = ::Stepford::FactoryGirl.stop_circular_refs
|
72
78
|
if all_opts.is_a?(Hash) && all_opts.size > 0
|
data/lib/stepford/version.rb
CHANGED