Non tech staff in a web company are useless dead weight
Sorry for the sensationalist title, but I thought it might grab your attention. I have found myself wondering a great deal recently, whether an internet company really needs so many non-tech staff as the ones I have worked in have had. It seems to me, that the majority of the work is done by web developers, database programmers and search engine experts. Is there really a need for an editorial team, an analytical team, a bunch of product managers and a whole advertising team? The tech staff are outnumbered by non-tech, but produce almost all of the work.
I understand that this is a very black and white way to view things. I know that there is a need to sell advertising on a site to keep revenue coming in. I know that someone needs to provide fresh content to keep the search engines happy. But, surely it is possible to survive as a business with just one person selling advertising space, and just one person writing content. Instead you end up with a whole bunch of people all with different ideas, all trying to take the business in different directions, all fighting for the time of the developers. The developers then end up working on 10 different things at once, getting none of them done fast, or to the quality they could achieve if they were more focused. Which brings me to my next point.
Who should control the direction of the business, tech or editorial?
I’m pretty sure that most of the big successful web sites are run and controlled by IT staff. Google, Yahoo, Digg, Reddit, Plentyoffish (1 guy), and many more. Non-tech staff are ill-equipped to make decisions as they are usually unaware of the possibilities and/or limitations of diffferent languages and technologies. I’m not saying they should not have a say at all. Everyone should be able to come up with ideas, but the nitty-gritty should be handled by IT. Advertising should stick to selling advertising, project management should stick to managing projects, and editorial should be primarily writing content to put on the site.
My 10 top films
I fancied a five minute break at work this morning, so I decided to list my 10 favourite films. I’m sure I’ve probably missed out some really good ones, but here’s the list anyway:
Pans Labyrinth
Donnie Darko
Pulp Fiction
Manchurian Candidate
The Matrix
The Terminator
Lock Stock and Two Smoking Barrels
The Usual Suspects
Snatch
The Illusionist
Black hat SEO - The easy option?
Back in October 2006 I decided that working for someone else for the rest of my life was not going to get me the big house, fast car and hundreds of women I have hoped for and expected ever since I can remember! I figured the easiest way to achieve these targets would be to launch a web site of my own. Something with a glaringly simple design, clear navigation, and providing a useful service to the user. Best Mobile Phones was born. OK, it seemed like a good idea at the time!
I quickly knocked up a very simple site in a couple of hours using Ruby on Rails, bought the domain name, and uploaded my site to my web hosts PrimeHosting.co.uk (who I thoroughly recommend). My design was shamelessly ripped from mobile-phones-uk.org.uk. OK, now time to get some traffic. I should point out before going any further, that when I started this site I had only the best intentions as far as search engine optimisation (SEO) was concerned!
I submitted the URL to Google, Yahoo, Ask, MSN live and a few other search engines. Then using a freelancer site, I found someone to write reviews of mobile phones for me for $3 per article. Bargain! Soon I had 50+ very poor quality mobile phone articles. As everyone with any SEO experience knows, content is king. In order to get the most benefit from all this content, I uploaded it to the site database slowly over a period of about 3 weeks, making the search engines believe that my site was updated regularly. By now my site had been indexed by Yahoo and Google, and I was receiving around 20-30 unique visitors a day from Yahoo. Nothing from Google, as I was still too low in the rankings. After no more than a month I was fed up with the slow progress of my various white hat SEO methods and began to turn to the dark side.
When a new phone is released the manufacturer publishes a press release. My first port of call was to copy and past the press release for each phone on to my site, and link it to each phone via a “related articles” section. This isn’t particularly bad, but search engines do frown on duplicate content. Next I started to spam the comments on all of the other major phone review sites with links to my site. Surprisingly most of these comments where actually published! At the same time as this, I started to increase the keyword density across the site. Every where a phone was referenced, I made sure the whole manufacturer name, phone name and model was quoted. I did the same on the left navigation. After doing this for around a month my rankings (and traffic) where shooting up! I was receiving around 300-400 unique visitors per day, and the site had only been going for two months!
There were many more tricks I was looking forward to trying, including creating a hexagonal blog farm to boost PR. I never got a chance to try them though, as 2 weeks later I was sandboxed by Google, my main traffic generator. After many hours spent on Google forums, I found that because of the way I had keyword stuffed my left nav, I had been labeled as a black hat doorway page by the search engines. Once I had reverted the navigation to its original state it took a further month to get the site out of the sandbox, and I had to upload new content daily. Even once out of the sandbox my ratings never recovered, and eventually I decided to stop flogging a dead horse (gave up).
There are hundreds of ever evolving black hat SEO techniques. I only used a couple of them. They may seem like an attractive option for getting results quickly which otherwise can take months. In reality, however, they can do much more harm than good, and unless you know what you are doing, they are far from the easy option.
UPDATE
Although my black hat tinkering did destroy my results in google, and also the majority of my traffic, I do have a glimmer of hope, I appear to still be number 1 in microsoft live search for the term “best mobile phones”, which is delivering around 200 UVs per month:
http://search.live.com/results.aspx?q=best+mobile+phones&mkt=en-gb&FORM=LVCP&go.x=0&go.y=0&go=Search
in_place_form_field - editing empty fields
Ruby on Rails AJAX helper in_place_form_field is great. However, it does not allow you to edit fields that have no data, which can be a real pain. There is a simple solution! Create the following extensions.rb file in RAILS_ROOT/lib/.
class Extensions
ActionView::Helpers::JavaScriptMacrosHelper.class_eval do
def in_place_editor_field(object, method, tag_options = {}, in_place_editor_options = {})
tag = ::ActionView::Helpers::InstanceTag.new(object, method, self)
tag.nil_content_replacement = in_place_editor_options[:nil_content_replacement] || 'Unknown'
tag_options = {:tag => "span", :id => "#{object}_#{method}_#{tag.object.id}_in_place_editor", :class => "in_place_editor_field"}.merge!(tag_options)
in_place_editor_options[:url] = in_place_editor_options[:url] || url_for({ :action => "set_#{object}_#{method}", :id => tag.object.id })
tag.to_content_tag(tag_options.delete(:tag), tag_options) +
in_place_editor(tag_options[:id], in_place_editor_options)
end
end
ActionView::Helpers::InstanceTag.class_eval do
attr_writer :nil_content_replacement
def value(object)
unless object.nil?
v = object.send(@method_name)
if @nil_content_replacement.nil?
return v
else
(v.nil? || v.to_s.strip=='') ? @nil_content_replacement : v
end
end
end
end
end
Then in RAILS_ROOT/config/environment.rb at the bottom add the following line:
require ‘extensions’
Generating an EAN-13 barcode using Ruby on Rails and RMagick
A fair while ago now I had to create an administration system for a client which needed a barcode printed out to stick on various items. These barcodes were then to be read by a barcode reader at a later date to check their progress in the system. There doesn’t appear to be much information on the internet on how to do this, so I have decided to publish my barcode creating code. There are many different types of barcode. I chose to generate one of the most popular formats - EAN-13.
The demonstration
There is a bare bones demonstration here: Barcode demonstration
Create the form
<div style="width: 500px;"> <%= start_form_tag( :action => "barcode" ) %> <label>Code <small>(12 or 13 digits)</small></label> <%= text_field( "e_a_n_bar_code", "code", "size" => 15 ) %> <label>height <small>(20-50)</small></label> <%= text_field( "e_a_n_bar_code", "size", "size" => 5 ) %> <%= submit_tag( "go" ) %> <%= end_form_tag %></div>
Create the controller
class BarcodeDemoController < ApplicationController
def form
end
def barcode
bar = EanBarCode.new
@image = bar.getImage( params[ :e_a_n_bar_code ][ "code" ], params[ :e_a_n_bar_code ][ "size" ].to_i, 'png' )
end
end
Create the model
class EanBarCode
SIZES = [
[ "20", "20" ],
[ "30", "30" ],
[ "40", "40" ],
[ "50", "50" ]
].freeze
attr_reader :groupC, :family, :ean13, :courB08_pil, :courB08_pbm
attr_writer :groupC, :family, :ean13
def initialize
a = { 0 => "0001101", 1 => "0011001", 2 => "0010011", 3 => "0111101", 4 => "0100011",
5 => "0110001", 6 => "0101111", 7 => "0111011", 8 => "0110111", 9 => "0001011"}
b = { 0 => "0100111", 1 => "0110011", 2 => "0011011", 3 => "0100001", 4 => "0011101",
5 => "0111001", 6 => "0000101", 7 => "0010001", 8 => "0001001", 9 => "0010111"}
c = { 0 => "1110010", 1 => "1100110", 2 => "1101100", 3 => "1000010", 4 => "1011100",
5 => "1001110", 6 => "1010000", 7 => "1000100", 8 => "1001000", 9 => "1110100"}
self.groupC = c
self.family = {0 => [a,a,a,a,a,a], 1 => [a,a,b,a,b,b], 2 => [a,a,b,b,a,b], 3 => [a,a,b,b,b,a], 4 => [a,b,a,a,b,b],
5 => [a,b,b,a,a,b], 6 => [a,b,b,b,a,a], 7 => [a,b,a,b,a,b], 8 => [a,b,a,b,b,a], 9 => [a,b,b,a,b,a]}
end
def makeCode( code )
self.ean13 = code.split(//)
# If the code already has a checksum
if self.ean13.length == 13
# Verify checksum
verifyChecksum( self.ean13 )
end
# If the code does not have a checksum
if self.ean13.length == 12
# Add checksum value
self.ean13 << computeChecksum( self.ean13 )
end
left = self.family[ self.ean13[ 0 ].to_i ]
# Add start separator
strCode = 'L0L'
for i in 0..5
strCode += left[ i ][ self.ean13[ i + 1 ].to_i ]
end
# Add the middle separator
strCode += '0L0L0'
# Compute the right codage class
for i in 7..13
strCode += self.groupC[ self.ean13[ i ].to_i ]
end
# Add stop separator
strCode += 'L0L'
return strCode
end
def computeChecksum( arg )
# UPCA/EAN13
weight=[1,3]*6
magic=10
sum = 0
for i in 0..11 # checksum based on first 12 digits.
sum = sum + arg[i].to_i * weight[i]
end
z = ( magic - (sum % magic) ) % magic
if z < 0 or z >= magic
return None
end
return z
end
def verifyChecksum( bits )
computedChecksum = self.computeChecksum(bits[12])
codeBarChecksum = bits[12]
end
def getImage( value, height = 50, extension = ‘png’ )
# courbB08.pil PIL Font file uuencoded
courB08_pil =”"”eJztl91rFkcUxp+Zt7vGFYzVtiJKICgYlLRWkaBBVGgDraFGCH5gsQp+QMBqabAVRYJYAlakCkohCpYgxaLkIu1NvLBeSAStglpqL6xQAsVe2AuL5u2buH3mzGaYPf9AKWTl8d3nl7MzZ2bnazvea9+97+PurFWut5e0Zu+s7VybYfKavP7LK3X/5TlM4Q3/OWbyf1ARD/6mgb2SjwtPhbpnq0iKZ6ahrmCjwqbxdgamRnHOA69jimN5zvIS8cDcUEeVdYzRAw1FHcJYXgPvG4s6Jlgj7xeEequS3wLeNvGvnrEOtq+Jt82szT+b86+WHlgS2jHGuHF6YHnog1zaupxqCcy3t4X3rVG9iXhgjW+bsFQ80BaxRDywTrF1VId6toPaqOI2UlsV20ptV2w7tUuxXVSXYl3UvoIZ9kFFPPBJ6D/HLD3QXbwjyDjI6YHPiz5FXiN7SQ8cDu/N9/1h3veEOP/Oe6gvQnmuvYYe+NL3qYyNVDxw2seF8XKa+jrKJREPnFdx56l+xfqpS4pdogZUeQPU91FcKh64GveBeOCaKu8adUM9e4O6reJuU/cUu0c9VM8+pB6r/B5TI+rZEerPUpyhB/6K5lsqHniuyntO1VR5Nb5CU86FHqZOsTqqXrF66o2ojlQ8zDwVN4+aX86FHqYpXg9YLeevWRzPc7LFZG+V1wN6mKXxvMzH6GFaJua5zGNLD7MqmtNcc+hh1oT1oCb5cf6aNj92mbPMGXqY9jCPasLaqQ1hjMv8pYfZpOI2UR9GcYl4mB1RnMtvB9me8N583B5qb3mNoIf5NGJc1+hhPvPrrjybioc5op49Qh0Ldfj8jlHHQ3s9O059Fc3zRDzMmVKcpYfpU+3oI/umxJyH+TYqLxUPc0X13xVqMMovFQ8zpPIbon6MWCoeZljVMUz9VIqz9DAP1Dt6QP0a9gpZ7+lhHhXjysreaOhhfiv1vaGH+T2Mv5rbU+hh/uAaOnlNXv+Hy4/7mtv3OW5hnpTODIYe5mm0xqbiYf4OcbLv08NU1ZyuuqKLOEvm6sjhJkd8TjRustgkrO3uvFGjh60r1uyiPHrY6eH84tb7l/SwM8vrAT3snHgNY9wcsoby+Y8edn5UxxTxsIuitrlcFpG9GcVx/6CHXRrKk72MHrYl3stYB/ceu7I4X02wlWSrCmaF1ehhV7NrovWKHrattI4betj20Fc8r7E87kf2g+gcy32BHnZDfKZmHPco2xnl4vqlk2yz6r/N1EfRPpiKh90d7VGpeNi9inGPst2lNdbSwx4McS8k7iDVE/Ytz3qoXsV6qZOKnaTOBDYqjPuRPRfOkz7uHNUf4uQMQg/7XekMYulhB6JnE/GwP0T1JuJhryrGM6G9HuWSiIcdDnPmhTs70sPeCuPes1vUXcXuUvcDGxV2n/olOisn4mEfhfOVby/3KDsSlZeIh32iGOe0faoY57R9ptgzajTKJREPOx7aJnOfHhUbxov0Mz0qU8v50aMyo/wu6VGZrdhsqqH8fnllHEEz4zj6DNMxK+4X+gyv8cszyoU+4zfmjNAO9zuXrNGXF1gj2ULFFpI1K9ZMtiww//22jGwFXg39535XkK0O+cl5gz7Du6iP5wd9hvfDs9LP9BnWR/U6tp6sU7FOsi1RLo5tIdsWled+t5HtVO3YSdalWBfZftW2/WQHVH4HyA6F9+GfPUR2VOV3lKxXsV6yE4qdIDul2Cmys6ptZ8n6Qi7+m7OP7ELoU/8tdIHsoo8L+V0ku6xyvkw2qNgg2VBgvg+GyK6XyrP0GW5ydE3EuXd5k+xOeOdVibtD9jNm/Qv15O4i”"”
# courbB08.pbm font file uuencoded
courB08_pbm =”"”eJxNkntM01cUx8+P2/1apUAZEpECq4KRjKhF0E55FYEp4yG6mglz2Q8Q1BhERhhls/zKI+CID4wbIAPKpk4GAzqZPKKMX2GIUwGJG+ImtKwKjIzXcGuBtncV0Hn+uLnn5Nzv55xv7mdRkbusVjquBACr0N3B+wCQi/m+ijAf4LGl/wgAiwkNDpRIyyABSjGkBQ/fa3c1bfLs4U8ulDcYUs/502rTpIlO9pycKp/Buql6f3rmZ1NqvpO2SZXf0duY3j0563zjoZpW8AvHRmVeZ/Co36mFR8bERzlsxOMJ+oJshsS57rlfzFzmnZFEFnIEZjTGizgLsLzjl4QtrNprBRu10e+u9GgePHjG63bPDw/H87uix0Vtsvkqg9qOlUimPLiOM4z69YfqIu5Pa2Sr/io6n9Xmf9e+57W1Iapo4lLQBdLSWc/z3KOSlgznDXTW/Flh21kXIeUIX8FZVL9dwP4NBH5jglYxkBNFmWgMcfsAxM/9gEL5TTwYpnfElR8qQ+WiCgeTHOAfb2bW/cQC/FozFOOQzAebtjRvQLI7HBtXvaZe25a3Q/1vZpPa+kd1XXKuflr5Cm48YUsUcjMXjsm/sf+22s6zQAbGZ8mEXMzSE4y9AHhRpltwB1N9ynz5H2MOi0MEi4E5O1ov9ogrFU5cMWAcdgQb3xHFtFK+0pkhVnYWxltx92j69p6jJ9OnHr+Cq5×5X6Mz70JcX2tEG5LIShM4EHIGoLIRsHzcvEuGwMYA4DZPn7gPMA1QIgltnt82cTu7j5n76mmz3TU5Bh3PFRTHku52aBgaTnJD7m1c0a3hNjbWWjBtMsP/OFac/LYANAAWepdYodB58NBFIuOjNSQ4cgXplqP2RyOe8fd999T8weqBRwLwNFdQobHgA1/YTV8PH+TwV59vBo7Y1J4rmHFv3T9e8rmmXdGSuPpSbBnhYJ7V8ICz6AfGcdTpRkpCUU8WcOT8wb+dSHIb6QZapx0MY2DO4i7jYV2AUNkkErpQFHVYmFRmYD7OJhDyQSiow4IkrS3TbpQqFA9slE4jnj6peXMTC+N8buJ20Uv5eOothuGIiluyCDtff3miBzJHjncOIC3bPT8FLabRPd0TCWy346Mmn9Rz23WyNMJcsnqhQani3CMFOZuYU7c20zTNVqNbGPNxALWnybeLEcTvXWpc10leI5ae/CI9qBqI686cnO6P6F33e2vAp0nz9+hnbNeueh/261UJK5aVeSf73ZSXA7dOBXvkXODEb9hVww4KtPNAbPvaZbi0q9kICCl+CiBJSzLva8TlntYlC4UHvCRTlaXOy13VAbN0eae2v3hNesWXLsWPkjfOPq7e6zd1fOfc1TckDaylrvleinnT8Ui87ScLMVhhEx7SUJ8U2zKrRR2Z1dEqZlkr7kDTuhFjpkvse9ZXN0R9H+DlYA4TXVm6/kXDQMyTeGnJFXlLlSgva5iLUEcbiyDzNqf4Wr9kKYVUIcY40DrnsW4E4zW9QxnHVYx+bo64mIskDWjZgCrqeVQFrS7Sh/uFLftIidKWbgj6Oq652d4c3v88Dw2JDK7bSWX/ByuaLZI=”"”
decodeFontFile( courB08_pil, “courB08.pil” )
bits = makeCode( value )
bits = bits.split( // )
code = “”
for digit in self.ean13
code += “%d”%digit
end
require ‘RMagick’
position = 8
# Create a new image
im = Magick::ImageList.new
im.new_image( bits.length + position, height )
# Create a drawer
draw = Magick::Draw.new
# Draw the bar codes
for bit in 0..bits.length
# Draw normal bar
if bits[ bit ] == ‘1′
draw.fill( ‘black’ )
draw.rectangle( bit + position, 0, bit + position, height - 10 )
end
# Draw long bar
if bits[ bit ] == ‘L’
draw.fill( ‘black’ )
draw.rectangle( bit + position, 0, bit + position, height - 3 )
end
end
draw.draw( im )
im.write( “barcode.” + extension )
return bits
end
def decodeFontFile(data, file)
# from zlib import decompress
# from base64 import decodestring
# from os.path import exists
data = Base64.decode64( data )
# If the font file is missing
if not File.exists?( file )
# Write font file
File.open( file, “w+” ).write( data )
end
end
def testWithChecksum()
bar = EanBarCode()
assert(bar.makeCode(’0000000000000′)== ‘L0L0001101000110100011010001101000110100011010L0L0111001011100101110010111001011100101110010L0L’ )
assert(bar.makeCode(’1111111111116′)== ‘L0L0011001001100101100110011001011001101100110L0L0110011011001101100110110011011001101010000L0L’ )
assert(bar.makeCode(’2222222222222′)== ‘L0L0010011001001100110110011011001001100110110L0L0110110011011001101100110110011011001101100L0L’ )
assert(bar.makeCode(’3333333333338′)== ‘L0L0111101011110101000010100001010000101111010L0L0100001010000101000010100001010000101001000L0L’ )
assert(bar.makeCode(’4444444444444′)== ‘L0L0100011001110101000110100011001110100111010L0L0101110010111001011100101110010111001011100L0L’ )
assert(bar.makeCode(’5555555555550′)== ‘L0L0110001011100101110010110001011000101110010L0L0100111010011101001110100111010011101110010L0L’ )
assert(bar.makeCode(’6666666666666′)== ‘L0L0101111000010100001010000101010111101011110L0L0101000010100001010000101000010100001010000L0L’ )
assert(bar.makeCode(’7777777777772′)== ‘L0L0111011001000101110110010001011101100100010L0L0100010010001001000100100010010001001101100L0L’ )
assert(bar.makeCode(’8888888888888′)== ‘L0L0110111000100101101110001001000100101101110L0L0100100010010001001000100100010010001001000L0L’ )
assert(bar.makeCode(’9999999999994′)== ‘L0L0001011001011100101110001011001011100010110L0L0111010011101001110100111010011101001011100L0L’ )
end
def testWithoutChecksum()
bar = EanBarCode()
assert(bar.makeCode(’000000000000′)== ‘L0L0001101000110100011010001101000110100011010L0L0111001011100101110010111001011100101110010L0L’ )
assert(bar.makeCode(’111111111111′)== ‘L0L0011001001100101100110011001011001101100110L0L0110011011001101100110110011011001101010000L0L’ )
assert(bar.makeCode(’222222222222′)== ‘L0L0010011001001100110110011011001001100110110L0L0110110011011001101100110110011011001101100L0L’ )
assert(bar.makeCode(’333333333333′)== ‘L0L0111101011110101000010100001010000101111010L0L0100001010000101000010100001010000101001000L0L’ )
assert(bar.makeCode(’444444444444′)== ‘L0L0100011001110101000110100011001110100111010L0L0101110010111001011100101110010111001011100L0L’ )
assert(bar.makeCode(’555555555555′)== ‘L0L0110001011100101110010110001011000101110010L0L0100111010011101001110100111010011101110010L0L’ )
assert(bar.makeCode(’666666666666′)== ‘L0L0101111000010100001010000101010111101011110L0L0101000010100001010000101000010100001010000L0L’ )
assert(bar.makeCode(’777777777777′)== ‘L0L0111011001000101110110010001011101100100010L0L0100010010001001000100100010010001001101100L0L’ )
assert(bar.makeCode(’888888888888′)== ‘L0L0110111000100101101110001001000100101101110L0L0100100010010001001000100100010010001001000L0L’ )
assert(bar.makeCode(’999999999999′)== ‘L0L0001011001011100101110001011001011100010110L0L0111010011101001110100111010011101001011100L0L’ )
end
end
barcode.rhtml code
<%= image_tag( "/barcode.png" ) %>
Rails - How to find out who is online
Well, this is my first post. I was frustrated in to starting this blog, through searching the internet for solutions to various programming problems, and finding either nothing, or only half the answer. Hopefully I will be able to help others solve some of their problems faster. Recently I have been writing a dating site in ruby on rails. One quite important feature of this site is a list of members who are currently online. After a couple of days of pain, here’s the pretty simple solution to the problem:
First off this only works if you use ActiveRecord to manage your sessions. To do this you must uncomment the following line in your environment.rb file:
config.action_controller.session_store = :active_record_store
Then create the sessions table by running the following command from your RAILS_ROOT:
rake db:sessions:create
Now, whenever a user logs in to your application add their id to the session. In my app at the moment it looks something like this:
member = Member.new( @params[ :member ] )
member = Member.find( :all, :conditions => "screen_name = '#{member.screen_name}' AND password = '#{member.password}'" )
if member != nil
session[ :member_id ] = member.id
redirect_to :action => 'success'
else
redirect_to :action => 'form'
end
This code will need work for a production environment, for one thing it is open to SQL injection attacks.
Now to find all of the users who are currently online it is just a matter of searching the sessions table for all the users who have been active in the last 10 minutes. This can be done using rails built in session model. Finally the session data needs to be unmarshaled and decoded. This stumped me for a while, but finally I found the answer at http://caboo.se/doc/classes/CGI/Session/ActiveRecordStore/SqlBypass.html. So here is my code to return an array of the member_ids of all the members online and active within the last 10 minutes.
def who_is_online
@whos_online = Array.new()
onlines = CGI::Session::ActiveRecordStore::Session.find( :all, :conditions => [ 'updated_at = ?', Time.now() - 10.minutes ] )
onlines.each do |online|
id = Marshal.load( Base64.decode64( online.data ) )
@whos_online << id[ :member_id ]
end
return @whos_online
end