[PLUGIN RELEASE] ActsAsOverflowable

acts_as_overflowable

Written by Nicholas Lega

== DESCRIPTION:

Allows a column to overflow data into a secondary column if the data size exceeds the character limit. This is useful for fast indexing.

Instead of trying to index text blobs, you can specify a varchar column to be used for indexing. Any characters that do not fit into the base column will be automatically saved into or retrieved from an overflow field.

== SYNOPSIS:

Create overflow column migration:


class CreateOverflowModels < ActiveRecord::Migration
def self.up
create_table :overflows do |t|
t.integer :overflowable_id
t.string :overflowable_type
t.string :overflow
t.timestamps
end

add_column(:my_class_that_uses_overflows,
:has_overflow,
:boolean)

add_column(:my_class_that_uses_overflows,
:overflowable_text,
:string,
:limit => 255)
end

def self.down
drop_table :overflows

remove_column(:my_class_that_uses_overflows,
:has_overflow)

remove_column(:my_class_that_uses_overflows,
:overflowable_text)
end
end

Add to model:


class MyClassThatUsesOverflow < ActiveRecord::Base
acts_as_overflowable :overflow_column => :overflowable_text,
:overflow_limit => 255,
:overflow_indicator => :has_overflow,
:overflow_table => :overflows
end

To get and set the value of the “overflowable_text” field as described in the test model, use:


# #{my_field_name_here}_with_overflow is the method name
long_text = overflowable_obj.overflowable_text_with_overflow

overflowable_obj.overflowable_text_with_overflow = very_long_text

Simply using the getters and setters for “overflowable_text” without appending “_with_overflow” to the method name will return the fragment of the text contained in the base column.

== INSTALL:

sudo gem install revolutionhealth-acts_as_overflowable -s http://gems.github.com

== SOURCE:

http://github.com/revolutionhealth/acts_as_overflowable/tree/master

== FEATURES/PROBLEMS:

* Only tested on mysql and sqlite3

[PLUGIN RELEASE] ActsAsOverflowable

acts_as_overflowable

Written by Nicholas Lega

== DESCRIPTION:

Allows a column to overflow data into a secondary column if the data size exceeds the character limit. This is useful for fast indexing.

Instead of trying to index text blobs, you can specify a varchar column to be used for indexing. Any characters that do not fit into the base column will be automatically saved into or retrieved from an overflow field.

== SYNOPSIS:

Create overflow column migration:


class CreateOverflowModels < ActiveRecord::Migration
def self.up
create_table :overflows do |t|
t.integer :overflowable_id
t.string :overflowable_type
t.string :overflow
t.timestamps
end

add_column(:my_class_that_uses_overflows,
:has_overflow,
:boolean)

add_column(:my_class_that_uses_overflows,
:overflowable_text,
:string,
:limit => 255)
end

def self.down
drop_table :overflows

remove_column(:my_class_that_uses_overflows,
:has_overflow)

remove_column(:my_class_that_uses_overflows,
:overflowable_text)
end
end

Add to model:


class MyClassThatUsesOverflow < ActiveRecord::Base
acts_as_overflowable :overflow_column => :overflowable_text,
:overflow_limit => 255,
:overflow_indicator => :has_overflow,
:overflow_table => :overflows
end

To get and set the value of the “overflowable_text” field as described in

Continue reading “[PLUGIN RELEASE] ActsAsOverflowable”

[PLUGIN RELEASE] ActsAsSeoFriendly


ActsAsSeoFriendly

== DESCRIPTION:

Create an SEO friendly field for a model automatically based on a given field.

So if you have a Blogs model, and you would like create an SEO friendly version
of the ‘title’ field, you would just add this to your model and then be able to
use the SEO friendly id as the unique id to the resource. The plugin will only
append an integer to the SEO id if there is a collision.


== SYNOPSIS:

Create seo column migration:


class CreateSeoTestModels < ActiveRecord::Migration
def self.up
create_table :seo_test_models do |t|
t.string :name
t.timestamps
end
SeoTestModel.create_seo_friendly_column()
end

def self.down
SeoTestModel.drop_seo_friendly_column()
drop_table :seo_test_models
end
end

Add to model:


class SeoTestModel < ActiveRecord::Base
acts_as_seo_friendly :resource_id => :name,
:seo_friendly_id_field => :seo_id, # default is :seo_friendly_id
:seo_friendly_id_limit => 100 # default is 50
end

To lookup the resource in the controllers use:


SeoTestModel.find_by_seo_id(params[:id])

== INSTALL:

sudo gem install revolutionhealth-acts_as_seo_friendly -s http://gems.github.com

== SOURCE:

http://github.com/revolutionhealth/acts_as_seo_friendly/tree/master

To see the plugin in action see our recipes section, for example:

http://www.revolutionhealth.com/recipes/thai-miang-khem-style-salad

== FEATURES/PROBLEMS:

* Only tested on mysql and sqlite3

[PLUGIN RELEASE] ActsAsSeoFriendly


ActsAsSeoFriendly

== DESCRIPTION:

Create an SEO friendly field for a model automatically based on a given field.

So if you have a Blogs model, and you would like create an SEO friendly version
of the ‘title’ field, you would just add this to your model and then be able to
use the SEO friendly id as the unique id to the resource. The plugin will only
append an integer to the SEO id if there is a collision.


== SYNOPSIS:

Create seo column migration:


class CreateSeoTestModels < ActiveRecord::Migration
def self.up
create_table :seo_test_models do |t|
t.string :name
t.timestamps
end
SeoTestModel.create_seo_friendly_column()
end

def self.down
SeoTestModel.drop_seo_friendly_column()
drop_table :seo_test_models
end
end

Add to model:


class SeoTestModel < ActiveRecord::Base
acts_as_seo_friendly :resource_id => :name,
:seo_friendly_id_field => :seo_id, # default is :seo_friendly_id
:seo_friendly_id_limit => 100 # default is 50
end

To lookup the resource in the controllers use:


SeoTestModel.find_by_seo_id(params[:id])

== INSTALL:

sudo gem install

Continue reading “[PLUGIN RELEASE] ActsAsSeoFriendly”

ThreadsafeBenchmark

When testing products such as services which need to be stress-tested prior to release, it’s necessary to use multi-threading to get as close to real world usage as possible. This gem, though not intended to be a replacement for full-fledged testing suites such as LoadRunner, can provide instantaneous results to facilitate TDD programming. To reduce the duplication of code, the gem utilizes Ruby’s built-in Benchmark module for the base functionality while preventing the output from clobbering through the use of thread-specific IO buffers.

require 'threadsafe_benchmark'
include ThreadsafeBenchmark

threads = []
max_num = 5000

5.to_i.times { |i|
threads << Thread.new(max_num) { |n|
threadsafe_bm(6) { |x|
x.report("for:") { for i in 1..n; a = "1"; end }
x.report("times:") { n.times do ; a = "1"; end }
x.report("upto:") { 1.upto(n) do ; a = "1"; end }
}
}
}

threads.each { |t| t.join }

Using the standard Benchmark, the results would be printed haphazardly making it difficult to read and interpret. But ThreadsafeBenchmark cleans everything up giving us nicely laid out columns.

user system total real
for 0.000000 .000000 .000000 ( 0.002889)
times 0.000000 .000000 .000000 ( 0.002477)
upto 0.000000 .000000 .000000 ( 0.002479)
for 0.000000 .000000 .000000 ( 0.002401)
times 0.010000 .000000 .010000 ( 0.002586)
upto 0.000000 .000000 .000000 ( 0.002413)
for 0.000000 .000000 .000000 ( 0.002205)
times 0.010000 .000000 .010000 ( 0.002245)
upto 0.000000 .000000 .000000 ( 0.002272)
for 0.000000 .000000 .000000 ( 0.001822)
times 0.010000 .000000 .010000 ( 0.001958)
upto 0.000000 .000000 .000000 ( 0.001943)
for 0.010000 .000000 .010000 ( 0.010090)
times 0.010000 .000000 .010000 ( 0.009225)
upto 0.010000 .000000 .010000 ( 0.007986)

The gem and source files are available at Rubyforge.

ThreadsafeBenchmark

When testing products such as services which need to be stress-tested prior to release, it’s necessary to use multi-threading to get as close to real world usage as possible. This gem, though not intended to be a replacement for full-fledged testing suites such as LoadRunner, can provide instantaneous results to facilitate TDD programming. To reduce the duplication of code, the gem utilizes Ruby’s built-in Benchmark module for the base functionality while preventing the output from clobbering through the use of thread-specific IO buffers.

require 'threadsafe_benchmark'
include ThreadsafeBenchmark

threads = []
max_num = 5000

5.to_i.times { |i|
threads << Thread.new(max_num) { |n|
threadsafe_bm(6) { |x|
x.report("for:") { for i in 1..n; a = "1"; end }
x.report("times:") { n.times do ; a = "1"; end }
x.report("upto:") { 1.upto(n) do ; a = "1"; end }
}
}
}

threads.each
Continue reading "ThreadsafeBenchmark"

Enhanced Migrations v1.2.0

The original release of this highly useful plugin marked a turning point in collaborative Rails development by freeing the developer to commit their database migration without fear of having it ignored because of a higher placed migration number. This latest release includes some minor bug fixes plus a useful method for stepping through migrations one at a time without the need for copying and pasting long version numbers.

  1. Fixed bug where an empty migrations_info table would create a non-parseable schema.rb.
  2. Made plugin database independent.
  3. Added capability to step through migrations using VERSION=[previous, next, first and last].
  4. dump_schema_information now returns all migrations, not just latest (credit to François Beausolei).
  5. Added tests which use SQLite and a task (enhanced_migrations:clean_sqlite_db) to help with testing.

As an example of item number three, consider the following situation. Using the enhanced migrations plugin, you’ve just created a migration that adds a new table to your database. Upon running it, you discover that you used the wrong data type for a column. Rather than having to copy and paste the previous migration’s version number, simply using ‘previous’ in the VERSION number will now suffice.


rake db:migrate VERSION=previous

This also works using prev along with next, first and last for their respective operations. Keep in mind that first will go to the first migration and is not the same as VERSION=0.

Migrate as usual


shell> rake db:migrate
== CreateRecipesTable: migrating ==============================================
-- create_table(:recipes)
-> 0.0902s
== CreateRecipesTable: migrated (0.0904s) =====================================

== AddRecipesForUser1: migrating ==============================================
-- execute("INSERT INTO recipes (name, owner) VALUES ('Lemon Meringue Pie', 'user1')")
-> 0.3684s
== AddRecipesForUser1: migrated (0.5302s) =====================================

== AddRecipesForUser2: migrating ==============================================
-- execute("INSERT INTO recipes (name, owner) VALUES ('Steak and Kidney Pie', 'user2')")
-> 0.2574s
== AddRecipesForUser2: migrated (0.3962s) =====================================

Migrate to the previous version


shell> rake db:migrate VERSION=previous
== AddRecipesForUser2: reverting ==============================================
-- execute("DELETE FROM recipes WHERE owner = 'user2'")
-> 0.4512s
== AddRecipesForUser2: reverted (0.4516s) =====================================

Migrate to the first version


shell> rake db:migrate VERSION=first
== AddRecipesForUser1: reverting ==============================================
-- execute("DELETE FROM recipes WHERE owner = 'user1'")
-> 0.1676s
== AddRecipesForUser1: reverted (0.1678s) =====================================

Migrate another previous version, essentially the same as VERSION=0 since we are already at the first migration.


shell> rake db:migrate VERSION=previous
== CreateRecipesTable: reverting ==============================================
-- drop_table(:recipes)
-> 0.1680s
== CreateRecipesTable: reverted (0.1683s) =====================================

How to get
Download the gem from Rubyforge and install it like so:

sudo gem install enhanced_migrations-1.2.1.gem

Update – 11/15/2007
The previous version had a small bug that was discovered during use here that sometimes caused an app’s database yaml file to be overwritten by the gem’s database config which is used only for testing. The chances of this happening outside of our particular setup are small but we felt it warranted an immediate fix. We’ve also added a gzip’d source file for the plugin fans out there.

Links in the post have been updated to reflect the location of this new release.

Enhanced Migrations v1.2.0

The original release of this highly useful plugin marked a turning point in collaborative Rails development by freeing the developer to commit their database migration without fear of having it ignored because of a higher placed migration number. This latest release includes some minor bug fixes plus a useful method for stepping through migrations one at a time without the need for copying and pasting long version numbers.

  1. Fixed bug where an empty migrations_info table would create a non-parseable schema.rb.
  2. Made plugin database independent.
  3. Added capability to step through migrations using VERSION=[previous, next, first and last].
  4. dump_schema_information now returns all migrations, not just latest (credit to François Beausolei).
  5. Added tests which use SQLite and a task (enhanced_migrations:clean_sqlite_db) to help with testing.

As an example of item number three, consider the following situation. Using the enhanced migrations plugin, you’ve just created a migration that adds a new table to

Continue reading “Enhanced Migrations v1.2.0”

Weblog update

Hey all I wanted to give you an update:

Three of our active contributors are moving on: Aaron, Eddie, and Val. They plan to stay active in the Rails community, as well as new areas like Facebook. You can follow their work at blog.hungrymachine.com. It has been a great pleasure to work with them and we wish them the best.

Weblog update

Hey all I wanted to give you an update:

Three of our active contributors are moving on: Aaron, Eddie, and Val. They plan to stay active in the Rails community, as well as new areas like Facebook. You can follow their work at blog.hungrymachine.com. It has been a great pleasure to work with them and we wish them the best.

FragmentFu – Fun with Fragments

First, please read the Advanced Rails Caching.. On the Edge for a primer on ESI, caching, and fragments.
To get started developing with ESI, I’m going to walk through a simple tutorial building an ESI enabled Rails application.
Get Mongrel-ESI
Find the latest mongrel ESI here: http://code.google.com/p/mongrel-esi/downloads/list


wget http://mongrel-esi.googlecode.com/files/mongrel-esi-0.0.5.gem

(Does anyone have a better solution of ruby projects hosted on Google Code? )

Install Mongrel ESI


sudo gem install mongrel-esi-0.0.5.gem

Create a new rails application (Currently using rails 1.2.3)


rails fragment_fu_demo

create
create app/controllers
create app/helpers
create app/models
.....

cd fragment_fu_demo


Create a home page for your application

Delete the public/index.html page


rm public/index.html

Edit the config/routes.rb and uncomment the following line:


map.connect '', :controller => "welcome"

Create a welcome controller


./script/generate controller welcome index

Install the FragmentFu plugin

   
./script/plugin install http://mongrel-esi.googlecode.com/svn/trunk/plugin/fragment_fu

Start your application

 
./script/server

visit http://localhost:3000 in your browser. You should see a “Welcome#index” page.

Start MongrelEsi
From the command line, run:


mongrel_esi start
** Starting Mongrel listening at 0.0.0.0:2000

visit http://localhost:2000 in your browser. You should see a “Welcome#index” page.

Create a fragment to cache
Edit the app/controllers/welcome_controller.rb
Add an action called now

 
def now
render :text => "#{Time.now} is #{Time.now.usec} nano-seconds"
end

Edit the app/views/welcome/index.html.erb and replace with the following:


<h1>Welcome</h1>
<p><%= render :esi => "/welcome/now" %></p>

Page without ESI parsing
visit http://localhost:3000 and you should see: “Time:” in the browser. If you view the source, you’ll see


<h1>Welcome</h1>
<p><esi:include src="/welcome/now" max-age="0"/></p>

This is the simplest of esi tags, and the default max-age is 0, which means do not cache.

Page with ESI Parsing

visit http://localhost:2000 and you’ll see:

Welcome
Date: Mon Aug 06 00:00:00 -0400 2007 which is 62972ns

Caching modules
Edit the index.html.erb and replace with the following:


<h1>Welcome</h1>
<p><%= render :esi => "/welcome/now" %></p>
<h2>What time is it again?</h2>
<p><%= render :esi => "/welcome/now"%></p>

If you refresh http://localhost:2000, you’ll notice they nanoseconds are different

Lets add a ttl to first call


<h1>Welcome</h1>
<p><%= render :esi => "/welcome/now", :ttl => 45.seconds %></p>
<h2>What time is it again?</h2>
<p><%= render :esi => "/welcome/now"%></p>

If you refresh http://localhost:2000, you’ll notice they are now the same. Refresh again in the next 45 seconds, and it will not change.

The next tutorial will cover a small TODO application, with inline invalidation and exception handling. Coming Soon!

FragmentFu – Fun with Fragments

First, please read the Advanced Rails Caching.. On the Edge for a primer on ESI, caching, and fragments.
To get started developing with ESI, I’m going to walk through a simple tutorial building an ESI enabled Rails application.
Get Mongrel-ESI
Find the latest mongrel ESI here: http://code.google.com/p/mongrel-esi/downloads/list


wget http://mongrel-esi.googlecode.com/files/mongrel-esi-0.0.5.gem

(Does anyone have a better solution of ruby projects hosted on Google Code? )

Install Mongrel ESI


sudo gem install mongrel-esi-0.0.5.gem

Create a new rails application (Currently using rails 1.2.3)


rails fragment_fu_demo

create
create app/controllers
create app/helpers
create app/models
.....

cd fragment_fu_demo


Create a home page for your application

Delete the public/index.html page


rm public/index.html

Edit the config/routes.rb and uncomment the following line:


map.connect '', :controller => "welcome"

Create a welcome controller


./script/generate controller welcome index

Install the FragmentFu plugin

   
./script/plugin install http://mongrel-esi.googlecode.com/svn/trunk/plugin/fragment_fu

Start

Continue reading “FragmentFu – Fun with Fragments”

Advanced Rails Caching.. on the Edge

When trying to scale a portal to millions of users with complex personalization, we had to rethink the application design. We needed partial page cacheability, and we wanted that close to the Edge… and recently the idea has been catching on. First with Components Are the New Black, and the idea of Nginx, SSI, and Memcache, what I’d like to briefly describe below is a partial page caching strategy that has worked well for the past year.

First, we use a technology called ESI. ESI stands for Edge Side Includes, which is a simple markup language for describing dynamic assembly of applications. At the core, its similar to SSI, but its a more versatile spec that has been accepted/implemented by a half dozen cache servers, both open source (Squid, Mongrel-ESI) and commercial (Oracle Web Cache, Akamai, among others). It also includes an invalidation protocol, exception handling on the edge, and a few other features.

The ESI W3C specification has been out for 6+ years, so these ideas are not new, but ESI is a diamond in the rough. No one seems to be using it.

A simple example of ESI in your rails application is including a common header. If the header is static, using a shared layout, or rendering a shared partial across applications, could be sufficient. But if you use the common idiom of a sign-in bar, like “Welcome Bob – Signout”, on a page you want to fully cache, or a common header you want to share across multiple applications, then you may consider another approach.

If you leverage ESI, you can put the below in your application layout.

<div id="header">
<esi:include src="/header" max-age="300"/>
</div>

The <esi:include> tag will inject the response from /header into the page and cache that fragment for 300 seconds. You can also add a variety of options, request header parameters (which can be used to personalize the request) along with a dozen other optional parameters, some of which I will outline below. This is simple SSI (Server Side Includes) .

But if we take it a step further, lets look at how we can apply it to a site like Twitter.

Twitter

If you look at the image above, the majority of the page can be fully cached. I’ve outlined two green boxes, which appear to be dynamic content, which can have different TTLs. Using ESI, your markup could be:

<div id="latest">
<esi:include src="/latest" max-age="5"/>
</div>
...
<esi:include src="/featured" max-age="3600"/>

The ESI enabled cache server would parse this markup, make 2 separate HTTP requests (/latest, and /featured) and cache those with their corresponding TTLs. You can furthermore cache the wrapping template with a Surrogate-Control header, which tells the cache server to keep a cached copy of the template. A request to this page 4 seconds later would make 0 requests to your rails infrastructure. 8 seconds later would only hit the /latest, returning the rest of the page from cache. You can also envision a application pool of servers just to handle /latest, but I’ll get into sharding applications via ESI in a later article.

Exception Handling

If you take this a step further, you can also define timeouts and exception behavior. As defined below, If the /latest request takes more than 1s, The cache server will give up, and retrieve the static snippet defined in the except block from your static web host.

<esi:try>
<esi:attempt>
<esi:include src="/latest" max-age="5" timeout="1"/>
</esi:attempt>
<esi:except>
<esi:include src="http://static.foo.com/latest" max-age="5" timeout="1"/>
</esi:except>
</esi:try>

We call these “Sorry” modules, because they often say “Sorry, this feature is having trouble”, but the rest of the page may surface meaningful content. Even better, write out more meaningful content to disk once a day and serve that from Apache.


Invalidation

The other benefit of ESI is Invalidation support. There are various mechanisms to invalidate content, but my favorite is Inline Invalidation. Consider the common rails idiom of updating data. You post to a controller which redirects to a view of that data. Since the HTTP redirect (301) bubbles all the way back to the browser, any content in the body of the redirect can be parsed by the ESI server. Therefore you can put the invalidation xml is the redirect response body and not conditionally dirty your view logic.

<esi:invalidate>
<?xml version="1.0"?>
<!DOCTYPE INVALIDATION SYSTEM "internal:///WCSinvalidation.dtd">
<INVALIDATION VERSION="WCS-1.1">
<OBJECT>
<BASICSELECTOR URI="/foo/bar/baz"/>
<ACTION REMOVALTTL="0"/>
</OBJECT>
</INVALIDATION>
</esi:invalidate>

The above is a simple single URL example, but the specification supports regex, which would work well for restful resources (e.g. /people/#{person.id}/*), among other things.


FragmentFu

FragmentFu is a plugin that enables ESI support in rails applications. At this point it is an extraction from our internal plugin for modules, caching, and ESI. Its in active extraction/development, but I wanted to involve the community in gathering ideas, suggestions and comments.

Some sample snippets:

<% render :esi => widget_url(1) %>

<%= render :esi => latest_url, :ttl => 5.seconds, :timeout => 1.second %>

<%= render :esi => featured_url, :ttl => 10.hours, :except => '/some/static/path' %>

We’ve also toyed around with the idea of fragment respond_to’s. Since ESI supports adding request header parameters, we can mimic Prototype’s
X-Requested-With behavior to implement:

def latest
...
respond_to |wants| do
wants.html { do something }
wants.fragment { do something }
end
end


How to get started

Mongrel-ESI is a great starting place. It was built in-house and released a few months ago on Google Code. It supports a large subset of ESI, ESI Invalidation, and its a great tool for developing applications that utilize ESI. You can grab Mongrel-ESI, download the Oracle Web Cache Standalone or even Squid 3.0… and when you’re site gets really big, its time to give Akamai a call. 🙂

Advanced Rails Caching.. on the Edge

When trying to scale a portal to millions of users with complex personalization, we had to rethink the application design. We needed partial page cacheability, and we wanted that close to the Edge… and recently the idea has been catching on. First with Components Are the New Black, and the idea of Nginx, SSI, and Memcache, what I’d like to briefly describe below is a partial page caching strategy that has worked well for the past year.

First, we use a technology called ESI. ESI stands for Edge Side Includes, which is a simple markup language for describing dynamic assembly of applications. At the core, its similar to SSI, but its a more versatile spec that has been accepted/implemented by a half dozen cache servers, both open source (Squid, Mongrel-ESI) and commercial (Oracle Web Cache, Akamai, among others). It also includes

Twitter

Continue reading “Advanced Rails Caching.. on the Edge”

Code Digest #2

When you program for a living, you write lots of code. There is often some code that you are fond of. We started the Code Digest series to present such code written by the RHG developers. We encourage other teams and individual developers to share similar snippets in their blogs so we all can learn from each other and become better rails developers.

Mai Nguyen

Simple AJAX error messaging

When simple javascript validation is not enough and your product managers insist on ajax for error messaging, guess what. You have to implement ajax error messaging. One such case is ‘username availability’. This simple example displays an error message on blur of a the username field. It doesn’t hit the server unless the value of the field is well-formed (at least that will save you some network traffic …)

Controller would look something like:

class FooController < ::ApplicationController

def is_username_taken

unless
Continue reading "Code Digest #2"

Code Digest #2

When you program for a living, you write lots of code. There is often some code that you are fond of. We started the Code Digest series to present such code written by the RHG developers. We encourage other teams and individual developers to share similar snippets in their blogs so we all can learn from each other and become better rails developers.

Mai Nguyen

Simple AJAX error messaging

When simple javascript validation is not enough and your product managers insist on ajax for error messaging, guess what. You have to implement ajax error messaging. One such case is ‘username availability’. This simple example displays an error message on blur of a the username field. It doesn’t hit the server unless the value of the field is well-formed (at least that will save you *some* network traffic …)

Controller would look something like:

class FooController < ::ApplicationController

def is_username_taken

unless Person.find_by_username(params[:username])
render :nothing => true
return
end

message_html_id = params[:message_html_id]
render :update do |page|
page.replace_html message_html_id ,"Username is not available, please choose another."
# if you want error styling associated with the failed state
page << "document.getElementById('" + message_html_id + "').className = 'failed'"
end

end

end

View would look something like:

<dl>
<dt><label>Username: </label></dt>
<dd id="username_input">
<%= text_field :person, :username, :class => "input", :type => 'text', :id => 'person_username_input' %>
</dd>
<dd id="username_messaging"><%= @person.errors.on(:username)%></dd>
</dl>

Your javascript would look something like:

var UserNameUnique = Class.create();
UserNameUnique.prototype = {
initialize: function( field_id, message_id) {
this.message_id = message_id;
this.field = document.getElementById( field_id );
if (typeof this.field == 'undefined') {return;}
// Observe blur on field
Event.observe(this.field, 'blur', this.checkName.bindAsEventListener(this));
},
checkName: function() {
if( typeof this.field != 'undefined' )
{
var name = this.field.value;
// don't hit server unless username is well-formed
var re = /^[A-Za-z])[a-zA-Z0-9]{2,25}$/;
if( name.match( re ))
new Ajax.Request('/foo/is_username_taken', {asynchronous:true, parameters:'username='+name+'&message_html_id='+this.message_id});
}
}
};

new UserNameUnique('person_username_input', 'username_messaging');

You could also use Rails helper observe_field instead of the writing your own javascript, but there is more flexibility in writing your own javascript (such as the need to check well-formed values before hitting the server).

Mark Brooks

Creating the options list for select tags can be annoying, especially if there is a requirement that the current “option” be displayed as the default option. With javascript, it isn’t such a big deal, but one must take care of the degraded case as well.

In any event, the base object is an array of two-item hashes representing both the name of a video channel and the number of videos in that channel. By way of example:

@channelinfo = [
{"name"=>"Fitness","count"=>4},
{"name"=>"Diabetes", "count"=>1},
{"name"=>"Pregnancy", "count"=>11}
]

The option values are the name fields of each hash. The key is, we want the current option value to be the first one in the option list, and the rest to be in alpha order by name.

So let’s create an option builder from the bottom up.

First, we only need the channel names for the select tag. This gives us what we need:

options = @channelinfo.collect { |channel| channel['name'] }

That gives us a list of channel names. Using the list above, it would be [“Fitness”, “Diabetes”, “Pregnancy”].

However, we need to make sure that the current channel is first in the list. Let’s say that current channel is Diabetes. Since we already have that value in @current_channel, we can exclude it from our options list:

options = @channelinfo.collect do |channel|
channel['name']
end.reject do |channelname|
channelname == @current_channel
end

Now the resulting list will look like [“Fitness”, “Pregnancy”]. However, we still need the current channel to be at the front of the lift, so we add it back as the first element:

options = @channelinfo.collect do |channel|
channel['name']
end.reject do |channelname|
channelname == @current_channel
end.unshift(@current_channel)

Now our options list looks like [“Diabetes”, “Fitness”, “Pregnancy”].

Two points. First, we want to make sure that, while the current channel is at the head of the list, the rest of the list items are in alpha order, so we add a sort directive in the appropriate place. Also, it is probably a good idea to exclude any nils that might pop up in the collection on the original data object, since it comes from a service and it is possible, however unlikely, that a hash might get spit out without a ‘name’ property. The more rigorous code looks like this now:

options = @channelinfo.collect do |channel|
channel['name']
end.compact.reject do | channelname |
channelname == @current_channel
end.sort.unshift(@current_channel)

Now we can generate our options list using:

options.collect do |channelname|
"<option>" + channelname + "</option>"
end.join(",")

although if you want to, you can simply combine the whole thing as follows:

@channelinfo.collect do |channel|
channel['name']
end.compact.reject do |channelname|
channelname == @current_channel
end.sort.unshift(@current_channel).collect do |channelname|
"<option>" + channelname + "</option>"
end.join(",")

to get the same result, and eliminate the unnecessary options binding.

Todd Fisher

Here is an extension trying to execute a block multiple times before giving up. Handy for network operations and such.

module Kernel
def could_fail(retries = 3, &block)
tries = 0
begin
yield
rescue Exception
tries += 1
if tries < retries
retry
else
raise
end
end
end
end

Call it as result = could_fail { some_operation_that_might_fail_first_time }

Todd Fisher

When you are writing a script that needs to auto install gems, you are likely to run into a problem that it stops because there are multiple platform versions available (jruby, win32, etc) and the gem command expects you to pick one that matches your platform. This patch forces to use a specific platform so no user interaction is needed. Original idea from Warren updated to support rubygems 0.9.4.

module GemTasks
def setup

return if $gems_initialized
$gems_initialized = true
Gem.manage_gems

# see => http://svn.bountysource.com/fishplate/scripts/debian_install.pl
Gem::RemoteInstaller.class_eval do

alias_method :find_gem_to_install_without_ruby_only_platform, :find_gem_to_install

def find_gem_to_install( gem_name, version_requirement, caches = nil )
if caches # old version of rubygems used to pass a caches object
caches.each {|k,v| caches[k].each { |name,spect| caches[k].remove_spec(name) unless spec.platform == 'ruby' } }
find_gem_to_install_without_ruby_only_platform( gem_name, version_requirement, caches )
else
Gem::StreamUI.class_eval do

alias_method :choose_from_list_without_choosing_ruby_only, :choose_from_list
def choose_from_list( question, list )
result = nil
result_index = -1
list.each_with_index do |item,index|
if item.match(/\(ruby\)/)
result_index = index
result = item
break
end
end
return [result, result_index]
end

end

find_gem_to_install_without_ruby_only_platform( gem_name, version_requirement )

end
end

end

end
end

Val Aleksenko

Since class-level instance variable are not inherited by subclasses, you need to go some extra steps when writing a plugin using them. Depending on the amount of such variables, I have been either defining a method instead of class-level instance variables or forwarding them to subclasses.

Example #1. acts_as_readonlyable needs to provide a single class level instance variable. Defining a method instead.

def acts_as_readonlyable(*readonly_dbs)
define_readonly_model_method(readonly_models)
end

def define_readonly_model_method(readonly_models)
(class << self; self; end).class_eval do
define_method(:readonly_model) { readonly_models[rand(readonly_models.size)] }
end
end

Example #2. acts_as_secure uses a bunch of variables. Forwarding them to subclasses.

def acts_as_secure(options = {})
@secure_except = unsecure_columns(options.delete(:except))
@secure_storage_type = options.delete(:storage_type) || :binary
@secure_class_crypto_provider = options.delete(:crypto_provider)
@secure_crypto_providers = {}
extend(ActsAsSecureClassMethods)
end

module ActsAsSecureClassMethods
def inherited(sub)
[:secure_except, :secure_storage_type, :secure_class_crypto_provider, :secure_crypto_providers].each do |p|
sub.instance_variable_set("@#{ p }", instance_variable_get("@#{ p }"))
end
super
end
end

[PLUGIN RELEASE] Metrics

From Jeffrey Damick

Introduction

This gem provides a metrics collecting for controllers, database queries, and specific blocks of code or methods. It is designed to be light-weight and have minimal impact on production builds while providing performance indicators of the running application.

Disclaimer

This software is released to be used at your own risk. For feedback please drop us a line at rails-trunk [ at ] revolution DOT com.
Using this plugin should not be your first step in application optimization/scaling or even the second one.

Example

class SomeClassToTest
collect_metrics_on :my_method

def my_method(blah = nil)
true
end
end

Output:

[ERROR] [2007-06-21 23:21:19] [trunk] [Metrics]|[76716]|[MysqlAdapter.log]|0.012727|args=["root localhost trunk_test", "CREATE DATABASE `trunk_test`"]
[ERROR] [2007-06-21 23:19:56] [trunk] [Metrics]|[35158]|[Request to [Test::SomeControllerWithMetricsId]]|0.001373|action = index|path =some?
[ERROR] [2007-06-21 23:19:56] [trunk] [Metrics]|[33676]|[SomeClassToUseModuleMixin.another_method]|0.000020|args=["also"]

for more samples and test cases see test/metrics_test.rb

Usage

The metrics are written to: logs/_metrics.log

Configuration can be updated in metrics/config/metrics.yml, you may copy this file to your RAILS_ROOT/config/metrics.yml and customize for your application, the RAILS_ROOT will be checked first.

Sample metrics.yml

production:
min_real_time_threshold: 1.0
single_line_output: true
some_module/test_controller: 0.0

Installation

As plugin:
script/plugin install svn://rubyforge.org/var/svn/metrics/trunk/vendor/plugins/metrics

License

metrics is released under the MIT license.

Support

The plugin RubyForge page is http://rubyforge.org/projects/metrics

[PLUGIN RELEASE] Metrics

From Jeffrey Damick

Introduction

This gem provides a metrics collecting for controllers, database queries, and specific blocks of code or methods. It is designed to be light-weight and have minimal impact on production builds while providing performance indicators of the running application.

Disclaimer

This software is released to be used at your own risk. For feedback please drop us a line at rails-trunk [ at ] revolution DOT com.
Using this plugin should not be your first step in application optimization/scaling or even the second one.

Example

class SomeClassToTest
collect_metrics_on :my_method

def my_method(blah = nil)
true
end
end

Output:

[ERROR] [2007-06-21 23:21:19] [trunk] [Metrics]|[76716]|[MysqlAdapter.log]|0.012727|args=["root localhost trunk_test", "CREATE DATABASE `trunk_test`"]
[ERROR] [2007-06-21
Continue reading "[PLUGIN RELEASE] Metrics"

Code Digest #1

When you program for a living, you write lots of code. There is often some code that you are fond of. We start the Code Digest series to present such code written by the RHG developers. We encourage other teams and individual developers to share similar snippets in their blogs so we all can learn from each other and become better rails developers.

Daniel Silva

<%= link_to(image_tag("/images/icon.gif", :style=>"padding-right:0px;"), {:controller => '/registration', :action => 'login', :dest => request.request_uri}, {:style => ""}) %> 

With this line, I can create an image link using Rails code, to take advantage of the power of routes in Rails while still creating this kind of structure: <a href=”#”><img border=”0″/></a>

Jack Dempsey

Ok, so here’s something useful for using keyboard short cuts to navigate through a small supporting application:

def include_keyboard_shortcuts

hotkey = 'Ctrl+Shift'
code_string = ''
keys = {
'm'=>'/account_info/show',
's'=>'/account_info/index',
'c'=>'/menu/go?menu_action=new_customer',
'p'=>'/products',
'o'=>'/account_info/orders',
't'=>'/order_tracker',
'x'=>'/auth_sessions/destroy',
'r'=>'/current_users/reset_password'
}

current_user_needed = %w{m o r}

keys.each_pair do |k,v|
next if current_user_needed.include?(k) && current_user.nil?
code_string << "shortcut('#{hotkey}+hm#{k}',function() { document.location.href='#{v}' });\n"
end

javascript_tag(code_string)

end

Then in a layout file just:

<%= javascript_include_tag 'shortcuts' %>
<%= include_keyboard_shortcuts %>

Makes use of a nice shortcuts.js lib.

Val Aleksenko

This is a fragment of a conversation on our internal IRC with my solution for refactoring a piece of code:

May 07 14:07:56 <Anthro> There has to be a better way to do this (x and y are non-negative integers): increment = (x!=0) ? ( (y!=0) ? 0 : 1 ) : -1
May 07 14:08:22 <Anthro> I think it can be done with math instead of logic.
May 07 14:16:56 <jack> Anthro: have you thought about using sin and cos? 😉
May 07 14:18:42 <muzzy> (x <=> 0) <=> (y <=> 0)

Granted, it is not as clear as the original, but it is hard to resist it from the aesthetic point of view.

Val Aleksenko

I love writing dynamically generated methods and classes in Ruby as the next guy. When I was writing acts_as_readonlyable, I needed to manage multiple connections. I found that the easiest solution for managing active connections was generation of an AR class for each read-only definition and borrowing the connection from it. The usage is acts_as_readonlyable :read_only_entry_in_database_config.

def acts_as_readonlyable(readonly_db)
define_readonly_class(readonly_db) unless ActiveRecord.const_defined?(readonly_class_name(readonly_db))
end

def readonly_class_name(db)
"Generated#{ db.camelize }"
end

def define_readonly_class(db)
ActiveRecord.module_eval %Q!
class #{ readonly_class_name(db) } < Base
self.abstract_class = true
establish_connection configurations[RAILS_ENV]['#{ db }']
end
!

end

Warren Konkel

The ConfigFile class is a quick and easy way to load YAML files located in your Rails application’s config directory. Simply place a YAML file into your config directory and then access it like a hash. For example this will read your config/database.yml: ConfigFile[‘database’][‘production’][‘adapter’].

class ConfigFile
def self.[](arg)
@@cached_configs ||= {}
@@cached_config_mtimes ||= {}

base_name = "config/#{arg}.yml"
filename = File.join(RAILS_ROOT, base_name)
raise "ERROR: Config not found: #{base_name}" unless File.exists?(base_name)

if @@cached_configs[arg].nil? || @@cached_config_mtimes[arg] < File.stat(filename).mtime.to_i
@@cached_configs[arg] = YAML.load(File.open(filename))
@@cached_config_mtimes[arg] = File.stat(filename).mtime.to_i
end

@@cached_configs[arg]
end
end

Code Digest #1

When you program for a living, you write lots of code. There is often some code that you are fond of. We start the Code Digest series to present such code written by the RHG developers. We encourage other teams and individual developers to share similar snippets in their blogs so we all can learn from each other and become better rails developers.

Daniel Silva

<%= link_to(image_tag("/images/icon.gif", :style=>"padding-right:0px;"), {:controller => '/registration', :action => 'login', :dest => request.request_uri}, {:style => ""}) %> 

With this line, I can create an image link using Rails code, to take advantage of the power of routes in Rails while still creating this kind of structure: <a href=”#”><img border=”0″/></a>

Jack Dempsey

Ok, so here’s something useful for using keyboard short

Continue reading “Code Digest #1”