Tech Tidbits - Ruby, Ruby On Rails, Merb, .Net, Javascript, jQuery, Ajax, CSS...and other random bits and pieces.

Tuesday, November 18, 2008

Nginx, Mongrel, Merb

Quick notes on installing and configuring nginx and running with existing mongrel and Merb app on my MacBookPro.

1) Download and install PCRE (if needed)
http://www.pcre.org/
ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/
ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-7.8.tar.gz

$ ./configure
$ ./make
$ ./make install

2) Download and install nginx:
http://nginx.net/
The latest stable version is nginx-0.6.32, the change log.
http://sysoev.ru/nginx/nginx-0.6.32.tar.gz

$ ./configure
$ ./make
$ ./make install

# Edit nginx.conf file (/usr/local/nginx/conf/nginx.conf)
# Note - based on Ezra's blog post (see notes below)

user nobody;
worker_processes 2;

error_log logs/error.log notice;
pid logs/nginx.pid;

events {
worker_connections 1024;
}

http {
include mime.types;
#include conf/mime.types;
default_type application/octet-stream;

# no sendfile on OSX uncomment
#this if your on linux or bsd
#sendfile on;
tcp_nopush on;

keepalive_timeout 65;
tcp_nodelay on;

upstream mongrel {
server 127.0.0.1:4000;
server 127.0.0.1:4001;
server 127.0.0.1:4002;
}

gzip on;
gzip_min_length 1100;
gzip_buffers 4 8k;
gzip_types text/plain;

server {
listen 80;
#server_name localhost;
root /Users/dsparlingimbp/git/dougsparlingdotcom/public;

access_log off;
rewrite_log on;

location ~ ^/$ {
if (-f /index.html){
rewrite (.*) /index.html last;
}
proxy_pass http://mongrel;
}

location / {
if (!-f $request_filename.html) {
proxy_pass http://mongrel;
}
rewrite (.*) $1.html last;
}

location ~ .html {
root /Users/dsparlingimbp/git/dougsparlingdotcom/public;
}

location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js|mov)$ {
root /Users/dsparlingimbp/git/dougsparlingdotcom/public;
}

location / {
proxy_pass http://mongrel;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

}
}


4) Commands for starting and stopping nginx
start: sudo /usr/local/nginx/sbin/nginx
stop: sudo kill `cat /usr/local/nginx/logs/nginx.pid`

5) Start Mongrel cluster (I'm using 3 Mongrels):
$ merb -c 3
or
$ merb -p 4000 -c 3

6) Resources
http://brainspl.at/articles/2006/08/23/nginx-my-new-favorite-front-end-for-mongrel-cluster
http://articles.slicehost.com/2008/5/13/ubuntu-hardy-installing-nginx-from-source
http://articles.slicehost.com/2008/5/13/ubuntu-hardy-adding-an-nginx-init-script
http://articles.slicehost.com/2008/4/25/ubuntu-hardy-setup-page-1
http://articles.slicehost.com/2008/4/25/ubuntu-hardy-setup-page-2
http://forum.slicehost.com/comments.php?DiscussionID=2179
http://merb.rubyforge.org/files/README.html

Sunday, November 9, 2008

Ruby - Randomize arrays and strings


Randomize the elements of an array:


random_array = my_array.sort_by{rand}

Randomize a string (jumble a given word):

jumbled_word = word.split("").sort_by{rand}.join

Saturday, November 8, 2008

Updating to RubyGems 1.3.0 on Mac OS X Leopard 10.5.4

When attempting to stall final release of Merb 1.0, I got an error message that I needed RubyGems version >=1.3.0:

ERROR: Error installing merb:
merb-core requires RubyGems version >= 1.3.

So I did the usual "gem update --system" but got an error:

$ sudo gem update --system
Password:
Updating RubyGems
Updating rubygems-update
Successfully installed rubygems-update-1.3.0
ERROR: While executing gem ... (NameError)
undefined local variable or method `remote_gemspecs' for #
$ gem --version
1.2.0

I found that you have to run a second set of instructions:

$ sudo gem install rubygems-update
Successfully installed rubygems-update-1.3.0
1 gem installed
$ sudo update_rubygems
Installing RubyGems 1.3.0

More info here:
http://rails.wincent.com/wiki/Updating_to_RubyGems_1.3.0_on_Mac_OS_X_Leopard_10.5.4

Wednesday, August 27, 2008

Dynamic Details Row in GridView


<%@ Page Language="C#" %>
<%@ Import Namespace="System.Web.Configuration" %>

<script runat="server">

void GridView1_PreRender(object sender, EventArgs e)
{
if (GridView1.SelectedRow != null)
{
Table table = GridView1.SelectedRow.Parent as Table;

if (table != null)
CreateRow(table, GridView1.SelectedIndex);
}
}

void CreateRow(Table table, int index)
{
GridViewRow row = new GridViewRow(-1, -1, DataControlRowType.DataRow, DataControlRowState.Normal);
row.Cells.Add(CreateColumn());
table.Rows.AddAt(index + 2, row);
}

private TableCell CreateColumn()
{
TableCell cell = new TableCell();
cell.ColumnSpan = GridView1.Columns.Count;
cell.Width = Unit.Percentage(100);

DataSourceControl ds = CreateDataSourceControl();

cell.Controls.Add(ds);
cell.Controls.Add(CreateDetailsView(ds));
return cell;
}

private static DetailsView CreateDetailsView(DataSourceControl ds)
{
DetailsView dv = new DetailsView();
dv.AutoGenerateRows = true;
dv.DataSourceID = ds.ID;
return dv;
}

private DataSourceControl CreateDataSourceControl()

{
SqlDataSource ds = new SqlDataSource();
ds.ConnectionString = WebConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString;
ds.SelectCommand = "SELECT * FROM [Users] WHERE [UserID] = @UserID";
ds.ID = "SqlDataSource2";

Parameter cp = new Parameter("UserID", TypeCode.String, GridView1.SelectedValue.ToString());
ds.SelectParameters.Add(cp);
return ds;
}

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head id="Head1" runat="server">
<title>Untitled Page</title>
</head>

<body>
<form id="form1" runat="server">
<div>

 <asp:GridView ID="GridView1" Runat="server" DataSourceID="SqlDataSource1" DataKeyNames="UserID"
AutoGenerateColumns="False" OnPreRender="GridView1_PreRender" EnableViewState="False">
<Columns>
<asp:CommandField ShowSelectButton="True">
<asp:BoundField ReadOnly="True" HeaderText="UserID" DataField="UserID" SortExpression="UserID">
<asp:BoundField HeaderText="UserName" DataField="UserName" SortExpression="UserName"></asp:BoundField>
</Columns>
</asp:GridView>

<asp:SqlDataSource ID="SqlDataSource1" Runat="server" SelectCommand="SELECT [UserID], [UserName] FROM [Users]"
ConnectionString="<%$ ConnectionStrings:MyConnectionString %>">
</asp:SqlDataSource>

</div>
</form>

</body>
</html>

Saturday, August 23, 2008

Amazon S3 with Ruby aws-s3 gem

I stated using Amazon S3 to store and backup the music I've written and recorded. I'm using the Ruby aws-s3 gem. Note that I check for some basic audio file types so I don't have to manually type the bucket or mime-type.

aws-s3.rb
---------
#!/usr/bin/env ruby
require 'rubygems'
require 'aws/s3'

# File to upload
local_file = ARGV[0]

# bucket & mime-type
if local_file =~ /mp3$/
bucket = 'doug_mp3'
mime_type = 'audio/mpeg'
elsif local_file =~ /wav$/
bucket = 'doug_wav'
mime_type = 'audio/x-wav'
elsif local_file =~ /aif[c|f]?$/
bucket = 'doug_aiff'
mime_type = 'audio/x-aiff'
else
bucket = ARGV[1]
mime_type = ARGV[2] || "application/octet-stream"
end

if bucket.nil?
puts "need a bucket"
exit
end

puts "file: #{local_file}"
puts "bucket: #{bucket}"
puts "mime_type: #{mime_type}"

AWS::S3::Base.establish_connection!(
:access_key_id => 'REPLACE_ME',
:secret_access_key => 'REPLACE_ME'
)

base_name = File.basename(local_file)

puts "Uploading #{local_file} as '#{base_name}' to '#{bucket}'"

AWS::S3::S3Object.store(
base_name,
File.open(local_file),
bucket,
:content_type => mime_type
)

puts "Uploaded!"

I can call the script a couple ways:

# with defaults based on extension
$ ruby aws-s3.rb mysong.mp3

# or explicitly (file, bucket, mime-type)
$ ruby aws-s3.rb mysong.mp3 doug_mp3 'audio/mp3'

Add and remove a remote source from gem 'remote sources''

I was attempting to add a gem and ran into an error I hadn't seen before:

Macintosh:aws-s3 dsparlingimbp$ sudo gem i aws-s3
WARNING: RubyGems 1.2+ index not found for:
http://merbivore.com

RubyGems will revert to legacy indexes degrading performance.
Updating metadata for 63 gems from http://gems.rubyforge.org/
...............................................................
complete
ERROR: could not find gem aws-s3 locally or in a repository


So I tried to update gems:

Macintosh:aws-s3 dsparlingimbp$ sudo gem update --system
Updating RubyGems
WARNING: RubyGems 1.2+ index not found for:
http://merbivore.com

RubyGems will revert to legacy indexes degrading performance.
Updating metadata for 63 gems from http://gems.rubyforge.org/
...............................................................
complete
ERROR: While executing gem ... (Gem::RemoteFetcher::FetchError)
bad response Not Found 404 (http://merbivore.com/Marshal.4.8)

Next I looked the gems environment:

Macintosh:rubygems-1.2.0 dsparlingimbp$ gem env
RubyGems Environment:
- RUBYGEMS VERSION: 1.2.0
- RUBY VERSION: 1.8.6 (2007-03-13 patchlevel 0) [i686-darwin8.8.5]
- INSTALLATION DIRECTORY: /usr/local/lib/ruby/gems/1.8
- RUBY EXECUTABLE: /usr/local/bin/ruby
- EXECUTABLE DIRECTORY: /usr/local/bin
- RUBYGEMS PLATFORMS:
- ruby
- x86-darwin-8
- GEM PATHS:
- /usr/local/lib/ruby/gems/1.8
- GEM CONFIGURATION:
- :update_sources => true
- :verbose => true
- :benchmark => false
- :backtrace => false
- :bulk_threshold => 1000
- :sources => ["http://gems.rubyforge.org", "http://merbivore.com"]
- REMOTE SOURCES:
- http://gems.rubyforge.org
- http://merbivore.com

Noticing the issues with Merb (which I recently installed), I removed http://merbivore.com from the list of remote sources:

sudo gem sources -r http://merbivore.com

I went ahead and added github while I was at it:

sudo gem sources -a http://gems.github.com

Checked my gems environment:

Macintosh:rubygems-1.2.0 dsparlingimbp$ gem env
RubyGems Environment:
- RUBYGEMS VERSION: 1.2.0
- RUBY VERSION: 1.8.6 (2007-03-13 patchlevel 0) [i686-darwin8.8.5]
- INSTALLATION DIRECTORY: /usr/local/lib/ruby/gems/1.8
- RUBY EXECUTABLE: /usr/local/bin/ruby
- EXECUTABLE DIRECTORY: /usr/local/bin
- RUBYGEMS PLATFORMS:
- ruby
- x86-darwin-8
- GEM PATHS:
- /usr/local/lib/ruby/gems/1.8
- GEM CONFIGURATION:
- :update_sources => true
- :verbose => true
- :benchmark => false
- :backtrace => false
- :bulk_threshold => 1000
- :sources => ["http://gems.rubyforge.org", "http://gems.github.com"]
- REMOTE SOURCES:
- http://gems.rubyforge.org
- http://gems.github.com

Everything looked good, so I tried to install aws-s3 gem again:

Macintosh:rubygems-1.2.0 dsparlingimbp$ sudo gem install aws-s3
Successfully installed aws-s3-0.5.1
1 gem installed
Installing ri documentation for aws-s3-0.5.1...
Installing RDoc documentation for aws-s3-0.5.1...

Et VoilĂ ! C'est si bon...

Saturday, July 26, 2008

authentication token

I had a need to both validate the source of data sent to a web service and to only accept the data if it was sent within a limited time period. One thing I didn't need, (but I imagine would be necessary in most cases) was to only allow the request once, in which case you would probably want to store the token in a database and flag it once it had been "redeemed."

To create the token, first concatenate current time in epoch seconds (or whatever time format you like, but stick with UTC since the end user may be any where in the world), username, and a password, then m5d and base64 that concatenated string and prepend it with the same timestamp, separated by a colon (':').

#!/usr/bin/ruby
require 'digest/md5'
require 'base64'

# Username/password
UID = 'marypoppins'
PWD = "supercalifragilisticexpialidocious"

# create token using utc secconds, uid, and pwd
def create_token(t=nil)
timestamp = t || Time.now.utc.to_i.to_s
plaintext = timestamp + UID + PWD
md5 = Digest::MD5.digest(plaintext)
return timestamp + ':' + Base64.encode64(md5).strip
end

# validate token
def validate_token(token)
token_timestamp = token.split(':')[0]
token2 = create_token(token_timestamp)
return token == token2 ? true : false
end

# validate timestamp and token
def validate_token_and_timestamp(token,num_secs=60)
token_timestamp = token.split(':')[0]
return nil if Time.now.to_i - token_timestamp.to_i > num_secs
token2 = create_token(token_timestamp)
return token == token2 ? true : false
end

The most basic example: create a token and validate it by creating a new token using the timestamp in the original token with the known user id and password. If the two tokens match, the original token is valid (which it will be in this contrived example):

# create a token and validate
if (validate_token(create_token))
puts "Valid token"
else
puts "Invalid token"
end

To check if the token was passed within a certain time period, just compare the timestamp on the original token with current time.

# create a token and validate timestamp and token
if (validate_token_and_timestamp(create_token,10))
puts "Valid token"
else
puts "Invalid token"
end


For demonstration only, use sleep to timeout a token:

# create a token, but let token timeout before trying to validate
timeout_token = create_token
sleep 15
if (validate_token_and_timestamp(timeout_token,10))
puts "Valid token"
else
puts "Invalid token"
end

Monday, July 21, 2008

Self Referential Associations with has_many :through


db/migrate/001_create_teletubbies.rb

class CreateTeletubbies < ActiveRecord::Migration
def self.up
create_table :teletubbies do |t|
t.string :name
t.timestamps
end
Teletubby.create(:name => 'Tinky Winky')
Teletubby.create(:name => 'Dipsy')
Teletubby.create(:name => 'Laa Laa')
Teletubby.create(:name => 'Po')
end

def self.down
drop_table :teletubbies
end
end

db/migrate/002_create_friendships.rb

class CreateFriendships < ActiveRecord::Migration
def self.up
create_table :friendships do |t|
t.integer :teletubby_id
t.integer :friend_id
t.timestamps
end
# Tinky Winky loves all the other Teletubbies very much
Friendship.create(:teletubby_id => 1, :friend_id => 2)
Friendship.create(:teletubby_id => 1, :friend_id => 3)
Friendship.create(:teletubby_id => 1, :friend_id => 4)
# Dipsy loves all the other Teletubbies very much
Friendship.create(:teletubby_id => 2, :friend_id => 1)
Friendship.create(:teletubby_id => 2, :friend_id => 3)
Friendship.create(:teletubby_id => 2, :friend_id => 4)
# Laa Laa loves Po very much
Friendship.create(:teletubby_id => 3, :friend_id => 4)
# Po loves Laa Laa very much
Friendship.create(:teletubby_id => 4, :friend_id => 3)
# Big Hug
end

def self.down
drop_table :friendships
end
end

app/models/teletubby.rb

class Teletubby < ActiveRecord::Base
# a teletubby 'has_many' friendships (teletubbies love each other very much)
has_many :friendships

# a teletubby 'has_many' friends ':through' friendships relationship
has_many :friends, :through => :friendships
end

app/models/friendship.rb

class Friendship < ActiveRecord::Base
# ActiveRecord already knows what a teletubby is (reflection)
belongs_to :teletubby

# No actual friendship model, so add :class_name and :foreign_key
belongs_to :friend, :class_name => "Teletubby", :foreign_key => "friend_id"
end


The database should look a little something like this:

mysql> select * from teletubbies;
+----+-------------+---------------------+---------------------+
| id | name | created_at | updated_at |
+----+-------------+---------------------+---------------------+
| 1 | Tinky Winky | 2008-07-21 15:41:42 | 2008-07-21 15:41:42 |
| 2 | Dipsy | 2008-07-21 15:41:42 | 2008-07-21 15:41:42 |
| 3 | Laa Laa | 2008-07-21 15:41:42 | 2008-07-21 15:41:42 |
| 4 | Po | 2008-07-21 15:41:42 | 2008-07-21 15:41:42 |
+----+-------------+---------------------+---------------------+
4 rows in set (0.00 sec)

mysql> select * from friendships;
+----+--------------+-----------+---------------------+---------------------+
| id | teletubby_id | friend_id | created_at | updated_at |
+----+--------------+-----------+---------------------+---------------------+
| 1 | 1 | 2 | 2008-07-21 15:41:42 | 2008-07-21 15:41:42 |
| 2 | 1 | 3 | 2008-07-21 15:41:42 | 2008-07-21 15:41:42 |
| 3 | 1 | 4 | 2008-07-21 15:41:42 | 2008-07-21 15:41:42 |
| 4 | 2 | 1 | 2008-07-21 15:41:42 | 2008-07-21 15:41:42 |
| 5 | 2 | 3 | 2008-07-21 15:41:42 | 2008-07-21 15:41:42 |
| 6 | 2 | 4 | 2008-07-21 15:41:42 | 2008-07-21 15:41:42 |
| 7 | 3 | 4 | 2008-07-21 15:41:42 | 2008-07-21 15:41:42 |
| 8 | 4 | 3 | 2008-07-21 15:41:42 | 2008-07-21 15:41:42 |
+----+--------------+-----------+---------------------+---------------------+
8 rows in set (0.00 sec)


Use script/console to check the relationships:

dsparling-imbps-computer:~/Sites/teletubbies dsparlingimbp$ script/console
Loading development environment (Rails 2.0.2)
>> t1 = Teletubby.find(1)
=> #<Teletubby id: 1, name: "Tinky Winky", created_at: "2008-07-21 15:41:42", updated_at: "2008-07-21 15:41:42">
>> t1.friends.each { |f| puts "#{t1.name} loves #{f.name} very much." }; false
Tinky Winky loves Dipsy very much.
Tinky Winky loves Laa Laa very much.
Tinky Winky loves Po very much.
=> false
>> t2 = Teletubby.find(2)
=> #<Teletubby id: 2, name: "Dipsy", created_at: "2008-07-21 15:41:42", updated_at: "2008-07-21 15:41:42">
>> t2.friends.each { |f| puts "#{t2.name} loves #{f.name} very much." }; false
Dipsy loves Tinky Winky very much.
Dipsy loves Laa Laa very much.
Dipsy loves Po very much.
=> false
>> t3 = Teletubby.find(3)
=> #<Teletubby id: 3, name: "Laa Laa", created_at: "2008-07-21 15:41:42", updated_at: "2008-07-21 15:41:42">
>> t3.friends.each { |f| puts "#{t3.name} loves #{f.name} very much." }; false
Laa Laa loves Po very much.
=> false
>> t4 = Teletubby.find(4)
=> #<Teletubby id: 4, name: "Po", created_at: "2008-07-21 15:41:42", updated_at: "2008-07-21 15:41:42">
>> t4.friends.each { |f| puts "#{t4.name} loves #{f.name} very much." }; false
Po loves Laa Laa very much.
=> false

Saturday, July 12, 2008

Screencast Addiction

Ruby/RoR
http://www.railsenvy.com/speaking
http://railscasts.com
http://www.rubyplus.org
http://www.buildingwebapps.com/podcasts
http://peepcode.com
http://www.pragprog.com/screencasts
http://envycasts.com

Rails Conf(s)
http://www.railsenvy.com/tags/conferences
http://www.railsenvy.com/2008/6/2/Railsconf-videos
http://blip.tv/search?q=rails+conf&x=0&y=0
http://www.confreaks.com
http://mwrc2008.confreaks.com/
The RubyFringe Videos

OSCON
http://www.railsenvy.com/2008/7/29/oscon-videos
http://blip.tv/search?q=oscon&x=0&y=0

Other
ThoughtWorks IT Matters and ThoughtWorks Talks
http://www.justin.tv/signals37/archive
http://developer.yahoo.com/yui/theater/
http://www.theflexshow.com/blog/
Gooogle search railsconf
Google search oscon
InfoQ Presentations

 

ruby-mp3info

I have an old SanDisk mp3 player that I use to listen to music and podcasts. Though it's a rather small card (512M), it's still annoying to have to scroll through my single playlist to get to a specific file.

The SanDisk sorts by the mp3 info title tag, so I wrote a small script that I run on my podcast files before I load them on my player so that they will show up first in the playlist by prepending '01_' to the mp3 title tag (and using the mp3 filename if the title is nil).

First, install the ruby-mp3info gem if you don't have it:

gem install ruby-mp3info

edit_mp3info_title.rb

require 'rubygems'
require "mp3info"

dir = Dir.open('/Users/dsparlingimbp/PODCAST/mp3')
puts "mp3 directory: #{dir.path}"

dir.each do |file|
next unless /\.mp3$/ =~ file
puts "mp3: #{file}"

mp3file = "#{dir.path}/#{file}"

Mp3Info.open(mp3file) do |mp3|
puts "original title: #{mp3.tag.title}"
title = '01_' + (mp3.tag.title || file)
mp3.tag.title = title
puts "new title: #{mp3.tag.title}"
end

end

Friday, July 11, 2008

Podcast Addiction

It's bad enough that I subscribe to more RSS feeds than I can possibly read in a day, but I've also become addicted to podcasts...many are tech podcasts, but I listen to a wide variety...

From my current iTunes podcasts directory:

Andy Jordan's Tech Diary
Audible Ajax
boagworld
The Bugle
The Christian Science Monitor - News
Digital Media Insider
The Flex Show (podcast and screencast)
FOO Casts
The French Pod Class
Hanselminutes
IT Conversations
L33t Tech News
Learning Rails (now a screencast)
.NET Rocks!
php_abstract
php|architecht
podcasts - Channel 9
Pragmatic Programmers
Radio Allegro
Rails Envy Podcast
Railscasts (screencast)
Rubyology
Ruby on Rails Podcast
StackOverflow
TalkCrunch
The Thirsty Developer
This American Life
Wall Street Journal Tech News Briefings
Wall Street Journal on Small Business
Wall Street Journal's Your Money Matters
The Washington Institute Podcast
The Web 2.0 Show
Web Design TV
WebDevRadio.com

A few others:
French Vibes

And a couple of "top tech podcasts" links:
The top 40 tech podcasts - .net magazine
Top 10 Best Technology Podcasts

Thursday, July 10, 2008

Ignore exisiting directory in subverion Rails repo

I needed to ignore the log directory of an existing Rails app I had checked out:

svn up
svn delete log
svn commit -m 'removed log dir'
mkdir log
svn add --non-recursive log
svn propset svn:ignore "*" log/
svn commit -m "Add log dir, ignore contents"

With a little help from:
http://rails.wincent.com/wiki/Checking_a_new_Rails_application_into_an_existing_Subversion_repository

Tuesday, June 3, 2008

CocoaMySQL - MySQL GUI for Mac OS X

I had to restore a MySQL database dump to a server at new host with no shell access. The host recommended SQLyog, but I'm working on this project on my Mac, and SQLyog is Windows only.

CocoaMySQL was a quick and easy install and did exactly what I needed.

Of course, on my Mac itself, it was the standard MySQL command:

$ mysql -h localhost -u root myshinynewdb < mydatabasedump.sql

Sunday, June 1, 2008

CSS, Firefox - font-family, form intput and textarea

I'll do my own write up at some point, but ran across this, which solved a problem I was having with FF:

http://cakebaker.42dh.com/2006/02/02/some-css-trouble-with-input-and-textarea-in-firefox/

Thursday, May 22, 2008

cache_fu: Conditional action-caching stops after a period of time

http://groups.google.com/group/acts_as_cached/browse_thread/thread/eff6287664362f66

fragment_cache.rb

module ActsAsCached
module FragmentCache
def self.setup!
class << CACHE
include Extensions
end

setup_fragment_cache_cache
setup_rails_for_memcache_fragments
setup_rails_for_action_cache_options
end

# add :ttl option to cache helper and set cache store memcache object
def self.setup_rails_for_memcache_fragments
if ::ActionView.const_defined?(:Template)
# Rails 2.1+
::ActionController::Base.cache_store = CACHE
else
# Rails < svn r8619
::ActionView::Helpers::CacheHelper.class_eval do
def cache(name = {}, options = nil, &block)
@controller.cache_erb_fragment(block, name, options)
end
end
::ActionController::Base.fragment_cache_store = CACHE
end
end

def self.setup_fragment_cache_cache
Object.const_set(:FragmentCacheCache, Class.new { acts_as_cached :store => CACHE })
end

# add :ttl option to caches_action on the per action level by passing in a hash instead of an array
#
# Examples:
# caches_action :index # will use the default ttl from your memcache.yml, or 25 minutes
# caches_action :index => { :ttl => 5.minutes } # cache index action with 5 minute ttl
# caches_action :page, :feed, :index => { :ttl => 2.hours } # cache index action with 2 hours ttl, all others use default
#
def self.setup_rails_for_action_cache_options
::ActionController::Caching::Actions::ActionCacheFilter.class_eval do
# convert all actions into a hash keyed by action named, with a value of a ttl hash (to match other cache APIs)
def initialize(*actions, &block)
if [].respond_to?(:extract_options!)
#edge
@options = actions.extract_options!
@actions = actions.inject(@options.except(:cache_path)) do |hsh, action|
action.is_a?(Hash) ? hsh.merge(action) : hsh.merge(action => { :ttl => nil })
end
@options.slice!(:cache_path)
else
#1.2.5
@actions = actions.inject({}) do |hsh, action|
action.is_a?(Hash) ? hsh.merge(action) : hsh.merge(action => { :ttl => nil })
end
end
end

# override to skip caching/rendering on evaluated if option
def before(controller)
return unless @actions.include?(controller.action_name.intern)

# maintaining edge and 1.2.x compatibility with this branch
# Jan Prill - added && false
#if @options && false
# DSS - removed false - false causing else to always run - this may be
# because Jan was using Rails 1.2...
if @options
action_cache_path = ActionController::Caching::Actions::ActionCachePath.new(controller, path_options_for(controller, @options))
else
action_cache_path = ActionController::Caching::Actions::ActionCachePath.new(controller)
end

# should probably be like ActiveRecord::Validations.evaluate_condition. color me lazy.
if conditional = @actions[controller.action_name.intern][:if]
conditional = conditional.respond_to?(:call) ? conditional.call(controller) : controller.send(conditional)
end
# Jan Prill - causing caching to stop after a period of time?
#@actions.delete(controller.action_name.intern) if conditional == false

# Jan Prill - added if clause
cache = controller.read_fragment(action_cache_path.path) if conditional != false
if cache && (conditional || conditional.nil?)
controller.rendered_action_cache = true
if method(:set_content_type!).arity == 2
set_content_type!(controller, action_cache_path.extension)
else
set_content_type!(action_cache_path)
end
controller.send(:render, :text => cache)
false
else
# 1.2.x compatibility
# Jan Prill added &&
controller.action_cache_path = action_cache_path if (controller.respond_to?(:action_cache_path) && conditional != false)
end
end

# override to pass along the ttl hash
def after(controller)
# Jan Prill - added || controller.action_cache_path.nil?
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache || controller.action_cache_path.nil?
# 1.2.x compatibility
path = controller.respond_to?(:action_cache_path) ? controller.action_cache_path.path : ActionController::Caching::Actions::ActionCachePath.path_for(controller)
controller.write_fragment(path, controller.response.body, action_ttl(controller))
end

private
def action_ttl(controller)
@actions[controller.action_name.intern]
end
end
end

module Extensions
def read(*args)
return if ActsAsCached.config[:skip_gets]
FragmentCacheCache.cache_store(:get, args.first)
end

def write(name, content, options = {})
ttl = (options.is_a?(Hash) ? options[:ttl] : nil) || ActsAsCached.config[:ttl] || 25.minutes
FragmentCacheCache.cache_store(:set, name, content, ttl)
end
end

module DisabledExtensions
def read(*args) nil end
def write(*args) "" end
end
end
end

Tuesday, May 20, 2008

git

I've been wanting to play with git, so I created repositories for all my jquery plugins at github.com.

http://github.com/dsparling

For now, that would be:

jClock
datemaker
regexpCommon

git documentation:

http://git.or.cz/#documentation
http://www.sourcemage.org/Git_Guide
http://www.gitcasts.com/

Tuesday, May 6, 2008

ASP.NET - Import and display Excel spreadsheet

I needed to upload and parse an Excel spreadsheet and display it in a GridView. For the most part, pretty straight forward. I ran into an issue "Could not find installable ISAM" once I added "IMEX=1" to my connection string. This was solved by adding (char)34 as shown in my working example:


string filePath = "~/somedirectory/somespreadsheet.xls";

string strConn = @"Provider=Microsoft.Jet.OLEDB.4.0;"
+ "Data Source=" + MapPath(filePath) + ";"
+ "Extended Properties=" + (char)34
+ "Excel 8.0;IMEX=1;" + (char)34;

DataSet dsRecords = new DataSet();
// "Sheet" is name of sheet being opened, must add $ after the name
OleDbDataAdapter da = new OleDbDataAdapter("SELECT * FROM [Sheet1$]", strConn);

//da.TableMappings.Add("Table", "ExcelText");
da.Fill(dsRecords);
scheduleGridView.DataSource = dsRecords.Tables[0].DefaultView;
scheduleGridView.DataBind();

As a note, you can also access the spreadsheet directly using the file path:
    
string strConn = @"Provider=Microsoft.Jet.OLEDB.4.0;"
+ "Data Source=c:/somedirectory/somespreadsheet.xls;"
+ "Extended Properties=Excel 8.0;";

Saturday, April 26, 2008

will_paginate gem

While upgrading an old project to Rails 2.0.2 (and having trouble getting the will_paginate plugin to install), I discovered that there is a will_paginate gem:

http://mislav.caboo.se/rails/will_paginate-love/

Railscasts - will_paginate

Thursday, April 24, 2008

Rails - ActionController::InvalidAuthenticityToken


ActionController::InvalidAuthenticityToken in User#login

Showing user/login.html.erb where line #2 raised:

No :secret given to the #protect_from_forgery call.
Set that or use a session store capable of generating its
own keys (Cookie Session Store).

This happened after I switched from using the file system to a database for storing sessions (after uncommenting the line "config.action_controller.session_store = :active_record_store" in config/environment.rb).

Once you switch to database store, you'll need to comment the "protect_from_forgery" line in app/controllers/application.rb

# See ActionController::RequestForgeryProtection for details
# Uncomment the :secret if you're not using the cookie session store
#protect_from_forgery # :secret => 'b3e36f40312f075ea2697fb85180e312'

Rails 2.0.2 - setup with functional tests

If you want to use setup with functional tests in Rails 2.0.2, you'll need to rename the setup method to setup_with_fixtures and call super before running your setup code:
  
def setup_with_fixtures
super
# Set up code here
end

Sunday, March 23, 2008

Ruby on Rails - Using External Asset Hosts

Rails uses the "public" directory on the current host as the default location for static assets (images, CSS, Javascript, etc). Setting up your Rails app to use an external assets server is as simple as adding a single line to a configuration file.

Add the following line to your config/environment.rb file:

ActionController::Base.asset_host = 'http://assets.yourdomain.com'

It's also possible to set a single environment to use an external asset host:

Add the following line to config/environments/production.rb (the most likely spot - this line should already be in this file, but commented out):

config.action_controller.asset_host = 'http://assets.yourdomain.com'

Now this line in my layout

<%= stylesheet_link_tag 'scaffold/base' %>

will translate to http://assets.yourdomain.com/stylesheets/scaffold/base.css

Additional information on setting up multiple asset hosts can be found at:

http://api.rubyonrails.com/classes/ActionView/Helpers/AssetTagHelper.html

Wednesday, March 12, 2008

Ruby Gem - Highline

I was looking for a way to prompt a user for input on the console and ran across the RubyGem HighLine. It does a lot more than this, but here's how to capture STDIN from the console:

#!/usr/local/bin/ruby
require 'rubygems'
require 'highline/import'

username = ask("Enter your username: ") { |q| q.echo = true }
password = ask("Enter your password: ") { |q| q.echo = "*" }

Here's the output on the console:

$ ruby highline.rb
Enter your username: doug
Enter your password: ******

Get it here:

HighLine

or simply install the gem the normal way:

$ sudo gem install highline

Autotesting Javascript in Rails

From Dr. Nic's Blog:

Autotesting Javascript in Rails

Installing Ruby without openssl

When installing Ruby on an old machine, I got this error:


ruby make[1]: *** [ossl_ssl.o] Error 1


Other than installing openssl, which wasn't option, install ruby without openssl:


$ ./configure prefix=/home/doug/bin/ruby --without-openssl

Monday, March 10, 2008

Javascript - insertAfter(), formatCurrency, DOM Scripting

insertAfter()

function insertAfter(newElement,targetElement) {
var parent = targetElement.parentNode;
if (parent.lastChild == targetElement) {
parent.appendChild(newElement);
} else {
parent.insertBefore(newElement,targetElement.nextSibling);
}
}


formatCurrency()

function formatCurrency(amount) {
var i = parseFloat(amount);
if(isNaN(i)) { i = 0.00; }
var minus = '';
if(i < 0) { minus = '-'; }
i = Math.abs(i);
i = parseInt((i + .005) * 100);
i = i / 100;
s = new String(i);
if(s.indexOf('.') < 0) { s += '.00'; }
if(s.indexOf('.') == (s.length - 2)) { s += '0'; }
s = minus + s;
return s;
}


DOM scripting - create text node with value of &nbsp;

var textEl = document.createTextNode('\u00a0');

Accordion Effects with Scriptaculous

I had a couple recent projects that required an accordion effect. I normally use jQuery, but found a couple nice Prototype/Scriptaculous accordions (one is for a Rails project, so we're already using Scriptaculous).

stickman labs
grf-design

Note - The stickman labs accordion doesn't appear to work with the latest Prototype (1.6.1). I'm using the combined js file from the stickman site.

Wednesday, February 27, 2008

jQuery Sortable with Ajax, PHP, and MySQL

I needed to create a sortable list that would save the order position of data items. I've been using jQuery more and more and decided to go with the Interface Elements for jQuery.

I found a blog entry titled jQuery Sortable Ajax PHP Integration that was a good start. However, it didn't actually use MySQL to store the sorted data so I've gone ahead and added the database queries and updates.

First I created a simple table to store links (I'd like my client to order the links they enter through an admin interface).


CREATE TABLE links (
id int(10) NOT NULL auto_increment,
link_url varchar(50) NOT NULL,
link_text varchar(50) NOT NULL,
order_position int(2),
primary key(id)
);


Next is the PHP page that reads the link data from the table and displays the links based on the order_position column of the table.

index.php

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script type="text/javascript" src="/js/jquery/jquery-1.2.3.min.js"></script>
<script type="text/javascript" src="interface.js"></script>

<script>
$(document).ready(
function() {
$("#sortme").Sortable({
accept : 'sortitem',
onchange : function (sorted) {
serial = $.SortSerialize('sortme');

$.ajax({
url: "sortdata.php",
type: "POST",
data: serial.hash,
// complete: function(){},
//success: function(feedback){ $('#data').html(feedback); }
// error: function(){}
});
}
});
}
);

</script>
</head>
<body>

<ul id="sortme">
<?php
// Connect to the database as necessary
$dbh = mysql_connect('127.0.0.1','username','password')
or die ("Unaable to connnect to MySQL");

$selected = mysql_select_db("sort_test",$dbh)
or die("Could not select sort_test");

$result = mysql_query('SELECT * FROM links ORDER BY order_position');
while ($row = mysql_fetch_array($result)) {
print '<li id="' . $row{'id'} . '" class="sortitem">' . $row{'link_text'} . "\n";
}
mysql_close($dbh);
?>
</ul>

</body>
</html>


This will make an Ajax call back to the server where PHP will update the database as you drag and sort the list data:

sortdata.php

<?php

$sortme = $_POST['sortme'];
// Connect to the database as necessary
$dbh = mysql_connect('127.0.0.1','username','password')
or die ("Unaable to connnect to MySQL");

$selected = mysql_select_db("sort_test",$dbh)
or die("Could not select sort_test");

for ($i = 0; $i < count($sortme); $i++) {
mysql_query("UPDATE links SET order_position=$i WHERE id=$sortme[$i]");
}
mysql_close($dbh);
?>


A little CSS certainly wouldn't hurt things. Here's some I found on another tutorial:

<style>
ul {
list-style: none;
}
li {
background: #727EA3;
color: #FFF;
width: 100px;
margin: 5px;
font-size: 10px;
font-family: Arial;
padding: 3px;
}
</style>


I wouldn't call this production ready code, but all the pieces are in place if you'd like to use jQuery with Ajax, PHP, and MySQL for a sortable list. Don't forget, you'll need to get jQuery itself and the interface.js file as well. Another thing to consider is alerting the user if the update fails or takes a long time.

Thursday, February 21, 2008

Ruby on Rails - Testing with Multiple Databases

I ran into errors when running tests for a Rails app using multiple databases. When testing, fixtures are automatically inserted into the primary database, and I was getting an error on an unused default fixture that was created when I built the model tied to the second database. Removing that fixture got my tests to pass...

More info here:

http://www.elctech.com/2007/3/8/using-and-testing-rails-with-multiple-databases
http://www.elctech.com/2007/5/30/using-and-testing-multiple-databases-in-rails-part-2

and on multiple databases in general:

http://wiki.rubyonrails.org/rails/pages/HowtoUseMultipleDatabases
http://wiki.rubyonrails.org/rails/pages/HowtoUseMultipleDatabasesWithFixtures

Tuesday, February 19, 2008

jQuery

I've been using the jQuery Javascript library a lot lately and it's definitely become my favorite library. (I've written four plugins in the last couple months to boot)

Here are a few links to useful jQuery resources.

jQuery: The Write Less, Do More, JavaScript Library
jQuery UI: Widgets, Components, and Interaction
Plugins - jQuery JavaScript Library
jQuery Blog
Learning jQuery
Visual jQuery
IBM developerWorks: Simplify Ajax development with jQuery

composite_primary_keys.gem

Ruby on Rails does not support composite primary keys and a legacy database I'm working with has lots of them...after a quick search I found this useful Gem:

composite_primary_keys.

Wednesday, January 23, 2008

10 Ruby on Rails Plugins You Should Be Using

Media72: 10 Ruby on Rails Plugins You Should Be Using

jQuery: Cookie Plugin

I've become a big fan of jQuery and have contributed two small plugins myself, so when it came time to switch to a library for cookie manipulation with Javascript (time to replace all that nasty inline JS code) my first choice was jQuery.

Not to be disappointed, I found that Klaus Hartl has written a very slim and useful Cookie Plugin for jQuery.

Here's a small page test using the jQuery Cookie plugin:

<html>
<head>
<title>jquery cookie</title>

<script type="text/javascript" src="jquery-1.2.1.min.js"></script>
<script type="text/javascript" src="jquery.cookie.js"></script>

<script type="text/javascript">

$(function($) {

function displayMessage(msg) {
$('#message').html(msg).css({color: 'green'});
}

displayMessage('jQuery cookie plugin test');

$('#setSessionCookie').click(function() {
$.cookie('test', 'Hmmm, cookie');
displayMessage("Cookie 'test' has been set.");
});

$('#setCookie').click(function() {
$.cookie('test', 'Hmmm, cookie', { expires: 7 });
displayMessage("Cookie 'test' has been set and will expire in 7 days.");
});

$('#getCookie').click(function() {
displayMessage("The value of the cookie named 'test' is: " + $.cookie('test'));
});

$('#deleteCookie').click(function() {
$.cookie('test', null);
displayMessage("Cookie 'test' has been deleted.");
});

$('#testCookiesEnabled').click(function() {
$.cookie('testcookiesenabled', null);
$.cookie('testcookiesenabled', 'enabled');
if ($.cookie('testcookiesenabled')) {
displayMessage("Cookie: "+ $.cookie('testcookiesenabled'));
} else {
displayMessage("Cookies disabled");
$.cookie('testcookiesenabled', null);
}
});

});

</script>

</head>

<body>

<p><span id="message" style="forecolor: red;"></span>

<p><input type="button" id="testCookiesEnabled" value="Cookies enabled?"/></p>

<p><input type="button" id="setSessionCookie" value="Set session cookie"/></p>

<p><input type="button" id="setCookie" value="Set cookie expires in 7 days"/></p>

<p><input type="button" id="getCookie" value="Show cookie value"/></p>

<p><input type="button" id="deleteCookie" value="Delete the cookie"/></p>

</body>
</html>


My jQuery cookie demo using jquery.cookie.js

Monday, January 21, 2008

ASP.NET GridView - Header always bold

GridView uses a table and creates a <th> tag for the header, which will bold all the text generated by the GridView. Here's a simple solution:

Set a style:

<style>
.GridViewHeaderStyle th {
font-weight: normal;
}
</style>

And apply it to your GridView:

<asp:GridView ID="grdSomeGrid" runat="server" CssClass="GridViewHeaderStyle">
...
</asp:GridView>

Saturday, January 19, 2008

ASP.NET GridView "ShowFooter"

Sometimes it's the small things that get you...

Uh, not that it ever happened to me, but in case you can't get your GridView template footer to show up...don't forget 'ShowFoooter="true"' :)


<asp:GridView ID="grdListings" runat="server" AutoGenerateColumns="false" BorderWidth="0"
AllowPaging="true" PageSize="5" OnPageIndexChanging="grdListings_PageIndexChanging" ShowFooter="true">

<Columns>
<asp:TemplateField>

<HeaderTemplate>
Header
</HeaderTemplate>

<ItemTemplate>
Item
</ItemTemplate>

<FooterTemplate>
Footer
</FooterTemplate>

</asp:TemplateField>
</Columns>
</asp:GridView>

Saturday, January 5, 2008

Install Ruby without openssl

When installing Ruby on an old machine, I got this error:


ruby make[1]: *** [ossl_ssl.o] Error 1


Other than installing openssl, which wasn't option, install ruby without openssl:


$ ./configure prefix=/home/doug/bin/ruby --without-openssl

Autotesting Javascript in Rails

From Dr. Nic's Blog:

Autotesting Javascript in Rails

Thursday, January 3, 2008

Upgrading to specific version of Rails


sudo gem install -v=2.0.1 rails --include-dependencies

About Me

My photo
Developer (Ruby on Rails, iOS), musician/composer, Buddhist, HSP, Vegan, Aspie.

Blog Archive

Labels