Showing posts with label rails. Show all posts
Showing posts with label rails. Show all posts

Sunday, February 17, 2008

Shared Ruby Code

It's no secret that I think plugins are unnecessary. Unfortunately, they are also popular, largely due to to how easy it is to create a plugin and put it in your Rails project.

It's also easy to create a new gem. In 2006 Dr. Nic released the newgem gem. This was a great step in 2006, but it's 2008 now and it doesn't feel like we've moved much in the way of shared ruby code.

We're all to blame.

The vast majority of Ruby work is working with Rails. I do believe the next step for RubyGems is to play seamlessly with Rails. Since RubyGems are great for any Ruby code, it doesn't make sense to add Rails specifics to RubyGems. However, RubyGems could be more opinionated about how gems are structured. Right now, RubyGems library code can be structured in almost any way that you'd like. If the structure followed a stricter convention then auto gem loading code could be written for Rails.

I like newgem, but I think the features provided by newgem should actually be part of RubyGems itself. If the gem command could create a project skeleton it could be opinionated about the file structure without creating a larger barrier to entry.

There is another change that needs to happen. Everyone knows that you should vendor everything for your Rails projects. But, there's no single step to go from uninstalled gem to vendored gem. The path is generally to install it locally and them unpack it in whatever folder you've created that gets autoloaded.

I'm pretty sure a remote unpack is fairly simple, which is why we are all to blame. RubyGems is an open source project. Anyone can contribute patches to make it better.

Lastly, if gems become the standard way to share Ruby/Rails code then we'll need a few rake tasks added to Rails. For example, who wouldn't want a rake command to upgrade to a later version of a gem.

I'm hoping 2008 is the year that shared ruby code becomes synonymous with RubyGems and the year Rails Plugins become unnecessary.

Saturday, February 02, 2008

Ruby: Did TDD make Ruby a viable option?

Some people ask if Test Driven Development (TDD) is what caused Ruby to become popular. Other people smarter than me claim that TDD is in fact the reason that dynamic languages are now viable options.

I'm sorry, but I disagree.

Can you write a non-trivial application in Ruby without tests and have confidence in it? No. Can you write a non-trivial application in Java without tests and have confidence in it? Again, no.

A compiler lets you know that some things are correct, but should not give you confidence that your application behaves as expected. A trivial example is, I need to know that the calculate_tax (that's calculateTax for you Java fans) method returns 56, not that the result is a Fixnum (or Integer in Java).

As much as it should be, TDD is not mainstream. There's no TDD Conference, but RailsConf sells out every year.

I would actually credit David Heinemeier Hansson with making Ruby viable by creating a framework that drove mass adoption. But, I bet of you looked at the majority of Rails applications you would find empty test folders (or only the generated tests, which are never run). I'm quite sure that's true because I expect the conferences to attract the best of the Ruby developers, and several of the people I talk to at those conferences "simply don't have time" to write tests.

Saturday, January 26, 2008

Ruby/Rails Conferences

Today I was browsing the web for Ruby/Rails conferences to submit speaking proposals to. Unfortunately there doesn't seem to be a list of Ruby/Rails conferences anywhere (that I could find). Hopefully this list will save some time for anyone else looking for a similar list.U. S. Regional ConferencesWhat other Ruby/Rails conferences have I missed?

Saturday, January 12, 2008

Rails: Changing application.rb to application_controller.rb

Update: Koz killed the ticket for now so don't bother +1ing it. I've posted the idea to the Rails Core list already here so I guess we'll all have to live with the flaw for now.

Update #2, Koz et al are on board with making the change assuming the community doesn't come up with any upgrade blockers. Cross your fingers, we might see the change in the near future.

I've always been slightly annoyed that ApplicationController does not follow convention and is defined in application.rb, but I never bothered to do anything about it, until now.

I was recently asked to look over a fairly new codebase that was being actively worked on by someone new to Rails. In one of the controllers I found code similar to the following snippet.

if RAILS_ENV == 'development'
caches_page :index
end

The code functionally works, but I prefer to see environment specific logic in the environment specific files (development.rb, test.rb, and production.rb). I suggested that the developer set the caching logic in the development.rb file. Unfortunately, referencing a controller in development.rb will raise an error because ApplicationController is not yet available. This stems from the fact that require 'application' has not yet been executed and due to the naming inconsistency the class can not be auto-loaded. The fix is easy enough, you can require 'application' before you reference a controller, but I wouldn't expect any Rails novice to know that.

Is the inconsistency a blocker for Rails adoption? Absolutely not, but is it necessary? Again, absolutely not.

I wanted to patch* Rails and remove the inconsistency, so I started by creating a sample app to see what the change will break. Using Rails 2.0.2 I created a sample application and froze the 2.0.2 gems. Next I created a simple controller that would allow me to see what breaks when I change application.rb to application_controller.rb.

class HelloController < ApplicationController
def world
render :text => "hello world"
end
end

At this point it's time to make the file change and see what breaks. After making the filename change I restart my server (using script/server) and to my surprise, nothing breaks. I refresh my browser and everything just works. Well, that was much better than expected, but I must have broken something or I expect this change would have already been made.

So after a bit of looking around I found that application.rb was explicitly being referenced in the following places:
  • activesupport/lib/active_support/dependencies.rb
  • actionpack/lib/action_controller/dispatcher.rb
  • railties/lib/console_with_helpers.rb
  • railties/lib/test_help.rb
  • railties/lib/commands/performance/request.rb
activesupport/lib/active_support/dependencies.rb
and actionpack/lib/action_controller/dispatcher.rb

The special cases defined in these files are actually bypassed (invisibly to the user) when ApplicationController follows naming conventions.

railties/lib/console_with_helpers.rb
Making the change does break script/console. On line 19 'application' is explicitly required and since this file no longer exists an error is raised. The fix here is simple, remove the explicit require and let auto-loading take care of things.

railties/lib/test_help.rb
Line 1 explicitly requires application, again, the simple fix is to remove this line completely.

railties/lib/commands/performance/request.rb
Same as above, remove the explicit require on line 3 and everything just works.

Should you follow the instructions above? Maybe. If you never plan on changing your frozen version of rails then it might be a good idea. If you plan on changing your frozen version at some point but you think it's worth doing this work again at that point then it might also make sense. If you plan on changing your frozen version and you are worried that the change might cause issues in the future then you are probably better off living with the inconsistency for now.

I'm going to go ahead and make the change because I don't enjoy remembering the special case every time I want to Command+t and jump to the file.

Like I said before, I did the research so I could contribute back to the community by way of a Rails patch. I've put together a patch and submitted it, and it's gotten plenty of support, but it needs more, much more I suspect since Koz appears to have put it on hold. So, if you would also like to see the inconsistency removed please add a +1 to the ticket. (at http://dev.rubyonrails.org/ticket/10570)

* If you've ever wanted to submit a patch to Rails but not known where to start, I highly suggest starting at http://dev.rubyonrails.com.

Tuesday, November 13, 2007

Rails: Enumerable#sum

Documentation
Calculates a sum from the elements. Examples:

payments.sum { |p| p.price * p.tax_rate }
payments.sum(&:price)

This is instead of payments.inject { |sum, p| sum + p.price }

Also calculates sums without the use of a block:

[5, 15, 10].sum # => 30

The default identity (sum of an empty list) is zero. However, you can override this default:

[].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
Usage
The Enumerable#sum method does exactly what you would expect: Sum the elements of the array.

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "sum the numbers from the array" do
grades = [50, 55, 67, 62, 71, 89, 84, 85, 99]
assert_equal 662, grades.sum
end
end

Monday, November 12, 2007

Rails: Enumerable#group_by

Documentation
Collect an enumerable into sets, grouped by the result of a block. Useful, for example, for grouping records by date.

e.g.
  latest_transcripts.group_by(&:day).each do |day, transcripts|
p "#{day} -> #{transcripts.map(&:class) * ', '}"
end
"2006-03-01 -> Transcript"
"2006-02-28 -> Transcript"
"2006-02-27 -> Transcript, Transcript"
"2006-02-26 -> Transcript, Transcript"
"2006-02-25 -> Transcript"
"2006-02-24 -> Transcript, Transcript"
"2006-02-23 -> Transcript"
Usage
The Enumerable#group_by method is helpful for grouping elements of an Enumerable by an attribute or an arbitrary grouping. The documentation provides a good example of how to group by an attribute; however, the group_by method can be used logically group by anything returned from the block given to group_by.

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "group by grades" do
grades = [50, 55, 60, 62, 71, 83, 84, 85, 99]
expected = {"A"=>[99], "B"=>[83, 84, 85], "C"=>[71], "D"=>[60, 62], "F"=>[50, 55]}
actual = grades.group_by do |grade|
case
when grade < 60 then "F"
when grade < 70 then "D"
when grade < 80 then "C"
when grade < 90 then "B"
else "A"
end
end
assert_equal expected, actual
end
end

Sunday, November 11, 2007

Rails: Enumerable#index_by

Documentation
Convert an enumerable to a hash. Examples:

people.index_by(&:login)
=> { "nextangle" => , "chade-" => , ...}
people.index_by { |person| "#{person.first_name} #{person.last_name}" }
=> { "Chade- Fowlersburg-e" => , "David Heinemeier Hansson" => , ...}
Usage
I've used Enumerable#index_by for 2 different reasons recently. In one instance I needed faster access to find an element in an array. We found that pulling something out of a hash was faster than using the find method of array. We did a few benchmarks similar to the contrived examples below.
array = [1..100].to_a
hash = array.index_by { |element| element }
array_bm = Benchmark.measure do
array.find { |element| element = 99 }
end
hash_bm = Benchmark.measure do
hash.include? 99
end

p array_bm.real # => 1.382
p hash_bm.real # => 1.001
note: we couldn't use the include? method for our particular instance, so I left it out of the contrived example.

The benchmarks for our project revealed even larger gains, thus it made sense to convert our array to a hash and work with that instead of the array.

The other usage we found for index_by was to utilize the fact that index_by overwrites the value instead of appending (which group_by does). We had a list of scores and wanted to group them together. The group_by method could handle that case; however, we only wanted to keep track of the highest score per group. The test below shows an example where a list of grades can be grouped by grade letter and the highest score per grade letter.

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "index by highest numeric per grade level" do
grades = [50, 55, 67, 62, 71, 89, 84, 85, 99]
expected = {"A"=>99, "B"=>89, "C"=>71, "D"=>67, "F"=>55}
actual = grades.sort.index_by do |grade|
case
when grade < 60 then "F"
when grade < 70 then "D"
when grade < 80 then "C"
when grade < 90 then "B"
else "A"
end
end
assert_equal expected, actual
end
end

Saturday, November 10, 2007

Rails: Integer#even?

Documentation
None

Usage
The Integer#even? method is helpful when adding zebra striping to views.

For example:
  <% @customers.each_with_index do |customer, index| %>
<div style="color:<%= index.even? ? "white" : "gray" %>">
<%= customer.name %>
</div>
<% end %>

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "10 is even?" do
assert_equal true, 10.even?
end
end

Friday, November 09, 2007

Rails: String#each_char

Documentation
Yields a single-character string for each character in the string. When $KCODE = ‘UTF8’, multi-byte characters are yielded appropriately.
Usage
The String#each_char method is nice for iterating through a string, one character at a time. I generally use regex for string manipulation; however, when context within the string matters, each_char is helpful.

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "each_char can be used to strip every other charater" do
strip, result = true, ""
"hello world".each_char do |char|
result << char if strip
strip = !strip
end
assert_equal "hlowrd", result
end
end

Thursday, November 08, 2007

Rails: Hash#diff

Documentation
None

Usage
The Hash#diff method is helpful for determining the difference between two hashes.

note: The receiver matches on both keys and values. If the pair is not matched it be returned. (for example: {:a => 1, :b => 3}.diff({:a=>2}) # => {:a=>1, :b=>3})

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "find the difference between two hashes" do
assert_equal({:b=>3}, {:a => 1, :b => 3}.diff({:a=>1}))
end
end

Wednesday, November 07, 2007

Rails: String#constantize

Documentation
Constantize tries to find a declared constant with the name specified in the string. It raises a NameError when the name is not in CamelCase or is not initialized.

Examples
  • "Module".constantize #=> Module
  • "Class".constantize #=> Class
Usage
Constantize is definitely the most used String method that I utilize while metaprogramming.

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
class Klass
end

test "constantize a string representation of a class" do
assert_equal Klass, "Klass".constantize
end
end

Tuesday, November 06, 2007

Rails: String#classify

Documentation
Create a class name from a table name like Rails does for table names to models. Note that this returns a string and not a Class. (To convert to an actual class follow classify with constantize.)

Examples
  • "egg_and_hams".classify #=> "EggAndHam"
  • "post".classify #=> "Post"
Usage
The classify method is helpful when metaprogramming. I generally replace "strings".singularize.camelize with "strings".classify (and the classify implementation calls singularize and camelize for me).

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "singularize and camelize a string via classify" do
assert_equal "Name", "names".classify
end
end

Monday, November 05, 2007

Rails: String#singularize

Documentation
The reverse of pluralize, returns the singular form of a word in a string.

Examples
  • "posts".singularize #=> "post"
  • "octopi".singularize #=> "octopus"
  • "sheep".singluarize #=> "sheep"
  • "word".singluarize #=> "word"
  • "the blue mailmen".singularize #=> "the blue mailman"
  • "CamelOctopi".singularize #=> "CamelOctopus"
Usage
I generally use singularize when metaprogrammming to convert from a potentially plural version of a class name to something that I know can be constantized.

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "change plural word to singular word" do
assert_equal "class_name", "class_names".singularize
end
end

Sunday, November 04, 2007

Rails: String#pluralize

Documentation
Returns the plural form of the word in the string.

Examples
  • "post".pluralize #=> "posts"
  • "octopus".pluralize #=> "octopi"
  • "sheep".pluralize #=> "sheep"
  • "words".pluralize #=> "words"
  • "the blue mailman".pluralize #=> "the blue mailmen"
  • "CamelOctopus".pluralize #=> "CamelOctopi"
Usage
I generally use pluralize for creating nice messages for users. The pluralize method can also be helpful for metaprogramming when converting from a class name to a table name.

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "change singular word to plural word" do
assert_equal "names", "name".pluralize
end
end

Saturday, November 03, 2007

Rails: String#camelize

Documentation
By default, camelize converts strings to UpperCamelCase. If the argument to camelize is set to ":lower" then camelize produces lowerCamelCase.

camelize will also convert ’/’ to ’::’ which is useful for converting paths to namespaces

Examples
  • "active_record".camelize #=> "ActiveRecord"
  • "active_record".camelize(:lower) #=> "activeRecord"
  • "active_record/errors".camelize #=> "ActiveRecord::Errors"
  • "active_record/errors".camelize(:lower) #=> "activeRecord::Errors"
Usage
I generally use camelize when metaprogrammming to convert from a underscored version of a class name.

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "change underscored word to camelized word" do
assert_equal "AClassName", "a_class_name".camelize
end
end

Friday, November 02, 2007

Rails: Hash#assert_valid_keys

Documentation
None

Usage
Hash#assert_valid_keys gives you the ability to verify that each of the keys in the Hash are expected. For example, if you take an options Hash as an argument to a method you may want to validate that only expected options are passed as part of the hash; assert_valid_keys gives you the ability to list the keys you expect.

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "raise argument error on unexpected key" do
assert_raises ArgumentError do
{:invalid_key => :a, :valid_key => :b}.assert_valid_keys(:valid_key)
end
end
end