The Unraveler The blog and website of Jake Bell, web developer living in Minneapolis, Minnesota. http://theunraveler.com The Hidden Dangers of :restrict_with_errors <p>I’ve been doing a lot of Ruby on Rails work for my day job lately, and ran across an interesting problem the other day. In one case, I had to disallow a record from being deleted if some other records associated with it exist.</p> <p>Consider the following model class:</p> <div class="highlighter-rouge"><pre class="highlight"><code>class Project &lt; ActiveRecord::Base has_many :memberships, dependent: :destroy has_many :tasks, dependent: :restrict_with_errors end </code></pre> </div> <p>Notice the use of ActiveRecord’s handy <a href="http://guides.rubyonrails.org/association_basics.html#has-many-association-reference">:restrict_with_errors</a> option. From the Rails docs:</p> <blockquote> <p><code class="highlighter-rouge">:restrict_with_error</code> causes an error to be added to the owner if there are any associated objects</p> </blockquote> <p>The idea is that when a project is deleted, we want to also delete its associated memberships. But if a project has tasks, we want to prevent the project from being deleted. So when we do the following, the project should not be deleted, and should get an error message attached to it.</p> <div class="highlighter-rouge"><pre class="highlight"><code>project = Project.create!(name: 'Test Project') project.memberships.create! project.tasks.create! project.destroy # =&gt; false project.errors.full_messages # =&gt; ["Cannot delete record because dependent tasks exist"] </code></pre> </div> <p>Can you spot the bug? That’s right—even though our project hasn’t been deleted, <strong>all of its memberships have</strong>!</p> <p>In order to understand the issue, we need to know a couple things about <code class="highlighter-rouge">ActiveRecord</code> associations and the <code class="highlighter-rouge">dependent</code> option.</p> <ul> <li>For associations with <code class="highlighter-rouge">dependent: :destroy</code>, the associated objects are destroyed <em>before</em> the main object. This is to prevent any foreign key constraints from complaining.</li> <li><code class="highlighter-rouge">destroy</code> operations run within a transaction. Associated records with <code class="highlighter-rouge">dependent: :destroy</code> are also removed within that transaction.</li> </ul> <p>So the issue here is that simply adding an error to the project doesn’t trigger a rollback, and the associations that have been destroyed before the operation was halted by the <code class="highlighter-rouge">restrict_with_error</code> stay destroyed.</p> <h3 id="the-solution">The Solution</h3> <p>Barring a patch to Rails core, there are two solutions to this issue:</p> <p>Firstly, we could change the order of the associations so that the <code class="highlighter-rouge">restrict_with_errors</code> halts the operation before the other associations are destroyed:</p> <div class="highlighter-rouge"><pre class="highlight"><code>class Project &lt; ActiveRecord::Base has_many :tasks, dependent: :restrict_with_errors has_many :memberships, dependent: :destroy end </code></pre> </div> <p>In this case, the operation will bail before the <code class="highlighter-rouge">memberships</code> association is destroyed.</p> <p>Otherwise, we could use the <code class="highlighter-rouge">restict_with_exception</code> option instead:</p> <div class="highlighter-rouge"><pre class="highlight"><code>class Project &lt; ActiveRecord::Base has_many :memberships, dependent: :destroy has_many :tasks, dependent: :restrict_with_exception end </code></pre> </div> <p>Here, when we attempt to destroy the project, an exception will be thrown that will roll back the entire delete operation.</p> <p>The first solution has some problems. For one, our associations are now order-dependent. If we were going to go with this option, we’d better surround that thing will a wall of tests so that no one (ourselves included) messes it up in the future. The second one seems much less brittle to me, so I ended up rolling with that.</p> <p>Hope this helps!</p> Mon, 20 Apr 2015 00:00:00 +0000 http://theunraveler.com/blog/2015/the-hidden-dangers-of-restrict_with_errors http://theunraveler.com/blog/2015/the-hidden-dangers-of-restrict_with_errors PHP Annotations Are a Horrible Idea <p><strong>UPDATE:</strong> It has come to my attention that there is an RFC pending that would add an annotation syntax to PHP (<a href="https://wiki.php.net/rfc/annotations">link</a>). I fully support making annotations a language feature. This post specifically criticizes the current implementation of annotations in code comments.</p> <p>Both the Symfony 2 and Doctrine 2 libraries and components make liberal use of what have come to be called annotations—special code comments, usually prefixed with an <code class="highlighter-rouge">@</code> that are actually interpreted by the application and affect its functionality. For example, in order to use PHP templates for a controller action with Symfony, one must declare that the returned value should be rendered with a template in the following way:</p> <div class="highlighter-rouge"><pre class="highlight"><code>/** * @Template(engine="php") */ public function indexAction() { ... } </code></pre> </div> <p>Without the special <code class="highlighter-rouge">@Template</code> comment, Symfony does not know where to find the template file.</p> <p>Doctrine 2 does the same thing when declaring object properties based on database table columns<sup id="fnref:doctrine"><a href="#fn:doctrine" class="footnote">1</a></sup>:</p> <div class="highlighter-rouge"><pre class="highlight"><code>/** * @Column(type="string", length=32, unique=true, nullable=false) */ protected $username; </code></pre> </div> <p><strong>This trend needs to die.</strong></p> <h3 id="why">Why?</h3> <p>Because <strong>code comments should never be necessary for a script to function properly</strong>. Code comments are exactly that—comments. Their purpose is to provide commentary on the code, not to provide logic that is critical to the application’s functionality.</p> <h4 id="dx-regressions">1. DX Regressions</h4> <p>Consider debugging code in which several different types of critical application logic are contained within annotations. There are several DX regressions:</p> <ul> <li>You can’t use any of PHP’s helpful linting capabilities (<code class="highlighter-rouge">php -l</code>).</li> <li>Try to <code class="highlighter-rouge">var_dump()</code> or <code class="highlighter-rouge">debug_backtrace()</code> an annotation. You simply can’t.</li> <li>Your IDE can’t link to classes in which an annotation is defined.</li> <li>Sometimes, developers set their editors (like vim) to automatically fold comments in order to save screen space. In this case, they may never actually see the annotations that are making your code work.</li> </ul> <h4 id="decreased-readabilitydiscoverability">2. Decreased Readability/Discoverability</h4> <p>A developer who knows PHP very well can spend a couple hours with most applications and frameworks, and figure out the basics. If you can read PHP, and the code is not unreasonable obtuse, you should be up and running pretty quickly. However, <em>annotations do not necessarily make sense to a developer who knows PHP perfectly well</em>, because they are not native to the language. Based on their knowledge of code comments (that, usually, they do not affect the application logic), they would not immediately know to look there when debugging.</p> <h4 id="reliance-on-yet-another-library">3. Reliance on Yet Another Library</h4> <p>If you use annotations, your application is now dependant on a library that parses annotations (<code class="highlighter-rouge">doctrine/common</code>, most likely). I’m sure quite a number of development hours were/are spent writing and bugfixing that library, all of which could have been spent on other projects.</p> <h4 id="icky-feeling">4. Icky Feeling</h4> <p>Perhaps it’s just me, but it just feels <em>wrong</em> to put application logic inside comments. They’re a bit magical, which does not lend itself to writing maintainable code.</p> <h3 id="alternatives">Alternatives</h3> <p>But most of all, annotations really signal to me a lack of functionality or flexibility in PHP itself—the PHP community is now officially so desperate for better metaprogramming capabilities that it is attempting to do so using code comments.</p> <p>With a language like Ruby, which allows for class-level function calls, you do not need annotations because you can define configuration in a declarative way, like so:</p> <div class="highlighter-rouge"><pre class="highlight"><code>class BlogPost &lt; ActiveRecord::Base attr_accessible :name, :body end </code></pre> </div> <p>The fact that the language permits you to call functions within a class, but outside of a function definition, allows for extremely elegant configuration. Though I don’t know much about the inner-workings of the Ruby language, I’m sure this has something to do with the fact that Ruby classes can be reopened throughout the compilation lifecycle.</p> <p>However, it seems like there are equally elegant ways to achieve the same result with PHP using class properties. Consider:</p> <div class="highlighter-rouge"><pre class="highlight"><code>class User { public static $mapping = [ 'username' =&gt; [ 'type' =&gt; 'string', 'length' =&gt; 32, 'unique' =&gt; true ] ]; } </code></pre> </div> <p>Then Doctrine could grab all of the $mapping properties from model classes, and wrap them in a nice configuration class so that we don’t have to deal with dumb arrays later in the lifecycle. <strike>And now that PHP has proper lambda functions and closures, you can even make your definition a bit more dynamic, as shown above, should the need arise.</strike></p> <h3 id="concluding">Concluding</h3> <p>So annotations suck for a variety of reasons. With PHP, we have elegant alternatives. My vote is that we stop using them, and remove the ability to use them from frameworks and libraries. If you have compelling reasons for using annotations, I would love to know what they are.</p> <div class="footnotes"> <ol> <li id="fn:doctrine"> <p>Fortunately, Doctrine also allows you to declare property metadata using PHP, YAML, or XML, though none of the options is particularly elegant. <a href="#fnref:doctrine" class="reversefootnote">&#8617;</a></p> </li> </ol> </div> Wed, 17 Oct 2012 00:00:00 +0000 http://theunraveler.com/blog/2012/php-annotations-are-a-horrible-idea http://theunraveler.com/blog/2012/php-annotations-are-a-horrible-idea Who gives a fuck about an Oxford comma? <p><img src="/images/posts/2012-oxcom.jpg" alt="Python Oxford Comma" /></p> Thu, 16 Feb 2012 00:00:00 +0000 http://theunraveler.com/blog/2012/who-gives-fuck-about-oxford-comma http://theunraveler.com/blog/2012/who-gives-fuck-about-oxford-comma Drupal 7: How to Add a WYSIWYG Editor to a "Text with Summary" Field <p>Recently, I was tasked with adding a WYSIWYG editor to the summary field of one of Drupal 7’s “Text with Summary” fields. Since the summary and main field content are actually stored in two separate fields, the WYSIWYG module’s “Teaser break” thing doesn’t really work. After Googling around and finding nothing, here’s what I came up with.</p> <p>First, a couple things to note:</p> <ul> <li>We will be using the <a href="http://drupal.org/project/wysiwyg">WYSIWYG module</a>, which automatically applies the WYSIWYG editor to any textarea where <code class="highlighter-rouge">#type</code> is <code class="highlighter-rouge">text_format</code>.</li> <li>We do not want the full formatting options for the summary field; the summary should use whatever format is selected for the content field it is attached to.</li> </ul> <p>Since we want to apply this to all “Text with summary” fields, the best place to intervene is with <code class="highlighter-rouge">hook_field_widget_form_alter()</code>. This hook is called every time a field widget is rendered, so we’ll just make our changes if the field has a <code class="highlighter-rouge">summary</code> attribute.</p> <div class="highlighter-rouge"><pre class="highlight"><code>/** * Implementation of hook_field_widget_form_alter(). * * Add WYSIWYG treatment to textarea summary form items. */ function MY_MODULE_field_widget_form_alter(&amp;$element, &amp;$form_state, $context) { if (!isset($element['summary'])) { return; } drupal_add_css(drupal_get_path('module', 'MY_MODULE') . '/assets/textarea-summary.css'); drupal_add_js(drupal_get_path('module', 'MY_MODULE') . '/assets/textarea-summary.js'); $element['summary']['#type'] = 'text_format'; $element['summary']['#format'] = $element['#format']; } /** * Implements hook_field_attach_presave(). */ function MY_MODULE_field_attach_presave($entity_type, $entity) { list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); foreach (field_info_instances($entity_type, $bundle) as $instance) { $field = field_info_field_by_id($instance['field_id']); $field_name = $field['field_name']; if ($field['type'] !== 'text_with_summary' || empty($entity-&gt;$field_name)) { continue; } $language = isset($entity-&gt;language) ? $entity-&gt;language : LANGUAGE_NONE; foreach ($entity-&gt;{$field_name}[$language] as $id =&gt; &amp;$value) { if (!is_array($value['summary'])) { continue; } $value['summary'] = $value['summary']['value']; } } } </code></pre> </div> <p>Notice also that we need to implement <code class="highlighter-rouge">hook_field_attach_presave()</code>. This is because changing the summary field to a formatted text area actually changes the data structure of the field instance; it makes <code class="highlighter-rouge">$field['summary']</code> into an array that also stores the format. We will get a database exception if we do not change the structure back to what the Field API expects.</p> <p>Then, let’s hide the format fieldset on the summary field with CSS.</p> <div class="highlighter-rouge"><pre class="highlight"><code>div.text-summary-wrapper fieldset.filter-wrapper { display: none; } </code></pre> </div> <p>Then, we’ll change the hidden format field whenever the main field’s format is changed.</p> <div class="highlighter-rouge"><pre class="highlight"><code>(function ($) { Drupal.behaviors.myModuleTextareaSummary = { attach: function (context, settings) { // Change the value of the summary format whenever the main textarea // format changes. $('.field-type-text-with-summary div.text-format-wrapper &gt; fieldset.filter-wrapper select').change(function(event) { var value = $(this).val(); $('.text-summary-wrapper select', $(this).closest('.text-format-wrapper')).val(value); }); } }; }(jQuery)); </code></pre> </div> <p>And there you have it.</p> Thu, 12 Jan 2012 00:00:00 +0000 http://theunraveler.com/blog/2012/drupal-7-how-add-wysiwyg-editor-text-summary-field http://theunraveler.com/blog/2012/drupal-7-how-add-wysiwyg-editor-text-summary-field