Cory O'Daniel – These are just words Software development, thoughts, and randomness

8Feb/100

Stick this in your bash

This isn't a tutorial or anything. Its merely just me dropping all my bash scripts/prompt stuff here so maybe I'll get some comments/tips on cool stuff to add, and so that I can always rip it if I'm on a remote computer.

My pride and joy is that bash prompt. So much info :P
Username@Host
List of IP Addresses
Number of files in the directory
Current Path
History Command Number
Git Branch (if a git repo)
Nerd-ass prompt

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
# ~/.bash_login
# Shell Options
shopt -s checkwinsize
shopt -s cdspell
 
# Exports
export EDITOR="mate -w"
export VISUAL="mate -w"
export HISTFILESIZE=3000
export HISTCONTROL=ignoredups
export DISPLAY=:0.0
export GEM_PATH="/Library/Ruby/Gems/1.8/gems"
 
export PATH=~/bin:/opt/local/bin:/opt/local/sbin:/usr/local/mysql/bin:~/bin:/usr/local/bin:/Applications/flex_sdk_3/bin:$PATH
 
# include functions, aliases & bashrc
if [ -f ~/.functions ]; then
  . ~/.functions;
fi
 
if [ -f ~/.aliases ]; then
  . ~/.aliases;
fi
 
if [ -f ~/.bashrc ]; then
  . ~/.bashrc;
fi
 
if [ -f ~/.bash_prompt ]; then
  . ~/.bash_prompt;
fi
 
if [ -f /opt/local/etc/bash_completion ]; then
  .  /opt/local/etc/bash_completion
fi
complete -C ~/.bash_completion.d/rake -o default rake
 
date
if [ -x /opt/local/bin/fortune ]; then
  /opt/local/bin/fortune -s
fi
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
# ~/.bash_prompt
# Define a few Color's
BLACK='\[\e[0;30m\]'
BLUE='\[\e[0;34m\]'
GREEN='\[\e[0;32m\]'
CYAN='\[\e[0;36m\]'
RED='\[\e[0;31m\]'
PURPLE='\[\e[0;35m\]'
BROWN='\[\e[0;33m\]'
LIGHTGRAY='\[\e[0;37m\]'
DARKGRAY='\[\e[1;30m\]'
LIGHTBLUE='\[\e[1;34m\]'
LIGHTGREEN='\[\e[1;32m\]'
LIGHTCYAN='\[\e[1;36m\]'
LIGHTRED='\[\e[1;31m\]'
LIGHTPURPLE='\[\e[1;35m\]'
YELLOW='\[\e[1;33m\]'
WHITE='\[\e[0;37m\]'
NC='\[\e[0m\]'              # No Color
 
function drpmpt () {
  first_prompt_line="$WHITE($CYAN\u@\h$WHITE)-($CYAN$(ip)$WHITE)->"
  second_prompt_line="$WHITE($CYAN$(ls -1|wc -l|tr -d "[:blank:]") files$WHITE)-($CYAN\w$WHITE)->"
  third_prompt_line="$WHITE($GREEN!\!$WHITE)$GREEN$(parse_git_branch)$WHITE"
  working_prompt="$first_prompt_line\n$second_prompt_line\n$third_prompt_line> $NC"
 
  PS1=$working_prompt
}
 
PROMPT_COMMAND=drpmpt
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
# ~/.aliases
# TO BYPASS AN ALIAS DO THE ORIGINAL COMMAND W \, ie \ls
# Aliases
alias pastie='sake pastie:clip'
alias ..='cd ..'
alias igem='sudo gem install --no-rdoc --no-ri'
alias c='clear'
alias dsrm="find . -type f -name .DS_Store -print0 | xargs -0 rm"
alias gcpp='dsrm; git commit .; git pull; git push'
alias sha1sum='openssl sha1'
alias lgem='gem install --no-rdoc --no-ri -i ./gems --ignore-dependencies'
 
alias start_wowza='/Library/WowzaMediaServerPro/bin/startup.sh'
alias stop_wowza='/Library/WowzaMediaServerPro/bin/shutdown.sh'
 
alias hist='history | grep $1'
alias ps='ps aux'
alias home='cd ~'
alias utgz='tar -zxvf'
alias tgz='tar -zcvf'
alias mnts='df -h'
 
# Alias to multiple ls commands
alias la='ls -Al'               # show hidden files
#alias ls='ls -aF ' # add colors and file type extensions
#alias lx='ls -lXB'              # sort by extension
alias lk='ls -lSr'              # sort by size
alias lc='ls -lcr'          # sort by change time
alias lu='ls -lur'          # sort by access time
alias lr='ls -lR'               # recursive ls
alias lt='ls -ltr'              # sort by date
alias lm='ls -al |more'         # pipe through 'more'
 
# Alias chmod commands
alias mx='chmod a+x'
alias 000='chmod 000'
alias 644='chmod 644'
alias 755='chmod 755'
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
# ~/.functions
 
# Parse git branch
parse_git_branch() {
  git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ [\1]/'
}
 
# Get assigned ip address
ip(){
OS=`uname`
case $OS in
   Linux) IP=`ifconfig  | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{ print $1}'`;;
   FreeBSD|OpenBSD|Darwin) IP=`ifconfig  | grep -E 'inet.[0-9]' | grep -v '127.0.0.1' | awk '{ print $2}'` ;;
   SunOS) IP=`ifconfig -a | grep inet | grep -v '127.0.0.1' | awk '{ print $2} '` ;;
   *) IP="Unknown";;
esac
 
# Remove new lines and trailing whitespace.
echo "$IP" | awk '{ printf "%s | ", $0 }' | awk '{ sub(/(\ \|\ )$/, ""); print}'
}
 
#Determine if an app is running
list(){
  ps aux -m -r | grep $1
}
 
 
git_prompt_ip(){
  export GIT_PROMPT_IP=$1
}
 
startWww(){
  bin/merb -p 4003 -e developmentWww -a thin
}
 
startApi(){
  bin/merb -p 4002 -e developmentApi -a thin
}

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

Tagged as: , , No Comments
8Feb/100

Cory’s Ruby YUI Compressor v 2.0 Simpler, shorter.

So, a while ago I write a Ruby YUI Compressor Wrapper which is still available and works great (although it could use a newer jar file) as far as I know from Mandula's Blog. Some other guy also wrote one that pretty much had the same functionality as my gem. He must have hated it or something, I dunno. Even another guy wrote a wrapper for Closure Compiler if thats your bag.

Anywho, on a new project I'm working on *I just need a inline YUI compressor* and I dont give a damn about anything else like bundling, globbing or whatever. So I ended up throwing together this little (~50 lines less comments) Ruby YUI Compressor to do my dirty work.

I won't make it a gem probably, because if I do someone will just make a ruby-yui-simpler-also gem and put it on digg and get a bigger following :P (</sarcasm>)

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
require 'open3'
require 'stringio'
 
class YUI
  include Open3 
  JAR_PATH = File.join(File.dirname(__FILE__),'yuicompressor-2.4.2.jar')
 
  class << self
    # @param io [String|File]
    # @param options [Hash] - See YUI.cli
    # @returns String
    #
    def compress(io, options = {})
      options[:type]    ||= :js
      options[:charset] ||= "utf-8"
 
      stdin, stdout, stderr = Open3.popen3( YUI.cli(options) )
      stdin.puts streamify(io).read
      stdin.close
 
      my_stderr = stderr.read
      my_stdout = stdout.read
 
      # if compression failed, just output original IO
      if my_stderr.empty?
        my_stdout
      else
        puts my_stderr
        streamify(io).read
      end
    end
 
    # Sets up the command line options
    # @param options [Hash]
    #   :jar_path         - [String] Path to the jar file, default "./yuicompress-2.4.2.jar"
    #   :charset          - [String] default 'utf-8'
    #   :type             - [Symbol] Options: js/css, default :js
    #   :line_break       - [Fixnum] Column to add line break at
    #   
    #   if the type of compression is JS, then additional options:
    #     :nomunge        - [Boolean] do not obfuscate
    #     :preserve_semi  - [Boolean] preserve all semicolons
    #     :disable_opt    - [Boolean] Disable all micro optimizations
    # 
    # @returns String
    #
    def cli(options)
      _cmd = ["java -jar #{options[:jar_path] || JAR_PATH}"]
      _cmd << "--type #{options[:type]}"
      _cmd << "--charset #{options[:charset]}" if options[:charset]
      _cmd << "--charset #{options[:line_break]}" if options[:line_break]
 
      if options[:type] == :js  
        _cmd << "--nomunge" if options[:nomunge]
        _cmd << "--preserve-semi" if options[:preserve_semi]
        _cmd << "--disable-optimizations" if options[:disable_opt]
      end
 
      _cmd.join(' ') 
    end
 
    # If a file or a string, treat it like its a stream
    #
    # @param string_or_stream [File|String]
    # @return StringIO
    #
    def streamify(string_or_stream)
      if string_or_stream.respond_to?(:read)
        string_or_stream
      else
        StringIO.new(string_or_stream)
      end
    end
 
  end #end class << self
 
end

Wanna use it?
1. Copy the code above, throw it in your lib folder or where ever
2. Download the YUI compressor JAR file and put it in the same directory.
3. Do something like the following...

1
2
3
YUI.compress("function weebleep(){var cool_variable= 3; return cool_variable;}") #=> "function weebleep(){var a=3;return a};"
YUI.compress(File.open( "public/javascripts/application.js")) #=> A bunch of compressed javascript
YUI.compress(File.open( "public/stylesheets/application.css"), {:type => :css}) #=> Compressed stylesheet

Other options include:
:jar_path - [String] Path to the jar file, default "./yuicompress-2.4.2.jar"
:charset - [String] default 'utf-8'
:type - [Symbol] Options: js/css, default :js
:line_break - [Fixnum] Column to add line break at

if the type of compression is JS, then additional options:
:nomunge - [Boolean] do not obfuscate
:preserve_semi - [Boolean] preserve all semicolons
:disable_opt - [Boolean] Disable all micro optimizations

Yay have a blast. Its another compression wrapper.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

Tagged as: , , No Comments
4Feb/100

How to close (get rid of) the Google Analytics Site Overlay

So, the google analytics site overlay is pretty cool. It's nice to see where people click. I googled around and some people say to clear the cookies. The funny thing is there is actually a 'close' button, but you can't see it if your page's background is white. Duh, it's in the top right corner. Just click close.

Here it is, sneakily hidden.

Where the hell is the close button?

Here it is with the click-n-drag (TM) revealing tool.

Oh, magic!

You may be saying "duh". But with a white background, its a bit tricky to find, and clearing cookies constantly is retarded (technically speaking).

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

3Feb/100

Ruby Objective-C like Null Pattern

So, I think the objective c null(nil) pattern is pretty cool. I know that lots of people talk about doing it in different ways. Here is my implementation. I'm not keen on having nil catch method missing, because I'm not sure what havoc that may cause in one of the 10million gems I use. I also like the last link above (which I just found when writing this article) except that I dont want to type "try" a million times.

This is my preferred method when dealing with deeply nested messages. Stick this in your lib.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Object
  def chain_message(*args)
    final_result = self
    args.each { |arg| 
      unless final_result.nil?
        final_result = final_result.send(arg)  
      else
        break
      end
    }
    final_result
  end
end

Wanna use it?

1
2
3
4
5
6
7
8
9
10
 
# In this example, if an of the messages return nil, i'll get a MethodMissing error and I'll be scorned.
my_object.first_message.second_message.the_message_whos_value_i_want
 
# The problem is, I dont care if its nil, I have a default, the method is still missing.
value = my_object.first_message.second_message.the_message_whos_value_i_want || "Im ok with this being nil"
 
# If chain_message encounters a nil, it just returns nil and you can use standard language logic to default that value
# You dont have to do a million if-checks.
value = my_object.chain_message( :first_message, :second_message, :the_message_whos_value_i_want ) || "Im ok with this being nil"

Another Example:

1
2
3
4
5
6
7
8
9
10
11
# Some cool way of finding purchases, be it AR, Datamapper, or whatever, just get an object already
purchases = Purchase.all( arbitrary_finder_logic )
 
# I need to output the name of who added the product on some arbitrary report no one will read
# yeah, I know. My validators should have made sure the values were there, I'm just making this up.
purchases.each do |purchase|
  buyer = purchase.chain_message(:purchaser, :name) || "John Doe"
  seller = purchase.chain_message(:product, :listor, :name) || "Jack Sales"
 
  puts "Buyer: #{buyer}, Seller: #{seller}"
end

Yay, Drying up if-else nil checking statements. High Five.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

1Feb/100

Symbol to Proc Textmate Find/Replace (reversal) macro

So I know some people are the fans of the symbol to proc in ruby and others aren't. I'm not. I'm all for doing things to save developer time, but I think the symbol to proc thing is super lazy. I see it a lot in projects I work on from other developers and I usually remove it when I see it. So I wrote a textmate find and replace macro to undo them. You can download it here or copy the following source into a file called whatever.tmMacro.

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>bundleUUID</key>
	<string>D52BDC31-EFB7-486E-8232-F5B5517E2DF8</string>
	<key>commands</key>
	<array>
		<dict>
			<key>argument</key>
			<dict>
				<key>action</key>
				<string>replaceAll</string>
				<key>findInProjectIgnoreCase</key>
				<false/>
				<key>findString</key>
				<string>(\(?)( ?)&amp;:(\w+)( ?)(\)?)</string>
				<key>ignoreCase</key>
				<true/>
				<key>replaceAllScope</key>
				<string>document</string>
				<key>replaceString</key>
				<string>{|i| i.$3 }</string>
				<key>wrapAround</key>
				<true/>
			</dict>
			<key>command</key>
			<string>findWithOptions:</string>
		</dict>
	</array>
	<key>keyEquivalent</key>
	<string>@P</string>
	<key>name</key>
	<string>SymbolToProcToNonLazy</string>
	<key>scope</key>
	<string>source.ruby</string>
	<key>useGlobalClipboard</key>
	<false/>
	<key>uuid</key>
	<string>94D8367E-8C6A-4920-AD8D-125CBEFD063D</string>
</dict>
</plist>

Once its installed you should be able to do Command-Shift-P and replace all the symbol-to-procs in the current document with their regular proc equivalent.

I've tested it with the following scenarios:

1
2
3
%w(My super cool array).map &:to_s
%w(My super cool array).map( &:to_s )
%w(My super cool array).map(&:to_s)

If you have any other crazy way of passing in your symbol to proc, let me know. Of course if this macro destroys your computer or sets your hair on fire I take no responsibility, just ask my illegitimate children.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

27Jan/101

An Excel CSV exporter for ActiveRecord

This is a mix of two blog posts on exporting ActiveRecord data to CSV. This explicitly was designed to export stuff so excel wouldn't freak out.

This strips new lines from any string field, set the BOM and converts the encoding to utf16.

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
require "fastercsv"
require "iconv"
 
# Excel info from: http://blog.plataformatec.com.br/2009/09/exporting-data-to-csv-and-excel-in-your-rails-app/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed:+PlataformaBlog+(Plataforma+Blog)
# Original post: http://www.brynary.com/2007/4/28/export-activerecords-to-csv
 
# Usage:
#   Given User is an ActiveRecord model
#   
#   Options for Model.to_csv, Array.to_csv, and ActionController::Base#send_csv
# 
#   :only     - Array, trumps :exclude. Only these attributes will be included instead of all attributes (things listed in :methods will still be sent)
#     :only => [ :id, :name, :created_at ]
#   :methods  - Array, additional methods to evaluate and add to the CSV response. Note: you can send nested method calls
#     :methods => [ :to_s, :complex_method, "my.method.on.a.related.object"]
#   :exclude  - Array, attributes to exclude from CSV result
# 
#  From ActionController
#     send_csv User, :only => [:first_name, :email, :created_at] #This will do all records
#     send_csv User.all(:conditions => ["created_at > ?", some_date]), :only => [:first_name, :email, :created_at]
#
#  From ActiveRecord Model
#   User.to_csv :only => [ :username, :email, :created_at ], :methods => [ :age, "account.id"] # All users additionally get their related Account#id number
#   User.all(:limit => 10).to_csv :exclude => [:password, :birthdate]
#
class ActiveRecord::Base
 
  # Shortcut for CSV of whole table
  def self.to_csv(*args)
    find(:all).to_csv(*args)
  end
 
  # Get the column headers
  def self.csv_columns(options={})
    tmp_columns = if options[:only]
      options[:only] #only trumps exclude
    else
      self.content_columns.map{|curr_col| curr_col.name } - options[:exclude].map{|curr_col| curr_col.to_s }
    end
 
    tmp_columns + options[:methods].map{|curr_col| curr_col.to_s }
  end
 
  # Record to a row level csv array
  def to_csv(options={})
    self.class.csv_columns(options).map { |curr_col|
      curr_col = curr_col.to_s  
 
      #Its a chain of method calls, on intermediary nil, just return nil
      if !curr_col.index(".")
        col_val = self.send(curr_col) 
      else
        col_val = self
        curr_col.split(".").each {|curr_method| col_val = col_val.send(curr_method) unless col_val.nil?}
      end
 
      col_val.gsub!("\n", " ") if col_val.is_a?(String) # Strip newlines, Appease Excel Gods      
      col_val
    }
  end
end
 
 
class Array
 
  # Convert an array of objects into a CSV String.
  def to_csv(options = {})
    column_options    = {
      :only    => options.delete(:only),
      :exclude => options.delete(:exclude) || [],
      :methods => options.delete(:methods) || []
    }
 
    options = {
      :col_sep => "\t" # Appease Excel Gods
    }.merge(options)
 
    if all? { |e| e.respond_to?(:to_csv) }
      header_row = first.class.csv_columns(column_options).to_csv
 
      content_rows = map { |e| 
        # Get all the values of non-excluded rows
        e.to_csv(column_options) 
      }.map{|r| 
        # Call to_csv on the array of row-level values, this will join them into a CSV row
        r.to_csv(column_options) 
      }
 
      ([header_row] + content_rows).join
    else
      FasterCSV.generate_line(self, options)
    end
  end
 
end
 
 
# module for extending ActionController
module ExcelCSVExporter
  BOM = "\377\376" #Byte Order Mark, Appease Excel Gods
 
  def send_csv(kollection, options={})
    filename = options.delete(:filename) || I18n.l(Time.now, :format => :short) + ".csv"
 
    content = kollection.to_csv(options)
 
    # Appease Excel Gods
    content = BOM + Iconv.conv("utf-16le", "utf-8", content)
    send_data content, :filename => filename    
  end
end
 
ActionController::Base.send :include, ExcelCSVExporter

Wanna use it?

1
2
3
4
5
6
7
class MyCoolController < ActionController::Base
  def index
    respond_to {|want|
      want.csv{ send_csv MyCoolModel.all, :exclude => [:secret_column], :methods => [:complex_method, "related.table.method"]}
    }
  end
end

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

Tagged as: , , , 1 Comment
20Jan/100

Building curb on Snow Leopard

Had some issues building curb on Snow Leopard today.

It was resulting in something like:

1
2
3
4
5
6
7
In file included from /opt/local/include/curl/curl.h:44,
                 from curb.h:12,
                 from curb.c:8:
/opt/local/include/curl/curlrules.h:144: error: size of array ‘__curl_rule_01__’ is negative
/opt/local/include/curl/curlrules.h:154: error: size of array ‘__curl_rule_02__’ is negative
lipo: can't open input file: /var/folders/wX/wX64Cb+PGjG-EXuklO+I+k+++TI/-Tmp-//ccKIrqTY.out (No such file or directory)
make: *** [curb.o] Error 1

It's a quick fix and your on your way to curling stuff...

1
2
3
sudo port install zlib +universal;
sudo port upgrade --enforce-variants openssl +universal;
sudo port install curl +universal;

Then build curb however you were building it before.

Yay, I can scrub other peoples sites now!

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

20Jan/100

Installing MySQL from DMG on Mac (and the few commands to make it work on the command line)

When you install mysql from the DMG on Mac OS X, it leaves a few things missing. Mysql binaries are missing from the path, and the sock file is being looked for in the wrong place by default.

Here is a quick fix:

1
2
3
echo "export PATH=/usr/local/mysql/bin:\$PATH" >> ~/.bash_login;
sudo mkdir -p /opt/local/var/run/mysql5;
sudo ln -s /tmp/mysql.sock /opt/local/var/run/mysql5/mysqld.sock;

Yay, now you can rock out with your bad self.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

20Jan/102

Thinking Sphinx cannot find Sphinx

I was getting this error this morning after setting up my new laptop for the project I am working on:

1
2
3
4
5
6
7
8
9
10
11
12
13
sh: line 1:   326 Trace/BPT trap          indexer 2>&1
sh: line 1:   329 Trace/BPT trap          /usr/local/bin/indexer 2>&1
 
Sphinx cannot be found on your system. You may need to configure the following
settings in your config/sphinx.yml file:
  * bin_path
  * searchd_binary_name
  * indexer_binary_name
 
For more information, read the documentation:
http://freelancing-god.github.com/ts/en/advanced_config.html
rake aborted!
uninitialized constant MysqlCompat::MysqlRes

Everyone probably knows this, but I wild-goose-chased trying to figure out if I had a setting wrong in my sphinx.yml file for Thinking Sphinx this morning for like half an hour before I realized what Thinking Sphinx meant about "Sphinx cannot be found on your system." was that the MySQL Development headers weren't installed. I fixed this with a simple port install on Mac.

1
port install mysql5-devel

The more you know.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

7Jan/100

DataMapper 0.10+ Basic Sphinx Support

I needed sphinx support and I needed it now. Apparently dm-sphinx-adapter wasn't ported to dm 0.10. I started porting it, but I kind of felt like I was going down a rabbit whole. I will continue to work on it over the next couple days, in the meantime at Vokle we used this to get our fulltext on. Note, this is doing the fulltext search across all columns in your fulltext index. Its not doing cool matchers like :updated_at.gt yada-yada. It also doesn't implement a DataMapper Adapter, its a super hack, but it gets the job done for us.

Give me your two cents and stay tuned for a full blown adapter.

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
module DataMapper
  module Model
    def sphinx_setup(opts={})
      @_sphinx_index = opts[:index]
    end
 
    def search(_query_, opts={})
      sph_client = opts.delete(:client) || Riddle::Client.new(*SimpleSphinx.conf)
      sph_client.match_mode = opts.delete(:match_mode) || :extended
 
      sphinx_query = _query_.to_s.gsub(/[\(\)\|\-!@~"&\/]/){|char| "\\#{char}"}
 
      results   = sph_client.query(sphinx_query, @_sphinx_index || self.storage_names[:default].to_sym)
      matches   = results[:matches]
      ids       = matches.map{|match| match[:doc]}
 
      # Fetch
      self.all(opts.merge({:id => ids}))
    end
  end
end
 
class SimpleSphinx
  class << self
    def conf
      [server, port]
    end
    def server=(s)
      @_server = s
    end
 
    def port=(p)
      @_port = p
    end
    def server
      @_server || 'localhost'
    end
    def port
      @_port || 3312
    end
  end
end

Wanna use it?

1
2
3
4
5
6
7
8
9
10
# Your Model here
Person.sphinx_setup(:index => "indexName") #defaults to storage name
SimpleSphinx.server = "example.com"
SimpleSphinx.port = 3312
 
Person.search("cory") #=> DataMapper::Collection
 
# you can pass an additional hash of stuff you would pass to Model#all and this will be used to filter
# the result set down POST sphinx when its collecting data from datamapper
Person.search("cory", :limit => 10, :gender => :male)

You've got your sphinxter back. Congrats!

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon