Rails on the Run

Rails experiments by Matt Aimonetti

Browsing Posts tagged gem

update Nov 11: the gem is finally available there or simply:

  sudo gem install googlecharts

Note that I’m working on merging this gem with another Google Charts gem. (see comments for more info about that)

gchart

I’ve been working on a Google Chart Gem that I have ready for a beta release but unfortunately, getting a new project setup on RubyForge takes forever. (apparently 72 hours)

It’s mainly a wrapper for the great GChart API, but instead of using a helper to generate your graphs, you can simply do:

  Gchart.bar(:title => 'My Mojo', :data => [1,2,4,67,100,41,234], :max_value => 300, :bg => 'c3c3c3')

  Gchart.line(:title => 'My Mojo',
              :data => [[1,2,4,67,100,41,234],[41,63,96,17,100,14,423]],
              :bg => '666666',
              :graph_bg => 'cccccc',
              :line_colors => 'ff0000,00ff00',
              :legend => ['morning','evening'])
  Gchart.pie(:data => [20,10,15,5,50], :title => 'SDRuby fu', :size => '400x200', :labels => ['matt', 'rob', 'patrick', 'jordan', 'ryan'])

img

As far as I know this is most complete Ruby wrapper for Google Chart API, but feel free to look around.

During Thanksgiving break I had fun with a friend of mine working on a Ruby challenge while digesting the traditional turkey.

http://content.screencast.com/media/a088950c-c9d1-4655-9b6e-b917e04dd6ec<em>74569570-772f-4886-b2ea-f305d1ede3aa</em>static<em>0</em>0_00000026.png”/></p>
<p>The challenge was quite simple, create a small library that can generate random words from the English dictionary. </p>
<p>But of course there was a twist. One should be able to choose the total amount of characters, the amount of words and the separator between the words. However we both had a <a href=word list.

I personally decided to use SQlite3 to store the words after parsing the text file if the database is empty.

It was a good exercise and it got me to play with SQLite and one of the Ruby adapter library. Once I was done, I decide to play with DrNic cool Gem generator.

http://static.flickr.com/50/130749539<em>89959dd059</em>t.jpg”/> </p>
<p>Nic is my favorite Aussie’s Rubyist (ok, I don’t know many) and I’ve been wanting to check on this lib for a very long time. And I have to say he did an awesome job! Writing a Ruby Gem has never that easy! And on top of that the generator creates RSpec examples (or test/unit tests), a basic website for your gem and has a bunch of rake tasks to deploy your newly created gem.</p>
<p>Feel free to check the source code:</p>
<p><a href=http://random-word-gen.rubyforge.org/svn/

And the Rubyforge site

By the way, I did find an almost useful use for this gem. Activation code generator. You know, the kind of string your receive on by email or SMS to activate a feature or an account. It’s always a pain to type a MD5Hash string, while, when using the word generator, the string is made of existing words, making the process way more user friendly.

I also plan on adding some random copyleft text to the sqlite db so the Gem will be able to generate titles, paragraphs and random quotes. I’m just tired of reading lorem ipsum and on top of that, I get to it, I might had text in various languages so you check if your app breaks when using another char set, or if your layout can’t handle too much text.

Honestly, I don’t expect you to use this gem, but I jut wanted to encourage people to start writing their own gem, the process is super easy and rewarding. And actually, feel free to try the challenge and post a link to your implementation in the comments. (That’s seriously, the best way to learn)

p.s: I’m sorry about my RSS feed constantly being reset, it seems to be a problem with Mephisto, my blog engine and we are trying to figure out what’s going on.

ambition

By now, you should have heard about ambition if not read the latest post from the author.

Ambition has a simple goal: making you stop writing SQL in your queries and only stick to Ruby. (who cares if you use ActiveRecord, Sequel, DataMapper or another ORM)

I’m so used to the ActiveRecord way of querying the database that I was not fully convinced that Ambition would help me in my daily tasks. I still gave it a try:

Testing Ambition

$ sudo gem install ambition -y

Started my console

$ script/console

and required Ambition

require 'ambition'

I started by doing a query the AR way:

1
2
 Photo.find(:all, :conditions => "photos.title IS NULL AND photos.width > 250 
AND photos.height > 200 AND users.name = 'test'", :include => :user) 

And I converted it into an Ambition call:


Photo.select {|p| p.title == nil && p.width > 250 && p.height > 200  && p.user.name == 'test'}.entries

145 vs 102 keystrokes. 30% less typing with Ambition! I don’t know about you, but I REALLY prefer the Ruby only query, much cleaner and much “DRYer”. However, that’s not always true:


Photo.find_by_title(nil)

(24chars)


Photo.detect{|p| p.title == nil}

(32 chars)

But what’s going on behind the scene? Do we have the exact same SQL query sent to our DB?

Well, Ambition doesn’t generate any SQL, it uses AR to do so. You want to make sure Ambition is not messing with you, try that:

1
2
 >> Photo.select {|p| p.title == nil && p.width > 250 && p.height > 200  && p.user.name == 'test'}.to_hash
 => {:conditions=>"(photos.`title` IS NULL AND (photos.`width` > 250 AND (photos.`height` > 200 AND users.name = 'test')))", :include=>[:user]}

That’s pretty hot. Especially when you have to use eager loading!

Obviously you can still do stuff like that:

1
2
3
Photo.select {|p| p.title == nil && p.width > 250 && p.height > 200  && p.user.name == 'test'}.each do |photo|
 puts photo.filename
end

(note the query will only be made once)

Another cool thing, is to do simple sorting:


>> Photo.select {|p| p.title == nil && p.user.name == 'test'}.sort_by { |p| [p.created_at, -p.size] }

creates the following:


=> { :o rder=>"photos.created_at, photos.size DESC", :conditions=>"(photos.`title` IS NULL AND users.name = 'test')", :include=>[:user]}

or


=> "SELECT * FROM photos JOIN user WHERE (photos.`title` IS NULL AND users.name = 'test') ORDER BY photos.created_at, photos.size DESC"

That’s cool, and you can still sort on relationships:

1
2
3
4
5
6
7
>> Photo.select {|p| p.title == nil }.sort_by { |p| p.user.name }
=> "SELECT * FROM photos JOIN user WHERE photos.`title` IS NULL ORDER BY users.name"</macro:code >
    
Or directly on the model:

<macro:code lang="ruby">>> Photo.sort_by(&:title)
=> "SELECT * FROM photos ORDER BY photos.title"

To finish, another detail which makes Ambition a great library

1
2
3
>> Photo.any? {|p| p.title =~ /ambition/ }
=> "SELECT count(*) AS count_all FROM photos WHERE (photos.`title` REGEXP 'ambition')" 
=> true

And if you were worried that it wouldn’t work with utf8, check this out:

1
2
3
4
5
6
>> Photo.any? {|p| p.title == 'école'}
=> SET NAMES 'utf8'
=> SET SQL_AUTO_IS_NULL=0
=> SHOW FIELDS FROM photos
=> SELECT count(*) AS count_all FROM photos WHERE (photos.`title` = 'école') 
=> false

Limitations

The only limitation I found in Ambition is that Ruby code won’t work in the block, for instance:


>> Photo.select {|p| p.title == nil && p.created_at < 1.week.ago && p.user.name == 'test'}.entries

won’t work at the moment. To inspect what’s going simply try:

1
2
>> Photo.select {|p| p.title == nil && p.created_at < 1.week.ago && p.user.name == 'test'}.to_sql
=> "SELECT * FROM photos JOIN user WHERE (photos.`title` IS NULL AND (photos.`created_at` < 1.`week`.`ago` AND users.name = 'test'))"

You can see that photos.created_at < 1.week.ago is the problem.

The recommended way to achieve the same result is to use variables:

1
2
3
>> date = 1.week.ago
>> Photo.select {|p| p.title == nil && p.created_at < date && p.user.name == 'test'}.to_sql
=> "SELECT * FROM photos JOIN user WHERE (photos.`title` IS NULL AND (photos.`created_at` < '2007-09-08 19:38:48' AND users.name = 'test'))"

However, note that method calls will work just fine:

1
2
3
4
5
6
>> def time_now_please
>> Time.now
>> end
    
>> Photo.select {|p| p.title == nil && p.created_at < time_now_please && p.user.name == 'test'}.to_sql
=> "SELECT * FROM photos JOIN user WHERE (photos.`title` IS NULL AND (photos.`created_at` < '2007-09-15 19:41:37' AND users.name = 'test'))"   

Conclusion

For now, Ambition is still just wrapping ActiveRecord::Base#find but the plan is to actually generate SQL. Hopefully we’ll also be able to use Ruby code from within an Ambition block. Kickers methods are very interesting and could become a really nice way of speeding up your app and keep your code clean.

Ambition is a great query library, I think I’ll start using it whenever I have “find” calls with multiple conditions especially if my conditions are related to another model. However I still didn’t figure out how to use an inner join with Ambition.