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 +4 -4
- data/README.md +13 -4
- data/lib/openfoodfacts.rb +6 -9
- data/lib/openfoodfacts/locale.rb +29 -0
- data/lib/openfoodfacts/product.rb +53 -8
- data/lib/openfoodfacts/user.rb +38 -0
- data/lib/openfoodfacts/version.rb +1 -1
- data/test/test_openfoodfacts.rb +54 -4
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 09d860eb4659431d4bf589f95572ab8dd0f8fc8d
|
4
|
+
data.tar.gz: a23608b0c33c5450db5cacb99b55e9234f1cf96c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33fb64a4602691cd37e59d17ad1d659b54888bac32a8ec35307bb324fd61d53964d56c038ff1b748b44e7c0fc3749e8f2d265cef83cf1a74260c06eb036db5a3
|
7
|
+
data.tar.gz: 0c3efdae29963a8d5cb8973184bd7ad1c0a9ebdc45078f48b7a0fd0bbb43769be901dd8ee07cb635c84110f264f8077570786664fdad51d78c0b35b96aae3f6b
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# OpenFoodFacts
|
2
2
|
|
3
3
|
[](http://badge.fury.io/rb/openfoodfacts)
|
4
|
-
[](https://travis-ci.org/openfoodfacts/openfoodfacts-ruby.svg?branch=master)
|
5
5
|
[](https://gemnasium.com/nicolasleger/openfoodfacts-ruby)
|
6
6
|
[](https://codeclimate.com/github/nicolasleger/openfoodfacts-ruby)
|
7
7
|
[](https://coveralls.io/r/nicolasleger/openfoodfacts-ruby)
|
@@ -31,8 +31,8 @@ require 'openfoodfacts'
|
|
31
31
|
|
32
32
|
# Browse a product
|
33
33
|
|
34
|
-
|
35
|
-
product = Openfoodfacts.
|
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.
|
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
|
|
data/lib/openfoodfacts.rb
CHANGED
@@ -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
|
-
|
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
|
19
|
+
Locale.locale_from_link(link)
|
17
20
|
end
|
18
21
|
|
19
22
|
# Get locales
|
20
23
|
#
|
21
24
|
def locales
|
22
|
-
|
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(
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
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(
|
22
|
-
"http://#{locale}.openfoodfacts.org/api/v0/produit/#{
|
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
|
data/test/test_openfoodfacts.rb
CHANGED
@@ -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::
|
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.
|
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-
|
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
|