RSpec | 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. Tue, 08 Sep 2015 00:24:41 +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
Simplifying your Ruby on Rails code: Presenter pattern, cells plugin https://kpumuk.info/ruby-on-rails/simplifying-your-ruby-on-rails-code/ https://kpumuk.info/ruby-on-rails/simplifying-your-ruby-on-rails-code/#comments Wed, 09 Sep 2009 05:41:14 +0000 http://kpumuk.info/?p=937 Today we will talk about code organization in Ruby on Rails projects. As everybody knows, Ruby on Rails is a conventional framework, which means you should follow framework architects’ decisions (put your controllers inside app/controllers, move all your logic into models, etc.) But there are many open questions around those conventions. In this write-up I […]

The post Simplifying your Ruby on Rails code: Presenter pattern, cells plugin first appeared on Dmytro Shteflyuk's Home.]]>
Today we will talk about code organization in Ruby on Rails projects. As everybody knows, Ruby on Rails is a conventional framework, which means you should follow framework architects’ decisions (put your controllers inside app/controllers, move all your logic into models, etc.) But there are many open questions around those conventions. In this write-up I will try to summarize my personal experience and show how I usually solve these problems.

Here is the list of questions we will talk about:

  1. You have some logic in your view, which uses your models extensively. There are no places in other views with such logic. The classic recommendation is to move this code into a model, but after a short time your models become bloated with stupid one-off helper methods. The solution: pattern Presenter.

  2. Your constructor contains a lot of code to retrieve some values for your views from the database or another storage. You have a lot of fragment_exist? calls to ensure no of your data is loaded when corresponding fragment is already in cache. It’s really hard to test a particular action because of it’s size. The solution: pattern Presenter.

  3. You have a partial, used everywhere on the site. It accepts a lot of parameters to configure how rendered code should look like. The header of this partial, which initializes default values of parameters, becomes larger and larger. The solution: cells plugin.

Please note: sample application is available on GitHub.

Presenter Pattern

Okay, you have an idea when to use this patterns. Let’s look at the example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class HomeController < ApplicationController
  def show
    unless fragment_exist?('home/top_videos')
      @top_videos = Video.top.all(:limit => 10)
    end
   
    unless fragment_exist?('home/categories')
      @categories = Category.all(:order => 'name DESC')
    end
   
    unless fragment_exist?('home/featured_videos')
      @featured_videos = Video.featured.all(:limit => 5)
    end

    unless fragment_exist?('home/latest_videos')
      @latest_videos = Video.latest.all(:limit => 5)
    end
  end
end

And the view:

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
<h1>Home page</h1>

<div id="top_videos">
    <h2>Top videos</h2>
    <% cache('home/top_videos') do %>
        <%= render 'videos', :videos => @top_videos, :hide_description => true %>
    <% end %>
</div>

<div class="tabs">
    <ul id="taxonomy">
        <li><a href="#" id="categories" class="current">Categories</a></li>
    </ul>
    <div class="categories_panel">
        <h2>Categories</h2>
        <% cache('home/categories') do %>
            <%= render 'categories' %>
        <% end %>
    </div>
</div>

<div class="box">
    <div id="latest">
        <h2>Latest videos</h2>
        <% cache('home/latest_videos') do %>
            <%= render 'videos', :videos => @latest_videos, :hide_thumbnail => true %>
        <% end %>
    </div>
    <div id="featured">
        <h2>Featured videos</h2>
        <% cache('home/featured_videos') do %>
            <%= render 'videos', :videos => @featured_videos, :hide_thumbnail => true %>
        <% end %>
    </div>
</div>

Note: this code is available in the first commit of my presenter example project.

Scary code, isn’t it? So let’s refactor it using Presenter pattern. I prefer to put presenters into a separate folder app/presenters, so first we should add it to Rails load path. Add this line to your config/environment.rb:

1
2
3
config.load_paths += %W(
  #{Rails.root}/app/presenters
)

Now we are ready to write our presenter (app/presenters/home_presenters/show_presenter.rb):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module HomePresenters
  class ShowPresenter
    def top_videos
      @top_videos ||= Video.top.all(:limit => 10)
    end

    def categories
      @categories ||= Category.all(:order => 'name DESC')
    end
   
    def featured_videos
      @featured_videos ||= Video.featured.all(:limit => 5)
    end

    def latest_videos
      @latest_videos ||= Video.latest.all(:limit => 5)
    end
  end
end

Sometimes presenters depend on parameters, so feel free to add an initialize method. It could accept particular params or whole params collection:

1
2
3
def initialize(video_id)
  @video_id = video_id
end

Now let’s refactor our controller:

1
2
3
4
5
class HomeController < ApplicationController
  def show
    @presenter = HomePresenters::ShowPresenter.new
  end
end

Whoa, that’s nice! View now is little different:

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
<h1>Home page</h1>

<div id="top_videos">
    <h2>Top videos</h2>
    <% cache('home/top_videos') do %>
        <%= render 'videos', :videos => @presenter.top_videos, :hide_description => true %>
    <% end %>
</div>

<div class="tabs">
    <ul id="taxonomy">
        <li><a href="#" id="categories" class="current">Categories</a></li>
    </ul>
    <div class="categories_panel">
        <h2>Categories</h2>
        <% cache('home/categories') do %>
            <%= render 'categories' %>
        <% end %>
    </div>
</div>

<div class="box">
    <div id="latest">
        <h2>Latest videos</h2>
        <% cache('home/latest_videos') do %>
            <%= render 'videos', :videos => @presenter.latest_videos, :hide_thumbnail => true %>
        <% end %>
    </div>
    <div id="featured">
        <h2>Featured videos</h2>
        <% cache('home/featured_videos') do %>
            <%= render 'videos', :videos => @presenter.featured_videos, :hide_thumbnail => true %>
        <% end %>
    </div>
</div>

Presenters testing is much easier than testing of bloated controllers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
describe HomePresenters::ShowPresenter do
  before :each do
    @presenter = HomePresenters::ShowPresenter.new
  end
 
  it 'should respond to :top_videos' do
    expect { @presenter.top_videos }.to_not raise_error
  end

  it 'should respond to :categories' do
    expect { @presenter.categories }.to_not raise_error
  end

  it 'should respond to :featured_videos' do
    expect { @presenter.featured_videos }.to_not raise_error
  end

  it 'should respond to :latest_videos' do
    expect { @presenter.latest_videos }.to_not raise_error
  end
end

Please note: this code is available in the second commit of my presenter example project.

Please note: you should not do any manipulations on models in presenters. They only decorate models with helper methods to be used inside controllers or views, nothing else. There are several articles describing a Conductor pattern as a presenter, do not repeat their mistakes. See the first link in the list below to get an idea about the differences.

Related links:

Cells Plugin

Okay, now we have a clean controller. But what about views? Let’s take a look at the videos partial:

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
<%
 hide_thumbnail   = hide_thumbnail === true;
 hide_description = hide_description === true;
 css_class      ||= 'videos'
 style          ||= :div
 case style.to_sym
   when :section
     parent_tag = 'section'
     child_tag = 'div'
   when :list
     parent_tag = 'ul'
     child_tag = 'li'
   else
     parent_tag = 'div'
     child_tag = 'div'
 end
%>

<% content_tag parent_tag, :class => css_class do %>
  <% videos.each do |video| %>
    <% content_tag child_tag do %>
      <h3><%= h video.title %></h3>
      <%= image_tag(video.thumbnail_url, :class => 'thumb') unless hide_thumbnail %>
      <%= '<p>%s</p>' % h(video.description) unless hide_description %>
    <% end %>
  <% end %>
<% end %>

So, what the heck? Is this a view or a controller? Remember old PHP days, with all this spaghetti code? That is it. It’s hard to test, it looks scary, it bad. So here cells plugin comes to the stage.

First, we need to install the plugin:

1
script/plugin install git://github.com/apotonick/cells.git

Now let’s generate a cell:

1
script/generate cell Video videos

And write some code (app/cells/video.rb):

1
2
3
4
5
6
7
8
9
10
11
12
13
class VideoCell < Cell::Base
  def videos
    @videos = @opts[:videos]
    @hide_thumbnail = @opts[:hide_thumbnail] === true;
    @hide_description = @opts[:hide_description] === true;
    @css_class = @opts[:css_class] || 'videos'
   
    view = (@opts[:style] || :div).to_sym
    view = :div unless [:section, :list].include?(view)

    render :view => "videos_#{view}"
  end
end

app/cells/video/videos_section.html.erb:

1
2
3
4
5
6
7
<section class="<%= @css_class %>">
  <% @videos.each do |video| %>
    <div>
      <%= render :partial => 'video', :locals => { :video => video } %>
    </div>
  <% end %>
</section>

app/cells/video/videos_list.html.erb:

1
2
3
4
5
6
7
<ul class="<%= @css_class %>">
  <% @videos.each do |video| %>
    <li>
      <%= render :partial => 'video', :locals => { :video => video } %>
    </li>
  <% end %>
</ul>

app/cells/video/videos_div.html.erb:

1
2
3
4
5
6
7
<div class="<%= @css_class %>">
  <% @videos.each do |video| %>
    <div>
      <%= render :partial => 'video', :locals => { :video => video } %>
    </div>
  <% end %>
</div>

app/cells/video/_video.html.erb:

1
2
3
<h3><%= h video.title %></h3>
<%= image_tag(video.thumbnail_url, :class => 'thumb') unless @hide_thumbnail %>
<%= '<p>%s</p>' % h(video.description) unless @hide_description %>

And the view:

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
<h1>Home page</h1>

<div id="top_videos">
    <h2>Top videos</h2>
    <% cache('home/top_videos') do %>
        <%= render_cell :video, :videos, :videos => @presenter.top_videos, :hide_description => true %>
    <% end %>
</div>

<div class="tabs">
    <ul id="taxonomy">
        <li><a href="#" id="categories" class="current">Categories</a></li>
    </ul>
    <div class="categories_panel">
        <h2>Categories</h2>
        <% cache('home/categories') do %>
            <%= render 'categories' %>
        <% end %>
    </div>
</div>

<div class="box">
    <div id="latest">
        <h2>Latest videos</h2>
        <% cache('home/latest_videos') do %>
            <%= render_cell :video, :videos, :videos => @presenter.latest_videos, :hide_thumbnail => true %>
        <% end %>
    </div>
    <div id="featured">
        <h2>Featured videos</h2>
        <% cache('home/featured_videos') do %>
            <%= render_cell :video, :videos, :videos => @presenter.featured_videos, :hide_thumbnail => true %>
        <% end %>
    </div>
</div>

Wow! That’s pretty easy to read and modify. All the logic is in the code now, all the views are easy to read, and moreover: it’s more than easy to test now! I have a little plugin called rspec-cells, and I have committed a patch yesterday to get it working with the latest RSpec. Here is how you spec could look like:

1
2
3
4
5
6
7
8
9
describe VideoCell do
  context '.videos' do
    it 'should initialize :videos variable' do
      videos = mock('Videos')
      render_cell :videos, :videos => videos
      assigns[:videos].should be(videos)
    end
  end
end

So it looks almost like a classic Ruby on Rails controller spec. I hope to review the code in nearest feature and will send a pull request to the cells plugin author. Of course, if you
found a bug, feel free to contact me.

Please note: this code is available in the third commit of my presenter example project.

Related links:

That’s all I wanted to show you today. I think a Presenter Example project will be updated periodically, so follow it on GitHub, follow me in Twitter or on GitHub to get instant updates. Also take a look at the rspec-cells plugin, maybe you will have some time to make it better.

The post Simplifying your Ruby on Rails code: Presenter pattern, cells plugin first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/simplifying-your-ruby-on-rails-code/feed/ 25
Generating permalink from string in Ruby https://kpumuk.info/ruby-on-rails/generating-permalink-from-string-in-ruby/ https://kpumuk.info/ruby-on-rails/generating-permalink-from-string-in-ruby/#comments Mon, 14 May 2007 15:27:39 +0000 http://kpumuk.info/ruby-on-rails/generating-permalink-from-string-in-ruby/ If you are creating Ruby on Rails application like a blog, you most probably want to generate URLs using post titles. It’s good practice, because search engines like keywords in URL, and it looks more human-readable. Just compare: http://example.com/posts/10 and http://example.com/posts/generating-permalinks-from-string (yeah, it’s long, but self-descriptive). Anyways, this is small post about converting a title […]

The post Generating permalink from string in Ruby first appeared on Dmytro Shteflyuk's Home.]]>
If you are creating Ruby on Rails application like a blog, you most probably want to generate URLs using post titles. It’s good practice, because search engines like keywords in URL, and it looks more human-readable. Just compare: http://example.com/posts/10 and http://example.com/posts/generating-permalinks-from-string (yeah, it’s long, but self-descriptive). Anyways, this is small post about converting a title to a permalink.

First thing I love in Ruby is an ability to extend classes with my own methods. I could just add method to_permalink to any string and then everywhere I could write something like @post.title.to_permalink. It’s amazing!

Here is my version of to_permalink method:

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
class String
  def to_permalink
    result = strip_tags
    # Preserve escaped octets.
    result.gsub!(/-+/, '-')
    result.gsub!(/%([a-f0-9]{2})/i, '--\1--')
    # Remove percent signs that are not part of an octet.
    result.gsub!('%', '-')
    # Restore octets.
    result.gsub!(/--([a-f0-9]{2})--/i, '%\1')

    result.gsub!(/&.+?;/, '-') # kill entities
    result.gsub!(/[^%a-z0-9_-]+/i, '-')
    result.gsub!(/-+/, '-')
    result.gsub!(/(^-+|-+$)/, '')
    return result.downcase
  end

  private
 
    def strip_tags
      return clone if blank?
      if index('<')
        text = ''
        tokenizer = HTML::Tokenizer.new(self)

        while token = tokenizer.next
          node = HTML::Node.parse(nil, 0, 0, token, false)
          # result is only the content of any Text nodes
          text << node.to_s if node.class == HTML::Text
        end
        # strip any comments, and if they have a newline at the end (ie. line with
        # only a comment) strip that too
        text.gsub(/<!--(.*?)-->[\n]?/m, '')
      else
        clone # already plain text
      end
    end
end

How it’s working? First thing you would see is a private method strip_tags. Yes, I know about ActionView::Helpers::TextHelper::strip_tags, and this is almost 100% copy of Rails version (the only difference is that my version always returns clone of the original string). I just don’t want to rely on the Rails library.

Then my method replaces all special characters with dashes (only octets like %A0 would be kept), and trims dashed from the beginning and the end of the string. Finally full string will be lowercased.

Of course, in your application you should check collisions (several posts which have the same title should have unique permalinks, for example you could append numbers starting from 1: hello, hello-1, hello-2, etc). This is not my goal to cover all difficulties you could face, it’s small post, do you remember?

Just for your pleasure, here are the RSpec tests for this method:

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
describe 'String.to_permalink from extensions.rb' do
  it 'should replace all punctuation marks and spaces with dashes' do
    "!.@#$\%^&*()Test case\n\t".to_permalink.should == 'test-case'
  end

  it 'should preserve _ symbol' do
    "Test_case".to_permalink.should == 'test_case'
  end
 
  it 'should preserve escaped octets and remove redundant %' do
    'Test%%20case'.to_permalink.should == 'test-%20case'
  end

  it 'should strip HTML tags' do
    '<a href="http://example.com">Test</a> <b>case</b>'.to_permalink.should == 'test-case'
  end

  it 'should strip HTML entities and insert dashes' do
    'Test&nbsp;case'.to_permalink.should == 'test-case'
  end

  it 'should trim beginning and ending dashes' do
    '-. Test case .-'.to_permalink.should == 'test-case'
  end

  it 'should not use ---aa--- as octet' do
    'b---aa---b'.to_permalink.should == 'b-aa-b'
  end
 
  it 'should replace % with -' do
    'Hello%world'.to_permalink.should == 'hello-world'
  end

  it 'should not modify original string' do
    s = 'Hello,&nbsp;<b>world</b>%20'
    s.to_permalink.should == 'hello-world%20'
    s.should == 'Hello,&nbsp;<b>world</b>%20'

    s = 'Hello'
    s.to_permalink.should == 'hello'
    s.should == 'Hello'
  end
end

It’s funny, right?

The post Generating permalink from string in Ruby first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/generating-permalink-from-string-in-ruby/feed/ 5
Useful helpers for RSpec mocks https://kpumuk.info/ruby-on-rails/useful-helpers-for-rspec-mocks/ https://kpumuk.info/ruby-on-rails/useful-helpers-for-rspec-mocks/#comments Sat, 05 May 2007 00:38:39 +0000 http://kpumuk.info/rspec/useful-helpers-for-rspec-mocks/ RSpec has great feature — mock objects (mocks). In a few words: mock object imitates behavior of the object, which used by the tested methods. And simple example immediately: 12345678910describe UserHelper   it 'should generate correct link to user profile in user_link' do     @user = mock('User')     @user.stub!(:id, 10)     @user.stub!(:new_record?, […]

The post Useful helpers for RSpec mocks first appeared on Dmytro Shteflyuk's Home.]]>
RSpec has great feature — mock objects (mocks). In a few words: mock object imitates behavior of the object, which used by the tested methods. And simple example immediately:

1
2
3
4
5
6
7
8
9
10
describe UserHelper
  it 'should generate correct link to user profile in user_link' do
    @user = mock('User')
    @user.stub!(:id, 10)
    @user.stub!(:new_record?, false)
    @user.stub!(:preferred_name, 'Dmytro S.')
    @user.stub!(:full_name, 'Dmytro Shteflyuk')
    user_link(@user).should == link_to('Dmytro S.', user_url(:id => 10), :title => 'Dmytro Shteflyuk')
  end
end

Well, and what does it mean? Initially we are creating mock object @user, which would be used instead of real model. Please don’t ask me why do we need such complexity and why we can’t just use real model. I will explain it myself. On the one hand, mocks are much faster than the database operations — when you have many tests (and you have, right?) total tests execution time would be much smaller if we would use mock objects. On the other hand, you don’t want to test the same methods again and again, right? In my example preferred_name method has non-trivia logic (it’s not simple database field), and it has already been tested in model spec. Imagine that you are using model in helper tests. If this method would break down, two specifications will be failed instead of one — model specification. In addition, there is one interesting feature exists — rcov, which shows how your code is covered by tests. Thus if model tests would not exist, and helper tests would use model,– rcov will show preferred_name method as covered, but it is not true. Oops, I have been distracted.

Oh yes, if you don’t know, stub is just method, which does nothing, it just return value, which you have passed to it.

So we have a test. Could we simplify it somehow? Yep!

1
2
3
4
5
6
7
describe UserHelper
  it 'should generate correct link to user profile in user_link' do
    @user = mock('User')
    add_stubs(@user, :id => 10, :new_record? => false, :preferred_name => 'Dmytro S.', :full_name => 'Dmytro Shteflyuk')
    user_link(@user).should == link_to('Dmytro S.', user_url(:id => 10), :title => 'Dmytro Shteflyuk')
  end
end

Much better. Helper method add_stubs adds to the object, passed as a first parameter, stubs, passed as a second parameter in hash. But it’s not all. Especially for the Active Record models RSpec has method mock_model, which automatically creates several stubs, common for all Ruby on Rails models, and accepts hash with stubs just like add_stubs does:

1
2
3
4
5
6
describe UserHelper
  it 'should generate correct link to user profile in user_link' do
    @user = mock_model(User, :preferred_name => 'Dmytro S.', :full_name => 'Dmytro Shteflyuk')
    user_link(@user).should == link_to('Dmytro S.', user_url(:id => @user.id), :title => 'Dmytro Shteflyuk')
  end
end

You definitely noticed that I have missed parameters :id and :new_record?, and it was not coincidence. Firstly, mock_model automatically defines unique ids for models, which were created using it. Secondly, it defines methods to_param (returns string representation of the id) and new_record? (returns false). The first parameter of the method is a model class, and the second one is a hash of stubs, as I have already said. That’s all, folks.

The post Useful helpers for RSpec mocks first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/useful-helpers-for-rspec-mocks/feed/ 5
Testing mailers with RSpec https://kpumuk.info/ruby-on-rails/testing-mailers-with-rspec/ https://kpumuk.info/ruby-on-rails/testing-mailers-with-rspec/#comments Thu, 19 Apr 2007 05:42:22 +0000 http://kpumuk.info/rspec/testing-mailers-with-rspec/ Unfortunately, RSpec does not provide helpers for testing mailers like TestUnit. But it is easy to add them to your application, and here you could find code snippet for testing mailers. For the beginning we would define helper for reading fixtures and put them into the spec/mailer_spec_helper.rb file: 123456789require File.dirname(__FILE__) + '/spec_helper.rb' module MailerSpecHelper   […]

The post Testing mailers with RSpec first appeared on Dmytro Shteflyuk's Home.]]>
Unfortunately, RSpec does not provide helpers for testing mailers like TestUnit. But it is easy to add them to your application, and here you could find code snippet for testing mailers.

For the beginning we would define helper for reading fixtures and put them into the spec/mailer_spec_helper.rb file:

1
2
3
4
5
6
7
8
9
require File.dirname(__FILE__) + '/spec_helper.rb'

module MailerSpecHelper
  private

    def read_fixture(action)
      IO.readlines("#{FIXTURES_PATH}/mailers/user_mailer/#{action}")
    end
end

Now we need to create fixtures for mailers in the spec/fixtures/mailers folder, each mailer in the separate subfolder like spec/fixtures/mailers/some_mailer/activation:

1
Hello, Bob

In spec/models/mailers/some_mailer_spec.rb we would write something like:

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
require File.dirname(__FILE__) + '/../../mailer_spec_helper.rb'

context 'The SomeMailer mailer' do
  FIXTURES_PATH = File.dirname(__FILE__) + '/../../fixtures'
  CHARSET = 'utf-8'

  fixtures :users

  include MailerSpecHelper
  include ActionMailer::Quoting

  setup do
    # You don't need these lines while you are using create_ instead of deliver_
    #ActionMailer::Base.delivery_method = :test
    #ActionMailer::Base.perform_deliveries = true
    #ActionMailer::Base.deliveries = []

    @expected = TMail::Mail.new
    @expected.set_content_type 'text', 'plain', { 'charset' => CHARSET }
    @expected.mime_version = '1.0'
  end

  specify 'should send activation email' do
    @expected.subject = 'Account activation'
    @expected.body    = read_fixture('activation')
    @expected.from    = '[email protected]'
    @expected.to      = users(:bob).email

    Mailers::UserMailer.create_activation(users(:bob)).encoded.should == @expected.encoded
  end
end

That’s almost all. We are fully confident that emails look as we expected. In controller we don’t need to re-test mailers again, we just need to become convinced of mailer calling!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
context 'Given an signup action of UserController' do
  controller_name 'user'

  setup do
    @user = mock('user')
    @valid_user_params = { :email => '[email protected]' }
  end

  specify 'should deliver activation email to newly created user' do
    User.should_receive(:new).with(@valid_user_params).and_return(@user)
    Mailers::UserMailer.should_receive(:deliver_activation).with(@user)
    post :signup, :user => @valid_user_params
    response.should redirect_to(user_activate_url)
  end
end
The post Testing mailers with RSpec first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/testing-mailers-with-rspec/feed/ 3
5 Things why I love RSpec https://kpumuk.info/ruby-on-rails/5-things-why-i-love-rspec/ https://kpumuk.info/ruby-on-rails/5-things-why-i-love-rspec/#comments Mon, 19 Mar 2007 07:31:02 +0000 http://kpumuk.info/ruby-on-rails/5-things-why-i-love-rspec/ RSpec provides a framework for writing what can be called executable specifications of program behavior. In this short post I want to explain why I use this framework in place of classic TestUnit library. I stopped getting confused with order of expected and real values. Compare: 1assert('login', @user.login) and [email protected] == 'login' BTW, I made […]

The post 5 Things why I love RSpec first appeared on Dmytro Shteflyuk's Home.]]>
RSpec provides a framework for writing what can be called executable specifications of program behavior. In this short post I want to explain why I use this framework in place of classic TestUnit library.

  1. I stopped getting confused with order of expected and real values. Compare:

    1
    assert('login', @user.login)

    and

    1
    @user.login.should == 'login'

    BTW, I made a mistake in the assert_equal parameters order when was writing this article.

  2. I could understand what objects should do at first look on the test:

    1
    2
    3
    4
    5
    6
    7
    context 'The User model with fixtures loaded' do
      specify 'should be friended by the user if this user added him to friends' do
        users(:bob).friends << users(:chris)
        users(:chris).should have(1).friended
        users(:chris).friended.first.should == users(:bob)
      end
    end
  3. Test results are presented in easy to understand and clear form, just simple sentences, which are making asserts about entities in the system. In most cases there is even no necessity to look at the tests to understand which functionality has one or another entity!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    The newly created User
    - should be valid with all neccessary attributes specified
    - should be invalid without login
    - should be invalid when login length is less than 2
    - should be invalid without email
    - should be invalid with wrong email
    - should be invalid with someone else's login
    - should be invalid with someone else's email
    - should not require password and password_confirmation
  4. I started writing tests before actual coding finally! In fact, every developer think over what one or another method should do, and frequently these thoughts looks like “when method received string ‘somestring’ it should return ‘anotherstring'”. It’s easy to describe these thoughts using BDD-tests.

  5. Integrated flexible mock-objects system allows testing functionality just once. For example, when you test controllers, you should not use database and models – it is sufficient to check, if necessary model methods would be called in right order with right parameters.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    context 'Given an signup action of UserController' do
      controller_name 'user'
     
      setup do
        @user = mock('user')
        add_stubs(@user, :password= => nil, :password_confirmation= => nil, :new_password= => nil)
      end
     
      specify 'should render edit template with empty User object' do
        User.should_receive(:new).with(:no_args).and_return(@user)
        get :signup
        response.should render_template(:edit)
        assigns[:user].should be(@user)
      end
    end

The post 5 Things why I love RSpec first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/5-things-why-i-love-rspec/feed/ 3
Create your fixtures easily https://kpumuk.info/ruby-on-rails/create-your-fixtures-easily/ https://kpumuk.info/ruby-on-rails/create-your-fixtures-easily/#comments Sat, 20 Jan 2007 21:09:46 +0000 http://kpumuk.info/ruby-on-rails/create-your-fixtures-easily/ Every time I’m creating fixtures for Rails application I’m being angry because any more or less considerable project needs tons of them. Most complicated thing is to track all relationships, validations, and to keep fixtures up to date. A few days ago Yurii Rashkovskii has released small utility — Fixturease. What if I would tell […]

The post Create your fixtures easily first appeared on Dmytro Shteflyuk's Home.]]>
Every time I’m creating fixtures for Rails application I’m being angry because any more or less considerable project needs tons of them. Most complicated thing is to track all relationships, validations, and to keep fixtures up to date. A few days ago Yurii Rashkovskii has released small utility — Fixturease. What if I would tell you that now you can create your fixtures using your models?

To install gem enter command:

1
gem install fixturease

It’s time to try something:

1
2
3
4
5
6
7
8
9
10
11
12
# Loading Fixturease console for test mode.
@mike = User.create(:login => 'mike', :password => 'pass')
# <User:0x482ac18 @new_record_before_save=true, @attributes={"id"=>5, "password"=>"pass", "login"=>"mike", "created_at"=>Sat Jan 20 21:40:23 +0200 2007}, @errors=#<ActiveRecord::Errors:0x4827b08 @base=#<User:0x482ac18 ...>, @errors={}>, @new_record=false>
@mike_post_1 = @mike.posts.create(:name => 'first post by mike')
# <Post:0x4811bb4 @new_record_before_save=true, @attributes={"name"=>"first post by mike", "updated_at"=>Sat Jan 20 21:40:38 +0200 2007, "id"=>3, "user_id"=>5, "created_at"=>Sat Jan 20 21:40:38 +0200 2007}, @errors=#<ActiveRecord::Errors:0x4811510 @base=#<Post:0x4811bb4 ...>, @errors={}>, @new_record=false>
@mike_post_2 = @mike.posts.create(:name => 'second post by mike')
# <Post:0x48099dc @new_record_before_save=true, @attributes={"name"=>"second post by mike", "updated_at"=>Sat Jan 20 21:40:45 +0200 2007, "id"=>4, "user_id"=>5, "created_at"=>Sat Jan 20 21:40:45 +0200 2007}, @errors=#<ActiveRecord::Errors:0x48093d8 @base=#<Post:0x48099dc ...>, @errors={}>, @new_record=false>
exit

# Saving fixtures:
# User [./spec/fixtures/users.yml]: mike
# Post [./spec/fixtures/posts.yml]: mike_post_1 and mike_post_2

In this very simple case we just created one fixture for User model named mike and two for Post model named mike_post_1 and mike_post_2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mike:
  id
: 5
  password
: pass
  login
: mike
  created_at
: 2007-01-20 21:40:23.250000 +02:00

mike_post_1
:
  name
: first post by mike
  updated_at
: 2007-01-20 21:40:38.765000 +02:00
  id
: 3
  user_id
: 5
  created_at
: 2007-01-20 21:40:38.765000 +02:00
mike_post_2
:
  name
: second post by mike
  updated_at
: 2007-01-20 21:40:45.562000 +02:00
  id
: 4
  user_id
: 5
  created_at
: 2007-01-20 21:40:45.562000 +02:00

You can always load fixtures to current environment using command load_fixtures! (or command-line switch -l). It allows you to use previously created fixtures to build new ones.

I like unit tests but I hate to write fixtures. This nice gem allows me to relax and to get pleasure from work.

The post Create your fixtures easily first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/create-your-fixtures-easily/feed/ 3
Ruby on Rails related cheat sheets https://kpumuk.info/ruby-on-rails/ruby-on-rails-related-cheat-sheets/ https://kpumuk.info/ruby-on-rails/ruby-on-rails-related-cheat-sheets/#comments Fri, 17 Nov 2006 05:32:47 +0000 http://kpumuk.info/ajax/ruby-on-rails-related-cheat-sheets/ There are couple of cheat sheets about Ruby on Rails and related technologies can be found in the web. I decided to collect all of them (almost all) in one post just to keep them in mind. All of them are in full color PDFs or PNGs. Ruby on Rails Development Cheat Sheets Ruby Cheatsheet […]

The post Ruby on Rails related cheat sheets first appeared on Dmytro Shteflyuk's Home.]]>
There are couple of cheat sheets about Ruby on Rails and related technologies can be found in the web. I decided to collect all of them (almost all) in one post just to keep them in mind. All of them are in full color PDFs or PNGs.

Ruby on Rails Development Cheat Sheets

JavaScript Development Cheat Sheets

Databases / SQL Cheat Sheets

Web Development Cheat Sheets

Other Cheat Sheets

The post Ruby on Rails related cheat sheets first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/ruby-on-rails-related-cheat-sheets/feed/ 13