rails | Dmytro Shteflyuk's Home https://kpumuk.info In my blog I'll try to describe about interesting technologies, my discovery in IT and some useful things about programming. Mon, 07 Sep 2015 23:10:33 +0000 en-US hourly 1 https://wordpress.org/?v=6.7.1 My top 7 RSpec best practices https://kpumuk.info/ruby-on-rails/my-top-7-rspec-best-practices/ https://kpumuk.info/ruby-on-rails/my-top-7-rspec-best-practices/#comments Wed, 25 Nov 2009 16:22:31 +0000 http://kpumuk.info/?p=1080 I use RSpec in all my projects. It’s really hard to overemphasize how helpful it is and how much easier becomes your life if you have good specs coverage. But its outstanding flexibility enables many ways to make your specs awful: horribly slow, over-bloated, even non-readable sometimes. I do not want to teach you BDD […]

The post My top 7 RSpec best practices first appeared on Dmytro Shteflyuk's Home.]]>
I use RSpec in all my projects. It’s really hard to overemphasize how helpful it is and how much easier becomes your life if you have good specs coverage. But its outstanding flexibility enables many ways to make your specs awful: horribly slow, over-bloated, even non-readable sometimes. I do not want to teach you BDD and RSpec here, but instead I will give you some ideas how to improve your specs quality and increase efficiency of your BDD workflow.

1. Use before :all block carefully

Sometimes it looks like a good idea to create a test data in before :all block. But be careful — these blocks are not wrapped in a transaction, so the data will not be rolled back after the test. In this case you should clear your data in the after :all block manually.

1
2
3
4
5
6
7
8
9
10
11
12
13
describe Friendship do
  before :all do
    @users = (1..5).collect { Factory(:user) }
  end

  after :all do
    @users.each { |user| user.destroy! }
  end

  it 'should do something' do
    # Something interesting with @users
  end
end

Another option is to move your before :all blocks to before :each to make them rolled back automatically.

2. For each test create exactly what it needs

Fixtures are cool when you start working on a project. But they quickly become painful while project grows: you add a new field to a fixture and break a half of your tests. There are tons of plugins which could simplify test data creation, I personally recommend factory_girl: it’s pretty slick and easy to use.

1
2
3
4
5
6
7
8
Factory.define :user do |f|
  f.sequence(:login) { |n| "user#{n}" }
  f.email { |a| "#{a.login}@example.com" }
  f.description "Ruby on Rails Developer"
end

# Somewhere in specs
@user = Factory(:user, :admin => true)

3. Do not create hundreds of records for a particular spec

Sometimes you want to test a method which operates on large set of records (filtering, trimming, etc). For example, this method returns 50 most popular videos (and no more). The straight approach is to create 51 record and make sure, that the size of the returned array is 50. When I saw a code snippet like this in our project first time, I was surprised. There was a few more pieces sharing this behavior, so here is my advice: add a parameter to the method, which will limit the number of records to return. In this case you can create 3 records, and pass 2 as a parameter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
describe User do
  it 'should return top users in User.top method' do
    @users = (1..3).collect { Factory(:user) }
    top_users = User.top(2).all
    top_users.should have(2).entries
  end
end

class User < ActiveRecord::Base
  # Select N top users. Returns 10 entries when called without arguments.
  #   User.top.all.size    # => 10
  #   User.top(2).all.size # => 2
  #
  named_scope :top, lambda { |*args| { :limit => (args.size > 0 ? args[0] : 10) } }
end

4. Do not over-mock

Mocking is interesting and sometimes very useful technology. You may mock just everything so you spec will not hit the database. But there is a catch: your model code may be changed some day causing callers to break. Since you mock everything, you will never get failing specs. So now you should update all your mocks to fit a new interface. Also you would not be able to find SQL queries errors if you have mocked them. Instead of this I use integration approach: controller should talk to models, which have to hit the database. Real database with real data (OK, not so real). The practice 2 can help you in test data creation.

Bad:

1
2
3
4
5
6
7
8
9
10
11
12
describe VideosController do
  describe '.create action' do
    it 'should assign top videos' do
      params = { :title => 'new video', :description => 'video description' }
      @video = mock_model(Video)
      Video.should_receive(:new).and_return(@video)
      @video.should_receive(:update_attributes).with(params).and_return(true)
      get :index, :video => params
      assigns[:video].should be(@video)
    end
  end
end

Good:

1
2
3
4
5
6
7
8
9
10
11
describe VideosController do
  describe '.create action' do
    it 'should assign top videos' do
      params = { :title => 'new video', :description => 'video description' }
      get :index, :video => params
      assigns[:video].should_not be_new_record
      assigns[:video].title.should == params[:title]
      assigns[:video].description.should == params[:description]
    end
  end
end

But you can use mocks to skip records retrieving from the database (make sure you have specs covering corresponding model code). Let me explain this. For example, you need to render 20 entries in an RSS feed. You could create 21 record in the database using a factory, and then ensure only 20 of them were retrieved, or you could mock your finder method and check its parameter. You may not like magic numbers like 20 in this particular case, and this is a good point. Just move this magic number to the config and ensure it was used to do the retrieval.

1
2
3
4
5
6
7
8
9
10
describe VideosController do
  describe '.index action' do
    it 'should assign top videos' do
      @videos = [mock_model(Video), mock_model(Video)]
      Video.should_receive(:top).with(50).and_return(@videos)
      get :index
      assigns[:top_videos].should be(@videos)
    end
  end
end

5. Use contexts

RSpec spec specifies how particular code should work. Usually, in the beginning you tell what you are going to describe in this spec, and inside describe block you specify what the code should do:

1
2
3
4
5
describe Video do
  it 'should return 5 records in Video.top method' do
    Video.top.should have(5).items
  end
end

Usually you have more than one it block for each method. To group related specs I recommend to use nested describe blocks. Since describe is aliased to context when placed inside another describe, I think it’s a good idea to use it for specs grouping. Each context may have its own before and after blocks (in this case parent blocks will be called right before child ones).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
describe Video do
  describe '.top' do
    it 'should return 5 records' do
      Video.top.should have(5).items
    end
  end

  context 'when just created' do
    before :each do
      @video = Video.new
    end
   
    # ...
  end
end

6. Create several test suites to speed up your workflow

There are many things you can do to make your BDD more efficient. We will take a look at two of them: creating a separate test suites and running recently modified specs.

There are several standard test suites configured in RSpec by default: spec:controllers, spec:views, spec:helpers, spec:lib. Check the rake -T spec output to get a list of available RSpec tasks. Let’s create a simple Rake tasks generator for spec suites:

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
SPEC_SUITES = [
  { :id => :acl, :title => 'access control', :files => %w(spec/controllers/**/acl_spec.rb) },
  { :id => :amazon, :title => 'Amazon libraries', :dirs => %w(spec/lib/amazon) }
]

namespace :spec do
  namespace :suite do
    SPEC_SUITES.each do |suite|
      desc "Run all specs in #{suite[:title]} spec suite"
      Spec::Rake::SpecTask.new(suite[:id]) do |t|
        spec_files = []
        if suite[:files]
          suite[:files].each { |glob| spec_files += Dir[glob] }
        end

        if suite[:dirs]
          suite[:dirs].each { |glob| spec_files += Dir["#{glob}/**/*_spec.rb"] }
        end

        t.spec_opts = ['--options', ""#{Rails.root}/spec/spec.opts""]
        t.spec_files = spec_files
      end
    end
  end
end

Check what tasks are available now:

1
2
3
4
5
~/test$ rake -T spec:suite

(in /Users/kpumuk/test)
rake spec:suite:acl     # Run all specs in access control spec suite
rake spec:suite:amazon  # Run all specs in Amazon libraries spec suite

It was easy! And now let’s take a look at the Rake task for running the recently touched specs (last 10 minutes).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Grab recently touched specs
def recent_specs(touched_since)
  recent_specs = Dir['app/**/*'].map do |path|
    if File.mtime(path) > touched_since
      spec = File.join('spec', File.dirname(path).split("/")[1..-1].join('/'),
      "#{File.basename(path, ".*")}_spec.rb")
      spec if File.exists?(spec)
    end
  end.compact

  recent_specs += Dir['spec/**/*_spec.rb'].select do |path|
    File.mtime(path) > touched_since
  end.uniq
end

namespace :spec do
  desc 'Run all recent specs in spec directory touched in last 10 minutes'
  Spec::Rake::SpecTask.new(:recent) do |t|
    t.spec_opts = ['--options', ""#{RAILS_ROOT}/spec/spec.opts""]
    t.spec_files = recent_specs(Time.now - 10.minutes)
  end
end

And don’t forget to check autospec and watchr gems.

7. Stop spec_helper from being loaded multiple times

Just don’t do that. If you got a big project, there is a chance that the spec_helper will be required in a many different ways: File.expand_path, File.join, etc.,— which results in it being loaded several times and it slows down your test suite!

To avoid this, add the following code at the top of your spec_helper.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# figure out where we are being loaded from
if $LOADED_FEATURES.grep(/spec\/spec_helper\.rb/).any?
  begin
    raise "foo"
  rescue => e
    puts <<-MSG
  ===================================================
  It looks like spec_helper.rb has been loaded
  multiple times. Normalize the require to:

    require "spec/spec_helper"

  Things like File.join and File.expand_path will
  cause it to be loaded multiple times.

  Loaded this time from:

    #{e.backtrace.join("\n    ")}
  ===================================================
    MSG
  end
end

It will show where you’ve tried to load spec_helper from so you will be able to fix it immediately. Also there is an interesting snippet of code here, which will find and replace all wrong includes.

Conclusion

RSpec is not a silver bullet. You can have 100% coverage and fine-grained specs, but it does not mean your application is completely bug-free. Refactor your specs, increase your programming level, and refactor again. Write specs for any issue that you, your QAs or users have faced. And remember: do not over-mock.

Credits

This is my first article written completely in Google Wave in collaboration with several good Russian rubyists. Thank you all, guys. I want to acknowledge the editorial help of Roman Dmytrenko and Alexey Kovyrin. Robby Russell created a great picture illustrating how sexy is RSpec. And thank you all my readers for your attention.

Did you like this article? You should follow me in Twitter here.

The post My top 7 RSpec best practices first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/my-top-7-rspec-best-practices/feed/ 27
Weekly Link Dump #2 https://kpumuk.info/links/weekly-link-dump-2/ https://kpumuk.info/links/weekly-link-dump-2/#comments Tue, 22 Sep 2009 13:51:04 +0000 http://kpumuk.info/?p=1035 Time to post some interesting stuff I’ve found in Internet last week. Today we going to talk about CSS font stacks, Ruby structs, extract_options! method came from Active Support, bash scripting, software version control visualization. When I started posting links, I decided not to mention articles posted by Smashing Magazine, thoughtbot and other sites which […]

The post Weekly Link Dump #2 first appeared on Dmytro Shteflyuk's Home.]]>
Time to post some interesting stuff I’ve found in Internet last week. Today we going to talk about CSS font stacks, Ruby structs, extract_options! method came from Active Support, bash scripting, software version control visualization.

Guide to CSS Font Stacks: Techniques and Resources

CSS Font stacks are one of those things that elude a lot of designers. Many stick to the basic stacks Dreamweaver auto-recommends or go even more basic by just specifying a single web-safe font.

But doing either of those things means you’re missing out on some great typography options. Font stacks can make it possible to show at least some of your visitors your site’s typography exactly the way you intend without showing everyone else a default font. Read on for more information on using and creating effective font stacks with CSS.

When I started posting links, I decided not to mention articles posted by Smashing Magazine, thoughtbot and other sites which everyone reads. But this is totally awesome: a complete guide on font faces, list of font tools and articles about typography. I promise, I will not publish links from Smashing Magazine ever again.

Structs inside out

Today we’re back to normal blog mode, where each article stands for itself. Muppet Labs are closed and we will be continuing our journey across the Ruby universe starting with an indepth look at Ruby’s Struct class — Ruby’s Swiss army knife for structured data.

Struct can be used without any additional require statement — it’s just there. This means it comes with zero additional overhead during initial interpreter startup — one of the many advantage of using Struct. But first let’s look at the basics.

Great Ruby Struct class usage examples.

Inside Ruby on Rails: extract_options! from Arrays

How many times did you see a method call like the following one in your Rails application: my_method :arg1, :foo => true?

What makes my_method quite special is the ability to pass an arbitrary number of parameters (:arg1, :arg2…) followed by a list of keyword/value options.

This is made possible by a really helpful method provided by ActiveSupport called extract_options!. What this core extension does is to extract the options from the given set of arguments. When no options are available, the method returns a blank Hash.

Nice Ruby on Rails method I didn’t know.

Advanced Bash-Scripting Guide

The shell is a command interpreter. More than just the insulating layer between the operating system kernel and the user, it’s also a fairly powerful programming language. A shell program, called a script, is an easy-to-use tool for building applications by “gluing together” system calls, tools, utilities, and compiled binaries. Virtually the entire repertoire of UNIX commands, utilities, and tools is available for invocation by a shell script. If that were not enough, internal shell commands, such as testing and loop constructs, lend additional power and flexibility to scripts. Shell scripts are especially well suited for administrative system tasks and other routine repetitive tasks not requiring the bells and whistles of a full-blown tightly structured programming language.

Old good guide for the bash scripting. Must read for any developer.

gource — software version control visualization

Gource is a software version control visualization tool for Git and CVS.

Software projects are displayed by Gource as an animated tree with the root directory of the project at its centre. Directories appear as branches with files as leaves. Developers can be seen working on the tree at the times they contributed to the project.

This freaking awesome tool produces animated visualization of source code history based on Git or CVS reporsitory. Also take a look at the code_swarm project, which does very similar things.

PS. Did you notice a new threaded comments structure in this blog? Also I have rewritten theme layout a little, so there is HTML5 here now.

[lang_ru]
ЗЗЫ. Журналисты WebStream.com.ua взяли у меня интервью. Читаем здесь.
[/lang_ru]

The post Weekly Link Dump #2 first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/links/weekly-link-dump-2/feed/ 2
Scribd open source projects https://kpumuk.info/development/scribd-open-source-projects/ Tue, 08 Sep 2009 02:06:05 +0000 http://kpumuk.info/?p=918 It’s time to summarize what we have done for the Open Source community. Scribd is pretty open company, we release a lot of code into the public after a time (sometimes it is short, sometimes it is not). Here I want to mention all the code we have opensourced. Please take into account that time […]

The post Scribd open source projects first appeared on Dmytro Shteflyuk's Home.]]>
It’s time to summarize what we have done for the Open Source community. Scribd is pretty open company, we release a lot of code into the public after a time (sometimes it is short, sometimes it is not). Here I want to mention all the code we have opensourced. Please take into account that time is moving on, so we are publishing more and more code. I will update this post periodically, so stay tuned. Follow me on Twitter to get instant updates.

Table of Contents

Here is the list of our projects in alphabetical order:

  • bounces-handler — Email Bounces Processing System with Rails plugin to prevent Rails mailers from sending any messages to a blocked addresses.
  • db-charmer — ActiveRecord Connections Magic (slaves, multiple connections, etc).
  • easy-prof — Simple and easy to use Ruby code profiler, which could be used as a Rails plugin.
  • Fast Sessions — Sessions class for ActiveRecord sessions store created to work fast (really fast).
  • loops — Simple background loops framework for Ruby on Rails and Merb.
  • magic-enum — Method used to define ENUM-like attributes in your model (int fields actually).
  • rlibsphinxclient — A Ruby wrapper for pure C searchd client API library.
  • rscribd — Ruby client library for the Scribd API.
  • Rspec Cells — A library for testing applications that are using Cells in RSpec.
  • Scribd Desktop Uploader — A fully native Cocoa Macintosh uploader app for the Scribd.com website.

bounces-handler

Bounces-handler package is a simple set of scripts to automatically process email bounces and ISP’s feedback loops emails, maintain your mailing blacklists and a Ruby on Rails plugin to use those blacklists in your RoR applications.

This piece of software has been developed as a part of more global work on mailing quality improvement in Scribd.com, but it was one of the most critical steps after setting up reverse DNS records, DKIM and SPF.

Links: Project Home Page on GitHub | Introduction Blog Post | RDoc Documentation.

db-charmer

DbCharmer is a simple yet powerful plugin for ActiveRecord that does a few things:

  • Allows you to easily manage AR models’ connections (switch_connection_to method)
  • Allows you to switch AR models’ default connections to a separate servers/databases
  • Allows you to easily choose where your query should go (Model.on_db methods)
  • Allows you to automatically send read queries to your slaves while masters would handle all the updates.
  • Adds multiple databases migrations to ActiveRecord

It requires Ruby on Rails version 2.3 or later. The main purpose of this plugin is to put all the databases-related code we have been using in Scribd for a while into a single easy-to use package.

Links: Project Home Page on GitHub | Test Rails Application on GitHub | RDoc Documentation.

easy-prof

Simple and easy to use Ruby code profiler, which could be used as a Rails plugin. The main idea behind the easy-prof is creating check points and your code and measuring time needed to execute code blocks. Here is the example of easy-prof output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[home#index] Benchmark results:
[home#index] debug: Logged in user home page
[home#index] progress: 0.7002 s [find top videos]
[home#index] progress: 0.0452 s [build categories list]
[home#index] progress: 0.0019 s [build tag cloud]
[home#index] progress: 0.0032 s [find featured videos]
[home#index] progress: 0.0324 s [find latest videos]
[home#index] debug: VIEW STARTED
[home#index] progress: 0.0649 s [top videos render]
[home#index] progress: 0.0014 s [categories render]
[home#index] progress: 2.5887 s [tag cloud render]
[home#index] progress: 0.0488 s [latest videos render]
[home#index] progress: 0.1053 s [featured video render]
[home#index] results: 3.592 s

From this output you can see what checkpoints takes longer to reach, and what code fragments are pretty fast.

Links: Project Home Page on GitHub | Introduction Blog Post | RDoc Documentation.

Fast Sessions

FastSessions is a sessions class for ActiveRecord sessions store created to work fast (really fast). It uses some techniques which are not so widely known in developers’ community and only when they cause huge problems, performance consultants are trying to help with them.

FastSessions plugin was born as a hack created for Scribd.com (large RoR-based web project), which was suffering from InnoDB auto-increment table-level locks on sessions table.

So, first of all, we removed id field from the table. Next step was to make lookups faster and we’ve used a following technique: instead of using (session_id) as a lookup key, we started using (CRC32(session_id), session_id) — two-columns key which really helps MySQL to find sessions faster because key cardinality is higher (so, mysql is able to find a record earlier w/o checking a lots of index rows). We’ve benchmarked this approach and it shows 10–15% performance gain on large sessions tables.

And last, but most powerful change we’ve tried to make was to not create database records for empty sessions and to not save sessions data back to database if this data has not been changed during current request processing. With this change we basically reduce inserts number by 50-90% (depends 0n application).

All of these changes were implemented and you can use them automatically after a simple plugin installation.

There is a fork patched by mudge for full compatibility with Ruby on Rails version 2.3 or later.

Links: Project Home Page on Google Code | Introduction Blog Post | Fork on GitHub Compatible with Rails 2.3 and Later.

loops

loops is a small and lightweight library for Ruby on Rails, Merb and other frameworks created to support simple background loops in your application which are usually used to do some background data processing on your servers (queue workers, batch tasks processors, etc).

Originally loops plugin was created to make our own loops code more organized. We used to have tens of different modules with methods that were called with script/runner and then used with nohup and other not so convenient backgrounding techniques. When you have such a number of loops/workers to run in background it becomes a nightmare to manage them on a regular basis (restarts, code upgrades, status/health checking, etc).

After a short time of writing our loops in more organized ways we were able to generalize most of the loops code so now our loops look like a classes with a single mandatory public method called run. Everything else (spawning many workers, managing them, logging, backgrounding, pid-files management, etc) is handled by the plugin itself.

Links: Project Home Page on GitHub | Introduction Blog Post | RDoc Documentation.

magic-enum

Method used to define ENUM-like attributes in your model (int fields actually). It’s easier to show what it does in code rather than to explain in plain English:

1
2
3
4
5
6
7
Statuses = {
  :unknown => 0,
  :draft => 1,
  :published => 2,
  :approved => 3
}
define_enum :status, :default => 1, :raise_on_invalid => true, :simple_accessors => true

is identical to

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
Statuses = {
  :unknown => 0,
  :draft => 1,
  :published => 2,
  :approved => 3
}
StatusesInverted = Statuses.invert

def status
  StatusesInverted[self[:status].to_i] || StatusesInverted[1]
end

def status=(value)
  raise ArgumentError, "Invalid value "#{value}" for :status attribute of the #{self.class} model" if
  Statuses[value].nil?
  self[:status] = Statuses[value]
end

def unknown?
  status == :unknown
end

def draft?
  status == :draft
end

def published?
  status == :published
end

def approved?
  status == :approved
end

This plugin was originally developed for Best Tech Videosand later was cleaned up in Scribd repository and released to the public.

Links: Project Home Page on GitHub | RDoc Documentation.

rlibsphinxclient

A Ruby wrapper for pure C searchd client API library. It works much faster than any Ruby client for Sphinx, so you can check it to ensure you application works as fast as possible.

Please note: this is *highly experimental* library so use it at your own risk.

Links: Project Home Page on GitHub | RDoc Documentation.

rscribd

Ruby client library for the Scribd API. This gem provides a simple and powerful library for the Scribd API, allowing you to write Ruby applications or Ruby on Rails websites that upload, convert, display, search, and control documents in many formats. For more information on the Scribd platform, visit the Scribd Platform Documentation page.

The main features are:

  • Upload your documents to Scribd’s servers and access them using the gem
  • Upload local files or from remote web sites
  • Search, tag, and organize documents
  • Associate documents with your users’ accounts

Links: Project Home Page on GitHub | Scribd Platform Documentation | RDoc Documentation.

Rspec Cells

This plugin allows you to test your cells easily using RSpec. Basically, it adds an example group especially for cells, with several helpers to perform cells rendering.

If you are not sure what is cells, please visit its home page.

Spec for a regular cell could look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
describe VideoCell do
    integrate_views

    context '.videos' do
      it 'should initialize :videos variable' do
        params[:id] = 10
        session[:user_id] = 20
        opts[:opt] = 'value'
        result = render_cell :videos, { :videos => [] }, :slug => 'hello'
        result.should have_tag('div', :class => :videos)
      end
    end
  end

Links: Project Home Page on GitHub | Cells Home Page | Cells Home Page on GitHub.

Scribd Desktop Uploader

A fully native Cocoa Macintosh uploader app for the Scribd.com website. Supports following features:

  • Upload many files at once from your desktop.
  • Edit titles, tags, and other metadata before uploading.
  • Quickly and easily manage bulk uploads, straight from your desktop.
  • Right-click to start uploading files directly to Scribd (Windows only).

Links: Project Home Page on GitHub | Home Page on Scribd.com.

Changelog

  • September 9, 2009
    • Fixed a link to GitHub homepage of the rspec-cells plugin.
  • September 8, 2009
The post Scribd open source projects first appeared on Dmytro Shteflyuk's Home.]]>