Incorporation

We incorporated our startup today...a small milestone on a long journey, so we took a picture to commemorate the moment.

HTML and Helpers

Look at all this code. Yikes!

Even though, in theory, you're supposed to be allowed to put HTML in your helpers, the result is that you still end up mixing up all your presentation and business logic. Some people would argue that it's not possible to separate presentation and content. I'm not arguing either way...just trying to point out that it's easy to get these things intermingled.

Anyhow, what's going on here is that a document group has a bunch of document templates. Users will have filled out some templates--in which case the "instances" of those templates become documents--and in other cases they won't have filled them out.

Originally, I was just going to do a condition based on the existence of the document or not, but then I realized this edge case that somehow a user may end up with two documents based on the same template. I'm not sure how this would happen, but could happen easily, for example, if the user bookmarked the URL for the new document page for that document template.

I suppose I could make the new action check for the existence of a document using said template for the current user, and that would almost ensure this problem wouldn't happen...from the UI. What happens if data is added directly to the database? In fact, that's how I found this case...I accidentally loaded some test data twice and my original code crapped out.

Anyhow, in my view:



    <%
    @document_group.document_templates.each do |child|
    array_of_links_to_document_if_exists_from_template(child).each do |link|
    %><li><%= link %></li><%
    end
    end
    %>


And in my helper:


def array_of_links_to_document_if_exists_from_template(document_template)
links = []
documents = Document.find(:all, :conditions => { :journal_id => @journal, :document_template_id => document_template })
if (!documents.empty?)
documents.each do |document|
links.push(link_to_remote(document.name, { :update => "dvMainPane", :url => { :controller => :documents, :action => :edit, :id => document }}))
end
else
links.push(link_to_remote(document_template.name, { :update => "dvMainPane", :url => { :controller => :documents, :action => :new, :journal_id => @journal, :document_template_id => document_template.id }}))
end
links
end

Is the helper the right place for business logic? I'm thinking right solution here isn't to put the logic in a helper...maybe helpers are really just rendering helpers...and that the logic should be in my model.

Navigating Associations in Scaffolding with Toffee

My friend Mark suggested Toffee for dealing with navigating scaffold associations.

Haven't tried it yet, but on the surface seems to solve some of what I was discussing.

Creating Multiple Objects (of different Models) in one form.

Yikes! Did I ever dig hole for myself, so let me tell you how I got out of it.

Long story short: I have a model architecture like this:

document
|
--> document_content_groups[]
|
-->document_contents

In other words, document_contents are name value pairs...think of them as questions (name) and answers (value). one or more document_contents are grouped into a document_content_group...think of that as a chunk of questions. in other words, a document_content_group has a collection of document_contents. then, a document has a collection of document_content_groups.

So, on a form where a user creates a new document really we're creating many things:

1 new document object
1 or more document_content_group objects
1 or more document_content objects

and, all of these must be associated with their parent properly.

On p. 499 the Rails book (Agile Web Development with Rails) it is suggested that something like this be done:

<% for_tag do %>
<% for @product in @products %>
<%= text_field("product[]", 'image_url') %><br />
<% end %>
<% end %>


and what this syntax is supposed to do is make the ID of the object be inserted into the form "name" field, e.g. name="product_ID[image_url]". But (sharing this is the purpose of this post), this syntax does not work with a newly created object, it only works with editing existing objects. I solved the problem like this:


@dcg_index = 0
@dc_index = 0
for document_content_group in @document_content_groups %>
<p><%= document_content_group.name %><br />
<%= text_field("document_content_group", 'name', :index => @dcg_index) %></p>
<%
for document_content in document_content_group.document_contents %>
<p><%= document_content.name %><br />
<%= hidden_field ("document_content", "document_content_group", :value => @dcg_index.to_s, :index => @dc_index) %>
<%= hidden_field ("document_content", "name", :value => document_content.name, :index => @dc_index) %>
<%= text_field("document_content", "value", :index => @dc_index) %></p>
<% @dc_index += 1%>
<% end %>
<% @dcg_index += 1%>
<% end %>


Now, the result is that when the form is posted I get two hashes in the params: document_content_groups and document_contents each of which contains hashes representing the objects I want to create.

I can easily create the document_content_groups by passing those hashes in to create, but I have to do some munging on the document_contents to prepare them:

@dcg_index = 0 # this must be the same initial index from the "new" form
for document_content_group_hash in params[:document_content_group].values do
document_content_group = @document.document_content_groups.create(document_content_group_hash)
for document_content_hash in params[:document_content].values do
if (document_content_hash["document_content_group"].to_i == @dcg_index)
# this is removed from the hash because it's not really the document_content_group_id...
# it's just a temporary ID that was used to group the items together from the _new form
document_content_hash.delete("document_content_group")
document_content_group.document_contents.create(document_content_hash)
end
end
@dcg_index += 1
end

Anyhow, not particularly pretty, but it gets the job done for now. Suggestions on how to make it more graceful are welcome.

Navigating Associations in Scaffolding

In a scaffold generated list.rhtml only the content_columns are displayed. I modified that to show all columns, but the drawback is that for foreign keys you only see the ID, not any thing descriptive. That is, if I have books which belong_to authors, then what I see from the list.rhtml is author_id, not author_name.

I’ve been working to get around that because I can save a lot of time if the joins work magically. So, I wrote some code that basically looks at a column, if it’s an “id column”, then it walks the association to find the object. Once found, it looks for some logical text to display, and if none is found then defaults to the ID.

I've put the code below, but I can’t believe Rails doesn’t provide a way to do this automagically...it seems like something that would be used so often. Anyone?

Anyhow Scaffold makes this:

<% for document_content_template in @ document_content_templates %>

<% for column in DocumentContentTemplate.content_columns %>
<%=h document_content_template.send(column.name) %>
<% end %>
[... snipped show-edit-destroy ]

<% end %>


Which I’ve changed to this:

<% for document_content_template in @document_content_templates %>

<% for column in DocumentContentTemplate.columns %>
<%=h display_column_text(column, document_content_template) %>
<% end %>
[... snipped show-edit-destroy ]

<% end %>


With these helpers:

1  def display_column_text(column, object)
2 if column.name[-3, 3] != "_id"
3 object.send(column.name)
4 else
5 value_from_join(column, object)
6 end
7 end
8
9 def value_from_join(column, object)
10 h = column.name.sub(/_id/, '') # strip off the id
11 r = object.send(h) # get the object
12
13 if (defined? r.name != nil)
14 r.name
15 elsif (defined? r.description != nil)
16 r.description
17 else
18 r.id
19 end
20 end

P.S. Yes, I realize there might be a bug on line 10.
P.P.S There’s actually a potentail bug on line 11 as well…what happens if you have a controller that’s like this:

belongs_to :model_name, :foreign_key => "some_other_id"

which will throw this error:
undefined method `some_other' for #<ModelName:0x47a8650>


UPDATE: the bug on line 11 can easily be handled like so:
begin                               # get the object
r = object.send(h)
rescue
return object.send(column.name)
end


Again, I can’t believe Rails doesn’t the magic to navigate around these objects more gracefully. Thoughts or comments anyone???

Perfection, Not Progress

I wasted an entire day yesterday on :counter_cache => true. (Why was it wasted? Because I didn't need a counter cache yesterday, but knew that I would need it in a few weeks or months...progress versus perfection rears its ugly head again.)

Anyhow the problem ultimately had nothing to do with counter caching, but the error message didn't help too much, and there wasn't much in Google about the error message, so I spent a lot of time looking for the problem in the wrong place. Hopefully this post will help others in the future if you're getting this error message:

NoMethodError in Document content templateController#create
undefined method `increment_counter' for Class:Class

Yesterday I was creating entities called DocumentTemplate and DocumentContentTemplate:
class DocumentTemplate < ActiveRecord::Base
has_many :DocumentContentTemplates
end

class DocumentContentTemplate < ActiveRecord::Base
belongs_to :DocumentTemplate
end

Then, I ran a quick script/generate scaffold on both of these, and modified the _form.rhmtl for DocumentContentTemplate in order to add the association to DocumentTemplate. Everything is working fine.

Next, I decide that I'm going to need a counter cache on the DocumentTemplate, so I modify the DocumentContentTemplate model and added the document_content_templates_count column to the document_templates table via migration:
class DocumentContentTemplate < ActiveRecord::Base
belongs_to :DocumentTemplate, :counter_cache => true
end

Now, when I create a new DocumentContentTemplate the method DocumentContentTemplate#create should automatically increment the document_content_templates_count column in the document_templates table. But, when I submit the form that calls the create method I get this error:
NoMethodError in Document content templateController#create 
undefined method `increment_counter' for Class:Class

I spent hours debugging and investigating all the things related to counter cache, but it turns out that wasn't the problem. Go way back to the has_many and belongs_to declarations:
has_many :DocumentContentTemplates
belongs_to :DocumentTemplate

which, if you look carefully, should really be:
has_many :document_content_templates
belongs_to :document_template

So, if you're seeing that error, that's how to fix it. But, this raises two issues that I want to bring up:

1. My understanding is that the camel-case of a class name is supposed to be used when referring to the class, not an instance of it. For example, camelcase is used in a class definition or when the class is being used:
class DocumentTemplate < ActiveRecord::Base

DocumentTemplate.find(:all)

With that in mind, aren't the has_* and belongs_to methods referring to classes? Why don't they use the camelcase?

2. Regardless of the answer to #1, why does Rails handle this error gracefully? Why does has_*|belongs_to :ClassName not throw an error when it should be has_*|belongs :class_name?