Ruby

Understand Ruby Symbols

You've probably already found some Ruby symbols, often used as hash keys. Example :

{:foo => 'bar'}

:foo is a symbol. 'bar' is a string. But we could do :

{'foo' => 'bar'}

So why use symbols ? Let's suppose the following case :

x = :sym
y = :sym
(x.__id__ == y.__id__ ) && ( :sym.__id__ == x.__id__)

This comparison will return true. One same symbol will always be the same object in memory in your whole application. But that's not a problem as symbols are not mutable.

And the same with strings :

x = "string"
y = "string"</p>

<p>(x.__id__ == y.__id__ ) || ( "string".__id__ == x.__id__)

Here, x, y and "string" are three different objects with different __id__. So 3 objects have been initialized in memory.

Now let's take again our first hash with the comparison string/symbol. With the symbols version :

{:foo => 'bar'}
{:foo => 'doe'}

We create 5 objects in memory : two hashes, a symbol and two strings.

With the strings version :

{'foo' => 'bar'}
{'foo' => 'doe'}

Here, we create 6 objects in memory : two hashes and 4 strings.

In an example case like this one it won't change anything. But in a real application context, if you replace all your symboles by strings, you'll need to increase the memory needed to run your application So whenever it's appropriate, stop using strings ans use symbols :)

Ruby console : ask for a password

When you get user information in console, you might, sometimes, need to get sensible information. Password for example. Which can't be displayed on the screen for security reasons. The library Ruby Password allows you to do that quite easily. However it implies you depend on this library. And that's something I don't wish. Particularly for something so simple.

The solution I'm suggesting here uses the linux features (not tested on windows. But who's still using it today ? :mrgreen: ).

begin
    print "Username: "
    username = $stdin.gets.chomp

    print "Password: "
    # We hide the entered characters before to ask for the password
    system "stty -echo"
    password = $stdin.gets.chomp
    system "stty echo"
rescue NoMethodError, Interrupt
    # When the process is exited, we display the characters again
    # And we exit
    system "stty echo"
    exit
end

What are we doing ? We start by asking the user's nickname.

print "Username: "
username = $stdin.gets.chomp
This is not a sensible data and can be displayed. Nothing specific to do.

Then we ask for a password. Here we must hide it.

print "Password: "
system "stty -echo"
password = $stdin.gets.chomp
system "stty echo"

The "stty -echo" will hide every character that should be displayed on the console. The "stty echo" displays them again.

Until there is works. Cool ! But somehowe we have a boring user, whom decides at the last moment to enter Ctrl-C to quit the program and finds himself in console with the -echo mode and doesn't see what he enters in. We've just lost a friend.

Fortunately we have exceptions notifications and we already detect an Interrupt to exit the program cleanly. Then we just have to display the characters again when there's this interrupt :)

begin
    # ...
rescue NoMethodError, Interrupt
    system "stty echo"
    exit
end

Hop ! :) And the project that uses this is glynn :)

Make a browser screenshot in Ruby with Selenium

Some time ago, a PHP wanabee (berk), Romain was looking to make a web page screenshot only with command line. Many solutions have been proposed. But my prefered one is Selenium. So I've decided to look closer into that.

First you need to have Selenium RC installed and launched. It's pretty simple. Download it, go to the selenium-server-1.0 and enter in command line

java -jar selenium-server.jar

Your Selenium server is started on the 4444 port, ready to be used ! You also need the selenium-client installed.

sudo gem install selenium-client

Your hard drive is now a bit less empty. We can start having fun with code ! :) I want to do a screenshot of my portfolio.

We start with the magic and explain after.

require 'rubygems'
require 'selenium'

# We load Selenium
@selenium = Selenium::SeleniumDriver.new("localhost", 4444, "*firefox", "http://42.dmathieu.com/", 10000);
@selenium.start

# We go to the main page and take the screenshot
@selenium.open "/"
@selenium.capture_entire_page_screenshot(File.expand_path(File.dirname(__FILE__)) + 'screenshot.png', '');

# We unload Selenium
@selenium.stop

We load the required libraries. Not complicated. We only need Selenium.

require 'rubygems'
require 'selenium'

Then we load Selenium, indicating the URL we wish to visit and the browser with which we want to visit it.

@selenium = Selenium::SeleniumDriver.new("localhost", 4444, "*firefox", "http://42.dmathieu.com/", 10000);
@selenium.start

We load the page, take the screenshot and save the created image.

@selenium.open "/"
@selenium.capture_entire_page_screenshot(File.expand_path(File.dirname(__FILE__)) + 'screenshot.png', '');

And we don't forget to free the memory.

@selenium.stop

And then the magic happens. Our beautiful screenshot (of the entire page, not only the screen) is then generated.

You'll notice that javascript is executed (try to deactivate it on your browser, you won't see my email on the page anymore). And the render is what we have in the browser.

So who's ready to start an open source project to generate websites thumbnails using Selenium ? :p

Ruby : use Active Record without Rails

If you've already been using Active Record. And as you're reading this blog and this article, I suppose you have, even though you may not know you have, you know how it is powerful. In case your memory fails you, active record is the list of methods used to access databases in Ruby on Rails. It is compatible with MySQL, SQLite and PostgreSQL (and if you wish to add your own adaptor, you can quite easily). But Rails is a framework for web applications. And God knows the web isn't the only face of computer programming. So we'll see how easy it is to use Active Record without Rails (or without all rails as activerecord is a part of it). First of all, you need to install the library. If you already Rails installed on your server, you're ready to go. Otherwise, do the following :

It'll get the latest stable version of the library and install it on your computer.

Now, let's start having fun with some code lines :)

Create a new document named (for example) main.rb.

First, you need to call the rubygems and activerecord libraries.

require 'rubygems'
require 'active_record'

With those lines at the top of your document, you now have access to all the methods provided by Active Record.

So let's use them.

ActiveRecord::Base.establish_connection(
    :adapter => 'mysql',
    :host => 'localhost',
    :user => 'root',
    :password => 'root',
    :database => 'test'
)

This will instantiate the library. <em>However, it'll not connect you to the database. You're getting connected only when you do the first query</em>

We now need to create a model. I invite you not to create it in the same document. So create a document named "user.rb" in the same directory as your main.rb. Put in that new document :

class User > ActiveRecord::Base
end

And in your main.rb, call it.

require 'user'

You now have access, in your main.rb, to the object User, which is a child of ActiveRecord::Base and grants it all the usual methods available to Ruby on Rails models. However, you're not in a rails application. So let's get all our users.

p User.find(:all)

Based on the fact that the table "users" exists in your database "test", this will print in your console the hash of all your users.

You can now do anything you want using ActiveRecord in your Ruby console or offline user interfaces applications.

But wait it's not over. Until now, we've defined all our configuration datas directly in the program. And that's very bad. So we need to add an external database.yml document.

adapter: mysql
host: localhost
username: root
password: root
database: refcrawler_dev

And to load that external document in ours to get it's values.

require 'yaml'
dbconfig = YAML::load(File.open('config/database.yml'))
ActiveRecord::Base.establish_connection(dbconfig)