https://bugs.ruby-lang.org/https://bugs.ruby-lang.org/favicon.ico?17113305112022-08-27T14:37:01ZRuby Issue Tracking SystemRuby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=989722022-08-27T14:37:01Znobu (Nobuyoshi Nakada)nobu@ruby-lang.org
<ul></ul><p>I agree with the concept, but wonder about these names.<br>
The meaning of <code>with_something_enabled</code> is obvious, but sole <code>with</code> feels vague a little, to me.</p>
<p>And <code>old_values</code> should be initialized as an empty hash, I think.</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=989732022-08-27T14:38:35Zbyroot (Jean Boussier)byroot@ruby-lang.org
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/98973/diff?detail_id=63063">diff</a>)</li></ul> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=989742022-08-27T14:58:32ZEregon (Benoit Daloze)
<ul></ul><p>I think this would be nice to have, indeed spelling it out manually is quite verbose and then we tend to have lots of helper methods for this.<br>
For the name, <code>with</code> seems natural to me, and it's also the name of the keyword in Python used for a somewhat similar purpose (a try-with-resource).</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=989752022-08-27T15:06:08Zbyroot (Jean Boussier)byroot@ruby-lang.org
<ul></ul><blockquote>
<p>sole with feels vague a little, to me.</p>
</blockquote>
<p>Well, the argument is that it's not alone, since it is used with at list one keyword argument.</p>
<p><code>something.with</code> is vague I agree, but I believe that e.g.:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">GC</span><span class="p">.</span><span class="nf">with</span><span class="p">(</span><span class="ss">stress: </span><span class="kp">true</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># test something</span>
<span class="k">end</span>
</code></pre>
<p>Is just as clear as:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">GC</span><span class="p">.</span><span class="nf">with_stress</span><span class="p">(</span><span class="kp">true</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># </span>
<span class="k">end</span>
</code></pre>
<p>or</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">GC</span><span class="p">.</span><span class="nf">with_stress</span> <span class="k">do</span>
<span class="c1">#</span>
<span class="k">end</span>
</code></pre> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=989762022-08-27T15:14:58Zretro (Josef Šimánek)
<ul></ul><p>There are similar methods (<code>with_*</code>) across various popular gems. For example <code>I18n.with_locale(locale)</code>. But <code>WITH</code> is also SQL keyword and was recently added to <code>ActiveRecord</code> (<a href="https://github.com/rails/rails/commit/098b0eb5dbcf1a942c4e727dcdc150151bea51ab" class="external">https://github.com/rails/rails/commit/098b0eb5dbcf1a942c4e727dcdc150151bea51ab</a>).</p>
<p>I was thinking about extending <code>tap</code>.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">I18n</span><span class="p">.</span><span class="nf">tap</span><span class="p">(</span><span class="ss">with: </span><span class="p">{</span><span class="ss">locale: :end</span><span class="p">})</span> <span class="k">do</span>
<span class="c1">#</span>
<span class="k">end</span>
</code></pre>
<p>but that seems to verbose. Other option would be to introduce <code>tap_with</code> method.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">I18n</span><span class="p">.</span><span class="nf">tap_with</span><span class="p">(</span><span class="ss">locale: :end</span><span class="p">)</span> <span class="k">do</span>
<span class="c1">#</span>
<span class="k">end</span>
</code></pre> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=989772022-08-27T15:21:59Zbyroot (Jean Boussier)byroot@ruby-lang.org
<ul></ul><blockquote>
<p>But WITH is also SQL keyword and was recently added to ActiveRecord</p>
</blockquote>
<p>It was added to <code>ActiveRecord::Relation</code>, which doesn't have any setters in its public API, and even if it did it still wouldn't be a concern in my opinion.</p>
<p>Since you pass attribute names that correspond to actual methods (accessors) on the object, it means the code needs to know the interface the object responds to, so presumably you also know <code>Object#with</code> wasn't overridden.</p>
<p>So if some existing classes already define a <code>#with</code> method with a different meaning it's not a problem at all.</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=990042022-08-29T12:08:55Zufuk (Ufuk Kayserilioglu)
<ul></ul><p>I'll add my 2 cents as well: I really really like the proposed idea and I would love to see it added to Ruby for 3.2.</p>
<p>However, I find <code>with</code> a little too generic as a name for the concept. Primarily, when I look at it, it doesn't tell me what will it do "with" the supplied arguments; the fact that it will execute the given block with the supplied arguments is a little hidden.</p>
<p>For that reason, I think an alternative name could be <code>exec_with</code>:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">GC</span><span class="p">.</span><span class="nf">exec_with</span><span class="p">(</span><span class="ss">stress: </span><span class="kp">true</span><span class="p">)</span> <span class="k">do</span>
<span class="c1">#</span>
<span class="k">end</span>
</code></pre>
<p>or <code>run_with</code>:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">GC</span><span class="p">.</span><span class="nf">run_with</span><span class="p">(</span><span class="ss">stress: </span><span class="kp">true</span><span class="p">)</span> <span class="k">do</span>
<span class="c1">#</span>
<span class="k">end</span>
</code></pre>
<p>or any other name that conveys the fact that the block will be executed with the supplied arguments.</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=990052022-08-29T12:31:10Zbyroot (Jean Boussier)byroot@ruby-lang.org
<ul></ul><p>I feel like in the do/end form it's quite clear:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">obj</span><span class="p">.</span><span class="nf">with</span><span class="p">(</span><span class="ss">foo: </span><span class="s2">"bar"</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># do something</span>
<span class="k">end</span>
</code></pre>
<p>Just like <code>5.times do</code> doesn't need to be named <code>exec_times</code> to be clear.</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=990082022-08-29T14:11:07Zngan (Ngan Pham)
<ul></ul><p>I really like Rails’ <code>with_options</code>:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Account</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="n">with_options</span> <span class="ss">dependent: :destroy</span> <span class="k">do</span> <span class="o">|</span><span class="n">assoc</span><span class="o">|</span>
<span class="n">assoc</span><span class="p">.</span><span class="nf">has_many</span> <span class="ss">:customers</span>
<span class="n">assoc</span><span class="p">.</span><span class="nf">has_many</span> <span class="ss">:products</span>
<span class="n">assoc</span><span class="p">.</span><span class="nf">has_many</span> <span class="ss">:invoices</span>
<span class="n">assoc</span><span class="p">.</span><span class="nf">has_many</span> <span class="ss">:expenses</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>I’m thinking if it’s called <code>with</code>, it would be a little less clear. Since we’re essentially setting attributes, and Ruby uses “attrs” (<code>attr_reader</code>, <code>attr_writer</code>), maybe we should call it <code>with_attrs</code> or <code>with_attributes</code>.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">obj</span><span class="p">.</span><span class="nf">with_attrs</span><span class="p">(</span><span class="ss">foo: </span><span class="s2">"bar"</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># do something</span>
<span class="k">end</span>
</code></pre> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=990422022-08-31T20:05:36ZDan0042 (Daniel DeLorme)
<ul></ul><p>Also really like the idea, also ambivalent about such a short and generic name.</p>
<p>I think it would be good if the name emphasized a bit more that the original values will be restored after the block. Something in the vein of <code>temporarily_with</code></p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=990442022-08-31T21:12:40Zaustin (Austin Ziegler)halostatue@gmail.com
<ul></ul><p>Dan0042 (Daniel DeLorme) wrote in <a href="#note-10">#note-10</a>:</p>
<blockquote>
<p>Also really like the idea, also ambivalent about such a short and generic name.</p>
<p>I think it would be good if the name emphasized a bit more that the original values will be restored after the block. Something in the vein of <code>temporarily_with</code></p>
</blockquote>
<ul>
<li><code>#override_with</code></li>
<li><code>#mut</code></li>
<li><code>#mutate</code></li>
<li><code>#borrow_with</code></li>
<li><code>#with_restore</code></li>
<li><code>#restore_do</code></li>
<li><code>#tap_restore</code></li>
<li><code>#tap_reset</code></li>
<li><code>#override_reset</code></li>
<li><code>#overlay_with</code></li>
<li><code>#overlay</code></li>
<li><code>#overlay_reset</code></li>
</ul>
<p>This could do something with <code>Object#extend</code> or maybe a temporary refinement (I have yet to <em>use</em> refinements, so I’m going to avoid trying to make an example that way).</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">module</span> <span class="nn">RestorableOverlay</span>
<span class="k">def</span> <span class="nf">overlay</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
<span class="p">(</span><span class="vi">@__restoreable_overlay__</span> <span class="o">||=</span> <span class="p">[])</span> <span class="o"><<</span> <span class="p">{}</span>
<span class="n">values</span><span class="p">.</span><span class="nf">each_pair</span> <span class="p">{</span> <span class="o">|</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="o">|</span>
<span class="vi">@__restorable_overlay__</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="nb">send</span><span class="p">(</span><span class="ss">:"</span><span class="si">#{</span><span class="n">k</span><span class="si">}</span><span class="ss">"</span><span class="p">)</span>
<span class="nb">send</span><span class="p">(</span><span class="ss">:"</span><span class="si">#{</span><span class="n">k</span><span class="si">}</span><span class="ss">="</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">overlay_commit</span>
<span class="vi">@__restorable_overlay__</span><span class="p">.</span><span class="nf">pop</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">overlay_rollback</span>
<span class="n">overlay_commit</span><span class="p">.</span><span class="nf">each_pair</span> <span class="p">{</span> <span class="o">|</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="o">|</span> <span class="nb">send</span><span class="p">(</span><span class="ss">:"</span><span class="si">#{</span><span class="n">k</span><span class="si">}</span><span class="ss">="</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span> <span class="p">}</span>
<span class="c1"># magic function that doesn’t exist</span>
<span class="k">if</span> <span class="vi">@__restorable_overlay__</span><span class="p">.</span><span class="nf">empty?</span>
<span class="n">unextend</span> <span class="no">RestorableOverlay</span>
<span class="n">instance_variable_del</span><span class="p">(</span><span class="ss">:@__restorable_overlay__</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">Object</span>
<span class="k">def</span> <span class="nf">overlay_with</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
<span class="kp">extend</span><span class="p">(</span><span class="no">RestorableOverlay</span><span class="p">)</span> <span class="k">unless</span> <span class="n">extended_with?</span><span class="p">(</span><span class="no">RestorableOverlay</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">block_given?</span>
<span class="k">begin</span>
<span class="n">overlay</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
<span class="k">yield</span>
<span class="k">ensure</span>
<span class="n">overlay_rollback</span>
<span class="k">end</span>
<span class="k">else</span>
<span class="n">overlay</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>I had done something <em>similar</em> back in 2004 while working with PDF::Writer (Ruby 1.8 mostly), originally released as <a href="https://github.com/halostatue/transaction-simple" class="external">https://github.com/halostatue/transaction-simple</a>. An absolute memory hog, broke parent / child ownership cycles, and slow as can be, but it <em>worked</em> for the most part.</p>
<p>Maybe worth revisiting?</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=990492022-09-01T10:37:15Zp8 (Petrik de Heus)
<ul></ul><p>Or maybe making the restore more explicit</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">obj</span><span class="p">.</span><span class="nf">assign_attrs</span><span class="p">(</span><span class="ss">foo: </span><span class="s2">"bar"</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># do something</span>
<span class="k">end</span><span class="p">.</span><span class="nf">restore</span>
</code></pre>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">obj</span><span class="p">.</span><span class="nf">assign_attrs</span><span class="p">(</span><span class="ss">foo: </span><span class="s2">"bar"</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">restorable</span><span class="o">|</span>
<span class="c1"># do something</span>
<span class="k">ensure</span>
<span class="n">restorable</span><span class="p">.</span><span class="nf">restore</span>
<span class="k">end</span>
</code></pre>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">obj</span><span class="p">.</span><span class="nf">public_send_all</span><span class="p">(</span><span class="ss">foo: </span><span class="s2">"bar"</span><span class="p">,</span> <span class="ss">baz: </span><span class="s2">"qux"</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># do something</span>
<span class="k">end</span><span class="p">.</span><span class="nf">undo</span>
</code></pre> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=991222022-09-11T02:34:17Zretro (Josef Šimánek)
<ul></ul><p>It would be super nice to somehow support <code>ENV</code> as well, since it is super common pattern in test suites.</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=991232022-09-11T02:49:41Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<ul></ul><p>This idea is very similar to Algebraic effects, which dry-rb has a variant on:</p>
<p><a href="https://dry-rb.org/gems/dry-effects/main/" class="external">https://dry-rb.org/gems/dry-effects/main/</a></p>
<p>I would personally like the <code>with</code> variant myself, granting that it is a bit vague, but is also in line with what a lot of effects style libraries are using.</p>
<p>I believe Dan Abramov has done a good job of explaining this concept:</p>
<p><a href="https://overreacted.io/algebraic-effects-for-the-rest-of-us/" class="external">https://overreacted.io/algebraic-effects-for-the-rest-of-us/</a></p>
<p>Mostly noting that there is precedent for this type of pattern in more functionally oriented languages.</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=992192022-09-21T05:58:29Zko1 (Koichi Sasada)
<ul></ul><p>On the other languages, JavaScript, C#, ... has different meaning (maybe similar to <code>instance_eval</code>) so I think the 1 word <code>with</code> is not good name.</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=992202022-09-21T06:03:43Zbyroot (Jean Boussier)byroot@ruby-lang.org
<ul></ul><p>I'm not very familiar with C#, but <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with" class="external">Javascript's <code>with</code> is deprecated</a>, and it's extremely rare to see it. I doubt most JavaScript developers even know about it.</p>
<p>If it was a very commonly used feature I would agree with the argument, but I don't think we should feel constrained by obscure features from other languages.</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=993002022-09-23T15:40:27ZDan0042 (Daniel DeLorme)
<ul></ul><p><code>#with</code> is not a good name. For a method defined on Object, the semantics should be the same on all classes in ruby. For example <code>#dup</code> does the same thing (create a duplicate) on all objects, even if the implemention is class-specific.</p>
<p>Examples which did not follow this rule and created problems in the past:<br>
<code>#id</code> was renamed to <code>#__id__</code> and <code>#object_id</code> because it often means the id of a DB record (in ActiveRecord most notably)<br>
<code>#method</code> means something different for ActionDispatch::Request<br>
<code>#send</code> means something different for BasicSocket</p>
<p>But <code>#with</code> is already defined on a lot of gems (<a href="https://pastebin.com/y1aMF53Y" class="external">https://pastebin.com/y1aMF53Y</a>) with behavior different from what is described above. Often the behavior is similar to the one requested for <code>Data#with</code> in <a class="issue tracker-2 status-5 priority-4 priority-default closed" title="Feature: Data: Add "Copy with changes method" [Follow-on to #16122 Data: simple immutable value object] (Closed)" href="https://bugs.ruby-lang.org/issues/19000">#19000</a>; returning a new instance with certain properties modified. That being said, it's common to use a bang for the mutating version of a non-mutating method, so I think <code>Object#with!</code> would make a fair amount of sense here.</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=1001002022-11-15T09:18:06Zbyroot (Jean Boussier)byroot@ruby-lang.org
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/100100/diff?detail_id=63508">diff</a>)</li></ul><p>I discussed this very quickly with <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/13">@matz (Yukihiro Matsumoto)</a> at RWC, and it seems the two main concerns are:</p>
<ul>
<li>
<code>with</code> is seen as too generic, so I propose <code>with_attr</code> instead.</li>
<li>The use case isn't seen as common enough, so I added 3 real world example in the description, If that's not enough I can add as much as you want, this pattern is extremely common.</li>
</ul> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=1003832022-12-01T07:36:55Zmatz (Yukihiro Matsumoto)matz@ruby.or.jp
<ul></ul><ul>
<li>
<code>with_attr</code> is better than plain <code>with</code>
</li>
<li>this method can be useful for some cases, but I am not sure if it should be a method of Object class</li>
<li>maybe it should be a utility method in a gem (e.g. <code>save_current_attr(obj, **kw) {....}</code>)</li>
</ul>
<p>Matz.</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=1003842022-12-01T07:38:44Zbyroot (Jean Boussier)byroot@ruby-lang.org
<ul></ul><p>Thank you Matz.</p>
<p>If it's not desired in ruby-core, I can add it to Active Support, that's no problem.</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=1005452022-12-10T17:10:57Zretro (Josef Šimánek)
<ul></ul><blockquote>
<p>The use case isn't seen as common enough, so I added 3 real world example in the description, If that's not enough I can add as much as you want, this pattern is extremely common.</p>
</blockquote>
<p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/13">@matz (Yukihiro Matsumoto)</a> I have seen this pattern repeated in almost every mid-sized project test suites. It is also present in various popular gems like I18n and Globalize. I can confirm in my eyes it is extremely common one as well.</p>
<p><a href="https://github.com/globalize/globalize/blob/3c146abbba4200aed1bbdbf3e63d5729a9dd95a1/lib/globalize.rb#L33-L42" class="external">https://github.com/globalize/globalize/blob/3c146abbba4200aed1bbdbf3e63d5729a9dd95a1/lib/globalize.rb#L33-L42</a><br>
<a href="https://github.com/ruby-i18n/i18n/blob/75fc49b08d254ad657ebd589ad37cda3c6fe7cec/lib/i18n.rb#L315-L327" class="external">https://github.com/ruby-i18n/i18n/blob/75fc49b08d254ad657ebd589ad37cda3c6fe7cec/lib/i18n.rb#L315-L327</a></p>
<p>It is also common on <code>ENV</code> (to temporarily change <code>ENV</code>). Projects like rubygems and bundler exposing a lot of ENV vars can extremely benefit from this in test suites.</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=1021562023-03-06T08:18:46Zbyroot (Jean Boussier)byroot@ruby-lang.org
<ul><li><strong>Status</strong> changed from <i>Open</i> to <i>Rejected</i></li></ul><p>Marking this as rejected.</p>
<p>Will be in Active Support 7.1</p> Ruby master - Feature #18951: Object#with to set and restore attributes around a blockhttps://bugs.ruby-lang.org/issues/18951?journal_id=1024402023-03-17T01:15:07Zdsisnero (Dominic Sisneros)dsisnero@gmail.com
<ul></ul><p>temp_set might be a better method name</p>