Why your pretty, tested, Observer might not work

I normally have my observers tested, so the announcement “there is no mail comming” was pretty irritating at first, until i noticed that it was true. So i began my search … and after quiet some time digging and trying i found that i forgot to add the new observer to the config….

Normal Observer Setup

#environment.rb
config.active_record.observers = :user_observer, ....

Automatic Observer Setup
And since i am forgetful, how about auto-loading all observers:

#environment.rb
observers = Dir.glob("#{RAILS_ROOT}/app/models/*_observer.rb").map do |file|
  File.basename(file).sub('.rb','')
end
config.active_record.observers = observers

It should behave like resource

One pillar of my high code-coverage is this little plugin, which allows you to simply state that a controller or a action should behave RESTfully.

So far it is experimental, meaning it works on my machine 🙂
Any testers and contributes highly welcome.

Install instruction and usage.

Example

describe UsersController do
  before :each do
    login_as :quentin #if your controllers need login
    @item = @user = stub_model(User)#or anything else that has a to_param method
  end

  behave_like_resource
end

Automatically reduce image sizes with smushit

UPDATE: lossless image size reduction of whole folders

smushit.com reduces image sizes dramatically, lossless!
Great for logos/icons…, but does not work for gifs!

I put together a small script to automate smushing (as long as i cannot find a popper API).

Usage
ruby smush.rb http://my.file.com/123.png where/to/save.png

Install
requires ruby and rubygems

sudo gem install json

Store this into smush.rb

optional(if this task is to full/gets blocked):
cange the &task=89266837334214400 to something else or use random

#smush.rb
def store_smushed_image(url,file)
  File.open(file,'w') do |f|
    f.puts smushed_image_data_for(url)
  end
end

def smushed_image_data_for(url)
  require 'cgi'
  url = CGI.escape url
  
  require 'net/http'
  require 'rubygems'
  require 'json'
  
  http = Net::HTTP.new('smushit.com')
  path = "/ws.php?img=#{url}&task=89266837334214400&id=paste2"
  
  resp, data = http.get(path, nil)
  raise "oops #{resp}" unless resp.is_a? Net::HTTPOK
  
  path = "/#{JSON.parse(data)['dest']}"
  resp, data = http.get(path, nil)
  data
end

#http://smushit.com/ws.php?img=http%3A%2F%2Fwww.famfamfam.com%2Flab%2Ficons%2Fsilk%2Ficons%2Fdrink_empty.png&task=89266837334214400&id=paste2
url = ARGV[0] || "http://www.famfamfam.com/lab/icons/silk/icons/drink_empty.png"
file = ARGV[1] || 'out.png'
store_smushed_image(url,file)

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