At a complete (Rails) loss

I’ve been banging my head against the wall for almost a week with a Rails application. This post is not a plea for help – I’d use a forum for that – just a record of the problem. That said, feel free to comment, especially if you have a similar problem.

This is all using Rails 2.3.2, Mongrel 1.1.5, installed as gems on Ubuntu 9.04.

The basic issue: 2 models, 2 controllers, 2 sets of views. Identical in almost every respect, little more than basic CRUD (index, create, update, destroy). (1) works, (2) does not.

Update – thanks for your comments, here and elsewhere. In the end I rebuilt from scratch with scaffolding and it’s all good so far. Guess there was something rogue in my hand-crafted code.

The details:
1. The models
Model Organism:

class Organism < ActiveRecord::Base
  has_many :chromosomes
  has_many :platforms

  validates_presence_of     :name,:binomial,:taxid
  validates_numericality_of :taxid
  validates_uniqueness_of   :name,:binomial,:taxid
end
&#91;/sourcecode&#93;

Model Chromosome:
&#91;sourcecode language="ruby"&#93;
class Chromosome < ActiveRecord::Base
  belongs_to :organism
  has_many   :features

  validates_presence_of     :name, :length
  validates_numericality_of :length
  validates_uniqueness_of   :name, :scope => :organism_id
end

2. The routes
File routes.rb contains (just the relevant lines):

ActionController::Routing::Routes.draw do |map|
  map.resources :chromosomes
  map.resources :organisms
end

3. Controller for Chromosome (this works)
Here’s the controller for Chromosome. It’s just your basic CRUD.
chromosomes_controller.rb

class ChromosomesController < ApplicationController

  def index
    @chromosomes = Chromosome.all
  end

  def edit
    @chromosome = Chromosome.find(params&#91;:id&#93;)
  end

  def update
    @chromosome = Chromosome.find(params&#91;:id&#93;)
    if @chromosome.update_attributes(params&#91;:chromosome&#93;)
      flash&#91;:notice&#93; = "Chromosome updated"
      redirect_to :action => :index
    else
      render :action => :new
    end
  end

  def new
    @chromosome = Chromosome.new
  end

  def create
    @chromosome = Chromosome.new(params[:chromosome])
    if @chromosome.save
      flash[:notice] = "Chromosome added"
      redirect_to :action => "index"
    else
      render :action => "new"
    end
  end

  def destroy
    @chromosome = Chromosome.find(params[:id])
    # don't delete chromosome if linked to features
    if Feature.all(:conditions => {:chromosome_id => @chromosome.id}).length > 0
      flash[:notice] = "Cannot delete chromosome:  it has features"
      redirect_to :action => "index"
    else
      @chromosome.destroy
      flash[:notice] = "Chromosome deleted"
      redirect_to :action => "index"
    end
  end

end

3. Views for Chromosome (these work too)
Now the views: index, edit, new and a partial named “_form.html.erb”.
index.html.erb:

<h1>Chromosomes</h1>
<div class="info">Add a <%= link_to 'new chromosome', new_chromosome_path %> here.</div>
<div class="info">Or click a 'last update' table entry to view/edit/delete.</div>

<table>
 <tr>
   <th>Name</th>
   <th>Length</th>
   <th>Organism</th>
   <th>Last Updated</th>
 </tr>

 <% @chromosomes.each do |@chrom| %>
   <tr class="data">
     <td><%=h @chrom.name %></td>
     <td><%=h @chrom.length %></td>
     <td><%=h @chrom.organism.name %></td>
     <td><%= link_to @chrom.updated_at, edit_chromosome_path(@chrom) %></td>
   </tr>
 <% end %>
</table>

new.html.erb and edit.html.erb – are essentially identical except for wording:
new.html.erb

<h1>New Sequence</h1>
<div class="info">Enter sequence details and click 'Save'.</div>

<%= render :partial => "form" %>

Here’s the partial ERB file, _form.html.erb:

<% form_for(@chromosome) do |f| %>
  <%= f.error_messages %>
  <table>
    <tr>
      <td><%= f.label      :name %></td>
      <td><%= f.text_field :name %></td>
    </tr>
    <tr>
      <td><%= f.label      :length %></td>
      <td><%= f.text_field :length %></td>
    </tr>
    <tr>
      <td><%= f.label      :organism %></td>
      <td>
        <%= render :partial => "organisms/select", :locals => {:organisms => @organisms} %>
      </td>
    </tr>
    <tr>
      <td colspan="2" class="right"><%= f.submit 'Save' %></td>
    </tr>
  </table>
<% end %>

<% if @chromosome.id %>
<div class="info">Or <%= link_to 'delete', @chromosome,
                         :confirm => "Are you sure?",
                         :method => :delete %> this chromosome permanently.</div>
<% end %>

The partial app/views/organisms/_select.html.erb is simply:

<%= collection_select(:chromosome, :organism_id, Organism.all, :id, :name) %>

4. Controller and views for Organism (these don’t work)
The controller organisms_controller.rb is identical to that for chromosomes, EXCEPT that @chromosome(s) is replaced by @organism(s) throughout and the destroy method is:

  def destroy
    @organism = Organism.find(params[:id])
    # don't delete organism if linked to chromosomes
    if Chromosome.all(:conditions => {:organism_id => @organism.id}).length > 0
      flash[:notice] = "Cannot delete organism:  it has chromosomes"
      redirect_to :action => "index"
    else
      @organism.destroy
      flash[:notice] = "Organism deleted"
      redirect_to :action => "index"
    end
  end

And index.html.erb, new.html.erb, edit.html.erb are also identical, except that @chrom(osome) is replaced by @org(anism) throughout and the fields are those for Organism. For example in _form.html.erb:

<% form_for(@organism) do |f| %>
  <%= f.error_messages %>
  <table>
    <tr>
      <td><%= f.label      :name %></td>
      <td><%= f.text_field :name %></td>
    </tr>
    <tr>
      <td><%= f.label      :binomial %></td>
      <td><%= f.text_field :binomial %></td>
    </tr>
    <tr>
      <td><%= f.label      :taxid %></td>
      <td><%= f.text_field :taxid %></td>
    </tr>
    <tr>
      <td colspan="2" class="right"><%= f.submit 'Save' %></td>
    </tr>
  </table>
<% end %>

<% if @organism.id %>
<div class="info">Or <%= link_to 'delete', @organism,
                         :confirm => "Are you sure?",
                         :method => :delete %> this organism permanently.</div>
<% end %>

5. The problem
Chromosome works just fine. Index shows a list of chromosomes. New adds a new chromosome. Edit fills in the form with chromosome parameters and allows update or destroy.

Organism does not work just fine. The new method works and adds a new organism. But edit gives this error (I removed the ERB/HTML tags as they mess up blog formatting):

NoMethodError in Organisms#edit
Showing app/views/organisms/_form.html.erb where line #1 raised:
undefined method `organism__path' for #ActionView::Base:0xb6a81fe0
Extracted source (around line #1):

1: form_for(@organism) do |f|
2: f.error_messages

Trace of template inclusion: app/views/organisms/edit.html.erb

RAILS_ROOT: /bioinfo/neil/projects/microarray_mining/code/Rails/ArrayMiner
Application Trace | Framework Trace | Full Trace

/usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/polymorphic_routes.rb:109:in `__send__'
/usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/polymorphic_routes.rb:109:in `polymorphic_url'
/usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/polymorphic_routes.rb:116:in `polymorphic_path'
/usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/helpers/form_helper.rb:298:in `apply_form_for_options!'
/usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/helpers/form_helper.rb:277:in `form_for'
code/Rails/ArrayMiner/app/views/organisms/_form.html.erb:1:in `_run_erb_app47views47organisms47_form46html46erb_locals_form_object'
code/Rails/ArrayMiner/app/views/organisms/edit.html.erb:4:in `_run_erb_app47views47organisms47edit46html46erb'

The problem?
There seems to be some issue with routes. For some reason, form_for(@organism) works fine with the new method, but not with the edit method. The error “undefined method ‘organism__path'” (note the 2 underscores) is perplexing.
Other observations – (1) moving lines around in routes.rb then restarting Mongrel has sporadically fixed the problem, but then errors return. (2) I have similar code for a model named Technology which misbehaves in the same way, but code for 3 other models in addition to Chromosome works fine. As before, all the code is more or less identical, just with different variable names.

4 thoughts on “At a complete (Rails) loss

  1. Joel

    what does “rake routes” say?

    I think you need to reboot rails when routes.rb has been changed. (Not sure though) — that might be a cause of error if you added organisms later.

    I generally stay away from using the automatic routing in rails, and just write a string like “/organism/edit/#{id}” since removed a layer of complexity.

    1. nsaunders Post author

      Thanks for the comment Joel. “rake routes” gives what I’d expect – the standard RESTful routes for both organisms and chromosomes. I’m also never sure whether server reboot is required when editing routes.rb – I know it is for environment.rb.

      Anyways, I’ve started again from scratch, building what I’d hand-coded using scaffolding and it’s all good so far.

  2. Adam Kraut

    Neil,

    Since you have nested resources, give this post a look over.

    http://adam.blog.heroku.com/past/2007/12/20/nested_resources_in_rails_2/

    Things I’ve found to fix problems like yours is to use form_for([@parent, @child]) when using forms on a nexted resources. Also, in your routes you can use map.resources :organisms, :has_many => :chromosomes.

    Then your router will handle nested URL’s such as:

    /organisms/2/chromosomes/new
    /organisms/1/chromosomes/2/edit

    Hope this is helpful. Post back your results as I recall dealing with the same issue recently.

    1. nsaunders Post author

      Thanks Adam. Yes, someone at rails forum pointed out that my resources are nested too. To be honest, I’m only just getting my head around regular resources, but your suggestions would be the way to go.

Comments are closed.