Class: Vehicles::Dataset
- Inherits:
-
Object
- Object
- Vehicles::Dataset
- Defined in:
- lib/vehicles/dataset.rb
Overview
Loads the bundled snapshot once, builds in-memory indexes, and answers every query. No HTTP, no SQLite, no ActiveRecord on the read path — the first call builds the index, every call after is a hash lookup.
Instances are memoized per data path (see .load), so the JSON is parsed once per process.
Constant Summary collapse
- BUILTIN_ALIASES =
Built-in make aliases (normalized key => make slug). Common abbreviations and nicknames so whatever a user types tends to land. Diacritics and case are already handled by Vehicles.normalize, so “škoda”/“citroën” need no entry.
{ "vw" => "volkswagen", "vdub" => "volkswagen", "merc" => "mercedes-benz", "mercedes" => "mercedes-benz", "benz" => "mercedes-benz", "mb" => "mercedes-benz", "chevy" => "chevrolet", "beemer" => "bmw", "bimmer" => "bmw", "alfa" => "alfa-romeo", "landrover" => "land-rover", "range rover" => "land-rover", "rangerover" => "land-rover", "vauxhall" => "opel" # GB badge-engineered Opel; map to the EU make we ship }.freeze
Instance Attribute Summary collapse
-
#region ⇒ Object
readonly
Returns the value of attribute region.
-
#schema_version ⇒ Object
readonly
Returns the value of attribute schema_version.
-
#version ⇒ Object
readonly
Returns the value of attribute version.
Class Method Summary collapse
-
.load(path = Vehicles.data_path) ⇒ Object
Memoized per path so the bundled JSON is parsed only once per process.
-
.reset! ⇒ Object
Drop the cache (used by the test suite between runs).
Instance Method Summary collapse
-
#all_models ⇒ Object
Flat list of every Model (memoized + frozen — shared, so don’t let callers mutate it).
-
#find_make(query) ⇒ Object
Resolve a make from a String/Symbol/Make via aliases, slug, or name.
-
#find_model(query) ⇒ Object
Resolve a free-text “make + model” string into one Model.
-
#initialize(raw) ⇒ Dataset
constructor
A new instance of Dataset.
-
#makes(kind: nil, region: nil) ⇒ Object
All makes, optionally filtered by kind/region.
-
#region?(region) ⇒ Boolean
Does this snapshot cover the given region? (Today only :eu.).
-
#search(query) ⇒ Object
Every model whose name (or full name) matches the query, ranked: exact name, then prefix, then substring, then full-name substring.
Constructor Details
#initialize(raw) ⇒ Dataset
Returns a new instance of Dataset.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/vehicles/dataset.rb', line 41 def initialize(raw) @version = raw["version"] @schema_version = raw["schema_version"] @region = raw["region"] @makes = (raw["makes"] || []).map { |attrs| Make.new(attrs) } @by_slug = {} # raw slug => Make @index = {} # normalized name/slug/alias => Make @makes.each do |make| @by_slug[make.slug] = make index(make.name, make) index(make.slug, make) make.aliases.each { |a| index(a, make) } end end |
Instance Attribute Details
#region ⇒ Object (readonly)
Returns the value of attribute region.
39 40 41 |
# File 'lib/vehicles/dataset.rb', line 39 def region @region end |
#schema_version ⇒ Object (readonly)
Returns the value of attribute schema_version.
39 40 41 |
# File 'lib/vehicles/dataset.rb', line 39 def schema_version @schema_version end |
#version ⇒ Object (readonly)
Returns the value of attribute version.
39 40 41 |
# File 'lib/vehicles/dataset.rb', line 39 def version @version end |
Class Method Details
.load(path = Vehicles.data_path) ⇒ Object
Memoized per path so the bundled JSON is parsed only once per process.
29 30 31 |
# File 'lib/vehicles/dataset.rb', line 29 def load(path = Vehicles.data_path) (@instances ||= {})[path] ||= new(JSON.parse(File.read(path))) end |
.reset! ⇒ Object
Drop the cache (used by the test suite between runs).
34 35 36 |
# File 'lib/vehicles/dataset.rb', line 34 def reset! @instances = {} end |
Instance Method Details
#all_models ⇒ Object
Flat list of every Model (memoized + frozen — shared, so don’t let callers mutate it). Backs ‘search`.
128 129 130 |
# File 'lib/vehicles/dataset.rb', line 128 def all_models @all_models ||= @makes.flat_map(&:models).freeze end |
#find_make(query) ⇒ Object
Resolve a make from a String/Symbol/Make via aliases, slug, or name.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/vehicles/dataset.rb', line 68 def find_make(query) return query if query.is_a?(Make) q = Vehicles.normalize(query) return nil if q.empty? # 1. user-supplied aliases win if (canonical = Vehicles.configuration.aliases[q]) return @by_slug[Vehicles.slugify(canonical)] || @index[Vehicles.normalize(canonical)] end # 2. built-in aliases if (slug = BUILTIN_ALIASES[q]) return @by_slug[slug] end # 3. direct slug / normalized name / make alias @by_slug[q] || @index[q] end |
#find_model(query) ⇒ Object
Resolve a free-text “make + model” string into one Model. Tries the longest leading make prefix first (“land rover defender”), then the remainder as the model. Returns nil if nothing matches.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/vehicles/dataset.rb', line 90 def find_model(query) q = Vehicles.normalize(query) tokens = q.split return nil if tokens.empty? (tokens.length - 1).downto(1) do |i| make = find_make(tokens[0, i].join(" ")) next unless make model = make.model(tokens[i..].join(" ")) return model if model end nil end |
#makes(kind: nil, region: nil) ⇒ Object
All makes, optionally filtered by kind/region. Unknown region => [] (honest: we don’t ship that pack yet), so callers never get wrong-region data.
59 60 61 62 63 64 65 |
# File 'lib/vehicles/dataset.rb', line 59 def makes(kind: nil, region: nil) return [] if region && !region_match?(region) list = @makes list = list.select { |m| m.kinds.include?(kind.to_sym) } if kind list end |
#region?(region) ⇒ Boolean
Does this snapshot cover the given region? (Today only :eu.)
133 134 135 |
# File 'lib/vehicles/dataset.rb', line 133 def region?(region) region_match?(region) end |
#search(query) ⇒ Object
Every model whose name (or full name) matches the query, ranked: exact name, then prefix, then substring, then full-name substring. Shorter names first.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/vehicles/dataset.rb', line 107 def search(query) q = Vehicles.normalize(query) return [] if q.empty? scored = [] all_models.each do |m| name_n = Vehicles.normalize(m.name) score = if name_n == q then 0 elsif name_n.start_with?(q) then 1 elsif name_n.include?(q) then 2 elsif Vehicles.normalize(m.full_name).include?(q) then 3 else next end scored << [score, m.name.length, m] end scored.sort_by { |score, len, _m| [score, len] }.map { |_s, _l, m| m } end |