Csv Schema

Hex pmBuild StatusLicense

Csv schema is a library helping you to build Ecto.Schema-like modules having a csv file as source.

The idea behind this library is give the possibility to create, at compile-time, a self-contained module exposing functions to retrieve data starting from a CSV.

Installation

If available in Hex, the package can be installed by adding csv_schema to your list of dependencies in mix.exs:

def deps do
[
{:csv_schema, "~> 0.2.0"}
]
end

Usage

Supposing you have a CSV file looking like this:

idfirst_namelast_nameemailgenderip_addressdate_of_birth
1IvoryOverstreetioverstreet0@businessweek.comFemale30.138.91.6210/22/2018
2UlickVasnevuvasnev1@vkontakte.ruMale35.15.164.7001/19/2018
3ChloeFreemantlecfreemantle2@parallels.comFemale133.133.113.25508/13/2018
.....................

Is possible to create an Ecto.Schema-like repository using Csv.Schema macro:

defmodule Person do
use Csv.Schema
alias Csv.Schema.Parser
schema "path/to/person.csv" do
field :id, "id"
field :first_name, "first_name", filter_by: true
field :last_name, "last_name", sort: :asc
field :identifier, ["first_name", "last_name"], key: true, join: " "
field :email, "email", unique: true
field :gender, "gender", filter_by: true, sort: :desc
field :ip_address, "ip_address"
field :date_of_birth, "date_of_birth", parser: &Parser.date!(&1, "{0M}/{0D}/{0YYYY}")
end
end

Note that it's not a requirement to map all fields, but every field mapped must have a column in csv file. For example the following field configuration will result in a compilation error:

field :id, "non_existing_id", ....

Schema could be configured using a custom separator

use Csv.Schema, separator: ?,

Moreover it's possible to configure if csv file has or has not an header. Depending on header param value field config changes:

# Csv with header
schema "path/to/person.csv" do
field :id, "id", key: true
...
end
# Csv without header. Note that field 1 is binded with the first csv column.
# Index goes from 1 to N
schema "path/to/person.csv" do
field :id, 1, key: true
...
end

Now Person module is a struct, defined like this:

defmodule Person do
defstruct id: nil,
first_name: nil,
last_name: nil,
email: nil,
gender: nil,
ip_address: nil,
date_of_birth: nil
end

This macro creates for you inside Person module those functions:

def by_id(integer_key), do: ...
def filter_by_first_name(string_value), do: ...
def by_email(string_value), do: ...
def filter_by_gender(string_value), do: ...
def get_all, do: ...

Where:

Field configuration

Every field should be formed like this:

field {struct_field}, {csv_header}, {opts}

where:

opts:

Note that every configuration is optional

Keep in mind

Compilation time increase in an exponential manner if csv contains lots of lines and you configure multiple fields candidate for method creation (flags key, unique and/or filter_by set to true).

Because "without data you're just another person with an opinion" here some data:

csv rowskeyuniquefilter_bycompile time ms
1_000no00419 ms
1_000yes111_980 ms
1_000yes222_542 ms
1_000yes243_565 ms
1_000yes201_758 ms
1_000yes042_090 ms
1_000no201_634 ms
1_000no041_971 ms
5_000no002_410 ms
5_000yes1115_282 ms
5_000yes2222_478 ms
5_000yes2428_060 ms
5_000yes2016_254 ms
5_000yes0415_043 ms
5_000no2014_518 ms
5_000no0412_931 ms
10_000no004_962 ms
10_000yes1128_995 ms
10_000yes2242_817 ms
10_000yes2454_759 ms
10_000yes2037_166 ms
10_000yes0429_913 ms
10_000no2033_578 ms
10_000no0429_096 ms

5 compilations average time.

Executed on my machine:

Lenovo Thinkpad T480
CPU: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
RAM: 32GB