importeur 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.circleci/config.yml +70 -0
- data/.env +8 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +13 -0
- data/README.md +85 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/importeur.gemspec +47 -0
- data/lib/importeur/active_record_postgres_loader.rb +63 -0
- data/lib/importeur/data_sources/appnexus.rb +25 -0
- data/lib/importeur/data_sources/combined.rb +33 -0
- data/lib/importeur/data_sources/rocketfuel.rb +26 -0
- data/lib/importeur/etl.rb +24 -0
- data/lib/importeur/extractor.rb +51 -0
- data/lib/importeur/version.rb +5 -0
- data/lib/importeur.rb +15 -0
- data/lib/task/db.rake +28 -0
- metadata +247 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2a5b8cd8b68e60849d7f6fc4921b41d4ff39187a
|
4
|
+
data.tar.gz: 9a23992b2d88fdad38518abc43057d3d585ea11c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e1e552cb90dc22addb41d5fdaf14cb4774ebdb13952849a46be3f5d20d3d89741078a22498133039f9411e9d3866882b87b1989afedef3291e9c38c7431191ac
|
7
|
+
data.tar.gz: 90056d4a61059cffe16bcd2cf2c6198a8485045568e5cfad40f009cdaff358aedc91865325e95343396c5b6b7c55682b86ca60f9736e277b38e8c7e198ed9138
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Ruby CircleCI 2.0 configuration file
|
2
|
+
#
|
3
|
+
# Check https://circleci.com/docs/2.0/language-ruby/ for more details
|
4
|
+
#
|
5
|
+
version: 2
|
6
|
+
jobs:
|
7
|
+
build:
|
8
|
+
docker:
|
9
|
+
# specify the version you desire here
|
10
|
+
- image: circleci/ruby:2.4.1-node-browsers
|
11
|
+
environment:
|
12
|
+
DATABASE_URL: 'postgres://importeur@localhost/importeur'
|
13
|
+
CAKE_DATA_BUCKET: 'ad2games-cake-data-test-fixtures'
|
14
|
+
AWS_REGION: 'eu-west-1'
|
15
|
+
AWS_ACCESS_KEY_ID: 'aws-access-key-id'
|
16
|
+
AWS_SECRET_ACCESS_KEY: 'aws-secret-access-key'
|
17
|
+
|
18
|
+
# Specify service dependencies here if necessary
|
19
|
+
# CircleCI maintains a library of pre-built images
|
20
|
+
# documented at https://circleci.com/docs/2.0/circleci-images/
|
21
|
+
- image: circleci/postgres:9.6
|
22
|
+
environment:
|
23
|
+
POSTGRES_USER: importeur
|
24
|
+
POSTGRES_DB: importeur
|
25
|
+
POSTGRES_PASSWORD: ''
|
26
|
+
|
27
|
+
working_directory: ~/repo
|
28
|
+
|
29
|
+
steps:
|
30
|
+
- checkout
|
31
|
+
|
32
|
+
# Download and cache dependencies
|
33
|
+
- restore_cache:
|
34
|
+
keys:
|
35
|
+
- v1-dependencies-gems
|
36
|
+
# fallback to using the latest cache if no exact match is found
|
37
|
+
- v1-dependencies-
|
38
|
+
|
39
|
+
- run:
|
40
|
+
name: install dependencies
|
41
|
+
command: |
|
42
|
+
bundle install --jobs=4 --retry=3 --path vendor/bundle
|
43
|
+
|
44
|
+
- save_cache:
|
45
|
+
paths:
|
46
|
+
- ./vendor/bundle
|
47
|
+
key: v1-dependencies-gems
|
48
|
+
|
49
|
+
# Database setup
|
50
|
+
- run: bundle exec rake create_test_db
|
51
|
+
|
52
|
+
# run tests!
|
53
|
+
- run:
|
54
|
+
name: run tests
|
55
|
+
command: |
|
56
|
+
mkdir /tmp/test-results
|
57
|
+
TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
|
58
|
+
|
59
|
+
bundle exec rspec --format progress \
|
60
|
+
--format RspecJunitFormatter \
|
61
|
+
--out /tmp/test-results/rspec.xml \
|
62
|
+
--format progress \
|
63
|
+
${TEST_FILES}
|
64
|
+
|
65
|
+
# collect reports
|
66
|
+
- store_test_results:
|
67
|
+
path: /tmp/test-results
|
68
|
+
- store_artifacts:
|
69
|
+
path: /tmp/test-results
|
70
|
+
destination: test-results
|
data/.env
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
APPNEXUS_USERNAME: 'appnexus-username'
|
2
|
+
APPNEXUS_PASSWORD: 'appnexus-password'
|
3
|
+
CAKE_DATA_BUCKET: 'ad2games-cake-data-test-fixtures'
|
4
|
+
AWS_REGION: 'eu-west-1'
|
5
|
+
AWS_ACCESS_KEY_ID: 'aws-access-key-id'
|
6
|
+
AWS_SECRET_ACCESS_KEY: 'aws-secret-access-key'
|
7
|
+
ROCKETFUEL_API_URI: 'https://api-sandbox.rocketfuel.com/2016/'
|
8
|
+
ROCKETFUEL_API_AUTH_TOKEN: 'rocketfuel-api-auth-token'
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
6
|
+
|
7
|
+
# Specify your gem's dependencies in importeur.gemspec
|
8
|
+
gemspec
|
9
|
+
|
10
|
+
group :development, :test do
|
11
|
+
gem 'rspec_junit_formatter', '0.2.2'
|
12
|
+
gem 'rubocop-ci', github: 'ad2games/rubocop-ci'
|
13
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Importeur
|
2
|
+
|
3
|
+
A universal data import tool (a.k.a. ETL) 🙌
|
4
|
+
|
5
|
+
For now it's a single import job, made more composable and put into its own
|
6
|
+
place to be used in other projects. The vision is to add more things, so it can
|
7
|
+
handle more data sources and targets, eventually.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'importeur'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install importeur
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
The center of it all is the `ETL` class. By inistantiating it with different
|
28
|
+
extractors, transformers and loaders, it can be whatever you want it to be:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
Importeur::ETL.new(
|
32
|
+
extractor: my_extractor,
|
33
|
+
transformer: my_transformer,
|
34
|
+
loader: my_loader
|
35
|
+
)
|
36
|
+
```
|
37
|
+
|
38
|
+
All three only need to have a `call` method and, hence, can be simple Procs.
|
39
|
+
The extractor should return something enumerable, which is then iterated over
|
40
|
+
and each item passed into the transformer. The loader receives the enumerable
|
41
|
+
result of that.
|
42
|
+
|
43
|
+
So far a more or less generic `Extractor` exists.
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
Importeur::Extractor.new(
|
47
|
+
source,
|
48
|
+
cursor,
|
49
|
+
cursor_key
|
50
|
+
)
|
51
|
+
```
|
52
|
+
|
53
|
+
It takes as arguments a data source that needs to implement
|
54
|
+
|
55
|
+
* An `items` method, returning anything enumerable, as well as a
|
56
|
+
`dataset_unique_id`, returning a unique ID for the current version of the
|
57
|
+
dataset
|
58
|
+
* A cursor, that needs to implement `read(key)` and `write(key, value)` (e.g.
|
59
|
+
`Rails.cache`) it makes the extractor return `nil` if there is no new data.
|
60
|
+
* A key (string), to be passed into the cursor.
|
61
|
+
|
62
|
+
An `ActiveRecordPostgresLoader` exists, that has a few very specific
|
63
|
+
dependencies. As the name suggests, it imports data into a Postgres database
|
64
|
+
using `ActiveRecord`. Additionally, the model used, needs to use
|
65
|
+
`acts_as_paranoid`.
|
66
|
+
|
67
|
+
Example use-cases can be found in the `spec/integration` directory.
|
68
|
+
|
69
|
+
## Development
|
70
|
+
|
71
|
+
In order to be able to run the tests, a Postgres database is needed. Having
|
72
|
+
a PostgreSQL user with CREATEDB permission, create `.env.test` file with your
|
73
|
+
DATABASE_URL configuration, for example:
|
74
|
+
|
75
|
+
`DATABASE_URL: 'postgres://user:password@localhost:5432/importeur_test'`
|
76
|
+
|
77
|
+
Then run `bundle exec rake create_test_db` to create the database and
|
78
|
+
`bundle exec rspec` to run tests.
|
79
|
+
|
80
|
+
We run `rake rubocop` to make sure, everything looks good.
|
81
|
+
|
82
|
+
## Contributing
|
83
|
+
|
84
|
+
Bug reports and pull requests are welcome on GitHub at
|
85
|
+
https://github.com/ad2games/importeur.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "importeur"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/importeur.gemspec
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
lib = File.expand_path('../lib', __FILE__)
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
+
require 'importeur/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = 'importeur'
|
10
|
+
spec.version = Importeur::VERSION
|
11
|
+
spec.authors = ['Helge Rausch']
|
12
|
+
spec.email = ['helge@rausch.io']
|
13
|
+
|
14
|
+
spec.summary = 'Universal data importer'
|
15
|
+
spec.description = 'Universal data importer'
|
16
|
+
spec.homepage = 'https://ad2games.com/'
|
17
|
+
|
18
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
19
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
20
|
+
if spec.respond_to?(:metadata)
|
21
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
22
|
+
else
|
23
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
24
|
+
'public gem pushes.'
|
25
|
+
end
|
26
|
+
|
27
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
28
|
+
f.match(%r{^(test|spec|features)/})
|
29
|
+
end
|
30
|
+
spec.bindir = 'exe'
|
31
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ['lib']
|
33
|
+
|
34
|
+
spec.add_development_dependency 'bundler', '~> 1.15'
|
35
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
36
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
37
|
+
spec.add_development_dependency 'activerecord', '<5'
|
38
|
+
spec.add_development_dependency 'pry'
|
39
|
+
spec.add_development_dependency 'vcr'
|
40
|
+
spec.add_development_dependency 'webmock'
|
41
|
+
spec.add_development_dependency 'bucket_cake'
|
42
|
+
spec.add_development_dependency 'pg'
|
43
|
+
spec.add_development_dependency 'database_cleaner'
|
44
|
+
spec.add_development_dependency 'appnexusapi'
|
45
|
+
spec.add_development_dependency 'rocketfuel_api'
|
46
|
+
spec.add_development_dependency 'dotenv'
|
47
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Importeur
|
4
|
+
class ActiveRecordPostgresLoader
|
5
|
+
BATCH_SIZE = 500
|
6
|
+
|
7
|
+
def initialize(model, primary_key)
|
8
|
+
@model = model
|
9
|
+
@primary_key = primary_key
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(data)
|
13
|
+
imported_ids = []
|
14
|
+
seen = Set.new
|
15
|
+
data.each_slice(BATCH_SIZE) do |batch|
|
16
|
+
batch_ids = batch.map { |attrs| attrs[primary_key] }
|
17
|
+
imported_ids.concat(batch_ids)
|
18
|
+
records = records_for_batch(batch_ids)
|
19
|
+
store_batch(batch, records, seen)
|
20
|
+
end
|
21
|
+
delete_old_records(imported_ids)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :model, :primary_key
|
27
|
+
|
28
|
+
def store_batch(batch, records, seen)
|
29
|
+
batch.each do |attrs|
|
30
|
+
next unless seen.add?(attrs[primary_key])
|
31
|
+
store_record(attrs, records)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def store_record(attrs, records)
|
36
|
+
record = records.fetch(attrs[primary_key], model.new)
|
37
|
+
record.assign_attributes(attrs)
|
38
|
+
record.deleted_at = nil
|
39
|
+
return unless record.changed?
|
40
|
+
record.imported_at = Time.now
|
41
|
+
record.save!
|
42
|
+
end
|
43
|
+
|
44
|
+
def records_for_batch(batch_ids)
|
45
|
+
model
|
46
|
+
.with_deleted
|
47
|
+
.where(primary_key => batch_ids)
|
48
|
+
.index_by(&primary_key)
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete_old_records(imported_ids)
|
52
|
+
# Basically `self.class.model.where.not(primary_key => imported_ids)`, but
|
53
|
+
# more efficient in this case.
|
54
|
+
model
|
55
|
+
.joins(<<-SQL)
|
56
|
+
LEFT JOIN (SELECT unnest(ARRAY[#{imported_ids.join(',')}]::int[]) AS primary_key) AS imported
|
57
|
+
ON imported.primary_key = #{model.table_name}.#{primary_key}
|
58
|
+
SQL
|
59
|
+
.where('imported.primary_key' => nil)
|
60
|
+
.delete_all
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Importeur
|
4
|
+
module DataSources
|
5
|
+
class Appnexus
|
6
|
+
def initialize(appnexus_service)
|
7
|
+
@appnexus_service = appnexus_service
|
8
|
+
end
|
9
|
+
|
10
|
+
def dataset_unique_id
|
11
|
+
md5 = Digest::MD5.new
|
12
|
+
items.each { |item| md5.update(item.to_s) }
|
13
|
+
md5.hexdigest
|
14
|
+
end
|
15
|
+
|
16
|
+
def items
|
17
|
+
@items ||= appnexus_service.get_all
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :appnexus_service
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Importeur
|
4
|
+
module DataSources
|
5
|
+
class Combined
|
6
|
+
def initialize(*entity_classes)
|
7
|
+
@entity_classes = entity_classes
|
8
|
+
end
|
9
|
+
|
10
|
+
def dataset_unique_id
|
11
|
+
data_sources.map(&:dataset_unique_id).join
|
12
|
+
end
|
13
|
+
|
14
|
+
def items
|
15
|
+
Enumerator.new do |y|
|
16
|
+
data_sources.each do |data_source|
|
17
|
+
data_source.items.each do |entity|
|
18
|
+
y << entity
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :entity_classes
|
27
|
+
|
28
|
+
def data_sources
|
29
|
+
@data_sources ||= entity_classes.map(&:new)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Importeur
|
4
|
+
module DataSources
|
5
|
+
class Rocketfuel
|
6
|
+
def initialize(rocketfuel_service, params = {})
|
7
|
+
@rocketfuel_service = rocketfuel_service
|
8
|
+
@params = params
|
9
|
+
end
|
10
|
+
|
11
|
+
def dataset_unique_id
|
12
|
+
md5 = Digest::MD5.new
|
13
|
+
items.each { |item| md5.update(item.to_s) }
|
14
|
+
md5.hexdigest
|
15
|
+
end
|
16
|
+
|
17
|
+
def items
|
18
|
+
@items ||= rocketfuel_service.get_all(params)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :rocketfuel_service, :params
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Importeur
|
4
|
+
class ETL
|
5
|
+
def initialize(extractor:, transformer:, loader:)
|
6
|
+
@extractor = extractor
|
7
|
+
@transformer = transformer
|
8
|
+
@loader = loader
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
extracted = extractor.call
|
13
|
+
return if extracted.nil?
|
14
|
+
transformed = extracted.lazy.flat_map do |entity|
|
15
|
+
transformer.call(entity)
|
16
|
+
end
|
17
|
+
loader.call(transformed.lazy)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :extractor, :transformer, :loader
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Importeur
|
4
|
+
class Extractor
|
5
|
+
def initialize(data_source, cursor, cursor_key)
|
6
|
+
@data_source = data_source
|
7
|
+
@cursor = cursor
|
8
|
+
@cursor_key = cursor_key
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
return unless has_new_data?
|
13
|
+
Enumerator.new do |y|
|
14
|
+
feed.each do |item|
|
15
|
+
y << item
|
16
|
+
end
|
17
|
+
store_new_dataset_id
|
18
|
+
clear_current_dataset_id
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :current_dataset_id, :cursor, :cursor_key, :data_source
|
25
|
+
|
26
|
+
def has_new_data?
|
27
|
+
last_known_id = cursor.read(cursor_key)
|
28
|
+
current_dataset_id != last_known_id
|
29
|
+
end
|
30
|
+
|
31
|
+
def store_new_dataset_id
|
32
|
+
cursor.write(cursor_key, current_dataset_id)
|
33
|
+
end
|
34
|
+
|
35
|
+
def clear_current_dataset_id
|
36
|
+
@current_dataset_id = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def current_dataset_id
|
40
|
+
@current_dataset_id ||= dataset_id
|
41
|
+
end
|
42
|
+
|
43
|
+
def dataset_id
|
44
|
+
data_source.dataset_unique_id
|
45
|
+
end
|
46
|
+
|
47
|
+
def feed
|
48
|
+
@feed ||= data_source.items
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/importeur.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'importeur/version'
|
4
|
+
require 'importeur/etl'
|
5
|
+
require 'importeur/extractor'
|
6
|
+
require 'importeur/active_record_postgres_loader'
|
7
|
+
require 'importeur/data_sources/combined'
|
8
|
+
require 'importeur/data_sources/appnexus'
|
9
|
+
require 'importeur/data_sources/rocketfuel'
|
10
|
+
|
11
|
+
module Importeur
|
12
|
+
def self.root
|
13
|
+
Pathname.new(File.dirname(__dir__))
|
14
|
+
end
|
15
|
+
end
|
data/lib/task/db.rake
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dotenv'
|
4
|
+
Dotenv.load('.env.test')
|
5
|
+
|
6
|
+
require 'active_record'
|
7
|
+
|
8
|
+
desc 'Create a database for testing'
|
9
|
+
task :create_test_db do
|
10
|
+
database_configuration =
|
11
|
+
ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(
|
12
|
+
ENV.fetch('DATABASE_URL')
|
13
|
+
).to_hash
|
14
|
+
|
15
|
+
ActiveRecord::Tasks::DatabaseTasks.drop(database_configuration)
|
16
|
+
ActiveRecord::Tasks::DatabaseTasks.create(database_configuration)
|
17
|
+
|
18
|
+
ActiveRecord::Base.establish_connection(ENV.fetch('DATABASE_URL'))
|
19
|
+
ActiveRecord::Migration.verbose = false
|
20
|
+
ActiveRecord::Schema.define(version: 1) do
|
21
|
+
drop_table :affiliates if table_exists?(:affiliates)
|
22
|
+
create_table :affiliates do |t|
|
23
|
+
t.string :name
|
24
|
+
t.datetime :deleted_at
|
25
|
+
t.datetime :imported_at
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: importeur
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Helge Rausch
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.15'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.15'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activerecord
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "<"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "<"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: vcr
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: webmock
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: bucket_cake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: pg
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: database_cleaner
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: appnexusapi
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rocketfuel_api
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: dotenv
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
description: Universal data importer
|
196
|
+
email:
|
197
|
+
- helge@rausch.io
|
198
|
+
executables: []
|
199
|
+
extensions: []
|
200
|
+
extra_rdoc_files: []
|
201
|
+
files:
|
202
|
+
- ".circleci/config.yml"
|
203
|
+
- ".env"
|
204
|
+
- ".gitignore"
|
205
|
+
- ".rspec"
|
206
|
+
- ".travis.yml"
|
207
|
+
- CHANGELOG.md
|
208
|
+
- Gemfile
|
209
|
+
- README.md
|
210
|
+
- Rakefile
|
211
|
+
- bin/console
|
212
|
+
- bin/setup
|
213
|
+
- importeur.gemspec
|
214
|
+
- lib/importeur.rb
|
215
|
+
- lib/importeur/active_record_postgres_loader.rb
|
216
|
+
- lib/importeur/data_sources/appnexus.rb
|
217
|
+
- lib/importeur/data_sources/combined.rb
|
218
|
+
- lib/importeur/data_sources/rocketfuel.rb
|
219
|
+
- lib/importeur/etl.rb
|
220
|
+
- lib/importeur/extractor.rb
|
221
|
+
- lib/importeur/version.rb
|
222
|
+
- lib/task/db.rake
|
223
|
+
homepage: https://ad2games.com/
|
224
|
+
licenses: []
|
225
|
+
metadata:
|
226
|
+
allowed_push_host: https://rubygems.org
|
227
|
+
post_install_message:
|
228
|
+
rdoc_options: []
|
229
|
+
require_paths:
|
230
|
+
- lib
|
231
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
232
|
+
requirements:
|
233
|
+
- - ">="
|
234
|
+
- !ruby/object:Gem::Version
|
235
|
+
version: '0'
|
236
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
237
|
+
requirements:
|
238
|
+
- - ">="
|
239
|
+
- !ruby/object:Gem::Version
|
240
|
+
version: '0'
|
241
|
+
requirements: []
|
242
|
+
rubyforge_project:
|
243
|
+
rubygems_version: 2.6.13
|
244
|
+
signing_key:
|
245
|
+
specification_version: 4
|
246
|
+
summary: Universal data importer
|
247
|
+
test_files: []
|