mek's ruby & co. notes http://blog.mekdigital.com Most recent posts at mek's ruby & co. notes posterous.com Wed, 21 Dec 2011 10:50:00 -0800 batch Image resize? http://blog.mekdigital.com/batch-image-resize http://blog.mekdigital.com/batch-image-resize

Today I almost opened photoshop to resize a bunch of images, but then I rememebered: magick!

#!/bin/bash
for i in *.jpg ; do
 convert "$i" -resize 720x720 "$i"
done

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Mon, 19 Dec 2011 11:02:00 -0800 Another Lesson Learned http://blog.mekdigital.com/another-lesson-learned http://blog.mekdigital.com/another-lesson-learned

Don't ever assume that Rails 2.3.5 and 2.3.9 are interchangeable just because for months everything worked perfectly in development, test, qa and staging...

Production Day will teach you the hard way!

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Mon, 12 Sep 2011 15:51:00 -0700 All I got was a 404! (URI::NotFoundError) trying to bundle rails http://blog.mekdigital.com/all-i-got-was-a-404-urinotfounderror-trying-t http://blog.mekdigital.com/all-i-got-was-a-404-urinotfounderror-trying-t

Today I was playing around with R 3.1 and suddenly I wanted to submit a patch. Following the guide I tried to 'bundle' to be able to run the tests. OOPS!

Well, if your gem building joy is interrupted by a 404 trying to fetch the ruby source mentioned in your RVM, (for me Looking for http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-p274.tar.gz), you can use a dirty workaround and install the specific gem with a flag:

gem install some_gem -- --with-ruby-include=/your_home/.rvm/src/ruby-1.9.2-head

 

/peace

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Mon, 18 Jul 2011 01:47:00 -0700 An autocomplete middleware for Sinatra & jQuery http://blog.mekdigital.com/an-autocomplete-middleware-for-sinatra-jquery http://blog.mekdigital.com/an-autocomplete-middleware-for-sinatra-jquery

An autocomplete script is based on a dom element that uses javascript to generate asynchronous requests to a remote service and parse the response to offer matching choices to the user, if any is found.

Let's use a trivial Player model with a username method and build an autocomplete field for it.

View

1
2
3
<input type="text" id="player_username" name="player_username" class="autocomplete" autocomplete="off" klass="player" method="username">
<div id="player_username_autocomplete" class="autocomplete" style="display: none;"></div>

we need to specify three things for the input field: it must have `autocomplete` as class attribute so that jQuery will be able to attach events to it, and specify the ruby class and method (klass, method) we want to invoke. It's also a good idea to disable the browser autocomplete feature that would interfere with our autocomplete. Specifying klass and method may seem redundant as they could appear in the ID, but it's not: player_picture_count is an example of ambiguous ID. 

Javascript

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
$(document).ready(function() {
  autocomplete_fields()
});

function autocomplete_fields(){

  $('input.autocomplete').each(function(){

    // lost focus: hide suggestions and select current option
    $(this).focusout(function(){
      hide_and_complete(this);
      return false;
    });

    // mouse up: hide suggestion and select current option
    $(this).mouseup(function(){
        hide_and_complete(this);
        return false;
    });

    // keypress is only used for the return key
    $(this).keypress(function(event){
      if (event.keyCode==13){
        hide_and_complete(this);
        return false;
      }
    });

    $(this).keyup(function(event){
      if(event.keyCode==38){
        var index = find_index('div.autocomplete ul li');
        if (--index < 0){ index = options_count(this)-1}
        $(autocomplete_id(this) + '_' + (index)).addClass('selected').siblings().removeClass('selected')
      }
      if (event.keyCode==40){
        var index = find_index('div.autocomplete ul li');
        if (++index >= options_count(this)){ index = 0; }
        $(autocomplete_id(this) + '_' + (index)).addClass('selected').siblings().removeClass('selected')
      }
      // letters, numbers and backspace trigger the autocomplete search
      if ($(this).val().length >= 2 && ((event.keyCode>45 && event.keyCode<106) || event.keyCode==8)){
        $.get(autocomplete_url(this), null, null, "script");
      }
    return false;
    });

  });
}

function find_index(el){
 var str = $('li.selected');
 return $(el).index(str);
}

function hide_and_complete(el){
  $(el).val($(autocomplete_id(el) + ' .selected').html());
  $(autocomplete_id(el)).hide();
}

function autocomplete_id(el){
  return '#' + $(el).attr('klass') + '_' + $(el).attr('method') + '_autocomplete'
}

function autocomplete_url(el){
  return '/autocomplete/' + $(el).attr('klass') + '/' + $(el).attr('method') + '/' + $(el).val()
}

function options_count(el){
  return $(autocomplete_id(el) + ' ul li').size()
}

the autocomplete javascript attaches and handles keyboard and mouse events for all the input fields of `autocomplete` class and the associated autocomplete list div. In our example, the player_username field will generate requests to /autocomplete/player/username/query when the length of the input is at least 2 and the ASCII code for the pressed key is between 45 and 106 (letters, numbers and some symbols), or a backspace. Arrows, clicks and tabs are used to interact with the autocomplete list.

Middleware

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
class Autocomplete < App
  get "/autocomplete/:klass/:method/:query/?" do |k, m, q|
    ensure_autocompletable(k,m)
    collection = constantize(classify(k)).all(:select => m,
                                              :conditions => ["#{m} like ?", "#{q}%"],
                                              :limit => 10,
                                              :order => "#{m} ASC")
    if collection.empty?
      list_items = '<li>no results</li>'
    else
      list_items = ''
      collection.each_with_index{|p,idx| list_items << "<li #{'class=\'selected\'' if idx.zero?} id='#{k}_#{m}_autocomplete_#{idx}'>#{p.send(m)}</li>"}
    end
    erb :'autocomplete/list', :locals => {:list_items => list_items, :klass => k, :method => m}, :layout => false
  end

  protected
  def ensure_autocompletable(k,m)
    @allow ||= {
      'player' => %w(username)
    }
    unless @allow[k] && @allow[k].include?(m)
      status 501; halt
    end
  end
end

the middleware responds to autocomplete requests with some magic*. It's important to whitelist every single class and method that are allowed to avoid issues: the ensure_autocompletable will halt the execution otherwise. There are two possibliy configurable elements in the query: the search by wildcard - that is usually a use case expected by the user - and a limit of 10 items to return. Also, generating HTML here... TODO: refactor.

Partial

1
2
3
4
5
6
$('#<%= klass %>_<%= method %>_autocomplete').html("<ul><%= list_items %></ul>");
$('#<%= klass %>_<%= method %>_autocomplete ul li').each(function(){
$(this).mouseover(function (){ $(this).addClass('selected').siblings().removeClass('selected');});
});
$('#<%= klass %>_<%= method %>_autocomplete').show();

the js partial rendered by the autocomplete simply populates the autocomplete div with the result from the query and attaches some event handling for highlighting the currently selected item in the autocomplete list.

Main App

1
2
3
4
5
6
7
class App < Sinatra::Base

  [omissis]

  require_relative 'autocomplete'
  use Autocomplete
end

That's about it!

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Fri, 24 Jun 2011 01:09:00 -0700 Working on Something.. http://blog.mekdigital.com/working-on-something http://blog.mekdigital.com/working-on-something

Screen_shot_2011-06-24_at_1

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Tue, 14 Jun 2011 12:13:00 -0700 do it often, do it everywhere http://blog.mekdigital.com/do-it-often-do-it-everywhere http://blog.mekdigital.com/do-it-often-do-it-everywhere

Cleanup

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Thu, 02 Jun 2011 20:01:00 -0700 using WillPaginate with Sinatra http://blog.mekdigital.com/using-willpaginate-with-sinatra http://blog.mekdigital.com/using-willpaginate-with-sinatra

The use of the will_paginate gem with Sinatra is trivial, all you need to do is instruct your ORM about pagination and make the helpers available to the views. In addition to this, you also need to implement the url method for the LinkRenderer class. This snippet assumes the use or ActiveRecord :)

This is what I was thinking... Google it, find it, put it in your code and it should work..

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

## DON'T USE ME, I HAVE A BUG!! ##

configure do
  # ActiveRecord
  require 'will_paginate'
  require "will_paginate/finders/active_record"

  WillPaginate::Finders::ActiveRecord.enable!

  # Views
  require 'will_paginate/view_helpers/base'
  require 'will_paginate/view_helpers/link_renderer'

  WillPaginate::ViewHelpers::LinkRenderer.class_eval do
    protected
    def url(page)
      url = @template.request.url
      if page == 1
        # strip out page param and trailing ? if it exists
        url.gsub(/page=[0-9]+/, '').gsub(/\?$/, '')
      else
        if url =~ /page=[0-9]+/
          url.gsub(/page=[0-9]+/, "page=#{page}")
        else
          url + "?page=#{page}"
        end
      end
    end
  end

  include WillPaginate::ViewHelpers::Base
end

Run this snippet and you will see what we're talking about!

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def original_get_url(page, url)
  if page == 1
    # strip out page param and trailing ? if it exists
    url.gsub(/page=[0-9]+/, '').gsub(/\?$/, '')
  else
    if url =~ /page=[0-9]+/
      url.gsub(/page=[0-9]+/, "page=#{page}")
    else
      url + "?page=#{page}"
    end
  end
end


def get_url(page, url)
  if page == 1
    # strip out page param and trailing ? if it exists
    url.gsub(/[\?&]page=[0-9]*/, '').gsub(/\?/,'&').sub(/&/, '?')
  else
    if url =~ /[\?&]page=[0-9]*/
      url.gsub(/([\?&])page=[0-9]*/, '\1' + "page=#{page}")
    else
      url + (url.include?('?') ? "&page=#{page}" : "?page=#{page}")
    end
  end
end

possible_urls = ["mypage?per_page=9&time=off",
                 "mypage?time=off",
                 "mypage?page=10&per_page=9&time=off",
                 "mypage?per_page=9&page=10",
                 "mypage?per_page=9&page=10&time=off",
                 "mypage?page=10",
                 "mypage?page="]

puts "Test URLS"
puts possible_urls
puts
puts "Parsing the URLs with the original url() method"
puts
1.upto(5) do |page|
  puts "Page: #{page}"
  possible_urls.each do |url|
    puts original_get_url(page, url)
  end
  puts
end

puts
puts
puts "Parsing the URLs with the modified url() method"
puts
1.upto(5) do |page|
  puts "Page: #{page}"
  possible_urls.each do |url|
    puts get_url(page, url)
  end
  puts
end

This seems to be working:

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
configure do
  # Pagination
  require 'will_paginate'
  require "will_paginate/finders/active_record"

  WillPaginate::Finders::ActiveRecord.enable!

  require 'will_paginate/view_helpers/base'
  require 'will_paginate/view_helpers/link_renderer'

  WillPaginate::ViewHelpers::LinkRenderer.class_eval do
    protected
    def url(page)
      url = @template.request.url
      if page == 1
        # strip out page param and trailing ? if it exists
        url.gsub(/[\?&]page=[0-9]*/, '').gsub(/\?/,'&').sub(/&/, '?')
      else
        if url =~ /[\?&]page=[0-9]*/
          url.gsub(/([\?&])page=[0-9]*/, '\1' + "page=#{page}")
        else
          url + (url.include?('?') ? "&page=#{page}" : "?page=#{page}")
        end
      end
    end
  end

  include WillPaginate::ViewHelpers
  include WillPaginate::ViewHelpers::Base
end

 

 

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Thu, 31 Mar 2011 11:06:00 -0700 Hash_All is now available as a Gem http://blog.mekdigital.com/hashall-is-now-available-as-a-gem http://blog.mekdigital.com/hashall-is-now-available-as-a-gem

The simple logic of the initial library is now much simpler. I am now using this gem in all my projects and it saves me a lot of lines of code!

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
module HashAll

  class ActiveRecord::Base
    
    # Wrapper/helper for ActiveRecord <tt>find(:all, *args)</tt>. Returns a collection of
    # objects injected into a Hash. The attribute used as a key for the Hash should be
    # specified as first argument. if :fetch is not specified in the arguments the whole
    # object will be assigned as value, otherwise only the attribute spefified.
    # You can pass in all the same arguments to this method as you can to <tt>find(:all)</tt>.
    #
    # ==== Example
    #
    # Given the models:
    #
    # Feed(id: integer, state: string, name: string)
    # Post(id: integer, feed_name: string, title: string, url: string)
    #
    # count the number of posts of every state. Row count is too high to use SQL JOIN.
    #
    # @post_per_state = {}
    # feeds = Feed.hash_all('feed_name', :fetch => 'state')
    # posts = Post.all(:select => 'COUNT(id), feed_name',
    # :group => 'feed_name')
    # posts.each do |p|
    # @post_per_state[feeds[p.feed_name]] ||= 0
    # @post_per_state[feeds[p.feed_name]] += p.attributes['COUNT(id)'].to_i
    # end
    #
    # => {"new york"=>6, "georgia"=>11, "new mexico"=>2, ... }
    
    def self.hash_all(hash_key='id', args={})
      fetch = args.delete(:fetch)
      collection = find(:all, args)
      return {} if collection.empty?
      collection.each.inject({}) { |hash,obj| hash[obj.send(hash_key)] = (fetch.nil? ? obj : obj.send(fetch)); hash}
    end
  
  end

end

Comments and suggestions are more than welcome!

 

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Thu, 17 Mar 2011 16:53:28 -0700 find a model by id or another (non integer) column using params[:value] http://blog.mekdigital.com/easy-loading-user-by-id-or-username http://blog.mekdigital.com/easy-loading-user-by-id-or-username

why making it complicated? 

1
2
3
4
5
6
7
8
def load_user
  if params[:id] =~ /\D+/
    @user = User.find_by_username(params[:id])
  else
    @user = User.find_by_id(params[:id])
  end
  raise ActiveRecord::RecordNotFound if @user.nil?
end

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Thu, 24 Feb 2011 00:18:00 -0800 Getting ready for coding a Rails patch http://blog.mekdigital.com/getting-ready-for-coding-a-rails-patch http://blog.mekdigital.com/getting-ready-for-coding-a-rails-patch

I find myself using the hash_all ActiveRecord method so often I decided to try evolving that simple hack into a Rails feature. Where should I start from ?

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Wed, 15 Dec 2010 18:03:00 -0800 ActiveRecord: hash_all! http://blog.mekdigital.com/activerecord-hashall http://blog.mekdigital.com/activerecord-hashall

During hard times where JOINS are not allowed and performance matters (i.e. don't create objects unless is really necessary), too many times I've been writing ActiveRecord find all, loop on the collection and return an hash where key is some column of the object...

Classic Scenario:

find :messages, :include :sender, :include :recipient

This will usually result in a reasonable query, but as the complexity grows things can get out of control with AR generating unnecessary db calls.

Optimized Scenario:

find :messages

find :users with ids in messages.map(&:sender_id) + messages.map(&:recipient_id)

and finally loop on messages and assign sender and recipient names as attr of the message, but this is still expensive.

Optimized Scenario V2:

find :messages

find users with hash_all on users passing once again the condition of ids being in messages.map(&:sender_id) + messages.map(&:recipient_id)

finally users[msg.sender_id] and users[msg. recipient_id] will be available at no additional cost.

1
2
3
4
5
6
7
8
9
10
11
12
class ActiveRecord::Base

  def self.hash_all(hash_key, *args)
    hash = {}
    collection = find(:all, *args)
    unless collection.empty?
      collection.each_with_index {|el, idx| hash[el.send(hash_key) || idx] = el}
    end
    hash
  end

end

 

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Fri, 19 Nov 2010 11:11:13 -0800 Heroku: Switching between credentials http://blog.mekdigital.com/heroku-switching-between-credentials http://blog.mekdigital.com/heroku-switching-between-credentials

Do you have two accounts, one for experiments and one for production? I do and at day one I got tired of editing the configuration file so I came up with a very simple script (that does not scale..):

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
if cat ~/.heroku/credentials | grep 654321 > /dev/null
then
echo "me@gmail.com" > ~/.heroku/credentials
 echo "123456" >> ~/.heroku/credentials
else
echo "myself@gmail.com" > ~/.heroku/credentials
 echo "654321" >> ~/.heroku/credentials
fi
cat ~/.heroku/credentials

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Tue, 07 Sep 2010 23:32:00 -0700 DreamHost Series: cronjob and custom gems http://blog.mekdigital.com/dreamhost-series-cronjob-and-custom-gems http://blog.mekdigital.com/dreamhost-series-cronjob-and-custom-gems

my DreamHostPS (private server) was so loaded with patches and hacks I decided to request a system reset: all my (root) configuration would be lost and it was two years of little tricks discovered, applied and forgotten!

After compiling and installing ruby-1.9.2 I found myself in trouble with cronjob: it could not see the gem installed in my home directory! CRON does not execute any user's .profile: PATH and GEM_PATH where off to the point that it was using ruby 1.8.7! =) 

I had probably created some .profile for the CRON user, but I don't know where so I good idea is to specify the required variables at the top of the file

 

RAILS_ENV=production

PATH=/usr/local/bin:/usr/bin:/bin

GEM_PATH=/home/etozzato/.gems:/usr/lib/ruby/gems/1.9.1

MAILTO=""

*/5 * * * * /home/etozzato/my/bin/update

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Sat, 04 Sep 2010 14:23:00 -0700 Totally Awesome! File-stored multi application cron job-based rakes! (WTF?) http://blog.mekdigital.com/totally-awesome-file-stored-multi-application http://blog.mekdigital.com/totally-awesome-file-stored-multi-application

Well, this was not easy to explain in a simple title! In short, I deployed 30 applications that need to run (very frequently) a rake task each. The first attempt to put 30 lines in the crontab totally failed: the server ran out of memory as expected. An ideal solution, given I have an array with the names of the applications, is to invoke a script able to route the request to a different application for each call:

 

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

apps = %w(my-audi my-bmw my-ferrari my-ford my-jeep my-landrover my-toyota my-volvo myacura mycadillac mychevrolet mychrysler mydodge mygmc myhonda myhummer myjaguar mylexus mylotus mymaserati mymazda mymercedes-benz mymini mymitsubishi mynissan myporsche myrover mysaab mysmart mysubaru myvolkswagen)

if File.exist?('/home/etozzato/my/logs/feed.id')
  feed_id = File.read('/home/etozzato/my/logs/feed.id').to_i
  `echo '#{feed_id + 1}' > /home/etozzato/my/logs/feed.id`
  if feed_id > webs.size-1
    feed_id = 0
    `echo '#{feed_id + 1}' > /home/etozzato/my/logs/feed.id`
  end
else
  feed_id = 0
  `echo '#{feed_id + 1}' > /home/etozzato/my/logs/feed.id`
end

puts `export RAILS_ENV=production && cd /home/etozzato/my/#{apps[feed_id]}.us && rake update_feeds`

 

1
*/3 * * * * /home/etozzato/my/bin/update

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Mon, 30 Aug 2010 12:33:00 -0700 Save memory using class caching! http://blog.mekdigital.com/save-memory-using-class-caching http://blog.mekdigital.com/save-memory-using-class-caching

When performances become a real issue you can start looking around in your code and optimize a lot. This model was previously fetching team name and nickname from the database, but those values never changed. 

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class Team

  def self.nickname_by_id(id=nil)
    return 'n/a' unless id
    @nicknames ||= ["Orioles",
                    "Red Sox",
                    "Yankees",
                    "Rays",
                    "Blue Jays",
                    "White Sox",
                    "Indians",
                    "Tigers",
                    "Royals",
                    "Twins",
                    "LA Angels",
                    "Athletics",
                    "Mariners",
                    "Rangers",
                    "Braves",
                    "Marlins",
                    "Nationals",
                    "Mets",
                    "Phillies",
                    "Cubs",
                    "Reds",
                    "Astros",
                    "Brewers",
                    "Pirates",
                    "Cardinals",
                    "Diamondbacks",
                    "Rockies",
                    "Dodgers",
                    "Padres",
                    "Giants"]
    @nicknames.at(id)
  end

  def self.name_by_id(id=nil)
    return 'n/a' unless id
    @names ||= ["Baltimore Orioles",
                "Boston Red Sox",
                "New York Yankees",
                "Tampa Bay Rays",
                "Toronto Blue Jays",
                "Chicago White Sox",
                "Cleveland Indians",
                "Detroit Tigers",
                "Kansas City Royals",
                "Minnesota Twins",
                "Los Angeles Angels",
                "Oakland Athletics",
                "Seattle Mariners",
                "Texas Rangers",
                "Atlanta Braves",
                "Florida Marlins",
                "Washington Nationals",
                "New York Mets",
                "Philadelphia Phillies",
                "Chicago Cubs",
                "Cincinnati Reds",
                "Houston Astros",
                "Milwaukee Brewers",
                "Pittsburgh Pirates",
                "St. Louis Cardinals",
                "Arizona Diamondbacks",
                "Colorado Rockies",
                "Los Angeles Dodgers",
                "San Diego Padres",
                "San Francisco Giants"]
    @names.at(id)
  end

end

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Tue, 24 Aug 2010 06:50:00 -0700 How to Memcached a POST form submission http://blog.mekdigital.com/how-to-memcached-a-post-form-submission http://blog.mekdigital.com/how-to-memcached-a-post-form-submission

Memcached is a wonderful tool, but a standard integration with Rails will raise the issue of performing caching based on the uri of the request, hence, if you activate caching for an action that handles POST requests you will be in trouble: users will receive a result cached for someone else until the expiration. 

I did not like the idea of switching to a GET request: it's widely used, but it can get ugly. My approach is to serialize the values of the form into a md5'ed string and append it to the form action url.

 

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// NOTE: all the form items call make_ssid(); onChange

function true_false(value){
  if (value == false)
   return '0'
  else
   return '1'
}

function make_ssid(){
  var form_action = $("search_form").action
  var ssid = ''
  form_action = form_action.replace(/\?.+/,'')
  // selects
  $$("form select").each(function(p){ ssid = ssid + p.selectedIndex})
  // radio buttons and checkboxes
  $$("input[type='checkbox'], input[type='radio']").each(function(p){ ssid = ssid + true_false(p.checked)})
  // one text field by id
  $("search_form").action = form_action + '?ssid=' + md5(ssid+$('query').value)
}

// Calculate the md5 hash of a string
// version: 1008.1718
// discuss at: http://phpjs.org/functions/md5
function md5(str) {
    var xl;
    var rotateLeft = function (lValue, iShiftBits) {
        return (lValue<<iShiftBits) | (lValue>>>(32-iShiftBits));
    };
    var addUnsigned = function (lX,lY) {
        var lX4,lY4,lX8,lY8,lResult;
        lX8 = (lX & 0x80000000);
        lY8 = (lY & 0x80000000);
        lX4 = (lX & 0x40000000);
        lY4 = (lY & 0x40000000);
        lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF);
        if (lX4 & lY4) {
            return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
        }
        if (lX4 | lY4) {
            if (lResult & 0x40000000) {
                return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
            } else {
                return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
            }
        } else {
            return (lResult ^ lX8 ^ lY8);
        }
    };
    var _F = function (x,y,z) { return (x & y) | ((~x) & z); };
    var _G = function (x,y,z) { return (x & z) | (y & (~z)); };
    var _H = function (x,y,z) { return (x ^ y ^ z); };
    var _I = function (x,y,z) { return (y ^ (x | (~z))); };
    var _FF = function (a,b,c,d,x,s,ac) {
        a = addUnsigned(a, addUnsigned(addUnsigned(_F(b, c, d), x), ac));
        return addUnsigned(rotateLeft(a, s), b);
    };
    var _GG = function (a,b,c,d,x,s,ac) {
        a = addUnsigned(a, addUnsigned(addUnsigned(_G(b, c, d), x), ac));
        return addUnsigned(rotateLeft(a, s), b);
    };
    var _HH = function (a,b,c,d,x,s,ac) {
        a = addUnsigned(a, addUnsigned(addUnsigned(_H(b, c, d), x), ac));
        return addUnsigned(rotateLeft(a, s), b);
    };
    var _II = function (a,b,c,d,x,s,ac) {
        a = addUnsigned(a, addUnsigned(addUnsigned(_I(b, c, d), x), ac));
        return addUnsigned(rotateLeft(a, s), b);
    };
    var convertToWordArray = function (str) {
        var lWordCount;
        var lMessageLength = str.length;
        var lNumberOfWords_temp1=lMessageLength + 8;
        var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64;
        var lNumberOfWords = (lNumberOfWords_temp2+1)*16;
        var lWordArray=new Array(lNumberOfWords-1);
        var lBytePosition = 0;
        var lByteCount = 0;
        while ( lByteCount < lMessageLength ) {
            lWordCount = (lByteCount-(lByteCount % 4))/4;
            lBytePosition = (lByteCount % 4)*8;
            lWordArray[lWordCount] = (lWordArray[lWordCount] | (str.charCodeAt(lByteCount)<<lBytePosition));
            lByteCount++;
        }
        lWordCount = (lByteCount-(lByteCount % 4))/4;
        lBytePosition = (lByteCount % 4)*8;
        lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition);
        lWordArray[lNumberOfWords-2] = lMessageLength<<3;
        lWordArray[lNumberOfWords-1] = lMessageLength>>>29;
        return lWordArray;
    };
    var wordToHex = function (lValue) {
        var wordToHexValue="",wordToHexValue_temp="",lByte,lCount;
        for (lCount = 0;lCount<=3;lCount++) {
            lByte = (lValue>>>(lCount*8)) & 255;
            wordToHexValue_temp = "0" + lByte.toString(16);
            wordToHexValue = wordToHexValue + wordToHexValue_temp.substr(wordToHexValue_temp.length-2,2);
        }
        return wordToHexValue;
    };
    var x=[],
        k,AA,BB,CC,DD,a,b,c,d,
        S11=7, S12=12, S13=17, S14=22,
        S21=5, S22=9 , S23=14, S24=20,
        S31=4, S32=11, S33=16, S34=23,
        S41=6, S42=10, S43=15, S44=21;
    // str = this.utf8_encode(str);
    x = convertToWordArray(str);
    a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
    
    xl = x.length;
    for (k=0;k<xl;k+=16) {
        AA=a; BB=b; CC=c; DD=d;
        a=_FF(a,b,c,d,x[k+0], S11,0xD76AA478);
        d=_FF(d,a,b,c,x[k+1], S12,0xE8C7B756);
        c=_FF(c,d,a,b,x[k+2], S13,0x242070DB);
        b=_FF(b,c,d,a,x[k+3], S14,0xC1BDCEEE);
        a=_FF(a,b,c,d,x[k+4], S11,0xF57C0FAF);
        d=_FF(d,a,b,c,x[k+5], S12,0x4787C62A);
        c=_FF(c,d,a,b,x[k+6], S13,0xA8304613);
        b=_FF(b,c,d,a,x[k+7], S14,0xFD469501);
        a=_FF(a,b,c,d,x[k+8], S11,0x698098D8);
        d=_FF(d,a,b,c,x[k+9], S12,0x8B44F7AF);
        c=_FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1);
        b=_FF(b,c,d,a,x[k+11],S14,0x895CD7BE);
        a=_FF(a,b,c,d,x[k+12],S11,0x6B901122);
        d=_FF(d,a,b,c,x[k+13],S12,0xFD987193);
        c=_FF(c,d,a,b,x[k+14],S13,0xA679438E);
        b=_FF(b,c,d,a,x[k+15],S14,0x49B40821);
        a=_GG(a,b,c,d,x[k+1], S21,0xF61E2562);
        d=_GG(d,a,b,c,x[k+6], S22,0xC040B340);
        c=_GG(c,d,a,b,x[k+11],S23,0x265E5A51);
        b=_GG(b,c,d,a,x[k+0], S24,0xE9B6C7AA);
        a=_GG(a,b,c,d,x[k+5], S21,0xD62F105D);
        d=_GG(d,a,b,c,x[k+10],S22,0x2441453);
        c=_GG(c,d,a,b,x[k+15],S23,0xD8A1E681);
        b=_GG(b,c,d,a,x[k+4], S24,0xE7D3FBC8);
        a=_GG(a,b,c,d,x[k+9], S21,0x21E1CDE6);
        d=_GG(d,a,b,c,x[k+14],S22,0xC33707D6);
        c=_GG(c,d,a,b,x[k+3], S23,0xF4D50D87);
        b=_GG(b,c,d,a,x[k+8], S24,0x455A14ED);
        a=_GG(a,b,c,d,x[k+13],S21,0xA9E3E905);
        d=_GG(d,a,b,c,x[k+2], S22,0xFCEFA3F8);
        c=_GG(c,d,a,b,x[k+7], S23,0x676F02D9);
        b=_GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A);
        a=_HH(a,b,c,d,x[k+5], S31,0xFFFA3942);
        d=_HH(d,a,b,c,x[k+8], S32,0x8771F681);
        c=_HH(c,d,a,b,x[k+11],S33,0x6D9D6122);
        b=_HH(b,c,d,a,x[k+14],S34,0xFDE5380C);
        a=_HH(a,b,c,d,x[k+1], S31,0xA4BEEA44);
        d=_HH(d,a,b,c,x[k+4], S32,0x4BDECFA9);
        c=_HH(c,d,a,b,x[k+7], S33,0xF6BB4B60);
        b=_HH(b,c,d,a,x[k+10],S34,0xBEBFBC70);
        a=_HH(a,b,c,d,x[k+13],S31,0x289B7EC6);
        d=_HH(d,a,b,c,x[k+0], S32,0xEAA127FA);
        c=_HH(c,d,a,b,x[k+3], S33,0xD4EF3085);
        b=_HH(b,c,d,a,x[k+6], S34,0x4881D05);
        a=_HH(a,b,c,d,x[k+9], S31,0xD9D4D039);
        d=_HH(d,a,b,c,x[k+12],S32,0xE6DB99E5);
        c=_HH(c,d,a,b,x[k+15],S33,0x1FA27CF8);
        b=_HH(b,c,d,a,x[k+2], S34,0xC4AC5665);
        a=_II(a,b,c,d,x[k+0], S41,0xF4292244);
        d=_II(d,a,b,c,x[k+7], S42,0x432AFF97);
        c=_II(c,d,a,b,x[k+14],S43,0xAB9423A7);
        b=_II(b,c,d,a,x[k+5], S44,0xFC93A039);
        a=_II(a,b,c,d,x[k+12],S41,0x655B59C3);
        d=_II(d,a,b,c,x[k+3], S42,0x8F0CCC92);
        c=_II(c,d,a,b,x[k+10],S43,0xFFEFF47D);
        b=_II(b,c,d,a,x[k+1], S44,0x85845DD1);
        a=_II(a,b,c,d,x[k+8], S41,0x6FA87E4F);
        d=_II(d,a,b,c,x[k+15],S42,0xFE2CE6E0);
        c=_II(c,d,a,b,x[k+6], S43,0xA3014314);
        b=_II(b,c,d,a,x[k+13],S44,0x4E0811A1);
        a=_II(a,b,c,d,x[k+4], S41,0xF7537E82);
        d=_II(d,a,b,c,x[k+11],S42,0xBD3AF235);
        c=_II(c,d,a,b,x[k+2], S43,0x2AD7D2BB);
        b=_II(b,c,d,a,x[k+9], S44,0xEB86D391);
        a=addUnsigned(a,AA);
        b=addUnsigned(b,BB);
        c=addUnsigned(c,CC);
        d=addUnsigned(d,DD);
    }
    var temp = wordToHex(a)+wordToHex(b)+wordToHex(c)+wordToHex(d);
    return temp.toLowerCase();
}

 

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Mon, 16 Aug 2010 12:00:00 -0700 what's your filename ? TextMate http://blog.mekdigital.com/whats-your-filename-textmate http://blog.mekdigital.com/whats-your-filename-textmate

as easy as sudo rm -Rf / and you'll have the filename in your clipboard. removing the path to the file is optional but that's how I needed that day

#!/usr/bin/env ruby

`echo #{ENV['TM_FILEPATH'].split('/').last.strip} | pbcopy`

update

echo $TM_FILENAME | pbcopy

;) there is no need to invoke Ruby. There is no need to parse  TM_FILEPATH when you have TM_FILENAME...

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Sat, 14 Aug 2010 00:59:00 -0700 ExpiresAt for ActionController (cache) http://blog.mekdigital.com/expiresat-for-actioncontroller-cache http://blog.mekdigital.com/expiresat-for-actioncontroller-cache

using expires_in seconds can be a pain when you need to expire some cache daily at a defined time - let's say after some background jobs are completed...

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
# expires_at '21:30', :public => true, :private => false
# will set max-age to the amount of seconds between now and tomorrow at 9:30PM

module ExpiresAt
  
  def self.included(base)
    base.send(:include, InstanceMethods)
  end
  
  module InstanceMethods
    def expires_at(time='00:00', options = {})
      h, m = time.to_s.split(':')
      h = h.to_i
      m = m.to_i
      h = 0 unless h.between?(0, 23)
      m = 0 unless m.between?(0, 59)
      seconds = ((Time.now.end_of_day + h.hours + m.minutes) - Time.now).to_i
      cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip }
      cache_control << "max-age=#{seconds}"
      cache_control.delete("no-cache")
      if options[:public]
        cache_control.delete("private")
        cache_control << "public"
      else
        cache_control << "private"
      end

      cache_control += options.symbolize_keys.reject{|k,v| k == :public || k == :private }.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}

      response.headers["Cache-Control"] = cache_control.join(', ')
    end
  end
end

ActionController::Base.send(:include, ExpiresAt)

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Thu, 12 Aug 2010 12:25:00 -0700 my monkey patch to webrick for port guessing http://blog.mekdigital.com/my-monkey-patch-to-webrick-for-port-guessing http://blog.mekdigital.com/my-monkey-patch-to-webrick-for-port-guessing

I get many Errno::EADDRINUSE in development and this is my (untested) 3 minutes approach. 

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
    def start(&block)
      raise ServerError, "already started." if @status != :Stop
      server_type = @config[:ServerType] || SimpleServer

      server_type.start{
        @logger.info \
          "#{self.class}#start: pid=#{$$} port=#{@listeners[0].addr[1]}"
        call_callback(:StartCallback)

        thgroup = ThreadGroup.new
        @status = :Running
        while @status == :Running
          begin
            if svrs = IO.select(@listeners, nil, nil, 2.0)
              svrs[0].each{|svr|
                @tokens.pop # blocks while no token is there.
                if sock = accept_client(svr)
                  sock.do_not_reverse_lookup = config[:DoNotReverseLookup]
                  th = start_thread(sock, &block)
                  th[:WEBrickThread] = true
                  thgroup.add(th)
                else
                  @tokens.push(nil)
                end
              }
            end
          rescue Errno::EBADF, IOError => ex
            # if the listening socket was closed in GenericServer#shutdown,
            # IO::select raise it.
          rescue Exception => ex
            msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
            @logger.error msg
          end
        end

        @logger.info "going to shutdown ..."
        thgroup.list.each{|th| th.join if th[:WEBrickThread] }
        call_callback(:StopCallback)
        @logger.info "#{self.class}#start done."
        @status = :Stop
      }
    end

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato
Thu, 12 Aug 2010 11:09:00 -0700 walking through enumerables http://blog.mekdigital.com/walking-through-enumerables http://blog.mekdigital.com/walking-through-enumerables

 

The Ruby alternative to ActiveSupport's in_groups_of(n) is each_slice(n)I had to write this as I always need to spend some minutes in sDoc looking for the Ruby method. 

 

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/584042/mek_guru.jpg http://posterous.com/users/5AfJ2OC307FD Emanuele Tozzato mekdigital Emanuele Tozzato