Class: FilterTable::Factory

Inherits:
Object
  • Object
show all
Defined in:
lib/inspec/utils/filter.rb

Defined Under Namespace

Classes: CustomPropertyType

Instance Method Summary collapse

Constructor Details

#initializeFactory

Returns a new instance of Factory.



288
289
290
291
292
293
294
295
# File 'lib/inspec/utils/filter.rb', line 288

def initialize
  @filter_methods = %i{where entries raw_data}
  @custom_properties = {}
  register_custom_matcher(:exist?) { |table| !table.raw_data.empty? }
  register_custom_property(:count) { |table|  table.raw_data.count }

  @resource = nil # TODO: this variable is never initialized
end

Instance Method Details

#install_filter_methods_on_resource(resource_class, raw_data_fetcher_method_name) ⇒ Object Also known as: connect

rubocop: disable Metrics/AbcSize, Metrics/MethodLength



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/inspec/utils/filter.rb', line 297

def install_filter_methods_on_resource(resource_class, raw_data_fetcher_method_name) # rubocop: disable Metrics/AbcSize, Metrics/MethodLength
  # A context in which you can access the fields as accessors
  non_block_struct_fields = @custom_properties.values.reject(&:block).map(&:field_name)
  unless non_block_struct_fields.empty?
    row_eval_context_type = Struct.new(*non_block_struct_fields.map(&:to_sym)) do
      attr_accessor :criteria_string
      attr_accessor :filter_table
      def to_s
        @criteria_string || super
      end
    end
  end

  properties_to_define = @custom_properties.map do |method_name, custom_property_structure|
    { method_name: method_name, method_body: create_custom_property_body(custom_property_structure) }
  end

  # Define the filter table subclass
  custom_properties = @custom_properties # We need a local var, not an instance var, for a closure below
  table_class = Class.new(Table) do
    # Install each custom property onto the FilterTable subclass
    properties_to_define.each do |property_info|
      define_method property_info[:method_name], &property_info[:method_body]
    end

    define_method :custom_properties_schema do
      custom_properties
    end

    # Install a method that can wrap all the fields into a context with accessors
    define_method :create_eval_context_for_row do |row_as_hash, criteria_string = ""|
      return row_eval_context_type.new if row_as_hash.nil?

      context = row_eval_context_type.new(*non_block_struct_fields.map { |field| row_as_hash[field] })
      context.criteria_string = criteria_string
      context.filter_table = self
      context
    end
  end

  # Now that the table class is defined and the row eval context struct is defined,
  # extend the row eval context struct to support triggering population of lazy fields
  # in where blocks. To do that, we'll need a reference to the table (which
  # knows which fields are populated, and how to populate them) and we'll need to
  # override the getter method for each lazy field, so it will trigger
  # population if needed.  Keep in mind we don't have to adjust the constructor
  # args of the row struct; also the Struct class will already have provided
  # a setter for each field.
  @custom_properties.values.each do |property_info|
    next unless property_info.opts[:lazy]

    field_name = property_info.field_name.to_sym
    row_eval_context_type.send(:define_method, field_name) do
      unless filter_table.field_populated?(field_name)
        filter_table.populate_lazy_field(field_name, NoCriteriaProvided) # No access to criteria here
        # OK, the underlying raw data has the value in the first row
        # (because we would trigger population only on the first row)
        # We could just return the value, but we need to set it on this Struct in case it is referenced multiple times
        # in the where block.
        self[field_name] = filter_table.raw_data[0][field_name]
      end
      # Now return the value using the Struct getter, whether newly populated or not
      self[field_name]
    end
  end

  # Define all access methods with the parent resource
  # These methods will be configured to return an `ExceptionCatcher` object
  # that will always return the original exception, but only when called
  # upon. This will allow method chains in `describe` statements to pass the
  # `instance_eval` when loaded and only throw-and-catch the exception when
  # the tests are run.
  methods_to_install_on_resource_class = @filter_methods + @custom_properties.keys
  methods_to_install_on_resource_class.each do |method_name|
    resource_class.send(:define_method, method_name) do |*args, &block|
      begin
        # self here is the resource instance
        filter_table_instance = table_class.new(self, send(raw_data_fetcher_method_name), " with")
        filter_table_instance.send(method_name, *args, &block)
      rescue Inspec::Exceptions::ResourceFailed, Inspec::Exceptions::ResourceSkipped => e
        FilterTable::ExceptionCatcher.new(resource_class, e)
      end
    end
  end
end

#register_custom_property(property_name, opts = {}, &property_implementation) ⇒ Object Also known as: add, register_column, register_custom_matcher



402
403
404
405
406
407
408
409
410
411
412
413
414
415
# File 'lib/inspec/utils/filter.rb', line 402

def register_custom_property(property_name, opts = {}, &property_implementation)
  if property_name.nil?
    # TODO: @resource is never initialized
    throw RuntimeError, "Called filter.add for resource #{@resource} with method name nil!"
  end

  if @custom_properties.key?(property_name.to_sym)
    # TODO: issue deprecation warning?
  else
    @custom_properties[property_name.to_sym] =
      CustomPropertyType.new(opts[:field] || property_name, property_implementation, opts)
  end
  self
end

#register_filter_method(method_name) ⇒ Object Also known as: add_accessor

TODO: This should almost certainly be privatized. Every FilterTable client should get :entries and :where; InSpec core resources do not define anything else, other than azure_generic_resource, which is likely a mis-use.



387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/inspec/utils/filter.rb', line 387

def register_filter_method(method_name)
  if method_name.nil?
    # TODO: @resource is never initialized
    throw RuntimeError, "Called filter.add_accessor for resource #{@resource} with method name nil!"
  end
  if @filter_methods.include? method_name.to_sym
    # TODO: issue deprecation warning?
  else
    @filter_methods.push(method_name.to_sym)
  end
  self
end