Class: Vehicles::Model

Inherits:
Object
  • Object
show all
Defined in:
lib/vehicles/model.rb,
sig/vehicles.rbs

Overview

A single vehicle nameplate, e.g. "Volkswagen Golf". A lightweight, immutable value object — no ActiveRecord, no mutation. Reads like English on purpose.

car = Vehicles.find("vw golf")
car.make        # => "Volkswagen"
car.name        # => "Golf"
car.full_name   # => "Volkswagen Golf"
car.body_type   # => :hatchback
car.suv?        # => false

Constant Summary collapse

KINDS =

Vehicle kinds (RDW voertuigsoort). Today the bundled data is all :car; the shape already supports the rest for when those packs land.

Returns:

  • (Array[Symbol])
%i[car motorcycle van truck pickup trailer bus moped quad trike].freeze
BODY_TYPES =

Body types — the sub-classification within a kind. For cars these are the familiar shapes; motorcycle styles (:naked, :adventure, …) reuse this field.

Returns:

  • (Array[Symbol])
%i[
  hatchback sedan wagon suv mpv coupe convertible roadster pickup van
].freeze
REGIONS =

Continent codes (the model regions rollup). Order = rough market size.

%i[eu na as sa oc af].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attrs, make:, make_slug:) ⇒ Model

Returns a new instance of Model.

Parameters:

  • attrs (Hash)

    one model entry from the dataset (name/slug/kind + optional body_type/global_decile/availability/regions/aliases/former_ids)

  • make (String)

    the parent make's display name

  • make_slug (String)

    the parent make's slug (for the composite slug)



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/vehicles/model.rb', line 34

def initialize(attrs, make:, make_slug:)
  @make       = make
  @make_slug  = make_slug
  @name       = attrs["name"]
  @model_slug = attrs["slug"]
  @kind       = (attrs["kind"] || "car").to_sym
  # nil = no body vocabulary for this record yet (most non-car kinds).
  # A default would be a lie — predicates just return false instead.
  @body_type  = attrs["body_type"]&.to_sym
  # Popularity decile 1 (most popular) … 10, averaged over every country
  # with registration counts. nil = catalog-only evidence, no counts yet.
  @global_decile = attrs["global_decile"]
  # ISO-3166-1 alpha-2 codes where an official source evidences the model.
  # Evidence of PRESENCE, not proof of official marketing (grey imports
  # count — they're real vehicles on real roads).
  @availability  = (attrs["availability"] || []).freeze
  # Continent rollup of availability (:eu/:na/:sa/:as/:oc/:af).
  @regions       = (attrs["regions"] || []).map(&:to_sym).freeze
  # Documented alternate names (nicknames, native scripts, market names).
  @aliases       = (attrs["aliases"] || []).freeze
  # Full canonical ids ("car/alfa-romeo/159sw") this record absorbed via
  # the dataset's append-only migration contract (SCHEMA.md: renames
  # alias, nothing is silently deleted). Empty for never-renamed records.
  @former_ids    = (attrs["former_ids"] || []).freeze
  freeze
end

Instance Attribute Details

#aliasesObject (readonly)

Returns the value of attribute aliases.



27
28
29
# File 'lib/vehicles/model.rb', line 27

def aliases
  @aliases
end

#availabilityObject (readonly)

Returns the value of attribute availability.



27
28
29
# File 'lib/vehicles/model.rb', line 27

def availability
  @availability
end

#body_typeSymbol (readonly)

Returns the value of attribute body_type.

Returns:

  • (Symbol)


27
28
29
# File 'lib/vehicles/model.rb', line 27

def body_type
  @body_type
end

#former_idsObject (readonly)

Returns the value of attribute former_ids.



27
28
29
# File 'lib/vehicles/model.rb', line 27

def former_ids
  @former_ids
end

#global_decileObject (readonly)

Returns the value of attribute global_decile.



27
28
29
# File 'lib/vehicles/model.rb', line 27

def global_decile
  @global_decile
end

#kindSymbol (readonly)

Returns the value of attribute kind.

Returns:

  • (Symbol)


27
28
29
# File 'lib/vehicles/model.rb', line 27

def kind
  @kind
end

#makeString (readonly)

Returns the value of attribute make.

Returns:

  • (String)


27
28
29
# File 'lib/vehicles/model.rb', line 27

def make
  @make
end

#make_slugString (readonly)

Returns the value of attribute make_slug.

Returns:

  • (String)


27
28
29
# File 'lib/vehicles/model.rb', line 27

def make_slug
  @make_slug
end

#model_slugString (readonly)

The bare model slug ("golf"), used as the value in model_options.

Returns:

  • (String)


73
74
75
# File 'lib/vehicles/model.rb', line 73

def model_slug
  @model_slug
end

#nameString (readonly)

Returns the value of attribute name.

Returns:

  • (String)


27
28
29
# File 'lib/vehicles/model.rb', line 27

def name
  @name
end

#regionsObject (readonly)

Returns the value of attribute regions.



27
28
29
# File 'lib/vehicles/model.rb', line 27

def regions
  @regions
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?

Value-object equality — two models with the same slug are equal.



138
139
140
# File 'lib/vehicles/model.rb', line 138

def ==(other)
  other.is_a?(Model) && other.slug == slug
end

#available_in?(country) ⇒ Boolean

Evidence of presence in a country (see availability for semantics):

Vehicles.find("vw golf").available_in?(:nl)  # => true

Returns:

  • (Boolean)


89
90
91
# File 'lib/vehicles/model.rb', line 89

def available_in?(country)
  availability.include?(country.to_s.downcase)
end

#available_in_region?(region) ⇒ Boolean

Evidence of presence on a continent (:eu/:na/:sa/:as/:oc/:af):

Vehicles.find("perodua myvi").available_in_region?(:as)  # => true

Returns:

  • (Boolean)


95
96
97
# File 'lib/vehicles/model.rb', line 95

def available_in_region?(region)
  regions.include?(region.to_sym)
end

#car?Boolean

predicate sugar for each kind/body type: car?, suv?, hatchback?, coupe?, ...

Returns:

  • (Boolean)


87
# File 'sig/vehicles.rbs', line 87

def car?: () -> bool

#common?Boolean

Returns:

  • (Boolean)


127
# File 'lib/vehicles/model.rb', line 127

def common?  = rarity == :common

#full_nameString Also known as: to_s

"Volkswagen Golf" — the natural label for a model on its own.

Returns:

  • (String)


62
63
64
# File 'lib/vehicles/model.rb', line 62

def full_name
  "#{make} #{name}"
end

#hashObject



143
144
145
# File 'lib/vehicles/model.rb', line 143

def hash
  slug.hash
end

#hatchback?Boolean

Returns:

  • (Boolean)


89
# File 'sig/vehicles.rbs', line 89

def hatchback?: () -> bool

#image(year: nil, color: nil, size: :md) ⇒ String?

One rendered image URL, ready for an . size picks the variant (:sm/:md/:lg), color a palette slug (Vehicles.colors); an un-rendered color falls back honestly server-side rather than erroring. year is accepted for forward-compatibility but not yet served. nil without hosted data — always render a placeholder path.

Parameters:

  • year: (Integer, nil) (defaults to: nil)
  • color: (Object) (defaults to: nil)

Returns:

  • (String, nil)


172
173
174
# File 'lib/vehicles/model.rb', line 172

def image(year: nil, color: nil, size: :md)
  Vehicles.resolve(:image, self, year: year, color: color, size: size)
end

#images(color: nil) ⇒ Object

The full hosted images payload — every variant with dimensions, the rendered palette for this model, served-vs-requested color, provenance. Reach for this when one URL isn't enough (srcset, color pickers, caching whole responses). nil without hosted data.



180
181
182
# File 'lib/vehicles/model.rb', line 180

def images(color: nil)
  Vehicles.resolve(:images, self, color: color)
end

#inspectObject



147
148
149
# File 'lib/vehicles/model.rb', line 147

def inspect
  %(#<Vehicles::Model "#{full_name}" #{body_type || kind}>)
end

#popular?Boolean

Top-20% popularity across covered markets. false when unranked (nil decile) — "we don't know" must never read as "popular".

Returns:

  • (Boolean)


108
109
110
# File 'lib/vehicles/model.rb', line 108

def popular?
  !global_decile.nil? && global_decile <= 2
end

#rare?Boolean

Returns:

  • (Boolean)


128
# File 'lib/vehicles/model.rb', line 128

def rare?    = rarity == :rare

#rarityObject

Rarity tier from the popularity decile — the coarse "how much data do I want to show" knob. Deciles are the moat's measured signal; this buckets them into three names apps can filter on WITHOUT us exposing raw counts:

:common  deciles 1-3   (the names everyone knows — safe default filter)
:average deciles 4-7
:rare    deciles 8-10
:unknown unranked (catalog-only evidence — no popularity signal yet)


119
120
121
122
123
124
125
# File 'lib/vehicles/model.rb', line 119

def rarity
  return :unknown if global_decile.nil?
  return :common  if global_decile <= 3
  return :average if global_decile <= 7

  :rare
end

#segmentSymbol?

Editorial market segment, e.g. :hot_hatch, :supercar. nil without hosted data.

Returns:

  • (Symbol, nil)


163
164
165
# File 'lib/vehicles/model.rb', line 163

def segment
  Vehicles.resolve(:segment, self)
end

#slugString

Composite, stable slug: "volkswagen-golf".

Returns:

  • (String)


68
69
70
# File 'lib/vehicles/model.rb', line 68

def slug
  "#{make_slug}-#{@model_slug}"
end

#suv?Boolean

Returns:

  • (Boolean)


88
# File 'sig/vehicles.rbs', line 88

def suv?: () -> bool

#to_hHash[Symbol, untyped]

Returns:

  • (Hash[Symbol, untyped])


130
131
132
133
134
135
# File 'lib/vehicles/model.rb', line 130

def to_h
  { make: make, model: name, slug: slug, kind: kind, body_type: body_type,
    global_decile: global_decile, rarity: rarity,
    availability: availability, regions: regions, aliases: aliases,
    former_ids: former_ids }
end

#two_wheeler?Boolean

Motorcycle OR moped — the union pickers usually want ("two-wheeler section" in an insurance/marketplace form).

Returns:

  • (Boolean)


83
84
85
# File 'lib/vehicles/model.rb', line 83

def two_wheeler?
  %i[motorcycle moped].include?(@kind)
end

#yearsRange[Integer]?

Production years as a Range, e.g. 1974..2024. nil without hosted data.

Returns:

  • (Range[Integer], nil)


158
159
160
# File 'lib/vehicles/model.rb', line 158

def years
  Vehicles.resolve(:years, self)
end