{"id":28089353,"url":"https://github.com/yippee-fun/strict_ivars","last_synced_at":"2025-09-15T02:18:50.262Z","repository":{"id":290053757,"uuid":"973242959","full_name":"yippee-fun/strict_ivars","owner":"yippee-fun","description":"Make Ruby raise a NameError if you read an undefined instance variable.","archived":false,"fork":false,"pushed_at":"2025-06-20T11:14:09.000Z","size":100,"stargazers_count":66,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-28T17:59:26.909Z","etag":null,"topics":["ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yippee-fun.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-26T15:16:58.000Z","updated_at":"2025-08-22T11:56:34.000Z","dependencies_parsed_at":"2025-05-13T13:04:13.647Z","dependency_job_id":"7294eaa9-3532-4858-8284-219cef92fca1","html_url":"https://github.com/yippee-fun/strict_ivars","commit_stats":null,"previous_names":["joeldrapper/aye_var","joeldrapper/strict_ivars","yippee-fun/strict_ivars"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/yippee-fun/strict_ivars","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yippee-fun%2Fstrict_ivars","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yippee-fun%2Fstrict_ivars/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yippee-fun%2Fstrict_ivars/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yippee-fun%2Fstrict_ivars/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yippee-fun","download_url":"https://codeload.github.com/yippee-fun/strict_ivars/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yippee-fun%2Fstrict_ivars/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275194021,"owners_count":25421441,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-15T02:00:09.272Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ruby"],"created_at":"2025-05-13T12:57:28.123Z","updated_at":"2025-09-15T02:18:50.254Z","avatar_url":"https://github.com/yippee-fun.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":" # Strict Ivars\n\nIf you reference an undefined method, constant or local varaible, Ruby will helpfully raise an error. But reference an undefined _instance_ variable and Ruby just returns `nil`. This can lead to all kinds of bugs — many of which can lay dormant for years before surprising you with an unexpected outage, data breach or data loss event.\n\nStrict Ivars solves this by making Ruby raise a `NameError` any time you read an undefined instance varaible. It’s enabled with two lines of code in your boot process, then it just works in the background and you’ll never have to think about it again. Strict Ivars has no known false-positives or false-negatives.\n\nIt’s especially good when used with [Literal](https://literal.fun) and [Phlex](https://www.phlex.fun), though it also works with regular Ruby objects and even ERB templates, which are actually pretty common spots for undefined instance variable reads to hide since that’s the main way of passing data to ERB.\n\nWhen combined with Literal, you can essentially remove all unexpected `nil`s. Literal validates your inputs and Strict Ivars ensures you’re reading the right instance variables.\n\n\u003e [!NOTE]\n\u003e JRuby and TruffleRuby are not currently supported.\n\n## Setup\n\nStrict Ivars should really be used in apps not libraries. Though you could definitely use it in your library’s test suite to help catch issues in the library code.\n\nInstall the gem by adding it to your `Gemfile` and running `bundle install`. You’ll probably want to set it to `require: false` here because you should require it manually at precisely the right moment.\n\n```ruby\ngem \"strict_ivars\", require: false\n```\n\nNow the gem is installed, you should require and initialize the gem as early as possible in your boot process. Ideally, this should be right after Bootsnap is set up. In Rails, this will be in your `boot.rb` file.\n\n```ruby\nrequire \"strict_ivars\"\n```\n\nYou can pass an array of globs to `StrictIvars.init` as `include:` and `exclude:`\n\n```ruby\nStrictIvars.init(include: [\"#{Dir.pwd}/**/*\"], exclude: [\"#{Dir.pwd}/vendor/**/*\"])\n```\n\nThis example include everything in the current directory apart from the `./vendor` folder (which is where GitHub Actions installs gems).\n\nIf you’re setting this up in Rails, your `boot.rb` file should look something like this.\n\n```ruby\nENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../Gemfile\", __dir__)\n\nrequire \"bundler/setup\" # Set up gems listed in the Gemfile.\nrequire \"bootsnap/setup\" # Speed up boot time by caching expensive operations.\n\nrequire \"strict_ivars\"\n\nStrictIvars.init(include: [\"#{Dir.pwd}/**/*\"], exclude: [\"#{Dir.pwd}/vendor/**/*\"])\n```\n\nIf you’re using Bootsnap, you should clear your bootsnap cache by deleting the folder `tmp/cache/bootsnap`.\n\n## How does it work?\n\nWhen Strict Ivars detects that you are loading code from paths its configured to handle, it quickly looks for instance variable reads and guards them with a `defined?` check.\n\nFor example, it will replace this:\n\n```ruby\ndef example\n  foo if @bar\nend\n```\n\n...with something like this:\n\n```ruby\ndef example\n  foo if (defined?(@bar) ? @bar : raise)\nend\n```\n\nThe replacement happens on load, so you never see this in your source code. It’s also always wrapped in parentheses and takes up a single line, so it won’t mess up the line numbers in exceptions.\n\n**Writes:**\n\nStrict Ivars doesn’t apply to writes, since these are considered the authoritative source of the instance variable definitions.\n\n```ruby\n@foo = 1\n```\n\n**Or-writes:**\n\nOr-writes are considered an authoritative definition, not a read.\n\n```ruby\n@foo ||= 1\n```\n\n**And-writes:**\n\nAnd-writes are considered an authoritative definition, not a read.\n\n```ruby\n@foo \u0026\u0026= 1\n```\n\n## Common mistakes\n\n#### Implicitly depending on undefined instance variables\n\n```ruby\ndef description\n  return @description if @description.present?\n  @description = get_description\nend\n```\n\nThis example is relying on Ruby’s behaviour of returning `nil` for undefiend instance variables, which is completely unnecessary. Instead of using `present?`, we could use `defined?` here.\n\n```ruby\ndef description\n  return @description if defined?(@description)\n  @description = get_description\nend\n```\n\nAlternatively, as long as `get_description` doesn’t return `nil` and expect us to memoize it, we could use an “or-write” `||=`\n\n```ruby\ndef description\n  @description ||= get_description\nend\n```\n\n#### Rendering instance variables that are only set somtimes\n\nIt’s common to render an instance variable in an ERB view that you only set on some controllers.\n\n```erb\n\u003cdiv data-favourites=\"\u003c%= @user_favourites %\u003e\"\u003e\u003c/div\u003e\n```\n\nThe best solution to this to always set it on all controllers, but set it to `nil` in the cases where you don’t have anything to render. This will prevent you from making a typo in your views.\n\nAlternatively, you could update the view to be explicit about the fact this ivar may not be set.\n\n```erb\n\u003cdiv data-favourites=\"\u003c%= (@user_favourites ||= nil) %\u003e\"\u003e\u003c/div\u003e\n```\n\nBetter yet, add a `defined?` check:\n\n```erb\n\u003c% if defined?(@user_favourites) %\u003e\n  \u003cdiv data-favourites=\"\u003c%= @user_favourites %\u003e\"\u003e\u003c/div\u003e\n\u003c% end %\u003e\n```\n\n## Performance\n\n#### Boot performance\n\nUsing Strict Ivars will impact startup performance since it needs to process each Ruby file you require. However, if you are using Bootsnap, the processed RubyVM::InstructionSequences will be cached and you probably won’t notice the incremental cache misses day-to-day.\n\n#### Runtime performance\n\nIn my benchmarks on Ruby 3.4 with YJIT, it’s difficult to tell if there is any performance difference with or without the `defined?` guards at runtime. Sometimes it’s about 1% faster with the guards than without. Sometimes the other way around.\n\nOn my laptop, a method that returns an instance varible takes about 15ns and a method that checks if an instance varible is defined and then returns it takes about 15ns. All this is to say, I don’t think there will be any measurable runtime performance impact, at least not in Ruby 3.4.\n\n#### Dynamic evals\n\nThere is a small additional cost to dynamically evaluating code via `eval`, `class_eval`, `module_eval`, `instance_eval` and `binding.eval`. Dynamic evaluation usually only happens at boot time but it can happen at runtime depending on how you use it.\n\n## Stability\n\nStrict Ivars has 100% line and branch coverage and there are no known false-positives, false-negatives or bugs.\n\n## Uninstall\n\nBecuase Strict Ivars only ever makes your code safer, you can always back out without anything breaking.\n\nTo uninstall Strict Ivars, first remove the require and initialization code from wherever you added it and then remove the gem from your `Gemfile`. If you are using Bootsnap, there’s a good chance it cached some pre-processed code with the instance variable read guards in it. To clear this, you’ll need to delete your bootsnap cache, which should be in `tmp/cache/bootsnap`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyippee-fun%2Fstrict_ivars","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyippee-fun%2Fstrict_ivars","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyippee-fun%2Fstrict_ivars/lists"}