openfoodfacts 0.3.1 → 0.6.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.
Files changed (53) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +13 -6
  3. data/lib/openfoodfacts.rb +8 -1
  4. data/lib/openfoodfacts/additive.rb +6 -6
  5. data/lib/openfoodfacts/allergen.rb +3 -3
  6. data/lib/openfoodfacts/brand.rb +2 -2
  7. data/lib/openfoodfacts/category.rb +3 -3
  8. data/lib/openfoodfacts/city.rb +3 -3
  9. data/lib/openfoodfacts/contributor.rb +3 -3
  10. data/lib/openfoodfacts/country.rb +3 -3
  11. data/lib/openfoodfacts/entry_date.rb +33 -0
  12. data/lib/openfoodfacts/faq.rb +5 -5
  13. data/lib/openfoodfacts/ingredient_that_may_be_from_palm_oil.rb +3 -3
  14. data/lib/openfoodfacts/label.rb +5 -10
  15. data/lib/openfoodfacts/language.rb +33 -0
  16. data/lib/openfoodfacts/last_edit_date.rb +33 -0
  17. data/lib/openfoodfacts/locale.rb +22 -8
  18. data/lib/openfoodfacts/manufacturing_place.rb +3 -3
  19. data/lib/openfoodfacts/mission.rb +72 -0
  20. data/lib/openfoodfacts/number_of_ingredients.rb +33 -0
  21. data/lib/openfoodfacts/nutrition_grade.rb +33 -0
  22. data/lib/openfoodfacts/origin.rb +3 -3
  23. data/lib/openfoodfacts/packager_code.rb +3 -3
  24. data/lib/openfoodfacts/packaging.rb +3 -3
  25. data/lib/openfoodfacts/press.rb +16 -26
  26. data/lib/openfoodfacts/product.rb +26 -30
  27. data/lib/openfoodfacts/product_state.rb +3 -3
  28. data/lib/openfoodfacts/purchase_place.rb +3 -3
  29. data/lib/openfoodfacts/store.rb +3 -3
  30. data/lib/openfoodfacts/trace.rb +3 -3
  31. data/lib/openfoodfacts/user.rb +5 -5
  32. data/lib/openfoodfacts/version.rb +1 -1
  33. data/test/minitest_helper.rb +7 -1
  34. data/test/test_openfoodfacts.rb +151 -25
  35. metadata +36 -57
  36. data/test/fixtures/additives.yml +0 -1963
  37. data/test/fixtures/additives_locale.yml +0 -1963
  38. data/test/fixtures/brands.yml +0 -31542
  39. data/test/fixtures/brands_locale.yml +0 -17498
  40. data/test/fixtures/faq.yml +0 -1078
  41. data/test/fixtures/fetch_product_3029330003533.yml +0 -280
  42. data/test/fixtures/index.yml +0 -510
  43. data/test/fixtures/labels.yml +0 -9091
  44. data/test/fixtures/login_user.yml +0 -38
  45. data/test/fixtures/press.yml +0 -1284
  46. data/test/fixtures/product_3029330003533.yml +0 -280
  47. data/test/fixtures/product_states.yml +0 -910
  48. data/test/fixtures/product_states_locale.yml +0 -1002
  49. data/test/fixtures/products_for_brand.yml +0 -5481
  50. data/test/fixtures/products_for_state.yml +0 -2178
  51. data/test/fixtures/products_with_additive.yml +0 -3130
  52. data/test/fixtures/search_Chocolat.yml +0 -586
  53. data/test/fixtures/search_Chocolat_1_000_000.yml +0 -200
@@ -15,9 +15,9 @@ module Openfoodfacts
15
15
 
16
16
  # Get manufacturing places
17
17
  #
18
- def all(locale: Openfoodfacts::DEFAULT_LOCALE)
18
+ def all(locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN)
19
19
  if path = LOCALE_PATHS[locale]
20
- Product.tags_from_page(self, "http://#{locale}.openfoodfacts.org/#{path}")
20
+ Product.tags_from_page(self, "https://#{locale}.#{domain}/#{path}")
21
21
  end
22
22
  end
23
23
 
@@ -30,4 +30,4 @@ module Openfoodfacts
30
30
  end
31
31
 
32
32
  end
33
- end
33
+ end
@@ -0,0 +1,72 @@
1
+ require 'hashie'
2
+ require 'open-uri'
3
+
4
+ module Openfoodfacts
5
+ class Mission < Hashie::Mash
6
+
7
+ # TODO: Add more locales
8
+ LOCALE_PATHS = {
9
+ 'fr' => 'missions',
10
+ 'uk' => 'missions',
11
+ 'us' => 'missions',
12
+ 'world' => 'missions'
13
+ }
14
+
15
+ class << self
16
+ def all(locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN)
17
+ if path = LOCALE_PATHS[locale]
18
+ url = "https://#{locale}.#{domain}/#{path}"
19
+ html = URI.open(url).read
20
+ dom = Nokogiri::HTML.fragment(html)
21
+
22
+ dom.css('#missions li').map do |mission_dom|
23
+ links = mission_dom.css('a')
24
+
25
+ attributes = {
26
+ "title" => links.first.text.strip,
27
+ "url" => URI.join(url, links.first.attr('href')).to_s,
28
+ "description" => mission_dom.css('div').first.children[2].text.gsub('→', '').strip,
29
+ "users_count" => links.last.text[/(\d+)/, 1].to_i
30
+ }
31
+
32
+ new(attributes)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ # Fetch mission
39
+ #
40
+ def fetch
41
+ if (self.url)
42
+ html = URI.open(self.url).read
43
+ dom = Nokogiri::HTML.fragment(html)
44
+
45
+ description = dom.css('#description').first
46
+
47
+ # Remove "All missions" link
48
+ users = dom.css('#main_column a')[0..-2].map do |user_link|
49
+ User.new(
50
+ "user_id" => user_link.text.strip,
51
+ "url" => URI.join(self.url, user_link.attr('href')).to_s,
52
+ )
53
+ end
54
+
55
+ mission = {
56
+ "title" => dom.css('h1').first.text.strip,
57
+ "description" => description.text.strip,
58
+ "description_long" => description.next.text.strip,
59
+
60
+ "users" => users,
61
+ "users_count" => users.count
62
+ }
63
+
64
+ self.merge!(mission)
65
+ end
66
+
67
+ self
68
+ end
69
+ alias_method :reload, :fetch
70
+
71
+ end
72
+ end
@@ -0,0 +1,33 @@
1
+ require 'hashie'
2
+
3
+ module Openfoodfacts
4
+ class NumberOfIngredients < Hashie::Mash
5
+
6
+ # TODO: Add more locales
7
+ LOCALE_PATHS = {
8
+ 'fr' => 'nombres-d-ingredients',
9
+ 'uk' => 'numbers-of-ingredients',
10
+ 'us' => 'numbers-of-ingredients',
11
+ 'world' => 'numbers-of-ingredients'
12
+ }
13
+
14
+ class << self
15
+
16
+ # Get last edit dates
17
+ #
18
+ def all(locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN)
19
+ if path = LOCALE_PATHS[locale]
20
+ Product.tags_from_page(self, "https://#{locale}.#{domain}/#{path}")
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ # Get products with last edit date
27
+ #
28
+ def products(page: -1)
29
+ Product.from_website_page(url, page: page, products_count: products_count) if url
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ require 'hashie'
2
+
3
+ module Openfoodfacts
4
+ class NutritionGrade < Hashie::Mash
5
+
6
+ # TODO: Add more locales
7
+ LOCALE_PATHS = {
8
+ 'fr' => 'notes-nutritionnelles',
9
+ 'uk' => 'nutrition-grades',
10
+ 'us' => 'nutrition-grades',
11
+ 'world' => 'nutrition-grades'
12
+ }
13
+
14
+ class << self
15
+
16
+ # Get nutrition grades
17
+ #
18
+ def all(locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN)
19
+ if path = LOCALE_PATHS[locale]
20
+ Product.tags_from_page(self, "https://#{locale}.#{domain}/#{path}")
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ # Get products with nutrition grade
27
+ #
28
+ def products(page: -1)
29
+ Product.from_website_page(url, page: page, products_count: products_count) if url
30
+ end
31
+
32
+ end
33
+ end
@@ -15,9 +15,9 @@ module Openfoodfacts
15
15
 
16
16
  # Get origins
17
17
  #
18
- def all(locale: Openfoodfacts::DEFAULT_LOCALE)
18
+ def all(locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN)
19
19
  if path = LOCALE_PATHS[locale]
20
- Product.tags_from_page(self, "http://#{locale}.openfoodfacts.org/#{path}")
20
+ Product.tags_from_page(self, "https://#{locale}.#{domain}/#{path}")
21
21
  end
22
22
  end
23
23
 
@@ -30,4 +30,4 @@ module Openfoodfacts
30
30
  end
31
31
 
32
32
  end
33
- end
33
+ end
@@ -15,9 +15,9 @@ module Openfoodfacts
15
15
 
16
16
  # Get packager codes
17
17
  #
18
- def all(locale: Openfoodfacts::DEFAULT_LOCALE)
18
+ def all(locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN)
19
19
  if path = LOCALE_PATHS[locale]
20
- Product.tags_from_page(self, "http://#{locale}.openfoodfacts.org/#{path}")
20
+ Product.tags_from_page(self, "https://#{locale}.#{domain}/#{path}")
21
21
  end
22
22
  end
23
23
 
@@ -30,4 +30,4 @@ module Openfoodfacts
30
30
  end
31
31
 
32
32
  end
33
- end
33
+ end
@@ -15,9 +15,9 @@ module Openfoodfacts
15
15
 
16
16
  # Get packagings
17
17
  #
18
- def all(locale: Openfoodfacts::DEFAULT_LOCALE)
18
+ def all(locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN)
19
19
  if path = LOCALE_PATHS[locale]
20
- Product.tags_from_page(self, "http://#{locale}.openfoodfacts.org/#{path}")
20
+ Product.tags_from_page(self, "https://#{locale}.#{domain}/#{path}")
21
21
  end
22
22
  end
23
23
 
@@ -30,4 +30,4 @@ module Openfoodfacts
30
30
  end
31
31
 
32
32
  end
33
- end
33
+ end
@@ -8,44 +8,34 @@ module Openfoodfacts
8
8
 
9
9
  # TODO: Add more locales
10
10
  LOCALE_PATHS = {
11
- 'fr' => 'presse',
12
- 'uk' => 'press',
13
- 'us' => 'press',
14
- 'world' => 'press'
11
+ 'fr' => 'revue-de-presse-fr'
15
12
  }
16
13
 
17
14
  LOCALE_DATE_FORMATS = {
18
- 'fr' => '%d/%m/%Y',
19
- 'uk' => '%m/%d/%Y',
20
- 'us' => '%m/%d/%Y',
21
- 'world' => '%m/%d/%Y'
15
+ 'fr' => '%d/%m/%Y'
22
16
  }
23
17
 
24
18
  class << self
25
- def items(locale: Openfoodfacts::DEFAULT_LOCALE)
19
+ def items(locale: 'fr', domain: DEFAULT_DOMAIN)
26
20
  if path = LOCALE_PATHS[locale]
27
- html = open("http://#{locale}.openfoodfacts.org/#{path}").read
21
+ date_format = LOCALE_DATE_FORMATS[locale]
22
+
23
+ html = URI.open("https://#{locale}.#{domain}/#{path}").read
28
24
  dom = Nokogiri::HTML.fragment(html)
29
-
30
- titles = dom.css('#main_column li')
25
+
26
+ titles = dom.css('#press_table tbody tr')
31
27
  titles.each_with_index.map do |item, index|
32
- data = item.inner_html.split(' - ')
28
+ colums = item.css('td')
33
29
 
34
- link = Nokogiri::HTML.fragment(data.first).css('a')
30
+ link = colums[1].css('a')
35
31
  attributes = {
36
- "title" => link.text.strip,
37
- "url" => link.attr('href').value
32
+ "type" => colums[0].text,
33
+ "title" => colums[1].text.strip,
34
+ "url" => link && link.attr('href') && link.attr('href').value,
35
+ "source" => colums[2].text.strip,
36
+ "date" => (DateTime.strptime(colums[3].text, date_format) rescue nil)
38
37
  }
39
38
 
40
- last = Nokogiri::HTML.fragment(data.last)
41
- if date_format = LOCALE_DATE_FORMATS[locale] and date = last.text.strip[/\d+\/\d+\/\d+\z/, 0]
42
- attributes["date"] = DateTime.strptime(date, date_format)
43
- end
44
-
45
- if data.length >= 3
46
- attributes["source"] = Nokogiri::HTML.fragment(data[-2]).text.strip
47
- end
48
-
49
39
  new(attributes)
50
40
  end
51
41
  end
@@ -53,4 +43,4 @@ module Openfoodfacts
53
43
  end
54
44
 
55
45
  end
56
- end
46
+ end
@@ -1,3 +1,4 @@
1
+ require 'cgi'
1
2
  require 'hashie'
2
3
  require 'net/http'
3
4
  require 'nokogiri'
@@ -16,23 +17,12 @@ module Openfoodfacts
16
17
 
17
18
  class << self
18
19
 
19
- # Products from API
20
- #
21
- def from_api(api_url, page: -1)
22
- if url
23
- body = open(api_url).read
24
- json = JSON.parse(body)
25
- json_products = json['products']
26
- json_products.map(&method(:new))
27
- end
28
- end
29
-
30
20
  # Get product
31
21
  #
32
- def get(code, locale: Openfoodfacts::DEFAULT_LOCALE)
22
+ def get(code, locale: DEFAULT_LOCALE)
33
23
  if code
34
24
  product_url = url(code, locale: locale)
35
- json = open(product_url).read
25
+ json = URI.open(product_url).read
36
26
  hash = JSON.parse(json)
37
27
 
38
28
  new(hash["product"]) if !hash["status"].nil? && hash["status"] == 1
@@ -42,16 +32,20 @@ module Openfoodfacts
42
32
 
43
33
  # Return product API URL
44
34
  #
45
- def url(code, locale: Openfoodfacts::DEFAULT_LOCALE)
46
- "http://#{locale}.openfoodfacts.org/api/v0/produit/#{code}.json" if code
35
+ def url(code, locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN)
36
+ if code
37
+ path = "api/v0/produit/#{code}.json"
38
+ "https://#{locale}.#{domain}/#{path}"
39
+ end
47
40
  end
48
41
 
49
42
  # Search products
50
43
  #
51
- def search(terms, locale: Openfoodfacts::DEFAULT_LOCALE, page: 1, page_size: 20, sort_by: 'unique_scans_n')
52
- terms = URI::encode(terms)
53
- url = "http://#{locale}.openfoodfacts.org/cgi/search.pl?search_terms=#{terms}&jqm=1&page=#{page}&page_size=#{page_size}&sort_by=#{sort_by}"
54
- json = open(url).read
44
+ def search(terms, locale: DEFAULT_LOCALE, page: 1, page_size: 20, sort_by: 'unique_scans_n', domain: DEFAULT_DOMAIN)
45
+ terms = CGI.escape(terms)
46
+ path = "cgi/search.pl?search_terms=#{terms}&jqm=1&page=#{page}&page_size=#{page_size}&sort_by=#{sort_by}"
47
+ url = "https://#{locale}.#{domain}/#{path}"
48
+ json = URI.open(url).read
55
49
  hash = JSON.parse(json)
56
50
  html = hash["jqm"]
57
51
 
@@ -85,11 +79,11 @@ module Openfoodfacts
85
79
  end
86
80
 
87
81
  def from_jquery_mobile_list(jqm_html)
88
- from_html_list(jqm_html, 'ul li:not(#loadmore)', /code=(\d+)\Z/i)
82
+ from_html_list(jqm_html, 'ul#search_results_list li:not(#loadmore)', /code=(\d+)\Z/i)
89
83
  end
90
84
 
91
85
  def from_website_list(html, locale: 'world')
92
- from_html_list(html, 'ul.products li', /\/(\d+)[\/|\Z]/i, locale: 'world')
86
+ from_html_list(html, 'ul.products li', /\/(\d+)\/?/i, locale: 'world')
93
87
  end
94
88
 
95
89
  # page -1 to fetch all pages
@@ -97,7 +91,7 @@ module Openfoodfacts
97
91
  if page == -1
98
92
  if products_count # Avoid one call
99
93
  pages_count = (products_count.to_f / 20).ceil
100
- (1..pages_count).map { |page| from_website_page(page_url, page: page) }.flatten
94
+ (1..pages_count).map { |page_i| from_website_page(page_url, page: page_i) }.flatten
101
95
  else
102
96
  products = []
103
97
 
@@ -111,13 +105,13 @@ module Openfoodfacts
111
105
  products
112
106
  end
113
107
  else
114
- html = open("#{page_url}/#{page}").read
108
+ html = URI.open("#{page_url}/#{page}").read
115
109
  from_website_list(html, locale: Locale.locale_from_link(page_url))
116
110
  end
117
111
  end
118
112
 
119
113
  def tags_from_page(_klass, page_url, &custom_tag_parsing)
120
- html = open(page_url).read
114
+ html = URI.open(page_url).read
121
115
  dom = Nokogiri::HTML.fragment(html)
122
116
 
123
117
  dom.css('table#tagstable tbody tr').map do |tag|
@@ -154,10 +148,11 @@ module Openfoodfacts
154
148
  # User can be nil
155
149
  # Tested not updatable fields: countries, ingredients_text, purchase_places, purchase_places_tag, purchase_places_tags
156
150
  #
157
- def update(user: nil)
151
+ def update(user: nil, domain: DEFAULT_DOMAIN)
158
152
  if self.code && self.lc
159
153
  subdomain = self.lc == 'world' ? 'world' : "world-#{self.lc}"
160
- uri = URI("http://#{subdomain}.openfoodfacts.org/cgi/product_jqm.pl")
154
+ path = 'cgi/product_jqm.pl'
155
+ uri = URI("https://#{subdomain}.#{domain}/#{path}")
161
156
  params = self.to_hash
162
157
  params.merge!("user_id" => user.user_id, "password" => user.password) if user
163
158
  response = Net::HTTP.post_form(uri, params)
@@ -172,17 +167,18 @@ module Openfoodfacts
172
167
 
173
168
  # Return Product API URL
174
169
  #
175
- def url(locale: Openfoodfacts::DEFAULT_LOCALE)
170
+ def url(locale: DEFAULT_LOCALE)
176
171
  self.class.url(self.code, locale: locale)
177
172
  end
178
173
 
179
174
  # Return Product web URL according to locale
180
175
  #
181
- def weburl(locale: nil)
182
- locale ||= self.lc || Openfoodfacts::DEFAULT_LOCALE
176
+ def weburl(locale: nil, domain: DEFAULT_DOMAIN)
177
+ locale ||= self.lc || DEFAULT_LOCALE
183
178
 
184
179
  if self.code && prefix = LOCALE_WEBURL_PREFIXES[locale]
185
- "http://#{locale}.openfoodfacts.org/#{prefix}/#{self.code}"
180
+ path = "#{prefix}/#{self.code}"
181
+ "https://#{locale}.#{domain}/#{path}"
186
182
  end
187
183
  end
188
184
 
@@ -15,9 +15,9 @@ module Openfoodfacts
15
15
 
16
16
  # Get product states
17
17
  #
18
- def all(locale: Openfoodfacts::DEFAULT_LOCALE)
18
+ def all(locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN)
19
19
  if path = LOCALE_PATHS[locale]
20
- Product.tags_from_page(self, "http://#{locale}.openfoodfacts.org/#{path}")
20
+ Product.tags_from_page(self, "https://#{locale}.#{domain}/#{path}")
21
21
  end
22
22
  end
23
23
 
@@ -30,4 +30,4 @@ module Openfoodfacts
30
30
  end
31
31
 
32
32
  end
33
- end
33
+ end
@@ -15,9 +15,9 @@ module Openfoodfacts
15
15
 
16
16
  # Get purchase places
17
17
  #
18
- def all(locale: Openfoodfacts::DEFAULT_LOCALE)
18
+ def all(locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN)
19
19
  if path = LOCALE_PATHS[locale]
20
- Product.tags_from_page(self, "http://#{locale}.openfoodfacts.org/#{path}")
20
+ Product.tags_from_page(self, "https://#{locale}.#{domain}/#{path}")
21
21
  end
22
22
  end
23
23
 
@@ -30,4 +30,4 @@ module Openfoodfacts
30
30
  end
31
31
 
32
32
  end
33
- end
33
+ end