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.

Ultraviolet Tools

A few weeks ago, I was modifying my clone of Enki (my blogging platform) to use Ultraviolet instead of CodeRay, as it supports using both TextMate themes and syntaxes. This means I can extend it fairly limitlessly and use TextMate itself to modify the themes an syntaxes.

However, Ultraviolet doesn’t come with the best tools to turn the TextMate syntax and theme files into files Ultraviolet can use, and the ones that it does provide puts those translated files INTO the gem on your system. This means that you can’t easily deploy or share them.

So, to remedy this I made a small gem Ultraviolet Tools that provides two command line tools: uv-create-syntax and uv-create-theme.

They aren’t very complicated, nor very documented, but they are straight-forward to use:

Install

Install Ultraviolet Tools with sudo gem install bjeanes-ultraviolet-tools -s http://gems.github.com.

Usage

Themes

Run uv-create-theme path/to/theme.tmTheme

Syntaxes

Run uv-create_syntax path/to/syntax.plist or uv-create_syntax path/to/bundle/with/syntaxes.tmBundle

Using the resulting files

Use my fork of ultraviolet as it has an option to change the load path for themes and syntaxes so that you can load them out of your application files.

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<% 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<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:

1
2
3
4
5
6
<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 accepts_nested_attributes_for When the Child Object validates_presence_of Parent

I came across an interesting dilemma today in Rails 2.3 whilst trying to use accepts_nested_attributes_for.

The models I have were:

1
2
3
4
5
6
7
8
9
class Organization < ActiveRecord::Base has_many :users accepts_nested_attributes_for :users end class User < ActiveRecord::Base belongs_to :organization validates_presence_of :organization end

I had an “Organization” creation form which allowed an administrator to create an Organization and an initial user for it. However, even though I had filled in all the required fields to pass the validations I was constantly getting the following error: Organization can not be blank.

Digging deeper I found that when an object is added to an association via the association.build method, the parent object isn’t actually set:

1
2
3
4
5
6
>> o = Organization.new(:name => "Test") => #<Organization id: nil, name: "Test"> >> u = o.users.build(:name => "Test User") => #<User id: nil, organization_id: nil, name: "Test User"> >> u.organization => nil

Apparently this flaw also applies to accepts_nested_attributes_for. When I send through the User params through from my form normally with the Organization params, it creates the User object in the users association as you’d expect, but raises a validation error because the User instance doesn’t recognise that it is has a parent Organization

My quick 5 minute fix was to modify the Organization model thusly:

1
2
3
4
5
6
7
8
9
10
class Organization < ActiveRecord::Base has_many :users accepts_nested_attributes_for :users def users_attributes_with_self_assignment=(attributes) self.users_attributes_without_self_assignment = attributes users.each { |u| u.organization = self } end alias_method_chain :users_attributes=, :self_assignment end

Related Rails bugs seem to be here and here. This patch will also solve the problem and is currently in edge, but didn’t make it in time for Rails 2.3.

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:

1
2
3
4
5
6
7
8
9
10
11
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

Using AlterEgo with ActiveRecord

Today after finding the gem AlterEgo and falling in love with the API, @chendo and I came to a sad realisation that its beauty was skin-deep. It seems it is not ActiveRecord-aware and overrides #state and #state= on your models. This means that your state column goes untouched and your models are always in the default state.

To get around this, @chendo and I hacked together this little monkey patch which does two things:

  1. It adds question methods for each of your defined states. So if you have states: active, inactive, and banned on your User model, your instances will have #active?, #inactive?, and #banned? defined.
  2. It properly sets and gets the state of the row and loads it into AlterEgo.

Here is the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
module AlterEgo def self.included(base) base.instance_eval do # This will allow us to define a question method for each state in a class # For instance if user has states :active and :inactive, methods active? and inactive? # will be defined when first called def method_missing_with_states(method, *args, &block) begin method_missing_without_states(method, *args, &block) rescue NoMethodError => e state_found = false result = nil base.states.keys.each do |state| if method.to_s == "#{state}?" && !state_found self.class.send :define_method, "#{state}?" do self.state == state end state_found = true result = self.__send__(method) end end raise(e) unless state_found result end end alias_method_chain :method_missing, :states # state setter to write to database define_method :state= do |value| write_attribute(:state, value.to_s) end # state getter to read from database define_method :state_with_active_record do (read_attribute(:state) || (self.state = state_without_active_record)).to_sym end alias_method_chain :state, :active_record end end end