class FilterTable::Factory
Constants
- CustomPropertyType
Public Class Methods
new()
click to toggle source
# File lib/inspec/utils/filter.rb, line 294 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
Public Instance Methods
install_filter_methods_on_resource(resource_class, raw_data_fetcher_method_name)
click to toggle source
# File lib/inspec/utils/filter.rb, line 303 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| # 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
Also aliased as: connect
register_column(property_name, opts = {}, &property_implementation)
Alias for: register_custom_property
register_custom_matcher(property_name, opts = {}, &property_implementation)
Alias for: register_custom_property
register_custom_property(property_name, opts = {}, &property_implementation)
click to toggle source
# File lib/inspec/utils/filter.rb, line 408 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)
click to toggle source
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.
# File lib/inspec/utils/filter.rb, line 393 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
Also aliased as: add_accessor
to_s()
click to toggle source
Calls superclass method
# File lib/inspec/utils/filter.rb, line 310 def to_s @criteria_string || super end
Private Instance Methods
create_custom_property_body(custom_property_struct)
click to toggle source
This provides the implementation for methods requested using register_custom_property
(:some_method_name, opts, &block) Some usage in the wild involves people passing a desired value to the generated method, like:
things.ids(23)
I'm calling this the 'filter_criterion_value'. I speculate that a default value is provided here so that users can meaningfully query for nil.
# File lib/inspec/utils/filter.rb, line 435 def create_custom_property_body(custom_property_struct) if !custom_property_struct.block.nil? # If the custom method provided its own block, rely on it. lambda do |filter_criteria_value = NoCriteriaProvided| # Here self is an instance of the FilterTable subclass that wraps the raw data. # Call the block with two args - the table instance, and any filter criteria value. custom_property_struct.block.call(self, filter_criteria_value) end else # No block definition, so the property was registered using (field: :some_field) # This does however support passing a block to this method, and filtering using it, like Enumerable#select. lambda do |filter_criteria_value = NoCriteriaProvided, &cond_block| if filter_criteria_value == NoCriteriaProvided && !block_given? # No second-order block given. Just return an array of the values in the selected column. result = where(nil) if custom_property_struct.opts[:lazy] result.populate_lazy_field(custom_property_struct.field_name, filter_criteria_value) end result = where(nil).get_column_values(custom_property_struct.field_name) # TODO: the where(nil). is likely unneeded result = result.flatten.uniq.compact if custom_property_struct.opts[:style] == :simple result else # A secondary block was provided. Rely on where() to execute the block, while also filtering on any single value # Suspected bug: if filter_criteria_value == NoCriteriaProvided, this is unlikely to match - see hash condition handling in where() above. where(custom_property_struct.field_name => filter_criteria_value, &cond_block) end end end end