├── bin ├── setup ├── console └── release ├── .gitignore ├── lib ├── stealth_dom_id │ ├── version.rb │ ├── configuration.rb │ ├── railtie.rb │ └── core.rb └── stealth_dom_id.rb ├── .standard.yml ├── test ├── test_helper.rb └── test_stealth_dom_id.rb ├── Gemfile ├── Rakefile ├── LICENSE.txt ├── stealth_dom_id.gemspec ├── README.md └── Gemfile.lock /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | -------------------------------------------------------------------------------- /lib/stealth_dom_id/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module StealthDomId 4 | VERSION = "0.3.0" 5 | end 6 | -------------------------------------------------------------------------------- /.standard.yml: -------------------------------------------------------------------------------- 1 | # For available configuration options, see: 2 | # https://github.com/standardrb/standard 3 | ruby_version: 3.0 4 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path("../lib", __dir__) 4 | require "stealth_dom_id" 5 | 6 | require "minitest/autorun" 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gemspec 6 | 7 | gem "rake", "~> 13.0" 8 | gem "minitest", "~> 5.16" 9 | gem 'standard', "~> 1.41" 10 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "minitest/test_task" 5 | 6 | Minitest::TestTask.create 7 | 8 | require "standard/rake" 9 | 10 | task default: %i[test standard] 11 | -------------------------------------------------------------------------------- /lib/stealth_dom_id.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "stealth_dom_id/version" 4 | require "stealth_dom_id/configuration" 5 | require "stealth_dom_id/core" 6 | require "stealth_dom_id/railtie" if defined?(Rails) 7 | 8 | module StealthDomId 9 | class Error < StandardError; end 10 | end 11 | -------------------------------------------------------------------------------- /lib/stealth_dom_id/configuration.rb: -------------------------------------------------------------------------------- 1 | module StealthDomId 2 | class Configuration 3 | attr_accessor :default_attribute 4 | end 5 | 6 | def self.configuration 7 | @configuration ||= Configuration.new 8 | end 9 | 10 | def self.configure 11 | yield(configuration) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/test_stealth_dom_id.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestStealthDomId < Minitest::Test 6 | def test_that_it_has_a_version_number 7 | refute_nil ::StealthDomId::VERSION 8 | end 9 | 10 | def test_it_does_something_useful 11 | assert false 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "stealth_dom_id" 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | require "irb" 11 | IRB.start(__FILE__) 12 | -------------------------------------------------------------------------------- /lib/stealth_dom_id/railtie.rb: -------------------------------------------------------------------------------- 1 | module StealthDomId 2 | class Railtie < Rails::Railtie 3 | initializer "stealth_dom_id.action_view" do 4 | ActiveSupport.on_load(:action_view) do 5 | include StealthDomId::Core 6 | end 7 | 8 | ActiveSupport.on_load(:view_component) do 9 | ViewComponent::Base.include StealthDomId::Core 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /bin/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VERSION=$1 4 | 5 | if [ -z "$VERSION" ]; then 6 | echo "Error: The version number is required." 7 | echo "Usage: $0 " 8 | exit 1 9 | fi 10 | 11 | printf "# frozen_string_literal: true\n\nmodule StealthDomId\n VERSION = \"$VERSION\"\nend\n" > ./lib/stealth_dom_id/version.rb 12 | bundle 13 | git add Gemfile.lock lib/stealth_dom_id/version.rb 14 | git commit -m "Bump version for $VERSION" 15 | git push 16 | git tag v$VERSION 17 | git push --tags 18 | gem build stealth_dom_id.gemspec 19 | gem push "stealth_dom_id-$VERSION.gem" 20 | -------------------------------------------------------------------------------- /lib/stealth_dom_id/core.rb: -------------------------------------------------------------------------------- 1 | module StealthDomId 2 | class AttributeError < ArgumentError; end 3 | 4 | module Core 5 | def dom_id(record_or_class, prefix = nil, attribute: nil) 6 | attribute ||= StealthDomId.configuration.default_attribute 7 | 8 | unless record_or_class.is_a?(Class) 9 | record_id = if attribute 10 | record_key_for_dom_id_by_attribute(record_or_class, attribute: attribute) 11 | else 12 | record_key_for_dom_id(record_or_class) 13 | end 14 | end 15 | 16 | if record_id 17 | "#{dom_class(record_or_class, prefix)}#{JOIN}#{record_id}" 18 | else 19 | dom_class(record_or_class, prefix || NEW) 20 | end 21 | end 22 | 23 | private 24 | 25 | JOIN = "_".freeze 26 | NEW = "new".freeze 27 | 28 | def record_key_for_dom_id_by_attribute(record, attribute:) 29 | key = [convert_to_model(record).send(attribute)] 30 | 31 | key ? key.join(JOIN) : key 32 | rescue NoMethodError => e 33 | raise AttributeError, "[StealthDomId] Attribute '#{attribute}' not found on #{record.class}" 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Rails Designer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /stealth_dom_id.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/stealth_dom_id/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "stealth_dom_id" 7 | spec.version = StealthDomId::VERSION 8 | spec.authors = ["Rails Designer Developers"] 9 | spec.email = ["devs@railsdesigner.com"] 10 | 11 | spec.summary = "Extends Rails `dom_id` helper to support custom attribute-based identifiers (example “slug”)" 12 | spec.description = "stealth_dom_id extends Rails' `dom_id` helper to generate DOM IDs using an alternative attribute instead of database primary keys. This helps prevent exposing internal database IDs." 13 | spec.homepage = "https://github.com/Rails-Designer/stealth_dom_id/" 14 | spec.license = "MIT" 15 | spec.required_ruby_version = ">= 3.0.0" 16 | 17 | spec.metadata["source_code_uri"] = "https://github.com/Rails-Designer/stealth_dom_id/" 18 | 19 | spec.files = Dir["{bin,app,config,db,lib,public}/**/*", "Rakefile", "README.md", "stealth_dom_id.gemspec", "Gemfile", "Gemfile.lock"] 20 | 21 | spec.required_ruby_version = ">= 3.0.0" 22 | spec.add_dependency "actionview", ">= 3.0", "< 8.1" 23 | spec.add_dependency "activesupport", ">= 3.0", "< 8.1" 24 | end 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stealth_dom_id 2 | 3 | > [!IMPORTANT] 4 | > This gem is sunsetted. The [suggested solution and replacement is outlined in this article](https://railsdesigner.com/dom-id-without-primary-id/). 5 | 6 | stealth_dom_id extends Rails' [`dom_id`](https://github.com/rails/rails/blob/main/actionview/lib/action_view/record_identifier.rb) helper by allowing you to generate DOM IDs using any attribute instead of the default database primary keys. This helps keep your internal database IDs private. 7 | 8 | ## Installation 9 | 10 | Add the gem to your application's Gemfile by running: 11 | ```bash 12 | bundle add stealth_dom_id 13 | ``` 14 | 15 | ## Usage 16 | 17 | Instead of exposing database IDs in your HTML elements with the standard `dom_id` helper: 18 | ```erb 19 | <%= dom_id(@user) %> 20 | # => "user_1" 21 | ``` 22 | 23 | You can use any available attribute of your choice: 24 | ```erb 25 | <%= dom_id(@user, attribute: :public_id) %> 26 | # => "user_a1b2c3" 27 | ``` 28 | 29 | The `attribute` parameter is optional and defaults to the model's primary key. Just like Rails' built-in `dom_id`, you can also include a prefix: 30 | ```erb 31 | <%= dom_id(@user, :admin, attribute: :public_id) %> 32 | # => "admin_user_a1b2c3" 33 | ``` 34 | 35 | ### Configuration 36 | 37 | You can set a default attribute to be used across your application. Add this to an initializer (e.g., `config/initializers/stealth_dom_id.rb`): 38 | ```ruby 39 | StealthDomId.configure do |config| 40 | config.default_attribute = :public_id 41 | end 42 | ``` 43 | 44 | Now `public_id` will be used whenever no specific attribute is provided. 45 | 46 | 47 | ## Development 48 | 49 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 50 | 51 | To install this gem onto your local machine, run `bundle exec rake install`. 52 | 53 | 54 | ## License 55 | 56 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 57 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | stealth_dom_id (0.3.0) 5 | actionview (>= 3.0, < 8.1) 6 | activesupport (>= 3.0, < 8.1) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | actionview (7.2.2) 12 | activesupport (= 7.2.2) 13 | builder (~> 3.1) 14 | erubi (~> 1.11) 15 | rails-dom-testing (~> 2.2) 16 | rails-html-sanitizer (~> 1.6) 17 | activesupport (7.2.2) 18 | base64 19 | benchmark (>= 0.3) 20 | bigdecimal 21 | concurrent-ruby (~> 1.0, >= 1.3.1) 22 | connection_pool (>= 2.2.5) 23 | drb 24 | i18n (>= 1.6, < 2) 25 | logger (>= 1.4.2) 26 | minitest (>= 5.1) 27 | securerandom (>= 0.3) 28 | tzinfo (~> 2.0, >= 2.0.5) 29 | ast (2.4.2) 30 | base64 (0.2.0) 31 | benchmark (0.3.0) 32 | bigdecimal (3.1.8) 33 | builder (3.3.0) 34 | concurrent-ruby (1.3.4) 35 | connection_pool (2.4.1) 36 | crass (1.0.6) 37 | drb (2.2.1) 38 | erubi (1.13.0) 39 | i18n (1.14.6) 40 | concurrent-ruby (~> 1.0) 41 | json (2.7.5) 42 | language_server-protocol (3.17.0.3) 43 | lint_roller (1.1.0) 44 | logger (1.6.1) 45 | loofah (2.23.1) 46 | crass (~> 1.0.2) 47 | nokogiri (>= 1.12.0) 48 | minitest (5.25.1) 49 | nokogiri (1.16.7-aarch64-linux) 50 | racc (~> 1.4) 51 | nokogiri (1.16.7-arm-linux) 52 | racc (~> 1.4) 53 | nokogiri (1.16.7-arm64-darwin) 54 | racc (~> 1.4) 55 | nokogiri (1.16.7-x86-linux) 56 | racc (~> 1.4) 57 | nokogiri (1.16.7-x86_64-darwin) 58 | racc (~> 1.4) 59 | nokogiri (1.16.7-x86_64-linux) 60 | racc (~> 1.4) 61 | parallel (1.26.3) 62 | parser (3.3.5.1) 63 | ast (~> 2.4.1) 64 | racc 65 | racc (1.8.1) 66 | rails-dom-testing (2.2.0) 67 | activesupport (>= 5.0.0) 68 | minitest 69 | nokogiri (>= 1.6) 70 | rails-html-sanitizer (1.6.0) 71 | loofah (~> 2.21) 72 | nokogiri (~> 1.14) 73 | rainbow (3.1.1) 74 | rake (13.2.1) 75 | regexp_parser (2.9.2) 76 | rubocop (1.66.1) 77 | json (~> 2.3) 78 | language_server-protocol (>= 3.17.0) 79 | parallel (~> 1.10) 80 | parser (>= 3.3.0.2) 81 | rainbow (>= 2.2.2, < 4.0) 82 | regexp_parser (>= 2.4, < 3.0) 83 | rubocop-ast (>= 1.32.2, < 2.0) 84 | ruby-progressbar (~> 1.7) 85 | unicode-display_width (>= 2.4.0, < 3.0) 86 | rubocop-ast (1.33.1) 87 | parser (>= 3.3.1.0) 88 | rubocop-performance (1.22.1) 89 | rubocop (>= 1.48.1, < 2.0) 90 | rubocop-ast (>= 1.31.1, < 2.0) 91 | ruby-progressbar (1.13.0) 92 | securerandom (0.3.1) 93 | standard (1.41.1) 94 | language_server-protocol (~> 3.17.0.2) 95 | lint_roller (~> 1.0) 96 | rubocop (~> 1.66.0) 97 | standard-custom (~> 1.0.0) 98 | standard-performance (~> 1.5) 99 | standard-custom (1.0.2) 100 | lint_roller (~> 1.0) 101 | rubocop (~> 1.50) 102 | standard-performance (1.5.0) 103 | lint_roller (~> 1.1) 104 | rubocop-performance (~> 1.22.0) 105 | tzinfo (2.0.6) 106 | concurrent-ruby (~> 1.0) 107 | unicode-display_width (2.6.0) 108 | 109 | PLATFORMS 110 | aarch64-linux 111 | arm-linux 112 | arm64-darwin 113 | x86-linux 114 | x86_64-darwin 115 | x86_64-linux 116 | 117 | DEPENDENCIES 118 | minitest (~> 5.16) 119 | rake (~> 13.0) 120 | standard (~> 1.41) 121 | stealth_dom_id! 122 | 123 | BUNDLED WITH 124 | 2.5.22 125 | --------------------------------------------------------------------------------