test_dummy 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,113 @@
1
+ class TestDummy::Definition
2
+ # == Extensions ===========================================================
3
+
4
+ # == Properties ===========================================================
5
+
6
+ attr_reader :operations
7
+
8
+ # == Class Methods ========================================================
9
+
10
+ # == Instance Methods =====================================================
11
+
12
+ def initialize(operations = nil)
13
+ @operations = operations ? operations.dup : [ ]
14
+ end
15
+
16
+ # Applies the operations defined in this definition to the model supplied,
17
+ # taking into account any options used for creation and only triggering based
18
+ # on the tags specified.
19
+ def apply!(model, create_options, tags)
20
+ @operations.each do |operation|
21
+ next if (operation.after)
22
+
23
+ operation.apply!(model, create_options, tags)
24
+ end
25
+
26
+ true
27
+ end
28
+
29
+ def apply_after_save!(model, create_options, tags)
30
+ @operations.each do |operation|
31
+ next unless (operation.after == :save)
32
+
33
+ operation.apply!(model, create_options, tags)
34
+ end
35
+
36
+ true
37
+ end
38
+
39
+ # Creates a copy of this Definition.
40
+ def clone
41
+ self.class.new(@operations)
42
+ end
43
+ alias_method :dup, :clone
44
+
45
+ # Returns a list of fields that could be populated with dummy data when the
46
+ # given tags are employed.
47
+ def fields(*tags)
48
+ tags = tags.flatten.compact
49
+
50
+ @operations.each_with_object([ ]) do |operation, collection|
51
+ if (_fields = operation.fields(tags))
52
+ collection.concat(_fields)
53
+ end
54
+ end.compact.uniq
55
+ end
56
+
57
+ def fields?(*matching_fields)
58
+ matching_fields = Hash[
59
+ matching_fields.flatten.compact.collect do |field|
60
+ [ field.to_sym, false ]
61
+ end
62
+ ]
63
+
64
+ @operations.each do |operation|
65
+ operation_fields = operation.fields
66
+
67
+ next unless (operation_fields)
68
+
69
+ operation_fields.each do |field|
70
+ next unless (field)
71
+
72
+ matching_fields[field] = true
73
+ end
74
+ end
75
+
76
+ !matching_fields.find do |field, found|
77
+ !found
78
+ end
79
+ end
80
+
81
+ def [](field)
82
+ field = field.to_sym
83
+
84
+ @operations.select do |operation|
85
+ operation.fields.include?(field)
86
+ end
87
+ end
88
+
89
+ def define_operation(model_class, fields, options)
90
+ if (fields.any?)
91
+ fields.each do |field|
92
+ field_options = options.merge(
93
+ :fields => [ field ].flatten.collect(&:to_sym)
94
+ )
95
+
96
+ model_class, foreign_key = TestDummy::Support.reflection_properties(model_class, field)
97
+
98
+ if (model_class and foreign_key)
99
+ field_options[:model_class] ||= model_class
100
+ field_options[:foreign_key] ||= foreign_key
101
+ end
102
+
103
+ @operations << TestDummy::Operation.new(field_options)
104
+ end
105
+ else
106
+ @operations << TestDummy::Operation.new(options)
107
+ end
108
+ end
109
+
110
+ def <<(operation)
111
+ @operations << operation
112
+ end
113
+ end
@@ -0,0 +1,41 @@
1
+ class TestDummy::Loader
2
+ # == Class Methods ========================================================
3
+
4
+ def self.load!(model_class)
5
+ @instance ||= new
6
+
7
+ @instance[model_class.to_s]
8
+ end
9
+
10
+ # == Instance Methods =====================================================
11
+
12
+ def initialize
13
+ @loaded = { }
14
+ end
15
+
16
+ def [](class_name)
17
+ return @loaded[class_name] if (@loaded.key?(class_name))
18
+
19
+ @loaded[class_name] = nil
20
+
21
+ dummy_path = File.expand_path(
22
+ "#{class_name.to_s.underscore}.rb",
23
+ TestDummy.dummy_extensions_path
24
+ )
25
+
26
+ if (File.exist?(dummy_path))
27
+ begin
28
+ Kernel.load(dummy_path)
29
+
30
+ @loaded[class_name] = true
31
+ rescue LoadError => e
32
+ @loaded[class_name] = e
33
+ end
34
+ else
35
+ @loaded[class_name] = false
36
+ end
37
+ rescue LoadError => e
38
+ # Persist that this load attempt failed and don't retry later.
39
+ @loaded[class_name] = e
40
+ end
41
+ end
@@ -0,0 +1,364 @@
1
+ class TestDummy::Operation
2
+ # == Constants ============================================================
3
+
4
+ VALID_OPTIONS = [
5
+ :block,
6
+ :fields,
7
+ :only,
8
+ :except,
9
+ :after,
10
+ :force,
11
+ :with,
12
+ :from,
13
+ :model_class,
14
+ :foreign_key,
15
+ :inherit
16
+ ].freeze
17
+
18
+ # == Properties ===========================================================
19
+
20
+ attr_reader :source_methods
21
+ attr_reader :source_keys
22
+ attr_reader :only
23
+ attr_reader :except
24
+ attr_reader :after
25
+ attr_reader :model_class
26
+ attr_reader :foreign_key
27
+
28
+ # == Instance Methods =====================================================
29
+
30
+ def initialize(options)
31
+ @blocks = [ ]
32
+
33
+ invalid_options = options.keys - VALID_OPTIONS
34
+
35
+ if (invalid_options.any?)
36
+ raise TestDummy::Exception, "Unknown options to #{self.class}: #{invalid_options.inspect}"
37
+ end
38
+
39
+ assign_block_options!(options)
40
+ assign_fields_options!(options)
41
+
42
+ assign_only_options!(options)
43
+ assign_except_options!(options)
44
+ assign_after_options!(options)
45
+
46
+ assign_force_options!(options)
47
+
48
+ assign_with_options!(options)
49
+
50
+ assign_from_options!(options)
51
+ assign_reflection_options!(options)
52
+ assign_inherit_options!(options)
53
+ assign_reflection_block!(options)
54
+
55
+ assign_default_block!(options)
56
+ end
57
+
58
+ def fields(tags = nil)
59
+ if (trigger?(tags))
60
+ @fields
61
+ else
62
+ [ ]
63
+ end
64
+ end
65
+
66
+ def trigger?(tags)
67
+ if (@only)
68
+ return false if (!tags or (tags & @only).empty?)
69
+ end
70
+
71
+ if (@except)
72
+ return false if (tags and (tags & @except).any?)
73
+ end
74
+
75
+ true
76
+ end
77
+
78
+ def assignments(model, create_options, tags)
79
+ unless (trigger?(tags))
80
+ return false
81
+ end
82
+
83
+ if (@force)
84
+ return @fields
85
+ end
86
+
87
+ # ActiveRecord::Base derived models will return an array of strings listing
88
+ # the fields that have been altered before the model is saved. This
89
+ # includes any fields that have been populated through the constructor
90
+ # call, carried through via scope, or have been altered through accessors.
91
+ if (model and model.respond_to?(:changed))
92
+ # If any of the source methods are listed as "changed", then this is
93
+ # interpreted as a hit, that the fields are already defined or will be.
94
+ if ((@source_methods & model.changed.collect(&:to_sym)).any?)
95
+ return false
96
+ end
97
+ end
98
+
99
+ # If this operation does not populate any fields, then there's no further
100
+ # testing required. The processing can continue without assignments.
101
+ unless (@fields)
102
+ return
103
+ end
104
+
105
+ if (create_options)
106
+ # If any of the source keys are listed in the options, then this is
107
+ # interpreted as a hit, that the fields are already defined or will be.
108
+ if ((@source_keys & create_options.keys.collect(&:to_sym)).any?)
109
+ return false
110
+ end
111
+ end
112
+
113
+ # Unless there's a model defined, at this point, there's nothing else to
114
+ # test and exclude defaults or assignments.
115
+ unless (model)
116
+ return @fields
117
+ end
118
+
119
+ # If there are potentially unassigned fields, the only way to proceed is
120
+ # to narrow it down to the ones that are still `nil`. The fields that are
121
+ # mapped to columns are considered unpopulated or set with defaults because
122
+ # of the previous tests. The remainder need to be tested by calling send.
123
+ columns = model.class.column_names.collect(&:to_sym)
124
+
125
+ applies_to_fields =@fields.select do |field|
126
+ columns.include?(field) or model.__send__(field).nil?
127
+ end
128
+
129
+ # If any of these fields need to be assigned, then that's the next step.
130
+ if (applies_to_fields.any?)
131
+ return applies_to_fields
132
+ end
133
+
134
+ # Otherwise there are fields defined, but none of them need to be assigned,
135
+ # so the operation is not necessary.
136
+ false
137
+ end
138
+
139
+ # Called to apply this operation. The model, create_options and tags
140
+ # arguments can be specified to provide more context.
141
+ def apply!(model, create_options, tags)
142
+ _assignments = assignments(model, create_options, tags)
143
+
144
+ return if (_assignments === false)
145
+
146
+ value = nil
147
+
148
+ # The defined blocks are tried in sequence until one of them returns
149
+ # a non-nil value.
150
+ @blocks.find do |block|
151
+ value =
152
+ case (block.arity)
153
+ when 0
154
+ block.call
155
+ when 1
156
+ block.call(model)
157
+ when 2
158
+ block.call(model, _assignments)
159
+ when 3
160
+ block.call(model, _assignments, tags)
161
+ else
162
+ block.call(model, _assignments, tags, create_options)
163
+ end
164
+
165
+ !value.nil?
166
+ end
167
+
168
+ return unless (_assignments)
169
+
170
+ model and !value.nil? and _assignments.each do |field|
171
+ next unless (field)
172
+
173
+ model.__send__(:"#{field}=", value)
174
+ end
175
+ end
176
+
177
+ protected
178
+ def flatten_any(array)
179
+ array = [ array ].flatten.compact.collect(&:to_sym)
180
+
181
+ return unless (array.any?)
182
+
183
+ array
184
+ end
185
+
186
+ def assign_block_options!(options)
187
+ return unless (options[:block])
188
+
189
+ @blocks << options[:block]
190
+ end
191
+
192
+ def assign_fields_options!(options)
193
+ @fields = options[:fields]
194
+
195
+ @source_keys = [ ]
196
+ @source_methods = [ ]
197
+
198
+ @fields and @fields.each do |field|
199
+ next unless (field)
200
+
201
+ @source_keys << field
202
+ @source_keys << field.to_s
203
+
204
+ @source_methods << field
205
+ end
206
+ end
207
+
208
+ def assign_only_options!(options)
209
+ if (only_options = options[:only])
210
+ @only = flatten_any(only_options)
211
+ end
212
+ end
213
+
214
+ def assign_except_options!(options)
215
+ if (except_options = options[:except])
216
+ @except = flatten_any(except_options)
217
+ end
218
+ end
219
+
220
+ def assign_after_options!(options)
221
+ if (after_option = options[:after])
222
+ @after = after_option
223
+ end
224
+ end
225
+
226
+ def assign_force_options!(options)
227
+ if (options[:force])
228
+ @force = options[:force]
229
+ end
230
+ end
231
+
232
+ def assign_with_options!(options)
233
+ if (with = options[:with])
234
+ @blocks << block_for_with_option(with)
235
+ end
236
+ end
237
+
238
+ def block_for_with_option(with)
239
+ case (with)
240
+ when Proc
241
+ with
242
+ when String, Symbol
243
+ lambda { respond_to?(with) ? send(with) : TestDummy::Helper.send(with) }
244
+ else
245
+ lambda { with }
246
+ end
247
+ end
248
+
249
+ def assign_from_options!(options)
250
+ from = options[:from]
251
+
252
+ return unless (from)
253
+
254
+ case (from)
255
+ when Proc
256
+ @from = from
257
+
258
+ return
259
+ when Array
260
+ from.collect(&:to_sym)
261
+ when Hash
262
+ from = from.to_a
263
+ when String
264
+ from = from.split('.')
265
+ else
266
+ raise TestDummy::Exception, "Argument to :from must be a String, Array or Hash."
267
+ end
268
+
269
+ @blocks << lambda do |model|
270
+ from.inject(model) do |_model, _method|
271
+ _model ? _model.send(_method) : nil
272
+ end
273
+ end
274
+ end
275
+
276
+ def block_for_inherit_options(spec)
277
+ case (spec)
278
+ when Proc
279
+ return spec
280
+ when Array
281
+ spec.collect(&:to_sym)
282
+ when String
283
+ spec = spec.split('.').collect(&:to_sym)
284
+ when Symbol
285
+ spec = [ spec ]
286
+ end
287
+
288
+ lambda do |model|
289
+ spec.inject(model) do |_model, _method|
290
+ _model ? _model.send(_method) : nil
291
+ end
292
+ end
293
+ end
294
+
295
+ def assign_reflection_options!(options)
296
+ return unless (options[:foreign_key])
297
+
298
+ @model_class = options[:model_class]
299
+ @foreign_key = options[:foreign_key]
300
+
301
+ @source_keys << @foreign_key
302
+ @source_keys << @foreign_key.to_s
303
+
304
+ @source_keys.uniq!
305
+
306
+ @source_methods << @foreign_key
307
+
308
+ @source_methods.uniq!
309
+ end
310
+
311
+ def assign_inherit_options!(options)
312
+ inherit = options[:inherit]
313
+
314
+ return unless (inherit)
315
+
316
+ case (inherit)
317
+ when Hash
318
+ # Use as-is
319
+ when Symbol, String
320
+ inherit = { inherit.to_sym => [ inherit.to_sym ] }
321
+ else
322
+ # Not supported, should raise.
323
+ end
324
+
325
+ @inherited_attributes =
326
+ inherit.collect do |attribute, spec|
327
+ [
328
+ attribute.to_sym,
329
+ block_for_inherit_options(spec)
330
+ ]
331
+ end
332
+ end
333
+
334
+ def assign_reflection_block!(options)
335
+ return unless (@model_class)
336
+
337
+ @blocks <<
338
+ if (@inherited_attributes)
339
+ lambda do |model|
340
+ @model_class.create_dummy do |reflection_model|
341
+ @inherited_attributes.each do |attribute, proc|
342
+ reflection_model.send("#{attribute}=", proc.call(model))
343
+ end
344
+ end
345
+ end
346
+ else
347
+ lambda do |model|
348
+ @model_class.create_dummy
349
+ end
350
+ end
351
+ end
352
+
353
+ def assign_default_block!(options)
354
+ return if (@blocks.any? or !@fields or @fields.empty?)
355
+
356
+ @fields.each do |field|
357
+ next unless (field and TestDummy::Helper.respond_to?(field))
358
+
359
+ @blocks << lambda do |model|
360
+ TestDummy::Helper.send(field)
361
+ end
362
+ end
363
+ end
364
+ end