btc_price 0.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9c22316b62b88011503e3ed00673873eb2d104a7
4
+ data.tar.gz: c23e0f1f7293b9761ae63038b1184ea01c7f5942
5
+ SHA512:
6
+ metadata.gz: 695989ee06c588b96daf4c43f27569759949766498a927c470c4ac4db78de9e5eb3141a07c7374dbd3c4b5d7d82d26c8205623619352f44ed4c9677896ba2970
7
+ data.tar.gz: d8f1ba3be11f91716694ba7ff9a6d171505d7af2bca8a83c0e2f157b503ec8e4a0ff819fdaa002841992d79c776866d6edf323878bc9af7afcd1a61bbf3a29c2
data/.gitignore ADDED
@@ -0,0 +1,22 @@
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
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ deploy:
2
+ provider: rubygems
3
+ api_key:
4
+ secure: "DsYnX0Ju3juHWMJLUfWh3fUEfXpTq8Ifpsm0xP4GAxpZbt6OHt2thtUdnOlCuvE7PwTLu1noj2BmKXMdrZf2p3CLOcvG2HH3Dhlizrxm6bmRK7EBw+OjP5udSg++TDddeBjJcB7RbNX898gOhr9OdzktibtBmwDsbHyCH3F8dm4="
5
+ language: ruby
6
+ rvm:
7
+ - 2.1.1
8
+ services:
9
+ - memcached
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in btc_price.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Matt Campbell
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,44 @@
1
+ [![Build Status](https://travis-ci.org/mecampbellsoup/btc_price.png?branch=master)](https://travis-ci.org/mecampbellsoup/btc_price)
2
+
3
+ # BTC Price
4
+
5
+ ![Because bitcoin is awesome!](http://media.coindesk.com/2014/03/47660272.jpg)
6
+
7
+ ## What does it do?
8
+
9
+ From within your Code (or really any) directory, run:
10
+
11
+ `git clone git@github.com:mecampbellsoup/btc_price.git`;
12
+
13
+ then:
14
+
15
+ `cd btc_price`.
16
+
17
+ If you do not have the [`memcached`](https://github.com/evan/memcached) gem, go get it:
18
+
19
+ `gem install memcached`.
20
+
21
+ Next, start (and immediately background) your cache process with:
22
+
23
+ `memcached -p 11211 &`;
24
+
25
+ and finally you can run the gem's executable:
26
+
27
+ `btc_price`.
28
+
29
+ You should see some content in STDOUT like this:
30
+
31
+ ```bash
32
+ External services status: Good.
33
+ Current BTC price: $622.21.
34
+ ```
35
+
36
+ If you run the executable again (`btc_price`), you'll notice it's much faster. This is because the library uses [`memcached`](https://github.com/evan/memcached) to cache the BTC price and avoid making redundant API requests.
37
+
38
+ # Why did I write this library?
39
+
40
+ I wrote this small library initially to learn about caching and how to implement it in Ruby.
41
+
42
+ We started using a text file for caching and later on switched to `Memcached`.
43
+
44
+ I like to hack on this on the weekends in order to improve my OOP and design skills.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new :test do |t|
4
+ t.pattern = 'spec/**/*_spec.rb'
5
+ t.libs << 'lib'
6
+ t.libs << 'spec'
7
+ t.verbose = true
8
+ end
9
+
10
+ task default: :test
data/bin/btc_price ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.push("lib")
4
+
5
+ require "btc_price"
6
+
7
+ puts BtcPrice::Runner.run
data/btc_price.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'btc_price/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "btc_price"
8
+ spec.version = BtcPrice::VERSION
9
+ spec.authors = ["Matt Campbell"]
10
+ spec.email = ["mecampbell25@gmail.com"]
11
+ spec.summary = %q{Gem for grabbing BTC price from CLI. Also send yourself email price alerts.}
12
+ spec.description = <<-DESC
13
+ Use this gem for BTC price discovery; you can also configure
14
+ email notifications at price levels of your choosing.
15
+ DESC
16
+ spec.homepage = ""
17
+ spec.license = "MIT"
18
+
19
+ spec.files = `git ls-files -z`.split("\x0")
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_runtime_dependency "memcached", "~> 1.8"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.6"
27
+ spec.add_development_dependency "rake", "~> 10.3"
28
+ spec.add_development_dependency "mocha", "~> 1.1"
29
+ spec.add_development_dependency "pry", "~> 0.9"
30
+ spec.add_development_dependency "minitest", "~> 5.3"
31
+ end
data/lib/btc_price.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "btc_price/version"
2
+ require "btc_price/cache"
3
+ require "btc_price/coinbase_gateway"
4
+ require "btc_price/runner"
5
+ require "btc_price/services_health"
6
+ require "btc_price/price"
7
+
8
+ module BtcPrice
9
+ end
@@ -0,0 +1,35 @@
1
+ require 'memcached'
2
+
3
+ $cache = Memcached.new("localhost:11211")
4
+
5
+ module BtcPrice
6
+ class Cache
7
+ def initialize(backend)
8
+ @backend = backend
9
+ end
10
+
11
+ def current_price
12
+ begin
13
+ read
14
+ rescue Memcached::NotFound
15
+ write
16
+ end
17
+ end
18
+
19
+ def clear
20
+ $cache.delete 'price'
21
+ end
22
+
23
+ private
24
+
25
+ def read
26
+ $cache.get 'price'
27
+ end
28
+
29
+ def write
30
+ price = @backend.current_price
31
+ $cache.set 'price', price
32
+ price
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,20 @@
1
+ require 'open-uri'
2
+ require 'json'
3
+
4
+ module BtcPrice
5
+ class CoinbaseGateway
6
+ def current_price
7
+ JSON(read_request_price)['subtotal']['amount'].to_f
8
+ end
9
+
10
+ private
11
+
12
+ def request_price
13
+ open('https://coinbase.com/api/v1/prices/sell')
14
+ end
15
+
16
+ def read_request_price
17
+ request_price.read
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ module BtcPrice
2
+ class Price
3
+ def self.adapter
4
+ @adapter ||= Cache.new(CoinbaseGateway.new)
5
+ end
6
+
7
+ def self.adapter=(new_adapter)
8
+ @adapter = new_adapter
9
+ end
10
+
11
+ def self.current_price
12
+ adapter.current_price
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ module BtcPrice
2
+ class Runner
3
+ def self.run
4
+ "External services status: #{(ServicesHealth.new.healthy? ? 'Good.' : 'Bad.')}"\
5
+ "\nCurrent BTC price: $#{sprintf("%.2f", Price.current_price)}."
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ module BtcPrice
2
+ class ServicesHealth
3
+ class CoinbaseApiError < StandardError ; end
4
+
5
+ def healthy?
6
+ # Check if Coinbase API is available
7
+ begin
8
+ response = CoinbaseGateway.new.send(:request_price)
9
+ rescue OpenURI::HTTPError => error
10
+ raise CoinbaseApiError, error.io
11
+ end
12
+
13
+ check_status(response)
14
+ end
15
+
16
+ private
17
+
18
+ def check_status(response)
19
+ # Inspect the response status
20
+ response.status.first == "200" ? true : false
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module BtcPrice
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ module BtcPrice
4
+ class MockCoinbaseGateway
5
+ attr_reader :fetch_counter
6
+
7
+ def initialize
8
+ @fetch_counter = 0
9
+ end
10
+
11
+ def current_price
12
+ @fetch_counter += 1
13
+ 500
14
+ end
15
+ end
16
+
17
+ describe Cache do
18
+ before do
19
+ $cache = $cache.clone
20
+ @gateway = MockCoinbaseGateway.new
21
+ @cache = Cache.new(@gateway)
22
+ begin
23
+ @cache.clear
24
+ rescue Memcached::NotFound
25
+ end
26
+ end
27
+
28
+ after do
29
+ @cache.clear
30
+ end
31
+
32
+ it "hits the external service when the cache is 'cold'" do
33
+ # stub API call; check return value of fetch, i.e. that it equals the stubbed API call
34
+ @cache.current_price.must_equal 500
35
+ # now the 3rd party API gets hit
36
+ end
37
+
38
+ it "does not hit the external service when the cache is 'warm'" do
39
+ @cache.current_price.must_equal 500
40
+ @cache.current_price.must_equal 500
41
+ @gateway.fetch_counter.must_equal 1
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ module BtcPrice
4
+ class TestCoinbaseGateway < CoinbaseGateway
5
+ def read_request_price
6
+ "{\"subtotal\":{\"amount\":\"511.03\",\"currency\":\"USD\"},\"fees\":[{\"coinbase\":{\"amount\":\"5.11\",\"currency\":\"USD\"}},{\"bank\":{\"amount\":\"0.15\",\"currency\":\"USD\"}}],\"total\":{\"amount\":\"505.77\",\"currency\":\"USD\"},\"amount\":\"505.77\",\"currency\":\"USD\"}"
7
+ end
8
+ end
9
+
10
+ describe CoinbaseGateway do
11
+ it "returns the current BTC price" do
12
+ gateway = TestCoinbaseGateway.new
13
+ gateway.current_price.must_equal 511.03
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe "bin/btc_price" do
4
+ it "prints price" do
5
+ `btc_price`.to_i.must_be_close_to(500, 5000)
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ module BtcPrice
5
+ describe Price do
6
+ before do
7
+ Price.adapter = OpenStruct.new(current_price: 492)
8
+ end
9
+
10
+ it "returns the current BTC price" do
11
+ Price.current_price.must_equal 492
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ module BtcPrice
4
+ describe Runner do
5
+ describe ".run" do
6
+ describe "when external services are healthy" do
7
+ before do
8
+ ServicesHealth.any_instance.expects(:healthy?).returns(true)
9
+ Price.expects(:current_price).returns(500)
10
+ end
11
+
12
+ it "returns a string containing a good health report and BTC price when services are available" do
13
+ string = Runner.run
14
+ string.must_match(/Good/)
15
+ string.must_match(/500/)
16
+ end
17
+ end
18
+
19
+ describe "when external services are not healthy" do
20
+ it "returns a string containing a bad health report when services aren't available" do
21
+ ServicesHealth.any_instance.expects(:healthy?).returns(false)
22
+ Runner.run.must_match(/Bad/)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ module BtcPrice
4
+ describe ServicesHealth do
5
+ describe "#healthy?" do
6
+ describe "when services are not healthy" do
7
+ it "raises an exception when a server error is encountered" do
8
+ error = OpenURI::HTTPError.new(503, "Service Unavailable")
9
+ CoinbaseGateway.any_instance.expects(:request_price).raises error
10
+ err = -> { ServicesHealth.new.healthy? }.must_raise ServicesHealth::CoinbaseApiError
11
+ err.message.must_match(/Service Unavailable/)
12
+ end
13
+
14
+ it "returns false when status code is not 200/OK" do
15
+ response = stub(status: ["400", "Bad Request"])
16
+ CoinbaseGateway.any_instance.expects(:request_price).returns(response)
17
+ ServicesHealth.new.healthy?.must_equal false
18
+ end
19
+ end
20
+
21
+ describe "when response status is 200" do
22
+ it "returns true" do
23
+ response = stub(status: ["200", "OK"])
24
+ CoinbaseGateway.any_instance.expects(:request_price).returns(response)
25
+ ServicesHealth.new.healthy?.must_equal true
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,6 @@
1
+ require 'btc_price'
2
+ require 'minitest/autorun'
3
+ require 'minitest/pride'
4
+ require 'minitest/unit'
5
+ require 'mocha/mini_test'
6
+ require 'pry'
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: btc_price
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Matt Campbell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: memcached
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '10.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '10.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mocha
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
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.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '0.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '5.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '5.3'
97
+ description: |
98
+ Use this gem for BTC price discovery; you can also configure
99
+ email notifications at price levels of your choosing.
100
+ email:
101
+ - mecampbell25@gmail.com
102
+ executables:
103
+ - btc_price
104
+ extensions: []
105
+ extra_rdoc_files: []
106
+ files:
107
+ - .gitignore
108
+ - .travis.yml
109
+ - Gemfile
110
+ - LICENSE.txt
111
+ - README.md
112
+ - Rakefile
113
+ - bin/btc_price
114
+ - btc_price.gemspec
115
+ - lib/btc_price.rb
116
+ - lib/btc_price/cache.rb
117
+ - lib/btc_price/coinbase_gateway.rb
118
+ - lib/btc_price/price.rb
119
+ - lib/btc_price/runner.rb
120
+ - lib/btc_price/services_health.rb
121
+ - lib/btc_price/version.rb
122
+ - spec/lib/cache_spec.rb
123
+ - spec/lib/coinbase_gateway_spec.rb
124
+ - spec/lib/integration_spec.rb
125
+ - spec/lib/price_spec.rb
126
+ - spec/lib/runner_spec.rb
127
+ - spec/lib/services_health_spec.rb
128
+ - spec/spec_helper.rb
129
+ homepage: ''
130
+ licenses:
131
+ - MIT
132
+ metadata: {}
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - '>='
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubyforge_project:
149
+ rubygems_version: 2.2.2
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: Gem for grabbing BTC price from CLI. Also send yourself email price alerts.
153
+ test_files:
154
+ - spec/lib/cache_spec.rb
155
+ - spec/lib/coinbase_gateway_spec.rb
156
+ - spec/lib/integration_spec.rb
157
+ - spec/lib/price_spec.rb
158
+ - spec/lib/runner_spec.rb
159
+ - spec/lib/services_health_spec.rb
160
+ - spec/spec_helper.rb
161
+ has_rdoc: