public_code 0.1.0 → 0.2.0

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: 391563ed134d7ff113b44b5608ddf2068dd18ca6048190aa1913e03a7313adc8
4
- data.tar.gz: 7b1e6be8d4b8bb9abdac10b8aaabf5a269c37b2fe119437f1387562985537538
3
+ metadata.gz: 535f7f0cf97ddb8664e239bfb344ee344763f4f9992ae47a30bbdc23b7eb4c6a
4
+ data.tar.gz: f6b8b87729c7c566cb14b31f6579107f01880ea4e713649a6e8b2694ae4aca9a
5
5
  SHA512:
6
- metadata.gz: d84156a63e7644dd3df2f9beb2c33b20cc12c2936db07c0f2d30770aad3cfaa2572439e91a0fabb33691fdcbbbaf333b1b09c577ebcf3e0aae7221db28b4a736
7
- data.tar.gz: ea22466225e19e92fe4fbe4d737153bd0c03c0d8ffa26aaac497d2fb38548066157e3aba9442e3a8b66712b4233f5ae966d40b7e04a7ca545de6cf8149ddd800
6
+ metadata.gz: 044c101dcebc4c309d96a7fbe9a21d4b00f95f3e72b9685a843e08e2ac6475aead151f78b585e51a7a3ae705971543f46c71af7fcc8f2711579fc5a8a617407f
7
+ data.tar.gz: 4ac279aef93d9311b5450261df822179fe743f0c3c2a4d9293f25cf95c3ca117d06d6313c77a7271cec0e2510fe999b8fc99ff5c0d4d61402fbf7170b3261a87
data/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.2.0] - 2026-10-12
6
+
7
+ ### Added
8
+
9
+ - A record whose table has no code column yet is addressed and found by its id, so a gem can give its records codes before every app has migrated.
10
+ - A record whose table has the code column is not found by an all-digit address.
11
+ - `assert_no_numbered_links` fails a test naming each page that links to an address with an all-digit path segment or query value.
12
+
13
+ ### Changed
14
+
15
+ - A record gets its code when the app's secure token setting says, as with Rails' own secure tokens.
16
+
17
+ ## [0.1.0]
18
+
5
19
  ### Added
6
20
 
7
21
  - A model that calls `has_public_code` gives each new record a random 24 character code, and every address Rails builds for the record carries that code in place of its id.
data/README.md CHANGED
@@ -61,3 +61,50 @@ A column with its own name works the same way:
61
61
  ```ruby
62
62
  has_public_code :code
63
63
  ```
64
+
65
+ ## Codes are for addresses only
66
+
67
+ A code replaces the id only where a person or an outside caller sees it: page
68
+ addresses, API and MCP addresses, and links sent out. Records still point at
69
+ each other by id, and a record's id never changes.
70
+
71
+ ## A gem whose records take codes in some apps first
72
+
73
+ A gem declares the code on its model the same way:
74
+
75
+ ```ruby
76
+ module EasyFlow
77
+ class Run < ApplicationRecord
78
+ has_public_code
79
+ end
80
+ end
81
+ ```
82
+
83
+ Until an app's table has the code column, a record is addressed and found by
84
+ its id exactly as before, so a gem can release this without every app
85
+ migrating at once. Once the table has the column, every address carries the
86
+ code and an all-digit address is not found. Ship the migration that adds the
87
+ column with the gem and name it in the gem's changelog.
88
+
89
+ An app switches a gem's records to codes by copying and running that migration,
90
+ then restarting so the new column is seen.
91
+
92
+ ## Checking pages for numbered links
93
+
94
+ In an integration test:
95
+
96
+ ```ruby
97
+ require "public_code/test_help"
98
+
99
+ class ClientAddressesTest < ActionDispatch::IntegrationTest
100
+ include PublicCode::TestHelp
101
+
102
+ test "no client page links to a numbered address" do
103
+ assert_no_numbered_links([ "/home", "/products/#{product.public_code}" ], safe: [ "page" ])
104
+ end
105
+ end
106
+ ```
107
+
108
+ It fails when a link or form on any page has an all-digit path segment, or an
109
+ all-digit query value whose name is not in `safe:`, and names each page and
110
+ link. Links to other sites, asset files and stored files are not checked.
@@ -4,9 +4,13 @@ module PublicCode
4
4
 
5
5
  class_methods do
6
6
  def has_public_code(column = :public_code)
7
- has_secure_token column, length: PublicCode::LENGTH
8
- define_method(:to_param) { public_send(column) }
9
- define_singleton_method(:find_by_public_code!) { |code| find_by!(column => code) }
7
+ define_singleton_method(:public_code_column?) { table_exists? && column_names.include?(column.to_s) }
8
+ define_method(:give_public_code) { self[column] ||= PublicCode.generate if self.class.public_code_column? }
9
+ define_method(:"regenerate_#{column}") { update!(column => PublicCode.generate) }
10
+ after_initialize :give_public_code, if: -> { new_record? && ActiveRecord.generate_secure_token_on == :initialize }
11
+ before_create :give_public_code
12
+ define_method(:to_param) { self.class.public_code_column? ? public_send(column) : super() }
13
+ define_singleton_method(:find_by_public_code!) { |address| public_code_column? ? find_by!(column => address) : find(address) }
10
14
  end
11
15
  end
12
16
  end
@@ -0,0 +1,32 @@
1
+ require "uri"
2
+
3
+ module PublicCode
4
+ module TestHelp
5
+ def assert_no_numbered_links(pages, safe: [])
6
+ found = pages.flat_map do |page|
7
+ get page
8
+ TestHelp.numbered_links(links_on_page, safe: safe).map { |link| "#{page} links to #{link}" }
9
+ end
10
+ assert found.empty?, ([ "Pages link to numbered addresses:" ] + found).join("\n")
11
+ end
12
+
13
+ def links_on_page = css_select("a[href], form[action]").map { |element| element["href"] || element["action"] }
14
+
15
+ def self.numbered_links(addresses, safe: [])
16
+ addresses.select do |address|
17
+ next false unless own_page?(address)
18
+
19
+ uri = URI(address)
20
+ numbered_path?(uri.path) || numbered_query?(uri.query, safe)
21
+ end
22
+ end
23
+
24
+ def self.own_page?(address) = address.start_with?("/") && !address.start_with?("//", "/assets/", "/rails/active_storage/")
25
+
26
+ def self.numbered_path?(path) = path.split("/").any? { |segment| number?(segment) }
27
+
28
+ def self.numbered_query?(query, safe) = URI.decode_www_form(query.to_s).any? { |name, value| !safe.include?(name) && number?(value) }
29
+
30
+ def self.number?(value) = value.match?(/\A\d+\z/)
31
+ end
32
+ end
@@ -1,3 +1,3 @@
1
1
  module PublicCode
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/public_code.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "active_record"
2
2
  require "active_support"
3
+ require "active_support/core_ext/securerandom"
3
4
  require "public_code/version"
4
5
  require "public_code/model"
5
6
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: public_code
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Schneider
@@ -50,6 +50,7 @@ files:
50
50
  - README.md
51
51
  - lib/public_code.rb
52
52
  - lib/public_code/model.rb
53
+ - lib/public_code/test_help.rb
53
54
  - lib/public_code/version.rb
54
55
  homepage: https://github.com/DYB-Development/public_code
55
56
  licenses: