Class: Pilipinas::Base
- Inherits:
-
Object
- Object
- Pilipinas::Base
- Defined in:
- lib/pilipinas/base.rb
Overview
Abstract base class for all Philippine geographic entities.
Concrete subclasses (Region, Province, City, Barangay) must implement Base.data_file to return the absolute path of their backing YAML file. Subclasses may also override Base.build to handle extra attributes.
Memory model
YAML is parsed exactly once per class per process. Cache stores three structures for each entity type:
-
:records— frozen Array of all instances in file order. -
:by_code— frozen Hash keyed by down-cased code for O(1) look-ups. -
:by_name— frozen Hash keyed by down-cased name for O(1) look-ups.
Associated sub-collections (e.g. a Region’s Provinces) are also cached so repeated calls like region.provinces are free after the first call.
Thread-safety
All shared state is managed through Cache, which uses a Mutex with double-checked locking. Entity objects are frozen value objects and are therefore inherently thread-safe.
Instance Attribute Summary collapse
-
#code ⇒ String
readonly
Geographic code (always a String, never nil).
-
#name ⇒ String
readonly
Human-readable name (always a String, never nil).
Class Method Summary collapse
-
.all ⇒ Array<Base>
Returns every record for this entity type.
-
.assoc_collection(code:, dir:) ⇒ Array<Base>
Load an associated sub-collection from a per-code YAML file.
-
.count ⇒ Integer
Total number of records.
-
.find_by(options) ⇒ Base?
Find a single record by one attribute.
-
.find_by_code(value) ⇒ Base?
Find a record whose
codematchesvalue(case-insensitive). -
.find_by_name(value) ⇒ Base?
Find a record whose
namematchesvalue(case-insensitive). -
.first ⇒ Base?
First record in collection order.
-
.last ⇒ Base?
Last record in collection order.
-
.method_missing(method_name, *args, **_kwargs) ⇒ Base?
Handles dynamic find_by_<attribute> methods.
-
.reset_cache ⇒ void
Clear all cached data.
- .respond_to_missing?(method_name, include_private = false) ⇒ Boolean
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
(also: #eql?)
Value equality based on class and code.
- #hash ⇒ Integer
-
#initialize(code:, name:) ⇒ Base
constructor
Builds a frozen, immutable value object.
- #inspect ⇒ String
- #to_s ⇒ String
Constructor Details
#initialize(code:, name:) ⇒ Base
Builds a frozen, immutable value object.
44 45 46 47 48 |
# File 'lib/pilipinas/base.rb', line 44 def initialize(code:, name:) @code = code.to_s.freeze @name = name.to_s.freeze freeze end |
Instance Attribute Details
#code ⇒ String (readonly)
Returns geographic code (always a String, never nil).
35 36 37 |
# File 'lib/pilipinas/base.rb', line 35 def code @code end |
#name ⇒ String (readonly)
Returns human-readable name (always a String, never nil).
38 39 40 |
# File 'lib/pilipinas/base.rb', line 38 def name @name end |
Class Method Details
.all ⇒ Array<Base>
Returns every record for this entity type.
The underlying YAML file is parsed at most once; subsequent calls return the same frozen Array from Cache.
84 85 86 |
# File 'lib/pilipinas/base.rb', line 84 def all index[:records] end |
.assoc_collection(code:, dir:) ⇒ Array<Base>
Load an associated sub-collection from a per-code YAML file.
Results are cached in Cache; repeated calls with the same arguments are zero-cost after the first invocation.
Instances are resolved from the canonical index rather than freshly constructed, so no duplicate objects exist in memory when both the full collection (e.g. Barangay.all) and an association (e.g. city.barangays) are accessed in the same process.
142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/pilipinas/base.rb', line 142 def assoc_collection(code:, dir:) # Pre-warm the canonical index outside the Cache.fetch block. # If the index is not yet cached, this call acquires and releases the # mutex. Once warm, find_by inside the block hits the lock-free fast # path, preventing recursive locking (deadlock) on the same Mutex. index Cache.fetch("assoc:#{name}:#{code}") do file = File.join(Pilipinas::DATA_DIR, dir.to_s, "#{code}.yml") load_yaml(file).filter_map { |h| find_by(code: h[:code]) }.freeze end end |
.count ⇒ Integer
Total number of records.
91 92 93 |
# File 'lib/pilipinas/base.rb', line 91 def count index[:records].size end |
.find_by(options) ⇒ Base?
Find a single record by one attribute.
Look-up is O(1) via a pre-built hash index and is case-insensitive.
122 123 124 125 126 127 |
# File 'lib/pilipinas/base.rb', line 122 def find_by() raise ArgumentError, 'options hash must not be empty' if .empty? attribute, value = .first find_by_attribute(attribute.to_sym, value.to_s) end |
.find_by_code(value) ⇒ Base?
Find a record whose code matches value (case-insensitive).
|
|
# File 'lib/pilipinas/base.rb', line 163
|
.find_by_name(value) ⇒ Base?
Find a record whose name matches value (case-insensitive).
|
|
# File 'lib/pilipinas/base.rb', line 168
|
.first ⇒ Base?
First record in collection order.
98 99 100 |
# File 'lib/pilipinas/base.rb', line 98 def first index[:records].first end |
.last ⇒ Base?
Last record in collection order.
105 106 107 |
# File 'lib/pilipinas/base.rb', line 105 def last index[:records].last end |
.method_missing(method_name, *args, **_kwargs) ⇒ Base?
Handles dynamic find_by_<attribute> methods.
177 178 179 180 181 182 183 184 185 186 |
# File 'lib/pilipinas/base.rb', line 177 def method_missing(method_name, *args, **_kwargs, &) match = method_name.to_s.match(/\Afind_by_(.+)\z/) return super unless match attribute = match[1].to_sym raise UnknownAttribute, "Invalid attribute '#{attribute}'." \ unless %i[code name].include?(attribute) find_by_attribute(attribute, args.first.to_s) end |
.reset_cache ⇒ void
This method returns an undefined value.
Clear all cached data.
Primarily useful between test examples to guarantee isolation.
159 160 161 |
# File 'lib/pilipinas/base.rb', line 159 def reset_cache Cache.clear end |
.respond_to_missing?(method_name, include_private = false) ⇒ Boolean
191 192 193 |
# File 'lib/pilipinas/base.rb', line 191 def respond_to_missing?(method_name, include_private = false) method_name.to_s.match?(/\Afind_by_(code|name)\z/) || super end |
Instance Method Details
#==(other) ⇒ Boolean Also known as: eql?
Value equality based on class and code.
64 65 66 |
# File 'lib/pilipinas/base.rb', line 64 def ==(other) other.is_a?(self.class) && other.code == @code end |
#hash ⇒ Integer
71 72 73 |
# File 'lib/pilipinas/base.rb', line 71 def hash [self.class, @code].hash end |
#inspect ⇒ String
56 57 58 |
# File 'lib/pilipinas/base.rb', line 56 def inspect "#<#{self.class.name} code=#{@code.inspect} name=#{@name.inspect}>" end |
#to_s ⇒ String
51 52 53 |
# File 'lib/pilipinas/base.rb', line 51 def to_s "#{self.class.name.split('::').last}(code: #{@code}, name: #{@name})" end |