[Cascade]

Codeship Status for ignat-zakrevsky/cascade Code Climate Test Coverage Gem Version Codacy Badge

The main aim of this gem is to provide some kind of template for parsing files. Usually, parsing file process contains next steps:

  1. Retreiving info from file
  2. Distinguish content from each file line
  3. Parse each column with corresponding parser
  4. Generate some kind of data record
  5. Save obtained record
  6. Handle errors
  7. Generate report

Cascade pretends to simplify main part of this step to save your time.

Examples

Minimal working example

More complicated example

Installation

Install the cascade-rb package from Rubygems:

gem install cascade-rb

or add it to your Gemfile for Bundler:

gem 'cascade-rb'

Usage

Require gem files

require 'cascade'

Configure it!

Cascade.configuration do
  # [Object#call] will be used for undefined fields types
  Cascade::RowProcessor.deafult_presenter
  # [Boolean] will throw exception in case of unavailable presenter
  Cascade::RowProcessor.use_default_presenter
  # [String] filepath with columns mapping, see below
  Cascade::ColumnsMatching.mapping_file
  # [Boolean] will raise fields parsing exceptions
  Cascade::ErrorHandler.raise_parse_errors
end

Provide iteratable object for parsing and run it!

Cascade::DataParser.new(data_provider: Csv.open("data_test.csv")).call

Columns mapping

Parsing file description should have next structure (example)

mapping:
  name: type

Columns parsing

There are several alredy defined fields parsers (types):

  • currency
  • boolean
  • string

Feel free to add new fields parsers and provide it through PR.

Components replaceability

There is a lot of DI in this gem, so, you can replace each component of the parser. Let's assume you want to parse JSON files instead of CSV, save this to ActiveRecord model, and you need Date fields parsing, ok! Writing new data provider:

class ParserJSON
  def open(file)
    JSON.parse(File.read(file))["rows"]
  end
end

Writing new data saver:

class PersonDataSaver
  def call(person_data)
    Person.create!(person_data)
  end
end

considering that there is no much logic even better

 PERSON_SAVER = -> (person_data) { Person.create!(person_data) }

Writing date parser:

class DateParser
  def call(value)
    Date.parse(value)
  end
end

or you can always use lambdas for such logic

DATE_PARSER = -> (value) { Date.parse(value) }

Provide all this stuff into data parser

Cascade::DataParser.new(
  data_provider: ParserJSON.new.open("data_test.csv"),
  row_processor: Cascade::RowProcessor.new(date: DateParser.new),
  data_saver: PERSON_SAVER
 ).call

And that's all! Example

Conventions

I'm fan of callable object as consequence I prefer #call methods for classes with one responsibility. Nice video that describes benefits of such approach