{"id":18,"date":"2019-03-08T12:42:00","date_gmt":"2019-03-08T12:42:00","guid":{"rendered":""},"modified":"2019-03-12T21:45:27","modified_gmt":"2019-03-12T21:45:27","slug":"ruby-best-practice-implementing-operator-and-ensuring-it-doesnt-break","status":"publish","type":"post","link":"https:\/\/ntlx.org\/de\/2019\/03\/ruby-best-practice-implementing-operator-and-ensuring-it-doesnt-break.html","title":{"rendered":"Ruby best practice: Implementing operator == and ensuring it doesn&#8217;t break"},"content":{"rendered":"<p>In ruby, comparing hashes, strings and objects is a complicated topic. Should you use <i>equal?<\/i>, <i>eql?<\/i> or <i>==<\/i>? There is plenty of help on this topic, but in this post, we will focus on the interesting behavior of the <i>==<\/i> operator and how you can make it behave as you need it for your use case.<\/p>\n<p>When comparing Hashes in Ruby, the <i>==<\/i> operator compares the content of a hash recursively.<\/p>\n<div style=\"background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\"> 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20<\/pre>\n<\/td>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\">my_hash = {\n    <span style=\"background-color: #fff0f0; color: #aa6600;\">:sub_hash<\/span> =&gt; {\n        <span style=\"background-color: #fff0f0; color: #aa6600;\">:value<\/span> =&gt; <span style=\"color: #0000dd; font-weight: bold;\">42<\/span>\n    }\n}\n\nmy_second_hash = {\n    <span style=\"background-color: #fff0f0; color: #aa6600;\">:sub_hash<\/span> =&gt; {\n        <span style=\"background-color: #fff0f0; color: #aa6600;\">:value<\/span> =&gt; <span style=\"color: #0000dd; font-weight: bold;\">42<\/span>\n    }\n}\n\nmy_third_hash = {\n    <span style=\"background-color: #fff0f0; color: #aa6600;\">:sub_hash<\/span> =&gt; {\n        <span style=\"background-color: #fff0f0; color: #aa6600;\">:value<\/span> =&gt; <span style=\"color: #0000dd; font-weight: bold;\">21<\/span>\n    }\n}\n\n<span style=\"color: #003388;\">puts<\/span> <span style=\"background-color: #fff0f0; color: #dd2200;\">\"my_hash == my_second_hash? <\/span><span style=\"background-color: #fff0f0; color: #3333bb;\">#{<\/span>my_hash == my_second_hash<span style=\"background-color: #fff0f0; color: #3333bb;\">}<\/span><span style=\"background-color: #fff0f0; color: #dd2200;\">\"<\/span>\n<span style=\"color: #003388;\">puts<\/span> <span style=\"background-color: #fff0f0; color: #dd2200;\">\"my_hash == my_third_hash? <\/span><span style=\"background-color: #fff0f0; color: #3333bb;\">#{<\/span>my_hash == my_third_hash<span style=\"background-color: #fff0f0; color: #3333bb;\">}<\/span><span style=\"background-color: #fff0f0; color: #dd2200;\">\"<\/span>\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p><!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\">1\n2<\/pre>\n<\/td>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\">my_hash == my_second_hash? <span style=\"color: #008800;\">true<\/span>\nmy_hash == my_third_hash? <span style=\"color: #008800;\">false<\/span>\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>Unfortunately, when comparing objects of arbitrary classes, the default operator only compares the object identity.<\/p>\n<div style=\"background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\"> 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10<\/pre>\n<\/td>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\"><span style=\"color: #008800; font-weight: bold;\">class<\/span> <span style=\"color: #bb0066; font-weight: bold;\">MyClass<\/span>\n  <span style=\"color: #008800; font-weight: bold;\">def<\/span> <span style=\"color: #0066bb; font-weight: bold;\">initialize<\/span>(value)\n    <span style=\"color: #3333bb;\">@value<\/span> = value\n  <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n<span style=\"color: #008800; font-weight: bold;\">end<\/span>\n\nmy_object = <span style=\"color: #003366; font-weight: bold;\">MyClass<\/span>.new(<span style=\"color: #0000dd; font-weight: bold;\">42<\/span>)\nmy_second_object = <span style=\"color: #003366; font-weight: bold;\">MyClass<\/span>.new(<span style=\"color: #0000dd; font-weight: bold;\">42<\/span>)\n\n<span style=\"color: #003388;\">puts<\/span> <span style=\"background-color: #fff0f0; color: #dd2200;\">\"my_object == my_second_object? <\/span><span style=\"background-color: #fff0f0; color: #3333bb;\">#{<\/span>my_object == my_second_object<span style=\"background-color: #fff0f0; color: #3333bb;\">}<\/span><span style=\"background-color: #fff0f0; color: #dd2200;\">\"<\/span>\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p><!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\">1<\/pre>\n<\/td>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\">my_object == my_second_object? <span style=\"color: #008800;\">false<\/span>\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>If you want to do a deep comparison of objects of your class, you need to implement your own operator <i>==<\/i> by overriding the existing operator.<\/p>\n<div style=\"background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\"> 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10\n11\n12\n13\n14\n15\n16<\/pre>\n<\/td>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\"><span style=\"color: #008800; font-weight: bold;\">class<\/span> <span style=\"color: #bb0066; font-weight: bold;\">MyClass<\/span>\n  <span style=\"color: #008800;\">attr_reader<\/span> <span style=\"background-color: #fff0f0; color: #aa6600;\">:value<\/span>\n\n  <span style=\"color: #008800; font-weight: bold;\">def<\/span> <span style=\"color: #0066bb; font-weight: bold;\">initialize<\/span>(value)\n    <span style=\"color: #3333bb;\">@value<\/span> = value\n  <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n\n  <span style=\"color: #008800; font-weight: bold;\">def<\/span> <span style=\"color: #0066bb; font-weight: bold;\">==<\/span>(other)\n    other.respond_to?(<span style=\"background-color: #fff0f0; color: #dd2200;\">\"value\"<\/span>) &amp;&amp; value == other.value\n  <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n<span style=\"color: #008800; font-weight: bold;\">end<\/span>\n\nmy_object = <span style=\"color: #003366; font-weight: bold;\">MyClass<\/span>.new(<span style=\"color: #0000dd; font-weight: bold;\">42<\/span>)\nmy_second_object = <span style=\"color: #003366; font-weight: bold;\">MyClass<\/span>.new(<span style=\"color: #0000dd; font-weight: bold;\">42<\/span>)\n\n<span style=\"color: #003388;\">puts<\/span> <span style=\"background-color: #fff0f0; color: #dd2200;\">\"my_object == my_second_object? <\/span><span style=\"background-color: #fff0f0; color: #3333bb;\">#{<\/span>my_object == my_second_object<span style=\"background-color: #fff0f0; color: #3333bb;\">}<\/span><span style=\"background-color: #fff0f0; color: #dd2200;\">\"<\/span>\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p><!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\">1<\/pre>\n<\/td>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\">my_object == my_second_object? <span style=\"color: #008800;\">true<\/span>\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>That was easy. But imagine this was a bigger class and someone else needed to add a property, not being aware of the existence of this operator and some other code depending on it to ensure no public member of the object changed. How can you ensure such a change doesn&#8217;t sneak in unnoticed?<\/p>\n<p>I stumbled across the following solution when implementing an operator == for a class in the&nbsp;<a href=\"http:\/\/bosh.io\/\" target=\"_blank\">BOSH<\/a>&nbsp;code together with my colleague&nbsp;<a href=\"https:\/\/github.com\/BeckerMax\" target=\"_blank\">Max<\/a>.<\/p>\n<p>As BOSH code is written in TDD &#8211; and your code should be as well &#8211; writing a test that breaks with a change as the one described above should ensure the operator to keep working. But how can such a test look like?<\/p>\n<p>Consider the following change to our code above:<\/p>\n<div style=\"background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\"> 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10\n11\n12\n13\n14\n15\n16\n17\n18<\/pre>\n<\/td>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\"><span style=\"color: #008800; font-weight: bold;\">class<\/span> <span style=\"color: #bb0066; font-weight: bold;\">MyClass<\/span>\n  <span style=\"color: #008800;\">attr_reader<\/span> <span style=\"background-color: #fff0f0; color: #aa6600;\">:value<\/span>\n  <span style=\"color: #008800;\">attr_reader<\/span> <span style=\"background-color: #fff0f0; color: #aa6600;\">:value_new<\/span>\n\n  <span style=\"color: #008800; font-weight: bold;\">def<\/span> <span style=\"color: #0066bb; font-weight: bold;\">initialize<\/span>(value)\n    <span style=\"color: #3333bb;\">@value<\/span> = value\n    <span style=\"color: #3333bb;\">@value_new<\/span> = value\n  <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n\n  <span style=\"color: #008800; font-weight: bold;\">def<\/span> <span style=\"color: #0066bb; font-weight: bold;\">==<\/span>(other)\n    other.respond_to?(<span style=\"background-color: #fff0f0; color: #dd2200;\">\"value\"<\/span>) &amp;&amp; value == other.value\n  <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n<span style=\"color: #008800; font-weight: bold;\">end<\/span>\n\nmy_object = <span style=\"color: #003366; font-weight: bold;\">MyClass<\/span>.new(<span style=\"color: #0000dd; font-weight: bold;\">42<\/span>)\nmy_second_object = <span style=\"color: #003366; font-weight: bold;\">MyClass<\/span>.new(<span style=\"color: #0000dd; font-weight: bold;\">42<\/span>)\n\n<span style=\"color: #003388;\">puts<\/span> <span style=\"background-color: #fff0f0; color: #dd2200;\">\"my_object == my_second_object? <\/span><span style=\"background-color: #fff0f0; color: #3333bb;\">#{<\/span>my_object == my_second_object<span style=\"background-color: #fff0f0; color: #3333bb;\">}<\/span><span style=\"background-color: #fff0f0; color: #dd2200;\">\"<\/span>\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>To detect the variable <i>@value_new<\/i>&nbsp;has been added using <i>rspec <\/i>can be done with a test like the following:<\/p>\n<div style=\"background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\"> 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32<\/pre>\n<\/td>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\"><span style=\"color: #003388;\">require<\/span> <span style=\"background-color: #fff0f0; color: #dd2200;\">'.\/object_compare_op'<\/span>\n\ndescribe <span style=\"background-color: #fff0f0; color: #aa6600;\">:MyClass<\/span> <span style=\"color: #008800; font-weight: bold;\">do<\/span>\n  describe <span style=\"background-color: #fff0f0; color: #dd2200;\">'operator =='<\/span> <span style=\"color: #008800; font-weight: bold;\">do<\/span>\n    context <span style=\"background-color: #fff0f0; color: #dd2200;\">'when instance variables are modified'<\/span> <span style=\"color: #008800; font-weight: bold;\">do<\/span>\n      let <span style=\"background-color: #fff0f0; color: #aa6600;\">:obj<\/span> <span style=\"color: #008800; font-weight: bold;\">do<\/span>\n        <span style=\"color: #003366; font-weight: bold;\">MyClass<\/span>.new(<span style=\"color: #0000dd; font-weight: bold;\">42<\/span>)\n      <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n      let <span style=\"background-color: #fff0f0; color: #aa6600;\">:other_obj<\/span> <span style=\"color: #008800; font-weight: bold;\">do<\/span>\n        <span style=\"color: #003366; font-weight: bold;\">MyClass<\/span>.new(<span style=\"color: #0000dd; font-weight: bold;\">42<\/span>)\n      <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n\n      all_members = <span style=\"color: #003366; font-weight: bold;\">MyClass<\/span>.new(<span style=\"color: #0000dd; font-weight: bold;\">0<\/span>).instance_variables.map { |var| var.to_s.tr(<span style=\"background-color: #fff0f0; color: #dd2200;\">'@'<\/span>, <span style=\"background-color: #fff0f0; color: #dd2200;\">''<\/span>) }\n      all_members.each <span style=\"color: #008800; font-weight: bold;\">do<\/span> |member|\n        it <span style=\"background-color: #fff0f0; color: #dd2200;\">\"returns false when <\/span><span style=\"background-color: #fff0f0; color: #3333bb;\">#{<\/span>member<span style=\"background-color: #fff0f0; color: #3333bb;\">}<\/span><span style=\"background-color: #fff0f0; color: #dd2200;\"> is modified\"<\/span> <span style=\"color: #008800; font-weight: bold;\">do<\/span>\n          <span style=\"color: #003388;\">eval<\/span> &lt;&lt;-<span style=\"color: #003366; font-weight: bold;\">END_EVAL<\/span>\n<span style=\"background-color: #fff0f0; color: #dd2200;\">            class MyClass<\/span>\n<span style=\"background-color: #fff0f0; color: #dd2200;\">              def modify_#{member}<\/span>\n<span style=\"background-color: #fff0f0; color: #dd2200;\">               @#{member} = 'foo'<\/span>\n<span style=\"background-color: #fff0f0; color: #dd2200;\">              end<\/span>\n<span style=\"background-color: #fff0f0; color: #dd2200;\">            end<\/span>\n<span style=\"color: #003366; font-weight: bold;\">          END_EVAL<\/span>\n          obj.send(<span style=\"background-color: #fff0f0; color: #dd2200;\">\"modify_<\/span><span style=\"background-color: #fff0f0; color: #3333bb;\">#{<\/span>member<span style=\"background-color: #fff0f0; color: #3333bb;\">}<\/span><span style=\"background-color: #fff0f0; color: #dd2200;\">\"<\/span>)\n          expect(obj == other_obj).to(\n            equal(<span style=\"color: #008800;\">false<\/span>),\n            <span style=\"background-color: #fff0f0; color: #dd2200;\">\"Modification of <\/span><span style=\"background-color: #fff0f0; color: #3333bb;\">#{<\/span>member<span style=\"background-color: #fff0f0; color: #3333bb;\">}<\/span><span style=\"background-color: #fff0f0; color: #dd2200;\"> not detected by == operator.\"<\/span>,\n          )\n        <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n      <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n    <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n  <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n<span style=\"color: #008800; font-weight: bold;\">end<\/span>\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>The variable <i>@value_new<\/i> only has an attribute reader, so we cannot simply assign a new value. But this doesn&#8217;t stop you from changing the value. Not in Ruby. Using the eval in the test, we add a method for all existing instance variables of <i>MyClass<\/i> (one in each iteration) that modifies the member.<\/p>\n<p>Afterwards, the newly added method is called to change the value of the member and the expect checks if the operator detects the modification. And &#8211; for our code above &#8211; will fail. Hence, whenever someone adds a new member to <i>MyClass<\/i>, he will be reminded to also it to the operator == by this test. Even if the code of test itself might not be as speaking, the output of the failing test is:<\/p>\n<p><b>&nbsp;Modification of value_new not detected by == operator.<\/b><\/p>\n<p>In some situations you may want to exclude a member from this check as it is just internal or not important to the equality of two objects. To enable this, we added an exclude list for private members to the test. This adds a bit of complexity to adding new members to the class as the test will bother you and you also have to add the member to the exclude list, but it improves the safety of your operator ==.<br \/>\n<!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\"> 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34<\/pre>\n<\/td>\n<td>\n<pre style=\"line-height: 125%; margin: 0;\"><span style=\"color: #003388;\">require<\/span> <span style=\"background-color: #fff0f0; color: #dd2200;\">'.\/object_compare_op'<\/span>\n\ndescribe <span style=\"background-color: #fff0f0; color: #aa6600;\">:MyClass<\/span> <span style=\"color: #008800; font-weight: bold;\">do<\/span>\n  describe <span style=\"background-color: #fff0f0; color: #dd2200;\">'operator =='<\/span> <span style=\"color: #008800; font-weight: bold;\">do<\/span>\n    context <span style=\"background-color: #fff0f0; color: #dd2200;\">'when instance variables are modified'<\/span> <span style=\"color: #008800; font-weight: bold;\">do<\/span>\n      let <span style=\"background-color: #fff0f0; color: #aa6600;\">:obj<\/span> <span style=\"color: #008800; font-weight: bold;\">do<\/span>\n        <span style=\"color: #003366; font-weight: bold;\">MyClass<\/span>.new(<span style=\"color: #0000dd; font-weight: bold;\">42<\/span>)\n      <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n      let <span style=\"background-color: #fff0f0; color: #aa6600;\">:other_obj<\/span> <span style=\"color: #008800; font-weight: bold;\">do<\/span>\n        <span style=\"color: #003366; font-weight: bold;\">MyClass<\/span>.new(<span style=\"color: #0000dd; font-weight: bold;\">42<\/span>)\n      <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n\n      all_members = <span style=\"color: #003366; font-weight: bold;\">MyClass<\/span>.new(<span style=\"color: #0000dd; font-weight: bold;\">0<\/span>).instance_variables.map { |var| var.to_s.tr(<span style=\"background-color: #fff0f0; color: #dd2200;\">'@'<\/span>, <span style=\"background-color: #fff0f0; color: #dd2200;\">''<\/span>) }\n      private_members = <span style=\"background-color: honeydew; color: #22bb22;\">%w[value_new]<\/span>\n      public_members = all_members - private_members\n      public_members.each <span style=\"color: #008800; font-weight: bold;\">do<\/span> |member|\n        it <span style=\"background-color: #fff0f0; color: #dd2200;\">\"returns false when <\/span><span style=\"background-color: #fff0f0; color: #3333bb;\">#{<\/span>member<span style=\"background-color: #fff0f0; color: #3333bb;\">}<\/span><span style=\"background-color: #fff0f0; color: #dd2200;\"> is modified\"<\/span> <span style=\"color: #008800; font-weight: bold;\">do<\/span>\n          <span style=\"color: #003388;\">eval<\/span> &lt;&lt;-<span style=\"color: #003366; font-weight: bold;\">END_EVAL<\/span>\n<span style=\"background-color: #fff0f0; color: #dd2200;\">            class MyClass<\/span>\n<span style=\"background-color: #fff0f0; color: #dd2200;\">              def modify_#{member}<\/span>\n<span style=\"background-color: #fff0f0; color: #dd2200;\">               @#{member} = 'foo'<\/span>\n<span style=\"background-color: #fff0f0; color: #dd2200;\">              end<\/span>\n<span style=\"background-color: #fff0f0; color: #dd2200;\">            end<\/span>\n<span style=\"color: #003366; font-weight: bold;\">          END_EVAL<\/span>\n          obj.send(<span style=\"background-color: #fff0f0; color: #dd2200;\">\"modify_<\/span><span style=\"background-color: #fff0f0; color: #3333bb;\">#{<\/span>member<span style=\"background-color: #fff0f0; color: #3333bb;\">}<\/span><span style=\"background-color: #fff0f0; color: #dd2200;\">\"<\/span>)\n          expect(obj == other_obj).to(\n            equal(<span style=\"color: #008800;\">false<\/span>),\n            <span style=\"background-color: #fff0f0; color: #dd2200;\">\"Modification of <\/span><span style=\"background-color: #fff0f0; color: #3333bb;\">#{<\/span>member<span style=\"background-color: #fff0f0; color: #3333bb;\">}<\/span><span style=\"background-color: #fff0f0; color: #dd2200;\"> not detected by == operator.\"<\/span>,\n          )\n        <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n      <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n    <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n  <span style=\"color: #008800; font-weight: bold;\">end<\/span>\n<span style=\"color: #008800; font-weight: bold;\">end<\/span>\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>With this kind of test, you can easily implement comparison operators for your classes that check for object equality rather than identity and ensure you do not forget to add new members of the class also to the comparison.<br \/>\nYou can take a look at productive code in the BOSH code base <a href=\"https:\/\/github.com\/cloudfoundry\/bosh\/blob\/612da215ff5fc6a518c0fb5f9f19402e7efa9299\/src\/bosh-template\/spec\/unit\/bosh\/template\/evaluation_context_spec.rb#L172\" target=\"_blank\">here<\/a>. As you may see it&#8217;s not much different to what I presented here &#8211; it&#8217;s a universal approach to solve the problem.<\/p>\n","protected":false},"excerpt":{"rendered":"In ruby, comparing hashes, strings and objects is a complicated topic. Should you use equal?, eql? or ==? There is plenty of help on this topic, but in this post, we will focus on the interesting behavior of the == operator and how you can make it behave as you need it for your use&#8230; <a class=\"view-article\" href=\"https:\/\/ntlx.org\/de\/2019\/03\/ruby-best-practice-implementing-operator-and-ensuring-it-doesnt-break.html\">Artikel ansehen<\/a>","protected":false},"author":1,"featured_media":78,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[7,6,5],"_links":{"self":[{"href":"https:\/\/ntlx.org\/de\/wp-json\/wp\/v2\/posts\/18"}],"collection":[{"href":"https:\/\/ntlx.org\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ntlx.org\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ntlx.org\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ntlx.org\/de\/wp-json\/wp\/v2\/comments?post=18"}],"version-history":[{"count":2,"href":"https:\/\/ntlx.org\/de\/wp-json\/wp\/v2\/posts\/18\/revisions"}],"predecessor-version":[{"id":79,"href":"https:\/\/ntlx.org\/de\/wp-json\/wp\/v2\/posts\/18\/revisions\/79"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ntlx.org\/de\/wp-json\/wp\/v2\/media\/78"}],"wp:attachment":[{"href":"https:\/\/ntlx.org\/de\/wp-json\/wp\/v2\/media?parent=18"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ntlx.org\/de\/wp-json\/wp\/v2\/categories?post=18"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ntlx.org\/de\/wp-json\/wp\/v2\/tags?post=18"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}