openbeautyfacts 0.6.3 → 0.10.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -1
  3. data/Rakefile +5 -3
  4. data/lib/openbeautyfacts/additive.rb +9 -39
  5. data/lib/openbeautyfacts/allergen.rb +14 -22
  6. data/lib/openbeautyfacts/brand.rb +14 -22
  7. data/lib/openbeautyfacts/category.rb +14 -22
  8. data/lib/openbeautyfacts/city.rb +14 -22
  9. data/lib/openbeautyfacts/contributor.rb +14 -22
  10. data/lib/openbeautyfacts/country.rb +14 -22
  11. data/lib/openbeautyfacts/entry_date.rb +15 -23
  12. data/lib/openbeautyfacts/faq.rb +16 -41
  13. data/lib/openbeautyfacts/ingredient.rb +14 -22
  14. data/lib/openbeautyfacts/ingredient_that_may_be_from_palm_oil.rb +14 -22
  15. data/lib/openbeautyfacts/label.rb +14 -22
  16. data/lib/openbeautyfacts/last_edit_date.rb +15 -23
  17. data/lib/openbeautyfacts/locale.rb +8 -31
  18. data/lib/openbeautyfacts/manufacturing_place.rb +14 -22
  19. data/lib/openbeautyfacts/mission.rb +16 -63
  20. data/lib/openbeautyfacts/number_of_ingredients.rb +15 -23
  21. data/lib/openbeautyfacts/origin.rb +14 -22
  22. data/lib/openbeautyfacts/packager_code.rb +14 -22
  23. data/lib/openbeautyfacts/packaging.rb +14 -22
  24. data/lib/openbeautyfacts/period_after_opening.rb +14 -22
  25. data/lib/openbeautyfacts/press.rb +16 -47
  26. data/lib/openbeautyfacts/product.rb +18 -175
  27. data/lib/openbeautyfacts/product_state.rb +14 -22
  28. data/lib/openbeautyfacts/purchase_place.rb +14 -22
  29. data/lib/openbeautyfacts/store.rb +14 -22
  30. data/lib/openbeautyfacts/trace.rb +14 -22
  31. data/lib/openbeautyfacts/user.rb +15 -30
  32. data/lib/openbeautyfacts/version.rb +3 -1
  33. data/lib/openbeautyfacts.rb +6 -9
  34. data/test/fixtures/additives.yml +48 -0
  35. data/test/fixtures/brands.yml +48 -0
  36. data/test/fixtures/entry_dates.yml +48 -0
  37. data/test/fixtures/entry_dates_locale.yml +48 -0
  38. data/test/fixtures/faq.yml +48 -0
  39. data/test/fixtures/fetch_product_3600550362626.yml +54 -0
  40. data/test/fixtures/index.yml +48 -0
  41. data/test/fixtures/ingredients.yml +99 -0
  42. data/test/fixtures/ingredients_locale.yml +99 -0
  43. data/test/fixtures/last_edit_dates.yml +48 -0
  44. data/test/fixtures/last_edit_dates_locale.yml +48 -0
  45. data/test/fixtures/login_user.yml +363 -0
  46. data/test/fixtures/mission.yml +48 -0
  47. data/test/fixtures/missions.yml +48 -0
  48. data/test/fixtures/number_of_ingredients_locale.yml +48 -0
  49. data/test/fixtures/numbers_of_ingredients.yml +48 -0
  50. data/test/fixtures/press.yml +48 -0
  51. data/test/fixtures/product_3600550362626.yml +54 -0
  52. data/test/fixtures/product_states.yml +48 -0
  53. data/test/fixtures/product_states_locale.yml +48 -0
  54. data/test/fixtures/products_for_brand.yml +99 -0
  55. data/test/fixtures/products_for_entry_date.yml +99 -0
  56. data/test/fixtures/products_for_ingredient.yml +99 -0
  57. data/test/fixtures/products_for_last_edit_date.yml +99 -0
  58. data/test/fixtures/products_for_number_of_ingredients.yml +99 -0
  59. data/test/fixtures/products_for_period_after_opening.yml +99 -0
  60. data/test/fixtures/products_for_state.yml +99 -0
  61. data/test/fixtures/products_with_additive.yml +99 -0
  62. data/test/fixtures/search_doux.yml +105 -0
  63. data/test/fixtures/search_doux_1_000_000.yml +54 -0
  64. data/test/minitest_helper.rb +5 -3
  65. data/test/test_openbeautyfacts.rb +129 -97
  66. metadata +59 -17
@@ -1,197 +1,40 @@
1
- require 'cgi'
2
- require 'hashie'
3
- require 'net/http'
4
- require 'nokogiri'
5
- require 'open-uri'
1
+ # frozen_string_literal: true
6
2
 
7
3
  module Openbeautyfacts
8
- class Product < Hashie::Mash
4
+ class Product < Openfoodfacts::Product
5
+ # Override constants for openbeautyfacts domain
6
+ DEFAULT_LOCALE = Locale::GLOBAL
7
+ DEFAULT_DOMAIN = 'openbeautyfacts.org'
9
8
 
10
- # TODO: Add more locales
9
+ # OpenBeautyFacts uses the same URL prefixes as OpenFoodFacts
11
10
  LOCALE_WEBURL_PREFIXES = {
12
11
  'fr' => 'produit',
13
12
  'uk' => 'product',
14
13
  'us' => 'product',
15
14
  'world' => 'product'
16
- }
15
+ }.freeze
17
16
 
18
17
  class << self
19
-
20
- # Get product
21
- #
22
- def get(code, locale: DEFAULT_LOCALE)
23
- if code
24
- product_url = url(code, locale: locale)
25
- json = URI.open(product_url).read
26
- hash = JSON.parse(json)
27
-
28
- new(hash["product"]) if !hash["status"].nil? && hash["status"] == 1
29
- end
30
- end
31
- alias_method :find, :get
32
-
33
- # Return product API URL
34
- #
18
+ # Override URL method to use openbeautyfacts domain
35
19
  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
40
- end
41
-
42
- # Search products
43
- #
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
49
- hash = JSON.parse(json)
50
- html = hash["jqm"]
51
-
52
- from_jquery_mobile_list(html)
53
- end
54
- alias_method :where, :search
55
-
56
- def from_html_list(html, list_css_selector, code_from_link_regex, locale: 'world')
57
- dom = Nokogiri::HTML.fragment(html)
58
- dom.css(list_css_selector).map do |product|
59
- attributes = {}
60
-
61
- if link = product.css('a').first
62
- attributes["product_name"] = link.inner_text.strip
63
-
64
- if code = link.attr('href')[code_from_link_regex, 1]
65
- attributes["_id"] = code
66
- attributes["code"] = code
67
- end
68
- end
69
-
70
- if image = product.css('img').first and image_url = image.attr('src')
71
- attributes["image_small_url"] = image_url
72
- attributes["lc"] = Locale.locale_from_link(image_url)
73
- end
74
- attributes["lc"] ||= locale
75
-
76
- new(attributes)
77
- end
78
-
79
- end
80
-
81
- def from_jquery_mobile_list(jqm_html)
82
- from_html_list(jqm_html, 'ul#search_results_list li:not(#loadmore)', /code=(\d+)\Z/i)
83
- end
84
-
85
- def from_website_list(html, locale: 'world')
86
- from_html_list(html, 'ul.products li', /\/(\d+)\/?/i, locale: 'world')
20
+ super(code, locale: locale, domain: domain)
87
21
  end
88
22
 
89
- # page -1 to fetch all pages
90
- def from_website_page(page_url, page: -1, products_count: nil)
91
- if page == -1
92
- if products_count # Avoid one call
93
- pages_count = (products_count.to_f / 20).ceil
94
- (1..pages_count).map { |page| from_website_page(page_url, page: page) }.flatten
95
- else
96
- products = []
97
-
98
- page = 1
99
- begin
100
- products_on_page = from_website_page(page_url, page: page)
101
- products += products_on_page
102
- page += 1
103
- end while products_on_page.any?
104
-
105
- products
106
- end
107
- else
108
- html = URI.open("#{page_url}/#{page}").read
109
- from_website_list(html, locale: Locale.locale_from_link(page_url))
110
- end
23
+ # Override search method to use openbeautyfacts domain
24
+ def search(terms, locale: DEFAULT_LOCALE, page: 1, page_size: 20, sort_by: 'unique_scans_n',
25
+ domain: DEFAULT_DOMAIN)
26
+ super(terms, locale: locale, page: page, page_size: page_size, sort_by: sort_by, domain: domain)
111
27
  end
112
-
113
- def tags_from_page(_klass, page_url, &custom_tag_parsing)
114
- html = URI.open(page_url).read
115
- dom = Nokogiri::HTML.fragment(html)
116
-
117
- dom.css('table#tagstable tbody tr').map do |tag|
118
- if custom_tag_parsing
119
- custom_tag_parsing.call(tag)
120
- else
121
- link = tag.css('a').first
122
-
123
- name = link.text.strip
124
- img_alt = link.css('img').attr('alt')
125
- if (name.nil? || name == '') && img_alt
126
- img_alt_text = img_alt.to_s.strip
127
- name = if img_alt_text.include?(':')
128
- img_alt_text.split(':').last.strip
129
- else
130
- img_alt_text[/\s+([^\s]+)$/, 1]
131
- end
132
- end
133
-
134
- _klass.new({
135
- "name" => name,
136
- "url" => URI.join(page_url, link.attr('href')).to_s,
137
- "products_count" => tag.css('td')[1].text.to_i
138
- })
139
- end
140
- end
141
- end
142
-
143
28
  end
144
29
 
145
- # Fetch product
146
- #
147
- def fetch
148
- if (self.code)
149
- product = self.class.get(self.code)
150
- self.merge!(product)
151
- end
152
-
153
- self
30
+ # Override weburl method to use openbeautyfacts domain
31
+ def weburl(locale: nil, domain: DEFAULT_DOMAIN)
32
+ super(locale: locale, domain: domain)
154
33
  end
155
- alias_method :reload, :fetch
156
34
 
157
- # Update product
158
- # Only product_name, brands and quantity fields seems to be updatable throught app / API.
159
- # User can be nil
160
- # Tested not updatable fields: countries, ingredients_text, purchase_places, purchase_places_tag, purchase_places_tags
161
- #
35
+ # Override update method to use openbeautyfacts domain
162
36
  def update(user: nil, domain: DEFAULT_DOMAIN)
163
- if self.code && self.lc
164
- subdomain = self.lc == 'world' ? 'world' : "world-#{self.lc}"
165
- path = 'cgi/product_jqm.pl'
166
- uri = URI("https://#{subdomain}.#{domain}/#{path}")
167
- params = self.to_hash
168
- params.merge!("user_id" => user.user_id, "password" => user.password) if user
169
- response = Net::HTTP.post_form(uri, params)
170
-
171
- data = JSON.parse(response.body)
172
- data["status"] == 1
173
- else
174
- false
175
- end
176
- end
177
- alias_method :save, :update
178
-
179
- # Return Product API URL
180
- #
181
- def url(locale: DEFAULT_LOCALE)
182
- self.class.url(self.code, locale: locale)
37
+ super(user: user, domain: domain)
183
38
  end
184
-
185
- # Return Product web URL according to locale
186
- #
187
- def weburl(locale: nil, domain: DEFAULT_DOMAIN)
188
- locale ||= self.lc || DEFAULT_LOCALE
189
-
190
- if self.code && prefix = LOCALE_WEBURL_PREFIXES[locale]
191
- path = "#{prefix}/#{self.code}"
192
- "https://#{locale}.#{domain}/#{path}"
193
- end
194
- end
195
-
196
39
  end
197
40
  end
@@ -1,33 +1,25 @@
1
- require 'hashie'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Openbeautyfacts
4
- class ProductState < Hashie::Mash
5
-
6
- # TODO: Add more locales
7
- LOCALE_PATHS = {
8
- 'fr' => 'etats',
9
- 'uk' => 'states',
10
- 'us' => 'states',
11
- 'world' => 'states'
12
- }
4
+ class ProductState < Openfoodfacts::ProductState
5
+ # Override constants for openbeautyfacts domain
6
+ DEFAULT_LOCALE = Locale::GLOBAL
7
+ DEFAULT_DOMAIN = 'openbeautyfacts.org'
13
8
 
14
9
  class << self
15
-
16
- # Get product states
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
10
+ # Override all method to use openbeautyfacts domain if it exists
11
+ def all(locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN, **options)
12
+ super(locale: locale, domain: domain, **options)
13
+ rescue NoMethodError
14
+ # Method doesn't exist in parent class, skip
22
15
  end
23
-
24
16
  end
25
17
 
26
- # Get products with state
27
- #
18
+ # Override products method to use openbeautyfacts domain if it exists
28
19
  def products(page: -1)
29
- Product.from_website_page(url, page: page, products_count: products_count) if url
20
+ super(page: page)
21
+ rescue NoMethodError
22
+ # Method doesn't exist in parent class, skip
30
23
  end
31
-
32
24
  end
33
25
  end
@@ -1,33 +1,25 @@
1
- require 'hashie'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Openbeautyfacts
4
- class PurchasePlace < Hashie::Mash
5
-
6
- # TODO: Add more locales
7
- LOCALE_PATHS = {
8
- 'fr' => 'lieux-de-vente',
9
- 'uk' => 'purchase-places',
10
- 'us' => 'purchase-places',
11
- 'world' => 'purchase-places'
12
- }
4
+ class PurchasePlace < Openfoodfacts::PurchasePlace
5
+ # Override constants for openbeautyfacts domain
6
+ DEFAULT_LOCALE = Locale::GLOBAL
7
+ DEFAULT_DOMAIN = 'openbeautyfacts.org'
13
8
 
14
9
  class << self
15
-
16
- # Get purchase places
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
10
+ # Override all method to use openbeautyfacts domain if it exists
11
+ def all(locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN, **options)
12
+ super(locale: locale, domain: domain, **options)
13
+ rescue NoMethodError
14
+ # Method doesn't exist in parent class, skip
22
15
  end
23
-
24
16
  end
25
17
 
26
- # Get products with purchase place
27
- #
18
+ # Override products method to use openbeautyfacts domain if it exists
28
19
  def products(page: -1)
29
- Product.from_website_page(url, page: page, products_count: products_count) if url
20
+ super(page: page)
21
+ rescue NoMethodError
22
+ # Method doesn't exist in parent class, skip
30
23
  end
31
-
32
24
  end
33
25
  end
@@ -1,33 +1,25 @@
1
- require 'hashie'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Openbeautyfacts
4
- class Store < Hashie::Mash
5
-
6
- # TODO: Add more locales
7
- LOCALE_PATHS = {
8
- 'fr' => 'magasins',
9
- 'uk' => 'stores',
10
- 'us' => 'stores',
11
- 'world' => 'stores'
12
- }
4
+ class Store < Openfoodfacts::Store
5
+ # Override constants for openbeautyfacts domain
6
+ DEFAULT_LOCALE = Locale::GLOBAL
7
+ DEFAULT_DOMAIN = 'openbeautyfacts.org'
13
8
 
14
9
  class << self
15
-
16
- # Get stores
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
10
+ # Override all method to use openbeautyfacts domain if it exists
11
+ def all(locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN, **options)
12
+ super(locale: locale, domain: domain, **options)
13
+ rescue NoMethodError
14
+ # Method doesn't exist in parent class, skip
22
15
  end
23
-
24
16
  end
25
17
 
26
- # Get products from store
27
- #
18
+ # Override products method to use openbeautyfacts domain if it exists
28
19
  def products(page: -1)
29
- Product.from_website_page(url, page: page, products_count: products_count) if url
20
+ super(page: page)
21
+ rescue NoMethodError
22
+ # Method doesn't exist in parent class, skip
30
23
  end
31
-
32
24
  end
33
25
  end
@@ -1,33 +1,25 @@
1
- require 'hashie'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Openbeautyfacts
4
- class Trace < Hashie::Mash
5
-
6
- # TODO: Add more locales
7
- LOCALE_PATHS = {
8
- 'fr' => 'traces',
9
- 'uk' => 'traces',
10
- 'us' => 'traces',
11
- 'world' => 'traces'
12
- }
4
+ class Trace < Openfoodfacts::Trace
5
+ # Override constants for openbeautyfacts domain
6
+ DEFAULT_LOCALE = Locale::GLOBAL
7
+ DEFAULT_DOMAIN = 'openbeautyfacts.org'
13
8
 
14
9
  class << self
15
-
16
- # Get traces
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
10
+ # Override all method to use openbeautyfacts domain if it exists
11
+ def all(locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN, **options)
12
+ super(locale: locale, domain: domain, **options)
13
+ rescue NoMethodError
14
+ # Method doesn't exist in parent class, skip
22
15
  end
23
-
24
16
  end
25
17
 
26
- # Get products with trace
27
- #
18
+ # Override products method to use openbeautyfacts domain if it exists
28
19
  def products(page: -1)
29
- Product.from_website_page(url, page: page, products_count: products_count) if url
20
+ super(page: page)
21
+ rescue NoMethodError
22
+ # Method doesn't exist in parent class, skip
30
23
  end
31
-
32
24
  end
33
25
  end
@@ -1,40 +1,25 @@
1
- require 'net/http'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Openbeautyfacts
4
- class User < Hashie::Mash
4
+ class User < Openfoodfacts::User
5
+ # Override constants for openbeautyfacts domain
6
+ DEFAULT_LOCALE = Locale::GLOBAL
7
+ DEFAULT_DOMAIN = 'openbeautyfacts.org'
5
8
 
6
9
  class << self
7
-
8
- # Login
9
- #
10
- def login(user_id, password, locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN)
11
- path = 'cgi/session.pl'
12
- uri = URI("https://#{locale}.#{domain}/#{path}")
13
- params = {
14
- "jqm" => "1",
15
- "user_id" => user_id,
16
- "password" => password
17
- }
18
-
19
- response = Net::HTTP.post_form(uri, params)
20
- data = JSON.parse(response.body)
21
-
22
- if data['user_id']
23
- data.merge!(password: password)
24
- new(data)
25
- end
10
+ # Override all method to use openbeautyfacts domain if it exists
11
+ def all(locale: DEFAULT_LOCALE, domain: DEFAULT_DOMAIN, **options)
12
+ super(locale: locale, domain: domain, **options)
13
+ rescue NoMethodError
14
+ # Method doesn't exist in parent class, skip
26
15
  end
27
-
28
16
  end
29
17
 
30
- # Login
31
- #
32
- def login(locale: DEFAULT_LOCALE)
33
- if user = self.class.login(self.user_id, self.password, locale: locale)
34
- self.name = user.name
35
- self
36
- end
18
+ # Override products method to use openbeautyfacts domain if it exists
19
+ def products(page: -1)
20
+ super(page: page)
21
+ rescue NoMethodError
22
+ # Method doesn't exist in parent class, skip
37
23
  end
38
-
39
24
  end
40
25
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Openbeautyfacts
2
- VERSION = "0.6.3"
4
+ VERSION = '0.10.0'
3
5
  end
@@ -1,3 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openfoodfacts'
4
+
5
+ require_relative 'openbeautyfacts/version'
6
+ require_relative 'openbeautyfacts/locale'
1
7
  require_relative 'openbeautyfacts/additive'
2
8
  require_relative 'openbeautyfacts/brand'
3
9
  require_relative 'openbeautyfacts/category'
@@ -10,7 +16,6 @@ require_relative 'openbeautyfacts/ingredient'
10
16
  require_relative 'openbeautyfacts/ingredient_that_may_be_from_palm_oil'
11
17
  require_relative 'openbeautyfacts/label'
12
18
  require_relative 'openbeautyfacts/last_edit_date'
13
- require_relative 'openbeautyfacts/locale'
14
19
  require_relative 'openbeautyfacts/manufacturing_place'
15
20
  require_relative 'openbeautyfacts/mission'
16
21
  require_relative 'openbeautyfacts/number_of_ingredients'
@@ -25,19 +30,12 @@ require_relative 'openbeautyfacts/purchase_place'
25
30
  require_relative 'openbeautyfacts/store'
26
31
  require_relative 'openbeautyfacts/trace'
27
32
  require_relative 'openbeautyfacts/user'
28
- require_relative 'openbeautyfacts/version'
29
-
30
- require 'json'
31
- require 'nokogiri'
32
- require 'open-uri'
33
33
 
34
34
  module Openbeautyfacts
35
-
36
35
  DEFAULT_LOCALE = Locale::GLOBAL
37
36
  DEFAULT_DOMAIN = 'openbeautyfacts.org'
38
37
 
39
38
  class << self
40
-
41
39
  # Return locale from link
42
40
  #
43
41
  def locale_from_link(link)
@@ -61,6 +59,5 @@ module Openbeautyfacts
61
59
  def product_url(barcode, locale: DEFAULT_LOCALE)
62
60
  Product.url(barcode, locale: locale)
63
61
  end
64
-
65
62
  end
66
63
  end