module GdprExporter::ClassMethods

Public Instance Methods

gdpr_collect(*args) click to toggle source

Declared in each model class with interest in collecting gdpr data. Instruments the singleton of those classes so that gdpr data can be collected and exported to csv.

Arguments are:

  • set of simple fields: i.e. fields that will be output as is

  • a hash of params:

{renamed_fields: {<field_from_db> => <field_name_in_output>}

table_name:     <the new table name in output>
description:    <a comment>
join:           <an association>}
# File lib/gdpr_exporter.rb, line 52
def gdpr_collect(*args)
  # Params handling
  if args.class == Hash # when user provides the hash_params only
    simple_fields, hash_params = [[], args]
  else
    simple_fields, hash_params = [args[0..-2], args.last]
  end

  unless hash_params.class == Hash
    raise ArgumentError.new("Gdpr fields collection error: last argument must be a hash!")
  end

  unless hash_params.key?(:user_id)
    raise ArgumentError.new("Gdpr fields collection error: the field aliasing user_id is not declared for '#{self}'!")
  end

  # Adds the eigen class to the set of classes eligible for gdpr data collection.
  GdprExporter.add_klass(self)

  # Adds instance fields to the eigenclass. They store
  # all the fields and info we are interested in.
  @gdpr_simple_fields = simple_fields
  @gdpr_hash_params = hash_params
  # Add readers for the instance vars declared above (for testing reasons)
  self.class.send :attr_reader, :gdpr_simple_fields
  self.class.send :attr_reader, :gdpr_hash_params

  # Build the csv header and prepare the fields used for querying
  #
  user_id_field = hash_params[:user_id]
  # csv_headers = [:user_id].concat @gdpr_simple_fields # Uncomment if user_id needed
  # query_fields = [user_id_field].concat @gdpr_simple_fields # Uncomment if user_id needed
  csv_headers = [].concat @gdpr_simple_fields
  query_fields = [].concat @gdpr_simple_fields

  if hash_params[:renamed_fields]
    csv_headers.concat hash_params[:renamed_fields].values
    query_fields.concat hash_params[:renamed_fields].keys
  end

  # Adds the class method 'gdpr_query' to the eigenclass.
  # It will execute the query.
  self.define_singleton_method(:gdpr_query) do |_user_id|
    decomposed_user_id_field = user_id_field.to_s.split(" ")
    result = case
             when decomposed_user_id_field.size == 3
               self
                 .includes(decomposed_user_id_field.first.to_sym => decomposed_user_id_field.second.to_sym)
                 .where(decomposed_user_id_field.second.pluralize.to_sym => { decomposed_user_id_field.last.to_sym => _user_id })
             when decomposed_user_id_field.size == 2
               self
                 .includes(decomposed_user_id_field.first.to_sym)
                 .where(decomposed_user_id_field.first.pluralize.to_sym => { decomposed_user_id_field.last.to_sym => _user_id })
             else
               self.where(user_id_field => _user_id)
             end

    # When there are multiple joins defined, just keep calling 'joins'
    # for each association.
    if hash_params[:joins]
      result = hash_params[:joins].inject(result) do | query, assoc |
        query.send(:joins, assoc)
      end
    end

    result
  end

  # Adds a method to export to csv to the eigenclass.
  self.define_singleton_method(:gdpr_export) do |rows, csv|
    return unless !rows.empty?

    csv << (hash_params[:table_name] ? [hash_params[:table_name]] :
              [self.to_s])

    if hash_params[:desc]
      csv << ['Description:', hash_params[:desc]]
    end

    csv << csv_headers
    rows.each do |r|
      csv << query_fields.map do |f|
        f_splitted = f.to_s.split(' ')
        if (f_splitted.size == 2)
          # field f is coming from an assoc, i.e. field has been defined
          # as "<tablename> <field>" in gdpr_collect then to get its value
          # do r.<tablename>.<field>
          f_splitted.inject(r) { |result, method| result.send(method) }
        elsif (f_splitted.size > 2)
          raise ArgumentError.new("Field #{f} is made of more than 2 words!?")
        else
          # No association involved, simply retrieve the field value.
          r.send(f)
        end
      end
    end
    csv << []
  end
end