Topic: Rails complaints and proposed solutions.

Rails complaints and proposed solutions

My Rails experience has been aggravating at times. I've been struggling with Rails, not because it's a lame framework, it's not. It's exceptionally good and getting better. It's just that the code pluralization is intolerable to me. I've been spending vast amounts of effort with hackish workarounds to pluralization. And while it's been both fun and frustrating, I've learned quite a bit about Rails by obstinately resisting code pluralization. My complaint is not that it sometimes gets the words wrong. My complaint is that it is taking liberties with my code in the first place. I just can't stand it messing with my identifiers.

It seems quite a few Rails programmers love pluralization. I'm genuinely happy for them. Others merely tolerate it. I applaud their fortitude. But for me, it's the one major design flaw in an otherwise outstanding framework. Calling it merely a flaw understates the hostility and revulsion I feel toward this thing. It's as obnoxious as blinking text and jittering images on a 12-year-old's personal photo album site. It makes me want to hit the back button. Just because we can do something doesn't mean we should.

It's easy enough to make a blunt workaround. I just tell it to make identical spellings for every word in both directions. Two lines of code in config/initializers/inflections.rb and the problem's solved. But if I ever want to import third-party code, I have to take extreme countermeasures to defeat the inflictor's unwelcome advances. There also seem to be a few bugs in Rails, where it depends on two distinct spellings for singular and plural in order to avoid name conflicts, notably the mapping of RESTful resources.

I realize it's too late to reconsider this design decision of Rails, but it's not too late to do a little more to accommodate the needs of those who prefer not to pluralize. If it really is just an issue of the color of the bike shed, then we shouldn't mind redecorating once in a while, or providing the tools for some of us to paint the damn shed.


Why automated code pluralization is Bad Design.

Consider these examples:

* A table named "roles_users" instead of "role_user"
    Is this meant to be more grammatically correct? It isn't.

* A routing path /users/5/articles/2/comments/9 instead of /user/5/article/2/comment/9
    Even if you love contextually pluralized code, who came up with this abomination?

* An array element called articles[2] instead of article[2] or article_array[2]
    I'd like to point out that article[2] is more correct English grammar than articles[2].
    However, I have to admit this is one of the strongest cases in favor of pluralization.
    An array named article is potentially confusing. An array named articles is okay
    with me, but an array named article_array carries more semantics, without losing
    searchability. Furthermore, in Ruby one symbol references one object. In this case, the
    symbol references one Array (of Article model objects), not multiple Article objects.

* An integer called article_count instead of articles
    Oh. Wait. Nevermind. Rails got that one right.  The integer name article_count carries
    more semantic cues than the integer name articles, and is more grammatically correct
    than the name articles_count.

* A one-row table for managing the entire application with a model called Everything.
    Rails creates a script db/schema.rb . Why is this file named in the singular, instead
    of db/schemata.rb ? Probably because it refers to the overall shape of the data for the
    entire application. Okay, fair enough. But when I want to do something similar, I get a
    controller called everythings_controller. But that table is only supposed to ever have
    one row, for managing the entire application. Sometimes records just want to be alone.

* An English word that has multiple plural forms.
    An attorney might use 'persons,' the regular plural of 'person,' while a community
    organizer would use the suppletive plural 'people.' A sociologist might use 'peoples'
    as the plural form of the singular 'people'. The plural of schema is is schemata or
    schemas, depending on your mood. But what's the plural of nickel? (No, I meant
    the element, not the coin.)

* A natural language that has no plural forms.
    Sorry about that, Matz.

Mainly, I just want one universal form of every identifier. I want my identifiers to be searchable, and I want them to stay right where I put them.


One proposed pluralization solution

Imagine I can put this line of code into config/environment.rb ...

ActiveRecord::Base.code_pluralization = false     # Proposed new feature

Setting this to true, or leaving it out, would cause everything to behave exactly as it does now. Setting it to false would cause Rails to behave slightly differently where the inflector is called in generators or similar processes. Instead of pluralizing an identifier, a contextual word can be added in the appropriate contexts, but always leaving the original word untouched between the underscores or CamelCasing. In much the same way as _controller and _helper are added to file names, and _count and _id are added to column names, we could add an underscore-separated word to the unadulterated spelling of an identifier. And that word would carry richer meaning than merely pluralizing the original identifier. Hopefully this can go a long way toward pleasing everyone.

Consider an application where a User record "has and belongs to many" Article records, an Article record "has many" Comment records, and each Comment record "belongs to" one User record. Here, each article is owned by one or more users, and other users can come along and attach a comment to an article.

In this alternate universe, there'd be identifiers like UserModel, ArticleController, and comment_count.

Instead of this code...

# Find all of "that article's" comments
@comments_of_that_article = @that_article.comments
@new_comment = @that_article.comments.build           # Note this is less grammatically correct.

...there could be this:
# Find all of "that article's" comments
@comments_of_that_article = @that_article.comment_array
@new_comment = @that_article.comment.build             # Note this is more grammatically correct.

And instead of this...
# Get all of the user's articles' comments into an array of arrays.
@articles = User.find(@that_user).articles
@comments = @articles.collect { |article| article.comments }

...there could be this:
# Get all of the user's articles' comments into an array of arrays.
@articles = User.find(@that_user).article_array                     # This habtm method is renamed
@comments = @articles.collect { |article| article.comment_array }   # This has_many method is renamed

Of course, for consistency I'd probably choose to use variables named @something_something_array instead of @comments and @articles:
# Get all of the user's articles' comments into an array of arrays.
@article_array = User.find(@that_user).article_array
@article_array_of_comment_arrays = @article_array.collect { |article| article.comment_array }
# Yes, I noticed I pluralized one of the arrays above. Sometimes pluralization adds value.

I would propose using _set instead of _array to indicate a "set of" records. It's shorter, and more appropriate to database set theory. However some people might be confused, since set_ as a prefix often means "write to". In Ruby though, it's customary to use a method called '=' instead of the Java-like method setSomething(newValue). So if it doesn't seem too confusing, I'd go with _set instead of _array.

For someone who understands the internals of Rails, this shouldn't be too difficult to implement. At some point, I will know enough to contribute such a thing to the Rails project, but for now, I'm too busy learning my way around. All I can do is offer this suggestion and hope it finds its way to the right person.


Rails inconsistencies

Here are some inconsistencies in Rails, and my humble suggestions of how they could be made more consistent:

Currently in Rails, this route.rb line...

map.resources :messages

...creates these routes:
 messages               GET    /messages                  {:action=>"index"  , :controller=>"messages"}
formatted_messages     GET    /messages.:format          {:action=>"index"  , :controller=>"messages"}
                        POST   /messages                  {:action=>"create" , :controller=>"messages"}
                        POST   /messages.:format          {:action=>"create" , :controller=>"messages"}
new_message            GET    /messages/new              {:action=>"new"    , :controller=>"messages"}
formatted_new_message  GET    /messages/new.:format      {:action=>"new"    , :controller=>"messages"}
edit_message           GET    /messages/:id/edit         {:action=>"edit"   , :controller=>"messages"}
formatted_edit_message GET    /messages/:id/edit.:format {:action=>"edit"   , :controller=>"messages"}
message                GET    /messages/:id              {:action=>"show"   , :controller=>"messages"}
formatted_message      GET    /messages/:id.:format      {:action=>"show"   , :controller=>"messages"}
                       PUT    /messages/:id              {:action=>"update" , :controller=>"messages"}
                       PUT    /messages/:id.:format      {:action=>"update" , :controller=>"messages"}
                       DELETE /messages/:id              {:action=>"destroy", :controller=>"messages"}
                       DELETE /messages/:id.:format      {:action=>"destroy", :controller=>"messages"}

I'm not sure why the non-GET routes don't have a name, but it doesn't seem it would do any harm to give them names. I'm glad to see the semicolons are now slashes in the newer versions. Instead of the standard resource mapping, I've been using this kind of routes in my applications:
 messages           GET    /messages/                   {:action=>"index"  , :controller=>"message"}
messages_f         GET    /messages/.:format           {:action=>"index"  , :controller=>"message"}
message_index      GET    /message/index               {:action=>"index"  , :controller=>"message"}
message_index_f    GET    /message/index.:format       {:action=>"index"  , :controller=>"message"}
message_create     POST   /message/create              {:action=>"create" , :controller=>"message"}
message_create_f   POST   /message/create.:format      {:action=>"create" , :controller=>"message"}
message_new        GET    /message/new                 {:action=>"new"    , :controller=>"message"}
message_new_f      GET    /message/new.:format         {:action=>"new"    , :controller=>"message"}
message_edit       GET    /message/:id/edit            {:action=>"edit"   , :controller=>"message"}
message_edit_f     GET    /message/:id/edit.:format    {:action=>"edit"   , :controller=>"message"}
message_show       GET    /message/:id                 {:action=>"show"   , :controller=>"message"}
message_show_f     GET    /message/:id.:format         {:action=>"show"   , :controller=>"message"}
                   GET    /message/:id/show            {:action=>"show"   , :controller=>"message"}
                   GET    /message/:id/show.:format    {:action=>"show"   , :controller=>"message"}
message_update     PUT    /message/:id/update          {:action=>"update" , :controller=>"message"}
message_update_f   PUT    /message/:id/update.:format  {:action=>"update" , :controller=>"message"}
message_destroy    DELETE /message/:id/destroy         {:action=>"destroy", :controller=>"message"}
message_destroy_f  DELETE /message/:id/destroy.:format {:action=>"destroy", :controller=>"message"}

In the interest of consistency, my non-GET routes have a minor redundancy, as with "DELETE /message/6/destroy" instead of "DELETE /message/6", but I find myself less likely to accidentally call a destructive action in such cases when the intention appears in both the HTTP request method and the URL. Also, note the inconsistencies in my routes. My index routes recognize two forms: the plural controller name (much like standard rails), and the singular name with an /index verb. This gives two ways to refer to the index action. Also, my show action normally omits the /show verb from the URL, while an anonymous route includes /show, and these all point to the same action. This makes the more explicit "GET /message/6/show" request route an alternative form of the shorter, more canonical "GET /message/6". This level of consistency makes it much easier to do metaprogramming with these routes. It's also easier to memorize the routes and their names, with just a few simple rules.

In standard Rails, this line in models/article.rb ...

class Article < ActiveRecord::Base
    has_many :comments       # Each Article record has many Comment records

...causes the Article model to acquire several methods for manipulating associated Comment records. This is what they're like now:
 # Don't try to follow the logic. Each line stands alone as a syntax illustration.
@comments_of_that_article = @that_article.comments
new_comment = @that_article.comments.build
@that_article.comments << @new_comment
@that_article.comments.replace(@edited_comment)
@that_article.comments.delete(@bad_comment)
@that_article.comments.delete_all
@that_article.comments.clear
@psychic_comments = @that_article.comments.find(:all, :conditions => ['date_created > ?', today])
@comment_count = @that_article.comments.size
@uniq_comments = @that_article.comments.uniq

This is what they could be like:
class Article < ActiveRecord::Base
    has_many :comment        # Each Article record has many Comment records

 # Don't try to follow the logic. Each line stands alone as a syntax illustration.
@comments_of_that_article = @that_article.comment_set
new_comment = @that_article.comment.build
@that_article.comment_set << @new_comment
@that_article.comment.replace(@edited_comment)
@that_article.comment.delete(@bad_comment)
@that_article.comment_set.delete_all
@that_article.comment_set.clear
@psychic_comments = @that_article.comment_set.find(:all, :conditions => ['date_created > ?', today])
@comment_count = @that_article.comment_set.size
@uniq_comments = @that_article.comment_set.uniq

It seems fairly straightforward to generate '.comment_set.' or '.comment_array.' instead of '.comments.' . It may be less straightforward to implement '.comment_set.' sometimes, and '.comment.' other times. The only reason I propose the two forms is for English grammar considerations. But on the other hand, some of the lines of code lose the in-line reminder of a has_many association. The comprehension benefit to the association reminder in every relevant line of code may outweigh the grammatical consistency consideration, in which case I say make them all the same '.comment_set.' or '.comment_array' but never '.comment.'. One way to implement the two-way style would be by aliasing the singular 'comment' to the 'comment_set', allowing the user to "mix and match" at will. But if it's not feasible to do this, it's still pretty good to use comment_set.build instead of comment.build to build one new comment for the set.

In this way, the identifiers stay stable, consistent, and searchable. And the many-to-one and many-to-many associations remain fairly obvious in the code.


Something to think about,

Loqi

Re: Rails complaints and proposed solutions.

If I understand your problem, I think your basic solution is to just replace the default Inflector rules:

module ActiveSupport
  Inflector.inflections do |inflect|
    # inflect.plural(/$/, 's')
    inflect.plural(/$/, '_set')
  end
end

Re: Rails complaints and proposed solutions.

If only it were that simple, Avit.

The inflector is called throughout Rails for many purposes. If I were to tell it that the plural of anything is anything_set, I'd have an even bigger mess. There are very few contexts where _set is appropriate, such as the names of some of the methods installed by has_many. Everywhere else, I'd like to use the plain-old singular identifier. Furthermore, the underscore character is a delimiter in the context of the inflector, adding excitement and thrills when going from plural to singular.

If I were to simply instruct the inflector to pluralize everything as *_set, I'd get this:

 class ArticleSetController < ApplicationController             # I'd rather have ArticleController
   helper :profile
   before_filter :require_login, :load_my_blog

   # GET /blog_set/5/article_set                                # Instead of /blog/5/article/index
   def index
     @my_article_set = @my_blog.article_set                     # This one comes out right
     @title = "Manage ${my_blog.user.name} Blog"
   end

  # PUT /blog_set/5/article_set/12                             # Instead of /blog/5/article/12/update
  def update
    @article = Article.find(params[:article_id])
    if @article.update_attributes(params[:article])
      flash[:notice] = 'The article has been successfully updated.'
      redirect_to blog_set_article_set_path(@blog, @article)   # blog_article_index_path(@blog,@article)
    else
      render :action => "edit"
    end
  end

# ... and so on ...


I have developed ways of getting around most of these problems for my own code. Foremost, I just tell the inflector to "do nothing" and accept that my has_many injections will have counterintuitive names. But this has subtle implications in unexpected places. There is code out there (including core Rails code) that gets confused when a plural and singular are identical.

Ah, the life of a counter-opinionated programmer.

Re: Rails complaints and proposed solutions.

Just curious, but why are you still using it? If your spending all this time fighting with its ways and hacking around it (which could possibly cause the framework to become unstable or buggy)  why not use something else?

Cheers, Red

Re: Rails complaints and proposed solutions.

reddavis999 wrote:

Just curious, but why are you still using it? If your spending all this time fighting with its ways and hacking around it (which could possibly cause the framework to become unstable or buggy)  why not use something else?

Cheers, Red

I think this kind of discourse is valuable. Every language and framework has its advantages and faults. While a new person blasting in may be a bit hard to swallow, its good to get a fresh peek at Rails.

Besides, despite the issues the OP is still using Rails. We'll get him converted soon enough. big_smile

Re: Rails complaints and proposed solutions.

The main reason I'm using Rails is that it's really, really good. But it's got all this itching powder sprinkled all over it. I have to wash everything before touching it.

Maybe y'all will eventually convert me to David's pluralization ways. (Y'heah... "maybe.") But I'm hoping Rails will eventually be adjusted to accommodate non-pluralizing code style. It can already mostly handle fish and sheep right out of the box. There are only a few bugs where it chokes on identical spellings. Third party code is more hit-or-miss on pluralization assumptions. But core Rails is almost able to accommodate some of what I describe. It's possible to do it without breaking existing code, and (I'm guessing) with very little effort by an experienced Rails committer. A couple of years form now, I'll be able to scratch my own itch. But for now, I can just put out the suggestion.

Re: Rails complaints and proposed solutions.

jbartels wrote:
reddavis999 wrote:

Just curious, but why are you still using it? If your spending all this time fighting with its ways and hacking around it (which could possibly cause the framework to become unstable or buggy)  why not use something else?

Cheers, Red

I think this kind of discourse is valuable. Every language and framework has its advantages and faults. While a new person blasting in may be a bit hard to swallow, its good to get a fresh peek at Rails.

Besides, despite the issues the OP is still using Rails. We'll get him converted soon enough. big_smile

Hehe, I'm not offended by his view of Rails, I understand where he's coming from and have heard many people before complain about the whole pluralization thing, and other parts.

@loqi - Have you tried merb and then using another ORM? You can then still get the railsy type stuff but can dont have to go through all the trouble of making every rails app you create go with your style.

Cheers, Red

Re: Rails complaints and proposed solutions.

That "just curious" message had a "just go away" flavor when I first read it, but now it has a "there's other good stuff" flavor. Thanks for the clarification, Red.

I have not tried Merb. I've been meaning to check it out. I've got my hands full of Rails right now, but I suppose I could put even more on my plate and learn about Merb too. My sense is it's small enough to learn quickly.

There are tons of plugins available for Rails. I need to mess with many of them to get them to play nice with a lobotomized inflector, but it's still less effort than building everything from scratch. And I learn a lot by doing this.

Where's a good resource for a Merb beginner?

Re: Rails complaints and proposed solutions.

loqi wrote:

That "just curious" message had a "just go away" flavor when I first read it, but now it has a "there's other good stuff" flavor. Thanks for the clarification, Red.

I have not tried Merb. I've been meaning to check it out. I've got my hands full of Rails right now, but I suppose I could put even more on my plate and learn about Merb too. My sense is it's small enough to learn quickly.

There are tons of plugins available for Rails. I need to mess with many of them to get them to play nice with a lobotomized inflector, but it's still less effort than building everything from scratch. And I learn a lot by doing this.

Where's a good resource for a Merb beginner?

Sorry about it sounding that way smile

Here are some links to get you started

http://wiki.merbivore.com/
http://www.rubyinside.com/merb-tutorial … s-716.html

Code examples and tutorials -
http://wiki.merbivore.com/example_apps/start
http://wiki.merbivore.com/hello_world
http://wiki.merbivore.com/cookbook/auth … ello_world
http://wiki.merbivore.com/my_first_blog

Cheers, Red

Re: Rails complaints and proposed solutions.

Sweeeeet!