Sergey Potapov

Talking about Linux, Ruby and other hackers' stuff.

Lazy Object Pattern in Ruby

I few days ago my colleague Arthur Shagall reviewing my code suggested me to use Lazy Object pattern to postpone some calculations during the load time. I hadn’t heard about the pattern before and even googling it didn’t give my much information. So I have decided to write this article to cover the topic.

Intention

Lazy Object allows you to postpone some calculation until the moment when the actual result of the calculation is used. That may help you to speed up booting of the application.

Implementation

It is pretty simple. We create a proxy object that takes a calculation block as its property and execute it on first method call.

1
2
3
4
5
6
7
8
9
10
11
12
13
class LazyObject < ::BasicObject
  def initialize(&callable)
    @callable = callable
  end

  def __target_object__
    @__target_object__ ||= @callable.call
  end

  def method_missing(method_name, *args, &block)
    __target_object__.send(method_name, *args, &block)
  end
end

Usage example 1

A constant assignment like this:

1
SQUARES = Array.new(10) { |i| i** 2}

Could be converted to this one:

1
SQUARES = LazyObject.new { Array.new(10) { |i| i** 2} }

So now if you want to use SQUARES it still behaves like an array:

1
2
3
SQUARES.class  # => Array
SQUARES.size   # => 10
SQUARES        # => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Usage example 2

Let’s say you have models State and Address in you Rails application. What you want do is to validate inclusion of address.state in states.

You can just hardcore the list of states:

1
2
3
4
5
class Address < ::ActiveRecord::Base
  STATES = ["AL", "AK", "AZ", "AR", "CA", "CO"]   # and so on

  validates :state, inclusion: { in: STATES }
end

But it does not reflect your changes in DB in any way.

Then you can fetch the values from DB:

1
STATES = State.all.map(&:code)

It seems to look better, but there are 2 possible pitfalls:

  • It increases load time (1 more SQL query)
  • It may cause real troubles if STATES is initialized before State model is seeded. In this case STATES will be empty.

So that is the situation where Lazy Object is useful:

1
STATES = LazyObject.new { State.all.map(&:code) }

Ruby gem

If your prefer to have it as a ruby gem, please take a look at rubygems.org/gems/lazy_object.

Thanks for reading!

Ignore Files With Git Locally

Sometimes it’s necessary to ignore some files in a repository only locally. For rails developers it’s often ./config/database.yml file. Every developer has his own database configuration.

With git it can be easily achieved, we may instruct git no to track changes in certain files:

1
git update-index --assume-unchanged ./config/database.yml

Next time we type git status the changes in ./config/database.yml won’t be shown.

If you think you need to track that file again, just do:

1
git update-index --no-assume-unchanged ./config/database.yml

(pay attention to --no prefix).

Thanks!

How to Compare Audio in Ruby

Or how to implement sound_like RSpec matcher

The problem I’m trying to solve in this article is comparison of two audio files. We’ll figure out how to verify that they sound similar.

I was developing an application that has a deal with audio processing and I had to write a test to verify outcome audio file matches a one from fixtures. Well, I’ve decided to compare audio binaries like these:

1
expect(File.read('outcome.mp3')).to eq File.read('fixture.mp3')

And it worked!

But soon my colleagues let me know I had broken the build. It turned out that outcome.mp3generated on their Mac books didn’t match fixture.mp3 generated on my linux laptop, despite the fact that both sounded absolutely the same. Probably we had different codecs. So I had to come up with a better idea.

Working With Fonts in Debian and Ubuntu

Install fonts

There are a lot of fonts in standard Debian repository. Packages which contains fonts starts with fonts-, so lets install them all. Run the next command as root:

1
apt-cache search ^fonts- | sed 's/^\(fonts-[^ ]*\).*$/\1/' | xargs apt-get install

Short explanation of the command:

  • apt-cache search ^fonts- - find all packages which starts with fonts-;
  • sed 's/^\(fonts-[^ ]*\).*$/\1/' - filter output to get only package names;
  • xargs apt-get install - pass package names to apt-get install to install them.

Preview fonts

Now you have more than 1500 fonts, but it’s hard to pick one that you need, because it’s hard to look through all of them. For our luck there exist specials to preview fonts, and one of is called fontmatrix. Lets install it:

1
apt-get install fontmatrix

And run it:

1
fontmatrix

Fontmatrix

Now it’s much easier to select right font!:)

How to Call Bash(not Shell) From Ruby

Few days ago I was writing a ruby wrapper for SoX command line tool. To reduce disk IO I wanted to use process substitution. It’s a cool shell feature which allows to use command output as an input file for another command. It’s pretty useful if the second command doesn’t work with standard input or you need to pass more than 1 input.

Let me show the classic example(works in bash and zsh):

1
2
cat <(echo 'Saluton!') <(echo 'Kiel vi fartas?')
# => Saluton! Kiel vi fartas?

So statement <(echo 'Saluton!') is treated like a file which contains line Saluton!. Underhood bash(zsh) creates a named pipeline where output of echo 'Saluton!' is written. Then the named pipeline is passed to cat command.

You can see it:

1
2
echo  <(echo 'Saluton!')
# => /dev/fd/63

So I wanted to use it in ruby:

1
2
cmd = "cat <(echo 'Saluton!') <(echo 'Kiel vi fartas?')"
system(cmd)

But unfortunately it doesn’t work:

1
sh: 1: Syntax error: "(" unexpected

The problem is that ruby’s system method and back quotes usesh not your current shell (which in my case is bash).

1
2
system "echo $0"
# => sh

In shells $0points to the current script or to interpreter if you’re running it interactively.

Fortunately there is a way to create a workaround to run bash:

1
2
3
4
5
6
require 'shellwords'

def bash(command)
  escaped_command = Shellwords.escape(command)
  system "bash -c #{escaped_command}"
end

Bash has option -c which takes bash script to execute. Shellwords is a standard ruby library which provides a method to escape shell commands.

So now it works as we want it to be:

1
2
3
bash("echo $0")  # => bash
cmd = "cat <(echo 'Saluton!') <(echo 'Kiel vi fartas?')"
bash(cmd)        # => Saluton! Kiel vi fartas?

Thanks for reading!

Validation in Rails With Themis

Sometimes ActiveRecord is not enough to meet complicated validation needs. At TMXCredit we’ve created Themis - ActiveRecord extension which helps to organize validations in a better way and adds some flexibility. Here I’m gonna describe some problems which Themis solves after that I’ll take a brief look at possible alternative solutions.

Modular validation

Themis allows you to extract duplicated validations into module for reuse. Usually rails applications are small enough so you don’t need it. But sometimes you do.

The next example is pretty flat(in real life you probably would use STI or composition to represent Doctor and Patient models) but it illustrates where Themis could be useful.

Let’s say you have 2 models:

1
2
3
4
5
6
7
8
9
class Doctor < ActiveRecord::Base
  validates :first_name, :last_name, :email, :diploma,
            :presence => true
end

class Patient < ActiveRecord::Base
  validates :first_name, :last_name, :email, :age,
            :presence => true
end

You see that both models have the same validation for first_name, last_name and email.

Themis allows you to fix the duplication problem by extracting common validations into a module:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Module with common validations.
module PersonValidation
  extend Themis::Validation

  validates :first_name, :last_name, :email, :presence => true
end

class Doctor < ActiveRecord::Base
  # import validation of first_name, last_name, email
  include PersonValidation

  validates :diploma, :presence => true
end

class Patient < ActiveRecord::Base
  include PersonValidation

  validates :age, :presence => true
end

Почему я изучаю Эсперанто

Как вы знаете, я начал изучать Эсперанто, некоторым даже успел “съесть” мозг, но всё же большинство по-прежнему задают вопросы почему и для чего я это делаю. Из этого можно прийти к выводу, что либо я безумен, либо я понимаю что-то, что другие не могут понять. Поэтому я решил написать небольшую статью и постараться объясниться.

Install More Screensavers on Mate Desktop

I switched from Gnome3 to Mate desktop and noticed that I’m able to use only few sreensavers. If you’re using Mate (it’s default for Linux Mint) you might experience the same problem.

There is a workaround how to use much more screensavers with Mate as usually able to do with Gnome.

Install packages with additional screensavers:

1
apt-get install xscreensaver-data-extra xscreensaver-gl-extra

Go to /usr/share/applications/screensavers directory:

1
cd /usr/share/applications/screensavers

There are located number of .desktop files. You should edit them by replacing the line

1
OnlyShowIn=GNOME;

with

1
OnlyShowIn=GNOME;MATE;

.

Obviously it’s a routine to change them all manually. So use sed tool to edit all files in once:

1
find . -name '*.desktop' | xargs sed -i 's/OnlyShowIn=GNOME;/OnlyShowIn=GNOME;MATE;/'

Now you are able to use more than hundred screensavers on Mate!

Failed to Add New Printer in Debian Wheezy

After migrating to Debian Wheezy (current test Debian repository) I faced a problem with installing a local USB printer: on attempt to add new USB printer (HP LaserJet M1005) I got an error message: “Failed to add new printer in Debian Wheezy” (“Не удалось добавить новый принтер” if you have Russian localization).

After googling for I a while I found a good workaround for it. All you need is to use pk-helper package from Sid (unstable Debian version), since it seems to be buggy in Wheezy.

So add Sid repository to your /etc/apt/source.list:

1
deb http://ftp.ua.debian.org/debian/ sid main non-free contrib

And reinstall cups-pk-helper package using Sid repository:

1
aptitude reinstall cups-pk-helper --target sid

That’s all. Now try to connect your USB printer. Btw, there is the bug report.

Rational Can’t Be Coerced Into BigDecimal in Ruby 1.9.3

Trying to move a rails application from ruby 1.8.7 to 1.9.3 I ran into coercion issue of Rational class.

Ruby 1.9.3:

1
2
3
4
5
6
7
8
require 'bigdecimal'
require 'rational'

# You can multiply Rational against BigDecimal
Rational(1) * BigDecimal('1')  # => <BigDecimal:a566d0,'0.1E1',9(36)>

# But you can't do the same when you change order
BigDecimal('1') * Rational(1)  # => TypeError: Rational can't be coerced into BigDecimal

On other hand in Ruby 1.8.7:

1
2
3
4
5
6
7
8
require 'bigdecimal'
require 'rational'

# BigDecimal * Rational works OK
BigDecimal('1') * Rational(1)  # => 1.0 (Float)

# But Rational * BigDecimal doesn't
Rational(1) * BigDecimal('1')  # => TypeError: Rational can't be coerced into BigDecimal

It’s looks weird. So I can only say for sure that Rational -> BigDecimal coercion is not implemented in Ruby.

I’ve tried to fix it with simple monkey patch:

1
2
3
4
5
6
7
8
9
10
11
# Works only for Ruby1.9.3
class Rational
  def coerce(value)
    case value
    when BigDecimal
      return self, value
    else
      super
    end
  end
end

And it works OK against simple examples:

1
2
Rational(1) * BigDecimal('1')  # => <BigDecimal:a566d0,'0.1E1',9(36)>
BigDecimal('1') * Rational(1)  # => <BigDecimal:a566d0,'0.1E1',9(36)>

But it causes intermittent segmentation faults when I run Rails application.

Any ideas about the coercion? Is it expected behaviour of ruby1.9.3? I found no bugs reported this issue on https://bugs.ruby-lang.org.

I’ll appreciate any feedback. Thanks.