Happy hacking with Conditions, Count, Group, Joins, Random, Scope and Select

UPDATE: use http://github.com/grosser/random_records for random

This morning i wanted to:

  • Select records
  • Scoped
  • Random
  • Unique
  • Joined through 2 tables

The scope
Person.filmmakers

named_scope :filmmakers, :joins=>{:team_memberships=>:movie}, 
  :conditions=>{'movies.status'=>'online'}, 
  :group=>'people.id'

So we want people that participated in a movie, and add group so that there are no duplicates.

Counting
Person.filmmakers.count

Does not work! Count discards group, since cont(:group=>something) would result in [[a,number_of_records_with_a],…]
So lets add a counting that works as expected, by returning the sum of all count results(sum of the distinct values).

scope_from_above do
  def count
    super(:group=>'people.id').size
  end
end

Random
Person.filmakers.random(3)

Normally random looks like this.

#UPDATE: use http://github.com/grosser/random_records for random 
class ActiveRecord::Base
  def self.random(num=1,options={})
    return [] if num.zero?
    num_records = count
    find(:all, {:offset => [rand(num_records),num_records-num].min, 
      :limit=>num}.merge(options))
  end
end

It does not use the scope count that we defined, but the regular count, so we have to pass in count separately.
And it returns records where the id is set to Movie.count or the last movie id, very strange.

Final scope and random

named_scope :filmmakers, :joins=>{:team_memberships=>:movie}, 
  :conditions=>{'movies.status'=>'online'}, 
  :group=>'people.id' do
  def count
    super(:group=>'people.id').size
  end
    
  def random(num=1,options={})
    #select people.* or id will always be Movie.count WTF
    super(num,options.merge(:count=>count,:select=>'people.*'))
  end
end

#UPDATE: use http://github.com/grosser/random_records for random 
class ActiveRecord::Base
  def self.random(num=1,options={})
    return [] if num.zero?
    num_records = count
    find(:all, {:offset => [rand(num_records),num_records-num].min, 
      :limit=>num}.merge(options))
  end
end

Enhanced RSpec Profiling

If normal rspec profile output is not enought, try this enhanced rspec profile formatter!

Example

Groups:
5.1150300 Movie
4.9603220 Icon
1.7279670 User
1.4466160 Person
...

Single examples:
4.8707710 Icon refresh! resizes existing thumbs
0.9086340 Review releases the movie
0.5203390 Movie finds invalid
...

Install

script/plugin install script/plugin install git://github.com/grosser/rspec_enhanced_profile.git

#add to spec/spec.opts:
--format RspecEnhancedProfile:tmp/profile.txt

Profiling RSpec

Small option, but took me 2 hours to find :/
This will automatically generate reports for your test-runs.

It only shows single examples, which is sad since i would also want to know which controller_specs take the longest…

Output:

Top 10 slowest examples:
0.5215740 User downgrades to a person
0.4326950 User finds invalid
0.1914630 User remembering unsets remember token
0.1218360 User is valid
0.0903790 Festival is valid

Install:

#spec/spec.opts
--format profile:spec/profile.txt

Record Gettext at Test Runtime

Recently our team got frustrated with all the phrases gettext would not find.

  • words in if blocks
  • word produced by helpers
  • words used in arrays that get translated at runtime

Here is a simple solution: collect all phrases that were used during testing. Since the testsuite often has  100% C0 coverage and any phrase that is not found will signals missing tests.

Install

#spec/spec_helper.rb or test/test_helper.rb
if ENV['LOG_GETTEXT']
  def _(word)
    File.open(ENV['LOG_GETTEXT'], File::WRONLY|File::APPEND|File::CREAT) do |f|
      f.puts word
    end
    gettext(word)
  end
end


#lib/taskts/gettext_test_log.rb
task :gettext_test_log do
  tmpfile = 'locale/tmp_gettext_test_log.txt'
  outfile = "app/testlog_phrases.rb"
  
#  system "rake test LOG_GETTEXT=#{tmpfile}"
  system "rake spec LOG_GETTEXT=#{tmpfile}"
  process_log(tmpfile,outfile)
end

def process_log(tmpfile,outfile)
  found = {}
  File.readlines(tmpfile).each do |line|
    line.strip!
    next if line.empty?
    next if line =~ /%s of /
    next if %w[nil Sun Mon Tue Wed Thu Fri Sat Sunday Monday Tuesday Wednesday Thursday  Friday  Saturday  Jan Feb Mar  Apr Jun  Jul Aug Sep Oct Nov Dec January February March April May June July August September October November December].include? line
    found[line]=true
  end
  
  File.open(outfile,'w') do |f|
    found.each do |k,v|
      f.puts "_('#{k}')"
    end
  end
end

Usage
The generated testlog_phrases.rb should be placed in a folder that is search by your updatepo task(it is not meant to be executed).