Recession Squeezing Customer Options

The current economic climate in the U.S. is causing downsizing of customer options in leiu of price increases. Recently, Subway nuked the small drink size option. Panera no longer allows a half sandwich order. It’s a defacto price increase. Store hours are among the casualties, too.

Okay, I am done with RadioShack forever

I wander into my local RadioShack for a simple RCA to S-Video adapter (or SVHS). After looking around, I ask the lady and she finds one for me. It looks unimpressive enough. I ponder a cable to plugin to it, since the original one being replaced was male to female and this was female to female. She starts to grab one of those expensive monster cable sets, but I waive her off.

I proceed to buy the adapter, at which point she rings it up… It was almost $25! No kidding. I said no thanks. Visibly annoyed, she canceled the order and I walked out, never to return.

Years ago I recall I ordered cables with success from CCT. Sure enough, they have what I need for about $1.77. Yes, more than $20 less than RadioShack. Same part.

RadioShack is a failure.

gnump3d and downsampling mp3s

Recently I discovered gnump3d. It supports downsampling, which is a feature I especially need. However, the example reencoding command for handling MP3s doesn’t seem to work. Instead, I am using a different variant.

downsample_medium_mp3 = /usr/bin/sox -t mp3 $FILENAME -t wav - | /usr/bin/toolame  -b 128 - -

The original uses the lame encoder to both read the MP3 source file and then reencode it, but that seems to fail miserably. Fortunately, using sox seems to resolve it.

It’s useful to note that this will not throttle the client connection, so if your goal was to limit the stream to, say, 128Kbps, it won’t work. Instead, you need to rate limit some other way.

TigerDirect CompUSA spam

What a perfect marriage. I never liked CrapUSA. It seems TigerDirect has bought them out, and now I am getting weekly spam about the CrapUSA store re-openings. I honestly don’t care. I unsubscribed — supposedly — after receiving the second email about them, but today I have yet another. Supposedly I am unsubscribed, again.

You have been unsubscribed from 'tiger_orlando'.

We shall see if that’s the end of this nonsense, or not.

Rails STI resouce routes routing

After hours of Googling and an ill timed browser reload while typing this the first time, I finally happened upon RoR ticket 10454 which details the problem I am having with inferred routes and STI models. I modified it to supposedly support any level of model nesting. Seems to work now. I inserted it into my environment.rb presently.

module ActionController
  module PolymorphicRoutes
    def build_named_route_call(records, namespace, inflection, options = {})
      records = Array.new([extract_record(records)]) unless records.is_a?(Array)

      # No STI
      #base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", records.pop)}_"

      # Hack
      record = records.pop.class
      until (record.superclass == ::ActiveRecord::Base)
        record = record.superclass
      end

      base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"

      method_root = records.reverse.inject(base_segment) do |string, name|
      segment = "#{RecordIdentifier.send!("singular_class_name", name)}_"
      segment << string
    end

    action_prefix(options) + namespace + method_root + routing_type(options)
    end
  end
end

DokuWiki and HOWTO migration

Since I deployed DokuWiki last fall, I’ve found it to be an excellent tool for managing documentation. It enhances the quality of the final documentation product whilest not getting in the way of writing it. I rather enjoy it.

I am now migrating some of my existing HOWTO documents over to the wiki. Afterward, there’s an chance I’ll actually integrate suggestions and corrections into the documents; something I haven’t done in two years. Messing with the Docbook XML has been too much of a pain to go back and perform any document maintanence.

The first document to be moved over, for no particular reason, is the Dirvish HOWTO.

has_many :through and with_scope protected

If you were following the excellent has_many :through blog, you may find you can’t use with_scope in the methods defined on your association. Instead, just send :with_scope along.

has_many :active_campaigns, :through => :campaign_distributions do
  def <<(active_campaign)
    CampaignDistribution.send(:with_scope, :create => {}) {
      self.concat active_campaign
    }
  end
end

Hilarious craigslist job post for Web Freaks

I was flipping through craigslist today and came cross a job posting for Web Freaks worthy of a ticket to planet clue. Let’s take a trip down the rabbit hole.

We are seeking a highly motivated Level 3 (Highest Level) Linux System Administrator to
maintain and administer approximately 500 LINUX servers in a Kissimmee, FL based Data
Center. $50K per year starting pay, no other benefits are provided as of now. Prior customer
service experience is a plus. Prior Linux experience is a must and cPanel experience is a very
large plus. Must be flexible with hours. On call required. We prefer that your life is not occupied
and you are willing to devote full attention to this company, our assets and our customers.

First, managing large clusters for only $50k a year without benefits? Laughable. Second, an unoccupied life? Sounds like code for indentured servitude. Web Freaks is clearly the kind of employer that doesn’t engender a desire to give of oneself in the pursuit of greater profit. Here’s a hint: treat someone like a valued, respected employee and he’ll move mountains for you. If you instead treat him like a tool, you’ll be stuck writing authoritarian nonsense like this job posting and be found wanton.

So, with that in mind, let us continue.

 - Capable of producing effective resolutions:
    we want you to think before you execute commands / respond to clients

Heh.

- INTEGRITY and HONESTY. IF YOU LACK INTEGRITY OR HONESTY DO NOT APPLY.
- Clean Appearance, well groomed, good manners are all necessary
- The ability to maintain a professional relationship with your employer,
    and maintain respectful manners at all times to your employer and fellow workers
- Good speaking skills. MUST SPEAK ENGLISH FLUENTLY - NO EXCEPTIONS
- Required to live within 15 minutes of the data center for rapid response.
    No Remote Technicians allowed
- AGE: A MATURE PERSON over 30 years old with life skills and experience
- EXTREMELY COMPETENT IN GENERAL
- TIME MANAGEMENT IS A REQUIREMENT. If you are not an expert in Time Management
     (ie: getting to work ON TIME, MAXIMIZING YOUR OFFICE TIME FOR THE COMPANY, etc)
     then you do not need to apply. We have a strict policy about being at work on time and etc.

Seeking a professional? Understandable. Not paying a professional size salary? Oops. And yes, requiring someone to live within 15 minutes of a data center is going to require more compensation.

Someone with life skills over 30 isn’t going to work in central Florida for 50K, no benefits, within 15 minutes of a colo facility whilest being on-call 24/7, interacting with customers, and managing at least three other employees.

Yeah, wow.

 If you are not this person, a person who is wanting to strive for success and greatly
appreciate the company you work for, then please, do not apply.

In closing, please bow before the greatness of your employer. Work is life!

Sounds like someone needs to yank his ass out of his greatness and realize your employees are your partners in success and failure. Treat them as valued individuals and they’ll serve you well. Treat them poorly and they certainly won’t appreciate you or your company.

CL post, for as long as it lives.

Daniel Suarte, IT Services spammer

Today I received my third email from someone claiming to be a Daniel Suarte, writing about filing open IT positions. However, I’ve never heard of this person before and I have no company or positions to fill. Clearly, this lamer is a spammer. He’s posting from a gmail account Daniel.Suarte@gmail.com. The email was not sent via gmail.

What a llamah.

Spammer is now using

Hi,

My name is Jennifer Florin and I am conducting my periodic check with you and your company to see if we can assist with any of your current IT/IS/Technical openings?

ed2k helper with Ruby

Nothing fancy. It works.

#!/usr/bin/ruby1.8

# ed2k link helper for Opera

host = '10.10.1.1'

link = ARGV.first

require 'net/telnet'
core = Net::Telnet::new(
        'Host' => host,
        'Port' => 4000
)

core.cmd("dllink #{link}")
core.cmd('q')
core.close

Moneydance, cross-platform money tracking

I’ve been investigating a switch from gnucash to Moneydance. There’s no way to export data directly from gnucash. Fortunately, someone wrote a Java app that will parse the XML and produce a basic QIF export with your accounts and transactions.

Thus far, Moneydance has been able to handle downloading data using OFX for most of my accounts. For Chase, I had to enable OFX functionality through the Web site.

Update, October 18th. Moneydance has its own special bag of problems. Somehow I managed to get it to raise an exception when I try to save my datafile. That result is a corrupt file that cannot be opened. I created my accounts by using gnucashtoqif and then editing accordingly, so I may have started with some odd data. Only seems to happen when I import an OFX from my brokerage account, but only with this data set. It imports fine with a new Moneydance account set. Still, gives me a moment of pause.

The investment stuff isn’t polished yet. If you track a bunch of investments, you’ll find some things are missing and many things aren’t immediately obvious.

I also found the liability account was kind of hit and miss. I prefer the way Gnucash handles it. I had to fight with Moneydance to automatically add all the prior transactions for my loan. The first time, it seemed to be willing to add some, not all, of the transactions. When I created the liability again, I could find no way of forcing it to create past loan payments. Seems like some kind of a bug that I had to do it by hand for two years of data.

I’ve filed a couple of these issues with Moneydance’s Trac based ticket system.

For $30, you get what you pay for, I guess.

It’s a positive enough experience to move forward with it and not enough to enslave myself to Intuit’s Quicken and be forced to upgrade every N years to maintain access to online account syncing.

Plus, Moneydance actually offers QIF and XML export, so I can at least recover my data more easily than from Gnucash should I decide to move to something else. (What else…?)

piston saves!

I download a prerelease of the latest Engines earlier in the summer with piston. My tests were failing in strange and magical ways. I spent quite a bit of time trying different things, then finally resorted to a fresh Rails app that I slowly populated with relevant files. Quickly I discovered the problem was my Engines plugin. I really thought I had the full release, but clearly not.

I was really relieved when I retargeted piston at the actual release version of Engines and updated. A single file change and I was back in business.

jasonb@faith:~/src$ piston switch http://svn.rails-engines.org/plugins/engines \
  vendor/plugins/engines/
Processing 'vendor/plugins/engines/'...
  Fetching remote repository's latest revision and UUID
  Restoring remote repository to known state at r600
  Updating remote repository to http://svn.rails-engines.org/plugins/engines@611
  Processing adds/deletes
  Removing temporary files / folders
  Updating Piston properties
  Updated to r611 (4 changes)

As it happened, the problem was:

+++ vendor/plugins/engines/lib/engines/testing.rb
@@ -2,7 +2,6 @@
 # for more details.

 require 'test/unit'
+require 'test_help'

Sigh. It does help to import the correct branch of the code, yes?

Because I used piston, it was stupid easy to retarget the correct version of Engines, then update and see what it was I screwed up. Further, any local changes I made — I had none for this plugin — would be preserved and subject to the usual Subversion conflict resolution process (such as it is).

It’s best not to modify plugins lest you find yourself maintaining even more code, but if you must, at least your changes won’t be clobbered. And you don’t have to run diff between plugin checkouts to try to deduce which of your changes are about to get lost.

mootools and form field hints

Usually I love mootools, really. Usually. I found CSS Guy’s form field hints posting inspiring, so I modified it to work with mootools. The result is something simple and easy.

$ES('div.hint').each(function(el) {
	new Element('div').addClass('hint-pointer').setHTML(' ').injectInside(el);
});
$each(document.forms, function(form) {
	$each($(form).getElementsBySelector('input, select'), function(input) {
		if($(input).getParent().getElementsByTagName('div')[0]) {
		$(input).addEvent('focus', function(e) {
			hint = $E('.hint', this.getParent());
			hint.setStyle('display', 'inline');
			hint.effect('opacity', {duration: 500, wait: false}).set(0).start(1);
		}.bind(input));
		$(input).addEvent('blur', function(e) {
			hint = $E('.hint', this.getParent());
			hint.effect('opacity', {duration: 500, wait: false}).set(1).start(0);
		}.bind(input));
		}
	});
});

I modified the CSS to suit my layouts better. You’ll notice above I am using divs instead of spans. I noticed IE6 would never hide the hints div ever again once it was diplayed. Fortunately the fade effect hides it for me.

.hint {
	display: none;
	position: absolute;
	right: 1em;
	border: 1px solid #c93;
	padding: 0.4em 0.5em;
	background-color: #ffc;
	font-size: 80%;
}
.hint .hint-pointer {
    position: absolute;
    left: -10px;
    top: 2px;
    width: 10px;
    height: 19px;
    background: url(/images/pointer.gif) left top no-repeat;
}

The mootools bonus: I can easily add a fade effect, which I did. I further simplified things by adding the hint-pointer div at render time with mootools instead of repeating the markup over and over again. A little DRY from Rubyland.

Yay.

dry model validations with class_eval

I find myself having to validate an email address in a couple of different models. I’ve had to refine the pattern a couple of times, but don’t always remember to do it in every place, which is silly anyway. Since I don’t feel the need to delve into a new validation, like a validates_email_address or whatever, the following lets me simply reuse an existing validation in multiple places.

module EmailValidation
  def self.included(base)
    base.class_eval do
    validates_format_of :email,
      :with => %r{\A([-\w.\+]+)@((?:[-A-Za-z0-9]+)\.+[A-Za-z]{2,})\z},
      :message => 'appears to be invalid. Please verify it is correct.'
    end
  end
end

The above is just saved as email_validation.rb in lib/. Then later in my model.

class FooWithEmail < ActiveRecord::Base
  include EmailValidation
end

And no, an email address shouldn’t have A-Z in the domain portion, but I run downcase! against that attribute before saving. Users can’t really be bothered to care if a domain should be uppercase, lowercase, or mixed.

ActiveScaffold and Mongrel death in production

Oh, I have such love for plugins that copy files around in production. I especially love it when the Rails app doesn’t have permission to write to the filesystem — why would it in production? — and Mongrel simply goes away. Running Mongrel without backgrounding it with start-stop-daemon reveals the trickery in action.

** Starting Mongrel listening at 127.0.0.1:8101
** Starting Rails with qa environment...
/usr/lib/ruby/1.8/fileutils.rb:1246:in `initialize': Permission denied -
 /srv/rails/current/config/../public/stylesheets/active_scaffold/default/stylesheet-ie.css (Errno::EACCES)

Why?

vendor/plugins/active_scaffold/init.rb
##
## Run the install script, too, just to make sure
##
#require File.dirname(__FILE__) + '/install'

Seriously, just don’t do it. At least verify the file is present before doing a blanket copy on each and every startup. Seriously.