public_code 0.1.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +60 -0
- data/lib/public_code/model.rb +8 -3
- data/lib/public_code/railtie.rb +5 -0
- data/lib/public_code/test_help.rb +32 -0
- data/lib/public_code/version.rb +1 -1
- data/lib/public_code.rb +5 -0
- data/lib/tasks/public_code.rake +11 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85226caa9d5bbc68eedc7aecda5105a079c13733853522736aa1fb04f6d99477
|
|
4
|
+
data.tar.gz: 5bfe2a1075d98348dad53b5b569d86e21c16938908f91ef08ea97015076df351
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b25139d8cc38a6ef010df8bd0167022f39e35469d89a80c026386603224d23128d21079c193981c074bd4ff0bedf4212c826668568f36792b81479be1519b25
|
|
7
|
+
data.tar.gz: 5909c90e24ef0e6dc33956b1454b20b7d0f37c47c327f3923649774da7a8a26a00712195359041436d9e7c06fbbcee5256d26cd1516c9308fa51e05cd3b96a57
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.3.0] - 2026-10-12
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `bin/rails public_code:status` lists every model that declares a code and whether it is addressed by code or by id.
|
|
10
|
+
- With `STRICT=1` the status task fails naming each model still addressed by id.
|
|
11
|
+
|
|
12
|
+
## [0.2.0] - 2026-10-12
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- 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.
|
|
17
|
+
- A record whose table has the code column is not found by an all-digit address.
|
|
18
|
+
- `assert_no_numbered_links` fails a test naming each page that links to an address with an all-digit path segment or query value.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- A record gets its code when the app's secure token setting says, as with Rails' own secure tokens.
|
|
23
|
+
|
|
24
|
+
## [0.1.0]
|
|
25
|
+
|
|
5
26
|
### Added
|
|
6
27
|
|
|
7
28
|
- 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,63 @@ 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
|
+
## Seeing which models still use ids
|
|
93
|
+
|
|
94
|
+
In a Rails app:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
bin/rails public_code:status
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
It lists every model that declares a code and says whether it is addressed by
|
|
101
|
+
code or by id. With `STRICT=1` it fails when any model is still addressed by
|
|
102
|
+
id, and names each one, so a deploy or CI step can require every migration to
|
|
103
|
+
have run.
|
|
104
|
+
|
|
105
|
+
## Checking pages for numbered links
|
|
106
|
+
|
|
107
|
+
In an integration test:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
require "public_code/test_help"
|
|
111
|
+
|
|
112
|
+
class ClientAddressesTest < ActionDispatch::IntegrationTest
|
|
113
|
+
include PublicCode::TestHelp
|
|
114
|
+
|
|
115
|
+
test "no client page links to a numbered address" do
|
|
116
|
+
assert_no_numbered_links([ "/home", "/products/#{product.public_code}" ], safe: [ "page" ])
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
It fails when a link or form on any page has an all-digit path segment, or an
|
|
122
|
+
all-digit query value whose name is not in `safe:`, and names each page and
|
|
123
|
+
link. Links to other sites, asset files and stored files are not checked.
|
data/lib/public_code/model.rb
CHANGED
|
@@ -4,9 +4,14 @@ module PublicCode
|
|
|
4
4
|
|
|
5
5
|
class_methods do
|
|
6
6
|
def has_public_code(column = :public_code)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
PublicCode.models << self
|
|
8
|
+
define_singleton_method(:public_code_column?) { table_exists? && column_names.include?(column.to_s) }
|
|
9
|
+
define_method(:give_public_code) { self[column] ||= PublicCode.generate if self.class.public_code_column? }
|
|
10
|
+
define_method(:"regenerate_#{column}") { update!(column => PublicCode.generate) }
|
|
11
|
+
after_initialize :give_public_code, if: -> { new_record? && ActiveRecord.generate_secure_token_on == :initialize }
|
|
12
|
+
before_create :give_public_code
|
|
13
|
+
define_method(:to_param) { self.class.public_code_column? ? public_send(column) : super() }
|
|
14
|
+
define_singleton_method(:find_by_public_code!) { |address| public_code_column? ? find_by!(column => address) : find(address) }
|
|
10
15
|
end
|
|
11
16
|
end
|
|
12
17
|
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
|
data/lib/public_code/version.rb
CHANGED
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
|
|
|
@@ -7,6 +8,10 @@ module PublicCode
|
|
|
7
8
|
LENGTH = 24
|
|
8
9
|
|
|
9
10
|
def self.generate = ActiveRecord::Base.generate_unique_secure_token(length: LENGTH)
|
|
11
|
+
|
|
12
|
+
def self.models = @models ||= []
|
|
10
13
|
end
|
|
11
14
|
|
|
12
15
|
ActiveSupport.on_load(:active_record) { include PublicCode::Model }
|
|
16
|
+
|
|
17
|
+
require "public_code/railtie" if defined?(Rails::Railtie)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
namespace :public_code do
|
|
2
|
+
desc "List each model that declares a code and whether it is addressed by code or by id, failing on any by id when STRICT=1"
|
|
3
|
+
task status: :environment do
|
|
4
|
+
Rails.application.eager_load! if defined?(Rails.application) && Rails.application
|
|
5
|
+
models = PublicCode.models.sort_by(&:name)
|
|
6
|
+
models.each { |model| puts "#{model.name} addressed by #{model.public_code_column? ? "code" : "id"}" }
|
|
7
|
+
|
|
8
|
+
by_id = models.reject(&:public_code_column?)
|
|
9
|
+
abort "Still addressed by id: #{by_id.map(&:name).join(", ")}" if ENV["STRICT"] == "1" && by_id.any?
|
|
10
|
+
end
|
|
11
|
+
end
|
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tyler Schneider
|
|
@@ -50,7 +50,10 @@ files:
|
|
|
50
50
|
- README.md
|
|
51
51
|
- lib/public_code.rb
|
|
52
52
|
- lib/public_code/model.rb
|
|
53
|
+
- lib/public_code/railtie.rb
|
|
54
|
+
- lib/public_code/test_help.rb
|
|
53
55
|
- lib/public_code/version.rb
|
|
56
|
+
- lib/tasks/public_code.rake
|
|
54
57
|
homepage: https://github.com/DYB-Development/public_code
|
|
55
58
|
licenses:
|
|
56
59
|
- MIT
|