kosher 0.1.12 → 0.2.0

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/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