Be Genius

Fork Me
Mugshot

Hi, I'm Bodaniel Jeanes.

I'm a Ruby developer from Brisbane, Australia

I work at Mocra where I hack on awesome code. Follow me, recommend me, and link with me.

Using Fish shell's event system to behave like method_missing

Dr Nic’s latest post, “hash bang cucumber”, reminded me of a piece of hax I whipped up a few weeks ago with Ruby and Fish Shell.

Fish has an event system that allows you to register functions to be auto-run after or during certain events (such as when a particular environment variable is changed). One of this events is called fish_command_not_found. It is triggered whenever you type a non-existant command into the prompt.

With this in mind, you can trigger certain commands to run by matching what fish couldn’t manage to run automatically by catching this event.

For instance:

function __fish_method_missing --on-event fish_command_not_found
  method_missing $argv
end

funcsave method_missing

Given that function is created (simply paste the whole code block into your Fish terminal), I can now create a command called method_missing (or whatever you call inside your __fish_method_missing) and place it somewhere in your $PATH — I like ~/.config/fish/bin.

My method_missing binary is simply something like this:

#!/usr/bin/env ruby

command = ARGV.shift

def run(cmd)
  puts "Running #{cmd.inspect} instead"
  system(cmd)
end

case command
when /^git(@|:\/\/).*\.git$/
  run("git clone #{command.inspect}")
when /^(?:ftp|https?):\/\/.+\.t(?:ar\.)?gz$/
  run("curl #{command.inspect} | tar xzv")
else
  $stderr.puts "No default action defined in #{__FILE__.inspect}"
  abort
end

Each command you want to register just becomes a new when statement. For instance, to implement the functionality Dr Nic was trying to achieve, I simply modify the case block as such:

case command
when /^[a-z0-9_\-\/]+\.feature(:\d+)?$/
  run("cucumber #{command}")
# ...
end

The existing entries I have in my method_missing command will auto-clone the repository of a pasted Git URL and download and expand a URL for a tar file, respectively. Not too shabby, and dead easy to implement

Using fields_for can generate invalid HTML

Just a quick post so show you how fields_for form helper can cause invalid HTML when used in combination with accepts_nested_attributes_for in your views.

If you are for any reason editing/creating your child objects in a tabular form, your view might look something like this:

<% form_for(@todo_list) do |f| %>
  <%= f.label      :name %>
  <%= f.text_field :name %>

  <h3>To Do Items:</h3>
  <table>
    <thead>
      <tr>
        <th>To Do Item</th>
        <th>Due Date</th>
        <th>Done?</th>
      </tr>
    </thead>
    <tbody>
      <% f.fields_for :items do |item| %>
        <tr>
          <td><%= item.text_field :content %></td>
          <td><%= item.text_field :due_date %></td>
          <td><%= item.check_box  :done %></td>
        </tr>
      <% end %>
    </tbody>
  </table>
  
  <%= f.submit 'Save' %>
<% end %>

At first glance this looks fine. However, when the ToDoList model has accepts_nested_attributes_for :items, the fields_for helper also outputs a hidden field for each existing Item instance with it’s ID.

This means that we get the following HTML:

<form action="/to_do_lists/1" class="edit_to_do_list" id="edit_to_do_list_1" method="post">
  <div style="margin:0;padding:0;display:inline">
    <input name="_method" type="hidden" value="put" />
    <input name="authenticity_token" type="hidden" value="CE2jyRoewbwsqu4eX8AFWFqsgrMfCN35Jzy6b43MhsA=" />
  </div>
  <label for="to_do_list_name">Name</label>
  <input id="to_do_list_name" name="to_do_list[name]" size="30" type="text" value="Sup" />

  <h3>To Do Items:</h3>
  <table>
    <thead>
      <tr>
        <th>To Do Item</th>
        <th>Due Date</th>
        <th>Done?</th>
      </tr>
    </thead>
    <tbody>
      <input id="to_do_list_items_attributes_0_id" name="to_do_list[items_attributes][0][id]" type="hidden" value="1" />
        <tr>
          <td><input id="to_do_list_items_attributes_0_content" name="to_do_list[items_attributes][0][content]" size="30" type="text" value="arst" /></td>
          <td><input id="to_do_list_items_attributes_0_due_date" name="to_do_list[items_attributes][0][due_date]" size="30" type="text" value="2009-07-30" /></td>
          <td><input name="to_do_list[items_attributes][0][done]" type="hidden" value="0" /><input id="to_do_list_items_attributes_0_done" name="to_do_list[items_attributes][0][done]" type="checkbox" value="1" /></td>
        </tr>
      <input id="to_do_list_items_attributes_1_id" name="to_do_list[items_attributes][1][id]" type="hidden" value="2" />
        <tr>
          <td><input id="to_do_list_items_attributes_1_content" name="to_do_list[items_attributes][1][content]" size="30" type="text" value="arst" /></td>
          <td><input id="to_do_list_items_attributes_1_due_date" name="to_do_list[items_attributes][1][due_date]" size="30" type="text" value="2009-07-30" /></td>
          <td><input name="to_do_list[items_attributes][1][done]" type="hidden" value="0" /><input id="to_do_list_items_attributes_1_done" name="to_do_list[items_attributes][1][done]" type="checkbox" value="1" /></td>
        </tr>
    </tbody>
  </table>
  
  <input id="to_do_list_submit" name="commit" type="submit" value="Save" />
</form>

The keen eye will notice the hidden <input> tags that are direct children of the tbody:

    <tbody>
      <input id="to_do_list_items_attributes_0_id" name="to_do_list[items_attributes][0][id]" type="hidden" value="1" />
        <tr><!-- clip! --></tr>
      <input id="to_do_list_items_attributes_1_id" name="to_do_list[items_attributes][1][id]" type="hidden" value="2" />
        <tr><!-- clip! --></tr>
    </tbody>

This is not a valid place for an <input> tag.

I’ve put an example project demonstrating this on my GitHub

Using fields_for with Nested Attributes, Calling it Multiple Times

There has been quite an annoying problem that has been bugging @chendo and I today.

In short, fields_for when used with accepts_nested_attributes_for does not reset it’s index when it’s called multiple times for the same attribute.

Our scenario was the following:

We had a 3 levels of hierarchical data that we had to display in a <table>. Because of the nature of the data we were displaying one level as columns and the other level as rows. Unfortunately, HTML mandates that we group everything by rows—each column is really just a single cell. This means that we have to iterate over the “column” values not just once, but once for every row.

Now, fields_for takes care of naming your <input> tags magically with the correct indexes so that your models can accept the params directly and magic happens. However, evidently calling fields_for multiple times was not part of the original intention, and it seems resetting the index after each call was neglected.

@chendo couldn’t see a cleaner way of doing this so we added our own option to reset the index. We didn’t just override the behaviour in case there is a good reason to leave it, but here is our code:

class ActionView::Helpers::FormBuilder
  def fields_for_with_nested_attributes_with_index_reset(association_name, args, block)
    if args.last.is_a?(Hash) && args.last[:reset_index]
      @nested_child_index = nil
    end

    fields_for_without_nested_attributes_without_index_reset(association_name, args, block)
  end

  alias_method_change :fields_for_with_nested_attributes, :index_reset
end