[Cascade]
The main aim of this gem is to provide some kind of template for parsing files. Usually, parsing file process contains next steps:
- Retreiving info from file
- Distinguish content from each file line
- Parse each column with corresponding parser
- Generate some kind of data record
- Save obtained record
- Handle errors
- Generate report
Cascade pretends to simplify main part of this step to save your time.
Examples
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