Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions .audition-baseline.json

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions .audition.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Audition — Ractor-readiness checks (https://github.com/yaroslav/audition)
#
# Known findings are recorded in .audition-baseline.json so CI fails only on
# *new* findings. Regenerate the baseline with:
# audition . --write-baseline
#
# Silence a single line inline, rubocop-style:
# risky_call # audition:disable <check-name>
fail_on: error
15 changes: 15 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,18 @@ jobs:
bundler-cache: true
- name: YARD Lint
run: bundle exec yard-lint lib/
audition:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 4.0
# audition needs Ruby 4.0+; installed standalone since rubydex is a
# precompiled native gem. Fails only on findings not in
# .audition-baseline.json. Static-only skips Ractor issues in dependencies.
- name: Install audition
run: gem install audition
- name: Audition (Ractor readiness)
run: audition . --static-only --plain
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ group :development, :test do
gem "allocation_stats"
gem "appraisal", "~> 2"
gem "appraisal-run", "~> 1.1"
gem "audition", require: false
gem "benchmark-ips", "~> 2"
gem "better_html"
gem "bundler"
Expand Down
32 changes: 32 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ GEM
thor (>= 0.14.0)
appraisal-run (1.1.0)
ast (2.4.3)
audition (0.2.1)
pastel (>= 0.8)
prism (>= 1.0)
rubydex (>= 0.2)
table_tennis (>= 1.0)
tty-link (>= 0.2)
base64 (0.3.0)
benchmark-ips (2.15.1)
better_html (2.2.0)
Expand All @@ -116,6 +122,7 @@ GEM
concurrent-ruby (1.3.8)
connection_pool (3.0.2)
crass (1.0.7)
csv (3.3.5)
cuprite (0.17)
capybara (~> 3.0)
ferrum (~> 0.17.0)
Expand All @@ -138,6 +145,7 @@ GEM
concurrent-ruby (~> 1.1)
webrick (~> 1.7)
websocket-driver (~> 0.7)
ffi (1.17.4)
globalid (1.3.0)
activesupport (>= 6.1)
haml (7.2.0)
Expand Down Expand Up @@ -181,6 +189,7 @@ GEM
net-smtp
marcel (1.1.0)
matrix (0.4.3)
memo_wise (1.13.0)
method_source (1.1.0)
mini_mime (1.1.5)
minitest (6.0.6)
Expand Down Expand Up @@ -213,10 +222,13 @@ GEM
racc (~> 1.4)
nokogiri (1.19.4-x86_64-linux-musl)
racc (~> 1.4)
paint (2.3.0)
parallel (1.28.0)
parser (3.3.12.0)
ast (~> 2.4.1)
racc
pastel (0.8.0)
tty-color (~> 0.5)
pp (0.6.3)
prettyprint
prettyprint (0.2.0)
Expand Down Expand Up @@ -326,6 +338,7 @@ GEM
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.47.1, < 2.0)
ruby-progressbar (1.13.0)
rubydex (0.3.0)
rubyzip (3.3.0)
securerandom (0.4.1)
selenium-webdriver (4.44.0)
Expand Down Expand Up @@ -364,6 +377,12 @@ GEM
lint_roller (~> 1.1)
rubocop-performance (~> 1.26.0)
stringio (3.2.0)
table_tennis (1.0.0)
csv (~> 3.3)
ffi (~> 1.17)
memo_wise (~> 1.13)
paint (~> 2.3)
unicode-display_width (~> 3.2)
tailwindcss-rails (4.6.0)
railties (>= 7.0.0)
tailwindcss-ruby (~> 4.0)
Expand All @@ -381,6 +400,8 @@ GEM
tilt (2.7.0)
timeout (0.6.1)
tsort (0.2.0)
tty-color (0.6.0)
tty-link (0.2.0)
turbo-rails (2.0.23)
actionpack (>= 7.1.0)
railties (>= 7.1.0)
Expand Down Expand Up @@ -422,6 +443,7 @@ DEPENDENCIES
allocation_stats
appraisal (~> 2)
appraisal-run (~> 1.1)
audition
benchmark-ips (~> 2)
better_html
bundler
Expand Down Expand Up @@ -478,6 +500,7 @@ CHECKSUMS
appraisal (2.5.0) sha256=36989221be127913b0dba8d114da2001e6b2dceea7bd4951200eaba764eed3ce
appraisal-run (1.1.0) sha256=93179b8fe4d7a3469a703dab69deccc88432bfe43f979acdebbe514e21af9789
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
audition (0.2.1) sha256=7a6661ee96e3fbb5bf97a97b23df9f5649ae4dfd3599ab115192cb62e10a7030
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
benchmark-ips (2.15.1) sha256=07a1a9f3c6105ecaf68c174fc3fbcddd71a0e9ada6236ae03093a0dcfd812d59
better_html (2.2.0) sha256=e68ab66ab09696b708333bbf35e8aa3c107500ba7892f528e2111624bdd8cf76
Expand All @@ -488,6 +511,7 @@ CHECKSUMS
concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
crass (1.0.7) sha256=94868719948664c89ddcaf0a37c65048413dfcb1c869470a5f7a7ceb5390b295
csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f
cuprite (0.17) sha256=b140d5dc70d08b97ad54bcf45cd95d0bd430e291e9dffe76fff851fddd57c12b
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
Expand All @@ -497,6 +521,7 @@ CHECKSUMS
erb_lint (0.9.0) sha256=dfb5e40ad839e8d1f0d56ca85ec9a7ac4c9cd966ec281138282f35b323ca7c31
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
ferrum (0.17.1) sha256=51d591120fc593e5a13b5d9d6474389f5145bb92a91e36eab147b5d096c8cbe7
ffi (1.17.4) sha256=bcd1642e06f0d16fc9e09ac6d49c3a7298b9789bcb58127302f934e437d60acf
globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11
haml (7.2.0) sha256=87fd2b71f7feab1724337b090a7d767f5ab2d42f08c974f3ead673f18cfcd55a
herb (0.9.2) sha256=88364c6394740f58d3795cb754a9f4a861631fe6464f250ac0e739d50727e13c
Expand All @@ -521,6 +546,7 @@ CHECKSUMS
mail (2.9.0) sha256=6fa6673ecd71c60c2d996260f9ee3dd387d4673b8169b502134659ece6d34941
marcel (1.1.0) sha256=fdcfcfa33cc52e93c4308d40e4090a5d4ea279e160a7f6af988260fa970e0bee
matrix (0.4.3) sha256=a0d5ab7ddcc1973ff690ab361b67f359acbb16958d1dc072b8b956a286564c5b
memo_wise (1.13.0) sha256=30220c38c4cef410849bc73553c58664dc2c91c6379e4a1df22aea02358b716b
method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
Expand All @@ -538,8 +564,10 @@ CHECKSUMS
nokogiri (1.19.4-x86_64-darwin) sha256=7fd17057d3e1f00e9954a74b3cd76595d3d4a5ef233b7ed9599047c204f70551
nokogiri (1.19.4-x86_64-linux-gnu) sha256=379fae440b28915e3f19d752ce2dcf8465ed2b2fbefd2a7ca0dd497bc981a06a
nokogiri (1.19.4-x86_64-linux-musl) sha256=17dfb7c1fa194ae02fbf7c51a7afc8d278045ab3fdacfd86f91d02d7b274470b
paint (2.3.0) sha256=327d623e4038619d5bd99ae5db07973859cd78400c7f0329eea283cef8e83be5
parallel (1.28.0) sha256=33e6de1484baf2524792d178b0913fc8eb94c628d6cfe45599ad4458c638c970
parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
pastel (0.8.0) sha256=481da9fb7d2f6e6b1a08faf11fa10363172dc40fd47848f096ae21209f805a75
pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
Expand Down Expand Up @@ -575,6 +603,7 @@ CHECKSUMS
rubocop-md (2.0.4) sha256=0d076b6b5e99dea2ddc928c4bd702497decb9a8400da7808091e02ebcafcfb93
rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
rubydex (0.3.0) sha256=902ba4546c548de6a1908acee54cf3f5b998f01c87e2ad6fbd708011774df4e5
rubyzip (3.3.0) sha256=a372fc67892a4f8c0bc8ec906b720353d8e48807a64b2e63adf99b1e3583a034
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
selenium-webdriver (4.44.0) sha256=6f1df072529af369589c46f0e01132952aabb250cfd683c274d74dc1eb5d8477
Expand All @@ -588,6 +617,7 @@ CHECKSUMS
standard-custom (1.0.2) sha256=424adc84179a074f1a2a309bb9cf7cd6bfdb2b6541f20c6bf9436c0ba22a652b
standard-performance (1.9.0) sha256=49483d31be448292951d80e5e67cdcb576c2502103c7b40aec6f1b6e9c88e3f2
stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
table_tennis (1.0.0) sha256=955f76ca4debd42ee88682af8d4554a74d533c89de4f1581bc37855b15faa956
tailwindcss-rails (4.6.0) sha256=d99512867173d55c5ef8890427682299d8539f550cec1408b3d8667a538bd365
tailwindcss-ruby (4.3.1) sha256=ced3198e057da98f21cb281f4821ac8882a2899047066895ba181caf312620c1
tailwindcss-ruby (4.3.1-aarch64-linux-gnu) sha256=b42af5fdb7ade3f3f70cda217c288986cc6b773601437b8deb80ea229a96680c
Expand All @@ -602,6 +632,8 @@ CHECKSUMS
tilt (2.7.0) sha256=0d5b9ba69f6a36490c64b0eee9f6e9aad517e20dcc848800a06eb116f08c6ab3
timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
tty-color (0.6.0) sha256=6f9c37ca3a4e2367fb2e6d09722762647d6f455c111f05b59f35730eeb24332a
tty-link (0.2.0) sha256=82fa3284910c250c3e10feef0f2d491717467c029c3eb3ac4b4a7c63fddcbfe5
turbo-rails (2.0.23) sha256=ee0d90733aafff056cf51ff11e803d65e43cae258cc55f6492020ec1f9f9315f
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
Expand Down
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

## main

* Add [audition](https://github.com/yaroslav/audition) Ractor-readiness checks to CI. Applied safe `.freeze` auto-fixes to string constants in `ViewComponent::Errors` and baselined existing findings so the gate fails only on new Ractor-isolation violations.

Check failure on line 13 in docs/CHANGELOG.md

View workflow job for this annotation

GitHub Actions / prose

[vale] reported by reviewdog 🐶 [Microsoft.Auto] In general, don't hyphenate 'auto-fixes'. Raw Output: {"message": "[Microsoft.Auto] In general, don't hyphenate 'auto-fixes'.", "location": {"path": "docs/CHANGELOG.md", "range": {"start": {"line": 13, "column": 110}}}, "severity": "ERROR"}

*Joel Hawksley*

* Update link to GOV.UK Components library in resources list to govuk-components.x-govuk.org

*Peter Yates*
Expand Down
44 changes: 22 additions & 22 deletions lib/view_component/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class DuplicateSlotContentError < StandardError
MESSAGE =
"It looks like a block was provided after calling `with_content` on COMPONENT, " \
"which means that ViewComponent doesn't know which content to use.\n\n" \
"To fix this issue, use either `with_content` or a block."
"To fix this issue, use either `with_content` or a block.".freeze

def initialize(klass_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
Expand All @@ -25,13 +25,13 @@ def initialize(errors, templates = nil)
end

class MultipleInlineTemplatesError < BaseError
MESSAGE = "Inline templates can only be defined once per-component."
MESSAGE = "Inline templates can only be defined once per-component.".freeze
end

class MissingPreviewTemplateError < StandardError
MESSAGE =
"A preview template for example EXAMPLE doesn't exist.\n\n" \
"To fix this issue, create a template for the example."
"To fix this issue, create a template for the example.".freeze

def initialize(example)
super(MESSAGE.gsub("EXAMPLE", example))
Expand All @@ -41,7 +41,7 @@ def initialize(example)
class MissingTemplateError < StandardError
MESSAGE =
"No templates for COMPONENT match the request DETAIL.\n\n" \
"To fix this issue, provide a suitable template."
"To fix this issue, provide a suitable template.".freeze

def initialize(component, request_detail)
detail = {
Expand All @@ -58,7 +58,7 @@ class DuplicateContentError < StandardError
MESSAGE =
"It looks like a block was provided after calling `with_content` on COMPONENT, " \
"which means that ViewComponent doesn't know which content to use.\n\n" \
"To fix this issue, use either `with_content` or a block."
"To fix this issue, use either `with_content` or a block.".freeze

def initialize(klass_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
Expand All @@ -70,7 +70,7 @@ class MissingCollectionArgumentError < StandardError
"The initializer for COMPONENT doesn't accept the parameter `PARAMETER`, " \
"which is required to render it as a collection.\n\n" \
"To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \
"See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections."
"See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections.".freeze

def initialize(klass_name, parameter)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
Expand All @@ -80,7 +80,7 @@ def initialize(klass_name, parameter)
class ReservedParameterError < StandardError
MESSAGE =
"COMPONENT initializer can't accept the parameter `PARAMETER`, as it will override a " \
"public ViewComponent method. To fix this issue, rename the parameter."
"public ViewComponent method. To fix this issue, rename the parameter.".freeze

def initialize(klass_name, parameter)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
Expand All @@ -90,14 +90,14 @@ def initialize(klass_name, parameter)
class InvalidCollectionArgumentError < BaseError
MESSAGE =
"The value of the first argument passed to `with_collection` isn't a valid collection. " \
"Make sure it responds to `to_ary`."
"Make sure it responds to `to_ary`.".freeze
end

class ContentSlotNameError < StandardError
MESSAGE =
"COMPONENT declares a slot named content, which is a reserved word in ViewComponent.\n\n" \
"Content passed to a ViewComponent as a block is captured and assigned to the `content` accessor without having to create an explicit slot.\n\n" \
"To fix this issue, either use the `content` accessor directly or choose a different slot name."
"To fix this issue, either use the `content` accessor directly or choose a different slot name.".freeze

def initialize(klass_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
Expand All @@ -107,7 +107,7 @@ def initialize(klass_name)
class InvalidSlotDefinitionError < BaseError
MESSAGE =
"Invalid slot definition. Please pass a class, " \
"string, or callable (that is proc, lambda, etc)"
"string, or callable (that is proc, lambda, etc)".freeze
end

class InvalidSlotNameError < StandardError
Expand All @@ -118,7 +118,7 @@ class SlotPredicateNameError < InvalidSlotNameError
"COMPONENT declares a slot named SLOT_NAME, which ends with a question mark.\n\n" \
"This isn't allowed because the ViewComponent framework already provides predicate " \
"methods ending in `?`.\n\n" \
"To fix this issue, choose a different name."
"To fix this issue, choose a different name.".freeze

def initialize(klass_name, slot_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
Expand All @@ -128,7 +128,7 @@ def initialize(klass_name, slot_name)
class RedefinedSlotError < StandardError
MESSAGE =
"COMPONENT declares the SLOT_NAME slot multiple times.\n\n" \
"To fix this issue, choose a different slot name."
"To fix this issue, choose a different slot name.".freeze

def initialize(klass_name, slot_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
Expand All @@ -138,7 +138,7 @@ def initialize(klass_name, slot_name)
class ReservedSingularSlotNameError < InvalidSlotNameError
MESSAGE =
"COMPONENT declares a slot named SLOT_NAME, which is a reserved word in the ViewComponent framework.\n\n" \
"To fix this issue, choose a different name."
"To fix this issue, choose a different name.".freeze

def initialize(klass_name, slot_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
Expand All @@ -148,7 +148,7 @@ def initialize(klass_name, slot_name)
class ReservedPluralSlotNameError < InvalidSlotNameError
MESSAGE =
"COMPONENT declares a slot named SLOT_NAME, which is a reserved word in the ViewComponent framework.\n\n" \
"To fix this issue, choose a different name."
"To fix this issue, choose a different name.".freeze

def initialize(klass_name, slot_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
Expand All @@ -158,15 +158,15 @@ def initialize(klass_name, slot_name)
class UncountableSlotNameError < InvalidSlotNameError
MESSAGE =
"COMPONENT declares a slot named SLOT_NAME, which is an uncountable word\n\n" \
"To fix this issue, choose a different name."
"To fix this issue, choose a different name.".freeze

def initialize(klass_name, slot_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
end
end

class ContentAlreadySetForPolymorphicSlotError < StandardError
MESSAGE = "Content for slot SLOT_NAME has already been provided."
MESSAGE = "Content for slot SLOT_NAME has already been provided.".freeze

def initialize(slot_name)
super(MESSAGE.gsub("SLOT_NAME", slot_name.to_s))
Expand All @@ -176,7 +176,7 @@ def initialize(slot_name)
class NilWithContentError < BaseError
MESSAGE =
"No content provided to `#with_content` for #{self}.\n\n" \
"To fix this issue, pass a value."
"To fix this issue, pass a value.".freeze
end

class TranslateCalledBeforeRenderError < BaseError
Expand All @@ -185,7 +185,7 @@ class TranslateCalledBeforeRenderError < BaseError
"on the view context that only exists once a ViewComponent is passed to " \
"the Rails render pipeline.\n\n" \
"It's sometimes possible to fix this issue by moving code dependent on " \
"`#translate` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)."
"`#translate` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void).".freeze
end

class HelpersCalledBeforeRenderError < BaseError
Expand All @@ -194,7 +194,7 @@ class HelpersCalledBeforeRenderError < BaseError
"on the view context that only exists once a ViewComponent is passed to " \
"the Rails render pipeline.\n\n" \
"It's sometimes possible to fix this issue by moving code dependent on " \
"`#helpers` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)."
"`#helpers` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void).".freeze
end

class ControllerCalledBeforeRenderError < BaseError
Expand All @@ -203,17 +203,17 @@ class ControllerCalledBeforeRenderError < BaseError
"on the view context that only exists once a ViewComponent is passed to " \
"the Rails render pipeline.\n\n" \
"It's sometimes possible to fix this issue by moving code dependent on " \
"`#controller` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)."
"`#controller` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void).".freeze
end

class SystemTestControllerNefariousPathError < BaseError
MESSAGE = "ViewComponent SystemTest controller attempted to load a file outside of the expected directory."
MESSAGE = "ViewComponent SystemTest controller attempted to load a file outside of the expected directory.".freeze
end

class AlreadyDefinedPolymorphicSlotSetterError < StandardError
MESSAGE =
"A method called 'SETTER_METHOD_NAME' already exists and would be overwritten by the 'SETTER_NAME' polymorphic " \
"slot setter.\n\nPlease choose a different setter name."
"slot setter.\n\nPlease choose a different setter name.".freeze

def initialize(setter_method_name, setter_name)
super(MESSAGE.gsub("SETTER_METHOD_NAME", setter_method_name.to_s).gsub("SETTER_NAME", setter_name.to_s))
Expand Down
Loading