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 [/sourcecode] Model Chromosome: [sourcecode language="ruby"] 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[:id]) end def update @chromosome = Chromosome.find(params[:id]) if @chromosome.update_attributes(params[:chromosome]) flash[:notice] = "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.
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.
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.
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.
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.