Module: Menuella::FoodSafety
- Defined in:
- lib/menuella/food_safety.rb,
lib/menuella/food_safety/version.rb
Overview
The Menuella food-safety vocabulary: EU Reg. 1169/2011 Annex II allergens and the Menuella declarations, in six languages.
Semantic keys instead of country-specific numbers — store the key, render the code, never the reverse.
de = Menuella::FoodSafety.disclosures("de")
de.allergens.find { _1.key == "WHEAT" }.declaration
# => "Enthält Getreide und glutenhaltige Erzeugnisse"
This module does no i18n. Hand it the locale your application already resolved; it will not sniff the environment, negotiate, or quietly fall back.
Everything it returns is frozen. The dataset is a shared constant, and a caller that could mutate one bundle would be mutating every other caller's copy in the same process.
Defined Under Namespace
Classes: Allergen, Codes, Declaration, Disclosures, Error, Icon, IconNode, UnknownIconError, UnsupportedLocaleError
Constant Summary collapse
- VERSION =
Kept in step with every other binding by scripts/verify.mjs: one dataset, one version, released from one tag.
"1.6.0"
Class Method Summary collapse
-
.allergen_key?(value) ⇒ Boolean
True when
valueis a current allergen key. -
.allergen_keys ⇒ Object
Every selectable allergen key, in canonical order.
-
.code_scheme ⇒ Object
The scheme #codes belong to.
-
.codes ⇒ Object
The footnote-code scheme.
-
.dataset(name) ⇒ Object
The raw packaged JSON for
name, for tooling that wants the dataset rather than the objects. -
.declaration_key?(value) ⇒ Boolean
True when
valueis a current declaration key. -
.declaration_keys ⇒ Object
Every declaration key, in canonical order.
-
.disclosures(locale) ⇒ Object
Every disclosure for
locale, ready to render. -
.fallback_locale ⇒ Object
The locale a bundle falls back to for anything it does not itself carry.
-
.icon(name) ⇒ Object
The glyph named
name, as data. -
.icon_names ⇒ Object
Every icon name that has a glyph, sorted.
-
.icon_to_svg(name, size: 24, css_class: nil, title: nil) ⇒ Object
The glyph named
nameas an string, for anything that interpolates markup — server-rendered HTML, e-mail, PDF. -
.locale?(value) ⇒ Boolean
True when
valueis a locale with a bundle. -
.locales ⇒ Object
Locales with a prebuilt bundle.
Class Method Details
.allergen_key?(value) ⇒ Boolean
True when value is a current allergen key. Retired keys and
display-only groups return false.
201 202 203 |
# File 'lib/menuella/food_safety.rb', line 201 def allergen_key?(value) allergen_keys.include?(value) end |
.allergen_keys ⇒ Object
Every selectable allergen key, in canonical order.
Derived from the dataset, never hand-listed. Keys are locale-independent, so this reads the fallback bundle rather than taking a locale argument.
180 181 182 |
# File 'lib/menuella/food_safety.rb', line 180 def allergen_keys memo(:allergen_keys) { disclosures(fallback_locale).allergens.map(&:key).freeze } end |
.code_scheme ⇒ Object
The scheme #codes belong to.
171 172 173 |
# File 'lib/menuella/food_safety.rb', line 171 def code_scheme codes.scheme end |
.codes ⇒ Object
The footnote-code scheme.
159 160 161 162 163 164 165 166 167 168 |
# File 'lib/menuella/food_safety.rb', line 159 def codes memo(:codes) do raw = read("codes") Codes.new( scheme: raw.fetch("scheme").freeze, allergens: deep_freeze(raw.fetch("allergens")), declarations: deep_freeze(raw.fetch("declarations")), ) end end |
.dataset(name) ⇒ Object
The raw packaged JSON for name, for tooling that wants the dataset
rather than the objects. Returns a fresh string each call.
251 252 253 |
# File 'lib/menuella/food_safety.rb', line 251 def dataset(name) File.read(File.join(DATA_DIR, "#{name.sub(/\.json\z/, '')}.json"), encoding: "UTF-8") end |
.declaration_key?(value) ⇒ Boolean
True when value is a current declaration key.
206 207 208 |
# File 'lib/menuella/food_safety.rb', line 206 def declaration_key?(value) declaration_keys.include?(value) end |
.declaration_keys ⇒ Object
Every declaration key, in canonical order.
185 186 187 |
# File 'lib/menuella/food_safety.rb', line 185 def declaration_keys memo(:declaration_keys) { disclosures(fallback_locale).declarations.map(&:key).freeze } end |
.disclosures(locale) ⇒ Object
Every disclosure for locale, ready to render.
Raises UnsupportedLocaleError rather than falling back: a silently wrong language on an allergen panel is worse than a loud failure. The caller knows which locales it supports, and #locale? is there to ask.
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/menuella/food_safety.rb', line 126 def disclosures(locale) raise UnsupportedLocaleError, locale unless locale?(locale) memo(:bundles, locale) do raw = read("bundles/#{locale}") Disclosures.new( locale: raw.fetch("locale").freeze, fallback_locale: raw.fetch("fallbackLocale").freeze, allergens: raw.fetch("allergens").map { |a| Allergen.new( key: a.fetch("key").freeze, group: a.fetch("group").freeze, is_member: a.fetch("isMember"), icon: a.fetch("icon").freeze, name: a.fetch("name").freeze, declaration: a.fetch("declaration").freeze, description: a.fetch("description").freeze, ) }.freeze, declarations: raw.fetch("declarations").map { |d| Declaration.new( key: d.fetch("key").freeze, category: d.fetch("category").freeze, icon: d.fetch("icon").freeze, name: d.fetch("name").freeze, description: d.fetch("description").freeze, ) }.freeze, ) end end |
.fallback_locale ⇒ Object
The locale a bundle falls back to for anything it does not itself carry.
117 118 119 |
# File 'lib/menuella/food_safety.rb', line 117 def fallback_locale "en" end |
.icon(name) ⇒ Object
The glyph named name, as data.
Every shape paints with currentColor, so a glyph inherits the
surrounding text colour and follows a light or dark theme with no
second asset.
215 216 217 |
# File 'lib/menuella/food_safety.rb', line 215 def icon(name) icons.fetch(name) { raise UnknownIconError, name } end |
.icon_names ⇒ Object
Every icon name that has a glyph, sorted.
190 191 192 |
# File 'lib/menuella/food_safety.rb', line 190 def icon_names memo(:icon_names) { icons.keys.sort.freeze } end |
.icon_to_svg(name, size: 24, css_class: nil, title: nil) ⇒ Object
The glyph named name as an string, for anything that
interpolates markup — server-rendered HTML, e-mail, PDF.
Decorative by default: emits aria-hidden unless title: is given,
which switches it to role="img" with a
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/menuella/food_safety.rb', line 226 def icon_to_svg(name, size: 24, css_class: nil, title: nil) glyph = icon(name) out = +%(<svg xmlns="http://www.w3.org/2000/svg" viewBox="#{escape(glyph.view_box)}") out << %( width="#{size.to_i}" height="#{size.to_i}" fill="none") out << %( class="#{escape(css_class)}") if css_class && !css_class.empty? out << if title && !title.empty? %( role="img"><title>#{escape(title)}</title>) else %( aria-hidden="true" focusable="false">) end glyph.nodes.each do |node| out << "<#{node.tag}" node.attributes.each { |key, value| out << %( #{key}="#{escape(value)}") } out << "/>" end out << "</svg>" out.freeze end |
.locale?(value) ⇒ Boolean
True when value is a locale with a bundle.
195 196 197 |
# File 'lib/menuella/food_safety.rb', line 195 def locale?(value) locales.include?(value) end |
.locales ⇒ Object
Locales with a prebuilt bundle. Derived from the shipped bundles/
directory, so adding a new locale is a matter of dropping bundles/xx.json
in — no source list to keep in step.
109 110 111 112 113 114 |
# File 'lib/menuella/food_safety.rb', line 109 def locales @locales ||= Dir.glob(File.join(DATA_DIR, "bundles", "*.json")) .map { File.basename(_1, ".json") } .sort .freeze end |