importeur 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ .env.test
12
+
13
+ # rspec failure tracking
14
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ before_install: gem install bundler -v 1.15.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1.0 (2017-10-26)
2
+
3
+ - Initial release
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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop-ci'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ load 'lib/task/db.rake'
10
+
11
+ task default: :spec
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Importeur
4
+ VERSION = '0.1.0'
5
+ 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: []