barnyard_harvester 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.DS_Store +0 -0
- data/.gitignore +6 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +23 -0
- data/README.md +94 -0
- data/Rakefile +1 -0
- data/barnyard_harvester.gemspec +28 -0
- data/lib/.DS_Store +0 -0
- data/lib/barnyard_harvester/hash.rb +84 -0
- data/lib/barnyard_harvester/hash_queue.rb +53 -0
- data/lib/barnyard_harvester/redis.rb +93 -0
- data/lib/barnyard_harvester/redis_queue.rb +84 -0
- data/lib/barnyard_harvester/version.rb +3 -0
- data/lib/barnyard_harvester.rb +162 -0
- data/lib/test.yml +15 -0
- data/spec/fixtures/data-add.yml +18 -0
- data/spec/fixtures/data-change.yml +15 -0
- data/spec/fixtures/data-delete-all-records-add-one.yml +3 -0
- data/spec/fixtures/data-delete.yml +12 -0
- data/spec/fixtures/data-init.yml +15 -0
- data/spec/hash_spec.rb +146 -0
- data/spec/loader_spec.rb +37 -0
- data/spec/redis_spec.rb +149 -0
- data/spec/spec_helper.rb +19 -0
- metadata +134 -0
data/.DS_Store
ADDED
Binary file
|
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use ruby-1.9.3-p125@barnyard
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2012 Jon Gillies
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# BarnyardHarvester
|
2
|
+
|
3
|
+
The Harvester gem provides a simple interface where you can iterate your data source and send records to the sync engine. The default backend storage is Redis. Since the workers will use Resque which requires Redis, this make sense to use Redis to cache the data. However, any backend cache can be implemented.
|
4
|
+
|
5
|
+
Your data sources are called "crops". You must assign a unique integer 1..100 to each crop. This is the integer that is used to create the Redis collection. By default Redis only allows a maximum of 16 databases, so this must be changed if you go above 16.
|
6
|
+
|
7
|
+
WARNING! Do not use crop number 1-9 in production, they are reserved for system testing.
|
8
|
+
|
9
|
+
The sync engine keeps a copy of your data source in a Redis databased indexed on the data source's primary key. When you sync your data, the engine uses this "cached" copy of the data to determine adds, deletes and changes. None of the data is inspected and the sync engine does not care what is in the data as long as it can be marshaled into a JSON string. The sync engine automatically does the conversion to and from JSON, so all you pass in is an object that responds to .to_json.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'barnyard_harvester'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install barnyard_harvester
|
24
|
+
|
25
|
+
## Getting Started
|
26
|
+
|
27
|
+
This example assumes that you have Redis running on your local box localhost:6379. If you do not have Redis installed you can add :backend => :hash to the constructor.
|
28
|
+
|
29
|
+
require "barnyard_harvester"
|
30
|
+
|
31
|
+
# Get a connection to your data source
|
32
|
+
# (For this example we will use a YAML file)
|
33
|
+
|
34
|
+
@data = YAML::load_file("test.yml")
|
35
|
+
|
36
|
+
h = BarnyardHarvester::Sync.new(:debug => false, :crop_number => 1,
|
37
|
+
#:backend => :hash
|
38
|
+
)
|
39
|
+
|
40
|
+
h.run do
|
41
|
+
|
42
|
+
# Iterate your data here and call #process with the primary key and the value
|
43
|
+
@data.each do |primary_key, value|
|
44
|
+
my_log.info "PK: #{primary_key}"
|
45
|
+
h.process primary_key, value # <<-- Send your data here!
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
puts h.stats
|
51
|
+
|
52
|
+
Sample output:
|
53
|
+
|
54
|
+
(0) adds, (0) deletes, (0) changes, (5) source records, (5) cache records
|
55
|
+
|
56
|
+
## Testing
|
57
|
+
|
58
|
+
RSPEC is used to test this gem. Note that you should run each spec test individually. For some reason they confilict if run together. You will need Redis installed on the localhost:6379 or adjust the test accordingly.
|
59
|
+
|
60
|
+
This tests the initilization and parameters:
|
61
|
+
|
62
|
+
rspec -c spec/loader_spec.rb
|
63
|
+
.......
|
64
|
+
|
65
|
+
Finished in 0.24556 seconds
|
66
|
+
7 examples, 0 failures
|
67
|
+
|
68
|
+
This tests the Redis backend of the harvester:
|
69
|
+
|
70
|
+
rspec -c spec/redis_spec.rb
|
71
|
+
.....
|
72
|
+
|
73
|
+
Finished in 0.27042 seconds
|
74
|
+
5 examples, 0 failures
|
75
|
+
|
76
|
+
This tests the Hash backend of the harvester:
|
77
|
+
|
78
|
+
rspec -c spec/hash_spec.rb
|
79
|
+
....
|
80
|
+
|
81
|
+
Finished in 0.06071 seconds
|
82
|
+
4 examples, 0 failures
|
83
|
+
|
84
|
+
## Questions or Problems?
|
85
|
+
|
86
|
+
supercoder@gmail.com
|
87
|
+
|
88
|
+
## Contributing
|
89
|
+
|
90
|
+
1. Fork it
|
91
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
92
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
93
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
94
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "barnyard_harvester/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "barnyard_harvester"
|
7
|
+
gem.version = BarnyardHarvester::VERSION
|
8
|
+
gem.authors = ["Jon Gillies"]
|
9
|
+
gem.email = %w(supercoder@gmail.com)
|
10
|
+
gem.description = %q{Performs harvests on data sources and detects adds, changes and deletes.}
|
11
|
+
gem.summary = %q{Please check the README.md for more information.}
|
12
|
+
gem.homepage = "https://github.com/jongillies/barnyard/tree/master/barnyard_harvester"
|
13
|
+
|
14
|
+
gem.rubyforge_project = "barnyard_harvester"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split("\n")
|
17
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
gem.require_paths = %w(lib)
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
gem.add_development_dependency "rspec"
|
23
|
+
gem.add_runtime_dependency "resque"
|
24
|
+
gem.add_runtime_dependency "crack"
|
25
|
+
gem.add_runtime_dependency "json"
|
26
|
+
gem.add_runtime_dependency "uuid"
|
27
|
+
|
28
|
+
end
|
data/lib/.DS_Store
ADDED
Binary file
|
@@ -0,0 +1,84 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'crack'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module BarnyardHarvester
|
7
|
+
|
8
|
+
class Barn
|
9
|
+
|
10
|
+
def initialize(args)
|
11
|
+
|
12
|
+
@crop_number = args.fetch(:crop_number) { raise "You must provide :crop_number" }
|
13
|
+
@redis_settings = args.fetch(:redis_settings) { DEFAULT_REDIS_SETTINGS }
|
14
|
+
@debug = args.fetch(:debug) { false }
|
15
|
+
@log = args.fetch(:logger) { Logger.new(STDOUT) }
|
16
|
+
|
17
|
+
@my_id = "#{args[:crop_number]}-#{self.class}"
|
18
|
+
@barn = Hash.new
|
19
|
+
|
20
|
+
# Setup data source connection
|
21
|
+
begin
|
22
|
+
@barn = YAML::load_file("#{@my_id}.yml")
|
23
|
+
rescue
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def log_run(harvester_uuid, crop_number, began_at, ended_at, source_count, change_count, add_count, delete_count)
|
28
|
+
@log.debug "HARVESTER_LOG #{harvester_uuid}, #{crop_number}, #{began_at}, #{ended_at}, #{source_count}, #{change_count}, #{add_count}, #{delete_count})"
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete(primary_key)
|
32
|
+
check_key primary_key
|
33
|
+
object = Crack::JSON.parse(@barn[primary_key])
|
34
|
+
@barn.delete primary_key
|
35
|
+
object # Return the object
|
36
|
+
end
|
37
|
+
|
38
|
+
def []= primary_key, object
|
39
|
+
check_key primary_key
|
40
|
+
check_object object
|
41
|
+
|
42
|
+
@barn[primary_key]= object.to_json
|
43
|
+
end
|
44
|
+
|
45
|
+
def [] primary_key
|
46
|
+
check_key primary_key
|
47
|
+
|
48
|
+
Crack::JSON.parse(@barn[primary_key])
|
49
|
+
end
|
50
|
+
|
51
|
+
def has_key?(primary_key)
|
52
|
+
check_key primary_key
|
53
|
+
|
54
|
+
if @barn.has_key? primary_key
|
55
|
+
Crack::JSON.parse(@barn[primary_key])
|
56
|
+
else
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def each
|
62
|
+
@barn.each do |primary_key, value|
|
63
|
+
yield primary_key, Crack::JSON.parse(value)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def flush
|
68
|
+
File.open("#{@my_id}.yml", "w") { |file| file.puts(@barn.to_yaml) }
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def check_key(primary_key)
|
74
|
+
# Raise an exception here if the key must conform to a specific format
|
75
|
+
# Example: raise "key must be a string object" unless key.is_a? String
|
76
|
+
end
|
77
|
+
|
78
|
+
def check_object(object)
|
79
|
+
raise "#{object.class} must implement the to_json method" unless object.respond_to? :to_json
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module BarnyardHarvester
|
2
|
+
class Queue
|
3
|
+
|
4
|
+
def initialize(args)
|
5
|
+
|
6
|
+
@debug = args.fetch(:debug) { false }
|
7
|
+
|
8
|
+
@log = args.fetch(:logger) { Logger.new(STDOUT) }
|
9
|
+
|
10
|
+
@my_id = "#{args[:crop_number]}-#{self.class}"
|
11
|
+
|
12
|
+
@queue = Hash.new
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def push(harvester_uuid, crop_change_uuid, crop_number, primary_key, transaction_type, value, old_value=Hash.new)
|
17
|
+
check_key primary_key
|
18
|
+
|
19
|
+
@queue[primary_key] = value.to_json
|
20
|
+
|
21
|
+
@log.debug "HashQueue: Now: #{DateTime.now}, Harvester:#{harvester_uuid}, Change:#{crop_change_uuid} crop_number: #{crop_number}, key: #{primary_key}, transaction_type: #{transaction_type})"
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def flush
|
26
|
+
File.open("#{@my_id}.yml", "w") { |file| file.puts(@queue.to_yaml) }
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def check_key(primary_key)
|
32
|
+
# Raise an exception here if the key must conform to a specific format
|
33
|
+
# Example: raise "key must be a string object" unless key.is_a? String
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
# AddQueue
|
39
|
+
#
|
40
|
+
class AddQueue < Queue
|
41
|
+
end
|
42
|
+
|
43
|
+
# ChangeQueue
|
44
|
+
#
|
45
|
+
class ChangeQueue < Queue
|
46
|
+
end
|
47
|
+
|
48
|
+
# DeleteQueue
|
49
|
+
#
|
50
|
+
class DeleteQueue < Queue
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require "redis"
|
2
|
+
require "crack"
|
3
|
+
require "json"
|
4
|
+
require "resque"
|
5
|
+
|
6
|
+
module BarnyardHarvester
|
7
|
+
|
8
|
+
class HarvesterLogs
|
9
|
+
@queue = :logs_harvester
|
10
|
+
end
|
11
|
+
|
12
|
+
class Barn
|
13
|
+
|
14
|
+
def initialize(args)
|
15
|
+
|
16
|
+
@crop_number = args.fetch(:crop_number) { raise "You must provide :crop_number" }
|
17
|
+
@redis_settings = args.fetch(:redis_settings) { DEFAULT_REDIS_SETTINGS }
|
18
|
+
@debug = args.fetch(:debug) { false }
|
19
|
+
@log = args.fetch(:logger) { Logger.new(STDOUT) }
|
20
|
+
|
21
|
+
redis_args = @redis_settings
|
22
|
+
|
23
|
+
# This sets the database number for redis to store the cached data
|
24
|
+
redis_args[:db] = args[:crop_number]
|
25
|
+
|
26
|
+
# Connect to Redis
|
27
|
+
@redis = Redis.new(redis_args)
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def log_run(harvester_uuid, crop_number, began_at, ended_at, source_count, change_count, add_count, delete_count)
|
32
|
+
|
33
|
+
begin
|
34
|
+
Resque.enqueue(HarvesterLogs, Time.now, harvester_uuid, crop_number, began_at, ended_at, source_count, change_count, add_count, delete_count)
|
35
|
+
rescue Exception => e
|
36
|
+
logger.fatal "#{self.class} Fail in Resque.enqueue of HarvesterLogs. #{e.backtrace}"
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete(primary_key)
|
42
|
+
check_key primary_key
|
43
|
+
|
44
|
+
value = @redis.get primary_key # Save the value
|
45
|
+
@redis.del primary_key # Delete the key
|
46
|
+
Crack::JSON.parse(value) # Return the object
|
47
|
+
end
|
48
|
+
|
49
|
+
def []= primary_key, object
|
50
|
+
check_key primary_key
|
51
|
+
check_object object
|
52
|
+
|
53
|
+
@redis.set primary_key, object.to_json
|
54
|
+
end
|
55
|
+
|
56
|
+
def [] primary_key
|
57
|
+
check_key primary_key
|
58
|
+
|
59
|
+
Crack::JSON.parse(@redis.get primary_key)
|
60
|
+
end
|
61
|
+
|
62
|
+
def has_key?(primary_key)
|
63
|
+
check_key primary_key
|
64
|
+
|
65
|
+
if @redis.exists primary_key
|
66
|
+
Crack::JSON.parse(@redis.get primary_key)
|
67
|
+
else
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def each
|
73
|
+
@redis.keys('*').each do |primary_key|
|
74
|
+
yield primary_key, Crack::JSON.parse(@redis.get(primary_key))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def flush
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def check_key(primary_key)
|
84
|
+
# Raise an exception here if the key must conform to a specific format
|
85
|
+
# Example: raise "key must be a string object" unless key.is_a? String
|
86
|
+
end
|
87
|
+
|
88
|
+
def check_object(object)
|
89
|
+
raise "#{object.class} must implement the to_json method" unless object.respond_to? :to_json
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module BarnyardHarvester
|
2
|
+
|
3
|
+
class ChangeLogs
|
4
|
+
@queue = :logs_change
|
5
|
+
end
|
6
|
+
|
7
|
+
class Queue
|
8
|
+
|
9
|
+
class ResqueQueue
|
10
|
+
def initialize(queue, queued_at, harvester_uuid, crop_change_uuid, crop_number, primary_key, transaction_type, value, old_value)
|
11
|
+
Resque.enqueue(queue, queued_at, harvester_uuid, crop_change_uuid, crop_number, primary_key, transaction_type, value, old_value)
|
12
|
+
Resque.enqueue(ChangeLogs,queued_at, harvester_uuid, crop_change_uuid, crop_number, primary_key, transaction_type, value, old_value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(args)
|
17
|
+
|
18
|
+
@debug = args.fetch(:debug) { false }
|
19
|
+
@log = args.fetch(:logger) { Logger.new(STDOUT) }
|
20
|
+
|
21
|
+
raise "arguments must contain :crop_number => some_integer" if args[:crop_number].nil?
|
22
|
+
|
23
|
+
resque_class_name = "Distribute"
|
24
|
+
|
25
|
+
# If the class does not exist, the rescue block will create it.
|
26
|
+
# The Class Queue is inherited by the AddQueue, ChangeQueue and DeleteQueue, but
|
27
|
+
# we only want to create one "resque" queue for this instantiation
|
28
|
+
begin
|
29
|
+
Object.const_get(resque_class_name)
|
30
|
+
rescue
|
31
|
+
# Set the queue name to this apol_harvester's id prefixed with a Q_
|
32
|
+
#Object.const_set(resque_class_name, Class.new { @queue = "Q_#{args[:crop_number]}"})
|
33
|
+
Object.const_set(resque_class_name, Class.new { @queue = "Farmer"})
|
34
|
+
end
|
35
|
+
|
36
|
+
@resque_queue = Object.const_get(resque_class_name)
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
def push(harvester_uuid, crop_change_uuid, crop_number, primary_key, transaction_type, value, old_value=Hash.new)
|
41
|
+
check_key primary_key
|
42
|
+
|
43
|
+
ResqueQueue.new(@resque_queue, DateTime.now, harvester_uuid, crop_change_uuid, crop_number, primary_key, transaction_type, value.to_json, old_value.to_json)
|
44
|
+
|
45
|
+
message = "RedisQueue: #{@resque_queue}, Now: #{DateTime.now}, Harvester:#{harvester_uuid}, Change:#{crop_change_uuid} crop_number: #{crop_number}, key: #{primary_key}, transaction_type: #{transaction_type})"
|
46
|
+
|
47
|
+
if @log.level == Logger::DEBUG
|
48
|
+
message += ", value: #{value.to_json}, old_value: #{old_value.to_json}"
|
49
|
+
@log.debug message
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Flush any data if needed.
|
54
|
+
#
|
55
|
+
def flush
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Raise an exception here if the key must conform to a specific format
|
61
|
+
#
|
62
|
+
def check_key(primary_key)
|
63
|
+
# Example: raise "key must be a string object" unless key.is_a? String
|
64
|
+
primary_key
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
# AddQueue
|
70
|
+
#
|
71
|
+
class AddQueue < Queue
|
72
|
+
end
|
73
|
+
|
74
|
+
# ChangeQueue
|
75
|
+
#
|
76
|
+
class ChangeQueue < Queue
|
77
|
+
end
|
78
|
+
|
79
|
+
# DeleteQueue
|
80
|
+
#
|
81
|
+
class DeleteQueue < Queue
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require "uuid"
|
2
|
+
require "logger"
|
3
|
+
|
4
|
+
require "barnyard_harvester/version"
|
5
|
+
|
6
|
+
module BarnyardHarvester
|
7
|
+
ADD = "add"
|
8
|
+
CHANGE = "change"
|
9
|
+
DELETE = "delete"
|
10
|
+
|
11
|
+
# Available Options:
|
12
|
+
#
|
13
|
+
# :crop_number (required)
|
14
|
+
# :debug true/1
|
15
|
+
# :resque_enqueue true/1 (Goes to Farmer queue)
|
16
|
+
#
|
17
|
+
# :logger_call_back (calls this function with a load of params)
|
18
|
+
# :data_call_back (I totally forgot what this was for)
|
19
|
+
# :backend (default is :redis, available are: :hash, :mongodb)
|
20
|
+
#
|
21
|
+
|
22
|
+
DEFAULT_REDIS_SETTINGS = {:host => "localhost", :port => 6379}
|
23
|
+
|
24
|
+
class Sync
|
25
|
+
|
26
|
+
attr_reader :my_barn, :my_add_queue, :my_change_queue, :my_delete_queue
|
27
|
+
attr_reader :add_count, :change_count, :delete_count, :source_count, :cache_count
|
28
|
+
attr_reader :resque_enqueue, :redis_settings
|
29
|
+
attr_reader :harvester_uuid, :backend, :crop_number
|
30
|
+
|
31
|
+
def initialize(args)
|
32
|
+
|
33
|
+
@crop_number = args.fetch(:crop_number) { raise "You must provide :crop_number" }
|
34
|
+
@redis_settings = args.fetch(:redis_settings) { DEFAULT_REDIS_SETTINGS }
|
35
|
+
@debug = args.fetch(:debug) { false }
|
36
|
+
@log = args.fetch(:logger) { Logger.new(STDOUT) }
|
37
|
+
|
38
|
+
|
39
|
+
@backend = args.fetch(:backend) { :redis }
|
40
|
+
|
41
|
+
require "barnyard_harvester/#{@backend.to_s}_queue"
|
42
|
+
require "barnyard_harvester/#{@backend.to_s}"
|
43
|
+
|
44
|
+
# YAML::ENGINE.yamler = 'syck'
|
45
|
+
|
46
|
+
@uuid = UUID.new
|
47
|
+
@harvester_uuid = @uuid.generate
|
48
|
+
|
49
|
+
@key_store = Hash.new
|
50
|
+
|
51
|
+
# Setup barn and queues
|
52
|
+
@my_barn = BarnyardHarvester::Barn.new args
|
53
|
+
@my_add_queue = BarnyardHarvester::AddQueue.new args
|
54
|
+
@my_change_queue = BarnyardHarvester::ChangeQueue.new args
|
55
|
+
@my_delete_queue = BarnyardHarvester::DeleteQueue.new args
|
56
|
+
|
57
|
+
@add_count = @change_count = @delete_count = @source_count = @cache_count = 0
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete_run
|
62
|
+
|
63
|
+
deletes = Array.new
|
64
|
+
|
65
|
+
# Iterate Cache Data, detect deletes.
|
66
|
+
@my_barn.each do |primary_key, value|
|
67
|
+
|
68
|
+
@cache_count += 1
|
69
|
+
|
70
|
+
next unless @key_store[primary_key].nil?
|
71
|
+
|
72
|
+
# We got delete
|
73
|
+
begin
|
74
|
+
crop_change_uuid = @uuid.generate
|
75
|
+
@my_delete_queue.push @harvester_uuid, crop_change_uuid, @crop_number, primary_key, BarnyardHarvester::DELETE, value
|
76
|
+
rescue Exception => e
|
77
|
+
@log.fatal "FATAL error pushing delete #{primary_key} to queue. #{e}"
|
78
|
+
exit 1
|
79
|
+
end
|
80
|
+
|
81
|
+
deletes << primary_key
|
82
|
+
|
83
|
+
@delete_count += 1
|
84
|
+
end
|
85
|
+
|
86
|
+
# Remove Deletes from the Cache Data
|
87
|
+
deletes.each do |primary_key|
|
88
|
+
@my_barn.delete primary_key
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
def process primary_key, value
|
94
|
+
|
95
|
+
@source_count += 1
|
96
|
+
|
97
|
+
crop_change_uuid = @uuid.generate
|
98
|
+
|
99
|
+
@key_store[primary_key] = :present # TODO What did this do: if @call_back.nil?
|
100
|
+
|
101
|
+
if @my_barn.has_key? primary_key
|
102
|
+
|
103
|
+
@log.debug "original: #{@my_barn[primary_key]}"
|
104
|
+
|
105
|
+
YAML::ENGINE.yamler = 'syck'
|
106
|
+
|
107
|
+
@log.debug "current : #{Crack::JSON.parse(value.to_json)}"
|
108
|
+
|
109
|
+
if @my_barn[primary_key] != Crack::JSON.parse(value.to_json)
|
110
|
+
#We got change!
|
111
|
+
begin
|
112
|
+
@my_change_queue.push(@harvester_uuid, crop_change_uuid, @crop_number, primary_key, BarnyardHarvester::CHANGE, value, @my_barn[primary_key])
|
113
|
+
rescue Exception => e
|
114
|
+
@log.fatal "FATAL error pushing change #{primary_key} to queue. #{e}"
|
115
|
+
exit 1
|
116
|
+
end
|
117
|
+
|
118
|
+
@my_barn[primary_key] = value
|
119
|
+
@change_count += 1
|
120
|
+
end
|
121
|
+
else
|
122
|
+
# We got add!
|
123
|
+
begin
|
124
|
+
@my_add_queue.push(@harvester_uuid, crop_change_uuid, @crop_number, primary_key, BarnyardHarvester::ADD, value)
|
125
|
+
rescue Exception => e
|
126
|
+
@log.fatal "FATAL error pushing add #{primary_key} to queue. #{e}"
|
127
|
+
exit 1
|
128
|
+
end
|
129
|
+
|
130
|
+
@my_barn[primary_key] = value
|
131
|
+
@add_count += 1
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def stats
|
136
|
+
"(#{@add_count}) adds, (#{@delete_count}) deletes, (#{@change_count}) changes, (#{@source_count}) source records, (#{@cache_count}) cache records"
|
137
|
+
end
|
138
|
+
|
139
|
+
def run
|
140
|
+
|
141
|
+
@began_at = Time.now
|
142
|
+
|
143
|
+
yield
|
144
|
+
|
145
|
+
# Detect and queue Deletes
|
146
|
+
delete_run
|
147
|
+
|
148
|
+
@ended_at = Time.now
|
149
|
+
|
150
|
+
# Let Farmer know I'm done and to flush the updates
|
151
|
+
@my_barn.flush
|
152
|
+
@my_add_queue.flush
|
153
|
+
@my_change_queue.flush
|
154
|
+
@my_delete_queue.flush
|
155
|
+
|
156
|
+
@my_barn.log_run(@harvester_uuid, @crop_number, @began_at, @ended_at, @source_count, @change_count, @add_count, @delete_count)
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
data/lib/test.yml
ADDED
data/spec/hash_spec.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require "barnyard_harvester"
|
2
|
+
require "yaml"
|
3
|
+
require "redis"
|
4
|
+
require "logger"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
|
8
|
+
# TODO.. Move the flush to the backend object, not here!!!!!!!!!
|
9
|
+
|
10
|
+
describe BarnyardHarvester do
|
11
|
+
|
12
|
+
|
13
|
+
def load_and_process_file(file, backend)
|
14
|
+
|
15
|
+
data = YAML::load_file file
|
16
|
+
|
17
|
+
redis_settings = {
|
18
|
+
:host => "localhost",
|
19
|
+
:port => 6379,
|
20
|
+
}
|
21
|
+
|
22
|
+
my_logger = Logger.new(STDOUT)
|
23
|
+
my_logger.level = Logger::INFO
|
24
|
+
|
25
|
+
h = BarnyardHarvester::Sync.new(:backend => backend, :debug => false, :crop_number => 1, :hash_settings => redis_settings, :logger => my_logger)
|
26
|
+
|
27
|
+
h.run do
|
28
|
+
data.each do |primary_key, value|
|
29
|
+
h.process primary_key, value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
h
|
34
|
+
end
|
35
|
+
|
36
|
+
def flush(crop_number)
|
37
|
+
|
38
|
+
File.delete("#{crop_number}-BarnyardHarvester::Barn.yml") if File.exist? "#{crop_number}-BarnyardHarvester::Barn.yml"
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
before(:each) do
|
43
|
+
|
44
|
+
|
45
|
+
@crop_number = 1
|
46
|
+
|
47
|
+
flush @crop_number
|
48
|
+
|
49
|
+
file = "spec/fixtures/data-init.yml"
|
50
|
+
|
51
|
+
data = YAML::load_file file
|
52
|
+
|
53
|
+
h = load_and_process_file(file, :hash)
|
54
|
+
|
55
|
+
h.add_count.should eq(data.count)
|
56
|
+
h.delete_count.should eq(0)
|
57
|
+
h.change_count.should eq(0)
|
58
|
+
h.source_count.should eq(data.count)
|
59
|
+
h.cache_count.should eq(data.count)
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
#it "test initial load of records" do
|
64
|
+
#
|
65
|
+
# data = YAML::load_file "spec/fixtures/data-init.yml"
|
66
|
+
#
|
67
|
+
# redis = Redis.new(:db => 1)
|
68
|
+
#
|
69
|
+
# data.each do |primary_key, value|
|
70
|
+
# value.to_json.should eq(redis.get(primary_key))
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
#end
|
74
|
+
|
75
|
+
it "test add one record" do
|
76
|
+
|
77
|
+
file = "spec/fixtures/data-add.yml"
|
78
|
+
data = YAML::load_file file
|
79
|
+
|
80
|
+
h = load_and_process_file(file, :hash)
|
81
|
+
|
82
|
+
h.add_count.should eq(1)
|
83
|
+
h.delete_count.should eq(0)
|
84
|
+
h.change_count.should eq(0)
|
85
|
+
h.source_count.should eq(data.count)
|
86
|
+
h.cache_count.should eq(data.count)
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
it "test change one record" do
|
91
|
+
|
92
|
+
file = "spec/fixtures/data-change.yml"
|
93
|
+
|
94
|
+
data = YAML::load_file file
|
95
|
+
|
96
|
+
h = load_and_process_file(file, :hash)
|
97
|
+
|
98
|
+
h.add_count.should eq(0)
|
99
|
+
h.delete_count.should eq(0)
|
100
|
+
h.change_count.should eq(1)
|
101
|
+
h.source_count.should eq(data.count)
|
102
|
+
h.cache_count.should eq(data.count)
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
it "test delete one record" do
|
107
|
+
|
108
|
+
file = "spec/fixtures/data-delete.yml"
|
109
|
+
|
110
|
+
data = YAML::load_file file
|
111
|
+
|
112
|
+
h = load_and_process_file(file, :hash)
|
113
|
+
|
114
|
+
|
115
|
+
h.add_count.should eq(0)
|
116
|
+
h.delete_count.should eq(1)
|
117
|
+
h.change_count.should eq(0)
|
118
|
+
h.source_count.should eq(data.count)
|
119
|
+
h.cache_count.should eq(data.count + 1)
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
it "test delete all records and add one" do
|
124
|
+
|
125
|
+
init_file = "spec/fixtures/data-init.yml"
|
126
|
+
init_data = YAML::load_file init_file
|
127
|
+
|
128
|
+
file = "spec/fixtures/data-delete-all-records-add-one.yml"
|
129
|
+
#data = YAML::load_file file
|
130
|
+
|
131
|
+
h = load_and_process_file(file, :hash)
|
132
|
+
|
133
|
+
h.add_count.should eq(1)
|
134
|
+
h.delete_count.should eq(5)
|
135
|
+
h.change_count.should eq(0)
|
136
|
+
h.source_count.should eq(1)
|
137
|
+
h.cache_count.should eq(init_data.count + 1)
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
after(:each) do
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
end
|
data/spec/loader_spec.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require "barnyard_harvester"
|
2
|
+
|
3
|
+
describe BarnyardHarvester do
|
4
|
+
|
5
|
+
it "no parameters should raise error" do
|
6
|
+
lambda{BarnyardHarvester::Sync.new}.should raise_error
|
7
|
+
end
|
8
|
+
|
9
|
+
it "passing only :crop_number => 1 should return BarnyardHarvester::Sync object" do
|
10
|
+
BarnyardHarvester::Sync.new(crop_number: 1).class.should eq(BarnyardHarvester::Sync)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "default backend should be :redis" do
|
14
|
+
BarnyardHarvester::Sync.new(crop_number: 1).backend.should eq(:redis)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "passing backend :hash should be :hash" do
|
18
|
+
BarnyardHarvester::Sync.new(crop_number: 1, backend: :hash).backend.should eq(:hash)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "passing bogus backend should raise an error" do
|
22
|
+
lambda{BarnyardHarvester::Sync.new(crop_number: 1, backend: :foobar)}.should raise_error
|
23
|
+
end
|
24
|
+
|
25
|
+
it "crop_number should be 1001" do
|
26
|
+
BarnyardHarvester::Sync.new(crop_number: 1001).crop_number.should eq(1001)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "all counters should be zero" do
|
30
|
+
BarnyardHarvester::Sync.new(crop_number: 1).change_count.should eq(0)
|
31
|
+
BarnyardHarvester::Sync.new(crop_number: 1).add_count.should eq(0)
|
32
|
+
BarnyardHarvester::Sync.new(crop_number: 1).delete_count.should eq(0)
|
33
|
+
BarnyardHarvester::Sync.new(crop_number: 1).source_count.should eq(0)
|
34
|
+
BarnyardHarvester::Sync.new(crop_number: 1).cache_count.should eq(0)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/spec/redis_spec.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require "barnyard_harvester"
|
2
|
+
require "yaml"
|
3
|
+
require "redis"
|
4
|
+
require "logger"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
|
8
|
+
# TODO.. Move the flush to the backend object, not here!!!!!!!!!
|
9
|
+
|
10
|
+
describe BarnyardHarvester do
|
11
|
+
|
12
|
+
|
13
|
+
def load_and_process_file(file, backend)
|
14
|
+
|
15
|
+
data = YAML::load_file file
|
16
|
+
|
17
|
+
redis_settings = {
|
18
|
+
:host => "localhost",
|
19
|
+
:port => 6379,
|
20
|
+
}
|
21
|
+
|
22
|
+
my_logger = Logger.new(STDOUT)
|
23
|
+
my_logger.level = Logger::INFO
|
24
|
+
|
25
|
+
h = BarnyardHarvester::Sync.new(:backend => backend, :debug => false, :crop_number => 1, :redis_settings => redis_settings, :logger => my_logger)
|
26
|
+
|
27
|
+
h.run do
|
28
|
+
data.each do |primary_key, value|
|
29
|
+
h.process primary_key, value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
h
|
34
|
+
end
|
35
|
+
|
36
|
+
def flush
|
37
|
+
|
38
|
+
r = Redis.new(:db => 1)
|
39
|
+
|
40
|
+
r.keys.each do |k|
|
41
|
+
r.del k
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
before(:each) do
|
47
|
+
|
48
|
+
flush
|
49
|
+
|
50
|
+
@crop_number = 1
|
51
|
+
|
52
|
+
file = "spec/fixtures/data-init.yml"
|
53
|
+
|
54
|
+
data = YAML::load_file file
|
55
|
+
|
56
|
+
h = load_and_process_file(file, :redis)
|
57
|
+
|
58
|
+
h.add_count.should eq(data.count)
|
59
|
+
h.delete_count.should eq(0)
|
60
|
+
h.change_count.should eq(0)
|
61
|
+
h.source_count.should eq(data.count)
|
62
|
+
h.cache_count.should eq(data.count)
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
it "test initial load of records" do
|
67
|
+
|
68
|
+
data = YAML::load_file "spec/fixtures/data-init.yml"
|
69
|
+
|
70
|
+
redis = Redis.new(:db => 1)
|
71
|
+
|
72
|
+
data.each do |primary_key, value|
|
73
|
+
value.to_json.should eq(redis.get(primary_key))
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
it "test add one record" do
|
79
|
+
|
80
|
+
file = "spec/fixtures/data-add.yml"
|
81
|
+
data = YAML::load_file file
|
82
|
+
|
83
|
+
h = load_and_process_file(file, :redis)
|
84
|
+
|
85
|
+
h.add_count.should eq(1)
|
86
|
+
h.delete_count.should eq(0)
|
87
|
+
h.change_count.should eq(0)
|
88
|
+
h.source_count.should eq(data.count)
|
89
|
+
h.cache_count.should eq(data.count)
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
it "test change one record" do
|
94
|
+
|
95
|
+
file = "spec/fixtures/data-change.yml"
|
96
|
+
|
97
|
+
data = YAML::load_file file
|
98
|
+
|
99
|
+
h = load_and_process_file(file, :redis)
|
100
|
+
|
101
|
+
h.add_count.should eq(0)
|
102
|
+
h.delete_count.should eq(0)
|
103
|
+
h.change_count.should eq(1)
|
104
|
+
h.source_count.should eq(data.count)
|
105
|
+
h.cache_count.should eq(data.count)
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
it "test delete one record" do
|
110
|
+
|
111
|
+
file = "spec/fixtures/data-delete.yml"
|
112
|
+
|
113
|
+
data = YAML::load_file file
|
114
|
+
|
115
|
+
h = load_and_process_file(file, :redis)
|
116
|
+
|
117
|
+
|
118
|
+
h.add_count.should eq(0)
|
119
|
+
h.delete_count.should eq(1)
|
120
|
+
h.change_count.should eq(0)
|
121
|
+
h.source_count.should eq(data.count)
|
122
|
+
h.cache_count.should eq(data.count + 1)
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
it "test delete all records and add one" do
|
127
|
+
|
128
|
+
init_file = "spec/fixtures/data-init.yml"
|
129
|
+
init_data = YAML::load_file init_file
|
130
|
+
|
131
|
+
file = "spec/fixtures/data-delete-all-records-add-one.yml"
|
132
|
+
#data = YAML::load_file file
|
133
|
+
|
134
|
+
h = load_and_process_file(file, :redis)
|
135
|
+
|
136
|
+
h.add_count.should eq(1)
|
137
|
+
h.delete_count.should eq(5)
|
138
|
+
h.change_count.should eq(0)
|
139
|
+
h.source_count.should eq(1)
|
140
|
+
h.cache_count.should eq(init_data.count + 1)
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
after(:each) do
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
|
12
|
+
# Run specs in random order to surface order dependencies. If you find an
|
13
|
+
# order dependency and want to debug it, you can fix the order by providing
|
14
|
+
# the seed, which is printed after each run.
|
15
|
+
# --seed 1234
|
16
|
+
config.order = 'random'
|
17
|
+
end
|
18
|
+
|
19
|
+
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: barnyard_harvester
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jon Gillies
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70309177353080 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70309177353080
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: resque
|
27
|
+
requirement: &70309177352620 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70309177352620
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: crack
|
38
|
+
requirement: &70309177352180 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70309177352180
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: json
|
49
|
+
requirement: &70309177351760 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70309177351760
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: uuid
|
60
|
+
requirement: &70309177351320 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70309177351320
|
69
|
+
description: Performs harvests on data sources and detects adds, changes and deletes.
|
70
|
+
email:
|
71
|
+
- supercoder@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .DS_Store
|
77
|
+
- .gitignore
|
78
|
+
- .rvmrc
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- barnyard_harvester.gemspec
|
84
|
+
- lib/.DS_Store
|
85
|
+
- lib/barnyard_harvester.rb
|
86
|
+
- lib/barnyard_harvester/hash.rb
|
87
|
+
- lib/barnyard_harvester/hash_queue.rb
|
88
|
+
- lib/barnyard_harvester/redis.rb
|
89
|
+
- lib/barnyard_harvester/redis_queue.rb
|
90
|
+
- lib/barnyard_harvester/version.rb
|
91
|
+
- lib/test.yml
|
92
|
+
- spec/fixtures/data-add.yml
|
93
|
+
- spec/fixtures/data-change.yml
|
94
|
+
- spec/fixtures/data-delete-all-records-add-one.yml
|
95
|
+
- spec/fixtures/data-delete.yml
|
96
|
+
- spec/fixtures/data-init.yml
|
97
|
+
- spec/hash_spec.rb
|
98
|
+
- spec/loader_spec.rb
|
99
|
+
- spec/redis_spec.rb
|
100
|
+
- spec/spec_helper.rb
|
101
|
+
homepage: https://github.com/jongillies/barnyard/tree/master/barnyard_harvester
|
102
|
+
licenses: []
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ! '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project: barnyard_harvester
|
121
|
+
rubygems_version: 1.8.15
|
122
|
+
signing_key:
|
123
|
+
specification_version: 3
|
124
|
+
summary: Please check the README.md for more information.
|
125
|
+
test_files:
|
126
|
+
- spec/fixtures/data-add.yml
|
127
|
+
- spec/fixtures/data-change.yml
|
128
|
+
- spec/fixtures/data-delete-all-records-add-one.yml
|
129
|
+
- spec/fixtures/data-delete.yml
|
130
|
+
- spec/fixtures/data-init.yml
|
131
|
+
- spec/hash_spec.rb
|
132
|
+
- spec/loader_spec.rb
|
133
|
+
- spec/redis_spec.rb
|
134
|
+
- spec/spec_helper.rb
|