Scala loops
Today, I finally got to play with Scala. I’ve been using mainly Ruby (and loving it) for most of my projects but I promised myself (in one of my new year’s resolution) that I will learn a new programming language this year. So from time to time, I will sprinkle this blog with some Scala love.
Now, for some mandatory hello world program. Nah. The code below simply prints the arguments to your Scala program.
var i = 0
while (i < args.length) {
println("hello " + args(i) + "!")
i += 1
}
// Assuming you have saved the code in hello.scala, do the following to run the code:
// scala hello.scala arg1 arg2 arg3
The code above is an imperative style used normally in languages like C or C++. Most new programmers are exposed to this style.
Being a Ruby developer, I quickly wondered if there is something similar to “.each” method? Yes, there is.
args.foreach(arg => println(arg)) args.foreach(println)
Above is a functional style and is something you will be comfortable with as you improve your programming skill. In Scala, the second line is possible if the function accepts a single argument.
Since ‘println’ is just a function, you could just replace it with your own function.
def printer(x: String) = {
println(x.toUpperCase)
}
args.foreach(arg => printer(arg))
Yes, you guessed right. You can also define anonymous function.
args.foreach(arg => { println(arg.toUpperCase) })
When I first heard that Scala is somewhat related to Java, my first thought was it is going to be another verbose language. Well, I was wrong
It appears to be a fun and powerful language worth learning.
24 Ruby tips and tricks
Peter Cooper will share more tips in his book to be released later this year. Stay tune and don’t forget to leave your email address to get updates at http://rubyreloaded.com/trickshots/
Here are some of the tips in the video.
Generate random numbers within a given range
irb(main):019:0> rand(10..20) => 12 irb(main):020:0> rand(10...20) # works with exclusive range => 16
Dump your object using awesome_print
# Install the gem first
gem install awesome_print
irb(main):001:0> require 'ap'
=> true
irb(main):002:0> ap :a => 1, :b => 'greg', :c => [1,2,3]
{
:a => 1,
:b => "greg",
:c => [
[0] 1,
[1] 2,
[2] 3
]
}
=> {:a=>1, :b=>"greg", :c=>[1, 2, 3]}
Concatenating strings
irb(main):005:0> "abc" + "def"
=> "abcdef"
irb(main):006:0> "abc".concat("def")
=> "abcdef"
irb(main):007:0> x = "abc" "def"
=> "abcdef"
Include modules in a single line
class MyClass include Module1, Module2, Module3 # However, the modules are included in reverse order. Confusing eh! end
Instance variable interpolation
irb(main):008:0> @name = "greg"
=> "greg"
irb(main):009:0> "my name is #{@name}"
=> "my name is greg"
irb(main):010:0> "my name is #@name"
=> "my name is greg"
I still prefer the use curly braces.
Syntax checking
➜ ruby -c facu.rb facu.rb:12: syntax error, unexpected keyword_end, expecting $end
Zipping arrays
irb(main):027:0> names = %w(fred jess john) => ["fred", "jess", "john"] irb(main):028:0> ages = [38, 47,91] => [38, 47, 91] irb(main):029:0> locations = %w(spain france usa) => ["spain", "france", "usa"] irb(main):030:0> names.zip(ages) => [["fred", 38], ["jess", 47], ["john", 91]] irb(main):031:0> names.zip(ages, locations) => [["fred", 38, "spain"], ["jess", 47, "france"], ["john", 91, "usa"]]
Range into arrays
irb(main):034:0> (10..20).to_a # what I used to do => [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] irb(main):035:0> [*10..20] => [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
Using parameter as default value
irb(main):047:0> def method(a, b=a); "#{a} #{b}"; end
=> nil
irb(main):048:0> method 1
=> "1 1"
irb(main):049:0> method 1, 2
=> "1 2"
Put regex match in a variable
irb(main):058:0> s = "Greg Moreno" => "Greg Moreno" irb(main):059:0> /(?<first>\w+) (?<second>\w+)/ =~ s => 0 irb(main):060:0> first => "Greg" irb(main):061:0> second => "Moreno"
Preventing model explosion via Rails serialization
A great thing about ActiveRecord is you can easily add a new model to your application and play around with it as you progress. However, this power can easily be overused leading to unnecessary overhead in your code.
Consider the case where you have preferences for each user. For example, a user may opt to show or hide his email address, adjust his timezone, or language. One solution is to simply add new columns to the users table that correspond to each preference type. For example, you can have a ‘show_email’, ‘timezone’, ‘locale’ columns in the ‘users’ table, which can make your table become wide as you add more preferences options. Another option is to use a separate ‘preferences’ table.
class User < ActiveRecord::Base
has_many :preferences
end
class Preferences < ActiveRecord::Base
belongs_to :user
# name - preference name
# value - preference value
end
Note there is no user interface to add or remove ‘preferences’, i.e. the kinds of preferences are fixed. Of course, in the future you may add a new kind of preference but this kind of work is better done outside of the user interface. Since that is the case, there is no need to represent ‘preferences’ as a separate model.
One better alternative is to use Rails serialization to store the different kinds and user-specific values. The code would look like this:
class User < ActiveRecord::Base
serialize :preferences, Hash
end
u = User.new
u.preferences = {:show_email => true, :locale => :en }
u.save
# somewhere in your view using haml
- if @user.preferences[:show_email]
= @user.email
Using ‘serialize’ results in less code, fewer tables, and less overall complexity. However, with serialization you lose the ability to efficiently search the preferences data. The million-dollar question is do you need to query these preferences? Do you need a finder that returns all users who wants to show their email?
One issue I had with ‘serialize’ is that by using it, I expose the implementation details. In the display example above, it is obvious I had it stored as a Hash. I would rather hide this detail and present the preferences attributes as user attributes instead. I also want default values for every user.
For example:
u = User.new # automatically assigns the default preferences
u.preferences
=> {:show_email => false, :locale => :en}
u.show_email = true # I can change it like an attribute via @user.update_attributes(params[:user])
u.preferences
=> {:show_email => true, :locale => :en}
I have created a module to support this. It is not a unique problem so others may have probably released a gem or plugin to do this. (I actually never bothered to search for one.) Nevertheless, it was a good exercise in metaprogramming.
To use my implementation, simply call ‘serializeable’ with the column you want to serialize and the default values.
class User < ActiveRecord::Base
serializeable :preferences, :show_email => true, :locale => :en
end
Below is the implementation of ‘serializeable’. The convention is to save it under your ‘lib’ folder and include it in your ‘config/application.rb’ if you are using Rails 3.
module AttributeSerializer
module ActiveRecordExtensions
module ClassMethods
def serializeable(serialized, serialized_accessors={})
serialize serialized, serialized_accessors.class
serialized_attr_accessor serialized, serialized_accessors
default_serialized_attr serialized, serialized_accessors
end
# Creates the accessors
def serialized_attr_accessor(serialized, accessors)
accessors.keys.each do |k|
define_method("#{k}") do
self[serialized] && self[serialized][k]
end
define_method("#{k}=") do |value|
self[serialized][k] = value
end
end
end
# Sets the default value of the serialized field
def default_serialized_attr(serialized, accessors)
method_name = "set_default_#{serialized}"
after_initialize method_name
define_method(method_name) do
self[serialized] = accessors if self[serialized].nil?
end
end
end
end
end
class ActiveRecord::Base
extend AttributeSerializer::ActiveRecordExtensions::ClassMethods
end
ActiveRecord is both easy and powerful. It can also lead to misuse and abuse. Even though you are adding just one model, remember that it is not just the model class itself. You are also adding the database migrations, unit tests, factories, finders, and validations that go along with the model. Next time you have a new requirement, see if serialization can do a better job.
Update: Adam Cuppy converted this code into a Rails plugin while Jay added dynamic finder methods. I also moved this into a gem I called ‘fancy_serializer‘.
Ruby 101: Improving your code by defining methods dynamically
Let’s say you have a user and you want to check its role.
class User
attr_accessor :role
end
u = User.new
u.role = 'admin'
# somewhere in your code you check the role
if u.role == 'admin'
puts 'admin'
elsif u.role == 'moderator'
puts 'moderator'
elsif u.role == 'guest'
puts 'guest'
end
Using a string value is bad code and you can improve this by using constants instead. But still, this is bad code becauses it exposes implementation details of your User class.
For our first improvement, we define methods that check the user’s role and hide the implementation of the role checking inside the User class.
class User
attr_accessor :role
def is_admin?
self.role == 'admin'
end
def is_moderator?
self.role == 'moderator'
end
def is_guest?
self.role == 'guest'
end
end
u = User.new
u.role = 'guest'
if u.is_admin?
puts 'admin'
elsif u.is_moderator?
puts 'moderator'
elsif u.is_guest?
puts 'guest'
end
Our first improvement is definitely better than the original but there are duplicate code in the role checking. You can eliminate the duplicate code by delegating the role checking to a single method.
class User
attr_accessor :role
def is_admin?
is_role? 'admin'
end
def is_moderator?
is_role? 'moderator'
end
def is_guest?
is_role? 'guest'
end
protected
def is_role?(name)
self.role == name
end
end
Our second improvement is a classic refactoring technique and common in any modern programming language. In other words, there is nothing “Ruby” about it. Before you get bored, I will now show the Ruby version.
The Ruby version uses ‘define_method()’ to further eliminate duplicate code.
class User
attr_accessor :role
def self.has_role(name)
define_method("is_#{name}?") do
self.role == "#{name}"
end
end
has_role :admin
has_role :moderator
has_role :guest
end
By using ‘define_method()’, we were able to add instance methods to our class User. You can check the new instance methods via irb.
ruby-1.9.2-p0 > User.instance_methods.grep /^is/ => [:is_admin?, :is_moderator?, :is_guest?, :is_a?]
Note that ‘has_role()’ is just another method and as such you can modify it to accept several parameters, an array, or other class. For example, we can make ‘has_role’ accept a list of roles.
class User
attr_accessor :role
def self.has_roles(*names)
names.each do |name|
define_method("is_#{name}?") do
self.role == "#{name}"
end
end
end
has_roles :admin, :moderator, :guest
end
How to use OpenAmplify with Ruby
The OpenAmplify API reads text you supply and returns linguistic data explaining and classifying the content. What you do with that analysis is, in the fine tradition of APIs and mashups, up to you. Some possibilities might include pairing ads with articles, creating rich tag-clouds, or monitoring the tone of forum threads.
I created a ruby gem to simplify the use of the OpenAmplify API. It’s still in the early stages but should be enough to get you started.
Output Format
In case you need a different format, OpenAmplify supports XML, JSON, RDF, CSV. It can also return the result as a fancy HTML page.
The source code is available in github: http://github.com/gregmoreno/openamplify
Using RabbitMQ and AMQP with Ruby
AMQP stands for Advanced Message Queuing Protocol. RabbitMQ is a server that implements the protocol. RabbitMQ is available on Linux, OSX, and Windows. Installation instructions for RabbitMQ are available at http://www.rabbitmq.com/install.html
I will be running RabbitMQ on Ubuntu 9.10. First, let’s install the server:
Next, we install the amqp gem by Aman Gupta. The gem can also be found at http://github.com/tmm1/amqp
Let’s now build a simple publisher code. Note the difference in the gem’s name and the file you need to use the gem.
All queues are created automatically the first time it is accessed. Make sure this is the same queue our consumers will use.
Next, the consumer code:
The ‘subscribe’ method registers with the queue telling it to call the block when a message has arrived. Alternatively, you can use ‘pop’ but this would constantly poll the server for new messages making unnecessary calls even if the queue is empty.
If you want to run AMQP on several machines, just specify the location of the broker in your publisher and consumer code:
You only need to run 1 rabbitmq server, which in my case is on Ubuntu. When you try the code in other machines, you only need the amqp gem installed.
I highly recommend Distributed Programming with Ruby by Mark Bates if you’re interested in distributed programming.
Deploy a Rails 3, Sqlite3 application in Tomcat using JRuby
and have a Ruby version running side-by-side.
A few months ago I got interested in JRuby while researching for text mining algorithms. I found some gems but they are either unmaintained or inadequate while the mature libraries I found were written in Java. No problem! JRuby to the rescue. Thank God.
Next stop, I decided to take Rails 3 and JRuby for a spin. Incidentally, I will be on a 3-city Rails tour in the Philippines this September and since there are many Filipino Java developers, they might find it interesting to see their favorite Java platform works nicely with Ruby on Rails.
Setup
I will be using the following for this tutorial:
java 1.6 + JDK
tomcat 7.0.2
rvm 1.0.1
jruby 1.5.0
ruby 1.9.2p0
Further below, I outline how to install these software. First, let’s see my current environment.
$ more /etc/issue Ubuntu 9.10 \n \l $ java -version java version "1.6.0_20" Java(TM) SE Runtime Environment (build 1.6.0_20-b02) Java HotSpot(TM) Server VM (build 16.3-b01, mixed mode) $ rvm -v rvm 1.0.1 by Wayne E. Seguin (wayneeseguin@gmail.com) [http://rvm.beginrescueend.com/] $ jruby -v jruby 1.5.0 (ruby 1.8.7 patchlevel 249) (2010-05-12 6769999) (Java HotSpot(TM) Client VM 1.6.0_20) [i386-java] $ TOMCAT/bin/version.sh Using CATALINA_BASE: /usr/local/apache-tomcat-7.0.2 Using CATALINA_HOME: /usr/local/apache-tomcat-7.0.2 Using CATALINA_TMPDIR: /usr/local/apache-tomcat-7.0.2/temp Using JRE_HOME: /usr Using CLASSPATH: /usr/local/apache-tomcat-7.0.2/bin/bootstrap.jar:/usr/local/apache-tomcat-7.0.2/bin/tomcat-juli.jar Server version: Apache Tomcat/7.0.2 Server built: Aug 4 2010 12:23:47 Server number: 7.0.2.0 OS Name: Linux OS Version: 2.6.31-22-generic Architecture: i386 JVM Version: 1.6.0_20-b02 JVM Vendor: Sun Microsystems Inc. $ ruby -v ruby 1.9.2p0 (2010-08-18 revision 29036) [i686-linux] # install jdk and tomcat $ aptitude install curl sun-java6-bin sun-java6-jre sun-java6-jdk $ wget http://apache.mobiles5.com/tomcat/tomcat-7/v7.0.2-beta/bin/apache-tomcat-7.0.2.tar.gz $ tar zxvf apache-tomcat-7.0.2.tar.gz $ mv apache-tomcat-7.0.2 /usr/local
Of course, these assume you want to use 7.0.2 and you want it installed at your /usr/local.
Install JRuby, Rails 3
I assume you already have rvm installed. If not, I highly recommend that you do. I can’t imagine a Ruby developer not using rvm
$ rvm install jruby $ rvm jruby $ rvm gemset create railsjam $ rvm jruby@railsjam $ gem install rails
Try a sample app
I’ve created sample app for the RailsJam tour. This have several functionalities already and better than creating a Rails app from scratch.
$ git clone git://github.com/gregmoreno/railsjam.git
Update the Gemfile
You need a separate set of gems to make your Rails 3 application work with JRuby. For learning purposes, I want my Rails 3 application to work other than JRuby. To accomplish that, we need to specify what gems are needed solely by JRuby.
source 'http://rubygems.org'
gem 'rails', '3.0.0'
if defined?(JRUBY_VERSION)
gem 'jdbc-sqlite3'
gem 'activerecord-jdbc-adapter'
gem 'activerecord-jdbcsqlite3-adapter'
gem 'jruby-openssl'
gem 'jruby-rack'
gem 'warbler'
else
gem 'sqlite3-ruby', :require => 'sqlite3'
end
(A copy of this Gemfile is available at the ‘jruby’ folder of the railsjam application.)
Now, it’s time to intall the gems. You must delete ‘Gemfile.lock’. Otherwise, bundle picks up wrong version of jdbc
$ rm Gemfile.lock $ jruby -S bundle install
Prepare the database.
The first time I worked on this tutorial, I needed to specify the jdbcsqlite3 as the database adapter. However, when I tried the tutorial on the same machine with a fresh gemset, it worked pretty well with just ‘sqlite3’. Just to be sure, I modified ‘database.yml’ to check for JRuby.
development:
adapter: <%= defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3' %>
database: db/development.sqlite3
pool: 5
timeout: 5000
production:
adapter: <%= defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3' %>
database: /home/greg/dev/railsjam/db/development.sqlite3
pool: 5
timeout: 5000
When you deploy to Tomcat, it will be on ‘production’ mode by default. Since sqlite3 is file based and for simplicity, I used the same development database.
Now, do the migration.
$ jruby -S rake db:migrate
Deploy to Tomcat
We use ‘warble’ which is an excellent tool for packaging your Rails application. It packages everything you need to run your Rails application inside a Java container.
$ warble $ cp railsjam.war $TOMCAT/webapps # start Tomcat # assuming you arein $TOMCAT dir $ sudo ./startup.sh
Check your Rails 3 application
# You should see the famous Rails welcome localhost:3000/railsjam # Play around with your application localhost:3000/railsjam/users
Deploy Rails 3 using Ruby 1.9.2
Without shutting down your JRuby and Tomcat version, let’s try to run our app using Ruby 1.9.2
# In a new console $ rvm 1.9.2 $ rvm gemset create railsjam $ rvm 1.9.2@railsjam $ gem install rails # Assuming you are in the ‘railsjam’ folder # This will install sqlite3-ruby gem $ bundle install $ rails server Now, go play with your Rails 3 applications # jruby + tomcat http://localhost:8080/railsjam/users # ruby 1.9.2 http://localhost:3000/users
In case you encountered some problems, here are some ways to solve them. If your problem is not listed here, you can email me. I only accept Paypal
JRuby does not support native extensions
You did not update the Gemfile to use the jdbc version of sqlite3. You will encounter this error when you install the gems.
$ bundle install
....
Installing sqlite3-ruby (1.3.1) with native extensions /home/greg/.rvm/rubies/jruby-1.5.2/lib/ruby/site_ruby/1.8/rubygems/installer.rb:482:in `build_extensions': ERROR: Failed to build gem native extension. (Gem::Installer::ExtensionBuildError)
/home/greg/.rvm/rubies/jruby-1.5.2/bin/jruby extconf.rb
WARNING: JRuby does not support native extensions or the `mkmf' library.
Check http://kenai.com/projects/jruby/pages/Home for alternatives.
extconf.rb:9: undefined method `dir_config' for main:Object (NoMethodError)
undefined method `attributes_with_quotes’ for class `ActiveRecord::Base’
I first encountered this problem when doing migration.
$ rake db:migrate rake aborted! undefined method 'attributes_with_quotes' for class 'ActiveRecord::Base'
This is caused by an old version of your jdbc gems. In my case, sometimes bundler installs the old versions:
Installing activerecord-jdbc-adapter (0.9.2) Installing activerecord-jdbcsqlite3-adapter (0.9.2)
As of this writing, the latest version is 0.9.7
Installing activerecord-jdbc-adapter-0.9.7-java Installing activerecord-jdbcsqlite3-adapter-0.9.7-java
Bundler keeps installing 0.9.2
$ rm Gemfile.lock $ jruby -S bundle install
no such file to load — sqlite3
$ rake db:migrate (in /home/greg/dev/projects/jruby/railsjam) rake aborted! no such file to load -- sqlite3
‘sqlite3′ is the default name of the database adapter but with jruby, it should be ‘jdbcsqlite3′.
But, when I tried ‘sqlite3′ with a fresh gemset and a new machine, it went well.
Anyway, just in case you run into the same problem in the future, add a condition
in your database.yml
development:
adapter: <%= defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3' %>
database: db/development.sqlite3
pool: 5
timeout: 5000
production:
adapter: <%= defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3' %>
database: /home/greg/dev/railsjam/db/development.sqlite3
pool: 5
timeout: 5000
We’re sorry, but something went wrong.
If you see the famous Rails error message, you need to dig in Tomcat’s log files.
$ cd /usr/local/apache-tomcat-7.0.2/logs $ ls -al localhost* -rw-r--r-- 1 root root 1181 2010-09-01 00:17 localhost.2010-09-01.log -rw-r--r-- 1 root root 1062 2010-09-01 00:18 localhost_access_log.2010-09-01.txt $ tail -f localhost.2010-09-01.log
In the log file, you will see the errors like missing database.
org.jruby.rack.RackInitializationException: The driver encountered an error: java.sql.SQLException: path to '/home/greg/dev/tmp/apache-tomcat-7.0.2/webapps/railsjam/WEB-INF/db/production.sqlite3': '/home/greg/dev/tmp/apache-tomcat-7.0.2/webapps/railsjam/WEB-INF/db' does not exist
Rails 3 upgrade part 4: Prototype helpers and Javascript
Rails 3 is embracing the unobtrusive Javascript (or UJS) mantra which is good because it is the right way; at the same time, it is bad because many applications will break when they upgrade to Rails 3. On the other hand, who’s expecting a smooth upgrade anyway
In my test application, I used jrails because I am more interested in jQuery than Prototype. But since jrails doesn’t work with Rails 3, I removed it.
When jrails was removed, I received this error:
undefined method `observe_field' for #<#<Class:0xb6867e58>:0xb6865b6c>
Install Prototype helper plugin
‘observe_field’ is a Prototype helper and Rails 3 removed the the link between its Javascript helpers and Prototype. The goal in Rails 3 is for developers to use their preferred Javascript library. Also note that remote_#{method} helpers have been removed from Rails and moved to Prototype Legacy Helper plugin . To install this plugin, just do:
rails plugin install git://github.com/rails/prototype_legacy_helper
Remove jQuery
Once the prototype_legacy_helper is installed, the missing method is gone but observe_field is not triggering. Removing jQuery fixes this problem.
Now what if you want to use jQuery instead of Prototype? It depends how dependent your application is to Prototype. I have not found a jQuery equivalent for Prototype helper plugin yet so that would be an issue like in my case. Based on this jQuery and Rails 3 tutorial, using the jQuery UJS driver looks very easy.
Rails 3 upgrade part 3: Code fixes, views, and forms
This is part 3 of my Rails 2 to Rails 3 upgrade experience. Part 1 is about the initial code upgrade and getting the application to boot while part 2 deals with routes. While Part 2 is mainly about routes, getting it work involved changes in other parts of the code which I’ll share this time. So while you are updating your routes, you may need to check this post in between changes.
Update ApplicationController
After regenerating your application with rails (i.e. rails new appname -d dbadapter), your ApplicationController would look like this:
class ApplicationController < ActionController::Base
protect_from_forgery
end
There’s no need to panic because rails:upgrade:backup made a copy of the controller to application_controller.rb.rails2.
If you have a lot of helper modules, you’ll most likely have this code in your Rails 2 ApplicationController:
helper :all
If you encounter a missing method error while monkey clicking your application, you probably forgot to update your Rails 3 ApplicationController.
Update ApplicationHelper
The ApplicationHelper module was also modified by the rails upgrde. So don’t forget to update this, too.
RAILS_* constants are deprecated is not entirely true
When you run rails:upgrade:check, it will list items you need to update including deprecated code. There is no need to change these as the word ‘deprecated’ means but I encountered several “can’t convert nil into String” errors.
rake rails:upgrade:check
(in /mnt/hgfs/greg-mini/dev/projects/propsify)
Deprecated constant(s)
Constants like RAILS_ENV, RAILS_ROOT, and RAILS_DEFAULT_LOGGER are now deprecated.
More information: http://litanyagainstfear.com/blog/2010/02/03/the-rails-module/
The culprits:
...
The weird part is some constants are just doing fine. In any case, here are the conversion:
RAILS_ROOT -> Rails.root RAILS_ENV -> Rails.env RAILS_DEFAULT_LOGGER -> Rails.logger
You can also check your environment the Ruby way:
# before
if RAILS_ENV == 'production'
...
# Rails 3
if Rails.env.production?
Output strings are automatically escaped
We should all be rejoicing that Rails is now serious about XSS protection except now your pages have become ugly with all those HTML tags. For example the code below will not give you a clickable link.
- signup = link_to('create one here', signup_path)
= "If you do not have an account, #{signup}."
To fix this, use the raw() helper.
= raw "If you do not have an account, #{signup}."
Too bad for me, I got tons of views that were coded like this.
Check for ‘concat’
A popular technique to simplify your view code is to use content blocks. You create a helper that takes a block and wraps it in some HTML tags. A simple implementation would look like this:
module LayoutHelper
def main_column(options={}, &block)
# calls column()
end
def column(options={}, &block)
# concat is not needed in Rails 3
concat content_tag(:div, capture(&block), options)
end
end
# in your view
- main_column do
= render 'form'
This works fine in Rails 2 but in Rails 3 the block gets outputted twice. concat is the way to output text in a non-output block (i.e. <% %> in erb) but it seems like erb blocks in Rails 3 do not need concat.
Helpers with blocks
Before Rails 3, form_for or fields_for use non-output syntax; it means no equals sign.
# erb
<% form_for @offer do |f| %>
# ...
<% end %>
# haml
- form_for @offer do |f|
# ...
In Rails 3, it should now be written as an output block.
# erb
<%= form_for @offer do |f| %>
# ...
<% end %>
# haml
= form_for @offer do |f|
= f.fields_for :items do |ff|
# ...
The rule is if the method is expected to return a string, it should use the output syntax. If it just buffering the returned string like content_for, it should NOT have the equals sign.