Archive for August, 2008

David Heinemeier Hansson on making money

<div><a href='http://www.omnisio.com'>Share and annotate your videos</a> with Omnisio!</div> <p>

Bill by the hour

After reading Practices of an Agile Developer, I have begun billing only by the hour since about 1 month ago. It was one of the many golden nuggets I gleaned from that book.

Here’s a nice and concise article that basically summarizes why its better to charge at an hourly rate (and not just better for the developer. It’s better for the client too). I’ve already seen much improvement in my clients’ happiness level.

It’s been my experience previously working at Compaq and later starting up our own business six years ago that in a fixed bid situation, either the client gets taken to the cleaners or the developers do. With fixed bid, the requirements of a project have to be so locked down and fine grained that there is no flexibility. If requirements change, costs for the developer can spin out of control. For a developer to make profit, they have to bid the project up really high to cover any unforeseen scope creep. Business as usual (fixed bid) just didn’t make sense to us.

I am stilling giving my clients a general idea on price, but I am not promising them anything. Fixed bids are broken promises - mainly because of damn scope creep, and the fact that what the client wants and what the client comes to expect over time are two different things. It’s imperative to create for the client what he or she has come to expect.

No!Spec letter

This is a useful letter to send to a client who is trying to get spec work out of you.

NO-spec.com

Adjusting contrast on your mac

press and hold ctrl+alt+cmd then tap “,” and “.” to adjust contrast again and again

Named Scopes

Great resource on named scopes

The introduction by Ryan Daigle

Ruby on Rails order by associated model - find.(:order => ”)

1
2
3
4
5
6
7
  def recent_obituaries(limit)
    Obituary.find(:all, :limit => limit, :include => :services, :order => "services.start_time DESC")
  end
 
 
# obituaries has_many :services
# services belongs_to :obituary

Installing Ruby Enterprise on slicehost

I already had Phusion Passenger installed on my slicehost. Now, I wanted to install Ruby Enterprise Edition after hearing how much memory it could possibly save on my slice.

I just followed the instructions here.

cd ~/sources
wget http://rubyforge.org/frs/download.php/41040/ruby-enterprise-1.8.6-20080810.tar.gz
tar xzvf ruby-enterprise-1.8.6-20080810.tar.gz
sudo ./ruby-enterprise-1.8.6-20080810/installer

Then I just hit enter at all the prompts.

At the end I received the following message.

Ruby Enterprise Edition is successfully installed!
If you're using Phusion Passenger (http://www.modrails.com),
and you want it to use Ruby Enterprise Edition, then edit your Apache
configuration file, and change the 'PassengerRuby' option:
 
  PassengerRuby /opt/ruby-enterprise-1.8.6-20080810/bin/ruby
 
If you ever want to uninstall Ruby Enterprise Edition, simply remove this
directory:
 
  /opt/ruby-enterprise-1.8.6-20080810

Change the apache config

cd /etc/apache2
sudo nano apache2.conf
# scroll to the bottom of the page
# change PassengerRuby /usr/bin/ruby1.8 to PassengerRuby /opt/ruby-enterprise-1.8.6-20080810/bin/ruby

Then I did:

sudo /etc/init.d/apache2 restart

Resources:
- Comparison to thin
- Ruby Enterprise Edition & Phusion

Adding categories to your rails blog

So you’ve created your blog (title, body, etc), and now you want to add categories.

Here’s a handy tutorial for how to add categories to your rails blog. This isn’t the way to do it in Rails 2.0.

Instead follow Akita’s tutorial with comments and posts in Rails 2, but adjust for categories.

Uploading multiple images with paperclip

Firstly, I recommend not using the model name in the field name. Instead do file_etc. Also I recommend against using Attachment as your model name. I ran into this problem:

undefined method `quoted_table_name' for Paperclip::Attachment:Class

Also see, Advanced Rails Recipes, recipe #13 for help on this.

Ok let’s go.

Create the model.

1
script/generate scaffold Photo file_file_name:string  file_content_type:string file_file_size:integer

Migrate the database in terminal

rake db:migrate

Add the following to app/model/photo.rb

1
has_attached_file :file, :styles => { :medium => "300x300>", :thumb => "100x100>" }

Aside: This assumes you are setting this up under the admin folder for app administration purposes.

Create admin folder with photos controller inside

script/generate controller admin::photos

(If you get a warning like helpers/photos_helper.rb already exists then just delete that file, and run the generator again.)

Set up controllers/admin/photos_controller.rb to look something like this:

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
class Admin::PhotosController < ApplicationController
  layout 'admin'
  before_filter :login_required
 
  # GET /photos
  # GET /photos.xml
  def index
    @photos = Photo.find(:all)
 
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @photos }
    end
  end
 
  # GET /photos/1
  # GET /photos/1.xml
  def show
    @photo = Photo.find(params[:id])
 
    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @photo }
    end
  end
 
  # GET /photos/new
  # GET /photos/new.xml
  def new
    @photo = Photo.new
 
    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @photo }
    end
  end
 
  # GET /photos/1/edit
  def edit
    @photo = Photo.find(params[:id])
  end
 
  # POST /photos
  # POST /photos.xml
  def create
    @photo = Photo.new(params[:photo])
 
    respond_to do |format|
      if @photo.save
        flash[:notice] = 'Photo was successfully created.'
        format.html { redirect_to(admin_photos_path) }
        format.xml  { render :xml => @photo, :status => :created, :location => @photo }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @photo.errors, :status => :unprocessable_entity }
      end
    end
  end
 
  # PUT /photos/1
  # PUT /photos/1.xml
  def update
    @photo = Photo.find(params[:id])
 
    respond_to do |format|
      if @photo.update_attributes(params[:photo])
        flash[:notice] = 'Photo was successfully updated.'
        format.html { redirect_to(admin_photos_path) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @photo.errors, :status => :unprocessable_entity }
      end
    end
  end
 
  # DELETE /photos/1
  # DELETE /photos/1.xml
  def destroy
    @photo = Photo.find(params[:id])
    @photo.destroy
 
    respond_to do |format|
      format.html { redirect_to(admin_photos_url) }
      format.xml  { head :ok }
    end
  end
end

Create the following view files under views/admin/photos/

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
# _form.html.erb
<%= f.error_messages %>
 
<p>
	<%= f.label :file %><br />
	<%= f.file_field :file %>
</p>
<p>
  <%= f.submit %> or <%= link_to 'Cancel', admin_photos_path %>
</p>
 
# edit.html.erb
<h2>Editing photo</h2>
 
<% form_for([:admin, @photo], :html => { :multipart => true }) do |f| %>
  <%= render :partial => 'form', :locals => { :f => f } %>
<% end %>
 
# index.html.erb
<h2>Photos <small>(<%= link_to 'Create new photo', new_admin_photo_path %>)</small></h2>
 
<table class="styledtable">
  <tr>
    <th>Photo</th>
		<th>Sizes</th>
    <th>Manage</th>
  </tr>
<% for photo in @photos %>
  <tr class="<%= cycle('even','odd') %>">
    <td><%= image_tag(photo.file.url(:thumb)) %></td>
		<td><%= link_to 'thumb', photo.file.url(:thumb) %>, <%= link_to 'medium', photo.file.url(:medium) %>, <%= link_to 'original', photo.file.url(:original) %></td>
    <td><%= link_to 'edit', edit_admin_photo_path(photo) %> | <%= link_to 'delete', [:admin, photo], :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>
 
 
# new.html.erb
<h2>New photo</h2>
 
<% form_for([:admin, @photo], :html => { :multipart => true }) do |f| %>
  <%= render :partial => 'form', :locals => { :f => f } %>
<% end %>
 
# show.html.erb
<p>
  <b>photo file name:</b>
  <%=h @photo.file_file_name %>
</p>
 
<p>
  <b>photo content type:</b>
  <%=h @photo.file_content_type %>
</p>
 
<p>
  <b>photo file size:</b>
  <%=h @photo.file_file_size %>
</p>
 
<%= link_to 'Back', admin_photos_path %>

Edit your config/routes.rb file so that photos can route to apps/controllers/admin/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  map.resources :photos
  map.resources :categories
  map.resources :manufacturers
  map.resources :users
  map.resource :session
  map.resources :line_items
  map.resources :orders
  map.resources :products
 
  map.namespace :admin do |admin|
    admin.resources :products
    admin.resources :orders
    admin.resources :manufacturers
    admin.resources :categories
    admin.resources :users
    admin.resource :session
    admin.resources :photos
  end

Add paperclip’s plugin functionality to the model photo.rb

1
2
3
class Photo < ActiveRecord::Base
	has_attached_file :file, :styles => { :medium => "300x300>", :thumb => "100x100>" }
end

Restart your server and test that you can add photos by going to http://localhost:3000/admin/photos/new

Did that work. Good. Now we can finally, move onto adding multiple images to another model. I am going to use the product.rb model and products_controller.rb to add these multiple images.

Add the belongs_to and has_many to the models.

1
2
# app/models/product.rb
has_many :photos
1
2
3
4
5
# app/models/photo.rb
  belongs_to :product
 
# paperclip
has_attached_file :file, :styles => { :medium => "300x300>", :thumb => "100x100>" }

Add references to the photos model

script/generate migration add_product_references_to_photos

And populate that file like so

1
2
3
4
5
6
7
8
9
10
11
12
13
class AddProductReferencesToPhotos < ActiveRecord::Migration
  def self.up
    change_table :photos do |t|
      t.references :product
    end
  end
 
  def self.down
    change_table :photos do |t|
      t.remove_references :product
    end
  end
end
rake db:migrate

Then I am going to follow Railscast Episode 73: complex forms to add multiple images.
————————
UPDATE:
Use advanced Rails Recipes # 13

DON’T FORGET THE MULTIPART FOR UPLOADING IN YOUR FORM!!! Recipe #13 fails to mention this since it’s not dealing with images.

1
2
3
<% form_for([:admin, @product], :html => { :multipart => true }) do |f| %>
	<%= render :partial => 'form', :locals => { :f => f } %>
<% end %>

————————

Add photos.build to new action of apps/controllers/admin/products_controller.rb

1
2
3
4
5
6
7
8
9
10
# admin/products_controller.rb
def new
    @product = Product.new
    3.times { @product.photos.build }
 
    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @product }
    end
  end

Create _photo.html.erb partial under /app/views/admin/products/

1
2
3
4
5
6
7
# _photo.html.erb
<% fields_for "product[photo_attributes][]", photo do |photo_form| %>
	<p>
		<%= photo_form.label :file %><br />
		<%= photo_form.file_field :file %>
	</p>
<% end %>

Add collection partial to /app/views/admin/products/_form.html.erb

1
	<%= render :partial => 'photo', :collection => @product.photos %>

And that’s it. That will work.

Though there are still some unresolved issues. Take a look at Ryan’s Railscasts (except for episode 75. See Advanced Rails Recipe #13 instead.) on complex forms to add ajax, and also make sure you add dependent destroy to your model. Otherwise, when you delete a product the photos won’t be deleted.

How to install ImageMagick on Tiger

Just install from source.

I needed this in order to use paperclip the rails plugin.