Bogdan Kulbida - Personal blog.

Software Engineering Consultant

MiniTest Expectations

Here are MiniTest::Spec’s key expectations that I use often.

positive_expectations.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  obj.must_be(operator, expected)       - for example, 10.must_be :< , 11
  obj.must_be_close_to                  - the equivalent of assert_in_delta
  obj.must_be_empty                     - Fails unless obj.empty?
  obj.must_be_instance_of(klass)        - Fails unless obj.class == klass
  obj.must_be_kind_of(klass)            - Fails unless obj is of class klass or klass is one of its superclasses.
  obj.must_be_nil
  obj.must_be_same_as                   - tests for true object equality
  lambda {}.must_be_silent
  obj.must_be_within_delta
  obj.must_be_within_epsilon
  obj.must_equal(other)                 - Does a ==/eql? comparison between two objects.
  obj.must_include(other)
  obj.must_match(regex)                 - A regular expression match, e.g. "hello".must_match /w+/
  lambda {}.must_output(stdout, [stderr..]) - The block should have certain output on stdout or stderr. Set stdout to nil just to check stderr.
  lambda {}.must_raise(exception)
  obj.must_respond_to(message)
  obj.must_throw(sym)

Opposite ones:

negative_expectations.rb
1
2
3
4
5
6
7
8
9
10
  obj.wont_be
  obj.wont_be_empty
  obj.wont_be_instance_of
  obj.wont_be_kind_of
  obj.wont_be_nil
  obj.wont_be_same_as
  obj.wont_equal
  obj.wont_include
  obj.wont_match
  obj.wont_respond_to

As you can see MiniTest adds much power to your tests and it supports spec-like DSL.

How to Refactor Fat Class or a Module in Ruby and Rails

This is a blog post about how to refactor a fat classe (or module) in ruby application. Some solution I come up with when working on some heavy project.

Just to be clear, I’m not going to encourage you to use this approach. You may use it as is or you can try to extend it and make it better. Comments are welcome.

Every good programmer loves well structured code, tryes to be DRY as much as possible and hates heavy methods with bunch of lines of crappy code.

All this heavy code slows us down when we try to maintain or fix or add new functionality to the application. Did you ever try to find a bug in the code like this one?

heavy_class.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class HeavyClass < ActionController::Base

  # ...

  def verify_request
    is_valid, msg = validate_request(params['t'], params['q'])
    status_code = is_valid ? 200 : 500
    status_code = 306 if !is_valid && msg == 306 #if they are trying to register using a WineAgent email, notify them they must contact us
    render(:update, :status => status_code){|page|
      page << msg if !is_valid
    }
  end

  # ...

private

  def validate_request(type, q)
    is_valid, msg = case params['t']
      when 'email' then
        return [true, nil] if session[:wc2010_user]
        if EmailVeracity::Address.new(params['q']).valid?
          r = if !Person.loginable.exists?(:email => params['q'])
            [true, nil]
          else
            [false, t(:'signup.validate_request.email.already_taken')]
          end
          if User.email_domain_is_blacklisted(params['q'])
            r = [false, t(:'signup.validate_request.email.blacklisted')]
          end
          r
        else
          [false, t(:'signup.validate_request.email.invalid')]
        end
      when 'password' then
        u = User.new(:updating_password => true)
        u.password = u.password_confirmation = params['q']
        valid = u.check_password
        if !u.errors.get(:password).blank?
          [valid, u.errors.get(:password).join(',')]
        else
          [valid, u.errors.get(:password)]
        end
      when 'postal_code' then
        zip_code = User::format_zip_code(params['q'])
        u = Person.first
        u.zip_code = params['q']
        u.valid?
        [u.errors.get(:postal_code).nil?, 'Enter a valid Postal Code']
      when 'user_name' then
        u = Person.first
        u.user_name = params['q']
        u.valid?
        msg = u.errors.get(:user_name)
        msg = msg.kind_of?(Array) ? msg.first : msg
        [u.errors.get(:user_name).nil?, "Screen name #{msg}"]
      when 'promotion_code' then
        [params['q'].blank? || (PromoCode::does_exist?(params['q']) || FirstInLineSubscriber::does_exist?(params['q'])), 'You have entered an invalid Promo Code']
      else
        [false, nil]
    end
    return [is_valid, msg]
  end
end

It is terrible and makes me wanna throw my laptop away and make some delicious cakes…

The main adea is to create classes that will follow Single Responsobility Principle (in ideal) and will handle all this functionality.