glib-web 6.2.0 → 6.3.2

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: 52b6b62180ee0cd668652772110774bc915d8789f1fd65e9ef22fadd781977a4
4
- data.tar.gz: 3d47de6a06859cce937834478bbef460e2cac999bbfbdf72cef10a781399e68f
3
+ metadata.gz: 4f55e891cbc4a9325dbf8fe235dcb593982686ec8e36f62d86b6aea853e83f22
4
+ data.tar.gz: 6299bcaf264a60d4a604dc6dc7f44f45f2671f2827b8a6fe26f266979d82dbf5
5
5
  SHA512:
6
- metadata.gz: c6537178f38fa7703d8c3e0436f1c3d4ac1171c340bcda27d18aad936a7ff40e329488c0cbf618dd0974e4e7a7926a3b209eb3c88f29447416b1000db9fee37d
7
- data.tar.gz: c532e338ce8890c52a5efc9c07c734fd99f4adba46f244f5a5c9c160b188b7630d0d18b080ad599a528ad9bb9a68fc5697c84137ec5383524308fc58b53c512b
6
+ metadata.gz: 368a1c82a9ee9f539b38d4e3396b0fa14cf449fef03d50862239b549d3d9593a19b8961f41e521d8a627bf58530b63b5ac6f1a89310bebe1d0b5a5e61d811361
7
+ data.tar.gz: b8fe1cd9c3256ad9d3d7ea1cafb5adef1ec80e984ec7678edfd5e6aaec585babdc96aaddcef189003269ab9a33b89b00aae5fe5ac1c4988458de0ec3fa33fec4
@@ -0,0 +1,73 @@
1
+ module Glib
2
+ module FormStepper
3
+ # A session-backed multi-step wizard: per-step answers accumulate in the
4
+ # session and nothing is persisted until the final step commits. Fits
5
+ # pre-account flows and any wizard where an abandoned run should simply
6
+ # disappear.
7
+ #
8
+ # The including controller declares the flow and its session bucket:
9
+ #
10
+ # class ApplyController < ApplicationController
11
+ # include Glib::FormStepper::Controller
12
+ #
13
+ # STEPS = [
14
+ # Glib::FormStepper::Step.new(key: :program, title: "Program", position: 1),
15
+ # Glib::FormStepper::Step.new(key: :auditor, title: "Auditor", position: 2)
16
+ # ].freeze
17
+ # SESSION_KEY = "apply_application".freeze
18
+ # end
19
+ #
20
+ # The current step is resolved from `params[:id]`, defaulting to the first step.
21
+ module Controller
22
+ extend ActiveSupport::Concern
23
+
24
+ included do
25
+ helper_method :current_step, :next_step, :prev_step, :step_data, :steps
26
+ end
27
+
28
+ def steps
29
+ self.class::STEPS
30
+ end
31
+
32
+ def current_step
33
+ @current_step ||= find_step(params[:id] || steps.first.key)
34
+ end
35
+
36
+ def next_step
37
+ idx = steps.index(current_step)
38
+ idx && steps[idx + 1]
39
+ end
40
+
41
+ def prev_step
42
+ idx = steps.index(current_step)
43
+ idx&.positive? ? steps[idx - 1] : nil
44
+ end
45
+
46
+ # Every answer gathered so far, one hash per step key.
47
+ def session_data
48
+ session[self.class::SESSION_KEY] ||= {}
49
+ end
50
+
51
+ # The current step's answers — what its form reads to prefill itself.
52
+ def step_data
53
+ session_data[current_step.key.to_s] || {}
54
+ end
55
+
56
+ # Merge (not replace) so re-submitting a step updates rather than clobbers.
57
+ def save_step_data(data)
58
+ key = current_step.key.to_s
59
+ session_data[key] = (session_data[key] || {}).merge(data)
60
+ end
61
+
62
+ def clear_session_data
63
+ session.delete(self.class::SESSION_KEY)
64
+ end
65
+
66
+ private
67
+ def find_step(key)
68
+ steps.find { |step| step.key == key.to_sym } ||
69
+ raise(ArgumentError, "Unknown step: #{key}")
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,12 @@
1
+ module Glib
2
+ module FormStepper
3
+ # One step in a session-backed wizard: just its identity and label. `key` is
4
+ # coerced to a symbol so a step declared with either a symbol or string, and a
5
+ # `params[:id]` that always arrives as a string, resolve to the same step.
6
+ Step = Data.define(:key, :title, :position) do
7
+ def initialize(key:, title:, position:)
8
+ super(key: key.to_sym, title: title, position: position)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ require 'countries' # ISO 3166 data (ISO3166::Country)
2
+
3
+ module Glib
4
+ # ISO 3166 country data for country dropdowns.
5
+ #
6
+ # Backed entirely by the `countries` gem, so there is no table to migrate or
7
+ # seed, and renames (Türkiye, Czechia) arrive with a `bundle update`. Store
8
+ # the alpha-2 code rather than the name, so a rename never invalidates
9
+ # existing data.
10
+ module CountryList
11
+ class << self
12
+ # `common_name` is what people actually call the place ("South Korea",
13
+ # "United Kingdom") rather than the formal ISO name ("Korea (Republic of)").
14
+ def options
15
+ @options ||= ISO3166::Country.all
16
+ .map { |country| { value: country.alpha2, text: country.common_name || country.iso_short_name } }
17
+ .sort_by { |option| option[:text] }
18
+ .freeze
19
+ end
20
+
21
+ def codes
22
+ @codes ||= options.pluck(:value).freeze
23
+ end
24
+
25
+ def name_for(code)
26
+ return if code.blank?
27
+
28
+ country = ISO3166::Country[code]
29
+ country && (country.common_name || country.iso_short_name)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ require 'countries' # ISO 3166-2 subdivision data (ISO3166::Country)
2
+
3
+ module Glib
4
+ # ISO 3166-2 subdivision (state/province/territory) data for state dropdowns.
5
+ #
6
+ # The companion to Glib::CountryList: backed entirely by the `countries` gem,
7
+ # so there is no table to migrate or seed, and renames arrive with a
8
+ # `bundle update`. Store the subdivision code (e.g. "NSW") rather than the
9
+ # name, so a rename never invalidates existing data. The code is only unique
10
+ # within a country, so always store it alongside the country code.
11
+ #
12
+ # Not every country has subdivisions in the ISO data (e.g. Vatican City); for
13
+ # those `options_for` returns `[]` and the UI should fall back to a plain text
14
+ # field or omit the state input entirely.
15
+ module StateList
16
+ class << self
17
+ # Options for a single country's state dropdown, sorted by display name.
18
+ # `country_code` is an alpha-2 code, matching what Glib::CountryList stores.
19
+ def options_for(country_code)
20
+ return [] if country_code.blank?
21
+
22
+ (@options_by_country ||= {})[country_code.to_s.upcase] ||= build_options(country_code)
23
+ end
24
+
25
+ def codes_for(country_code)
26
+ options_for(country_code).pluck(:value)
27
+ end
28
+
29
+ def name_for(country_code, state_code)
30
+ return if country_code.blank? || state_code.blank?
31
+
32
+ country = ISO3166::Country[country_code]
33
+ subdivision = country&.subdivisions&.dig(state_code.to_s.upcase)
34
+ subdivision&.name
35
+ end
36
+
37
+ private
38
+ def build_options(country_code)
39
+ country = ISO3166::Country[country_code]
40
+ return [] if country.nil?
41
+
42
+ country.subdivisions
43
+ .map { |code, subdivision| { value: code, text: subdivision.name } }
44
+ .sort_by { |option| option[:text] }
45
+ .freeze
46
+ end
47
+ end
48
+ end
49
+ end
@@ -24,7 +24,11 @@ markdown = '## Emphasis' + "\n" +
24
24
  It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
25
25
  It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages,
26
26
  and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
27
- "
27
+ ".gsub("\n", " \n")
28
+ # The gsub turns each bare newline into a markdown hard break: the renderer
29
+ # uses standard (breaks: false) semantics, under which these hand-wrapped
30
+ # lines would otherwise reflow into one block — and this dialog exists to
31
+ # demonstrate scrolling, so it needs its ~20 visible lines.
28
32
 
29
33
 
30
34
  options = {}
@@ -367,5 +367,8 @@ page.body childViews: ->(body) do
367
367
  # end
368
368
  # )
369
369
  # end
370
+ #
371
+
372
+ res.button text: 'windows/reload', onClick: ->(action) { action.windows_reload }
370
373
  end
371
374
  end
data/lib/glib/engine.rb CHANGED
@@ -1,7 +1,139 @@
1
+ require 'active_snapshot/models/snapshot'
2
+
1
3
  module Glib
2
4
  module Web
3
5
  class Engine < ::Rails::Engine
4
6
  # isolate_namespace Glib::Web
7
+
8
+ # Extends ActiveSnapshot::Snapshot (the snapshot record class) with a structured-diff
9
+ # reader so consumers don't have to re-parse raw Hashdiff tuples from the `diff` column.
10
+ # Runs as an engine initializer (not at file-load time) because `scope` and other AR
11
+ # class methods aren't available until ActiveRecord is fully loaded.
12
+ initializer 'glib.active_snapshot_ext' do
13
+ ActiveSnapshot::Snapshot.class_eval do
14
+ unless const_defined?(:Change)
15
+ const_set(:Change, Struct.new(:index, :action, :from, :to))
16
+ end
17
+
18
+ # Mirror `Glib::ApplicationRecord.created_desc` so call sites can use the codebase-wide
19
+ # convention instead of `order(created_at: :desc)`.
20
+ unless respond_to?(:created_desc)
21
+ scope :created_desc, -> { order(created_at: :desc) }
22
+ end
23
+
24
+ # Parses the stored diff into a flat hash of Change structs:
25
+ # { 'item' => [Change, ...], 'association_name' => [Change, ...], ... }
26
+ # Returns an empty structure if no diff exists (e.g. the first snapshot).
27
+ # Reads from a dedicated `diff` column if the table has one (app override),
28
+ # otherwise falls back to `metadata['diff']` (glib-web's default storage).
29
+ #
30
+ # JSONB round-trips every value as a string, so datetime/date column values arrive as
31
+ # ISO8601 strings instead of Time/Date objects. The cast_* methods resolve the column
32
+ # type from the live model schema and cast the value back so consumers (e.g. timeline
33
+ # helpers) receive typed values. Casts are guarded:
34
+ # - Deleted column → type_for_attribute returns a default Value type (type=nil) → skipped
35
+ # - Deleted model → safe_constantize returns nil → skipped
36
+ # - Deleted association → reflect_on_association returns nil → skipped
37
+ # - Changed column type → type.cast returns nil for unparseable values (no crash)
38
+ # - Exotic type failure → rescue returns the raw value (defense-in-depth)
39
+ def changes_from_prev_version
40
+ diff = self[:diff] || metadata&.dig('diff')
41
+ return { 'item' => [] } if diff.nil?
42
+
43
+ root = item_type.to_s.safe_constantize
44
+
45
+ result = {
46
+ 'item' => (diff['item'] || []).map { |data| cast_item_change(build_change(data), root) }
47
+ }
48
+
49
+ (diff['associations'] || {}).each do |association, tuples|
50
+ assoc_model = root&.reflect_on_association(association.to_s)&.klass
51
+ result[association.to_s] = tuples.map { |data| cast_association_change(build_change(data), assoc_model) }
52
+ end
53
+
54
+ result
55
+ end
56
+
57
+ # Converts a single raw Hashdiff tuple (e.g. ["~", "field", old, new]) into a Change struct.
58
+ # For item-level tuples, `data[1]` is the column name (e.g. "title").
59
+ # For association tuples, `data[1]` is a numeric path like "[0]" (position in the collection).
60
+ # Both are captured as `index` so consumers can label rows via humanize.
61
+ def build_change(data)
62
+ actions = {
63
+ '+' => 'create',
64
+ '-' => 'destroy',
65
+ '~' => 'update'
66
+ }.freeze
67
+
68
+ change = ActiveSnapshot::Snapshot.const_get(:Change).new
69
+ change.action = actions[data[0].to_s]
70
+
71
+ path = data[1].to_s
72
+ bracket_match = path.match(/\[(?<index>[^\]]+)\]/)
73
+ change.index = bracket_match ? bracket_match[:index] : path
74
+
75
+ payload = data[2]
76
+ case change.action
77
+ when 'create'
78
+ change.from = nil
79
+ change.to = payload
80
+ when 'destroy'
81
+ change.from = payload
82
+ change.to = nil
83
+ when 'update'
84
+ change.from = payload
85
+ change.to = data[3]
86
+ else
87
+ raise "Unexpected Hashdiff op: #{data[0].inspect}"
88
+ end
89
+
90
+ change
91
+ end
92
+
93
+ private
94
+ # Item-level change: from/to are scalar column values. Cast each to the column's real type.
95
+ def cast_item_change(change, root)
96
+ change.from = cast_column_value(root, change.index, change.from)
97
+ change.to = cast_column_value(root, change.index, change.to)
98
+ change
99
+ end
100
+
101
+ # Association-level change: from/to are attribute hashes. Cast each attribute by the
102
+ # association model's column types.
103
+ def cast_association_change(change, assoc_model)
104
+ change.from = cast_attributes(assoc_model, change.from)
105
+ change.to = cast_attributes(assoc_model, change.to)
106
+ change
107
+ end
108
+
109
+ # Cast a single value when `column` is a date/time column on `model`; otherwise return it
110
+ # unchanged. Safeguards for schema drift after the snapshot was taken:
111
+ # - Deleted column → type_for_attribute returns a default type (type=nil) → skipped
112
+ # - Changed column type → type.cast may return nil for the old value → return raw value
113
+ # - Exotic failure → rescue returns raw value
114
+ # In all fallback cases the raw value is returned so the timeline never loses data.
115
+ def cast_column_value(model, column, value)
116
+ return value if value.nil? || model.nil?
117
+
118
+ type = model.type_for_attribute(column.to_s)
119
+ return value unless %i[datetime date time].include?(type.type)
120
+
121
+ casted = type.cast(value)
122
+ # If the column type changed after the snapshot was taken, the old value may not
123
+ # be parseable as the new type — type.cast returns nil silently. Return the raw
124
+ # value so the timeline still shows the original data instead of "<not set>".
125
+ casted.nil? ? value : casted
126
+ rescue StandardError
127
+ value
128
+ end
129
+
130
+ def cast_attributes(model, attrs)
131
+ return attrs unless model && attrs.is_a?(Hash)
132
+
133
+ attrs.to_h { |column, value| [column, cast_column_value(model, column, value)] }
134
+ end
135
+ end
136
+ end
5
137
  end
6
138
  end
7
139
  end
data/lib/glib/snapshot.rb CHANGED
@@ -95,8 +95,20 @@ module Glib
95
95
 
96
96
  raise 'unknown action' if !known_actions.include?(action.to_s)
97
97
 
98
+ # Cheap pre-check WITHOUT a lock — the overwhelmingly common case is "nothing
99
+ # changed". same_as_before? is a pure read (it re-queries snapshot_prev and
100
+ # associations each call) and cannot produce a false "same", so returning nil
101
+ # here is always safe and skips the row lock entirely.
102
+ return nil if same_as_before?
103
+
98
104
  result = nil
99
105
  with_lock do
106
+ # Re-check under the lock: another writer may have snapshotted this exact
107
+ # change between the unlocked pre-check and here. NOT optional — without it,
108
+ # every concurrent writer that passed the pre-check would compute
109
+ # last_version + 1 and insert a duplicate snapshot of the same change.
110
+ next if same_as_before?
111
+
100
112
  version = last_version + 1
101
113
  metadata = {
102
114
  action: action,
@@ -114,11 +126,8 @@ module Glib
114
126
  snapshot_obj.delete(:user)
115
127
  end
116
128
 
117
- # dont create version if same as before
118
- if !same_as_before?
119
- result = create_snapshot!(**snapshot_obj)
120
- remove_old_snapshot if result.present?
121
- end
129
+ result = create_snapshot!(**snapshot_obj)
130
+ remove_old_snapshot if result.present?
122
131
  end
123
132
  result
124
133
  end
@@ -151,8 +160,10 @@ module Glib
151
160
  associations = {}
152
161
  end
153
162
 
163
+ item_attrs = normalize_attributes(item.attributes.except(*ignore_keys))
164
+ current_attrs = normalize_attributes(attributes.except(*ignore_keys))
154
165
  obj = {
155
- 'item' => ::Hashdiff.diff(item.attributes.except(*ignore_keys), attributes.except(*ignore_keys))
166
+ 'item' => ::Hashdiff.diff(item_attrs, current_attrs)
156
167
  }
157
168
 
158
169
  obj['associations'] = associations_for_snapshot.reduce({}) do |prev, curr|
@@ -183,22 +194,29 @@ module Glib
183
194
  now = association_records
184
195
 
185
196
  if before.blank?
186
- prev.merge(curr.to_s => ::Hashdiff.diff([], now.map { |record| record.attributes.except(*association_ignored_keys) }))
197
+ prev.merge(
198
+ curr.to_s => ::Hashdiff.diff(
199
+ [],
200
+ now.map { |record| normalize_attributes(record.attributes.except(*association_ignored_keys)) }
201
+ )
202
+ )
187
203
  else
188
204
  diff = before.map.with_index do |record_before, index|
189
205
  record_now = now.find_by(id: record_before.id)
190
206
  if record_now.blank?
191
- ['-', "[#{index}]", record_before.attributes.except(*association_ignored_keys)]
207
+ ['-', "[#{index}]", normalize_attributes(record_before.attributes.except(*association_ignored_keys))]
192
208
  elsif record_now.present?
193
- next if Hashdiff.diff(record_before.attributes.except(*association_ignored_keys), record_now.attributes.except(*association_ignored_keys)).blank?
194
- ['~', "[#{index}]", record_before.attributes.except(*association_ignored_keys), record_now.attributes.except(*association_ignored_keys)]
209
+ before_attrs = normalize_attributes(record_before.attributes.except(*association_ignored_keys))
210
+ now_attrs = normalize_attributes(record_now.attributes.except(*association_ignored_keys))
211
+ next if Hashdiff.diff(before_attrs, now_attrs).blank?
212
+ ['~', "[#{index}]", before_attrs, now_attrs]
195
213
  end
196
214
  end.compact_blank
197
215
 
198
216
  diff2 = now.map.with_index do |record_now, index|
199
217
  record_before = before.detect { |record| record.id == record_now.id }
200
218
  if record_before.blank?
201
- ['+', "[#{index}]", record_now.attributes.except(*association_ignored_keys)]
219
+ ['+', "[#{index}]", normalize_attributes(record_now.attributes.except(*association_ignored_keys))]
202
220
  end
203
221
  end.compact_blank
204
222
 
@@ -296,5 +314,32 @@ module Glib
296
314
  Array(records)
297
315
  end
298
316
  end
317
+
318
+ # Datetimes lose sub-millisecond precision on the snapshot store→reify round-trip:
319
+ # the JSON column encodes Time via ActiveSupport::JSON::Encoding.time_precision
320
+ # (default 3 = milliseconds, truncated not rounded), and reify casts that string
321
+ # back to a Time whose usec is a multiple of 1000. The live DB value keeps full
322
+ # microsecond precision, so Hashdiff (which compares Time values with ==) reports a
323
+ # phantom change on every re-snapshot of any watched sub-second datetime. Floor both
324
+ # sides to the same precision before the compare. The divisor is derived from
325
+ # time_precision so a consumer who raises it to 6 (microseconds) is not truncated.
326
+ # Because Hashdiff emits these floored values, the stored `metadata['diff']` also
327
+ # carries ms-precision datetimes (not just the in-memory comparison) — readers that
328
+ # introspect the raw diff see `.123000` even when the column holds `.123456`.
329
+ def normalize_attributes(hash)
330
+ divisor = snapshot_time_divisor
331
+ hash.transform_values { |value| normalize_datetime(value, divisor) }
332
+ end
333
+
334
+ def normalize_datetime(value, divisor = snapshot_time_divisor)
335
+ return value unless value.respond_to?(:usec)
336
+ return value if divisor <= 1
337
+
338
+ value.change(usec: value.usec - value.usec % divisor)
339
+ end
340
+
341
+ def snapshot_time_divisor
342
+ 10 ** (6 - ActiveSupport::JSON::Encoding.time_precision)
343
+ end
299
344
  end
300
345
  end
data/lib/glib-web.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'ostruct'
2
+
1
3
  if defined?(::Rails)
2
4
  require 'glib/engine'
3
5
  require 'glib/snapshot'
data/lib/tasks/db.rake CHANGED
@@ -81,6 +81,12 @@ namespace :db do
81
81
  attrs[key] = value.to_json
82
82
  elsif value.is_a? BigDecimal
83
83
  attrs[key] = value.to_f
84
+ elsif value.is_a?(IPAddr)
85
+ # inet/cidr columns deserialize to IPAddr. Dump as a plain string so
86
+ # fixtures stay portable: IPAddr's internal `family` constant differs
87
+ # between Linux/WSL and macOS, so a YAML-dumped IPAddr object raises
88
+ # when loaded on the other OS. Strings re-parse via cast_value on any platform.
89
+ attrs[key] = value.to_s
84
90
  end
85
91
  end
86
92
 
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glib-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.2.0
4
+ version: 6.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2019-10-04 00:00:00.000000000 Z
10
+ date: 2026-07-24 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activestorage
@@ -122,6 +121,34 @@ dependencies:
122
121
  - - ">="
123
122
  - !ruby/object:Gem::Version
124
123
  version: '0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: ostruct
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ - !ruby/object:Gem::Dependency
139
+ name: countries
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '7.1'
145
+ type: :runtime
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '7.1'
125
152
  - !ruby/object:Gem::Dependency
126
153
  name: rubocop
127
154
  requirement: !ruby/object:Gem::Requirement
@@ -150,7 +177,6 @@ dependencies:
150
177
  - - ">="
151
178
  - !ruby/object:Gem::Version
152
179
  version: '0'
153
- description:
154
180
  email: ''
155
181
  executables: []
156
182
  extensions: []
@@ -163,6 +189,8 @@ files:
163
189
  - app/controllers/concerns/glib/analytics/funnel.rb
164
190
  - app/controllers/concerns/glib/auth/policy.rb
165
191
  - app/controllers/concerns/glib/auth/response.rb
192
+ - app/controllers/concerns/glib/form_stepper/controller.rb
193
+ - app/controllers/concerns/glib/form_stepper/step.rb
166
194
  - app/controllers/concerns/glib/json/dynamic_text.rb
167
195
  - app/controllers/concerns/glib/json/libs.rb
168
196
  - app/controllers/concerns/glib/json/new_dynamic_text.rb
@@ -228,8 +256,10 @@ files:
228
256
  - app/models/glib/active_storage/attachment.rb
229
257
  - app/models/glib/active_storage/blob.rb
230
258
  - app/models/glib/application_record.rb
259
+ - app/models/glib/country_list.rb
231
260
  - app/models/glib/dummy_job_application.rb
232
261
  - app/models/glib/dynamic_text_record.rb
262
+ - app/models/glib/state_list.rb
233
263
  - app/models/glib/text.rb
234
264
  - app/policies/glib/application_policy.rb
235
265
  - app/validators/email_typo_validator.rb
@@ -541,10 +571,8 @@ files:
541
571
  - lib/glib/time_returning_mailer.rb
542
572
  - lib/glib/value.rb
543
573
  - lib/tasks/db.rake
544
- homepage:
545
574
  licenses: []
546
575
  metadata: {}
547
- post_install_message:
548
576
  rdoc_options: []
549
577
  require_paths:
550
578
  - lib
@@ -559,8 +587,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
559
587
  - !ruby/object:Gem::Version
560
588
  version: '0'
561
589
  requirements: []
562
- rubygems_version: 3.4.6
563
- signing_key:
590
+ rubygems_version: 4.0.6
564
591
  specification_version: 4
565
592
  summary: ''
566
593
  test_files: []