pilipinas 1.1.1 → 1.1.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 989723b7dcd5133a2d2240aa124e0c5400a981f521d0904d8b2858fb71f15e9e
4
- data.tar.gz: c01c0377bf9572381844a38d1c77dbe45aed0e554076159a9964371f5f7aa851
3
+ metadata.gz: '069c53de29b6607d4f19d961e085870e474ee3f53e5a9950d5564211d05b7286'
4
+ data.tar.gz: fbaa56417882627d836b445625b65fab4cd2077fc4f901019dc2c841b38f6c4f
5
5
  SHA512:
6
- metadata.gz: b2589f13688bd95ad3ad176d9dccc2412a0df9a3484a173129e3dc66e7f78dad67ea1f7920f7bee9136f412b5c6221a7776b2f7a682ef1e0fdb55d2ffd1cd307
7
- data.tar.gz: 73bc97b32dcaa44f0a9181e63d450770535e12f73254fcea500551a46bb788a0dfe2bf9ed7de897a84ab1c22f0a19545d050e2b7a1c8cd76c7d3515a55b60d0d
6
+ metadata.gz: 9bf493ab7a8d154f11c17b992d93abb8695e3c92b9286a8be3c14d0aad1d95568f2abb5467cbb86416a082cfda21eda542103cffd0e039aae32bbe34d9b3575f
7
+ data.tar.gz: a11da75e206b70b6c1fb8cee5f0e3f132ddaa74b10beb3a51e946271f415367511cddce3f6a9c307ba8eb46ba7fcd64f8094c5ca2fbf5604de3c5c86ee4cc395
data/CHANGELOG.md CHANGED
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.1.3] - 2026-06-13
11
+
12
+ ### Fixed
13
+
14
+ - `rake pilipinas:load` now upserts ActiveRecord seed rows by `location_id` instead of `code`, matching the stable identifier from `lib/data/pilipinas_data.yml`.
15
+ - `rails generate pilipinas:code_indexes` now generates a migration that adds unique `location_id` indexes to all four `pilipinas_*` tables, matching the loader's upsert conflict target.
16
+
17
+ ### Changed
18
+
19
+ - Updated loader upgrade guidance and README wording to reference the required `location_id` unique indexes.
20
+
21
+ ### Tests
22
+
23
+ - Added regression coverage that asserts `Loader.bulk_insert` uses `location_id` as the `upsert_all` unique key and reports the matching missing-index guidance.
24
+
25
+ ---
26
+
27
+ ## [1.1.2] - 2026-06-12
28
+
29
+ ### Fixed
30
+
31
+ - `rake pilipinas:load` now seeds the ActiveRecord tables from `lib/data/pilipinas_data.yml`, the complete bundled data source. Previously the loader used the compact file-backed YAML files, so database rows were populated with only `code` and `name` while columns such as `location_id`, `parent_id`, `lft`, `rgt`, coordinates, and classification fields remained `NULL`.
32
+
33
+ - Re-running `rake pilipinas:load` now removes stale compact rows created by earlier loader versions. This fixes cases where old rows such as provinces with `location_id: nil` remained in the database because their compact internal codes did not match the full PSA-style codes used by `pilipinas_data.yml`.
34
+
35
+ ### Changed
36
+
37
+ - Improved loader memory behavior by transforming rows directly from the parsed full-data table and keeping only the current insert batch in memory.
38
+
39
+ ### Tests
40
+
41
+ - Added regression coverage for complete-column seeding, stale compact-row cleanup, legacy seed behavior, and exact batch-boundary inserts.
42
+
43
+ ---
44
+
45
+ ## [1.1.1] - 2026-06-11
46
+
47
+ ### Added
48
+
49
+ - `rails generate pilipinas:code_indexes` — new migration generator that adds `UNIQUE` indexes on the `code` column to all four `pilipinas_*` tables. Run this if your database was created with a pre-1.0 migration and `rake pilipinas:load` raises `ArgumentError: No unique index found for code`.
50
+
51
+ ### Fixed
52
+
53
+ - `Loader.bulk_insert` now rescues the bare `ArgumentError` raised by `upsert_all` when the unique index on `code` is absent, and re-raises it as a descriptive `Pilipinas::Error` that tells the user exactly which commands to run (`rails generate pilipinas:code_indexes && rails db:migrate`). Previously the error surfaced as an unguided `ArgumentError` from deep inside ActiveRecord.
54
+
55
+ ---
56
+
10
57
  ## [1.1.0] - 2026-05-13
11
58
 
12
59
  ### Added
@@ -18,19 +65,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
65
  self.enforce_readonly = false
19
66
  end
20
67
  ```
21
- - `lib/pilipinas/testing/rspec.rb` — a ready-made RSpec helper that disables
22
- the read-only guard on all four DB models for the entire test suite.
23
- Require it once in `rails_helper.rb`:
68
+ - `lib/pilipinas/testing/rspec.rb` — a ready-made RSpec helper that disables the read-only guard on all four DB models for the entire test suite. Require it once in `rails_helper.rb`:
24
69
  ```ruby
25
70
  require 'pilipinas/testing/rspec'
26
71
  ```
27
- - Spec coverage for `StaticRecord` — 8 examples covering default behaviour,
28
- `enforce_readonly = false`, subclass inheritance, and class-level isolation.
72
+ - Spec coverage for `StaticRecord` — 8 examples covering default behaviour, `enforce_readonly = false`, subclass inheritance, and class-level isolation.
29
73
 
30
74
  ### Changed
31
75
 
32
- - `readonly?` now gates on `self.class.enforce_readonly && !new_record?` instead
33
- of unconditionally returning `!new_record?`.
76
+ - `readonly?` now gates on `self.class.enforce_readonly && !new_record?` instead of unconditionally returning `!new_record?`.
34
77
 
35
78
  ---
36
79
 
@@ -40,26 +83,20 @@ Complete rewrite of the gem. Zero runtime dependencies.
40
83
 
41
84
  ### Added
42
85
 
43
- - In-memory layer with thread-safe `Pilipinas::Cache` (Mutex + double-checked
44
- locking) and O(1) look-ups via separate code/name hash indices.
86
+ - In-memory layer with thread-safe `Pilipinas::Cache` (Mutex + double-checked locking) and O(1) look-ups via separate code/name hash indices.
45
87
  - Immutable value objects — every entity instance is frozen.
46
- - `Pilipinas::Region`, `Province`, `City`, `Barangay` with `.all`, `.count`,
47
- `.first`, `.last`, `.find_by`, `.find_by_code`, `.find_by_name`.
88
+ - `Pilipinas::Region`, `Province`, `City`, `Barangay` with `.all`, `.count`, `.first`, `.last`, `.find_by`, `.find_by_code`, `.find_by_name`.
48
89
  - Hierarchy traversal: `region.provinces`, `province.cities`, `city.barangays`.
49
- - Optional ActiveRecord layer (`Pilipinas::Db::*`) with memory-efficient scopes
50
- (`.lite`, `.by_code`, `.by_name`, `.find_lite_by_code`, `.find_lite_by_name`).
51
- - `StaticRecord` concern: disables STI, adds lean SELECT scopes, enforces
52
- read-only on persisted records.
53
- - Migration generator (`rails generate pilipinas:migration`) and
54
- `rake pilipinas:load` seeding task.
90
+ - Optional ActiveRecord layer (`Pilipinas::Db::*`) with memory-efficient scopes (`.lite`, `.by_code`, `.by_name`, `.find_lite_by_code`, `.find_lite_by_name`).
91
+ - `StaticRecord` concern: disables STI, adds lean SELECT scopes, enforces read-only on persisted records.
92
+ - Migration generator (`rails generate pilipinas:migration`) and `rake pilipinas:load` seeding task.
55
93
  - Full RSpec suite (57 examples).
56
94
  - GitHub Actions CI pipeline.
57
95
 
58
96
  ### Changed
59
97
 
60
98
  - Requires Ruby ≥ 3.4 (developed against Ruby 4.0).
61
- - Removed all runtime gem dependencies (previously depended on `yaml_db` and
62
- others).
99
+ - Removed all runtime gem dependencies (previously depended on `yaml_db` and others).
63
100
 
64
101
  ---
65
102
 
@@ -74,6 +111,8 @@ Complete rewrite of the gem. Zero runtime dependencies.
74
111
  - Rails generator for migrations.
75
112
  - Railtie for automatic Rake task loading in Rails apps.
76
113
 
114
+ [1.1.2]: https://github.com/denmarkmeralpis/pilipinas/compare/v1.1.1...v1.1.2
115
+ [1.1.1]: https://github.com/denmarkmeralpis/pilipinas/compare/v1.1.0...v1.1.1
77
116
  [1.1.0]: https://github.com/denmarkmeralpis/pilipinas/compare/v1.0.0...v1.1.0
78
117
  [1.0.0]: https://github.com/denmarkmeralpis/pilipinas/compare/v0.0.1...v1.0.0
79
118
  [0.0.1]: https://github.com/denmarkmeralpis/pilipinas/releases/tag/v0.0.1
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pilipinas (1.1.1)
4
+ pilipinas (1.1.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -76,7 +76,7 @@ GEM
76
76
  prism (>= 1.3.0)
77
77
  rdoc (>= 4.0.0)
78
78
  reline (>= 0.4.2)
79
- json (2.19.8)
79
+ json (2.19.9)
80
80
  language_server-protocol (3.17.0.5)
81
81
  lint_roller (1.1.0)
82
82
  logger (1.7.0)
@@ -280,7 +280,7 @@ CHECKSUMS
280
280
  i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
281
281
  io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
282
282
  irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
283
- json (2.19.8) sha256=6354310fd76ef69b87d5bd1f38b40d730613baf90b6803d2d0a48f618d32dfaa
283
+ json (2.19.9) sha256=9b9025b7cdddafa38d316eca0b2358488e42d417045c1b90d216a9fefe46b79a
284
284
  language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
285
285
  lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
286
286
  logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
@@ -296,7 +296,7 @@ CHECKSUMS
296
296
  nokogiri (1.19.3-x86_64-linux-musl) sha256=248c906d2166eca5efb56d52fdee5f9a1f51d69a72e2b64fdac647b4ce39ea3f
297
297
  parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
298
298
  parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
299
- pilipinas (1.1.1)
299
+ pilipinas (1.1.3)
300
300
  pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
301
301
  prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
302
302
  prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
data/README.md CHANGED
@@ -177,7 +177,7 @@ rake pilipinas:load
177
177
  > **Upgrading from an older version?**
178
178
  >
179
179
  > If `rake pilipinas:load` raises
180
- > `Pilipinas::Error: pilipinas:load requires a unique index on the code column`,
180
+ > `Pilipinas::Error: pilipinas:load requires a unique index on the location_id column`,
181
181
  > your database was created with an earlier migration that did not include those
182
182
  > indexes. Add them with the dedicated generator:
183
183
  >
@@ -4,13 +4,13 @@ require 'rails/generators/base'
4
4
  require 'rails/generators/active_record'
5
5
 
6
6
  module Pilipinas
7
- # Rails generator that adds unique indexes on the +code+ column to all four
8
- # pilipinas_* tables.
7
+ # Rails generator that adds unique indexes on the +location_id+ column to all
8
+ # four pilipinas_* tables.
9
9
  #
10
10
  # Run this if your database was created with an older version of the gem
11
11
  # that did not include these indexes, and +rake pilipinas:load+ raises:
12
12
  #
13
- # ArgumentError: No unique index found for code
13
+ # ArgumentError: No unique index found for location_id
14
14
  #
15
15
  # @example
16
16
  # rails generate pilipinas:code_indexes
@@ -23,7 +23,7 @@ module Pilipinas
23
23
 
24
24
  def generate_migration
25
25
  migration_template 'templates/add_pilipinas_code_indexes.rb',
26
- 'db/migrate/add_pilipinas_code_indexes.rb'
26
+ 'db/migrate/add_pilipinas_location_id_indexes.rb'
27
27
  end
28
28
 
29
29
  # @param _dir [String] unused (required by the interface)
@@ -1,8 +1,8 @@
1
- class AddPilipinasCodeIndexes < ActiveRecord::Migration<%= migration_version %>
1
+ class AddPilipinasLocationIdIndexes < ActiveRecord::Migration<%= migration_version %>
2
2
  def change
3
- add_index :pilipinas_regions, :code, unique: true, if_not_exists: true
4
- add_index :pilipinas_provinces, :code, unique: true, if_not_exists: true
5
- add_index :pilipinas_cities, :code, unique: true, if_not_exists: true
6
- add_index :pilipinas_barangays, :code, unique: true, if_not_exists: true
3
+ add_index :pilipinas_regions, :location_id, unique: true, if_not_exists: true
4
+ add_index :pilipinas_provinces, :location_id, unique: true, if_not_exists: true
5
+ add_index :pilipinas_cities, :location_id, unique: true, if_not_exists: true
6
+ add_index :pilipinas_barangays, :location_id, unique: true, if_not_exists: true
7
7
  end
8
8
  end
@@ -9,8 +9,9 @@ module Pilipinas
9
9
  # * **Idempotent** — uses +upsert_all+ (Rails 6.1+) so re-running the Rake
10
10
  # task is safe. Falls back to +insert_all+ (Rails 6.0) or individual
11
11
  # +create!+ calls on older versions.
12
- # * **Memory-efficient** — rows are inserted in batches of {BATCH_SIZE}
13
- # (default 500) so the process never holds a 42 k-row Array in memory.
12
+ # * **Memory-aware** — rows are transformed and inserted in batches of
13
+ # {BATCH_SIZE} (default 500) so the process never holds a full
14
+ # ActiveRecord insert payload in memory.
14
15
  # * **Atomic** — all four tables are seeded inside a single transaction; a
15
16
  # failure rolls back everything, leaving no partial data.
16
17
  #
@@ -21,22 +22,109 @@ module Pilipinas
21
22
  # Number of rows inserted per SQL statement.
22
23
  # 500 balances SQL statement size against the number of round-trips.
23
24
  BATCH_SIZE = 500
25
+ FULL_DATA_FILE = 'pilipinas_data.yml'
26
+ LOCATION_TABLE = 'pilipinas_locations'
27
+
28
+ FULL_DATA_SEEDS = [
29
+ ['Region', 'Locations::Region', %w[location_id lft rgt code name longitude latitude]],
30
+ ['Province', 'Locations::Province', %w[location_id parent_id lft rgt code name longitude latitude]],
31
+ [
32
+ 'City',
33
+ 'Locations::Town',
34
+ %w[location_id parent_id lft rgt code name city income_class urban_rural district longitude latitude]
35
+ ],
36
+ ['Barangay', 'Locations::Barangay', %w[location_id parent_id lft rgt code name urban_rural]]
37
+ ].freeze
38
+ private_constant :FULL_DATA_FILE, :LOCATION_TABLE, :FULL_DATA_SEEDS
24
39
 
25
40
  class << self
26
41
  # Seed all four geographic tables inside a single transaction.
27
42
  #
28
43
  # @return [void]
29
44
  def run
30
- ActiveRecord::Base.transaction do
31
- seed(Db::Region, 'regions.yml')
32
- seed(Db::Province, 'provinces.yml')
33
- seed(Db::City, 'cities.yml')
34
- seed(Db::Barangay, 'barangays.yml')
45
+ records = nil
46
+ column_indexes, records = full_location_table
47
+
48
+ ActiveRecord::Base.uncached do
49
+ ActiveRecord::Base.transaction do
50
+ FULL_DATA_SEEDS.each do |model_name, type, attributes|
51
+ model = Db.const_get(model_name)
52
+ delete_stale_compact_rows(model)
53
+ seed_full_data(model, records, column_indexes, type, attributes)
54
+ end
55
+ end
35
56
  end
57
+ ensure
58
+ records&.clear
59
+ clear_query_cache
36
60
  end
37
61
 
38
62
  private
39
63
 
64
+ # Load the full Rails fixture-style location dump bundled with the gem.
65
+ #
66
+ # The older per-table YAML files only contain +code+ and +name+ for the
67
+ # file-backed API. Database seeding needs the complete dump so lft/rgt,
68
+ # parent links, coordinates, and classification fields are populated.
69
+ #
70
+ # @return [Array(Hash, Array<Array>)]
71
+ def full_location_table
72
+ data = Psych.load_file(File.join(DATA_DIR, FULL_DATA_FILE)) || {}
73
+ table = data.fetch(LOCATION_TABLE)
74
+ columns = table.fetch('columns').map(&:to_s)
75
+
76
+ [columns.each_with_index.to_h, table.fetch('records')]
77
+ end
78
+
79
+ # Insert or update rows for one split table from the full location dump.
80
+ #
81
+ # @param model [Class] ActiveRecord model class
82
+ # @param records [Array<Array>] full location dump rows
83
+ # @param column_indexes [Hash] source column names mapped to row indexes
84
+ # @param type [String] source STI type to select
85
+ # @param attributes [Array<String>] destination table attributes
86
+ # @return [void]
87
+ def seed_full_data(model, records, column_indexes, type, attributes)
88
+ type_index = column_indexes.fetch('type')
89
+ attribute_indexes = attributes.to_h { |attribute| [attribute, column_indexes.fetch(attribute)] }
90
+ now = timestamp
91
+ batch = []
92
+
93
+ records.each do |record|
94
+ next unless record[type_index] == type
95
+
96
+ batch << full_data_attributes(record, attribute_indexes, now)
97
+
98
+ next if batch.size < BATCH_SIZE
99
+
100
+ bulk_insert(model, batch)
101
+ batch.clear
102
+ end
103
+
104
+ bulk_insert(model, batch) unless batch.empty?
105
+ batch.clear
106
+ end
107
+
108
+ # @param record [Array] full location dump row
109
+ # @param attribute_indexes [Hash] destination attributes mapped to row indexes
110
+ # @param now [Time] timestamp shared by the current table seed
111
+ # @return [Hash]
112
+ def full_data_attributes(record, attribute_indexes, now)
113
+ attribute_indexes.to_h { |attribute, index| [attribute, record[index]] }
114
+ .merge('created_at' => now, 'updated_at' => now)
115
+ end
116
+
117
+ # Older loader versions seeded compact file-backed rows with only +code+
118
+ # and +name+. Those rows cannot be upserted into the full-data records
119
+ # because their +code+ values differ from the PSA codes in
120
+ # +pilipinas_data.yml+, so remove them before inserting the canonical set.
121
+ #
122
+ # @param model [Class] ActiveRecord model class
123
+ # @return [void]
124
+ def delete_stale_compact_rows(model)
125
+ model.where(location_id: nil).delete_all
126
+ end
127
+
40
128
  # Insert or update rows for one table from a YAML file.
41
129
  #
42
130
  # @param model [Class] ActiveRecord model class
@@ -54,16 +142,17 @@ module Pilipinas
54
142
  records.each_slice(BATCH_SIZE) do |slice|
55
143
  batch = slice.map { |r| r.transform_keys(&:to_s).merge('created_at' => now, 'updated_at' => now) }
56
144
  bulk_insert(model, batch)
145
+ batch.clear
57
146
  end
58
147
  end
59
148
 
60
149
  # Perform the most capable bulk-insert available for this AR version.
61
150
  #
62
151
  # +upsert_all+ (Rails 6.1+) is idempotent — it updates existing rows
63
- # matched by the unique index on +code+, which is present in every YAML
64
- # record. Using +code+ (not +location_id+) as the conflict column is
65
- # essential because +location_id+ is nullable and NULL != NULL in SQL —
66
- # a unique index on a nullable column cannot reliably detect conflicts.
152
+ # matched by the unique index on +location_id+, the stable identifier in
153
+ # the full location dump. Compact rows from older loader versions have a
154
+ # NULL +location_id+ and are deleted before the canonical full-data rows
155
+ # are inserted.
67
156
  #
68
157
  # +insert_all+ (Rails 6.0) silently skips conflicts.
69
158
  # Legacy path: individual +create!+ calls (raises on conflict).
@@ -74,10 +163,10 @@ module Pilipinas
74
163
  def bulk_insert(model, batch)
75
164
  if model.respond_to?(:upsert_all)
76
165
  begin
77
- model.upsert_all(batch, unique_by: :code)
166
+ model.upsert_all(batch, unique_by: :location_id)
78
167
  rescue ArgumentError
79
168
  raise Pilipinas::Error,
80
- 'pilipinas:load requires a unique index on the `code` column, ' \
169
+ 'pilipinas:load requires a unique index on the `location_id` column, ' \
81
170
  "which is missing from #{model.table_name}. " \
82
171
  'Your database was likely created with an older version of the gem. ' \
83
172
  "Run the following to add the missing indexes and retry:\n\n " \
@@ -89,6 +178,17 @@ module Pilipinas
89
178
  else
90
179
  batch.each { |attrs| model.unscoped.create!(attrs) }
91
180
  end
181
+ ensure
182
+ clear_query_cache
183
+ end
184
+
185
+ # Clear any per-connection query-cache state so a long-running Rails
186
+ # process does not retain loader queries or bind values between batches.
187
+ #
188
+ # @return [void]
189
+ def clear_query_cache
190
+ connection = ActiveRecord::Base.connection
191
+ connection.clear_query_cache if connection.respond_to?(:clear_query_cache)
92
192
  end
93
193
 
94
194
  # @return [Time] current UTC time, compatible with or without ActiveSupport
@@ -4,5 +4,5 @@ module Pilipinas
4
4
  # Semantic version of the gem.
5
5
  #
6
6
  # Follows {https://semver.org Semantic Versioning}: MAJOR.MINOR.PATCH.
7
- VERSION = '1.1.1'
7
+ VERSION = '1.1.3'
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pilipinas
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nujian Den Mark Meralpis