class Eco::API::Common::People::EntryFactory

TODO: EntryFactory should suppport multiple schemas itself (rather that being done on `Session`)

> currently, it's through session.entry_factory(schema: id), but this is wrong

> This way, Entries and PersonEntry will be able to refer to attr_map and person_parser linked to schema_id

> “schema_id” should be an optional column in the input file, or parsable via a custom parser to scope the schema

Helper factory class to generate entries (input entries). @attr_reader schema [Ecoportal::API::V1::PersonSchema] person schema to be used in this entry factory

Attributes

person_parser[R]
schema[R]

Public Class Methods

new(e, schema:, person_parser: nil, default_parser: nil, attr_map: nil) click to toggle source

@param e [Eco::API::Common::Session::Environment] requires a session environment, as any child of `Eco::API::Common::Session::BaseSession` @param schema [Ecoportal::API::V1::PersonSchema] schema of person details that the parser will be based upon. @param person_parser [nil, Eco::API::Common::People::PersonParser] set of attribute, type and format parsers/serializers. @param attr_map [nil, Eco::Data::Mapper] attribute names mapper to translate external names into internal ones and _vice versa_.

# File lib/eco/api/common/people/entry_factory.rb, line 19
def initialize(e, schema:, person_parser: nil, default_parser: nil, attr_map: nil)
  fatal "Constructor needs a PersonSchema. Given: #{schema}" if !schema.is_a?(Ecoportal::API::V1::PersonSchema)
  fatal "Expecting PersonParser. Given: #{person_parser}"    if person_parser && !person_parser.is_a?(Eco::API::Common::People::PersonParser)
  fatal "Expecting Mapper object. Given: #{fields_mapper}"   if attr_map && !attr_map.is_a?(Eco::Data::Mapper)
  super(e)

  @schema = Ecoportal::API::V1::PersonSchema.new(JSON.parse(schema.doc.to_json))
  @source_person_parser = person_parser

  # load default parser + custom parsers
  @default_parser = default_parser&.new(schema: @schema) || Eco::API::Common::People::DefaultParsers.new(schema: @schema)
  base_parser = @default_parser.merge(@source_person_parser)
  # new parser with linked schema
  @person_parser = @source_person_parser.new(schema: @schema).merge(base_parser)
  @person_parser_patch_version = @source_person_parser.patch_version
  @attr_map = attr_map
end

Public Instance Methods

entries(data: (no_data = true; nil), file: (no_file = true; nil), format: (no_format = true; nil), **options) click to toggle source

Helper that provides a collection of `Entries`, which in turn provides with further helpers to find and exclude entries. It accepts a `file:` and `format:` or `data:` but not both options together. @raise Exception

- if you try to provide `data:` and `file:` at the same time.
- if you provide `file:` but omit `format:`.
- if the `format:` you provide is not a `Symbol`.
- if there is no _parser/serializer_ defined for `format:`.

@param data [Array<Hash>] data to be parsed. It cannot be used alongside with `file:` @param file [String] absolute or relative path to the input file. It cannot be used alongside with `data:`. @param format [Symbol] it must be used when you use the option `file:` (i.e. `:xml`, `:csv`), as it specifies the format of the input `file:`. @param options [Hash] further options. @option options [String] :encoding optional parameter to read `file:` by expecting certain encoding. @option options [Boolean] :check_headers signals if the `csv` file headers should be expected. @return [Eco::API::Common::People::Entries] collection of `Eco::API::Common::People::PersonEntry`.

# File lib/eco/api/common/people/entry_factory.rb, line 87
def entries(data: (no_data = true; nil), file: (no_file = true; nil), format: (no_format = true; nil), **options)
  fatal("You should at least use data: or file:, but not both") if no_data == no_file
  fatal("You must specify a valid format: (symbol) when you use file.") if file && no_format
  fatal("Format should be a Symbol. Given '#{format}'") if format && !format.is_a?(Symbol)
  fatal("There is no parser/serializer for format ':#{format.to_s}'") unless no_format || @person_parser.defined?(format)

  options.merge!(content:  data)     unless no_data
  options.merge!(file:     file)     unless no_file
  options.merge!(format:   format)   unless no_format

  Entries.new(to_array_of_hashes(**options), klass: PersonEntry, factory: self)
end
export(data:, file: "export", format: :csv, encoding: "utf-8", internal_names: false) click to toggle source

Helper that generates a file out of `data:`. @raise Exception

- if you try to provide `data:` in the wrong format.
- if you `file:` is empty.
- if the `format:` you provide is not a `Symbol`.
- if there is no _parser/serializer_ defined for `format:`.

@param data [Eco::API::Organization::People] data to be parsed. @param file [String] absolute or relative path to the ouput file. @param format [Symbol] it specifies the format of the output `file:` (i.e. `:xml`, `:csv`). There must be a parser/serializer defined for it. @param encoding [String] optional parameter to geneate `file:` content by unsing certain encoding. @return [Void].

# File lib/eco/api/common/people/entry_factory.rb, line 160
def export(data:, file: "export", format: :csv, encoding: "utf-8", internal_names: false)
  fatal("data: Expected Eco::API::Organization::People object. Given: #{data.class}") unless data.is_a?(Eco::API::Organization::People)
  fatal("A file should be specified.") unless !file.to_s.strip.empty?
  fatal("Format should be a Symbol. Given '#{format}'") if format && !format.is_a?(Symbol)
  fatal("There is no parser/serializer for format ':#{format.to_s}'") unless @person_parser.defined?(format)

  run = true
  if Eco::API::Common::Session::FileManager.file_exists?(file)
    prompt_user("Do you want to overwrite it? (Y/n):", explanation: "The file '#{file}' already exists.", default: "Y") do |response|
      run = (response == "") || reponse.upcase.start_with?("Y")
    end
  end

  if run
    deps = {"supervisor_id" => {people: data}}

    data_entries = data.map do |person|
      self.new(person, dependencies: deps).yield_self do |entry|
        internal_names ? entry.mapped_entry : entry.external_entry
      end
    end

    File.open(file, "w", enconding: encoding) do |fd|
      fd.write(person_parser.serialize(format, data_entries))
    end
  end

end
new(data, dependencies: {}) click to toggle source

key method to generate objects of `PersonEntry` that share dependencies via this `EntryFactory` environment. @note this method is necessary to make the factory object work as a if it was a class `PersonEntry` you can call `new` on. @param data [Hash, Person] data to be parsed/serialized. Parsed: the external hashed entry. Serialized: a Person object. @return [Eco::API::Common::People::PersonEntry]

# File lib/eco/api/common/people/entry_factory.rb, line 63
def new(data, dependencies: {})
  PersonEntry.new(
    data,
    person_parser: person_parser,
    attr_map:      @attr_map,
    dependencies:  dependencies,
    logger:        logger
  )
end
newFactory(schema: nil) click to toggle source
# File lib/eco/api/common/people/entry_factory.rb, line 37
def newFactory(schema: nil)
  self.class.new(
    environment,
    schema: schema,
    person_parser: @source_person_parser,
    default_parser: @default_parser,
    attr_map: @attr_map
  )
end
to_array_of_hashes(**kargs) click to toggle source
# File lib/eco/api/common/people/entry_factory.rb, line 100
def to_array_of_hashes(**kargs)
  data = []
  content, file, encoding, format = kargs.values_at(:content, :file, :encoding, :format)

  # Support for multiple file
  if file.is_a?(Array)
    return file.each_with_object([]) do |f, out|
      logger.info("Parsing file '#{f}'")
      curr = to_array_of_hashes(**kargs.merge(file: f))
      out.concat(curr)
    end
  end
  # Get content only when it's not :xls
  # note: even if content was provided, file takes precedence
  content = get_file_content(file, format, encoding) if (format != :xls) && file

  case content
  when Hash
    logger.error("Input data as 'Hash' not supported. Expecting 'Enumerable' or 'String'")
    exit(1)
  when String
    deps = {check_headers: true} if kargs[:check_headers]
    to_array_of_hashes(content: person_parser.parse(format, content, deps: deps || {}))
  when Enumerable
    sample = content.to_a.first
    case sample
    when Hash, Array, ::CSV::Row
      Eco::CSV::Table.new(content).to_array_of_hashes
    else
      logger.error("Input content 'Array' of '#{sample.class}' is not supported.")
    end
  else
    if file && format == :xls
      person_parser.parse(format, file)
    else
      logger.error("Could not obtain any data out of these: #{kargs}. Given content: '#{content.class}'")
      exit(1)
    end
  end.tap do |out_array|
    start_from_two = (format == :csv) || format == :xls
    out_array.each_with_index do |entry_hash, i|
      entry_hash["idx"] = start_from_two ? i + 2 : i + 1
      entry_hash["source_file"] = file
    end
  end

end

Private Instance Methods

fatal(msg) click to toggle source
# File lib/eco/api/common/people/entry_factory.rb, line 202
def fatal(msg)
  logger.fatal(msg)
  raise msg
end
get_file_content(file, format, encoding) click to toggle source
# File lib/eco/api/common/people/entry_factory.rb, line 191
def get_file_content(file, format, encoding)
  unless Eco::API::Common::Session::FileManager.file_exists?(file)
    logger.error("File does not exist: #{file}")
    exit(1)
  end
  ext        = File.extname(file)
  encoding ||= Eco::API::Common::Session::FileManager.encoding(file)
  encoding   = (encoding != "utf-8")? "#{encoding}|utf-8": encoding
  content    = File.read(file, encoding: encoding)
end