openfoodfacts 0.1.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 273c69bba5eca84c0012464a5442bde9793f36ab
4
- data.tar.gz: e70b9511d2bf3ade15e7edf8dcd534d7d68bdee7
3
+ metadata.gz: 09d860eb4659431d4bf589f95572ab8dd0f8fc8d
4
+ data.tar.gz: a23608b0c33c5450db5cacb99b55e9234f1cf96c
5
5
  SHA512:
6
- metadata.gz: 9db37b080d40213333cff30b49a981afa3b40a726f84c68fa45beda509e8a918909344a57538296fe9c879a724b3686047883458fd1e928128020204d2e633ce
7
- data.tar.gz: b4a28ae44b9072407daf43127feb4fc9096fb5e1e7d2e8b763039e54f171c159d05f00226f29593dbc95695ae6e8eab0a363bfafa7703d639b145363f678e430
6
+ metadata.gz: 33fb64a4602691cd37e59d17ad1d659b54888bac32a8ec35307bb324fd61d53964d56c038ff1b748b44e7c0fc3749e8f2d265cef83cf1a74260c06eb036db5a3
7
+ data.tar.gz: 0c3efdae29963a8d5cb8973184bd7ad1c0a9ebdc45078f48b7a0fd0bbb43769be901dd8ee07cb635c84110f264f8077570786664fdad51d78c0b35b96aae3f6b
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # OpenFoodFacts
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/openfoodfacts.svg)](http://badge.fury.io/rb/openfoodfacts)
4
- [![Build Status](https://travis-ci.org/nicolasleger/openfoodfacts-ruby.svg)](https://travis-ci.org/nicolasleger/openfoodfacts-ruby)
4
+ [![Build Status](https://travis-ci.org/openfoodfacts/openfoodfacts-ruby.svg?branch=master)](https://travis-ci.org/openfoodfacts/openfoodfacts-ruby.svg?branch=master)
5
5
  [![Dependency Status](https://gemnasium.com/nicolasleger/openfoodfacts-ruby.svg)](https://gemnasium.com/nicolasleger/openfoodfacts-ruby)
6
6
  [![Code Climate](https://codeclimate.com/github/nicolasleger/openfoodfacts-ruby/badges/gpa.svg)](https://codeclimate.com/github/nicolasleger/openfoodfacts-ruby)
7
7
  [![Coverage Status](https://coveralls.io/repos/nicolasleger/openfoodfacts-ruby/badge.svg)](https://coveralls.io/r/nicolasleger/openfoodfacts-ruby)
@@ -31,8 +31,8 @@ require 'openfoodfacts'
31
31
 
32
32
  # Browse a product
33
33
 
34
- barcode = "3029330003533"
35
- product = Openfoodfacts.product(barcode, locale: 'fr') # or Openfoodfacts::Product.get
34
+ code = "3029330003533"
35
+ product = Openfoodfacts::Product.get(code, locale: 'fr')
36
36
 
37
37
  product.product_name
38
38
  # => "Crousti Moelleux Complet"
@@ -40,6 +40,15 @@ product.product_name
40
40
  product.nutriments.to_hash
41
41
  # => {"sodium"=>"0.44", "sugars"=>6.5, "fat_unit"=>"g", "carbohydrates_unit"=>"g", "proteins_unit"=>"g", "nutrition-score-fr_100g"=>-2, "fat"=>2.5, "proteins_serving"=>12.8, "sodium_serving"=>0.535, "salt"=>1.1176, "proteins"=>10.5, "nutrition-score-uk_serving"=>-2, "nutrition-score-fr"=>-2, "fat_serving"=>3.04, "sugars_unit"=>"g", "sugars_100g"=>"6.5", "sodium_unit"=>"g", "saturated-fat_unit"=>"g", "saturated-fat_serving"=>0.608, "sodium_100g"=>0.44, "fiber_unit"=>"g", "energy"=>1067, "energy_unit"=>"kJ", "sugars_serving"=>7.9, "carbohydrates_100g"=>44, "nutrition-score-uk"=>-2, "proteins_100g"=>10.5, "fiber_serving"=>7.29, "carbohydrates_serving"=>53.5, "nutrition-score-fr_serving"=>-2, "energy_serving"=>1300, "fat_100g"=>"2.5", "saturated-fat_100g"=>"0.5", "nutrition-score-uk_100g"=>-2, "fiber"=>6, "salt_serving"=>1.36, "salt_100g"=>"1.1176", "carbohydrates"=>44, "fiber_100g"=>6, "energy_100g"=>1067, "saturated-fat"=>0.5}
42
42
 
43
+ # Update product
44
+
45
+ user = Openfoodfacts::User.login("USERNAME", "PASSWORD")
46
+ if user
47
+ product.brands = "Jacquet"
48
+ product.update
49
+ # true
50
+ end
51
+
43
52
  # Search products
44
53
 
45
54
  Openfoodfacts::Product.search("Chocolate", locale: 'world', page_size: 3)
@@ -47,7 +56,7 @@ Openfoodfacts::Product.search("Chocolate", locale: 'world', page_size: 3)
47
56
 
48
57
  # Config
49
58
 
50
- Openfoodfacts.locales
59
+ Openfoodfacts:Locale.all
51
60
  # => ["ad", "ad-es", "ad-fr", "ad-pt", "ae", "al", "am", "ar", "at", "au", "ax", "az", "bd", "be", "be-de", "be-fr", "bf", "bg", "bi", "bi-rn", "bn", "br", "by", "by-be", "bz", "ca", "ca-fr", "cg", "ch", "ch-fr", "ch-it", "ci", "cl", "cn", "co", "cr", "cu", "cy", "cy-tr", "cz", "de", "dk", "do", "dz", "dz-fr", "ec", "eg", "es", "es-ca", "es-eu", "es-gl", "fi", "fi-sv", "fr", "ga", "gf", "gn", "gp", "gr", "hk", "hr", "hu", "id", "ie", "ie-ga", "il", "il-ar", "il-ru", "in", "iq", "iq-ku", "ir", "is", "it", "jp", "ke", "ke-sw", "kh", "kr", "kw", "kz", "kz-kk", "lb", "lk", "lk-ta", "lu", "lu-de", "lu-lb", "lv", "ma", "ma-es", "ma-fr", "mc", "md", "mf", "ml", "mn", "mo", "mo-zh", "mq", "mr", "mt", "mt-mt", "mu", "mx", "my", "nc", "nl", "no", "nz", "nz-mi", "pa", "pe", "pf", "ph", "ph-tl", "pl", "pm", "pt", "qa", "re", "ro", "rs", "ru", "sa", "se", "sg", "sg-ms", "sg-ta", "sg-zh", "si", "sk", "sn", "sy", "th", "tn", "tr", "tw", "tz", "tz-sw", "ua", "uk", "us", "ve", "vn", "vu", "vu-bi", "world", "ye", "yt", "za", "za-af", "za-nr", "za-ss", "za-st", "za-tn", "za-ts", "za-ve", "za-xh", "za-zu"]
52
61
  ```
53
62
 
@@ -1,4 +1,6 @@
1
+ require_relative 'openfoodfacts/locale'
1
2
  require_relative 'openfoodfacts/product'
3
+ require_relative 'openfoodfacts/user'
2
4
  require_relative 'openfoodfacts/version'
3
5
 
4
6
  require 'json'
@@ -6,26 +8,21 @@ require 'nokogiri'
6
8
  require 'open-uri'
7
9
 
8
10
  module Openfoodfacts
9
- DEFAULT_LOCALE = 'world'
11
+
12
+ DEFAULT_LOCALE = Locale::GLOBAL
10
13
 
11
14
  class << self
12
15
 
13
16
  # Return locale from link
14
17
  #
15
18
  def locale_from_link(link)
16
- link[/^https?:\/\/([^.]+)\./i, 1]
19
+ Locale.locale_from_link(link)
17
20
  end
18
21
 
19
22
  # Get locales
20
23
  #
21
24
  def locales
22
- url = "http://openfoodfacts.org/"
23
- body = open(url).read
24
- dom = Nokogiri.parse(body)
25
-
26
- dom.css('ul li a').map { |locale_link|
27
- locale_from_link(locale_link.attr('href'))
28
- }.uniq.sort
25
+ Locale.all
29
26
  end
30
27
 
31
28
  # Get product
@@ -0,0 +1,29 @@
1
+ module Openfoodfacts
2
+ class Locale < String
3
+
4
+ GLOBAL = 'world'
5
+
6
+ class << self
7
+
8
+ # Get locales
9
+ #
10
+ def all
11
+ url = "http://openfoodfacts.org/"
12
+ body = open(url).read
13
+ dom = Nokogiri.parse(body)
14
+
15
+ dom.css('ul li a').map { |locale_link|
16
+ locale_from_link(locale_link.attr('href'))
17
+ }.uniq.sort
18
+ end
19
+
20
+ # Return locale from link
21
+ #
22
+ def locale_from_link(link)
23
+ link[/^https?:\/\/([^.]+)\./i, 1]
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
@@ -1,25 +1,29 @@
1
1
  require 'hashie'
2
- require 'json'
3
2
  require 'nokogiri'
4
3
  require 'open-uri'
5
4
 
6
5
  module Openfoodfacts
7
6
  class Product < Hashie::Mash
7
+
8
8
  class << self
9
+
9
10
  # Get product
10
11
  #
11
- def get(barcode, locale: Openfoodfacts::DEFAULT_LOCALE)
12
- product_url = url(barcode, locale: locale)
13
- json = open(product_url).read
14
- hash = JSON.parse(json)
12
+ def get(code, locale: Openfoodfacts::DEFAULT_LOCALE)
13
+ if code
14
+ product_url = url(code, locale: locale)
15
+ json = open(product_url).read
16
+ hash = JSON.parse(json)
15
17
 
16
- new(hash["product"]) if !hash["status"].nil? && hash["status"] == 1
18
+ new(hash["product"]) if !hash["status"].nil? && hash["status"] == 1
19
+ end
17
20
  end
21
+ alias_method :find, :get
18
22
 
19
23
  # Return product API URL
20
24
  #
21
- def url(barcode, locale: Openfoodfacts::DEFAULT_LOCALE)
22
- "http://#{locale}.openfoodfacts.org/api/v0/produit/#{barcode}.json"
25
+ def url(code, locale: Openfoodfacts::DEFAULT_LOCALE)
26
+ "http://#{locale}.openfoodfacts.org/api/v0/produit/#{code}.json"
23
27
  end
24
28
 
25
29
  # Search products
@@ -52,6 +56,47 @@ module Openfoodfacts
52
56
  new(attributes)
53
57
  end
54
58
  end
59
+ alias_method :where, :search
60
+
61
+ end
62
+
63
+ # Fetch product
64
+ #
65
+ def fetch
66
+ if (self.code)
67
+ product = self.class.get(self.code)
68
+ self.merge!(product)
69
+ end
70
+
71
+ self
72
+ end
73
+ alias_method :reload, :fetch
74
+
75
+ # Update product
76
+ # Only product_name, brands and quantity fields seems to be updatable throught app / API.
77
+ # User can be nil
78
+ # Tested not updatable fields: countries, ingredients_text, purchase_places, purchase_places_tag, purchase_places_tags
79
+ #
80
+ def update(user: nil)
81
+ if self.code && self.lc
82
+ uri = URI("http://#{self.lc}.openfoodfacts.org/cgi/product_jqm.pl")
83
+ params = self.to_hash
84
+ params.merge!("user_id" => user.user_id, "password" => user.password) if user
85
+ response = Net::HTTP.post_form(uri, params)
86
+
87
+ data = JSON.parse(response.body)
88
+ data["status"] == 1
89
+ else
90
+ false
91
+ end
92
+ end
93
+ alias_method :save, :update
94
+
95
+ # Return Product API URL
96
+ #
97
+ def url(locale: Openfoodfacts::DEFAULT_LOCALE)
98
+ self.class.url(self.code, locale: locale)
55
99
  end
100
+
56
101
  end
57
102
  end
@@ -0,0 +1,38 @@
1
+ module Openfoodfacts
2
+ class User < Hashie::Mash
3
+
4
+ class << self
5
+
6
+ # Login
7
+ # WARNING SECURITY: It is not made throught HTTPS Secure so password can be read on the way.
8
+ #
9
+ def login(user_id, password, locale: Openfoodfacts::DEFAULT_LOCALE)
10
+ uri = URI("http://#{locale}.openfoodfacts.org/cgi/session.pl")
11
+ params = {
12
+ "jqm" => "1",
13
+ "user_id" => user_id,
14
+ "password" => password
15
+ }
16
+
17
+ response = Net::HTTP.post_form(uri, params)
18
+ data = JSON.parse(response.body)
19
+
20
+ if data['user_id']
21
+ data.merge!(password: password)
22
+ new(data)
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ # Login
29
+ #
30
+ def login(locale: Openfoodfacts::DEFAULT_LOCALE)
31
+ if user = self.class.login(self.user_id, self.password, locale: locale)
32
+ self.name = user.name
33
+ self
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -1,3 +1,3 @@
1
1
  module Openfoodfacts
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,28 +1,56 @@
1
1
  require_relative 'minitest_helper'
2
2
 
3
3
  class TestOpenfoodfacts < Minitest::Test
4
+
5
+ # Gem
6
+
4
7
  def test_that_it_has_a_version_number
5
8
  refute_nil ::Openfoodfacts::VERSION
6
9
  end
7
10
 
11
+ # Locale
12
+
8
13
  def test_it_fetches_locales
9
14
  VCR.use_cassette("index") do
10
- locales = ::Openfoodfacts::locales
15
+ locales = ::Openfoodfacts::Locale.all
11
16
  assert_includes locales, "world"
12
17
  assert_includes locales, "fr"
13
18
  assert_includes locales, "be-fr"
14
19
  end
15
20
  end
16
21
 
22
+ # User
23
+
24
+ def test_it_login_user
25
+ VCR.use_cassette("login_user", record: :once, match_requests_on: [:host, :path]) do
26
+ user = ::Openfoodfacts::User.login("wrong", "absolutely")
27
+ assert_nil user
28
+ end
29
+ end
30
+
31
+ # Product
32
+
33
+ def test_it_returns_product_url
34
+ product = ::Openfoodfacts::Product.new(code: "3029330003533")
35
+ assert_equal ::Openfoodfacts::Product.url(product.code, locale: 'ca'), product.url(locale: 'ca')
36
+ end
37
+
17
38
  def test_it_fetches_product
18
39
  product_code = "3029330003533"
40
+
41
+ VCR.use_cassette("fetch_product_#{product_code}", record: :once, match_requests_on: [:host, :path]) do
42
+ product = ::Openfoodfacts::Product.new(code: product_code)
43
+ product.fetch
44
+ refute_empty product.brands_tags
45
+ end
46
+ end
47
+
48
+ def test_it_get_product
49
+ product_code = "3029330003533"
19
50
 
20
51
  VCR.use_cassette("product_#{product_code}", record: :once, match_requests_on: [:host, :path]) do
21
52
  assert_equal ::Openfoodfacts::Product.get(product_code).code, product_code
22
53
  end
23
- VCR.use_cassette("product_#{product_code}", record: :once, match_requests_on: [:host, :path]) do
24
- #assert_equal ::Openfoodfacts.product(product_code).code, product_code # Backward compatibility
25
- end
26
54
  end
27
55
 
28
56
  def test_that_it_search
@@ -42,4 +70,26 @@ class TestOpenfoodfacts < Minitest::Test
42
70
  refute_equal ::Openfoodfacts::Product.search(term, page: 2).first.code, first_product.code
43
71
  end
44
72
  end
73
+
74
+ def test_it_updates_product
75
+ product_code = "3029330003533"
76
+ product = ::Openfoodfacts::Product.new(code: product_code)
77
+ product_last_modified_t = nil
78
+
79
+ VCR.use_cassette("fetch_product_#{product_code}", record: :all, match_requests_on: [:host, :path]) do
80
+ product.fetch
81
+ product_last_modified_t = product.last_modified_t
82
+ end
83
+
84
+ VCR.use_cassette("update_product_#{product_code}", record: :all, match_requests_on: [:host, :path]) do
85
+ product.update # Empty update are accepted, allow testing without altering data.
86
+ end
87
+
88
+ VCR.use_cassette("refetch_product_#{product_code}", record: :all, match_requests_on: [:host, :path]) do
89
+ product.fetch
90
+ end
91
+
92
+ refute_equal product_last_modified_t, product.last_modified_t
93
+ end
94
+
45
95
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openfoodfacts
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicolas Leger
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-05-21 00:00:00.000000000 Z
11
+ date: 2015-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hashie
@@ -119,7 +119,9 @@ files:
119
119
  - README.md
120
120
  - Rakefile
121
121
  - lib/openfoodfacts.rb
122
+ - lib/openfoodfacts/locale.rb
122
123
  - lib/openfoodfacts/product.rb
124
+ - lib/openfoodfacts/user.rb
123
125
  - lib/openfoodfacts/version.rb
124
126
  - test/fixtures/index.yml
125
127
  - test/fixtures/product_3029330003533.yml