Class: Eco::API::Common::People::EntryFactory

Inherits:
Session::BaseSession show all
Includes:
Data::Files
Defined in:
lib/eco/api/common/people/entry_factory.rb

Overview

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).

Constant Summary

Constants included from Data::Files

Data::Files::DEFAULT_TIMESTAMP_PATTERN

Constants included from Data::Files::Encoding

Data::Files::Encoding::BOM_BYTES

Instance Attribute Summary collapse

Attributes included from Language::AuxiliarLogger

#logger

Attributes inherited from Session::BaseSession

#config, #environment, #session

Instance Method Summary collapse

Methods included from Data::Files::ClassMethods

#copy_file, #create_directory, #csv_files, #dir_exists?, #file_basename, #file_empty?, #file_exists?, #file_fullpath, #file_name, #file_path, #folder_files, #script_subfolder, #split, #timestamp, #timestamp_file

Methods included from Data::Files::Encoding

#encoding, #file_empty?, #file_exists?, #get_file_content_with_encoding, #has_bom?, #remove_bom, #scoped_encoding

Methods included from Language::AuxiliarLogger

#log

Methods included from Data::Files::InstanceMethods

#get_file_content, #read_with_tolerance

Methods inherited from Session::BaseSession

#api, #api?, #file_manager, #logger, #mailer, #mailer?, #s3uploader, #s3uploader?, #sftp, #sftp?

Constructor Details

#initialize(e, schema:, person_parser: nil, default_parser: nil, attr_map: nil) ⇒ EntryFactory

Returns a new instance of EntryFactory.

Parameters:

  • e (Eco::API::Common::Session::Environment)

    requires a session environment, as any child of Eco::API::Common::Session::BaseSession

  • schema (Ecoportal::API::V1::PersonSchema)

    schema of person details that the parser will be based upon.

  • person_parser (nil, Eco::API::Common::People::PersonParser) (defaults to: nil)

    set of attribute, type and format parsers/serializers.

  • attr_map (nil, Eco::Data::Mapper) (defaults to: nil)

    attribute names mapper to translate external names into internal ones and vice versa.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/eco/api/common/people/entry_factory.rb', line 29

def initialize(e, schema:, person_parser: nil, default_parser: nil, attr_map: nil)
  msg = "Constructor needs a PersonSchema. Given: #{schema.class}"
  fatal msg unless schema.is_a?(Ecoportal::API::V1::PersonSchema)

  msg = "Expecting PersonParser. Given: #{person_parser.class}"
  fatal msg if person_parser && !person_parser.is_a?(Eco::API::Common::People::PersonParser)

  msg = "Expecting Mapper object. Given: #{attr_map.class}"
  fatal msg 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

Instance Attribute Details

#schemaEcoportal::API::V1::PersonSchema (readonly)

person schema to be used in this entry factory

Returns:

  • (Ecoportal::API::V1::PersonSchema)

    the current value of schema



16
17
18
# File 'lib/eco/api/common/people/entry_factory.rb', line 16

def schema
  @schema
end

Instance Method Details

#entries(data: (no_data = true; nil), file: (no_file = true; nil), format: (no_format = true; nil), **options) ⇒ Eco::API::Common::People::Entries

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.

Parameters:

  • data (Array<Hash>) (defaults to: (no_data = true; nil))

    data to be parsed. It cannot be used alongside with file:

  • file (String) (defaults to: (no_file = true; nil))

    absolute or relative path to the input file. It cannot be used alongside with data:.

  • format (Symbol) (defaults to: (no_format = true; nil))

    it must be used when you use the option file: (i.e. :xml, :csv), as it specifies the format of the input file:.

  • options (Hash)

    further options.

Options Hash (**options):

  • :encoding (String)

    optional parameter to read file: by expecting certain encoding.

  • :check_headers (Boolean)

    signals if the csv file headers should be expected.

Returns:

Raises:

  • 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:.


105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/eco/api/common/people/entry_factory.rb', line 105

def entries(data: (no_data = true; nil), file: (no_file = true; nil), format: (no_format = true; nil), **options) # rubocop:disable Style/Semicolon
  msg = "You should at least use data: or file:, but not both"
  fatal msg if no_data == no_file

  msg = "You must specify a valid format: (symbol) when you use file."
  fatal msg if file && no_format

  msg = "Format should be a Symbol. Given '#{format}'"
  fatal msg if format && !format.is_a?(Symbol)

  msg = "There is no parser/serializer for format ':#{format}'"
  fatal msg 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) ⇒ Void

Helper that generates a file out of data:.

Parameters:

  • data (Eco::API::Organization::People)

    data to be parsed.

  • file (String) (defaults to: "export")

    absolute or relative path to the ouput file.

  • format (Symbol) (defaults to: :csv)

    it specifies the format of the output file: (i.e. :xml, :csv). There must be a parser/serializer defined for it.

  • encoding (String) (defaults to: "utf-8")

    optional parameter to geneate file: content by unsing certain encoding.

Returns:

  • (Void)

    .

Raises:

  • 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:.


186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/eco/api/common/people/entry_factory.rb', line 186

def export(data:, file: "export", format: :csv, encoding: "utf-8", internal_names: false)
  msg = "data: Expected Eco::API::Organization::People object. Given: #{data.class}"
  fatal msg unless data.is_a?(Eco::API::Organization::People)

  fatal "A file should be specified."                   if file.to_s.strip.empty?
  fatal "Format should be a Symbol. Given '#{format}'"  if format && !format.is_a?(Symbol)

  msg = "There is no parser/serializer for format ':#{format}'"
  fatal msg unless @person_parser.defined?(format)

  run = true
  if self.class.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 == "") || response.upcase.start_with?("Y")
    end
  end

  return unless run

  deps         = {"supervisor_id" => {people: data}}
  data_entries = data.map do |person|
    new(person, dependencies: deps).then 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

#new(data, dependencies: {}) ⇒ Eco::API::Common::People::PersonEntry

Note:

this method is necessary to make the factory object work as a if it was a class PersonEntry you can call new on.

key method to generate objects of PersonEntry that share dependencies via this EntryFactory environment.

Parameters:

  • data (Hash, Person)

    data to be parsed/serialized. Parsed: the external hashed entry. Serialized: a Person object.

Returns:



80
81
82
83
84
85
86
87
88
# File 'lib/eco/api/common/people/entry_factory.rb', line 80

def new(data, dependencies: {})
  PersonEntry.new(
    data,
    person_parser: person_parser,
    attr_map:      @attr_map,
    dependencies:  dependencies,
    logger:        logger
  )
end

#newFactory(schema: nil) ⇒ Object

rubocop:disable Naming/MethodName



52
53
54
55
56
57
58
59
60
# File 'lib/eco/api/common/people/entry_factory.rb', line 52

def newFactory(schema: nil) # rubocop:disable Naming/MethodName
  self.class.new(
    environment,
    schema:         schema,
    person_parser:  @source_person_parser,
    default_parser: @default_parser,
    attr_map:       @attr_map
  )
end

#person_parserEco::API::Common::People::PersonParser

Note:

if the custom person parser has changed, it updates the copy of this EntryFactory instance

provides with a Eco::API::Common::People::PersonParser object (collection of attribute parsers)

Returns:



65
66
67
68
69
70
71
# File 'lib/eco/api/common/people/entry_factory.rb', line 65

def person_parser
  if @person_parser_patch_version < @source_person_parser.patch_version
    @person_parser.merge(@source_person_parser)
    @person_parser_patch_version = @source_person_parser.patch_version
  end
  @person_parser
end

#to_array_of_hashes(**kargs) ⇒ Object

rubocop:disable Metrics/AbcSize



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/eco/api/common/people/entry_factory.rb', line 125

def to_array_of_hashes(**kargs) # rubocop:disable Metrics/AbcSize
  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
  if (format != :xls) && file # rubocop:disable Style/IfUnlessModifier
    content = get_file_content(file, encoding: encoding)
  end

  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
    when NilClass
      abort("There is NO input data")
    else
      abort("Input content 'Array' of '#{sample.class}' is not supported.")
    end
  else
    if file && format == :xls
      person_parser.parse(format, file)
    else
      abort("Could not obtain any data out of these: #{kargs}. Given content: '#{content.class}'")
    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