traject_sequel_writer 0.9.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: 7783d67f45b23e824e2fbf85c66b6cfd6b24cdbd
4
+ data.tar.gz: 619072aaeaa9e74e5470b2e8e97610fa9931dd31
5
+ SHA512:
6
+ metadata.gz: a8b8ae0fb6691de8f43365652b858a0c6adb9da6c755362c9d3e7a2ace98ff381aa3d0aed31eb087a0108eed0c20a1f7acba7275015ddf4dcccec3d23b50f5b6
7
+ data.tar.gz: 46a744c5574a998dd440aa8db3c966cedcc8c115eeb632142dae8ef8a170d05df25c152943720cecadd9e1b140a048bee82cbaf46f13cefee9b51cf6a3c9cc13
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Jonathan Rochkind
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,114 @@
1
+ [![Build Status](https://travis-ci.org/traject/traject_sequel_writer.svg)](https://travis-ci.org/traject/traject_sequel_writer)
2
+
3
+ # TrajectSequelWriter
4
+
5
+ A `Writer` plugin for [traject](https://github.com/traject/traject), that writes to an rdbms using [Sequel](https://github.com/jeremyevans/sequel).
6
+
7
+ The writer can be used as standard, as the destination of your indexing pipeline.
8
+
9
+ It was actually written for a use case where it's used as a "side effect" in a traject `each_record`, writing different data out to an rdbms on the side, while the main indexing is to Solr. This ends up a bit hacky at present but works.
10
+
11
+
12
+ ## Installation
13
+
14
+ We recommend using bundler with a traject project that has dependencies.
15
+ Add this line to your traject project's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'traject_sequel_writer', "~> 1.0"
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or if you don't use bundler with your traject project, install directly like:
26
+
27
+ $ gem install traject_sequel_writer
28
+
29
+ ## Usage
30
+
31
+ As a standard traject writer, you can just set a few settings:
32
+
33
+ ~~~ruby
34
+ settings do
35
+ provide "writer_class_name", "Traject::SequelWriter"
36
+ # ...
37
+ ~~~
38
+
39
+ You can set up the connection with a [Sequel connection string](http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html). Here's
40
+ an example for JDBC mysql (under JRuby). Note we discovered the `&characterEncoding=utf8` arg to the JDBC adapter was important.
41
+ Connection string parameters may vary for platform (MRI vs JDBC in Jruby) and database.
42
+
43
+ ~~~ruby
44
+ provide "sequel_writer.connection_string", "jdbc:mysql://dbhost.example.com:3306/database_name?characterEncoding=utf8&user=user&password=password"
45
+ # You also need to tell the writer what table to write to; the table should already exist
46
+ provide "sequel_writer.table_name", "my_table"
47
+ ~~~
48
+
49
+ By default, the writer will try to write to every non-pk column defined in your table -- if your Traject::Context output_hash's is missing
50
+ a value for a column, null will be inserted for that column. Or you can explicitly define which columns to use:
51
+
52
+ ~~~ruby
53
+ provide "sequel_writer.columns", ["column1", "column2"]
54
+ ~~~~
55
+
56
+ Still, your Context output_hash's must provide output key/values for every column mentioned, or else
57
+ null will be inserted for that column. Keys in the output_hash that don't match output columns
58
+ will be ignored.
59
+
60
+ ### All settings
61
+
62
+ * `sequel_writer.connection_string` : [Sequel connection string](http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html)
63
+ * `sequel_writer.database`: As an alternative to `sequel_connection_string`, pass in an already instantiated Sequel::Database object, as in from `Sequel.connect`
64
+ * `sequel_writer.table_name`: Required, what table to write to.
65
+ * `sequel_writer.column_names` Which columns to write to, by default all non-pk columns in the table. Since we use multi-row import statements,
66
+ column_names not present in the Traject::Context#output_hash will end up with SQL `null` inserted.
67
+ * `sequel_writer.thread_pool_size` Number of threads to use for writing to DB. Default 1, should be good.
68
+ * `sequel_writer.batch_size` Count of records to batch together in a single multi-row SQL `INSERT`. Default 100. Should be good.
69
+
70
+ ### Using as a side-channel additional output
71
+
72
+ In one project, we wanted to index to Solr. But we wanted to calculate completely different output to send
73
+ as a side-channel to an RDBMS table. Here's a little bit hacky way to do that, that would really work
74
+ for any traject writer.
75
+
76
+ ~~~ruby
77
+ sequel_writer = Traject::SequelWriter.new(
78
+ 'sequel_writer.connection_string' => conn_str,
79
+ 'sequel_writer.table_name' => 'my_table')
80
+
81
+ each_record do |record, context|
82
+ # imagine this returns an array of hashes, each of which represents a row
83
+ # you want to insert into the table.
84
+
85
+ rows_to_insert = make_rows_to_insert(record)
86
+
87
+ rows_to_insert.each do |row|
88
+ # Don't re-use the variable name `context`, can cause accidental shared concurrent state
89
+ sequel_writer.put( Traject::Context.new(:output_hash => row, :source_record => record) )
90
+ end
91
+ end
92
+
93
+ # Don't forget to close our side-channel sequel writer, to make
94
+ # sure anything queued gets written
95
+ after_processing do
96
+ sequel_writer.close
97
+ end
98
+ ~~~
99
+
100
+
101
+
102
+ ## Development
103
+
104
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
105
+
106
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
107
+
108
+ ## Contributing
109
+
110
+ 1. Fork it ( https://github.com/[my-github-username]/traject_sequel_writer/fork )
111
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
112
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
113
+ 4. Push to the branch (`git push origin my-new-feature`)
114
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+
6
+ task :default => [:test]
7
+
8
+ Rake::TestTask.new do |t|
9
+ t.libs << "test"
10
+ t.test_files = FileList['test/test*.rb']
11
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "traject_sequel_writer"
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
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,142 @@
1
+ require 'traject'
2
+ require 'traject/util'
3
+ require 'traject/indexer/settings'
4
+
5
+ require 'sequel'
6
+
7
+ require 'thread'
8
+
9
+ module Traject
10
+ class SequelWriter
11
+ # Sequel db connection object
12
+ attr_reader :sequel_db
13
+
14
+ # Sequel table/relation object
15
+ attr_reader :db_table
16
+
17
+ def initialize(argSettings)
18
+ @settings = Traject::Indexer::Settings.new(argSettings)
19
+
20
+ unless (!! @settings["sequel_writer.connection_string"]) ^ (!! @settings["sequel_writer.database"])
21
+ raise ArgumentError, "Exactly one of either setting `sequel_writer.connection_string` or `sequel_writer.database` is required"
22
+ end
23
+ unless @settings["sequel_writer.table_name"]
24
+ raise ArgumentError, "setting `sequel_writer.table_name` is required"
25
+ end
26
+
27
+ @sequel_db = @settings["sequel_writer.database"] || Sequel.connect(@settings["sequel_writer.connection_string"])
28
+ @db_table = @sequel_db[ @settings["sequel_writer.table_name"].to_sym ]
29
+
30
+
31
+ # Which keys to send to columns? Can be set explicitly with sequel_writer.columns,
32
+ # or we'll use all non-PK columns introspected from the db schema.
33
+ @column_names = @settings["sequel_writer.columns"]
34
+
35
+ unless @column_names
36
+ @column_names = @sequel_db.schema( @db_table.first_source_table ).find_all do |column, info|
37
+ info[:primary_key] != true
38
+ end.collect {|pair| pair.first}
39
+ end
40
+ @column_names = @column_names.collect {|c| c.to_sym}
41
+ @column_names = @column_names.freeze
42
+
43
+
44
+ # How many threads to use for the writer?
45
+ # if our thread pool settings are 0, it'll just create a null threadpool that
46
+ # executes in calling context. Default to 1, for waiting on DB I/O.
47
+ @thread_pool_size = (@settings["sequel_writer.thread_pool"] || 1).to_i
48
+
49
+ @batch_size = (@settings["sequel_writer.batch_size"] || 100).to_i
50
+
51
+ @batched_queue = Queue.new
52
+ @thread_pool = Traject::ThreadPool.new(@thread_pool_size)
53
+
54
+ @after_send_batch_callbacks = Array(@settings["sequel_writer.after_send_batch"] || [])
55
+ end
56
+
57
+ # Get the logger from the settings, or default to an effectively null logger
58
+ def logger
59
+ @settings["logger"] ||= Yell.new(STDERR, :level => "gt.fatal") # null logger
60
+ end
61
+
62
+ def put(context)
63
+ @thread_pool.raise_collected_exception!
64
+
65
+ @batched_queue << context
66
+ if @batched_queue.size >= @batch_size
67
+ batch = Traject::Util.drain_queue(@batched_queue)
68
+ @thread_pool.maybe_in_thread_pool(batch) {|batch_arg| send_batch(batch_arg) }
69
+ end
70
+ end
71
+
72
+ def close
73
+ @thread_pool.raise_collected_exception!
74
+
75
+ # Finish off whatever's left. Do it in the thread pool for
76
+ # consistency, and to ensure expected order of operations, so
77
+ # it goes to the end of the queue behind any other work.
78
+ batch = Traject::Util.drain_queue(@batched_queue)
79
+ @thread_pool.maybe_in_thread_pool(batch) {|batch_arg| send_batch(batch_arg) }
80
+
81
+
82
+ # Wait for shutdown, and time it.
83
+ logger.debug "#{self.class.name}: Shutting down thread pool, waiting if needed..."
84
+ elapsed = @thread_pool.shutdown_and_wait
85
+ if elapsed > 60
86
+ logger.warn "Waited #{elapsed} seconds for all threads, you may want to increase sequel_writer.thread_pool (currently #{@settings["solr_writer.thread_pool"]})"
87
+ end
88
+ logger.debug "#{self.class.name}: Thread pool shutdown complete"
89
+
90
+ # check again now that we've waited, there could still be some
91
+ # that didn't show up before.
92
+ @thread_pool.raise_collected_exception!
93
+
94
+ @sequel_db.disconnect
95
+ end
96
+
97
+ def send_batch(batch)
98
+ list_of_arrays = hashes_to_arrays(@column_names, batch.collect {|context| context.output_hash})
99
+
100
+ begin
101
+ db_table.import @column_names, list_of_arrays
102
+ rescue Sequel::DatabaseError, Sequel::PoolTimeout => batch_exception
103
+ # We rescue PoolTimeout too, because we're mysteriously getting those, they are maybe dropped DB connections?
104
+ # Try them each one by one, mostly so we can get a reasonable error message with particular record.
105
+ logger.warn("SequelWriter: error (#{batch_exception}) inserting batch of #{list_of_arrays.count} starting from system_id #{batch.first.output_hash['system_id']}, retrying individually...")
106
+
107
+ batch.each do |context|
108
+ send_single(context)
109
+ end
110
+ end
111
+
112
+ @after_send_batch_callbacks.each do |callback|
113
+ callback.call(batch, self)
114
+ end
115
+ end
116
+
117
+ def send_single(context)
118
+ db_table.insert @column_names, hash_to_array(@column_names, context.output_hash)
119
+ rescue Sequel::DatabaseError => e
120
+ logger.error("SequelWriter: Could not insert row: #{context.output_hash}: #{e}")
121
+ raise e
122
+ end
123
+
124
+
125
+ # Turn an array of hashes into an array of arrays,
126
+ # with each array being a hashes values matching column_names, in that order
127
+ def hashes_to_arrays(column_names, list_of_hashes)
128
+ list_of_hashes.collect do |h|
129
+ hash_to_array(column_names, h)
130
+ end
131
+ end
132
+
133
+ def hash_to_array(column_names, hash)
134
+ column_names.collect {|c| hash[c.to_s]}
135
+ end
136
+
137
+ def after_send_batch(&block)
138
+ @after_send_batch_callbacks << block
139
+ end
140
+
141
+ end
142
+ end
@@ -0,0 +1,3 @@
1
+ module TrajectSequelWriter
2
+ VERSION = "0.9.0"
3
+ end
@@ -0,0 +1,5 @@
1
+ require "traject_sequel_writer/version"
2
+
3
+ module TrajectSequelWriter
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,41 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/spec'
3
+
4
+ require 'traject_sequel_writer'
5
+
6
+ require 'minitest/autorun'
7
+
8
+ require 'sequel'
9
+
10
+ # Create a temporary sqlite db for tests, remove it when we're done
11
+ require 'fileutils'
12
+ FileUtils::mkdir_p 'tmp'
13
+
14
+
15
+ TEST_SEQUEL_CONNECT_STR = if defined? JRUBY_VERSION
16
+ "jdbc:sqlite:tmp/testing.sqlite"
17
+ else
18
+ "sqlite://tmp/testing.sqlite"
19
+ end
20
+
21
+ db = Sequel.connect(TEST_SEQUEL_CONNECT_STR)
22
+ db.drop_table?(:test)
23
+ db.create_table(:test) do
24
+ primary_key :id
25
+ Time :created_at
26
+ String :string_a
27
+ String :string_b
28
+ String :string_c
29
+ TrueClass :boolean_a
30
+ Integer :int_a
31
+ end
32
+
33
+ db[:test].delete
34
+ # Disconnect it now, make the code do the work
35
+ db.disconnect
36
+
37
+
38
+
39
+ MiniTest.after_run do
40
+ File.unlink("tmp/testing.sqlite")
41
+ end
@@ -0,0 +1,113 @@
1
+ require 'test_helper'
2
+
3
+ require 'traject/sequel_writer'
4
+
5
+ describe "Traject::SequelWriter" do
6
+ describe "in use" do
7
+ before do
8
+ @writer = writer
9
+ write_mock_docs(@writer, 63)
10
+ end
11
+
12
+ after do
13
+ @writer.db_table.delete
14
+ end
15
+
16
+ it "writes" do
17
+ assert_equal 63, @writer.db_table.count
18
+
19
+ @writer.db_table.each do |hash|
20
+ assert_kind_of String, hash[:string_a], "string_a is not filled out for #{hash}"
21
+ assert_kind_of String, hash[:string_b], "string_b is not filled out for #{hash}"
22
+ assert_nil hash[:string_c]
23
+ assert_includes [true, false], hash[:boolean_a], "boolean_a is not true or false for #{hash}"
24
+ assert_kind_of Integer, hash[:int_a]
25
+ end
26
+ end
27
+ end
28
+
29
+
30
+ it "writes with sequel.database parameter instead of connection_str" do
31
+ sequel_db = Sequel.connect(TEST_SEQUEL_CONNECT_STR)
32
+
33
+ writer = writer("sequel_writer.connection_string" => nil,
34
+ "sequel_writer.database" => sequel_db)
35
+
36
+ write_mock_docs(writer, 63)
37
+
38
+ assert_equal 63, writer.db_table.count
39
+
40
+ writer.db_table.each do |hash|
41
+ assert_kind_of String, hash[:string_a], "string_a is not filled out for #{hash}"
42
+ assert_kind_of String, hash[:string_b], "string_b is not filled out for #{hash}"
43
+ assert_nil hash[:string_c]
44
+ assert_includes [true, false], hash[:boolean_a], "boolean_a is not true or false for #{hash}"
45
+ assert_kind_of Integer, hash[:int_a]
46
+ end
47
+
48
+ writer.db_table.delete
49
+ end
50
+
51
+ describe "after_send_batch" do
52
+ before do
53
+ @writer = writer
54
+
55
+ @received_in_batches = Queue.new
56
+
57
+ @writer.after_send_batch do |batch, writer|
58
+ assert_kind_of Traject::SequelWriter, writer
59
+ assert_kind_of Array, batch
60
+
61
+ batch.each {|c| @received_in_batches.push c }
62
+ end
63
+
64
+ @num_docs = 63
65
+
66
+ write_mock_docs(@writer, @num_docs)
67
+ end
68
+
69
+ after do
70
+ @writer.db_table.delete
71
+ end
72
+
73
+ it "calls callbacks" do
74
+ assert_equal @num_docs, @received_in_batches.size
75
+ end
76
+ end
77
+
78
+ describe "errors" do
79
+ it "raises without required settings" do
80
+ assert_raises(ArgumentError) { Traject::SequelWriter.new }
81
+ assert_raises(ArgumentError) { Traject::SequelWriter.new("sequel_writer.connect_string" => "foo") }
82
+ assert_raises(ArgumentError) { Traject::SequelWriter.new("sequel_writer.table_name" => "foo") }
83
+ end
84
+ end
85
+
86
+
87
+ # Helpers
88
+
89
+ def write_mock_docs(writer, num)
90
+ (1..num).each do |i|
91
+ context = Traject::Indexer::Context.new
92
+ context.output_hash.merge!(
93
+ "id" => "ignore_me", # should ignore pk by default
94
+ "string_a" => "String_a #{i}",
95
+ "string_b" => "String_b #{i}",
96
+ "no_such_column" => "ignore me",
97
+ "boolean_a" => (i % 2 == 0) ? true : false,
98
+ "int_a" => i
99
+ )
100
+ writer.put context
101
+ end
102
+ writer.close
103
+ end
104
+
105
+ def writer(args = {})
106
+ args = {"sequel_writer.connection_string" => TEST_SEQUEL_CONNECT_STR,
107
+ "sequel_writer.table_name" => "test",
108
+ "sequel_writer.batch_size" => 5}.merge(args)
109
+ return Traject::SequelWriter.new(args)
110
+ end
111
+
112
+
113
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: traject_sequel_writer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Jonathan Rochkind
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: traject
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sequel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.22'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.22'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.9'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.9'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ description:
84
+ email:
85
+ - jonathan@dnil.net
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - LICENSE.txt
91
+ - README.md
92
+ - Rakefile
93
+ - bin/console
94
+ - bin/setup
95
+ - lib/traject/sequel_writer.rb
96
+ - lib/traject_sequel_writer.rb
97
+ - lib/traject_sequel_writer/version.rb
98
+ - test/test_helper.rb
99
+ - test/test_traject_sequel_writer.rb
100
+ homepage: https://github.com/traject/traject_sequel_writer
101
+ licenses:
102
+ - MIT
103
+ metadata: {}
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project:
120
+ rubygems_version: 2.4.5
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: Plug-in for traject, write to an rdbms with Sequel gem
124
+ test_files:
125
+ - test/test_helper.rb
126
+ - test/test_traject_sequel_writer.rb