hanover 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hanover.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Bryce Kerley
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.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Hanover
2
+
3
+ Hanover is a Riak-based implementation of a couple CRDTs.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'hanover'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install hanover
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env rake
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require "bundler/gem_tasks"
5
+
6
+ task :default => :test
7
+
8
+ Rake::TestTask.new(:test) do |test|
9
+ test.libs << 'lib' << 'test'
10
+ test.pattern = 'test/*_test.rb'
11
+ test.verbose = true
12
+ end
13
+
14
+ task :test => :riak_running
15
+
16
+ task :riak_running do
17
+ begin
18
+ sh 'curl -s localhost:8091 > /dev/null'
19
+ rescue => e
20
+ puts "Couldn't see that Riak is running on port 8091. Sad trombone. :("
21
+ raise e
22
+ end
23
+ end
data/hanover.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/hanover/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Bryce Kerley"]
6
+ gem.email = ["bkerley@brycekerley.net"]
7
+ gem.description = %q{A Riak-based CRDT implementation of sets and counters.}
8
+ gem.summary = %q{Riak-based CRDT implementation.}
9
+ gem.homepage = "https://github.com/bkerley/hanover"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "hanover"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Hanover::VERSION
17
+
18
+ gem.add_dependency 'riak-client', '~> 1.0.3'
19
+ gem.add_development_dependency 'rake'
20
+ gem.add_development_dependency 'minitest'
21
+ gem.add_development_dependency 'shoulda-context'
22
+ end
data/lib/hanover.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'set'
2
+ require 'json'
3
+ require 'riak'
4
+
5
+ require "hanover/version"
6
+
7
+ module Hanover
8
+ # Your code goes here...
9
+ end
10
+
11
+ %w{g_set two_p_set g_counter persistence}.each do |f|
12
+ require_relative File.join('hanover', f)
13
+ end
@@ -0,0 +1,54 @@
1
+ module Hanover
2
+ class GCounter
3
+ attr_accessor :counts
4
+ def initialize
5
+ self.counts = Hash.new{0}
6
+ end
7
+
8
+ def to_json
9
+ {
10
+ type: 'GCounter',
11
+ c: counts
12
+ }.to_json
13
+ end
14
+
15
+ def self.from_json(json)
16
+ # don't symbolize keys
17
+ h = JSON.parse json
18
+ raise ArgumentError.new 'unexpected type field in JSON' unless h['type'] == 'GCounter'
19
+
20
+ gc = new
21
+ gc.counts = h['c']
22
+ return gc
23
+ end
24
+
25
+ def increment
26
+ counts[tag] += 1
27
+ end
28
+
29
+ def value
30
+ counts.values.inject(0, &:+)
31
+ end
32
+
33
+ def merge(other)
34
+ new_keys = Set.new
35
+ new_keys.merge counts.keys
36
+ new_keys.merge other.counts.keys
37
+
38
+ new_keys.each do |k|
39
+ counts[k] = [counts[k], other.counts[k]].max
40
+ end
41
+ end
42
+
43
+ def tag
44
+ radix = 36
45
+ [
46
+ object_id.to_s(radix),
47
+ Process.uid.to_s(radix),
48
+ Process.gid.to_s(radix),
49
+ Process.pid.to_s(radix),
50
+ `hostname`.strip
51
+ ].join
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,37 @@
1
+ module Hanover
2
+ class GSet
3
+ attr_accessor :members
4
+ def initialize
5
+ self.members = Set.new
6
+ end
7
+
8
+ def self.from_json(json)
9
+ h = JSON.parse json, symbolize_names: true
10
+ raise ArgumentError.new 'unexpected type field in JSON' unless h[:type] == 'GSet'
11
+
12
+ gs = new
13
+ gs.members.merge h[:a]
14
+
15
+ return gs
16
+ end
17
+
18
+ def add(atom)
19
+ self.members.add atom
20
+ end
21
+
22
+ def include?(atom)
23
+ self.members.include? atom
24
+ end
25
+
26
+ def to_json(*args)
27
+ {
28
+ type: 'GSet',
29
+ a: self.members.to_a
30
+ }.to_json(*args)
31
+ end
32
+
33
+ def merge(other)
34
+ self.members.merge other.members
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,88 @@
1
+ module Hanover
2
+ class Persistence
3
+ attr_reader :key, :content
4
+ def initialize(content, key = nil)
5
+ @content = content
6
+ @key = key
7
+ @klass = @content.class
8
+
9
+ save
10
+ end
11
+
12
+ def respond_to?(name)
13
+ methods.include?(name) || @content.respond_to?(name)
14
+ end
15
+
16
+ def method_missing(name, *args, &block)
17
+ result = @content.send name, *args, &block
18
+ save unless @robject.raw_data == @content.to_json
19
+ result
20
+ end
21
+
22
+ def initialize_from_key(key)
23
+ @key = key
24
+ @robject = bucket.get key
25
+
26
+ get_klass
27
+ @content = @klass.new
28
+ perform_merges
29
+ end
30
+
31
+ def reload
32
+ @robject.reload
33
+ perform_merges
34
+ end
35
+
36
+ def inspect
37
+ "#<Hanover::Persistence::0x#{object_id.to_s(16)} @key=#{@robject.key} #{@content.inspect}>"
38
+ end
39
+
40
+ def self.find(key)
41
+ p = allocate
42
+ p.initialize_from_key key
43
+ return p
44
+ end
45
+
46
+ def self.client
47
+ return @client if defined? @client
48
+ @client = Riak::Client.new http_port: 8091
49
+ end
50
+
51
+ private
52
+ def save
53
+ create unless @robject
54
+ @robject.raw_data = @content.to_json
55
+ @robject.content_type = 'application/json'
56
+ @robject.store
57
+ reload
58
+ end
59
+
60
+ def create
61
+ @robject = bucket.new @key
62
+ @robject.raw_data = @content.to_json
63
+
64
+ @robject.store
65
+ @key = @robject.key
66
+ end
67
+
68
+ def perform_merges
69
+ if @robject.conflict?
70
+ @robject.siblings.each {|s| @content.merge @klass.from_json s.raw_data }
71
+ else
72
+ @content.merge @klass.from_json @robject.raw_data
73
+ end
74
+ end
75
+
76
+ def get_klass
77
+ if @robject.conflict?
78
+ @klass = Hanover.const_get @robject.siblings.first.data['type']
79
+ else
80
+ @klass = Hanover.const_get @robject.data['type']
81
+ end
82
+ end
83
+
84
+ def bucket
85
+ self.class.client.bucket 'hanover'
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,52 @@
1
+ module Hanover
2
+ class TwoPSet
3
+ attr_accessor :added, :removed
4
+
5
+ def initialize
6
+ self.added = GSet.new
7
+ self.removed = GSet.new
8
+ end
9
+
10
+ def self.from_json(json)
11
+ h = JSON.parse json, symbolize_names: true
12
+ raise ArgumentError.new 'unexpected type field in JSON' unless h[:type] == 'TwoPSet'
13
+
14
+ tps = new
15
+ tps.added = GSet.from_json h[:a].to_json
16
+ tps.removed = GSet.from_json h[:r].to_json
17
+
18
+ return tps
19
+ end
20
+
21
+ def include?(atom)
22
+ members.include? atom
23
+ end
24
+
25
+ def members
26
+ self.added.members - self.removed.members
27
+ end
28
+
29
+ def add(atom)
30
+ raise ArgumentError.new 'already removed' if self.removed.include? atom
31
+
32
+ self.added.add atom
33
+ end
34
+
35
+ def remove(atom)
36
+ self.removed.add atom
37
+ end
38
+
39
+ def merge(other)
40
+ self.added.merge other.added
41
+ self.removed.merge other.removed
42
+ end
43
+
44
+ def to_json(*args)
45
+ {
46
+ type: 'TwoPSet',
47
+ a: self.added,
48
+ r: self.removed
49
+ }.to_json *args
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module Hanover
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,36 @@
1
+ require_relative 'helper'
2
+
3
+ class GCounterTest < HanoverCase
4
+ context 'GCounter' do
5
+ subject { GCounter.new }
6
+
7
+ should 'increment' do
8
+ assert_equal 0, subject.value
9
+
10
+ subject.increment
11
+ subject.increment
12
+
13
+ assert_equal 2, subject.value
14
+ end
15
+
16
+ should 'merge' do
17
+ other = subject.class.new
18
+ subject.increment
19
+ other.increment
20
+
21
+ subject.merge other
22
+
23
+ assert_equal 2, subject.value
24
+ end
25
+
26
+ should 'round trip from JSON' do
27
+ subject.increment
28
+ subject.increment
29
+
30
+ j = subject.to_json
31
+ other = subject.class.from_json j
32
+
33
+ assert_equal 2, other.value
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'helper'
2
+
3
+ class GSetTest < HanoverCase
4
+ context 'GSet' do
5
+ subject { GSet.new }
6
+ should 'have entries after addition' do
7
+ subject.add :alpha
8
+ subject.add :bravo
9
+
10
+ assert_includes subject, :alpha
11
+ assert_includes subject, :bravo
12
+ end
13
+
14
+ should 'merge' do
15
+ other = GSet.new
16
+
17
+ subject.add :charlie
18
+ other.add :delta
19
+
20
+ subject.merge other
21
+
22
+ assert_includes subject, :charlie
23
+ assert_includes subject, :delta
24
+ end
25
+
26
+ should 'json round trip' do
27
+ subject.add 'echo'
28
+ subject.add 'foxtrot'
29
+ subject.add 7
30
+
31
+ j = subject.to_json
32
+ other = GSet.from_json j
33
+
34
+ assert_equal subject.members, other.members
35
+ end
36
+ end
37
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'shoulda-context'
2
+ require 'minitest/autorun'
3
+
4
+ require_relative File.join('..', 'lib', 'hanover')
5
+
6
+ class HanoverCase < MiniTest::Unit::TestCase
7
+ include Shoulda::Context::Assertions
8
+ include Shoulda::Context::InstanceMethods
9
+ extend Shoulda::Context::ClassMethods
10
+
11
+ include Hanover
12
+ end
@@ -0,0 +1,52 @@
1
+ require_relative 'helper'
2
+
3
+ class PersistenceTest < HanoverCase
4
+ context 'the Persistence class' do
5
+ subject { Persistence }
6
+
7
+ should 'wrap a CRDT' do
8
+ wrapped = Persistence.new GSet.new
9
+
10
+ assert wrapped.key
11
+ assert_kind_of GSet, wrapped.content
12
+ end
13
+
14
+ should 'load successfully' do
15
+ wrapped = Persistence.new GSet.new
16
+
17
+ found = Persistence.find wrapped.key
18
+
19
+ assert_equal wrapped.key, found.key
20
+ end
21
+ end
22
+
23
+ context 'a wrapped GSet' do
24
+ subject { Persistence.new GSet.new }
25
+
26
+ should 'name itself' do
27
+ assert subject.key
28
+ end
29
+
30
+ should 'delegate adding elements to the set' do
31
+ subject.add 'alpha'
32
+ subject.add 'bravo'
33
+
34
+ assert_includes subject, 'alpha'
35
+ assert_includes subject, 'bravo'
36
+ end
37
+
38
+ should 'support parallel adds consistently' do
39
+ other = Persistence.find subject.key
40
+ subject.add 'alpha'
41
+ other.add 'bravo'
42
+ subject.reload
43
+
44
+ third = Persistence.find subject.key
45
+
46
+ assert_includes subject, 'alpha'
47
+ assert_includes subject, 'bravo'
48
+ assert_includes third, 'alpha'
49
+ assert_includes third, 'bravo'
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,48 @@
1
+ require_relative 'helper'
2
+
3
+ class TwoPSetTest < HanoverCase
4
+ context 'TwoPSet' do
5
+ subject { TwoPSet.new }
6
+
7
+ should 'support adding and removing' do
8
+ subject.add :alpha
9
+ subject.add :bravo
10
+
11
+ assert_includes subject, :alpha
12
+ assert_includes subject, :bravo
13
+
14
+ subject.remove :bravo
15
+
16
+ refute_includes subject, :bravo
17
+ end
18
+
19
+ should 'merge' do
20
+ other = subject.class.new
21
+
22
+ subject.add 'echo'
23
+ other.add 'foxtrot'
24
+ subject.add 'george'
25
+ other.remove 'george'
26
+
27
+ assert_includes subject.members, 'george'
28
+
29
+ subject.merge other
30
+
31
+ assert_includes subject, 'echo'
32
+ assert_includes subject, 'foxtrot'
33
+
34
+ refute_includes subject, 'george'
35
+ end
36
+
37
+ should 'round trip from JSON' do
38
+ subject.add 'charlie'
39
+ subject.add 'delta'
40
+ subject.remove 'delta'
41
+
42
+ j = subject.to_json
43
+ other = subject.class.from_json j
44
+
45
+ assert_equal subject.members, other.members
46
+ end
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hanover
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Bryce Kerley
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: riak-client
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.3
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.3
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: minitest
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: shoulda-context
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: A Riak-based CRDT implementation of sets and counters.
79
+ email:
80
+ - bkerley@brycekerley.net
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - Gemfile
87
+ - LICENSE
88
+ - README.md
89
+ - Rakefile
90
+ - hanover.gemspec
91
+ - lib/hanover.rb
92
+ - lib/hanover/g_counter.rb
93
+ - lib/hanover/g_set.rb
94
+ - lib/hanover/persistence.rb
95
+ - lib/hanover/two_p_set.rb
96
+ - lib/hanover/version.rb
97
+ - test/g_counter_test.rb
98
+ - test/g_set_test.rb
99
+ - test/helper.rb
100
+ - test/persistence_test.rb
101
+ - test/two_p_set_test.rb
102
+ homepage: https://github.com/bkerley/hanover
103
+ licenses: []
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ! '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ! '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 1.8.23
123
+ signing_key:
124
+ specification_version: 3
125
+ summary: Riak-based CRDT implementation.
126
+ test_files:
127
+ - test/g_counter_test.rb
128
+ - test/g_set_test.rb
129
+ - test/helper.rb
130
+ - test/persistence_test.rb
131
+ - test/two_p_set_test.rb