ruby | 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:15:31 +0000 en-US hourly 1 https://wordpress.org/?v=6.7.1 Submitting a patch to the Open Source project: composite_primary_keys https://kpumuk.info/ruby-on-rails/submitting-a-patch-to-the-open-source-project-composite_primary_keys/ https://kpumuk.info/ruby-on-rails/submitting-a-patch-to-the-open-source-project-composite_primary_keys/#respond Thu, 17 Dec 2009 10:28:12 +0000 http://kpumuk.info/?p=1127 Not so far ago I have found a weird bug in the Open Source Ruby gem called composite_primary_keys, occurred when you specify :primary_key option for has_one or has_many association. There are two ways to get it fixed: submit an issue and wait till someone will work out this problem or fix it by yourself and […]

The post Submitting a patch to the Open Source project: composite_primary_keys first appeared on Dmytro Shteflyuk's Home.]]>
Not so far ago I have found a weird bug in the Open Source Ruby gem called composite_primary_keys, occurred when you specify :primary_key option for has_one or has_many association. There are two ways to get it fixed: submit an issue and wait till someone will work out this problem or fix it by yourself and then pull request to get the patch merged into the core. This is a great library and I use it in almost all my project, so I decided to help the author and fix this bug by myself. Here I will show you how to do that.

I have discovered, that composite_primary_keys breaks my SQL queries when :primary_key option specified both for has_many and has_one associations.

Step 0. Reproducing the bug

First of all we need to reproduce a bug. Please note: if you know where the problem is, you can skip this step. Let’s start from a simple example:

1
rails cpk_bug && cd cpk_bug

Now we will add dependencies to the config/environment.rb:

1
config.gem 'composite_primary_keys'

and to the config/environments/test.rb:

1
2
3
config.gem 'rspec', :lib => 'spec'
config.gem 'rspec-rails', :lib => false
config.gem 'factory_girl'

Okay. Now we are ready to start. Let’s generate some migrations:

1
2
3
4
5
script/generate rspec_model document upload_request_id:integer title:string description:text
script/generate rspec_model upload_request filename:string state:integer
script/generate rspec_model copyright_request upload_request_id:integer explanation:text
rm -rf spec/fixtures
rake db:migrate && rake db:test:clone

Let’s create our factories:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Factory.define :copyright_request do |cr|
  cr.explanation "This document is copyrighted by O'Reilly"
end

Factory.define :document do |d|
  d.sequence(:title) { |n| "Document #{n}" }
  d.description { |a| "The perfect description for the document '#{a.title}'" }
  d.association :upload_request
end

Factory.define :upload_request do |ur|
  ur.sequence(:filename) { |n| "file#{'%03d' % n}.pdf" }
  ur.state 0
end

And our spec which should fail:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require 'spec/spec_helper'

describe Document do
  context 'when has copyright requests' do
    before :each do
      # Fake upload request used to desynchronise document and
      # upload request IDs
      Factory(:upload_request)
    end
   
    it 'should not have any copyright requests when just created' do
      @document = Factory(:document)
      @document.copyright_requests.should == []
    end
   
    it 'should return a list of copyright requests from #copyright_requests' do
      @document = Factory(:document)
      @request1 = Factory(:copyright_request, :upload_request => @document.upload_request)
     
      @document.copyright_requests.should == [@request1]
    end
  end
end

Ok, it fails because we haven’t defined our associations on models. Let’s do it:

1
2
3
4
5
6
7
8
9
10
11
12
class Document < ActiveRecord::Base
  belongs_to :upload_request
 
  has_many :copyright_requests, :primary_key => :upload_request_id, :foreign_key => :upload_request_id
end

class UploadRequest < ActiveRecord::Base
end

class CopyrightRequest < ActiveRecord::Base
  belongs_to :upload_request
end

Woohoo! Specs are failing with an error we’re trying to reproduce:

1
2
3
4
5
6
7
8
9
10
11
12
~/cpk_bug$ spec spec
..F.

1)
'Document when has copyright requests should return a list of copyright requests from #copyright_requests' FAILED
expected: [#<CopyrightRequest id: 1, upload_request_id: 2, explanation: "This document is copyrighted by O'Reilly", created_at: "2009-12-07 11:19:11", updated_at: "2009-12-07 11:19:11">],
     got: [] (using ==)
/Users/kpumuk/cpk_bug/spec/models/document_spec.rb:20:

Finished in 0.155708 seconds

4 examples, 1 failure

Step 1. Setting up an environment for composite_primary_keys gem

In general to submit a patch to the Open Source project hosted by GitHub you have to perform the following steps: fork the repository on GitHub, write tests which fail, write a patch, ensure it works, push your changes to your fork repository, and submit a pull request. Let’s do just that!

To fork the repository I will use a perfect github gem by Dr Nic (BTW, he is the author of composite_primary_keys!)

1
2
3
4
sudo gem install github
gh clone drnic/composite_primary_keys
cd composite_primary_keys
gh fork

Now let’s configure our test environment and run tests:

1
2
3
rake local:setup
rake mysql:build_databases
rake test_mysql

You should see something like this:

1
2
3
4
5
6
Using native MySQL
Started
...................................................................................
Finished in 0.487679 seconds.

83 tests, 262 assertions, 0 failures, 0 errors

If you have any problems, check the test/README_tests.txt file for help.

Step 2. Reproducing failing tests inside composite_primary_keys test suite

We are doing TDD, right? So before any fixes we have to write a failing test first. Gem we’re hacking has a powerful test suite with many database tables created, so all we need is just to add associations to one of models, which will cover our issue.

First, add this to the test/fixtures/membership.rb model:

1
2
  has_many :readings, :primary_key => :user_id, :foreign_key => :user_id
  has_one :reading, :primary_key => :user_id, :foreign_key => :user_id, :order => 'id DESC'

And this tests set goes to the test/test_associations.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  def test_has_many_with_primary_key
    @membership = Membership.find([1, 1])
   
    assert_equal 2, @membership.readings.size
  end

  def test_has_one_with_primary_key
    @membership = Membership.find([1, 1])
   
    assert_equal 2, @membership.reading.id
  end

  def test_joins_has_many_with_primary_key
    @membership = Membership.find(:first, :joins => :readings, :conditions => { :readings => { :id => 1 } })
   
    assert_equal [1, 1], @membership.id
  end

  def test_joins_has_one_with_primary_key
    @membership = Membership.find(:first, :joins => :reading, :conditions => { :readings => { :id => 2 } })
   
    assert_equal [1, 1], @membership.id
  end

Now rake test_mysql produces following error (there are 4 of them, I will show only the first one):

1
2
3
4
  1) Error:
test_has_many_with_primary_key(TestAssociations):
ActiveRecord::StatementInvalid: Mysql::Error: Operand should contain 1 column(s): SELECT * FROM `readings` WHERE (`readings`.`user_id` = 1,1)
...

Well, that are the errors we are working on. Time to fix them!

Step 3. Fixing the bug

I will not explain how I fixed that, you can check my commit for details. Here is the diff:

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
diff --git a/lib/composite_primary_keys/associations.rb b/lib/composite_primary_keys/associations.rb
index 6b63664..9a9e173 100644
--- a/lib/composite_primary_keys/associations.rb
+++ b/lib/composite_primary_keys/associations.rb
@@ -180,11 +180,12 @@ module ActiveRecord::Associations::ClassMethods
                 raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
               else
                 foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
+                primary_key = options[:primary_key] || parent.primary_key
                 " LEFT OUTER JOIN %s ON %s " % [
                   table_name_and_alias,
                   composite_join_clause(
                     full_keys(aliased_table_name, foreign_key),
-                    full_keys(parent.aliased_table_name, parent.primary_key)),
+                    full_keys(parent.aliased_table_name, primary_key)),
                 ]
             end
           when :belongs_to
@@ -338,7 +339,7 @@ module ActiveRecord::Associations
           @finder_sql << " AND (#{conditions})" if conditions
 
         else
-          @finder_sql = full_columns_equals(@reflection.klass.table_name, @reflection.primary_key_name, @owner.quoted_id)
+          @finder_sql = full_columns_equals(@reflection.klass.table_name, @reflection.primary_key_name, owner_quoted_id)
           @finder_sql << " AND (#{conditions})" if conditions
       end
 
@@ -386,7 +387,7 @@ module ActiveRecord::Associations
             "#{@reflection.klass.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
             "#{@reflection.klass.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
         else
-          @finder_sql = full_columns_equals(@reflection.klass.table_name, @reflection.primary_key_name, @owner.quoted_id)
+          @finder_sql = full_columns_equals(@reflection.klass.table_name, @reflection.primary_key_name, owner_quoted_id)
       end
 
       @finder_sql << " AND (#{conditions})" if conditions

Run tests to get the following output:

1
2
3
4
5
6
7
~/cpk_bug/composite_primary_keys (master)$ rake test_mysql
Using native MySQL
Started
.......................................................................................
Finished in 0.511129 seconds.

87 tests, 266 assertions, 0 failures, 0 errors

We are done for now!

Step 4. Committing changes and pulling request

It’s time to commit our changes now:

1
2
3
4
5
6
7
8
9
10
11
12
13
~/cpk_bug/composite_primary_keys (master)$ git add .
~/cpk_bug/composite_primary_keys (master)$ git commit -m 'Fixed several bugs in has_one and has_many associations when :primary_key specified'
[master 3e29891] Fixed several bugs in has_one and has_many associations when :primary_key specified
 3 files changed, 31 insertions(+), 3 deletions(-)
~/cpk_bug/composite_primary_keys (master)$ git push
Counting objects: 16, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 2.14 KiB, done.
Total 9 (delta 7), reused 0 (delta 0)
To git@github.com:kpumuk/composite_primary_keys.git
   050d832..3e29891  HEAD -> master
~/cpk_bug/composite_primary_keys (master)$ gh home

Okay, the next step is a pull request. Last command opened a browser window with your fork. Navigate to the latest commit and press the “Pull Request” button (at the time of writing this article gh pull-request didn’t worked, and you can try to fix by yourself to understand the workflow):

Patching the composite_primary_keys gem

That’s all, we have contributed to the community! Today Darrin Holst merged my commit into the core, and you can find it here. Not all things went smooth (I forgot to add a fixture, so tests were failing on first run), but he helped me a lot to get it working. That’s how Open Source works: we help each other to develop high quality software.

Credits

First of all, thanks to Dr Nic for the great plugin, one of the best piece of functionality I can’t imagine life without. Thanks to Darrin Holst for his patience and great help in debugging tests problem, and also for merging my commit into the composite_primary_keys core. Thanks to GitHub for the great Open Source code hosting solution, which makes working on Open Source projects so exciting.

Do you have comments or suggestions? You are welcome! Also, I will be happy if you follow me in Twitter.

The post Submitting a patch to the Open Source project: composite_primary_keys first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/submitting-a-patch-to-the-open-source-project-composite_primary_keys/feed/ 0
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
Memo #5: Use ary.uniq method carefully in Ruby https://kpumuk.info/ruby-on-rails/memo-5-use-ary-uniq-method-carefully-in-ruby/ https://kpumuk.info/ruby-on-rails/memo-5-use-ary-uniq-method-carefully-in-ruby/#comments Wed, 15 Jul 2009 10:55:17 +0000 http://kpumuk.info/?p=721 Today my friend asked me to help him with an unexpected behavior of Ruby’s Hash.uniq method. Here is an example: 123456[{"id"=>667824693}, {"id"=>667824693}].uniq # => [{"id"=>667824693}, {"id"=>667824693}] [{"id"=>66782469}, {"id"=>66782469}].uniq # => [{"id"=>66782469}] [{"id"=>6678246931}, {"id"=>6678246931}].uniq # => [{"id"=>6678246931}] Check the first command result. Very disappointing, right? So what happen? Quick looking through the Ruby code completely explained […]

The post Memo #5: Use ary.uniq method carefully in Ruby first appeared on Dmytro Shteflyuk's Home.]]>
Today my friend asked me to help him with an unexpected behavior of Ruby’s Hash.uniq method. Here is an example:

1
2
3
4
5
6
[{"id"=>667824693}, {"id"=>667824693}].uniq
# => [{"id"=>667824693}, {"id"=>667824693}]
[{"id"=>66782469}, {"id"=>66782469}].uniq
# => [{"id"=>66782469}]
[{"id"=>6678246931}, {"id"=>6678246931}].uniq
# => [{"id"=>6678246931}]

Check the first command result. Very disappointing, right? So what happen? Quick looking through the Ruby code completely explained it. Here is how this method works internally (this is just prototype in Ruby, original code is in C, but works in the same way):

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
def ary_make_hash(ary, val)
  ary.inject({}) do |hash, el|
    hash[el] = val
    hash
  end
end

def uniq(ary)
  ary = ary.dup
  ary.uniq!
  ary
end

def uniq!(ary)
  hash = ary_make_hash(ary, 0)
  return nil if ary.length == hash.length

  j = 0
  (0...ary.length).each do |idx|
    if hash.delete(ary[idx])
      ary[j] = ary[idx]
      j += 1
    end
  end
  ary.slice!(0, j)
  ary
end

Let’s test it:

1
2
3
4
5
6
uniq([{"id"=>667824693}, {"id"=>667824693}])
# => [{"id"=>667824693}, {"id"=>667824693}]
uniq([{"id"=>66782469}, {"id"=>66782469}])
# => [{"id"=>66782469}]
uniq([{"id"=>6678246931}, {"id"=>6678246931}])
# => [{"id"=>6678246931}]

And just to make sure our conclusions are correct:

1
2
3
4
5
6
[{"id"=>667824693}, {"id"=>667824693}].map { |el| el.hash }
# => [29793216, 29793156]
[{"id"=>66782469}, {"id"=>66782469}].map { |el| el.hash }
# => [255119887, 255119887]
[{"id"=>6678246931}, {"id"=>6678246931}].map { |el| el.hash }
# => [482552381, 482552381]

So the idea behind the Hash.uniq method is the method Hash.hash, which produces different results for hashes in the first example. Be careful when doing obj.uniq on complex objects.

Update: There is a good research on Hash.hash method here.

The post Memo #5: Use ary.uniq method carefully in Ruby first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/memo-5-use-ary-uniq-method-carefully-in-ruby/feed/ 4
Installing and Using Scribe with Ruby on Mac OS https://kpumuk.info/mac-os-x/installing-and-using-scribe-with-ruby-on-mac-os/ https://kpumuk.info/mac-os-x/installing-and-using-scribe-with-ruby-on-mac-os/#comments Wed, 13 May 2009 18:46:24 +0000 http://kpumuk.info/?p=668 In Scribd we have tons of analytics data generated daily that should be somehow processed. Currently we use MySQL to store all this stuff, but that is not the best option for logging lots of data. So we’ve decided to try some more specialized tools, which could help us to store and process our data. […]

The post Installing and Using Scribe with Ruby on Mac OS first appeared on Dmytro Shteflyuk's Home.]]>
Scribe In Scribd we have tons of analytics data generated daily that should be somehow processed. Currently we use MySQL to store all this stuff, but that is not the best option for logging lots of data. So we’ve decided to try some more specialized tools, which could help us to store and process our data. The most interesting thing which could simplify analytics data collecting was Scribe. As it turned out, installation process is not so simple as expected so here you will find a few steps manual on how to install Scribe on a developer machine.

0. Prerequisites

First thing you’ll need is to install thrift library that is used by Scribe to do all the networking communication. To build it you need to have boost C++ library installed:

1
sudo port install boost

1. Installing Thrift

Now we are ready to download and build thrift. I prefer to keep all manually built tools in /opt:

1
2
3
4
5
6
git clone git://git.thrift-rpc.org/thrift.git
cd thrift
./bootstrap.sh
./configure --prefix=/opt/thrift
sudo make
sudo make install

Please note, that make command needs root privileges (it installs Ruby bindings to the system folder).

You will also need to install fb303 library (it is used in all facebook/thrift related tools to do status/health monitoring calls):

1
2
3
4
5
cd contrib/fb303
./bootstrap.sh
./configure --prefix=/opt/fb303 --with-thriftpath=/opt/thrift
make
sudo make install

2. Installing Scribe

Download Scribe from Sourceforge. Of course, you can use current development version from SVN.

1
2
3
4
5
cd scribe
./bootstrap.sh
./configure --prefix=/opt/scribe --with-thriftpath=/opt/thrift --with-fb303path=/opt/fb303
make
sudo make install

3. Configuring Scribe

I’ve created the /opt/scribe/conf directory and copied examples/example1.conf configuration there:

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
##
## Sample Scribe configuration
##

# This file configures Scribe to listen for messages on port 1463 and write
# them to /tmp/scribetest

port=1463
max_msg_per_second=2000000
check_interval=3

# DEFAULT
<store>
category=default
type=buffer

target_write_size=20480
max_write_interval=1
buffer_send_rate=2
retry_interval=30
retry_interval_range=10

<primary>
type=file
fs_type=std
file_path=/tmp/scribetest
base_filename=thisisoverwritten
max_size=1000000
add_newlines=1
</primary>

<secondary>
type=file
fs_type=std
file_path=/tmp
base_filename=thisisoverwritten
max_size=3000000
</secondary>
</store>

Now we are ready to start scribe server:

1
sudo /opt/scribe/bin/scribed -c /opt/scribe/conf/example1.conf

4. Creating a test Ruby client application

First thing you’ll need is to gather all Ruby bindings into your app directory:

1
2
3
4
5
mkdir testapp
cd testapp
/opt/thrift/bin/thrift -o . -I /opt/fb303/share/ --gen rb /path/to/downloaded/scribe/if/scribe.thrift
/opt/thrift/bin/thrift -o . -I /opt/fb303/share/ --gen rb /opt/fb303/share/fb303/if/fb303.thrift
mv gen-rb scribe

Do not forget to replace /path/to/downloaded/scribe with real path where you have extracted Scribe sources to.

And here is the test console Ruby script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env ruby

$LOAD_PATH.unshift(File.dirname(__FILE__) + '/scribe')
require 'scribe'

begin
  socket = Thrift::Socket.new('localhost', 1463)
  transport = Thrift::FramedTransport.new(socket)
  protocol = Thrift::BinaryProtocol.new(transport, false)
  client = Scribe::Client.new(protocol)
  transport.open()
  log_entry = LogEntry.new(:category => 'test', :message => 'This is a test message')
  client.Log([log_entry])
  transport.close()
rescue Thrift::Exception => tx
  print 'Thrift::Exception: ', tx.message, "\n"
end

Woot, it works! Time to start creating your highly productive logging/data collection system.

P.S. On my machine I got the following output from this script:

1
2
3
4
5
6
7
./scribe/fb303_types.rb:9: warning: already initialized constant DEAD
./scribe/fb303_types.rb:10: warning: already initialized constant STARTING
./scribe/fb303_types.rb:11: warning: already initialized constant ALIVE
./scribe/fb303_types.rb:12: warning: already initialized constant STOPPING
./scribe/fb303_types.rb:13: warning: already initialized constant STOPPED
./scribe/fb303_types.rb:14: warning: already initialized constant WARNING
./scribe/fb303_types.rb:15: warning: already initialized constant VALID_VALUES

To fix this issue, open the generated scribe/scribe_types.rb and replace require 'fb303_types' line with this:

1
require File.dirname(__FILE__) + '/fb303_types'
The post Installing and Using Scribe with Ruby on Mac OS first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/mac-os-x/installing-and-using-scribe-with-ruby-on-mac-os/feed/ 5
Memo #4: Managing Ruby Gems https://kpumuk.info/ruby-on-rails/memo-4-managing-ruby-gems/ https://kpumuk.info/ruby-on-rails/memo-4-managing-ruby-gems/#comments Mon, 13 Apr 2009 10:07:43 +0000 http://kpumuk.info/?p=422 The power of Ruby is not only in its flexibility. It allows to create easy to maintain reusable parts of software, and also provides a way to redistribute them and integrate with your applications — RubyGems system. The only thing that could hurt developer’s brain is managing installed gems. When you are updating already installed […]

The post Memo #4: Managing Ruby Gems first appeared on Dmytro Shteflyuk's Home.]]>
RubyGems The power of Ruby is not only in its flexibility. It allows to create easy to maintain reusable parts of software, and also provides a way to redistribute them and integrate with your applications — RubyGems system. The only thing that could hurt developer’s brain is managing installed gems. When you are updating already installed gem, previous version will stay in gems folder and will be available to use. But why do you need all these obsolete libraries? There is a command to cleanup stale libraries in RubyGems — gem cleanup.

First thing I want to mention is gems documentation. Every gem in system has powerful documentation based on rdoc, built during an installation procedure. The easiest way to view these docs is to run gem server and open http://localhost:8808/ in browser:

1
gem server

You will be able to review which gems has been installed, and check their documentation.

I’m pretty sure you have lots of gems with the same name, but different versions. You could use the following command to remove all gem versions except the latest one:

1
gem cleanup

That’s all. You have only latest gems installed. But wait, what about Max OS X users, who have pre-installed Ruby? They (actually we) are in trouble, because…

Fixing problem with gem cleanup on Mac OS X 10.5.x (Leopard)

On Mac OS X when you will try to use this command, you will hit following error:

1
2
Attempting to uninstall sqlite3-ruby-1.2.1
ERROR:  While executing gem ... (Gem::InstallError)

That’s common problem and I will show how to fix it. First, run following command:

1
2
3
4
5
6
7
8
9
10
11
12
kpumuk@kpumuk-mbp~: gem list -d sqlite3

*** LOCAL GEMS ***

sqlite3-ruby (1.2.4, 1.2.1)
    Author: Jamis Buck
    Homepage: http://sqlite-ruby.rubyforge.org/sqlite3
    Installed at (1.2.4): /Library/Ruby/Gems/1.8
                 (1.2.1): /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8

    SQLite3/Ruby is a module to allow Ruby scripts to interface with a
    SQLite3 database.

You may now uninstall the offending gem with:

1
gem uninstall --install-dir  /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8 sqlite3-ruby

So, in general

1
gem list -d <gem-name>

will get you the location of the gem, and

1
gem uninstall --install-dir </install/directory> <gem-name>

will uninstall the gem (found here.)

The post Memo #4: Managing Ruby Gems first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/memo-4-managing-ruby-gems/feed/ 3
Memo #3: Advanced usage of Ruby hashes and arrays https://kpumuk.info/ruby-on-rails/memo-3-advanced-usage-of-ruby-hashes-and-arrays/ https://kpumuk.info/ruby-on-rails/memo-3-advanced-usage-of-ruby-hashes-and-arrays/#comments Fri, 23 Jan 2009 00:59:58 +0000 http://kpumuk.info/?p=254 One of the most used features in any programming language is a Hash. Today we are going to talk about some of the Ruby’s Hash features, which are well documented, but rarely used — parameters of the Hash constructor. In the second part of this article we will take a look at the arguments of […]

The post Memo #3: Advanced usage of Ruby hashes and arrays first appeared on Dmytro Shteflyuk's Home.]]>
Ruby One of the most used features in any programming language is a Hash. Today we are going to talk about some of the Ruby’s Hash features, which are well documented, but rarely used — parameters of the Hash constructor. In the second part of this article we will take a look at the arguments of the Array class’ constructor.

Take a look at the following example.

1
2
3
4
5
6
a = %w(apple banana apple)
h = a.inject({}) do |h, fruit|
  h[fruit] ||= 0
  h[fruit] += 1
  h
end

Here we have an array of fruits and we need to calculate a frequency of each fruit. As you can see, in the line 3 we are initializing frequency value to 0 if there are was no fruit with this name before. We can simplify this code:

1
2
3
4
5
a = %w(apple banana apple)
h = a.inject(Hash.new(0)) do |h, fruit|
  h[fruit] += 1
  h
end

In line 2 we are creating a new hash, which default value is 0. This means that if we would try to retrieve value for a non-existing key, 0 would be returned.

Let’s check another example:

1
2
3
4
5
6
a = %w(apple banana apple)
h = {}
a.each_with_index do |fruit, i|
  h[fruit] ||= []
  h[fruit] << i
end

Here we are collecting indexes of each fruit in the source array. But now we can’t just create a new hash and pass [] as the default value, because all keys in this hash will refer to the same array, so in result we will get an array [1, 2, 3] for each fruit. So let’s try the following:

1
2
3
4
5
a = %w(apple banana apple)
h = Hash.new { |h, key| h[key] = [] }
a.each_with_index do |fruit, i|
  h[fruit] << i
end

In this case we are creating a new array object for any non-existing key, that was accessed. So

1
  h['some non-existing key']

will return [] and save it in the hash. When you will hit this key next time, previously created array will be returned.

You can pass a block to Array constructor too. For example, you need an array with 10 random numbers:

1
2
a = []
10.times { a << rand(100) }

You can simplify it using map method:

1
a = (1..10).map { rand(100) }

But you can do it even easier:

1
a = Array.new(10) { rand(100) }

Next Memo will cover managing Ruby Gems, so stay tuned.

The post Memo #3: Advanced usage of Ruby hashes and arrays first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/memo-3-advanced-usage-of-ruby-hashes-and-arrays/feed/ 6
Sphinx Client API 0.3.1 and 0.4.0 r909 for Sphinx 0.9.8 r909 released https://kpumuk.info/ruby-on-rails/sphinx-client-api-0-3-1-and-0-4-0-r909-for-sphinx-0-9-8-r909-released/ https://kpumuk.info/ruby-on-rails/sphinx-client-api-0-3-1-and-0-4-0-r909-for-sphinx-0-9-8-r909-released/#comments Sun, 09 Dec 2007 19:33:10 +0000 http://kpumuk.info/ror-plugins/sphinx-client-api-0-3-1-and-0-4-0-r909-for-sphinx-0-9-8-r909-released/ I have a good news: Sphinx Client API has been updated and now it supports all brand new features of the unstable Sphinx 0.9.8 development snapshot. What does it mean for you as a developer? What features you will get if you would decide to switch to the new version? I will describe most valuable […]

The post Sphinx Client API 0.3.1 and 0.4.0 r909 for Sphinx 0.9.8 r909 released first appeared on Dmytro Shteflyuk's Home.]]>
Sphinx Search Engine I have a good news: Sphinx Client API has been updated and now it supports all brand new features of the unstable Sphinx 0.9.8 development snapshot. What does it mean for you as a developer? What features you will get if you would decide to switch to the new version? I will describe most valuable improvements of the Sphinx in this article, and will show how to use them with new Sphinx Client API 0.4.0 r909.

Table of contents

Multi-query support

What does it mean? Multi-query support means sending multiple search queries to Sphinx at once. It’s saving network connection overheads and other round-trip costs. But what’s much more important, it unlocks possibilities to optimize “related” queries internally. Here is quote from the Sphinx home page:

One typical Sphinx usage pattern is to return several different “views” on the search results. For instance, one might need to display per-category match counts along with product search results, or maybe a graph of matches over time. Yes, that could be easily done earlier using the grouping features. However, one had to run the same query multiple times, but with different settings.

From now on, if you submit such queries through newly added multi-query interface (as a side note, ye good olde Query() interface is not going anywhere, and compatibility with older clients should also be in place), Sphinx notices that the full-text search query is the same and it is just sorting/grouping settings which are different. In this case it only performs expensive full-text search once, but builds several different (differently sorted and/or grouped) result sets from retrieved matches. I’ve seen speedups of 1.5-2 times on my simple synthetic queries; depending on different factors, the speedup could be even greater in practice.

To perform multi-query you should add several queries using AddQuery method (parameters are exactly the same as in Query call), and then call RunQueries. Please note, that all parameters, filters, query settings are stored between AddQuery calls. It means that if you have specified sort mode using SetSortMode before first AddQuery call, then sort mode will be the same for the second AddQuery call. Currently you can reset only filters (using ResetFilters) and group by (ResetGroupBy) settings. BTW, you can use Query as usually to perform single query, but don’t try to make this call after you have added query into the batch using AddQuery.

Stop speaking, let’s look the example:

1
2
3
4
5
6
7
8
9
10
sphinx = Sphinx::Client.new
sphinx.SetFilter('group_id', [1])
sphinx.AddQuery('wifi')

sphinx.ResetFilters
sphinx.SetFilter('group_id', [2])
sphinx.AddQuery('wifi')

results = sphinx.RunQueries
pp results

As the result we will get array of 2 hashes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[{"total_found"=>2,
  "status"=>0,
  "matches"=>
   [{"attrs"=>{"group_id"=>1, "created_at"=>1175658647}, "weight"=>2, "id"=>3},
    {"attrs"=>{"group_id"=>1, "created_at"=>1175658490}, "weight"=>1, "id"=>1}],
  "error"=>"",
  "words"=>{"wifi"=>{"hits"=>6, "docs"=>3}},
  "time"=>"0.000",
  "attrs"=>{"group_id"=>1, "created_at"=>2},
  "fields"=>["name", "description"],
  "total"=>2,
  "warning"=>""},
 {"total_found"=>1,
  "status"=>0,
  "matches"=>
   [{"attrs"=>{"group_id"=>2, "created_at"=>1175658555}, "weight"=>2, "id"=>2}],
  "error"=>"",
  "words"=>{"wifi"=>{"hits"=>6, "docs"=>3}},
  "time"=>"0.000",
  "attrs"=>{"group_id"=>1, "created_at"=>2},
  "fields"=>["name", "description"],
  "total"=>1,
  "warning"=>""}]

Each hash contains the same data as result of Query method call. Also they have additional fields error and warning which contains error and warning message respectively when not empty.

Note: I have added ResetFilters call before creating second query. Without this call our query will have two filters with conflicting conditions, so there will be no results at all.

Extended engine V2

New querying engine (codenamed “extended engine V2”) is going to gradually replace all the currently existing matching modes. At the moment, it is fully identical to extended mode in functionality, but is much less CPU intensive for some queries. Here are notes from Sphinx author:

I have already seen improvements of up to 3-5 times in extreme cases. The only currently known case when it’s slower is processing complex extended queries with tens to thousands keywords; but forthcoming optimizations will fix that.

V2 engine is currently in alpha state and does not affect any other matching mode yet. Temporary SPH_MATCH_EXTENDED2 mode was added to provide a way to test it easily. We are in the middle of extensive internal testing process (under simulated production load, and then actual production load) right now. Your independent testing results would be appreciated, too!

So, to use new matching mode we should use SPH_MATCH_EXTENDED2 mode. Let’s do it!

1
2
3
sphinx = Sphinx::Client.new
sphinx.SetMatchMode(Sphinx::Client::SPH_MATCH_EXTENDED2)
sphinx.Query('wifi')

Easy enough, right? You should try it by yourself to feel power of new engine. Please note, that this mode is temporary and it will be removed after release.

64-bit document and word IDs support

Before version 0.9.8 the Sphinx was limited to index up to 4 billion documents because of using 32-bit keys. From here on it has ability to use 64-bit IDs, and new feature does not impact on 32-bit keys performance. Let’s look at the example. First we will make query to DB with 32-bit keys:

1
2
3
sphinx = Sphinx::Client.new
result = sphinx.Query('wifi')
pp result['matches'][0]['id'].class

As you can see, class of the id field is Fixnum. Let’s try to make call to index with 64-bit keys. You will get Bignum as the result, and it means that you can have more than 4 billion documents!

Multiple-valued attributes

Plain attributes only allow to attach 1 value per each document. However, there are cases (such as tags or categories) when it is necessary to attach multiple values of the same attribute and be able to apply filtering to value lists. In these cases we can use multiple-valued attributes now.

1
2
3
sphinx = Sphinx::Client.new
sphinx.SetFilter('tag', [1,5])
pp sphinx.Query('wifi')

In case of using miltiple-valued attribute tag you will get result 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
{"total_found"=>2,
 "status"=>0,
 "matches"=>
  [{"attrs"=>
     {"tag"=>[4, 5],
      "group_id"=>2,
      "created_at"=>1175658555},
    "weight"=>2,
    "id"=>2},
   {"attrs"=>
     {"tag"=>[1, 2, 3],
      "group_id"=>1,
      "created_at"=>1175658490},
    "weight"=>1,
    "id"=>1}],
 "error"=>"",
 "words"=>{"wifi"=>{"hits"=>6, "docs"=>3}},
 "time"=>"0.000",
 "attrs"=>
  {"price"=>5,
   "tag"=>1073741825,
   "is_active"=>4,
   "group_id"=>1,
   "created_at"=>2},
 "fields"=>["name", "description"],
 "total"=>2,
 "warning"=>""}

As you can see, multiple-valued attributes returned as array of integers.

Geodistance feature

Sphinx now is able to compute geographical distance between two points specified by latitude and longitude pairs (in radians). So you now can specify per-query “anchor point” (and attribute names to fetch per-entry latitude and longitude from), and then use “@geodist” virtual attribute both in the filters and in the sorting clause. In this case distance (in meters) from anchor point to each match will be computed, used for filtering and/or sorting, and returned as a virtual attribute too.

1
2
3
sphinx = Sphinx::Client.new
sphinx.SetGeoAnchor('lat', 'long', 0.87248, 0.63195)
result = sphinx.Query('wifi')

Download

As always, you can download Sphinx Client API from project home page. Take into account that version 0.3.1 of the client API intended to use with Sphinx 0.9.7, and Sphinx Client API 0.4.0 r909 requires Sphinx 0.9.8 r909 development snapshot. You could download Sphinx from the Download section of the Sphinx home page.

The post Sphinx Client API 0.3.1 and 0.4.0 r909 for Sphinx 0.9.8 r909 released first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/sphinx-client-api-0-3-1-and-0-4-0-r909-for-sphinx-0-9-8-r909-released/feed/ 4
Sphinx Search Engine 0.9.7, Ruby Client API 0.3.0 https://kpumuk.info/ruby-on-rails/sphinx-search-engine-0-9-7-ruby-client-api-0-3-0/ https://kpumuk.info/ruby-on-rails/sphinx-search-engine-0-9-7-ruby-client-api-0-3-0/#comments Thu, 05 Apr 2007 14:44:36 +0000 http://kpumuk.info/ror-plugins/sphinx-search-engine-0-9-7-ruby-client-api-0-3-0/ [lang_en] It’s happened! We all waited for Sphinx update and finally Andrew Aksyonoff has released version 0.9.7 of his wonderful search engine (who does not know about it, look my previous posts here and here). [/lang_en] [lang_ru] Свершилось! Мы все ждали обновления Sphinx, и вот наконец Andrew Aksyonoff выпустил версию 0.9.7 своего замечательного поискового движка […]

The post Sphinx Search Engine 0.9.7, Ruby Client API 0.3.0 first appeared on Dmytro Shteflyuk's Home.]]>
[lang_en]

Sphinx Search EngineIt’s happened! We all waited for Sphinx update and finally Andrew Aksyonoff has released version 0.9.7 of his wonderful search engine (who does not know about it, look my previous posts here and here).

[/lang_en]

[lang_ru]

Sphinx Search EngineСвершилось! Мы все ждали обновления Sphinx, и вот наконец Andrew Aksyonoff выпустил версию 0.9.7 своего замечательного поискового движка (для тех, кто не понимает, о чем я говорю: посмотрите мои предыдущие заметки здесь и здесь).

[/lang_ru]

[lang_en]

Major Sphinx updates include:

  • separate groups sorting clause in group-by mode
  • support for 1-grams, prefix and infix indexing
  • improved documentation

Now about Sphinx Client API for Ruby. In this version I decided that it is not so good to have different interfaces in different languages (BuildExcerpts in PHP and build_excerpts in Ruby). Therefor applications which using version 0.1.0 or 0.2.0 of API should be reviewed after update. Check documentation for details.

New things in the Sphinx Ruby API:

  • Completely synchronized API with PHP version.
  • Fixed bug with processing attributes in query response (thanks to shawn).
  • Fixed bug query processing time round-up (thanks to michael).
  • 100% covered by RSpec specifications.

You could always download latest version from the Sphinx Client API for Ruby page.

If you are using Sphinx in your Ruby on Rails application, you should try acts_as_sphinx plugin.

[/lang_en]

[lang_ru]

Основные новшества Sphinx включают:

  • separate groups sorting clause in group-by mode
  • support for 1-grams, prefix and infix indexing
  • improved documentation

Теперь о Sphinx Client API для Ruby. В этой версии я решил, что нехорошо иметь разные интерфейсы в разных языка (BuildExcerpts в PHP и build_excerpts в Ruby). Потому код приложений, в которых использовали версии 0.1.0 или 0.2.0 API, необходимо пересмотреть. Детали смотрите в документации.

Изменения в Sphinx Client API для Ruby:

  • Полностью синхронизирован API с версией PHP.
  • Исправлена ошибка с обработкой атрибутов в результатах запроса (спасибо shawn).
  • Исправлена ошибка с округлением временем обработки запроса (спасибо michael).
  • Библиотека покрыта на 100% спецификациями RSpec.

Вы всегда можете загрузить последнюю версию со страницы Sphinx Client API для Ruby.

Если Вы используете Sphinx в приложении на Ruby on Rails, посмотрите плагин acts_as_sphinx.

[/lang_ru]

The post Sphinx Search Engine 0.9.7, Ruby Client API 0.3.0 first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/sphinx-search-engine-0-9-7-ruby-client-api-0-3-0/feed/ 5
Colorizing console Ruby-script output https://kpumuk.info/ruby-on-rails/colorizing-console-ruby-script-output/ https://kpumuk.info/ruby-on-rails/colorizing-console-ruby-script-output/#comments Fri, 23 Mar 2007 16:28:06 +0000 http://kpumuk.info/ruby-on-rails/colorizing-console-ruby-script-output/ Very often I have to implement console scripts (because of my laziness, for boring processes optimization). Many of them write some information to the output, show process status or display results of work. Anyway, it’s very wearisome action to read script output, and I want to highlight most important things: errors in red, successfully finished […]

The post Colorizing console Ruby-script output first appeared on Dmytro Shteflyuk's Home.]]>
Very often I have to implement console scripts (because of my laziness, for boring processes optimization). Many of them write some information to the output, show process status or display results of work. Anyway, it’s very wearisome action to read script output, and I want to highlight most important things: errors in red, successfully finished steps in green color, etc. And it is a case when ANSI escape sequences could help. They are supported by the most terminals, including VT100 (btw, Windows NT family console does not support it, but I will back to this issue later).

For the beginning, let’s examine ANSI escape sequence structure. It starts with ESC symbol (ASCII-code 27), following by left square bracket [. There are one or more numbers separated by semicolon ; with a letter at the end appear after it.

I will not describe all possible codes, anybody who wishes could find them in Wikipedia. The sequence with m letter at the end is used to change foreground and background colors. In general situation it looks like: ESC[31m, where 31 sets red color as foreground. Here the table with codes which supported by most terminals:

Code Effect
0 Turn off all attributes
1 Set bright mode
4 Set underline mode
5 Set blink mode
7 Exchange foreground and background colors
8 Hide text (foreground color would be the same as background)
30 Black text
31 Red text
32 Green text
33 Yellow text
34 Blue text
35 Magenta text
36 Cyan text
37 White text
39 Default text color
40 Black background
41 Red background
42 Green background
43 Yellow background
44 Blue background
45 Magenta background
46 Cyan background
47 White background
49 Default background color

As you could see from the table, you are able to set foreground and background colors separately, also you could combine them into one sequence (for example, ESC[1;33;44m – bright yellow text on blue background).

Attention: Don’t forget to turn off all attributes before exit, otherwise all following text would be displayed with your attributes.

That’s enough of the theory, let’s examine example:

1
2
3
4
# Actual work
puts "Importing categories [ e[32mDONEe[0m ]"
# Actual work
puts "Importing tags       [e[31mFAILEDe[0m]"

As the result you will see something like following:

VT100 Example 1

All my life I used codes just like shown in the previous example, but not so long ago I found simple helpers when delving in the RSpec sources:

1
2
3
4
5
6
7
8
9
10
11
def colorize(text, color_code)
  "#{color_code}#{text}e[0m"
end

def red(text); colorize(text, "e[31m"); end
def green(text); colorize(text, "e[32m"); end

# Actual work
puts 'Importing categories [ ' + green('DONE') + ' ]'
# Actual work
puts 'Importing tags       [' + red('FAILED') + ']'

It’s a good idea, and now I’m using it. And recommend it to you :-)

Now about sorrowful things. Windows XP (and as far as I remember, Windows 2000 too) does not support ANSI escape sequences. If you are love perversions, look at the Command Interpreter Ansi Support article. Others could stay here and look, how to solve problem using Ruby facilities.

You should install win32console first:

1
gem install win32console

Now add following lines at the beginning of your script (and again, I found them in RSpec):

1
2
3
4
5
begin
  require 'Win32/Console/ANSI' if RUBY_PLATFORM =~ /win32/
rescue LoadError
  raise 'You must gem install win32console to use color on Windows'
end

Script output will be colorized both on windows and Unix systems.

And in the end I will show full table of different codes, which you could use in your scripts:

VT100 Terminal

It has been obtained using following script:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/ruby

[0, 1, 4, 5, 7].each do |attr|
  puts '----------------------------------------------------------------'
  puts "ESC[#{attr};Foreground;Background"
  30.upto(37) do |fg|
    40.upto(47) do |bg|
      print "\033[#{attr};#{fg};#{bg}m #{fg};#{bg}  "
    end
  puts "\033[0m"
  end
end

Updated 06/10/2010: Replaced PLATFORM constant with the RUBY_PLATFORM (thanks to Ian Alexander Wood).

The post Colorizing console Ruby-script output first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/colorizing-console-ruby-script-output/feed/ 10