glib-web 5.0.9 → 5.1.1

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: 66abe13aca7ef703ab65bcbaeded784ff74339b4dcfd3659658bdefc99f0ec30
4
- data.tar.gz: a8c90ab7a9684a4eeed86c8d6e25d3676281157c856ed07cd582b5ccdebfa622
3
+ metadata.gz: 783a7ad2cc1fcb744c02e91df3185c7d455c6bef84d56fc0eff2503f3a39f826
4
+ data.tar.gz: 960797098836ddd26a310b592f953f46284f986bde09f42deae1b889a5a85cd1
5
5
  SHA512:
6
- metadata.gz: ef265df7cd0d8f0e3d951f384b574cf01e6c4039f774e0df286fb0617ce58be80551cb5e1ccbf05996af4b9127aaa8cfdccbac7f146d22b0816acc6690400814
7
- data.tar.gz: e5ba874af4579d1310671a292404a2cb27b8ff89b609ff2a2c0f0ff3609a301a8566d8e3a86099d4a41c9ca6249a19cd00ce78a4cef30c8642e85efed260f07a
6
+ metadata.gz: e5a23a583b69403fd8a65db92b658fb2aab0b7ca6e2946ba4da0e39d194504b2ed9694c638167d68828b92c97d7c3851746eb189be75576ec777201e8ad570a1
7
+ data.tar.gz: 43ac55043ae8321c33aa4c662b5f4833be211cf0d95f6cc9f8ad5b16f4b7685a4a475e5e0c77d3c01c99292f1401577a8b600f2e863055274582c1a5d0997bfb
@@ -1,26 +1,80 @@
1
1
  module Glib
2
- # Class-level helper that overrides one or more enum readers to return a
3
- # symbol instead of Rails' default string form. Lets callers compare with
4
- # `record.status == :active` without having to remember which form they're
5
- # holding (see backend/01a_defensive_programming.md item 7).
2
+ # Class-level helper for declaring enums that read back as symbols, can never
3
+ # be persisted nil, and crucially know their attribute type even before
4
+ # the backing column exists.
6
5
  #
7
- # Usage:
6
+ # Preferred form (declares everything in one call; use INSTEAD of a bare
7
+ # `enum`). Because it calls `attribute` before `enum`, the enum's type comes
8
+ # from the declared values rather than from column introspection, so a model
9
+ # loaded against a not-yet-migrated column (fresh deploy, eager-loaded CI, an
10
+ # un-migrated local DB) no longer raises "Undeclared attribute type for enum":
8
11
  #
9
12
  # class Order < ApplicationRecord
10
- # enum :payment_status, { draft: 0, pending: 1, finalized: 2 }
11
- # enum :finalize_intent, { unknown: 0, charge: 1 }, prefix: true
13
+ # include Glib::EnumSymbolization
12
14
  #
13
- # enum_symbolize :payment_status, :finalize_intent
15
+ # enum_symbolize :payment_status, { draft: 0, pending: 1, finalized: 2 }
16
+ # enum_symbolize :finalize_intent, { unknown: 0, charge: 1 }, prefix: true
14
17
  # end
18
+ #
19
+ # The attribute type is inferred from the values: string values back a
20
+ # :string column, everything else (integer-keyed hashes, positional arrays)
21
+ # backs an :integer column. Pass the values as a *braced* hash — a bare
22
+ # `key: 0` list is parsed as keyword options, not values.
23
+ #
24
+ # Legacy form (attach presence + symbolized reader to enum(s) already declared
25
+ # above). Prefer the form above for new code:
26
+ #
27
+ # enum :status, { draft: 0, published: 1 }
28
+ # enum_symbolize :status
29
+ #
30
+ # Both forms add `validates :attr, presence: true` (a nil enum has no symbolic
31
+ # key and is almost always a bug) and override the reader to return a symbol,
32
+ # so callers can compare with `record.status == :active` without tracking
33
+ # which form they're holding (see backend/01a_defensive_programming.md item 7).
15
34
  module EnumSymbolization
16
35
  extend ActiveSupport::Concern
17
36
 
18
37
  class_methods do
19
- def enum_symbolize(*attrs)
20
- attrs.each do |attr|
21
- define_method(attr) { super()&.to_sym }
38
+ def enum_symbolize(*attrs, **opts)
39
+ if attrs.size >= 2 && (attrs[1].is_a?(Hash) || attrs[1].is_a?(Array))
40
+ # Full form: enum_symbolize :name, { ... }[, **enum_opts]
41
+ name, values = attrs
42
+ # MUST precede `enum`, and is NOT redundant: `enum` resolves its type
43
+ # by introspecting the DB column, which raises "Undeclared attribute
44
+ # type for enum" (RuntimeError) when the column isn't there yet — a
45
+ # not-yet-migrated column loaded on deploy, eager-loaded CI, or an
46
+ # un-migrated local DB. Declaring the attribute first pins the type
47
+ # from the enum's own values, so it no longer needs the column. Do not
48
+ # remove or move below `enum`.
49
+ attribute name, Glib::EnumSymbolization.backing_type(values)
50
+ enum name, values, **opts
51
+ glib_symbolize_reader(name)
52
+ else
53
+ # Legacy form: enum_symbolize :a[, :b, ...] for enums already declared.
54
+ if opts.any?
55
+ raise ArgumentError,
56
+ "enum_symbolize: pass enum values as a braced hash, " \
57
+ "e.g. enum_symbolize :#{attrs.first}, { ... }"
58
+ end
59
+ attrs.each { |attr| glib_symbolize_reader(attr) }
22
60
  end
23
61
  end
62
+
63
+ private
64
+
65
+ def glib_symbolize_reader(attr)
66
+ validates attr, presence: true
67
+ define_method(attr) { super()&.to_sym }
68
+ end
69
+ end
70
+
71
+ # Infer the attribute type backing an enum from its declared values, so the
72
+ # type never depends on the (possibly not-yet-migrated) DB column. A hash
73
+ # whose values are strings backs a :string column; integer-keyed hashes and
74
+ # positional arrays back an :integer column.
75
+ def self.backing_type(values)
76
+ first = values.is_a?(Hash) ? values.values.first : nil
77
+ first.is_a?(String) ? :string : :integer
24
78
  end
25
79
  end
26
80
  end
@@ -0,0 +1,28 @@
1
+ module Glib
2
+ # The truthful base class for the rare, justified unit tests (see dev-doc's
3
+ # AvoidUnitTest / RequireUnitTestJustification). Every other test should be a
4
+ # controller test on Glib::IntegrationTest.
5
+ #
6
+ # The name is the policy: you may only be here after exhausting the
7
+ # controller-test route. Assume a controller test IS possible and look
8
+ # harder first — that conclusion is almost always premature (AvoidUnitTest's
9
+ # docs list the patterns that make "unit-only" behaviour reachable
10
+ # end-to-end). Subclassing this base is a confession, not a credential: it
11
+ # must sit in a unit-test directory under a justification header, both
12
+ # enforced by RequireUnitTestJustification.
13
+ #
14
+ # Deliberately NOT a descendant of ActionDispatch::IntegrationTest:
15
+ #
16
+ # - The declaration line states the test type honestly. A unit test
17
+ # subclassing an "IntegrationTest" base is a lie that also defeats
18
+ # base-class-keyed tooling.
19
+ # - HTTP verb helpers (`get`/`post`/...) don't exist here, so a unit test
20
+ # structurally cannot drift into half-integration territory — the inverse
21
+ # of the guarantee DevDoc::Test::Lints::HttpDrivenControllerTests provides
22
+ # for test/controllers/.
23
+ #
24
+ # Project-wide helpers attached to ActiveSupport::TestCase (fixtures,
25
+ # glib_travel, coverage) are inherited as usual.
26
+ class LastResortUnitTest < ActiveSupport::TestCase
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_job/test_helper'
2
+
3
+ module Glib
4
+ # The truthful base for full-stack side-effect tests (jobs, mailers,
5
+ # channels): real DB, fixtures, deliveries and enqueued-job execution — but
6
+ # no HTTP verbs. Use it when the contract under test is a side effect (a
7
+ # job's record mutations, a mailer's body and links) rather than an HTTP
8
+ # response; tests that DO assert responses belong on Glib::IntegrationTest.
9
+ #
10
+ # Deliberately NOT a descendant of ActionDispatch::IntegrationTest, for the
11
+ # same reasons as Glib::LastResortUnitTest: the declaration line states the
12
+ # test type honestly, and the HTTP verb helpers are structurally absent, so
13
+ # a side-effect test cannot silently drift into half-integration territory.
14
+ # Unlike LastResortUnitTest this base is not a confession — jobs and mailers
15
+ # genuinely have no HTTP surface of their own — but it is confined all the
16
+ # same: DevDoc/Test/RequireUnitTestJustification only permits it inside the
17
+ # side-effect directories (test/jobs/, test/mailers/, test/channels/),
18
+ # because anywhere else it would evade that directory's enforcement.
19
+ #
20
+ # Project-wide helpers attached to ActiveSupport::TestCase (fixtures,
21
+ # glib_travel, coverage) are inherited as usual; ActiveJob::TestHelper is
22
+ # included here because running the very jobs under test
23
+ # (perform_enqueued_jobs) is this tier's bread and butter.
24
+ class NonHttpIntegrationTest < ActiveSupport::TestCase
25
+ include ActiveJob::TestHelper
26
+ end
27
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require 'shellwords'
5
+
6
+ module Glib
7
+ module Test
8
+ # Fast RuboCop guard for the subset-run workflow.
9
+ #
10
+ # Lives in glib-web's test helper so any project that includes
11
+ # `Glib::TestHelpers` into `ActiveSupport::TestCase` picks it up. Minitest
12
+ # runs all loaded test classes on every invocation, so this fires on every
13
+ # `rails test` — but it lints ONLY the files you have changed in the
14
+ # working tree (unstaged + staged + untracked). That keeps it fast and
15
+ # fails the run the moment an edited file has an offense.
16
+ #
17
+ # Skip with `SKIP_CHANGED_RUBOCOP=1` during tight debug loops. Auto-skips
18
+ # on CI because gem caches put thousands of vendored `.rb` files into the
19
+ # working tree (the "working-tree diff == your edits" premise breaks).
20
+ class ChangedFilesRubocop < ::ActiveSupport::TestCase
21
+ # Extensions RuboCop targets by default (see its AllCops/Include). We
22
+ # rely on `--force-exclusion` to honour the project's AllCops/Exclude.
23
+ RUBOCOP_EXTENSIONS = %w[.rb .rake .jbuilder .gemspec].freeze
24
+
25
+ def test_changed_files_are_rubocop_clean
26
+ skip 'opted out via SKIP_CHANGED_RUBOCOP' if ENV['SKIP_CHANGED_RUBOCOP'] == '1'
27
+ skip 'CI lints every file separately; this guard is for local working-tree edits' if ENV['CI']
28
+
29
+ files = changed_ruby_files
30
+ # IMPORTANT: never call rubocop with zero paths — it would lint the whole repo.
31
+ skip 'no changed Ruby files in the working tree' if files.empty?
32
+
33
+ output = `bundle exec rubocop --force-exclusion --format simple #{files.shelljoin} 2>&1`
34
+
35
+ assert(
36
+ $CHILD_STATUS.success?,
37
+ 'RuboCop offenses in files you changed (fix them with `rubocop -a`):' \
38
+ "\n\n#{output}"
39
+ )
40
+ end
41
+
42
+ private
43
+ def changed_ruby_files
44
+ # `git status --porcelain` covers unstaged, staged, and untracked in
45
+ # one spawn. Each line is `XY PATH` (or `XY ORIG -> PATH` for
46
+ # renames); PATH starts at index 3. Deleted files are dropped by the
47
+ # File.file? check below (no on-disk entry to lint).
48
+ `git status --porcelain`
49
+ .split("\n")
50
+ .filter_map { |line| line[3..]&.split(' -> ')&.last }
51
+ .select { |path| RUBOCOP_EXTENSIONS.include?(File.extname(path)) && File.file?(path) }
52
+ end
53
+ end
54
+ end
55
+ end
@@ -4,6 +4,12 @@ module Glib
4
4
 
5
5
  included do
6
6
  extend ClassMethods
7
+
8
+ # Auto-load ChangedFilesRubocop only when this module is mixed into a
9
+ # test class. AllHelpers also includes Glib::TestHelpers into view
10
+ # helper modules in production controllers; in that context
11
+ # ActiveSupport::TestCase is not loaded, so we skip the require.
12
+ require 'glib/test/changed_files_rubocop' if defined?(::ActiveSupport::TestCase)
7
13
  end
8
14
 
9
15
  module ClassMethods
@@ -33,7 +39,17 @@ module Glib
33
39
  def response_assert_equal
34
40
  expected = __get_previous_result_from_git
35
41
  result = __log_controller_data(response.body)
36
- assert_equal JSON.parse(expected), JSON.parse(result), "Result mismatch! #{__git_is_available? ? `git diff #{__controller_log_dir}/#{__controller_log_file}` : ''}"
42
+ # The message is a proc so the `git diff` subprocess only runs on FAILURE
43
+ # (a positional string message is built eagerly — one shell-out per
44
+ # passing assertion, and needless git-index contention under parallel
45
+ # workers). The path must be QUOTED: log filenames derive from test
46
+ # names, and an unquoted name containing `->` makes the shell treat `>`
47
+ # as a redirection, creating stray empty files in the app root.
48
+ failure_message = proc do
49
+ diff = __git_is_available? ? `git diff "#{File.join(__controller_log_dir, __controller_log_file)}"` : ''
50
+ "Result mismatch! #{diff}"
51
+ end
52
+ assert_equal JSON.parse(expected), JSON.parse(result), failure_message
37
53
  end
38
54
 
39
55
  def crawl_json_pages(user, log_file: nil, dump_actions: false, dump_path: nil, skip_similar_page: false, &block)
data/lib/glib-web.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'glib/version'
2
1
  if defined?(::Rails)
3
2
  require 'glib/engine'
4
3
  require 'glib/snapshot'
@@ -10,6 +9,8 @@ require 'glib/dynamic_text'
10
9
  require 'glib/test_helpers'
11
10
  require 'glib/test/parallel_coverage'
12
11
  require 'glib/integration_test'
12
+ require 'glib/last_resort_unit_test'
13
+ require 'glib/non_http_integration_test'
13
14
 
14
15
  require 'glib/time_freezable_mailer'
15
16
  require 'glib/time_returning_mailer'
metadata CHANGED
@@ -1,10 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glib-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.9
4
+ version: 5.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
11
  date: 2019-10-04 00:00:00.000000000 Z
@@ -149,6 +150,7 @@ dependencies:
149
150
  - - ">="
150
151
  - !ruby/object:Gem::Version
151
152
  version: '0'
153
+ description:
152
154
  email: ''
153
155
  executables: []
154
156
  extensions: []
@@ -511,7 +513,9 @@ files:
511
513
  - lib/glib/json_crawler/coverage.rb
512
514
  - lib/glib/json_crawler/http.rb
513
515
  - lib/glib/json_crawler/router.rb
516
+ - lib/glib/last_resort_unit_test.rb
514
517
  - lib/glib/mailer_tester.rb
518
+ - lib/glib/non_http_integration_test.rb
515
519
  - lib/glib/rubocop.rb
516
520
  - lib/glib/rubocop/cops/json_ui/base_nested_parameter.rb
517
521
  - lib/glib/rubocop/cops/json_ui/nested_action_parameter.rb
@@ -520,15 +524,17 @@ files:
520
524
  - lib/glib/rubocop/cops/multiline_method_call_style.rb
521
525
  - lib/glib/rubocop/cops/test_name_parentheses.rb
522
526
  - lib/glib/snapshot.rb
527
+ - lib/glib/test/changed_files_rubocop.rb
523
528
  - lib/glib/test/parallel_coverage.rb
524
529
  - lib/glib/test_helpers.rb
525
530
  - lib/glib/time_freezable_mailer.rb
526
531
  - lib/glib/time_returning_mailer.rb
527
532
  - lib/glib/value.rb
528
- - lib/glib/version.rb
529
533
  - lib/tasks/db.rake
534
+ homepage:
530
535
  licenses: []
531
536
  metadata: {}
537
+ post_install_message:
532
538
  rdoc_options: []
533
539
  require_paths:
534
540
  - lib
@@ -543,7 +549,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
543
549
  - !ruby/object:Gem::Version
544
550
  version: '0'
545
551
  requirements: []
546
- rubygems_version: 4.0.6
552
+ rubygems_version: 3.4.6
553
+ signing_key:
547
554
  specification_version: 4
548
555
  summary: ''
549
556
  test_files: []
data/lib/glib/version.rb DELETED
@@ -1,5 +0,0 @@
1
- module Glib
2
- module Web
3
- VERSION = '0.0.1'
4
- end
5
- end