Feeds:
Posts
Comments

Archive for the ‘ruby’ Category

Bookpiles

This project started out as a list of books in a text file.

screenshot

When I think about a book, I think about its content, the people who talked about it and how it made me feel. Central to those thoughts is the visual representation of the book itself: its cover. A list in a text file was not the best way to think about books. Over time, I realized that it would be the kind of problem suited for a small web application.

screenshot

I spent many hours working on this project. It used to be an excuse to play with Ruby on Rails. It used to be an excuse to play the limits of rich-client Javascript applications. It used to be an excuse to play with client and server-side optimizations, not by necessity but by a conscious effort to want to try things on a project I fully understand.

This is an application I designed for myself and that I use, for the lack of a better word, religiously. Hearing about books I want to read, buying a book, starting a new book, or finishing one, these are events that make me want to go to my profile and update it.

This application was initially meant to replace a text file. But the nature of a public display of books created new possibilities. When it comes to people I know, I want to know what they are reading so that we can talk about it the next time we meet.

“How was that book?”

Also, you can look at what people have read and discover what interests them. I have had a lot of interesting discussions after people browsed the books I have read.

Finally, this is also meant to be a portfolio piece. I can send people to the site to have a look at what I can do. The project is open-source and people can read the code and reach their own conclusions.

I’m open to comments and suggestions. Let me know what you think.

Code: http://github.com/jpalardy/bookpiles
Live app: http://bookpiles.ca

Advertisements

Read Full Post »

Bundler Without Rails

Yesterday I reached into a project I had not touched in months. When I wrote that Ruby script, it was supposed to be a one-off effort, but, as it usually goes for things like these, it had ended sticking around for much longer than anticipated.

I have RVM installed and I had installed many Rubies and done all kinds of gem manipulations. In short, the “environment” in which that project had worked was gone.

I had the “require” statements to guide me:


require rubygems
require dm-core
require dm-timestamps

require json

However, that’s not the whole story. In this specific case, DataMapper requires
more gems based on the connection string you give it.

I think we have all tried this:

  1. try to run a script
  2. see what “require” crashed the whole thing
  3. install some gems (hopefully with the version needed)
  4. repeat

Isn’t Bundler supposed to solve that problem?

Bundler

I have used Bundler with Rails 3. But that’s all configured and just automagically works. In a standalone project, there are a few things you need to do yourself.

First:

> bundle init

All that command did was to create an empty Gemfile.

Open the Gemfile with your favorite editor and add your gem dependencies. Mine looked like this:


# A sample Gemfile
source :gemcutter

gem "dm-core"
gem "dm-timestamps"
gem "dm-sqlite-adapter"

gem "json"

Then, run:

> bundle install

So far, this is all regular Bundler stuff. What about your script?

Bundler knows about all your dependencies, surely it will “require” all I need, right?

Yes and … no.

Bundler Documentation Fail

Here’s a screenshot from Bundler’s documentation

Thank you Bundler, I “require” you and now … huh … I still require all the gems I need?! I doesn’t sound very DRY to me.

What the “bundler/setup” line did was to configure the load path.

And you could do your requires manually…

If I’m writing this it’s because there’s a way. I’m just surprised that the Bundler website doesn’t seem to document this useful feature. If there are good reasons why this is not documented (tradeoffs or something) or, even, the default behavior — we can only guess.

Here’s what your script should do:


require rubygems
require bundler/setup

Bundler.require

The “Bundler.require” line will require all your dependencies.

One last note, do lock (bundle lock) your Gemfile so that the dependency resolution phase is skipped. It will make loading your script much faster. (this also applies to Rails projects)

Read Full Post »

Booklife goes Live

I read a lot … enough to write a webapp to track what I want to read, what I’m reading and what I read. I give you booklife.

Booklife is an application to scratch one of my itch.

How I use it:

  • when I hear about a book, I put it somewhere in my lists
  • when I’m batching books to get the free delivery, I pick from my “to buy” list
  • when I’m planning to go to chapters/indigo, I print out a list of books to check out

Because it’s multi-user:

  • I can check who read what books, maybe to get a recommendation, maybe to borrow them
  • you can get a feeling from people based on the books they read

I’m going to let people use it for a while and see where this goes.

What now?

Read Full Post »

I’ve been playing with autotest for my latest Rails project, and it’s great.

However, if you’re doing a “Ruby without Rails” project and your directory structure is different from the established conventions, you’re out to reverse engineer autotest itself and find outdated recommendations on Google.

It doesn’t have to be this way, here’s my .autotest:


require ‘autotest/redgreen’
require ‘autotest/timestamp’

# quit on first CTRL-c
Autotest.add_hook(:interrupt) do |at|
  at.wants_to_quit = true
end

# run all tests for ANY file
Autotest.add_hook(:initialize) do |at|
  at.clear_mappings
  at.add_mapping(/.*/) do |filename, _|
    Dir.glob(‘**/test_*.rb’)
  end
end

 
Enjoy!

Read Full Post »

Ruby Streams

I’ve been thinking about my business days problem some more. And this time I turned to Ruby, a language which I am becoming quite familiar with.

This is my naive implementation of streams in Ruby. Streams are thus functions. The actual class name is ‘Proc’, a lambda returns a ‘Proc’. Each call returns a new value.


def stream_from(value)
  lambda {value = value.succ}
end

class Proc
  def filter
    f = lambda do
          value = self.call
          return (yield(value) ? value : f.call)
        end
  end

  def take(n)
    result = []
    n.times {result << self.call}
    return result
  end
end

As for “filter”, it takes a predicate passed as block. The filter becomes a “decorator” for the stream.

This is a nice example of closures. The ‘value’ parameters becomes the state for the stream. Also of note, the use of the variable ‘f’ to fake recursion of an anonymous function.

Prints the 20 next business days:


s = stream_from(Date.today).
      filter {|date| date.cwday < 6}
puts s.take(20)

Note that my “contract” with stream_from is that what I pass it implements succ. Here’s an example with integer:


s = stream_from(4)
puts s.take(20)

And, of course, filters can be chained. The next Monday that falls on a 17th:


s = stream_from(Date.today).
      filter {|date| date.day == 17}.
      filter {|date| date.cwday == 1}
puts s.call.to_s

Here’s the same code as before, but with a function cast as a block:


def monday?(date)
  date.cwday == 1
end

s = stream_from(Date.today).
      filter {|date| date.day == 17}.
      filter(&method(:monday?))

I decided consciously to avoid optimizations here. It did keep my code short and clean. Besides, you should only optimize based on need.

There’s much more to streams… combining streams together, maps, more complicated generation of the next value, etc.

So, here’s your next interview questions:

“What’s the first 3 prime numbers after 5,000,000,000 which has a 3 as last digit?”


s = stream_from(5_000_000_000).
      filter{|x| x.prime?}.
      filter{|x| x.mod(10) == 3}
puts s.take(3)

I leave the code for “prime?” to your imagination.

The concept of command-line pipes springs to mind. One of the reason the command line is so powerful is that you can combine filters together to perform truly complicated tasks. Another reason is that each filter does only one thing and does it well.

It’s all about thinking in pipes.

Read Full Post »