module CsvRowModel module Import class Presenter include Concerns::InheritedClassVar include Concerns::Inspect include ActiveWarnings inherited_class_hash :attributes, dependencies: %i[dependencies] attr_reader :row_model delegate :context, to: :row_model def initialize(row_model) @row_model = row_model end # Safe to override. # # @return [Boolean] returns true, if this instance should be skipped def skip? !valid? end # Safe to override. # # @return [Boolean] returns true, if the entire csv file should stop reading def abort? false end def valid?(*args) super filter_errors errors.empty? end # @return [Presenter] returns the presenter of the previous row_model def previous row_model.previous.try(:presenter) end def attributes self.class.attribute_names .zip(self.class.attribute_names.map { |attribute_name| public_send(attribute_name) }) .to_h end protected # add errors from row_model and remove each dependent attribute from errors if it's row_model_dependencies # are in the errors def filter_errors using_warnings? ? row_model.using_warnings { _filter_errors } : _filter_errors end def _filter_errors row_model.valid? self.class.attribute_names.each do |attribute_name| next unless errors.messages[attribute_name] && row_model.errors.messages.slice(*self.class.options(attribute_name)[:dependencies]).present? errors.delete attribute_name end errors.messages.reverse_merge!(row_model.errors.messages) end # @param [Array] Array of column_names to check # @return [Boolean] if column_names are present def row_model_present?(*column_names) column_names.each { |column_name| return false if row_model.public_send(column_name).blank? } true end # @param [Symbol] attribute_name the attribute to check # @return [Boolean] if the dependencies are valid def valid_dependencies?(attribute_name) row_model_present?(*self.class.options(attribute_name)[:dependencies]) end # equal to: @method_name ||= yield # @param [Symbol] method_name method_name in description # @return [Object] the memoized result def memoize(method_name) variable_name = "@#{method_name}" instance_variable_get(variable_name) || instance_variable_set(variable_name, yield) end class << self # @return [Array] attribute names for the Presenter def attribute_names attributes.keys end # @param [Symbol] attribute_name name of attribute to find option # @return [Hash] options for the attribute_name def options(attribute_name) attributes[attribute_name].first end # @param [Symbol] attribute_name name of attribute to find block # @return [Proc, Lambda] block called for attribute def block(attribute_name) attributes[attribute_name].last end protected # @return [Hash{Symbol => Array}] map of `dependency => [array of presenter attributes dependent on dependency]` def _dependencies dependencies = {} attribute_names.each do |attribute_name| options(attribute_name)[:dependencies].each do |dependency| dependencies[dependency] ||= [] dependencies[dependency] << attribute_name end end dependencies end def inspect_methods @inspect_methods ||= %i[row_model].freeze end # Adds column to the row model # # @param [Symbol] attribute_name name of attribute to add # @param [Proc] block to calculate the attribute # @param options [Hash] # @option options [Hash] :memoize whether to memoize the attribute (default: true) # @option options [Hash] :dependencies the dependcies it has with the underlying row_model (default: []) def attribute(attribute_name, options={}, &block) options = check_and_merge_options(options, memoize: true, dependencies: []) merge_attributes(attribute_name.to_sym => [options, block]) define_attribute_method(attribute_name) end # Define the attribute_method # @param [Symbol] attribute_name name of attribute to add def define_attribute_method(attribute_name) define_method("__#{attribute_name}", &block(attribute_name)) define_method(attribute_name) do return unless valid_dependencies?(attribute_name) self.class.options(attribute_name)[:memoize] ? memoize(attribute_name) { public_send("__#{attribute_name}") } : public_send("__#{attribute_name}") end end end end end end