IntegratedData
A tool for compositing custom domain objects from disparate data sources.
Synopsis
IntegratedData
is a micro-framework for data integration that allows you to build POROs whose properties are scattered across various sources.
Concepts
Data Sources
An IntegratedData::Source
is any class capable of retrieving data from some source on initialization. It could access a database, API, CSV, or pretty much anything under the digital sun. Public methods of IntegratedData::Sources
act like different strategies for accessing the data therein.
Entities
An IntegratedData::Entity
represents a concept with various attributes. Each attribute can specify different data sources and strategies it can be derived from, using different identifiers.
Identifiers
Finally, you are encouraged to make proper value objects for your identifiers by using IntegratedData::Identifier
s. When initializing an entity, any ids with a corresponding identifier class will be coerced into an instance of one of these.
API
Data Sources
Convert any class into a data source by extending IntegratedData::Source
:
require 'integrated_data'
class MySource
extend IntegratedData::Source
end
Requirements
Sources are required to implement these methods:
-
IntegratedData::Source.build(options = {})
Data sources must implement a class method, build
, that can accept a Hash of options and returns an object you can invoke strategies on.
-
IntegratedData::Source
strategies
The object returned by IntegratedData::Source.build
should have public methods that accept a single argument–an identifier–and return data from the source.
Results
Extensions
There are currently no gems that extend IntegratedData::Source
.
Identifiers
Convert any class into an identifier by extending IntegratedData::Identifier
:
require 'integrated_data'
class MyIdentifier
extend IntegratedData::Identifier
end
Requirements
Entities are required to implement these methods:
Results
Extensions
There are currently no gems that extend IntegratedData::Identifier
.
Entities
Convert any class into an entity by extending IntegratedData::Entity
:
require 'integrated_data'
class MyEntity
extend IntegratedData::Entity
end
Requirements
Entities are required to implement these methods:
-
IntegratedData::Source#initialize(attributes = {})
Entities must be able to be initialized with a single argument, a Hash of attributes.
Results
-
Lookup DSL
Entities gain a class method, lookup
, that can be used to register ways to look up attributes. They can be instantiated with a hash of identifiers, and are then automatically initialized with a hash of attributes.
-
@identifiers
Entity instances have access to an instance variable, @identifiers
, reflecting the identifiers they were furnished with on initialization.
-
@attributes
Entity instances have access to an instance variable, @attributes
, reflecting the attributes they were furnished with on initialization.
Extensions
There are currently no gems that extend IntegratedData::Entity
.
Examples
Sources
CSVs
An in-memory data source could be implemented as such:
class InMemorySource < Array
extend IntegratedData::Source
class << self
def build(data: [])
data = Array.try_convert(data).map do |hashlike|
Hash.try_convert(data)
end.compact
new data
end
end
def call(id)
find do |attributes|
id == attributes[:id]
end
end
end
A CSV data source could be implemented as such:
require 'smarter_csv'
class CSVSource
extend IntegratedData::Source
class << self
def build(file:)
new(file)
end
end
def initialize(file)
@file = file
end
def call(id)
@data ||= SmarterCSV.process(@file)
@data.find do |attributes|
id == attributes[:id]
end
end
def uncached(id)
SmarterCSV.process(@file).find do |attributes|
id == attributes[:id]
end
end
end
Identifiers
An identifier could be implemented as such:
class PaddedString < String
extend IntegratedData::Identifier
class << self
def parse(value)
string = value.to_s
string.insert(0, '0') until string.length >= 10
new string
end
end
def == other
super self.class.parse other
end
end
Entities
Finally, we could put it all together as such:
require 'ostruct'
class Student < OpenStruct
extend IntegratedData::Entity
lookup :first_name, from: InMemorySource, by: :student_id, data: [student_id: '0000000001', first_name: 'Chris']
end