https://bugs.ruby-lang.org/https://bugs.ruby-lang.org/favicon.ico?17113305112019-05-01T17:09:34ZRuby Issue Tracking SystemRuby master - Feature #15815: Add option to raise NoMethodError for OpenStructhttps://bugs.ruby-lang.org/issues/15815?journal_id=778822019-05-01T17:09:34Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<ul></ul><p>It's hard to decide on features of <code>OpenStruct</code> as it's somewhat an antipattern.</p>
<p>Note that you might be able to write <code>users.map{@[:id]}</code> in the next version and avoid using an <code>OpenStruct</code> altogether.</p>
<p>That being said, I am wondering if a frozen <code>OpenStruct</code> could possibly raise an error upon reading an undefined key.</p> Ruby master - Feature #15815: Add option to raise NoMethodError for OpenStructhttps://bugs.ruby-lang.org/issues/15815?journal_id=778832019-05-01T17:47:56Zshevegen (Robert A. Heiler)shevegen@gmail.com
<ul></ul><blockquote>
<p>Note that you might be able to write <code>users.map{@[:id]}</code> in the next version and<br>
avoid using an <code>OpenStruct</code> altogether.</p>
</blockquote>
<p>I think that the idiom, or "mental mode", e. g. how users use ruby, is a bit different<br>
between the two examples. At the least to me, "OpenStruct" is instantly recognizable<br>
and "exception: true" is also no longer a rare idiom, after some core methods accepted<br>
it; the map {@[:id] variant, at the least to me, is slightly harder to understand for<br>
my brain. But anyway, that's just my opinion - to the suggestion itself, one described<br>
use case:</p>
<blockquote>
<p>But I want to prevent typo for a key name.</p>
</blockquote>
<p>Although I personally do not use struct and openstruct a lot, even though I think it is<br>
a cool idea (prototypical objects all the way, at all times), to me the suggestion appears<br>
useful. So I am in light support of the suggestion; I don't feel particularly strong<br>
either way, though. (May help for people to comment who actually use struct and openstruct<br>
a lot.)</p> Ruby master - Feature #15815: Add option to raise NoMethodError for OpenStructhttps://bugs.ruby-lang.org/issues/15815?journal_id=801562019-07-29T06:37:59Zko1 (Koichi Sasada)
<ul></ul><p>mtsmfm-san, if you are interest about this ticket yet, could you file on our dev-meeting agenda?<br>
<a href="https://bugs.ruby-lang.org/issues/15996" class="external">https://bugs.ruby-lang.org/issues/15996</a></p>
<p>Thanks.</p> Ruby master - Feature #15815: Add option to raise NoMethodError for OpenStructhttps://bugs.ruby-lang.org/issues/15815?journal_id=807072019-08-13T09:30:10Zmtsmfm (Fumiaki Matsushima)mtsmfm@gmail.com
<ul></ul><p>ko1-san</p>
<p>Sorry for being late.<br>
I've added <a href="https://bugs.ruby-lang.org/issues/15996?next_issue_id=15993&prev_issue_id=15998#note-17" class="external">https://bugs.ruby-lang.org/issues/15996?next_issue_id=15993&prev_issue_id=15998#note-17</a></p> Ruby master - Feature #15815: Add option to raise NoMethodError for OpenStructhttps://bugs.ruby-lang.org/issues/15815?journal_id=807082019-08-13T10:15:07Zosyo (manga osyo)
<ul></ul><p>What about adding block arguments to <code>OpenStruct.new</code> like <code>Hash</code> instead of options?</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">h</span> <span class="o">=</span> <span class="no">Hash</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span><span class="o">|</span><span class="nb">hash</span><span class="p">,</span> <span class="n">key</span><span class="o">|</span>
<span class="k">raise</span><span class="p">(</span><span class="no">IndexError</span><span class="p">,</span> <span class="s2">"hash[</span><span class="si">#{</span><span class="n">key</span><span class="si">}</span><span class="s2">] has no value"</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1"># Error: in `block in <main>': hash[1] has no value (IndexError)</span>
<span class="n">h</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="nb">require</span> <span class="s2">"ostruct"</span>
<span class="c1"># Adding block arguments like Hash</span>
<span class="n">os</span> <span class="o">=</span> <span class="no">OpenStruct</span><span class="p">.</span><span class="nf">new</span><span class="p">({</span><span class="ss">a: </span><span class="mi">1</span><span class="p">})</span> <span class="p">{</span><span class="o">|</span><span class="n">open_struct</span><span class="p">,</span> <span class="n">method_name</span><span class="o">|</span>
<span class="k">raise</span><span class="p">(</span><span class="no">NoMethdError</span><span class="p">,</span> <span class="s2">"undefined method `</span><span class="si">#{</span><span class="n">method_name</span><span class="si">}</span><span class="s2">' for </span><span class="si">#{</span><span class="n">open_struct</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1"># Error: in `block in <main>': undefined method `b' for #<OpenStruct a=1> (NoMethodError)</span>
<span class="n">os</span><span class="p">.</span><span class="nf">b</span>
</code></pre> Ruby master - Feature #15815: Add option to raise NoMethodError for OpenStructhttps://bugs.ruby-lang.org/issues/15815?journal_id=809502019-08-23T22:41:19Zesparta (Espartaco Palma)
<ul></ul><p>Personally I found the proposed signature confusing, more when taking in account that OpenStruct calls:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">os</span> <span class="o">=</span> <span class="no">OpenStruct</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">a: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">exception: </span><span class="kp">true</span><span class="p">)</span>
<span class="c1"># vs</span>
<span class="n">os</span> <span class="o">=</span> <span class="no">OpenStruct</span><span class="p">.</span><span class="nf">new</span><span class="p">({</span><span class="ss">a: </span><span class="mi">1</span><span class="p">},</span> <span class="ss">exception: </span><span class="kp">true</span><span class="p">)</span>
</code></pre>
<blockquote>
<p>I'd like to add exception option to raise NoMethodError in such case.</p>
</blockquote>
<blockquote>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="nb">require</span> <span class="s1">'ostruct'</span>
<span class="n">os</span> <span class="o">=</span> <span class="no">OpenStruct</span><span class="p">.</span><span class="nf">new</span><span class="p">({</span><span class="ss">a: </span><span class="mi">1</span><span class="p">},</span> <span class="ss">exception: </span><span class="kp">true</span><span class="p">)</span>
<span class="n">os</span><span class="p">.</span><span class="nf">a</span> <span class="c1">#=> 1</span>
<span class="n">os</span><span class="p">.</span><span class="nf">b</span> <span class="c1">#=> NoMethodError</span>
</code></pre>
</blockquote>
<p>How about a subclass of OpenStruct to take this feature?</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">ExceptionalOpenStruct</span> <span class="o"><</span> <span class="no">OpenStruct</span>
<span class="k">def</span> <span class="nf">method_missing</span><span class="p">(</span><span class="n">mid</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="k">if</span> <span class="n">args</span><span class="p">.</span><span class="nf">length</span> <span class="o">==</span> <span class="mi">0</span>
<span class="k">raise</span> <span class="no">NoMethodError</span> <span class="k">unless</span> <span class="vi">@table</span><span class="p">.</span><span class="nf">key?</span><span class="p">(</span><span class="n">mid</span><span class="p">)</span>
<span class="vi">@table</span><span class="p">[</span><span class="n">mid</span><span class="p">]</span>
<span class="k">else</span>
<span class="k">super</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">eostr</span> <span class="o">=</span> <span class="no">ExceptionalOpenStruct</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">a: </span><span class="mi">1</span><span class="p">)</span>
<span class="n">eostr</span><span class="p">.</span><span class="nf">a</span>
<span class="c1"># => 1</span>
<span class="n">eostr</span><span class="p">.</span><span class="nf">b</span>
<span class="c1"># NoMethodError: NoMethodError</span>
<span class="n">ebstr</span><span class="p">.</span><span class="nf">b</span> <span class="o">=</span> <span class="mi">2</span>
<span class="n">eostr</span><span class="p">.</span><span class="nf">b</span>
<span class="c1"># => 2</span>
</code></pre>
<p>(I'm probably missing some feature, but that's the idea...)</p>
<p>Another concern is probably the performance: OpenStruct will check another branch just to verify if now it also need to raise an exception.<br>
The flexibility of OpenStruct makes it not exactly fast, handling exception would make all other uses cases more slow performance wise.[*benchmark needed]</p>
<p>osyo (manga osyo) wrote:</p>
<blockquote>
<p>What about adding block arguments to <code>OpenStruct.new</code> like <code>Hash</code> instead of options?</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">h</span> <span class="o">=</span> <span class="no">Hash</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span><span class="o">|</span><span class="nb">hash</span><span class="p">,</span> <span class="n">key</span><span class="o">|</span>
<span class="k">raise</span><span class="p">(</span><span class="no">IndexError</span><span class="p">,</span> <span class="s2">"hash[</span><span class="si">#{</span><span class="n">key</span><span class="si">}</span><span class="s2">] has no value"</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1"># Error: in `block in <main>': hash[1] has no value (IndexError)</span>
<span class="n">h</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="nb">require</span> <span class="s2">"ostruct"</span>
<span class="c1"># Adding block arguments like Hash</span>
<span class="n">os</span> <span class="o">=</span> <span class="no">OpenStruct</span><span class="p">.</span><span class="nf">new</span><span class="p">({</span><span class="ss">a: </span><span class="mi">1</span><span class="p">})</span> <span class="p">{</span><span class="o">|</span><span class="n">open_struct</span><span class="p">,</span> <span class="n">method_name</span><span class="o">|</span>
<span class="k">raise</span><span class="p">(</span><span class="no">NoMethdError</span><span class="p">,</span> <span class="s2">"undefined method `</span><span class="si">#{</span><span class="n">method_name</span><span class="si">}</span><span class="s2">' for </span><span class="si">#{</span><span class="n">open_struct</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1"># Error: in `block in <main>': undefined method `b' for #<OpenStruct a=1> (NoMethodError)</span>
<span class="n">os</span><span class="p">.</span><span class="nf">b</span>
</code></pre>
</blockquote> Ruby master - Feature #15815: Add option to raise NoMethodError for OpenStructhttps://bugs.ruby-lang.org/issues/15815?journal_id=812552019-08-29T07:09:09Zmatz (Yukihiro Matsumoto)matz@ruby.or.jp
<ul></ul><p>I like the OP's idea. It's up to <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/182">@marcandre (Marc-Andre Lafortune)</a></p>
<p>Matz.</p> Ruby master - Feature #15815: Add option to raise NoMethodError for OpenStructhttps://bugs.ruby-lang.org/issues/15815?journal_id=823272019-10-25T19:32:11Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<ul><li><strong>Assignee</strong> set to <i>marcandre (Marc-Andre Lafortune)</i></li></ul> Ruby master - Feature #15815: Add option to raise NoMethodError for OpenStructhttps://bugs.ruby-lang.org/issues/15815?journal_id=877352020-09-26T06:56:29Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<ul></ul><p>I'm proposing to add <code>OpenStruct::Strict</code>, that will not return <code>nil</code> for unknown attributes and instead raise a <code>NotMethodError</code>. See PR <a href="https://github.com/ruby/ruby/pull/3594" class="external">https://github.com/ruby/ruby/pull/3594</a> (last commit)</p>
<p>Usage with <code>JSON</code> is easy:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="ss">object_class: </span><span class="no">OpenStruct</span><span class="o">::</span><span class="no">Strict</span><span class="p">)</span>
</code></pre>
<p>This seems to me to avoid issues that other solutions had:</p>
<ul>
<li>Any other solutions is difficult to use with <code>JSON.parse</code> API</li>
<li>I considered adding <code>OpenStruct#strict!</code> (returning <code>self</code> after mutating it), seems the 2nd best option.</li>
<li>
<code>OpenStruct.new({...}, exception: true)</code> made it difficult to remain compatible with <code>OpenStruct.new(using: 'keyword arguments')</code>. Also seemed confusing API.</li>
<li>
<code>OpenStruct.new(...) { block to handle unknown attribute }</code> was longer to write, and potentially less/not Ractor compatible, and not serializable in general.</li>
</ul>
<p>The downside to <code>OpenStruct::Strict</code> is that it makes adding any other configuration more difficult. Given the fact that <code>OpenStruct</code> is to mature now though, I feel it is not an actual issue. Anyone wanting to subclass has to pick a base class (or define it's own <code>respond_to_unknown_attribute!</code>).</p>
<p>Comments welcome.</p> Ruby master - Feature #15815: Add option to raise NoMethodError for OpenStructhttps://bugs.ruby-lang.org/issues/15815?journal_id=877632020-09-28T02:11:34Zmtsmfm (Fumiaki Matsushima)mtsmfm@gmail.com
<ul></ul><p>Cool.<br>
It's much better API to integrate with <code>JSON.parse</code>.</p>