{ height: 1%; } - Ruby on Rails and User Interface Design

CSS, UI Design, Ruby on Rails and cheese ... lots of cheese

Typo with incorrect comment and article timestamps!

Posted by Richard White Thu, 23 Feb 2006 18:33:42 GMT

Ever since I migrated to typo I’ve been wondering what was up with the article and comment timestamps. Obviously something seemed wrong when every comment and article was showing up as being created at the same time. For whatever reason the idea that Typo could be getting something as basic as the timestamp wrong seemed preposterous so I assumed this was only some problem manifested by gremlins in my browser (and the fact that I’ve been preoccupied with all sorts of other things made this a good excuse).

I finally worked up the nerve to look at the database table today and realized that while all the updated_at dates were correct on comments the created_at were all the same (I had been manually adjusting the article timestamps through the admin interface so these weren’t incorrect)! Initial research yielded little especially for a problem that would seem to be very pervasive amongst Typo users. I eventually found ticket on the Typo trac which claims that this is actually a RoR 1.0 bug. Thankfully patching this issue is relatively simple.

The final piece was to fix existing comments which I basically did by renaming the updated_at field to created_at and vice versa.

I’m a little peeved at myself for letting this go so long but hopefully it’ll be smoother sailing from here on in. Then again selecting categories when I create an article still doesn’t work so who knows what else is wrong with Typo.

On Ajax Scaffold known issues

Posted by Richard White Wed, 22 Feb 2006 17:36:00 GMT

AjaxScaffold has been deprecated in favor of ActiveScaffold

* rwhite 2/23 – All these issues have been fixed as of version 2.1.0 read more about it here*

I’ve gotten a couple emails about some issues with the Ajax Scaffold Generator, here is the current list I have:

  • You’ll get a couple errors when you try and run a scaffodl that was generated with different model and controller names like so:
    script/generate ajax_scaffold Gnome Console
  • Safari: The inline create form moves from being a the top to being one row down from the last time it was called.
  • The Effect.Highlight() calls cause new rows to all showup as the same color, ie they aren’t restriped
  • Safari: Clicking edit on the last element in the list crashes the browser.

The good news is that I have patch almost all of these in my dev environment and should have a point release out by the end of the week. I’ll try to keep a running list of known issues on the demo page, but in the meantime just leave a comment on this article of any issues you come across.

On the NEW Ajax Scaffold Generator

Posted by Richard White Tue, 21 Feb 2006 02:05:00 GMT

AjaxScaffold has been deprecated in favor of ActiveScaffold

So I just gemmed and uploaded the v2.0 release of the ajax scaffold generator. There are quite a few improvements in this version, most notably:

  • The generated scaffold code looks production ready: valid XHTML, CSS, fully styled.
  • It now uses a <table>
  • Its designed to be used as a component so you can easily create an admin console with a couple generated scaffolds.
  • A number of CSS styles have already been included for commonly used elements like required field labels, example field input and for some basic form layout control (explained later in this article).
  • Works on Firefox 1+, IE 6+ and Safari 2+ (It may work on others, but I haven’t tested anywhere else, so let me know what you find out)

Why not jump over to the demo page and check it out and then come back here for the walkthrough. (and it wouldn’t hurt if you went and Dugg this article somewhere in between ;) ).

Some of this information is outdated as of version 3.0.0, check out the 3.0.0 release notes

Getting Started

Getting up and running is fairly straightforward, but I’ll hold your hand just in case :)

  • Install the Ajax Scaffold Generator gem
  • gem install ajax_scaffold_generator
    
  • Create a database table (using Rails Migrations hopefully) and configure your config/database.yml
  • Run the generator against the database table you just created, for our purposes here our table is named widgets our model is named Widget and our controller is named Widget (generation options are the same as for the regular Rails scaffold which you can read about here).
  • ruby script/generate ajax_scaffold Widget
    

    or on *nix

    script/generate ajax_scaffold Widget
    
  • Start up WEBrick or whatever RoR server you use and go to http://localhost:3000/widgets/ (obviously this will be different depending on what you have named your objects and if you are running on something other than the default WEBrick port)

Ajax Scaffold Screenshot

Thats it! You have a fully working ajaxified scaffold page.

Adding form validation

Of course we probably want to add some validation to our objects so lets start by making widget.name required.

We add the following to models/widget.rb

class Widget
  <strong>validates_presence_of :name</strong>
end

And now for some UI sugar we will add a class and an asterisk to the name label in views/widget/_form.rhtml:

&lt;div class="form-element"&gt;
  &lt;label <strong>class="required"</strong> for="widget_name"&gt;Name<strong>*</strong>&lt;/label&gt;
  &lt;%= text_field 'widget', 'name'  &gt;
&lt;/div&gt;

And now when we attempt to create a widget without a name we get this helpful message.

Ajax Scaffold Screenshot

UI Sugar

I also find it good practice to give people examples of what their form input should look like (help text is good too but I prefer examples). So lets put an example on the version field:

&lt;div class="form-element"&gt;
  &lt;label for="widget_version">Version&lt;/label&gt;
  &lt;%= text_field 'widget', 'version' %&gt;
<strong>  &lt;label class="example">ex: v1.0, v0.2.4, rc4&lt;/label&gt;</strong>
&lt;/div&gt;

Obviously putting the example under the input is just personal preference and when I have client-side errors I will usually put the example on top, but thats neither here nor there.

Next I may want to make sure that a certain set of fields always appear on a new line. A good example of this might be a situation where I want First Name and Last Name on one line and Address to always be on a line beneath those two elements. To accomplish this we simply wrap each “section” of form elements in a <div class=”row”> in our views/widgets/_form.rhtml:

&lt;fieldset&gt;
  <strong>&lt;div class="row"&gt;</strong>
    &lt;div class="form-element"&gt;
      &lt;label class="required" for="widget_name"&gt;Name*&lt;/label&gt;
      &lt;%= text_field 'widget', 'name'  %&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  <strong>&lt;div class="row"&gt;</strong>
    &lt;div class="form-element"&gt;
      &lt;label for="widget_version"&gt;Version&lt;/label&gt;
      &lt;%= text_field 'widget', 'version'  %&gt;
      &lt;label class="example"&gt;ex: v1.0, v0.2.4, rc4&lt;/label&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/fieldset&gt;

And voila!

Ajax Scaffold Screenshot

General Error Messages

This information is outdated as of version 3.0.0, check out the 3.0.0 release notes

Handling when something goes very wrong is an oft overlooked part of many systems. Your generated scaffold has a fairly simple way of passing general errors back to the user. For the same of example I am going to short circuit the controller method for creating a new widget to always return one of these general errors:

def new
...

#render :layout => false
<strong>render :inline => "Muwhahah! No new widget for you!", 
  :layout => false, :status => 500</strong>
end

Now watch what happens when I try to create a new widget:

Ajax Scaffold Screenshot

Ouch denied!

Embedding multiple scaffolds on the same page

One of the beauties of AjaxScaffolds is that they are designed to be used as components that you can embed on other pages. I’ve documented this in a later article here.

A Few Caveats

There are just a few caveats to all of this. These should hopefully be resolved in future versions (which anyone is more than welcome to pitch in on).

  • The scaffold only generates properly when both element names are the same, thus the following doesn’t work (without some tweaks to the code generated
    <strike>ruby script/generate ajax_scaffold Widget WidgetConsole</strike>
  • There is no graceful degredation for clients without JS Added in 2.2.0
  • There is no pagination or find feature (thanks to Craig for reminding me to point this out) Added in 3.0.0

Future Direction & Wrapping Up

Well that’s is about all we have for the first release of Ajax Scaffold Generator v2.

Just to give you an idea of where we are going here are some future improvements in the development queue:
  • Make the tables sortable
  • Export table data to CSV
  • Ability to specify different types of messages sent to the client (info, warning, etc) without having the operation fail (ie not having to set status = 500)
  • Have it update regularly to pick up on changes made by other users
  • Client side validation
  • Use JSTs to create the forms (update, create) to save server requests
  • Print stylesheet
  • Fix those caveats I listed previously

Undoubtedly, as with any first release, there will be some bugs to please forward those and any feedback you might have either via the comments on this blog or by email me at rrwhite AT gmail.com.

And finally, I need to give a shout out to Mark James for his Silk icons and stay tuned to this blog as I’ll be writing up more of the interesting implementation details and technical workarounds that I had to do to make this all work (If your into that sort of thing).

Happy generating.

Typo Migration and Brighthouse Theme

Posted by Richard White Mon, 20 Feb 2006 17:46:00 GMT

I spent the better half of yesterday migrating this blog to Typo and bringing the associated articles on the old one over. I was originally just using a Blogger account that was being SFTP’d to my web server. That was a decent solution for a blog when you want a quick and dirty way to get started. But it was time to move to something with a little more flexibility and credibility(?). It also forced me to learn how to configure lighttpd to handle multiple Rails applications on the same domain (what other apps are on this domain you say? you’ll see, you’ll see).

In the process of converting to Typo I needed to migrate my look and feel. In Typo it was easy enough to do this by creating a new theme, which is exactly what I did and I named her Brighthouse. You can download the theme from the resources link on your right. I’ll go ahead and include my short configuration instructions here so they’ll be easier to find (you can also find them in about.markdown which is what shows up when you import the theme into Typo.

Unfortunately I cannot find any good sites that keep a list of Type themes. I found this page on the Typo trac, but it looks like it hasn’t been updated lately. There is also TypoGarden that recently put on a theme contest. There are some themes on there but you have to search back through their post archives and there appears to be no concise list or way to upload new themes. Let me know if you know of any Typo theme sites.

Getting Started with Brighthouse

  1. Download the zip file and extract it into your themes directory in Typo
  2. Add your personal information to the about box in /themes/brighthouse/layouts/default.rhtml
  3. Add your headshot photo into /themes/brighthouse/images/headshot.gif_(of course you can change that url in the default.rhtml)

Optionally you can create static sidebar lists with specialized list icons using &lt;ul class="resources"&gt; and &lt;ul class="feeds"&gt;

On acts_as_paranoid continued

Posted by Richard White Mon, 20 Feb 2006 15:46:00 GMT

A few days ago I mentioned some of my struggles with the acts_as_paranoid Rails plugin. I also mentioned that I solved my problem with making an association to a paranoid model ignore the delete_at field. Unfortunately my fix was not properly tested and does not actually work. When I redefined the def task method I broke def task=.

My workaround to this was a simple, but very ugly, hack. I removed def task and replaced it with the following method.

  def task_wrapper
    if self.task.nil?
      self.task= Task.find_with_deleted(task_id) if task_id != 0
    end
    self.task
  end

Which is basically prefetching that association using the find_with_deleted method given to you by acts_as_paranoid. I would call this method any time before I need to call object.task. See, told you it was messy.

I would have spent more time coming up with a better solution but I soon realized what I was doing had a very funny odor to it. That’s right ye olde dreaded CodeSmell. I remodeled the whole association to better reflect what I was trying to accomplish and now do not use acts_as_paranoid at all (well at least in this case).

Just wanted to followup lest anyone actually think I got it right the first time.

On the need for more robust Rails Migrations

Posted by Richard White Sat, 18 Feb 2006 02:06:00 GMT

One of the most frustrating parts of Rails Migrations is the fact that each migration is NOT transactional. Its possible that some query halfway through the migration could fail and leave you database schema in a “bad state”. After reading this blurb on RailsBestPractices on the RoR wiki I thought that it might be possible to wrap my migrations in a simple transaction, with the only caveat that it won’t work for MySQL:
... Put a transaction around your migrations. That way, you know that you won’t be dumped out half-way through a migration, and have to disentangle whatever half-finished mess you end up with to try to run it again. The slight crimp to this is that MySQL doesn’t allow data definition commands (like CREATE TABLE, for instance) inside a transaction, and they cause an implicit commit. It’s still a good idea for data-only migrations, though.
Unfortunately the following does not work
class TestTransaction
self.up
<strong>    transaction do</strong>
    create_table :widgets, :force => true do |t|
      t.column :name, :string, :null => false
      t.column :version, :string, :null => false
    end
    remove_column :widgets, :type
  end
end

...
Because when I run rake migrate I get the following
rake aborted!
RuntimeError: ERROR   
C25P02  Mcurrent transaction is aborted, commands ignored until
end of transaction block      
Fpostgres.c     L926    Rexec_simple_query: CREATE TABLE cars
("id" serial primary key, "make" character varying(255) NO
T NULL, "model" character varying(255) NOT NULL)

If anyone has any ideas drop me a line rrwhite AT gmail.com

Update: Thanks to Eric Pugh for pointing out that removing :force => true makes this work!

On acts_as_paranoid

Posted by Richard White Mon, 13 Feb 2006 02:06:00 GMT

Installed the acts_as_paranoid plugin today for a top secret project I am working on. The documentation is quite lacking so I’ll fill in a few holes here. This may be obvious to everyone else out there but in order for it to work you need to add the following to the end of your environment.rb file:
require_gem 'acts_as_paranoid'
Doing this got rid of the 500 error I was encountering, but my objects were still being outright deleted, instead of having the deleted_at field set. A checked of the development.log showed that tasks.deleted_at IS NULL was being injected into SQL queries. So obviously the plugin was working on some level. My class looked like the following:
class Task
  acts_as_taggable
  <strong>acts_as_paranoid</strong>
For grins I tried swapping those two acts_* statements:
class Task
  <strong>acts_as_paranoid</strong>
  acts_as_taggable
And it started working correctly! Not sure the cause of this I’ll try and ping the developers for acts_as_paranoid. Also, while for the most part I wanted deleted items to not show up in any associations there were a few where I still wanted the deleted association to be retrieved. I solved this by adding the following:
class TimeEntry < ActiveRecord::Base
  belongs_to :task

  def task
    <strong>Task.find_with_deleted(task_id) if task_id != 0</strong>
  end
If you know of a more elegant way to do this, drop me a line and let me know.

On Rails Migrations Continued

Posted by Richard White Sat, 04 Feb 2006 02:06:00 GMT

Encountered another issue with migrations today when trying to do the following
add_column :appt_service_logins, :id, :primary_key
This threw an error when running rake migrate against my PostgreSQL DB that essentially boiled down to migrations trying to run the following SQL
ALTER TABLE appt_service_logins ADD id
Obviously missing the datatype for the column. I tracked this down to type_to_sql returning nil. This was because def native_database_types define a Hash where primary key does not map to another Hash with a :name key (see previous post). Therefore the following in def type_to_sql would return nil since it specifically looks for the value using the :name key.
def type_to_sql(type, limit = nil) #:nodoc:
  native = native_database_types[type]
  limit ||= native[:limit]
  column_type_sql = native[:name]
  column_type_sql << "(#{limit})" if limit
  column_type_sql  
end
To get around this I modified def type_to_sql to use the native value if column_type_sql was nil.
def type_to_sql(type, limit = nil) #:nodoc:
  native = native_database_types[type]
  limit ||= native[:limit] 
  column_type_sql = native[:name]
  column_type_sql << "(#{limit})" if limit      
  if column_type_sql
    column_type_sql
  else
    native
  end
end
I have no idea what the implications of such a change would be which is why I submitted this as a bug.

In the meantime since I was worried about the implications of changing def type_to_sql, I added a native database type of bigserial and am adding the column as a non-null bigserial and with a unique index.

Changes to postgre_extensions.rb:

def native_database_types
{
  :primary_key => "bigserial primary key",
  :string      => { :name => "character varying", :limit => 255 },
  :text        => { :name => "text" },
  :integer     => { :name => "integer" },
  :float       => { :name => "float" },
  :datetime    => { :name => "timestamp" },
  :timestamp   => { :name => "timestamp" },
  :time        => { :name => "time" },
  :date        => { :name => "date" },
  :binary      => { :name => "bytea" },
  :boolean     => { :name => "boolean" },
  :bigint      => { :name => "int8" },
  <strong>:bigserial   => { :name => "bigserial" }</strong>
}
end
In self.up in the migration file:
add_column :appt_service_logins, :id, :bigserial, :null => false
add_index :appt_service_logins, :id, :unique
To wrap up I attempted to put all of my changes into a Rails plugin so I wouldn’t have to put require ‘postgre_extensions’ at the top of each migration file as this fellow did but to no avail. I settled on just adding require ‘postgre_extensions’ to the bottom of my environment.rb. If you know of a better way to do this, let me know.

On Rails Migrations and PostgreSQL data types

Posted by Richard White Mon, 30 Jan 2006 02:06:00 GMT

ActiveRecord::Migrations for Ruby on Rails is nice and easy way to keep track of sql changes in a SQL server agnostic way. I won’t get much into the basics of migrations since others have already done a good job of that, but I will say that it has made a mid-project switch from MySQL to PostgreSQL as easy as it really should be.

The problem is of course that this database neutrality comes at a price and of course that price is in the form of a lack of precision when defining your schema. Since migrations supports [all sorts] of databases it also ends up only supporting the lowest common denominator of their abilites.

Case in point, on a current project I was trying to setup migrations from an existing schema. Unfortunately that schema included bigint(int8) id columns, and migrations only offers you :integer and :float in the way of numeric types. Fortunately getting around these problems is trivial in ruby, if you are willing to give up some of the database portability of migrations.

All that is required is to override the method in the PostgreSQL adapter that defines the mappings of data types in migrations to database data types and add a mapping for :bigint.
class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
 def native_database_types
 {
   <strong>:primary_key => "bigserial primary key",</strong>
   :string      => { :name => "character varying", :limit => 255 },
   :text        => { :name => "text" },
   :integer     => { :name => "integer" },
   :float       => { :name => "float" },
   :datetime    => { :name => "timestamp" },
   :timestamp   => { :name => "timestamp" },
   :time        => { :name => "time" },
   :date        => { :name => "date" },
   :binary      => { :name => "bytea" },
   :boolean     => { :name => "boolean" },
   <strong>:bigint      => { :name => "int8" }</strong>
 }
 end
end
Those of you that have looked at this method before will notice that I have also changed the :primary_key value from “serial primary key” to “bigserial primary key”. If you are familiar with PostgreSQL, or have just googled PostgreSQL datatypes like I did you will find that bigserial is the equivalent of a bigint with auto increment (or identity depending on where you come from).

Now all there is to do is to include the preceding code in each migration file. I simply created postgre_extensions.rb in the /lib directory of my rails app and added “require ‘postgre_extensions’” to the top of my migration files.

Older posts: 1 2 3