coin 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development do
4
+ gem "micro_test"
5
+ gem "pry"
6
+ # gem "pry-stack_explorer"
7
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ coderay (1.0.8)
5
+ method_source (0.8.1)
6
+ micro_test (0.3.1)
7
+ os
8
+ os (0.9.6)
9
+ pry (0.9.10)
10
+ coderay (~> 1.0.5)
11
+ method_source (~> 0.8)
12
+ slop (~> 3.3.1)
13
+ pry (0.9.10-java)
14
+ coderay (~> 1.0.5)
15
+ method_source (~> 0.8)
16
+ slop (~> 3.3.1)
17
+ spoon (~> 0.0)
18
+ slop (3.3.3)
19
+ spoon (0.0.1)
20
+
21
+ PLATFORMS
22
+ java
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ micro_test
27
+ pry
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Nathan Hopkins
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,125 @@
1
+ # Coin
2
+
3
+ ## Memcached? We don't need no stinking Memcached.
4
+
5
+ ... Well, you might depending upon your specific needs.
6
+ But be sure to have a look at Coin before you reach for the sledgehammer.
7
+
8
+ Coin is an absurdly simple in memory object caching system written in Ruby.
9
+
10
+ ## Why Coin?
11
+
12
+ * No configuration required
13
+ * No added complexity to your stack
14
+ * Small footprint (under 200 lines)
15
+ * Simple API
16
+
17
+ Coin uses [Distributed Ruby (DRb)](http://pragprog.com/book/sidruby/the-druby-book)
18
+ to create a simple in memory caching server that addresses many of the same needs as Memcached
19
+ and other similar solutions.
20
+
21
+ ## Quick Start
22
+
23
+ Installation
24
+
25
+ ```bash
26
+ $ gem install coin
27
+ ```
28
+
29
+ Basic Usage
30
+
31
+ ```ruby
32
+ require "coin"
33
+ Coin.write :foo, true
34
+ Coin.read :foo # => true
35
+ ```
36
+
37
+ ## Next Steps
38
+
39
+ Examples of more advanced usage.
40
+
41
+ ```ruby
42
+ require "coin"
43
+
44
+ # read and/or assign a default value in a single step
45
+ Coin.read(:bar) { true } # => true
46
+
47
+ # write data with an explicit expiration (in seconds)
48
+ # this example expires in 5 seconds (default is 300)
49
+ Coin.write :bar, true, 5
50
+ sleep 5
51
+ Coin.read :bar # => nil
52
+
53
+ # delete an entry
54
+ Coin.write :bar, true
55
+ Coin.delete :bar
56
+ Coin.read :bar # => nil
57
+
58
+ # read and delete in a single step
59
+ Coin.write :bar, true
60
+ Coin.read_and_delete :bar # => true
61
+ Coin.read :bar # => nil
62
+
63
+ # determine how many items are in the cache
64
+ 10.times do |i|
65
+ Coin.write "key#{i}", true
66
+ end
67
+ Coin.length # => 10
68
+
69
+ # clear the cache
70
+ Coin.clear
71
+ Coin.length # => 0
72
+ ```
73
+
74
+ ## Deep Cuts
75
+
76
+ Coin automatically starts a DRb server that hosts the Coin::Vault.
77
+ You can take control of this behavior if needed.
78
+
79
+ ```ruby
80
+ require "coin"
81
+
82
+ # configure the port that the DRb server runs on (default is 8955)
83
+ Coin.port = 8080
84
+
85
+ # configure the URI that the DRb server runs on (defaults to druby://localhost:PORT)
86
+ Coin.uri = "druby://10.0.0.100:8080"
87
+
88
+ # access the DRb server exposing Coin::Vault
89
+ Coin.server # => #<Coin::Vault:0x007fe182852e18>
90
+
91
+ # determine if the server is running
92
+ Coin.server_running? # => true
93
+
94
+ # determine the pid of the server process
95
+ Coin.pid # => "63299"
96
+
97
+ # stop the server
98
+ Coin.stop_server # => true
99
+
100
+ # start the server
101
+ Coin.start_server # => true
102
+
103
+ # start the server forcing a restart if the server is already running
104
+ Coin.start_server true # => true
105
+ ```
106
+
107
+ ## Run the Tests
108
+
109
+ ```bash
110
+ $ gem install coin
111
+ $ gem unpack coin
112
+ $ cd coin-VERSION
113
+ $ bundle
114
+ $ mt
115
+ ```
116
+
117
+ ## Notes
118
+
119
+ Coin's default behavior launches a single DRb server that provides
120
+ shared access across all processes on a **single machine**.
121
+
122
+ _It should be relatively simple to update Coin to work across multiple machines,
123
+ so keep an eye peeled for this feature in the future._
124
+
125
+ ![Coin Processes](https://www.lucidchart.com/publicSegments/view/50b8299a-7c5c-4175-8121-6b190a7a0a70/image.png)
data/bin/coin ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby --
2
+ require "drb/drb"
3
+ require "tempfile"
4
+ require File.join(File.dirname(__FILE__), "..", "lib", "coin")
5
+
6
+ Signal.trap("HUP") do
7
+ File.delete(Coin.pid_file) if File.exist?(Coin.pid_file)
8
+ DRb.thread.kill if DRb.thread
9
+ end
10
+
11
+ if File.exist?(Coin.pid_file)
12
+ begin
13
+ Process.kill("HUP", File.read(Coin.pid_file).to_i)
14
+ rescue Exception => ex
15
+ end
16
+ end
17
+
18
+ File.open(Coin.pid_file, "w") do |file|
19
+ file.write Process.pid
20
+ end
21
+
22
+ DRb.start_service ENV["COIN_URI"], Coin::Vault.instance
23
+ puts "Coin::Vault listening at: #{ENV["COIN_URI"]}"
24
+ DRb.thread.join
data/lib/coin.rb ADDED
@@ -0,0 +1,103 @@
1
+ require "drb/drb"
2
+ require "rbconfig"
3
+ require "forwardable"
4
+
5
+ Dir[File.join(File.dirname(__FILE__), "coin", "*.rb")].each do |file|
6
+ require file
7
+ end
8
+
9
+ module Coin
10
+ class << self
11
+ extend Forwardable
12
+ def_delegators :server, :write, :read_and_delete, :delete, :clear, :length
13
+
14
+ def read(key, lifetime=300)
15
+ value = server.read(key)
16
+ if value.nil? && block_given?
17
+ value = yield
18
+ write(key, value, lifetime)
19
+ end
20
+ value
21
+ end
22
+
23
+ attr_writer :port
24
+ def port
25
+ @port ||= 8955
26
+ end
27
+
28
+ attr_writer :uri
29
+ def uri
30
+ "druby://localhost:#{port}"
31
+ end
32
+
33
+ def server
34
+ return nil unless ENV["COIN_URI"].nil?
35
+
36
+ if server_running?
37
+ if @server
38
+ begin
39
+ @server.ok? if @server
40
+ rescue DRb::DRbConnError => ex
41
+ @server = nil
42
+ end
43
+ end
44
+
45
+ if @server.nil?
46
+ begin
47
+ @server = DRbObject.new_with_uri(uri)
48
+ @server.ok?
49
+ rescue DRb::DRbConnError => ex
50
+ @server = nil
51
+ end
52
+ end
53
+ end
54
+
55
+ return @server if @server && server_running?
56
+
57
+ start_server
58
+ DRb.start_service
59
+ @server = DRbObject.new_with_uri(uri)
60
+ end
61
+
62
+ def pid_file
63
+ "/tmp/coin-pid-63f95cb5-0bae-4f66-88ec-596dfbac9244"
64
+ end
65
+
66
+ def pid
67
+ File.read(Coin.pid_file) if File.exist?(Coin.pid_file)
68
+ end
69
+
70
+ def server_running?
71
+ @pid = pid
72
+ return false unless @pid
73
+ begin
74
+ Process.kill(0, @pid.to_i)
75
+ rescue Errno::ESRCH => ex
76
+ return false
77
+ end
78
+ true
79
+ end
80
+
81
+ def start_server(force=nil)
82
+ return if server_running? && !force
83
+ stop_server if force
84
+ ruby = "#{RbConfig::CONFIG["bindir"]}/ruby"
85
+ script = File.expand_path(File.join(File.dirname(__FILE__), "..", "bin", "coin"))
86
+ env = {
87
+ "COIN_URI" => Coin.uri
88
+ }
89
+ pid = spawn(env, "#{ruby} #{script}")
90
+ Process.detach(pid)
91
+
92
+ sleep 0.1 while !server_running?
93
+ true
94
+ end
95
+
96
+ def stop_server
97
+ Process.kill("HUP", @pid.to_i) if server_running?
98
+ sleep 0.1 while server_running?
99
+ true
100
+ end
101
+
102
+ end
103
+ end
data/lib/coin/vault.rb ADDED
@@ -0,0 +1,75 @@
1
+ require "thread"
2
+ require "singleton"
3
+ require "forwardable"
4
+
5
+ module Coin
6
+ class Vault
7
+ extend Forwardable
8
+ include Singleton
9
+ def_delegators :@dict, :length
10
+
11
+ def read(key)
12
+ value = @dict[key]
13
+ value = nil if value && value_expired?(value)
14
+ return value[:value] if value
15
+ nil
16
+ end
17
+
18
+ def read_and_delete(key)
19
+ value = nil
20
+ @mutex.synchronize do
21
+ value = read(key)
22
+ @dict.delete(key)
23
+ end
24
+ value
25
+ end
26
+
27
+ def write(key, value, lifetime=300)
28
+ @mutex.synchronize do
29
+ @dict[key] = { :value => value, :cached_at => Time.now, :lifetime => lifetime }
30
+ end
31
+ value
32
+ end
33
+
34
+ def delete(key)
35
+ @mutex.synchronize { @dict.delete(key) }
36
+ end
37
+
38
+ def clear
39
+ @mutex.synchronize { @dict = {} }
40
+ end
41
+
42
+ def ok?
43
+ true
44
+ end
45
+
46
+ protected
47
+
48
+ def initialize
49
+ @mutex = Mutex.new
50
+ @dict = {}
51
+ start_sweeper
52
+ end
53
+
54
+ def start_sweeper
55
+ Thread.new do
56
+ loop do
57
+ sleep 60
58
+ sweep
59
+ end
60
+ end
61
+ end
62
+
63
+ def sweep
64
+ now = Time.now
65
+ @dict.each do |key, value|
66
+ delete(key) if value_expired?(value)
67
+ end
68
+ end
69
+
70
+ def value_expired?(value)
71
+ Time.now - value[:cached_at] > value[:lifetime]
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,3 @@
1
+ module Coin
2
+ VERSION = "0.1.1"
3
+ end
data/test/coin_test.rb ADDED
@@ -0,0 +1,74 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper")
2
+
3
+ class CoinTest < MicroTest::Test
4
+ before do
5
+ @key = "key-#{SecureRandom.uuid}"
6
+ end
7
+
8
+ after do
9
+ Coin.stop_server
10
+ end
11
+
12
+ test "stop_server" do
13
+ Coin.start_server
14
+ assert Coin.server_running?
15
+ Coin.stop_server
16
+ assert Coin.server_running? == false
17
+ end
18
+
19
+ test "server method starts the server" do
20
+ Coin.stop_server
21
+ assert !Coin.server_running?
22
+ Coin.server
23
+ assert Coin.server_running?
24
+ end
25
+
26
+ test "read access starts the server" do
27
+ Coin.stop_server
28
+ assert !Coin.server_running?
29
+ Coin.read :foo
30
+ assert Coin.server_running?
31
+ end
32
+
33
+ test "read with assignment block" do
34
+ value = rand(99999999999)
35
+ assert Coin.read(@key).nil?
36
+ Coin.read(@key) { value }
37
+ assert Coin.read(@key) == value
38
+ end
39
+
40
+ test "read and delete" do
41
+ value = rand(99999999999)
42
+ Coin.write @key, value
43
+ val = Coin.read_and_delete(@key)
44
+ assert val == value
45
+ assert Coin.read(@key).nil?
46
+ end
47
+
48
+ test "write with expiration" do
49
+ Coin.write(@key, true, 1)
50
+ assert Coin.read(@key)
51
+ sleep 1
52
+ assert Coin.read(@key).nil?
53
+ end
54
+
55
+ test "delete" do
56
+ Coin.write(@key, true)
57
+ assert Coin.read(@key)
58
+ Coin.delete(@key)
59
+ assert Coin.read(@key).nil?
60
+ end
61
+
62
+ test "length" do
63
+ 10.times { |i| Coin.write("key#{i}", rand(9999)) }
64
+ assert Coin.length >= 10
65
+ end
66
+
67
+ test "clear" do
68
+ 10.times { |i| Coin.write("key#{i}", rand(9999)) }
69
+ assert Coin.length >= 10
70
+ Coin.clear
71
+ assert Coin.length == 0
72
+ end
73
+
74
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.require :default, :development
4
+ require File.join(File.dirname(__FILE__), "..", "lib", "coin")
@@ -0,0 +1,43 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper")
2
+ require "securerandom"
3
+
4
+ class VaultTest < MicroTest::Test
5
+
6
+ before do
7
+ @vault = Coin::Vault.instance
8
+ @key = "key-#{SecureRandom.uuid}"
9
+ end
10
+
11
+ test "read using an unstored key" do
12
+ assert @vault.read(@key).nil?
13
+ end
14
+
15
+ test "basic write & read" do
16
+ value = rand(999999999)
17
+ @vault.write(@key, value)
18
+ assert @vault.read(@key) == value
19
+ end
20
+
21
+ test "read_and_delete" do
22
+ value = rand(999999999)
23
+ @vault.write(@key, value)
24
+ val = @vault.read_and_delete(@key)
25
+ assert @vault.read(@key).nil?
26
+ assert val == value
27
+ end
28
+
29
+ test "delete" do
30
+ @vault.write(@key, true)
31
+ assert @vault.read(@key)
32
+ @vault.delete(@key)
33
+ assert @vault.read(@key).nil?
34
+ end
35
+
36
+ test "clear" do
37
+ 10.times { |i| @vault.write("key#{i}", true) }
38
+ assert @vault.length >= 10
39
+ @vault.clear
40
+ assert @vault.length == 0
41
+ end
42
+
43
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: coin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nathan Hopkins
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-30 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email:
16
+ - natehop@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/coin/vault.rb
22
+ - lib/coin/version.rb
23
+ - lib/coin.rb
24
+ - test/coin_test.rb
25
+ - test/test_helper.rb
26
+ - test/vault_test.rb
27
+ - bin/coin
28
+ - Gemfile
29
+ - Gemfile.lock
30
+ - LICENSE.txt
31
+ - README.md
32
+ homepage: https://github.com/hopsoft/coin
33
+ licenses:
34
+ - MIT
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubyforge_project:
53
+ rubygems_version: 1.8.23
54
+ signing_key:
55
+ specification_version: 3
56
+ summary: An absurdly simple in memory object caching system.
57
+ test_files: []
58
+ has_rdoc: