kosher 0.1.12 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,33 @@
1
1
  Kosher
2
2
  ======
3
3
 
4
- Kosher wraps Amazon in a loving embrace.
4
+ Kosher models an offer by a bookseller.
5
5
 
6
- ![Walter Benjamin](https://github.com/papercavalier/kosher/raw/master/walter_benjamin.jpg)
6
+ It knows if the offer is good or bad. It also knows if the offer is
7
+ better than another offer. It is somewhat overengineered.
8
+
9
+ ![Booksellers](http://upload.wikimedia.org/wikipedia/commons/thumb/b/b9/Bucharest_booksellers_2.jpg/600px-Bucharest_booksellers_2.jpg)
10
+
11
+ Usage
12
+ -----
13
+
14
+ include Kosher
15
+
16
+ offer = Offer.new(1234,
17
+ Seller.new('ABCDEF',
18
+ 'John Doe Books',
19
+ 4.8),
20
+ Condition.new(1),
21
+ Description.new('A fine copy'),
22
+ 48,
23
+ 1000,
24
+ 399,
25
+ 'USD',
26
+ 'http://bookseller.com/listings/1234'
27
+
28
+ offer.kosher?
29
+ => true
30
+
31
+ offer.description = "Withdrawn library book"
32
+ offer.kosher?
33
+ => false
data/Rakefile CHANGED
@@ -9,4 +9,3 @@ RSpec::Core::RakeTask.new(:spec) do |spec|
9
9
  end
10
10
 
11
11
  task :default => :spec
12
-
data/kosher.gemspec CHANGED
@@ -14,16 +14,11 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.rubyforge_project = 'kosher'
16
16
 
17
+ s.add_dependency('money', '~> 3.6.1')
18
+ s.add_development_dependency('rspec', '~> 2.5.0')
19
+
17
20
  s.files = `git ls-files`.split("\n")
18
21
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
22
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
23
  s.require_paths = ['lib']
21
-
22
- s.add_dependency('json', '~> 1.5.1')
23
- s.add_dependency('sucker', '~> 1.3.1')
24
- s.add_development_dependency('fabrication', '~> 0.9.5')
25
- s.add_development_dependency('rspec', '~> 2.5.0')
26
- s.add_development_dependency('ruby-debug19', '~> 0.11.6')
27
- s.add_development_dependency('vcr', '~> 1.7.0')
28
- s.add_development_dependency('webmock', '~> 1.6.2')
29
24
  end
@@ -1,25 +1,15 @@
1
1
  module Kosher
2
- class Condition < Struct.new(:grade)
3
-
4
- alias_method :to_i, :grade
5
-
6
- CONDITIONS = {
7
- 'new' => 1,
8
- 'mint' => 2,
9
- 'verygood' => 3,
10
- 'good' => 4,
11
- 'acceptable' => 5 }
12
-
13
- def initialize(string = '')
14
- self.grade = CONDITIONS[string] || 6
2
+ class Condition
3
+ def initialize(grade)
4
+ @grade = grade
15
5
  end
16
6
 
17
7
  def kosher?
18
- grade <= 4
8
+ @grade <= Config.min_condition
19
9
  end
20
10
 
21
11
  def new?
22
- grade == 1
12
+ @grade == 1
23
13
  end
24
14
 
25
15
  def used?
@@ -0,0 +1,45 @@
1
+ module Kosher
2
+ module Config
3
+ extend self
4
+
5
+ def base_currency
6
+ @base_currency ||= 'EUR'
7
+ end
8
+
9
+ def base_currency=(code)
10
+ @base_currency = code
11
+ end
12
+
13
+ def blacklist
14
+ @blacklist ||= []
15
+ end
16
+
17
+ def blacklist=(seller_ids)
18
+ @blacklist = seller_ids
19
+ end
20
+
21
+ def max_hours_shipped
22
+ @max_hours_shipped ||= 48
23
+ end
24
+
25
+ def max_hours_shipped=(hours)
26
+ @max_hours_shipped = hours
27
+ end
28
+
29
+ def min_rating
30
+ @min_rating ||= 4.8
31
+ end
32
+
33
+ def min_rating=(rating)
34
+ @min_rating = rating
35
+ end
36
+
37
+ def min_condition
38
+ @min_condition ||= 4
39
+ end
40
+
41
+ def min_condition=(grade)
42
+ @min_condition = grade
43
+ end
44
+ end
45
+ end
@@ -1,31 +1,27 @@
1
1
  module Kosher
2
- class Description < String
2
+ class Description
3
3
  DAMAGED = "\\b(?:missing|torn|broken|split|discard|withdrawn|rent|stain|school|damaged|water)"
4
4
  EXLIB = "(?:e?x|discarded|retired|former|has|have)[\\s._-]*lib"
5
5
  MARKED = "(highlight|hilit|underlin)"
6
6
  MISSING_VOL = "(vols?|volume) only"
7
7
  REVIEW_COPY = "\\b(?:uncorrected|advanced?\\sreview|arc)\\b"
8
8
 
9
+ def initialize(string = '')
10
+ @description = string
11
+ end
12
+
9
13
  def kosher?
10
- !(present? && bad?)
11
- end
14
+ !(damaged? || ex_library? || marked? || missing_volume? || review_copy?)
15
+ end
12
16
 
13
17
  private
14
18
 
15
- def bad?
16
- damaged? ||
17
- ex_library? ||
18
- marked? ||
19
- missing_volume? ||
20
- review_copy?
21
- end
22
-
23
19
  def damaged?
24
20
  matches(DAMAGED)
25
21
  end
26
22
 
27
23
  def does_not_match(value)
28
- !match(Regexp.new(value, true))
24
+ !matches(value)
29
25
  end
30
26
 
31
27
  def ex_library?
@@ -37,7 +33,7 @@ module Kosher
37
33
  end
38
34
 
39
35
  def matches(value)
40
- !does_not_match(value)
36
+ @description.match(Regexp.new(value, true))
41
37
  end
42
38
 
43
39
  def missing_volume?
@@ -48,10 +44,6 @@ module Kosher
48
44
  "(?:no|not an?)\\s+#{value}"
49
45
  end
50
46
 
51
- def present?
52
- self != ''
53
- end
54
-
55
47
  def review_copy?
56
48
  matches(REVIEW_COPY)
57
49
  end
data/lib/kosher/offer.rb CHANGED
@@ -1,38 +1,45 @@
1
+ require 'money'
2
+
1
3
  module Kosher
2
4
  class Offer < Struct.new(
5
+ :id,
3
6
  :seller,
4
7
  :condition,
5
8
  :description,
6
- :ships_in,
7
- :ships_free,
8
- :cents,
9
- :exchange_id,
10
- :listing_id)
11
-
12
- def self.build(doc)
13
- offer = new
14
- offer.seller = Seller.build(doc['Merchant'])
15
-
16
- attributes = doc['OfferAttributes']
17
- offer.condition = Condition.new(attributes['SubCondition'])
18
- offer.description = Description.new(attributes['ConditionNote'].to_s)
19
-
20
- listing = doc['OfferListing']
21
- offer.ships_in = listing['AvailabilityAttributes']['MaximumHours'].to_i
22
- offer.ships_free = listing['IsEligibleForSuperSaverShipping'] == '1'
23
- offer.cents = listing['Price']['Amount'].to_i
24
- offer.exchange_id = listing['ExchangeId']
25
- offer.listing_id = listing['OfferListingId']
26
-
27
- offer
9
+ :hours_shipped,
10
+ :price_in_cents,
11
+ :shipping_in_cents,
12
+ :currency,
13
+ :url)
14
+
15
+ include Comparable
16
+
17
+ def <=>(another)
18
+ if self.kosher? != another.kosher?
19
+ self.kosher? ? 1 : -1
20
+ else
21
+ -(self.final_price.exchange_to(Config.base_currency) <=> another.final_price.exchange_to(Config.base_currency))
22
+ end
23
+ end
24
+
25
+ def available?
26
+ hours_shipped.to_i <= Config.max_hours_shipped
27
+ end
28
+
29
+ def final_price
30
+ price + shipping
28
31
  end
29
32
 
30
33
  def kosher?
31
- condition.kosher? && seller.kosher? && description.kosher? && ships_now?
34
+ condition.kosher? && seller.kosher? && description.kosher? && available?
35
+ end
36
+
37
+ def price
38
+ Money.new(price_in_cents, currency)
32
39
  end
33
40
 
34
- def ships_now?
35
- ships_in.to_i <= 48
41
+ def shipping
42
+ Money.new(shipping_in_cents.to_i, currency)
36
43
  end
37
44
  end
38
45
  end
data/lib/kosher/seller.rb CHANGED
@@ -1,29 +1,11 @@
1
1
  module Kosher
2
- class Seller < Struct.new(:merchant_id, :name, :average_rating)
3
- class << self
4
- attr_accessor :blacklist
5
-
6
- def build(doc)
7
- merchant_id = doc['MerchantId']
8
- name = doc['Name']
9
- average_rating = doc['AverageFeedbackRating'].to_f
10
-
11
- new(merchant_id, name, average_rating)
12
- end
13
- end
14
-
15
- def blacklist
16
- self.class.blacklist
17
- end
18
-
2
+ class Seller < Struct.new(:id, :name, :rating)
19
3
  def blacklisted?
20
- blacklist.include?(merchant_id) rescue false
4
+ Config.blacklist.include?(id)
21
5
  end
22
6
 
23
7
  def kosher?
24
- return false if blacklisted?
25
-
26
- average_rating == 0.0 || average_rating > 4.7
8
+ !blacklisted? && (rating.to_f == 0.0 || rating >= Config.min_rating)
27
9
  end
28
10
  end
29
11
  end
@@ -1,3 +1,3 @@
1
1
  module Kosher
2
- VERSION = '0.1.12'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/kosher.rb CHANGED
@@ -1,16 +1,5 @@
1
- require 'sucker'
2
- require 'kosher/struct'
3
- require 'kosher/algorithm'
4
1
  require 'kosher/condition'
2
+ require 'kosher/config'
5
3
  require 'kosher/description'
6
- require 'kosher/errors'
7
- require 'kosher/item'
8
4
  require 'kosher/offer'
9
- require 'kosher/request'
10
5
  require 'kosher/seller'
11
-
12
- module Kosher
13
- def self.new(args={})
14
- Request.new(args)
15
- end
16
- end
@@ -2,29 +2,17 @@ require 'spec_helper'
2
2
 
3
3
  module Kosher
4
4
  describe Condition do
5
- describe "#to_i" do
6
- def this(str)
7
- Condition.new(str).to_i
5
+ describe "#kosher?" do
6
+ before do
7
+ Config.min_condition = 1
8
8
  end
9
9
 
10
- it "casts as integer" do
11
- this('new').should eql 1
12
- this('mint').should eql 2
13
- this('verygood').should eql 3
14
- this('good').should eql 4
15
- this('acceptable').should eql 5
10
+ it "returns true if condition is equal to or better than minimum condition" do
11
+ Condition.new(1).should be_kosher
16
12
  end
17
13
 
18
- it "casts unrecognized conditions as 6" do
19
- this('refurbished').should eql 6
20
- end
21
- end
22
-
23
- describe "#kosher?" do
24
- it "returns true if condition is good or better" do
25
- Condition.new('verygood').should be_kosher
26
- Condition.new('good').should be_kosher
27
- Condition.new('acceptable').should_not be_kosher
14
+ it "returns false if condition is less than minimum condition" do
15
+ Condition.new(2).should_not be_kosher
28
16
  end
29
17
  end
30
18
  end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ module Kosher
4
+ describe Config do
5
+ describe ".base_currency" do
6
+ it "defaults to EUR" do
7
+ Config.base_currency = nil
8
+ Config.base_currency.should eql 'EUR'
9
+ end
10
+ end
11
+
12
+ describe ".blacklist" do
13
+ it "defaults to an empty array" do
14
+ Config.blacklist = nil
15
+ Config.blacklist.should eql []
16
+ end
17
+ end
18
+
19
+ describe ".max_hours_shipped" do
20
+ it "defaults to 48" do
21
+ Config.max_hours_shipped = nil
22
+ Config.max_hours_shipped.should eql 48
23
+ end
24
+ end
25
+
26
+ describe ".min_rating" do
27
+ it "defaults to 4.8" do
28
+ Config.min_rating = nil
29
+ Config.min_rating.should eql 4.8
30
+ end
31
+ end
32
+
33
+ describe ".min_condition" do
34
+ it "defaults to 4" do
35
+ Config.min_condition = nil
36
+ Config.min_condition.should eql 4
37
+ end
38
+ end
39
+ end
40
+ end
@@ -2,54 +2,46 @@ require 'spec_helper'
2
2
 
3
3
  module Kosher
4
4
  describe Description do
5
- def this(value)
6
- Description.new(value)
7
- end
8
-
9
- it "inherits from String" do
10
- Description.ancestors.should include String
11
- end
12
-
13
5
  it "validates a blank description" do
14
- this('').should be_kosher
6
+ Description.new('').should be_kosher
15
7
  end
16
8
 
17
9
  it "validates a non-blank description" do
18
- this('foo').should be_kosher
10
+ Description.new('foo').should be_kosher
19
11
  end
20
12
 
21
13
  it "does not validate advance review copies" do
22
- this('Uncorrected review copy').should_not be_kosher
23
- this('arc').should_not be_kosher
24
- this('arc.').should_not be_kosher
14
+ Description.new('Uncorrected review copy').should_not be_kosher
15
+ Description.new('arc').should_not be_kosher
16
+ Description.new('arc.').should_not be_kosher
25
17
 
26
- this('marc').should be_kosher
18
+ Description.new('marc').should be_kosher
27
19
  end
28
20
 
29
21
  it "does not validate marked books" do
30
- this('Some highlighting').should_not be_kosher
31
- this('Underlining.').should_not be_kosher
32
- this('Good. Hiliting.').should_not be_kosher
22
+ Description.new('Some highlighting').should_not be_kosher
23
+ Description.new('Underlining.').should_not be_kosher
24
+ Description.new('Good. Hiliting.').should_not be_kosher
33
25
 
34
- this('No highlighting.').should be_kosher
26
+ Description.new('No highlighting.').should be_kosher
35
27
  end
36
28
 
37
29
  it "does not validate books with missing volumes" do
38
- this('First vol only.').should_not be_kosher
30
+ Description.new('First vol only.').should_not be_kosher
39
31
  end
40
32
 
41
33
  it "does not validate damaged or worn books" do
42
- this('Different').should be_kosher
43
- this('Rental').should_not be_kosher
44
- this('Torn pages').should_not be_kosher
34
+ Description.new('Different').should be_kosher
35
+ Description.new('Rental').should_not be_kosher
36
+ Description.new('Torn pages').should_not be_kosher
45
37
  end
46
38
 
47
39
  it "does not validate withdrawn library copies" do
48
- this('xlib').should_not be_kosher
49
- this('ex-library').should_not be_kosher
50
- this('retired library copy').should_not be_kosher
40
+ Description.new('xlib').should_not be_kosher
41
+ Description.new('ex-library').should_not be_kosher
42
+ Description.new('retired library copy').should_not be_kosher
51
43
 
52
- this('Not an ex-library').should be_kosher
44
+ Description.new('Not an ex-library').should be_kosher
53
45
  end
54
46
  end
55
47
  end