vin_exploder 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/Gemfile.lock +47 -0
- data/README.textile +74 -0
- data/lib/vin_exploder/adapter.rb +47 -0
- data/lib/vin_exploder/cache/activerecord_cache_store.rb +42 -0
- data/lib/vin_exploder/cache/sequel_cache_store.rb +40 -0
- data/lib/vin_exploder/cache.rb +76 -0
- data/lib/vin_exploder/configuration.rb +45 -0
- data/lib/vin_exploder/exploder.rb +30 -0
- data/lib/vin_exploder/explosion.rb +40 -0
- data/lib/vin_exploder/version.rb +3 -0
- data/lib/vin_exploder.rb +19 -0
- data/spec/abstract_adapter_spec.rb +16 -0
- data/spec/cache/activerecord_cache_store_spec.rb +74 -0
- data/spec/cache/cache_store_spec.rb +49 -0
- data/spec/cache/sequel_cache_store_spec.rb +62 -0
- data/spec/configuration_spec.rb +50 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/vin_exploder_spec.rb +51 -0
- metadata +139 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
vin_exploder (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
activemodel (3.0.8)
|
10
|
+
activesupport (= 3.0.8)
|
11
|
+
builder (~> 2.1.2)
|
12
|
+
i18n (~> 0.5.0)
|
13
|
+
activerecord (3.0.8)
|
14
|
+
activemodel (= 3.0.8)
|
15
|
+
activesupport (= 3.0.8)
|
16
|
+
arel (~> 2.0.10)
|
17
|
+
tzinfo (~> 0.3.23)
|
18
|
+
activesupport (3.0.8)
|
19
|
+
arel (2.0.10)
|
20
|
+
builder (2.1.2)
|
21
|
+
diff-lcs (1.1.2)
|
22
|
+
i18n (0.5.0)
|
23
|
+
rspec (2.6.0)
|
24
|
+
rspec-core (~> 2.6.0)
|
25
|
+
rspec-expectations (~> 2.6.0)
|
26
|
+
rspec-mocks (~> 2.6.0)
|
27
|
+
rspec-core (2.6.3)
|
28
|
+
rspec-expectations (2.6.0)
|
29
|
+
diff-lcs (~> 1.1.2)
|
30
|
+
rspec-mocks (2.6.0)
|
31
|
+
sequel (3.24.1)
|
32
|
+
simplecov (0.4.2)
|
33
|
+
simplecov-html (~> 0.4.4)
|
34
|
+
simplecov-html (0.4.5)
|
35
|
+
sqlite3 (1.3.3)
|
36
|
+
tzinfo (0.3.27)
|
37
|
+
|
38
|
+
PLATFORMS
|
39
|
+
ruby
|
40
|
+
|
41
|
+
DEPENDENCIES
|
42
|
+
activerecord (>= 3.0.0)
|
43
|
+
rspec (>= 2.6.0)
|
44
|
+
sequel
|
45
|
+
simplecov
|
46
|
+
sqlite3
|
47
|
+
vin_exploder!
|
data/README.textile
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
h1. VinExploder
|
2
|
+
|
3
|
+
A ruby library for retrieving and caching Vehicle Identification Number (VIN) lookups.
|
4
|
+
|
5
|
+
h2. How to install
|
6
|
+
|
7
|
+
*Bundler*
|
8
|
+
|
9
|
+
<pre>
|
10
|
+
gem vin_exploder
|
11
|
+
</pre>
|
12
|
+
|
13
|
+
*Rubygems*
|
14
|
+
|
15
|
+
<pre>
|
16
|
+
gem install vin_exploder
|
17
|
+
</pre>
|
18
|
+
|
19
|
+
h2. How to use
|
20
|
+
|
21
|
+
Using the vin exploder will require a decoding service adapter. Thankfully one is available in the Vinquery "gem":http://github.com/tinomen/vinquery
|
22
|
+
|
23
|
+
<pre>
|
24
|
+
require 'vin_exploder'
|
25
|
+
# require the Vinquery.com adapter
|
26
|
+
require 'vinquery/vin_exploder/adapter'
|
27
|
+
|
28
|
+
# tell vin_exploder to use the Vinquery.com adapter and pass it's required config
|
29
|
+
VinExploder.config.adapter :vinquery, {:url => 'VINQUERY_URL', :access_code => 'ACCESS_CODE', :report_type => 'REPORT_TYPE'}
|
30
|
+
# request the decoded VIN data
|
31
|
+
vin_explosion = VinExploder.get('1FTWX31R19EB18840')
|
32
|
+
|
33
|
+
vin_explosion.valid? # true
|
34
|
+
vin_explosion.make # Ford
|
35
|
+
</pre>
|
36
|
+
|
37
|
+
h3. Rails use
|
38
|
+
|
39
|
+
Create an initializer for the config
|
40
|
+
|
41
|
+
<pre>
|
42
|
+
require 'vin_exploder'
|
43
|
+
require 'vinquery/vin_exploder/adapter'
|
44
|
+
|
45
|
+
VinExploder.config.adapter :vinquery, {:url => 'VINQUERY_URL', :access_code => 'ACCESS_CODE', :report_type => 'REPORT_TYPE'}
|
46
|
+
</pre>
|
47
|
+
|
48
|
+
h3. Optional Cache
|
49
|
+
|
50
|
+
To save on the cost of looking up a VIN multiple times setup a cache
|
51
|
+
|
52
|
+
<pre>
|
53
|
+
require 'vin_exploder'
|
54
|
+
# require the Sequel based cache store
|
55
|
+
require 'vin_exploder/cache/sequel_cache_store'
|
56
|
+
# require the Vinquery.com adapter
|
57
|
+
require 'vinquery/vin_exploder/adapter'
|
58
|
+
|
59
|
+
# tell vin_exploder to use the Sequel based cache store adapter and pass in the connection config
|
60
|
+
VinExploder.config.cache_store :sequel_cache_store, "mysql://localhost:3896/vin_cache"
|
61
|
+
|
62
|
+
# tell vin_exploder to use the Vinquery.com adapter and pass it's required config
|
63
|
+
VinExploder.config.adapter :vinquery, {:url => 'VINQUERY_URL', :access_code => 'ACCESS_CODE', :report_type => 'REPORT_TYPE'}
|
64
|
+
|
65
|
+
# request the decoded VIN data
|
66
|
+
vin_explosion = VinExploder.get('1FTWX31R19EB18840')
|
67
|
+
|
68
|
+
vin_explosion.valid? #=> true
|
69
|
+
vin_explosion.make #=> Ford
|
70
|
+
|
71
|
+
# decoded VIN data is retrieved from cache
|
72
|
+
vin_explosion2 = VinExploder.get('1FTWX31R19EB18840')
|
73
|
+
vin_explosion2.make #=> Ford
|
74
|
+
</pre>
|
@@ -0,0 +1,47 @@
|
|
1
|
+
|
2
|
+
module VinExploder
|
3
|
+
|
4
|
+
module Decode
|
5
|
+
|
6
|
+
class VinExploderAdapterError < StandardError; end
|
7
|
+
|
8
|
+
class AbstractAdapter
|
9
|
+
|
10
|
+
def initialize(options={})
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
# Fetch, normalize and return the decoded data as a hash.
|
15
|
+
#
|
16
|
+
# *vin*::
|
17
|
+
# The Vehicle Identification Number
|
18
|
+
#
|
19
|
+
# == Returns:
|
20
|
+
# An array containing the decoded data hash.
|
21
|
+
#
|
22
|
+
# Should an error occur while decoding the vin the hash should
|
23
|
+
# include a key of :errors with an array of errors
|
24
|
+
# in the form of: {'error name' => 'error message'}
|
25
|
+
#
|
26
|
+
# Normalize the hash from the vendor to use keys known by VinExploder.
|
27
|
+
# All keys should be made into symbols following the ruby snake case convention.
|
28
|
+
#
|
29
|
+
# The keys that must be normalized are as follows:
|
30
|
+
# make
|
31
|
+
# model
|
32
|
+
# year => YYYY
|
33
|
+
# driveline => FWD|4WD|AWD
|
34
|
+
# body_style
|
35
|
+
# fuel_type => GAS|DIESEL|HYBRID
|
36
|
+
# trim_level
|
37
|
+
#
|
38
|
+
# All remaining keys should be symbols but can remain vendor specific
|
39
|
+
def explode(vin)
|
40
|
+
raise NotImplementedError.new("Unimplemented explode method.")
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'Base64'
|
3
|
+
|
4
|
+
module VinExploder
|
5
|
+
module Cache
|
6
|
+
|
7
|
+
# A VinExploder cache adapter using ActiveRecord for saving the decoded vin attributes.
|
8
|
+
#
|
9
|
+
# this store assumes a model with 2 attributes:
|
10
|
+
# - *key*: the first 8, 10th and 11th characters of the vin
|
11
|
+
# - *data*: A hash of the decoded vin attributes
|
12
|
+
class ActiveRecordCacheStore < Store
|
13
|
+
|
14
|
+
def initialize(options = {})
|
15
|
+
super
|
16
|
+
@model = options[:model_class]
|
17
|
+
end
|
18
|
+
|
19
|
+
def read(vin)
|
20
|
+
key = make_vin_cache_key(vin)
|
21
|
+
obj = @model.find_by_key(key)
|
22
|
+
obj.data unless obj.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def write(vin, hash)
|
26
|
+
key = make_vin_cache_key(vin)
|
27
|
+
obj = @model.new :key => key, :data => hash
|
28
|
+
obj.save
|
29
|
+
hash
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete(vin)
|
33
|
+
key = make_vin_cache_key(vin)
|
34
|
+
obj = @model.find_by_key(key)
|
35
|
+
deleted = obj.delete()
|
36
|
+
!deleted.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
require 'Base64'
|
3
|
+
|
4
|
+
module VinExploder
|
5
|
+
module Cache
|
6
|
+
|
7
|
+
# A VinExploder cache adapter using Sequel for saving the decoded vin attributes.
|
8
|
+
#
|
9
|
+
# this store assumes there are 2 columns on the table:
|
10
|
+
# - *key*: the first 8, 10th and 11th characters of the vin
|
11
|
+
# - *data*: A hash of the decoded vin attributes
|
12
|
+
class SequelCacheStore < Store
|
13
|
+
|
14
|
+
def initialize(options = {})
|
15
|
+
super
|
16
|
+
@connection = Sequel.connect(options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def read(vin)
|
20
|
+
key = make_vin_cache_key(vin)
|
21
|
+
data = @connection[:vins].where(:key => key).first
|
22
|
+
Marshal.load(data[:data]) unless data.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def write(vin, hash)
|
26
|
+
key = make_vin_cache_key(vin)
|
27
|
+
@connection[:vins].insert(:key => key, :data => Marshal.dump(hash))
|
28
|
+
hash
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete(vin)
|
32
|
+
key = make_vin_cache_key(vin)
|
33
|
+
result = @connection[:vins].where(:key => key).delete
|
34
|
+
result > 0
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
|
2
|
+
module VinExploder
|
3
|
+
|
4
|
+
module Cache
|
5
|
+
|
6
|
+
|
7
|
+
# An abstract cache store class that acts as a null cache as well
|
8
|
+
class Store
|
9
|
+
|
10
|
+
attr_reader :connection
|
11
|
+
|
12
|
+
# Create a new cache.
|
13
|
+
def initialize(options = nil)
|
14
|
+
@options = options ? options.dup : {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# Fetches VIN data from the cache using the given key. If the VIN has been
|
18
|
+
# cached, then the VIN attributes are returned.
|
19
|
+
#
|
20
|
+
# If the VIN is not in the cache (a cache miss occurred),
|
21
|
+
# then nil will be returned. However, if a block has been passed, then
|
22
|
+
# that block will be run in the event of a cache miss. The return value
|
23
|
+
# of the block will be written to the cache under the given VIN,
|
24
|
+
# and that return value will be returned.
|
25
|
+
#
|
26
|
+
# cache.write("VIN_NUMBER", {:make => 'Ford', :model => 'F150'})
|
27
|
+
# cache.fetch("VIN_NUMBER") # => {:make => 'Ford', :model => 'F150'}
|
28
|
+
#
|
29
|
+
# cache.fetch("VIN_NUMBER_2") # => nil
|
30
|
+
# cache.fetch("VIN_NUMBER_2") do
|
31
|
+
# {:make => 'Dodge', :model => '1500'}
|
32
|
+
# end
|
33
|
+
# cache.fetch("VIN_NUMBER_2") # => {:make => 'Dodge', :model => '1500'}
|
34
|
+
#
|
35
|
+
def fetch(vin)
|
36
|
+
if block_given?
|
37
|
+
hash = read(vin)
|
38
|
+
if hash.nil?
|
39
|
+
hash = yield
|
40
|
+
write(vin, hash)
|
41
|
+
end
|
42
|
+
else
|
43
|
+
hash = read(vin)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Fetches VIN data from the cache, using the given key. If VIN has
|
48
|
+
# been cached with the given key, then the VIN attributes are returned. Otherwise,
|
49
|
+
# nil is returned.
|
50
|
+
def read(vin)
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
|
54
|
+
# Writes the value to the cache, with the key.
|
55
|
+
def write(vin, hash)
|
56
|
+
hash
|
57
|
+
end
|
58
|
+
|
59
|
+
# Deletes an entry in the cache. Returns +true+ if an entry is deleted.
|
60
|
+
def delete(vin)
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
# the cache key for vins should be based on characters 0-8, 10-11.
|
67
|
+
# Position 9 is a checksum value and should not be used in the key.
|
68
|
+
def make_vin_cache_key(vin)
|
69
|
+
key = vin.slice(0,8)
|
70
|
+
key << vin.slice(10,2)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
module VinExploder
|
3
|
+
|
4
|
+
class Configuration
|
5
|
+
|
6
|
+
attr_accessor :cache_options, :adapter_options
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@cache_store = nil
|
10
|
+
@cache_options = {}
|
11
|
+
@adapter = nil
|
12
|
+
@adapter_options = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def cache_store(*args)
|
16
|
+
if args.empty?
|
17
|
+
case @cache_store
|
18
|
+
when Symbol
|
19
|
+
VinExploder::Cache.const_get(@cache_store.to_s.split('_').map{|s| s.capitalize }.join)
|
20
|
+
else
|
21
|
+
@cache_store
|
22
|
+
end
|
23
|
+
else
|
24
|
+
@cache_store = args.shift
|
25
|
+
@cache_options = args.shift || {}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def adapter(*args)
|
30
|
+
if args.empty?
|
31
|
+
case @adapter
|
32
|
+
when Symbol
|
33
|
+
VinExploder::Decode.const_get(@adapter.to_s.split('_').map{|s| s.capitalize }.join)
|
34
|
+
else
|
35
|
+
@adapter
|
36
|
+
end
|
37
|
+
else
|
38
|
+
@adapter = args.shift
|
39
|
+
@adapter_options = args.shift || {}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module VinExploder
|
2
|
+
|
3
|
+
class MissingAdapter < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class Exploder
|
7
|
+
|
8
|
+
def initialize(adapter, cache=nil)
|
9
|
+
raise MissingAdapter.new("No vin decoding vendor adapter has been provided.") if adapter.nil?
|
10
|
+
@cache = cache ? cache : Cache::Store.new
|
11
|
+
@adapter = adapter
|
12
|
+
end
|
13
|
+
|
14
|
+
# Get vin Explosion.
|
15
|
+
#
|
16
|
+
# == Parameters
|
17
|
+
# vin:: vehicle identification number
|
18
|
+
#
|
19
|
+
# == Return
|
20
|
+
# An Explosion object containing the decoded vin attributes
|
21
|
+
def get(vin)
|
22
|
+
hash = @cache.fetch(vin) do
|
23
|
+
# get from vender adapter
|
24
|
+
@adapter.explode(vin)
|
25
|
+
end
|
26
|
+
Explosion.new vin, hash, hash.delete(:errors)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module VinExploder
|
4
|
+
|
5
|
+
class Explosion
|
6
|
+
|
7
|
+
attr_reader :vin, :success, :errors, :make, :model, :year, :driveline, :body_style, :fuel_type, :all
|
8
|
+
|
9
|
+
def initialize(vin, vin_hash, errors={})
|
10
|
+
@vin = vin
|
11
|
+
@all = vin_hash
|
12
|
+
@make = @all[:make]
|
13
|
+
@model = @all[:model]
|
14
|
+
@year = @all[:year]
|
15
|
+
@driveline = @all[:driveline]
|
16
|
+
@body_style = @all[:body_style]
|
17
|
+
@fuel_type = @all[:fuel_type]
|
18
|
+
@trim_level = @all[:trim_level]
|
19
|
+
@errors = errors.nil? ? {} : errors
|
20
|
+
@success = @errors.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
def valid?
|
24
|
+
@success
|
25
|
+
end
|
26
|
+
alias :success? :valid?
|
27
|
+
|
28
|
+
# def _dump(level=-1)
|
29
|
+
# [@vin, @all.to_yaml].join('^')
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# def self._load(args)
|
33
|
+
# a = *args.split('^')
|
34
|
+
# hash = YAML.load(a[1])
|
35
|
+
# new(a[0], hash)
|
36
|
+
# end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/lib/vin_exploder.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
module VinExploder
|
3
|
+
|
4
|
+
def self.explode(vin)
|
5
|
+
cache = VinExploder.config.cache_store ? VinExploder.config.cache_store.new(VinExploder.config.cache_options) : nil
|
6
|
+
adapter = VinExploder.config.adapter.new(VinExploder.config.adapter_options)
|
7
|
+
exploder = Exploder.new(adapter, cache)
|
8
|
+
explosion = exploder.get(vin)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.config
|
12
|
+
@@config ||= VinExploder::Configuration.new()
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'vin_exploder/cache'
|
18
|
+
require 'vin_exploder/explosion'
|
19
|
+
require 'vin_exploder/exploder'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'vin_exploder/adapter'
|
3
|
+
|
4
|
+
module VinExploder
|
5
|
+
module Decode
|
6
|
+
|
7
|
+
describe AbstractAdapter do
|
8
|
+
|
9
|
+
it "should raise an NotImplementedError on explode" do
|
10
|
+
expect { AbstractAdapter.new.explode("VIN") }.to raise_error(NotImplementedError)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sqlite3'
|
3
|
+
require 'vin_exploder/cache'
|
4
|
+
require 'vin_exploder/cache/activerecord_cache_store'
|
5
|
+
|
6
|
+
module VinExploder
|
7
|
+
module Cache
|
8
|
+
|
9
|
+
class TestArCacheStore < ActiveRecord::Base
|
10
|
+
attr_accessor :vin, :make
|
11
|
+
serialize :data
|
12
|
+
def initialize(attributes={})
|
13
|
+
super attributes
|
14
|
+
key = attributes.delete(:key)
|
15
|
+
# @data = attributes[:data]
|
16
|
+
@attributes = {'key' => key, 'vin' => attributes[:vin], 'make' => attributes[:make], 'data' => attributes[:data]}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe ActiveRecordCacheStore do
|
21
|
+
|
22
|
+
before(:all) do
|
23
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ':memory:')
|
24
|
+
ActiveRecord::Schema.define do
|
25
|
+
create_table :test_ar_cache_stores, :force => true do |t|
|
26
|
+
t.column :key, :string
|
27
|
+
t.column :vin, :string
|
28
|
+
t.column :make, :string
|
29
|
+
t.column :data, :text
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
@store = ActiveRecordCacheStore.new :model_class => TestArCacheStore
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
after(:each) do
|
38
|
+
TestArCacheStore.delete_all
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should read and write a hash" do
|
42
|
+
hash = @store.read("fake_vin_number")
|
43
|
+
hash.should be_nil
|
44
|
+
@store.write("fake_vin_number", {:vin => 'fake_vin_number', :make => 'Ford'})
|
45
|
+
hash = @store.read("fake_vin_number")
|
46
|
+
hash.should_not be_nil
|
47
|
+
hash[:make].should == 'Ford'
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should fetch and cache new vins" do
|
51
|
+
hash = @store.fetch("fake_vin_number") do
|
52
|
+
{:vin => 'fake_vin_number', :make => 'Ford'}
|
53
|
+
end
|
54
|
+
hash2 = @store.read("fake_vin_number")
|
55
|
+
hash[:vin].should == hash2[:vin]
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should delete a vin from the cache" do
|
59
|
+
hash = @store.read("fake_vin_number")
|
60
|
+
hash.should be_nil
|
61
|
+
@store.write(:fake_vin_number, {:vin => 'fake_vin_number', :make => 'Ford'})
|
62
|
+
hash = @store.read("fake_vin_number")
|
63
|
+
hash.should_not be_nil
|
64
|
+
hash[:make].should == 'Ford'
|
65
|
+
deleted = @store.delete("fake_vin_number")
|
66
|
+
deleted.should == true
|
67
|
+
hash = @store.read("fake_vin_number")
|
68
|
+
hash.should be_nil
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module VinExploder
|
4
|
+
module Cache
|
5
|
+
|
6
|
+
describe Store do
|
7
|
+
|
8
|
+
describe '#read' do
|
9
|
+
it 'should return the cached vin explosion' do
|
10
|
+
Store.new.read('DOESNTMATTER').should == nil
|
11
|
+
end
|
12
|
+
it 'should return nil if no explosion found' do
|
13
|
+
Store.new.read('DOESNTMATTER').should == nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#write' do
|
18
|
+
it 'should return the value that was written' do
|
19
|
+
Store.new.write('VIN', 'EXPLODED_VIN').should == 'EXPLODED_VIN'
|
20
|
+
Store.new.read('VIN').should == nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#fetch' do
|
25
|
+
it 'should execute the block and pass through #write' do
|
26
|
+
store = Store.new
|
27
|
+
store.should_receive(:write).with('VIN', 'EXPLODED_VIN').exactly(1).times { 'EXPLODED_VIN' }
|
28
|
+
store.fetch('VIN') do
|
29
|
+
'EXPLODED_VIN'
|
30
|
+
end.should == 'EXPLODED_VIN'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should return nil with no block' do
|
34
|
+
store = Store.new
|
35
|
+
store.should_receive(:read).with('VIN').exactly(1).times { 'EXPLODED_VIN' }
|
36
|
+
store.fetch('VIN').should == 'EXPLODED_VIN'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#delete' do
|
41
|
+
it 'should return true if cache item was deleted' do
|
42
|
+
Store.new.delete('VIN').should == true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sqlite3'
|
3
|
+
require 'sequel'
|
4
|
+
require 'vin_exploder/cache'
|
5
|
+
require 'vin_exploder/cache/sequel_cache_store'
|
6
|
+
|
7
|
+
module VinExploder
|
8
|
+
module Cache
|
9
|
+
|
10
|
+
describe SequelCacheStore do
|
11
|
+
|
12
|
+
before(:all) do
|
13
|
+
@db_config = {:adapter => "sqlite"}
|
14
|
+
@store = SequelCacheStore.new @db_config
|
15
|
+
@store.connection.create_table(:vins) do
|
16
|
+
String :key, :unique=>true, :size=>10
|
17
|
+
String :data, :null=>false, :text=>true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
after(:each) do
|
22
|
+
@store.connection[:vins].truncate
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should initialize" do
|
26
|
+
@store.class.should == SequelCacheStore
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should read and write a hash" do
|
30
|
+
hash = @store.read(:fake_vin_number)
|
31
|
+
hash.should be_nil
|
32
|
+
@store.write(:fake_vin_number, {:vin => 'fake_vin_number', :make => 'Ford'})
|
33
|
+
hash = @store.read(:fake_vin_number)
|
34
|
+
hash.should_not be_nil
|
35
|
+
hash[:make].should == 'Ford'
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should fetch and cache new vins" do
|
39
|
+
hash = @store.fetch(:fake_vin_number) do
|
40
|
+
{:vin => 'fake_vin_number', :make => 'Ford'}
|
41
|
+
end
|
42
|
+
hash2 = @store.read(:fake_vin_number)
|
43
|
+
hash[:vin].should == hash2[:vin]
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should delete a vin from the cache" do
|
47
|
+
hash = @store.read(:fake_vin_number)
|
48
|
+
hash.should be_nil
|
49
|
+
@store.write(:fake_vin_number, {:vin => 'fake_vin_number', :make => 'Ford'})
|
50
|
+
hash = @store.read(:fake_vin_number)
|
51
|
+
hash.should_not be_nil
|
52
|
+
hash[:make].should == 'Ford'
|
53
|
+
deleted = @store.delete(:fake_vin_number)
|
54
|
+
deleted.should == true
|
55
|
+
hash = @store.read(:fake_vin_number)
|
56
|
+
hash.should be_nil
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'vin_exploder/configuration'
|
3
|
+
|
4
|
+
module VinExploder
|
5
|
+
|
6
|
+
describe Configuration do
|
7
|
+
|
8
|
+
describe '#cache_store' do
|
9
|
+
it 'should return nil if no cache is set' do
|
10
|
+
c = Configuration.new
|
11
|
+
c.cache_store.should == nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should convert a symbol to a camel case constant within the VinExploder::Cache scope' do
|
15
|
+
class VinExploder::Cache::TestSymbolConst; end
|
16
|
+
c = Configuration.new
|
17
|
+
c.cache_store :test_symbol_const
|
18
|
+
c.cache_store.should == VinExploder::Cache::TestSymbolConst
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should return the constant previously set' do
|
22
|
+
c = Configuration.new
|
23
|
+
c.cache_store String
|
24
|
+
c.cache_store.should == String
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#adapter' do
|
29
|
+
it 'should return nil where no adapter defined' do
|
30
|
+
c = Configuration.new
|
31
|
+
c.adapter.should == nil
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should convert a symbol to a camel case constant within the VinExploder::Decode scope' do
|
35
|
+
class VinExploder::Decode::TestSymbolConst; end
|
36
|
+
c = Configuration.new
|
37
|
+
c.adapter :test_symbol_const
|
38
|
+
c.adapter.should == VinExploder::Decode::TestSymbolConst
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should return the constant previously set' do
|
42
|
+
c = Configuration.new
|
43
|
+
c.adapter String
|
44
|
+
c.adapter.should == String
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
2
|
+
if ENV["COVERAGE"]
|
3
|
+
require 'simplecov'
|
4
|
+
SimpleCov.start 'rails'
|
5
|
+
end
|
6
|
+
|
7
|
+
require "bundler"
|
8
|
+
Bundler.setup
|
9
|
+
|
10
|
+
require 'rspec'
|
11
|
+
|
12
|
+
require 'vin_exploder'
|
13
|
+
|
14
|
+
RSpec.configure do |config|
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'vin_exploder'
|
3
|
+
|
4
|
+
class VinExploder::Decode::TestAdapter; end
|
5
|
+
|
6
|
+
module VinExploder
|
7
|
+
|
8
|
+
describe Exploder do
|
9
|
+
|
10
|
+
describe '#new' do
|
11
|
+
it "should raise a MissingAdapter error if nil is provided to initialize" do
|
12
|
+
expect { Exploder.new(nil) }.to raise_error(MissingAdapter)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should raise a ArgumentError error if no arguments are provided to initialize" do
|
16
|
+
expect { Exploder.new }.to raise_error(ArgumentError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should initialize with a default cache store when no cache argument is passed" do
|
20
|
+
s = VinExploder::Cache::Store.new
|
21
|
+
VinExploder::Cache::Store.should_receive(:new).exactly(1).times { s }
|
22
|
+
e = Exploder.new(double("Adapter"))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#get' do
|
27
|
+
it "should get a vin Explosion" do
|
28
|
+
a = double("Adapter")
|
29
|
+
a.should_receive(:explode) { {} }
|
30
|
+
e = Exploder.new(a)
|
31
|
+
ex = e.get('VIN')
|
32
|
+
ex.class.should == VinExploder::Explosion
|
33
|
+
ex.valid?.should == true
|
34
|
+
ex.success?.should == true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "VinExploder#explode" do
|
39
|
+
it "should return an Explosion object" do
|
40
|
+
ta = VinExploder::Decode::TestAdapter.new
|
41
|
+
ta.should_receive(:explode) { {} }
|
42
|
+
VinExploder::Decode::TestAdapter.should_receive(:new){ ta }
|
43
|
+
|
44
|
+
VinExploder.config.adapter :test_adapter
|
45
|
+
VinExploder.explode("VIN").class.should == Explosion
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vin_exploder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jake Mallory
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-06-20 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.6.0
|
24
|
+
type: :development
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: simplecov
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: sequel
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: sqlite3
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
type: :development
|
58
|
+
version_requirements: *id004
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: activerecord
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 3.0.0
|
68
|
+
type: :development
|
69
|
+
version_requirements: *id005
|
70
|
+
description: A caching client for vin decoding web services. The caching allows the consumer to avoid the cost of secondary lookups.
|
71
|
+
email: tinomen@gmail.com
|
72
|
+
executables: []
|
73
|
+
|
74
|
+
extensions: []
|
75
|
+
|
76
|
+
extra_rdoc_files:
|
77
|
+
- Gemfile
|
78
|
+
- Gemfile.lock
|
79
|
+
- README.textile
|
80
|
+
files:
|
81
|
+
- lib/vin_exploder/adapter.rb
|
82
|
+
- lib/vin_exploder/cache/activerecord_cache_store.rb
|
83
|
+
- lib/vin_exploder/cache/sequel_cache_store.rb
|
84
|
+
- lib/vin_exploder/cache.rb
|
85
|
+
- lib/vin_exploder/configuration.rb
|
86
|
+
- lib/vin_exploder/exploder.rb
|
87
|
+
- lib/vin_exploder/explosion.rb
|
88
|
+
- lib/vin_exploder/version.rb
|
89
|
+
- lib/vin_exploder.rb
|
90
|
+
- Gemfile
|
91
|
+
- Gemfile.lock
|
92
|
+
- README.textile
|
93
|
+
- spec/abstract_adapter_spec.rb
|
94
|
+
- spec/cache/activerecord_cache_store_spec.rb
|
95
|
+
- spec/cache/cache_store_spec.rb
|
96
|
+
- spec/cache/sequel_cache_store_spec.rb
|
97
|
+
- spec/configuration_spec.rb
|
98
|
+
- spec/spec_helper.rb
|
99
|
+
- spec/vin_exploder_spec.rb
|
100
|
+
homepage: http://github.com/tinomen/vin_exploder
|
101
|
+
licenses: []
|
102
|
+
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options:
|
105
|
+
- --title
|
106
|
+
- A caching client for vin decoding services
|
107
|
+
- --main
|
108
|
+
- README.textile
|
109
|
+
- --line-numbers
|
110
|
+
- --charset=UTF-8
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: "0"
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.3.6
|
125
|
+
requirements: []
|
126
|
+
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 1.7.2
|
129
|
+
signing_key:
|
130
|
+
specification_version: 3
|
131
|
+
summary: A caching client for vin decoding services
|
132
|
+
test_files:
|
133
|
+
- spec/abstract_adapter_spec.rb
|
134
|
+
- spec/cache/activerecord_cache_store_spec.rb
|
135
|
+
- spec/cache/cache_store_spec.rb
|
136
|
+
- spec/cache/sequel_cache_store_spec.rb
|
137
|
+
- spec/configuration_spec.rb
|
138
|
+
- spec/spec_helper.rb
|
139
|
+
- spec/vin_exploder_spec.rb
|