strict_ivars 0.6.0 → 1.0.0.rc1

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: 6e1244d297abc3e3ade0882ca3a8767b63701c42721a7199803ad75b46c566ed
4
- data.tar.gz: c141c19397e2942a40bb049ca77790ca9b29a6de9e0cd30e218162b1e51c4ee7
3
+ metadata.gz: b910c7decefccc4495a320886042153d9178c12f76cda7ccb549c304be2b6725
4
+ data.tar.gz: 8af1c060ed4ad3b1eef1b306c336a5bac415018f19bc73954e056bb47f9d86ce
5
5
  SHA512:
6
- metadata.gz: 8cd2d471ca00c86a910843da2cacf803d71f30d48c946f469a5f18ef34c23b54d99d20897501a769695ff37114b677b8ce25c927a135876d99f1202324ad2e8f
7
- data.tar.gz: ab6d3154078514bb0bd9615bae2cecdda743cb7a3dcd5f55d8571fae1f5201f94d090ebffd582881876f3b66bdcfd2035e4d600b77b7fa3350759f33deb4850a
6
+ metadata.gz: 957162900af7f34fe4e111065f8af1886327a3a3ae5ebdc8de4529869b02d4d71b1d2b27fdbf21ac5aba316e150463d990bdb4c065658b7381d599c520026a9b
7
+ data.tar.gz: 7748495c8e7a2e7cdddbd39b46bcb4e4d217db9b1f4f070fb810445f17b2fcd260356376ee5a7e936790410f83017dc0fdc232a69bdd1da8515204d39e8401ff
data/README.md CHANGED
@@ -1,6 +1,35 @@
1
1
  # Strict Ivars
2
2
 
3
- Strict Ivars is a tiny pre-processor for Ruby that guards your instance variable reads, ensuring the instance variable is actually defined. This helps catch typos nice and early. It‘s especially good when used with [Literal](https://literal.fun).
3
+ Strict Ivars is a tiny pre-processor for Ruby that guards your instance variable reads, ensuring the instance variable is actually defined. This helps catch typos nice and early. It‘s especially good when used with [Literal](https://literal.fun) and [Phlex](https://www.phlex.fun), though it also works with ERB.
4
+
5
+ > [!NOTE]
6
+ > JRuby and TruffleRuby are not currently supported.
7
+
8
+ ## Setup
9
+
10
+ Strict Ivars should be used in apps not libraries. Though you could use it in your library’s test suite.
11
+
12
+ Install 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.
13
+
14
+ ```ruby
15
+ gem "strict_ivars", require: false
16
+ ```
17
+
18
+ Now 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.
19
+
20
+ ```ruby
21
+ require "strict_ivars"
22
+ ```
23
+
24
+ You can pass an array of globs to `StrictIvars.init` as `include:` and `exclude:`
25
+
26
+ ```ruby
27
+ StrictIvars.init(include: ["#{Dir.pwd}/**/*"], exclude: ["#{Dir.pwd}/vendor/**/*"])
28
+ ```
29
+
30
+ This example include everything in the current directory apart from the `./vendor` folder (which is where GitHub Actions installs gems).
31
+
32
+ If you’re using Bootsnap, you should clear your bootsnap cache by deleting the folder `tmp/cache/bootsnap`.
4
33
 
5
34
  ## How does it work?
6
35
 
@@ -24,8 +53,6 @@ end
24
53
 
25
54
  The 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.
26
55
 
27
- The real guard is a little uglier than this. It uses `::Kernel.raise` so it’s compatible with `BasicObject`. It also raises a `StrictIvars::NameError` with a helpful message mentioning the name of the instance variable, and that inherits from `NameError`, allowing you to rescue either `NameError` or `StrictIvars::NameError`.
28
-
29
56
  **Writes:**
30
57
 
31
58
  Strict Ivars doesn’t apply to writes, since these are considered the authoritative source of the instance variable definitions.
@@ -36,7 +63,7 @@ Strict Ivars doesn’t apply to writes, since these are considered the authorita
36
63
 
37
64
  **Or-writes:**
38
65
 
39
- This is considered a definition and not guarded.
66
+ Or-writes are considered an authoritative definition, not a read.
40
67
 
41
68
  ```ruby
42
69
  @foo ||= 1
@@ -44,47 +71,67 @@ This is considered a definition and not guarded.
44
71
 
45
72
  **And-writes:**
46
73
 
47
- This is considered a definition and not guarded.
74
+ And-writes are considered an authoritative definition, not a read.
48
75
 
49
76
  ```ruby
50
77
  @foo &&= 1
51
78
  ```
52
79
 
53
- ## Setup
54
-
55
- Install the gem by adding it to your `Gemfile` and running `bundle install`.
80
+ ## Common issues
56
81
 
57
- You may want to set it to `require: false` here because you should require it manually at precisely the right moment.
82
+ #### Implicitly depending on undefined instance variables
58
83
 
59
84
  ```ruby
60
- gem "strict_ivars", require: false
85
+ def description
86
+ return @description if @description.present?
87
+ @description = get_description
88
+ end
61
89
  ```
62
90
 
63
- Now 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.
91
+ This 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.
64
92
 
65
93
  ```ruby
66
- require "strict_ivars"
94
+ def description
95
+ return @description if defined?(@description)
96
+ @description = get_description
97
+ end
98
+ ```
67
99
 
68
- StrictIvars.init(include: ["#{Dir.pwd}/**/*"], exclude: ["#{Dir.pwd}/vendor/**/*"])
100
+ Alternatively, as long as `get_description` doesn’t return `nil` and expect us to memoize it, we could use an “or-write” `||=`
101
+
102
+ ```ruby
103
+ def description
104
+ @description ||= get_description
105
+ end
69
106
  ```
70
107
 
71
- You can pass an array of globs to `include:` and `exclude:`.
108
+ #### Rendering instance variables that are only set somtimes
72
109
 
73
- ## Compatibility
110
+ It’s common to render an instance variable in an ERB view that you only set on some controllers.
74
111
 
75
- Because Strict Ivars only transforms the source code that matches your include paths and becuase the check happens at runtime, it’s completely compatible with the rest of the Ruby ecosystem.
112
+ ```erb
113
+ <div data-favourites="<%= @user_favourites %>"></div>
114
+ ```
76
115
 
77
- #### For apps
116
+ The 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.
78
117
 
79
- Strict Ivars is really designed for apps, where you control the boot process and you want some extra safety in the code you and your team writes.
118
+ Alternatively, you could update the view to be explicit about the fact this ivar may not be set.
80
119
 
81
- #### For libraries
120
+ ```erb
121
+ <div data-favourites="<%= (@user_favourites ||= nil) %>"></div>
122
+ ```
82
123
 
83
- You could use Strict Ivars as a dev dependency in your gem’s test suite, but I don’t recommend initializing Strict Ivars in a library directly.
124
+ Better yet, add a `defined?` check:
125
+
126
+ ```erb
127
+ <% if defined?(@user_favourites) %>
128
+ <div data-favourites="<%= @user_favourites %>"></div>
129
+ <% end %>
130
+ ```
84
131
 
85
132
  ## Performance
86
133
 
87
- #### Startup performance
134
+ #### Boot performance
88
135
 
89
136
  Using 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.
90
137
 
@@ -92,12 +139,14 @@ Using Strict Ivars will impact startup performance since it needs to process eac
92
139
 
93
140
  In 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.
94
141
 
95
- On 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.
142
+ On 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.
143
+
144
+ #### Dynamic evals
96
145
 
97
- All this is to say, I don’t think there will be any measurable runtime performance impact.
146
+ There 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.
98
147
 
99
148
  ## Uninstall
100
149
 
101
150
  Becuase Strict Ivars only ever makes your code safer, you can always back out without anything breaking.
102
151
 
103
- To 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 were 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`.
152
+ To 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`.
@@ -41,12 +41,12 @@ class StrictIvars::BaseProcessor < Prism::Visitor
41
41
  @annotations.push(
42
42
  [receiver_location.start_character_offset, "(#{receiver_local} = "],
43
43
  [receiver_location.end_character_offset, ")"],
44
- [location.start_character_offset, "*(::StrictIvars.process_eval_args(#{receiver_local}, :#{name}, "],
44
+ [location.start_character_offset, "*(::StrictIvars.__process_eval_args__(#{receiver_local}, :#{name}, "],
45
45
  [location.end_character_offset, "))"]
46
46
  )
47
47
  else
48
48
  @annotations.push(
49
- [location.start_character_offset, "*(::StrictIvars.process_eval_args(self, :#{name}, "],
49
+ [location.start_character_offset, "*(::StrictIvars.__process_eval_args__(self, :#{name}, "],
50
50
  [location.end_character_offset, "))"]
51
51
  )
52
52
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StrictIvars
4
- VERSION = "0.6.0"
4
+ VERSION = "1.0.0.rc1"
5
5
  end
data/lib/strict_ivars.rb CHANGED
@@ -16,6 +16,16 @@ module StrictIvars
16
16
 
17
17
  CONFIG = Configuration.new
18
18
 
19
+ # Initializes StrictIvars so that code loaded after this point will be
20
+ # guarded against undefined instance variable reads. You can pass an array
21
+ # of globs to `include:` and `exclude:`.
22
+ #
23
+ # ```ruby
24
+ # StrictIvars.init(
25
+ # include: ["#{Dir.pwd}/**/*"],
26
+ # exclude: ["#{Dir.pwd}/vendor/**/*"]
27
+ # )
28
+ # ```
19
29
  #: (include: Array[String], exclude: Array[String]) -> void
20
30
  def self.init(include: EMPTY_ARRAY, exclude: EMPTY_ARRAY)
21
31
  CONFIG.include(*include) unless include.length == 0
@@ -35,7 +45,9 @@ module StrictIvars
35
45
  end
36
46
  end
37
47
 
38
- def self.process_eval_args(receiver, method_name, *args)
48
+ # For internal use only. This method pre-processes arguments to an eval method.
49
+ #: (Object, Symbol, *untyped)
50
+ def self.__process_eval_args__(receiver, method_name, *args)
39
51
  method = METHOD_METHOD.bind_call(receiver, method_name)
40
52
  owner = method.owner
41
53
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strict_ivars
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 1.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Drapper