test_dummy 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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