Compassionate Communications. A different kind of Rails application. 3
Advanced Rails Studio: Custom Form Builder 8
Custom Form Builder
Use a custom form builder to clean up your html.erb files.
class LabelFormBuilder < ActionView::Helpers::FormBuilder
helpers = field_helpers +
%w{date_select datetime_select time_select} +
%w{collection_select select country_select time_zone_select} -
%w{hidden_field label fields_for} # Don't decorate these
helpers.each do |name|
define_method(name) do |field, *args|
options = args.last.is_a?(Hash) ? args.pop : {}
label = label(field, options[:label], :class => options[:label_clas])
@template.content_tag(:p, label +'<br/>' + super) #wrap with a paragraph
end
end
endThen you can remove all the <p> and label tags from you form.
<h1>Editing user</h1>
<% form_for(@user, :builder => LabelFormBuilder) do |f| %>
<%= f.error_messages %>
<%= f.text_field :name %>
<%= f.text_field :address %>
<%= f.text_area :comment %>
<%= f.check_box :check %>
<%= f.submit "Update" %>
<% end %>
<%= link_to 'Show', @user %> |
<%= link_to 'Back', users_path %>Add this to your application initializer to have all form use this form builder
ActionView::Base.default_form_builder = LabelFormBuilder<% form_for(@user, :builder => LabelFormBuilder) do |f| %><% form_for(@user) do |f| %>Now the same form with no custom builder was looking like this before.
<h1>Editing user</h1>
<% form_for(@user) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.label :address %><br />
<%= f.text_field :address %>
</p>
<p>
<%= f.label :comment %><br />
<%= f.text_area :comment %>
</p>
<p>
<%= f.label :check %><br />
<%= f.check_box :check %>
</p>
<p>
<%= f.submit "Update" %>
</p>
<% end %>
<%= link_to 'Show', @user %> |
<%= link_to 'Back', users_path %>Advanced Rails Studio: Meta Programming 3
Chad is giving a very nice presentation walking us through meta programming step by step. You can see the code examples we are creating during his talk, but just looking at the code will note give the whole picture.
# ruby it self uses meta programming
class Person
attr_accessor :name
end
chad = Person.new
chad.name = 'chad'
# classes are open
# create new class
class Blah
def greeting
puts "hello"
end
end
# reopen class and return id
class Blah
def do_something!
greeting
end
end
b = Blah.new
b.greeting
b.do_something!
# reopen existing class
class String
def encrypt
tr "a-z", "b-za"
end
end
puts "cat".encrypt
# Conceptually ruby (the virtual machine) creates a structure to represent the class
# And this structure can dynamically be defined and changed at runtime.
{
:String => {:name => "String",
:methods => {
:ecryypt => '<method body>',
:tr => '<method body>',
:update => '<method body>',
},
:instance_variables => {
"@name" => "Chad"
}
}
}
# replace existing method
class String
def encrypt
upcase.reverse
end
end
puts "cat".encrypt
# Rails extends base classes in activesupport. I.e Fixnum 20.minutes.ago
class Fixnum
def minutes
self*60
end
end
puts 20.minutes
class Fixnum
def from_now
Time.now + self
end
def ago
Time.now - self
end
end
puts 20.minutes.from_now
puts 20.minutes.ago
# Class definition are executed line by line
class Chad
#exit #uncomment this and the program will halt here!
puts "Hello, defining #{self}"
end
puts Chad.new.inspect
# Can conditionaly create class
class Chad
#exit #uncomment this and the program will halt here!
puts "Hello, defining #{self}"
puts "Type OK when prompted"
response = gets.chomp
if response == "OK"
def greeting
puts "OK"
end
else
def greeting
puts "O NO!!!"
end
end
end
puts Chad.new.greeting
# Could use this to have different code for RAILS_ENV is "Prodution" or "Development"# Sending messages to Object. Object receive message, all method calls have received
"Chad".upcase #,I.e. String "Chad" gets message upcase
puts "Hello" # event 'puts' is a message
puts self.class.name # Even when running script, running in context of an Object
class Person
def initialize(name)
@name = name
greeting
puts "self.inspect: #{self.inspect}"
end
def greeting
puts "0, hello #{@name}."
end
end
puts Person.new('daniel').greeting
# Calling class methods
class Person
puts "Puts is send to self. Self is person:#{self} when defining class."
end
# Can point to a class
person_class = Person
puts person_class.class
class Man
end
class AstroMan
end
# Factory method, classes are just object that can be passed around
def man_or_astroman
klass = (rand(2) > 0 ? Man : AstroMan)
klass.new
end
puts man_or_astroman
puts man_or_astroman
puts man_or_astroman
puts man_or_astroman
# Constance in Ruby are not Constants
if false #Don't run this
String = "HAHAHA" #You can even change the class constants implementation
# You'll get a warning (Warning: already initialized constant String), but you can change it
Integer = "bla"
Array = 123
end
# Methods can be defined on Objects and not just Classes
animal = "Cat"
def animal.speak
"woof"
end
puts "animal.speak: #{animal.speak}"
# "dog".speak doesn't exists, only the specific animal instance has speak
# It's not often done in Ruby, but in another context you'll do it all the time...
# .. Adding class methods (singleton methods (not related to pattern)).
class Human
def self.announce_self
puts "I AM #{self}, and I AM BEING DEFINED"
end
announce_self # Can invoked defined class method while defining class
end
# Same as doing def Human.annouce_sef end
Human.announce_self
# We are getting closer to has_many
class Superman < Human
announce_self
end
# Let's try doing something similar to ActiveRecord
module ActiveRecord
class Base
def self.has_many(*things)
puts "#{self} has_many #{things}"
end
end
end
class BuzzLightYear < ActiveRecord::Base
has_many :space_ships # does nothing for now but it's valid syntax
end# This is more the way Rails works
# Can do included hook and extend
module ActiveRecord
module Associations
module HashManyAssocation
def self.included(klass)
klass.extend(ClassMethods)
end
module ClassMethods
def has_many(things, options = {})
# TODO: define methods
puts "#{self} has many #{things}"
end
end
end
end
end
module ActiveRecord
class Base
# INCLUDE
include ActiveRecord::Associations::HashManyAssocation
end
end
class AstorMan < ActiveRecord::Base
has_many :space_ships
end# But could simply extend
module ActiveRecord
module Associations
module HashManyAssocation
def has_many(things, options = {})
# TODO: define methods
puts "#{self} has many #{things}"
end
end
end
end
module ActiveRecord
class Base
# EXTENDS
extend ActiveRecord::Associations::HashManyAssocation
end
end
class AstroMan < ActiveRecord::Base
has_many :space_ships
end
# acts_as_ ... to add functionality without extend ActiveRecord::Base
# We could use include with the included hook
module SuperHero
def self.included(klass)
klass.extend(ClassMethods)
end
module ClassMethods
def acts_as_superhero
puts "I'm a bird, I'm a plane, no I'm #{self}"
end
end
def fight_crime
puts "OK, fighting crime"
end
end
# use include
class AstroMan < ActiveRecord::Base
include SuperHero
end
AstroMan.new.fight_crime
# And include in your base class
class ActiveRecord::Base
include SuperHero
end
# then acts_as is available
class SuperMan < ActiveRecord::Base
acts_as_superhero
end
SuperMan.new.fight_crime# Calling non-existant methods
class Chad
def method_missing(method_name, *args)
puts "You called #{method_name} with #{args.inspect}"
end
end
Chad.new.just_do_it('again', 'and again')
# Calling non-existant classes
def Object.const_missing(name)
puts "Trying to get to non existing clas #{name}"
# trick: could require the file if class is missing
end
AnythingClass# Now that we went through the concepts let's do some meta programming
# eval
def evaluator(str, a_binding)
a_value = 123
eval(str, a_binding)
end
str = "puts a_value"
a_value = 321
evaluator(str, binding) # -> 321. binding is the current scope of the program
evaluator(str, nil) # -> 123. don't pass binding
# instance_eval
class Thing
def a_value
123
end
end
Thing.new.instance_eval("puts self.a_value") # -> 123. run in context of an instance
# Two more 'eval': class_eval and module_val
#class_eval
class Person
end
Person.class_eval do # Be in context of class
p self # -> Person
def greeting
puts "Hello"
end
end
Person.new.greeting # defining instance method
def add_greeting_to(klass)
klass.class_eval do
def greeting
puts "Greeting"
end
end
end
add_greeting_to(String)
"asdf".greeting # -> Greeting
# module_eval is basically same thing as class_eval
# define_method
class Chad
define_method(:foo) do |arg1|
puts "hello, #{arg1}"
end
end
Chad.new.foo(:bar)RailsConf registration opens today. Be ready! 2
May 29-June 1, 2008 in Portland, Oregon,
UPDATE: registration is now open.
UPDATE2: I’ll be presenting with Tony a 3 hour tutorial on Powering AIR Applications with Rails. See you all there!
Rails 2.0 17
Rails 2.0 is out! Thanks guys for all the hard work, this release is just impressive.
Acts_as_nested_set ActiveRecord rendered with mx:Tree in Flex. 9
class Category < ActiveRecord::Base
acts_as_nested_set
endclass CategoriesController < ApplicationController
def index
Category.result_to_attributes_xml(Category.root.full_set)
end
end<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
applicationComplete="categories.send()">
<mx:HTTPService id="categories" url="http://localhost:3000/categories" resultFormat="e4x" />
<mx:Tree dataProvider="{categories.lastResult}"
labelField="@name"
width="100%" height="100%" />
</mx:Application>Result:

<node name="Main Category" id="15" description="">
<node name="Cameras & Photo" id="16" description="">
<node name="Bags" id="17" description=""/>
<node name="Accessories" id="18" description=""/>
<node name="Analog Cameras" id="19" description=""/>
<node name="Digital Cameras" id="20" description=""/>
</node>
<node name="Cell Phones" id="21" description="">
<node name="Accessories" id="22" description=""/>
<node name="Phones" id="23" description=""/>
<node name="Prepaid Cards" id="24" description=""/>
</node>
<node name="Dvds" id="25" description="">
<node name="Blueray" id="26" description=""/>
<node name="HD DVD" id="27" description=""/>
<node name="DVD" id="28" description=""/>
</node>
</node>I used the http://wiki.rubyonrails.org/rails/pages/BetterNestedSet plugin.
Too cool!
UPDATE: The BetterNestedSet plugin doesn’t work out of the box with Rails 2.0 RC1. Thanks Joel for that info. Read more in the comment of this blog entry.
UPDATE2: Thanks Fabien, BetterNestedSet now works with Rails 2.0!
RailsLogVisualizer0.7 for AIR beta 2. 6
Install RailsLogVisualizer0.7.air
Install Manually
1) Instal Adobe AIR beta 2. (See release notes if previous version was installed)Download AIR for OSX Download AIR for Windows
Learn more on AIR
2) Download and install http://myspyder.net/tools/railslogvisualizer/RailsLogVisualizer0.7.air For time.onrails.org the log file is currently 98Mb and is loaded and process in less than a minute. Here are the loading details:
Loaded 98571986bytes in 28093 milliseconds. Parsing file. Please Wait this may take some time.... Parsing. Split 1639453entries in 1447 milliseconds. found:220767 in 1925 milliseconds. Aggregating data. aggregated:220767 in 13426 milliseconds. Aggregated:89135 aggregated String :4440464(bytes) in 2790 milliseconds.Then you can navigation through time and see how many request where processed and drill down in specific action and specific methods. For example, here we can quickly see that for October 99 people signed up, 869 did login, 22 forgot their password.

Sweet way to write Flex Unit tests for Rails
Using ActiveResources from Flex? Using FlexUnit? Here is a nice way to write your tests.
package tests
{
import flexunit.framework.*;
import mx.rpc.AsyncToken;
import mx.rpc.events.ResultEvent;
import resources.Raffles;
public class TestRaffles extends BaseTestCase
{
private var raffles:Raffles;
public function TestRaffles(name : String = null)
{
super(name);
fixtures(["raffles"]);
raffles = new Raffles();
}
public function testRemoteFindRaffle():void
{
assertRemote(raffles.show(1));
}
public function assertRemote_testRemoteFindRaffle(data:Object):void
{
Assert.assertTrue("Raffle show successfully called", data is ResultEvent);
assertEquals("MyString", data.result.name);
}
}
}Note this code is not yet a plugin and is using code you can find here: http://code.google.com/p/flexonrails/source. I was starting to use it on multiple projects so I thought it was to time find a home for it. Also it is using the org.onrails.rails.ActiveResourceClient Flex class. I would recommend that you use Alex MacCaw’s ActvieResrouce for Actionscript. I still need to talk with Alex and integrate this fixture loading code with his code.
The BaseTestCase Flex class is an extended TestCase that provides support for fixtures. Now in your constructor you can define which fixtures you want to reload between each test. Only tests methods starting with “testRemote” will trigger refreshing the fixtures. As you know, when using AMF or HttpService remote invocations are asynchronous and you cannot test the result of a remote call in the same method than where the call is made from. That’s why I added the assertRemote method which takes an AsyncToken as parameters. This will automatically invoke a method whos name starts with assertRemote_ followed by the test method name. This simplifies greatly writing asynchronous tests. FlexUnit provides the addAsync method, we just add the convenience assertRemote function to setup all the callbacks.
To make this work for you Flex with Rails project. You need to fixtures_controller.rb to your controllers and setup the following routes:
if RAILS_ENV == "test"
map.resources :fixtures, :new => { :test_results => :post }
map.crossdomain '/crossdomain.xml', :controller => 'fixtures', :action => 'crossdomain'
endYou need to extend your Flex TestCase from tests.BaseTestCase.
Enjoy, Daniel.
Installing RMagick on Leopard (without MacPorts or Fink) 42
I’ve recently upgraded to OS X 10.5 (Leopard), and all-in-all, I’m pleased with the experience. My biggest issue has been the default stacks behavior—the icon changes to the last thing added to the stack, making visual identification unnecessarily cumbersome. I worked around this annoyance (as outlined here) by changing the sort to name rather than date added, and adding a dummy folder named “_1” that will sort to the top. For extra bonus points, I customized the icon of the dummy folder. For some yet unknown reason, the most recently downloaded item still peeks through from time to time, but it’s much better than before.
Maybe it’s my Windows history showing through, but I went with the “clean-sweep” erase and install method. For a non-developer, I’d probably recommend the upgrade (and in fact I used that method for my Father-in-law’s MacBook), but I had lots of custom bits scattered about my machine, and didn’t want to be chasing any incompatibility gremlins.
So now, to get my development environment set up on the new machine… Leopard includes a fairly complete Rails stack out of the box, with a non-broken Ruby, readline support, and most of the commonly used gems. Read more here.
MySQL was not included, but the latest installer (mysql-5.0.45-osx10.4-i686.dmg) for 10.4 from dev.mysql.com downloads worked (mostly) fine. The Server and the StartupItem install and operate correctly. The PrefPane installs, but does not appear to actually do … anything. I’ll have to work on that, but I can live without it for now. After a bit of manual hacking on my database dump file from Tiger (where I was running a 5.1.x beta of MySQL), all my databases are back in place.
One last piece that I needed for my Rails apps—RMagick. I know it’s possible to install RMagick and its dependencies, um, “autoRMagickally” via a package management system like MacPorts or Fink, but I prefer not to. For some background on why not, you can read this article at hivelogic. The last time I was rebuilding my laptop and desktop near the same time, I put together a shell script to automate the process of installing RMagick. I got it back out and dusted off the cobwebs, and voila! RMagick on Leopard. (note: replace wget with “curl -O”, if you don’t have wget installed on your machine) Here’s the code:
#!/bin/sh
wget http://download.savannah.gnu.org/releases/freetype/freetype-2.3.5.tar.gz
tar xzvf freetype-2.3.5.tar.gz
cd freetype-2.3.5
./configure --prefix=/usr/local
make
sudo make install
cd ..
wget http://superb-west.dl.sourceforge.net/sourceforge/libpng/libpng-1.2.22.tar.bz2
tar jxvf libpng-1.2.22.tar.bz2
cd libpng-1.2.22
./configure --prefix=/usr/local
make
sudo make install
cd ..
wget ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz
tar xzvf jpegsrc.v6b.tar.gz
cd jpeg-6b
ln -s `which glibtool` ./libtool
export MACOSX_DEPLOYMENT_TARGET=10.5
./configure --enable-shared --prefix=/usr/local
make
sudo make install
cd ..
wget ftp://ftp.remotesensing.org/libtiff/tiff-3.8.2.tar.gz
tar xzvf tiff-3.8.2.tar.gz
cd tiff-3.8.2
./configure --prefix=/usr/local
make
sudo make install
cd ..
wget http://jaist.dl.sourceforge.net/sourceforge/wvware/libwmf-0.2.8.4.tar.gz
tar xzvf libwmf-0.2.8.4.tar.gz
cd libwmf-0.2.8.4
make clean
./configure
make
sudo make install
cd ..
wget http://www.littlecms.com/lcms-1.17.tar.gz
tar xzvf lcms-1.17.tar.gz
cd lcms-1.17
make clean
./configure
make
sudo make install
cd ..
wget ftp://mirror.cs.wisc.edu/pub/mirrors/ghost/GPL/gs860/ghostscript-8.60.tar.gz
tar zxvf ghostscript-8.60.tar.gz
cd ghostscript-8.60/
./configure --prefix=/usr/local
make
sudo make install
cd ..
wget ftp://mirror.cs.wisc.edu/pub/mirrors/ghost/GPL/current/ghostscript-fonts-std-8.11.tar.gz
tar zxvf ghostscript-fonts-std-8.11.tar.gz
sudo mv fonts /usr/local/share/ghostscript
wget http://imagemagick.site2nd.org/imagemagick/ImageMagick-6.3.5-9.tar.gz
tar xzvf ImageMagick-6.3.5-9.tar.gz
cd ImageMagick-6.3.5
export CPPFLAGS=-I/usr/local/include
export LDFLAGS=-L/usr/local/lib
./configure --prefix=/usr/local --disable-static --with-modules --without-perl --without-magick-plus-plus --with-quantum-depth=8 --with-gs-font-dir=/usr/local/share/ghostscript/fonts
make
sudo make install
cd ..
sudo gem install RMagickRails Rocks! 3
I am trying so see the result of a change in a Java program….5 minutes compilation…5 minutes deployment. Arggggggggggg!
Rails Rocks! Change + refresh = done!
Short term memory is good, hopefully I will forget that experience soon.
;-)