Couchrest Couchdb thumbnail generation with Mini_Magick and Merb (or Rails)

Thumbnails – or avatars – are a standard part of many web apps. Paperclip has made things super easy for active record and datamapper. Here is how I accomplished similar – but basic – thumbnail generation for couchdb. The end solution is RESTful, good for my needs, and hopefully good for your needs too.

You should know that couchdb supports inline attachments and I opted for that route rather than store the images in a file folder like paperclip does. I felt this was more inline with the couchdb way of doing things.

Dependencies

Begin by installing mini_magick

1 sudo gem install mini_magick

Or add mini_magick to dependencies.rb

1 dependency "mini_magick", "1.2.5"

I’m using Merb. The setup for rails would be to use config.gem mini_magick I believe.

Setup the view

Place the avatar uploading mechanism in it’s own form. Use the /users/update_avatar POST action to process the thumbnails.

Setup the users.rb update_avatar POST action and route

The update_avatar action will process the ‘avatar’ image file using the custom ‘update_avatar_images’ method. If you are on rails, you will have to pass params[:avatar] here.

Additionally, the avatar route/action is defined in users.rb also. This will allow us to display the thumbnails restfully via /user/scottmotte/avatar & /user/scottmotte/avatar?style=mini

Furthermore, you will see that my validation is not in the model as it ideally should be. I haven’t figured out how to tackle this yet based on how the couchdb attachments work inline as an array.

Setup the user.rb model to process the thumbnails

Rather than pick out the little pieces of the user.rb model that are related to the update_avatar action in users.rb I’m just posting the entire model.

The key method to examine is the update_avatar_images and it’s cousin resize_and_crop – which resizes the uploaded avatar to a square.

The strange AppConfig array I am using there is from my config/app_config/settings.yml file from merbjedi’s merb_app_config. It looks like this the following but you could just as easily define your own array or list each upload separately.

The put_attachment method is from couchrest and puts the images to the couchdb user document.

Now when a user uploads an avatar image, it creates an inline original.png, thumb.png, and mini.png. And the are accessible via /users/scottmotte/avatar?style=mini

That’s how I did things. Let me know if you have a better way. A gem would be ideal. Maybe I’ll morph this into a gem if I find myself repeating it enough.

Add custom methods to Spreedly gem

I am using the spreedly gem again rather than my own custom Subscriber.rb model. I figured out how I could tack on extra methods to the Spreedly::Subscriber class. You just create a model/class called Spreedly::Subscriber. This is probably rudimentary knowledge for many, but for me it wasn’t. Sometimes it sucks to have a business background. =)

I needed to add the activate_free_trial method for the mocking portion of the spreedly gem. I did it by creating a model named spreedly_subscriber.rb and added the following code.

Using Spreedly with Merb via HTTParty

This is almost completely stolen from the spreedly gem, but I added some custom methods and didn’t want to be a slave to the gem. It allows you to do things like Subscriber.create!(id, email=nil, screen_name=nil) & Subscriber.activate_free_trial. I was using active resource with merb previously but Nathaniel’s spreedly gem convinced me to use HTTParty instead.

To use, create a file in app/models/subscriber.rb and paste the following in. I am using AppConfig for the settings. (sudo gem install merb_app_config).

Here’s some example usage.

Unobtrusive Ajax with Merb, Datamapper, and jQuery

This article describes how to add ajax to your merb & datamapper application using jquery. It uses an example from my own project investapp to create a ‘more’ link similar to twitter’s.

First, install the agnostic branch of will_paginate. This isn’t available as a public gem yet, but auxesis maintains a working fork of will_paginate agnostic. Add the following to dependencies.rb

Use will_paginate just like you do in rails applications. Here I’m paginating the messages. I’m also providing .js to the @display messages. Rails does this with the respond_to block.

Add the will_paginate helper links to your view.

Now for the jQuery. First and foremost make sure you are including jquery in your view.

Setup the ajax using jquery. There is a decent amount going on here.

Firstly, create a getWithAjax jquery method. Thanks goes to Ryan Bates jQuery episode for this. This basically tells merb to use the index.js.erb file instead of the index.html.erb file.

Secondly, inside the document ready is code that takes the default will_paginate helper html code and hides all the Prev, 1,2,3,4, Next links, replaces, them with the text ‘More’ like on twitter, and makes them ajaxifiable via a class=‘more-entries’ attribute.

Thirdly, the $(“div.pagination a[rel=‘next’]”).not(‘.next_page’).show(); makes sure only the next page link is viewable.

Finally, we just have to tell merb what to do once the link is clicked and goes to index.js.erb.

Our messages/index action just got hit and returned us @messages. The first //append the partials section appends the next batch of 15 messages below the previous 15 messages – just how twitter does.

The next line shows the next ‘next_page’ link so we will be able to click next again.

And the final line, hides the link we just clicked.

The end.

Merb and Datamapper Single Table Inheritance

To begin add the Discriminator property to your model. I will be using User as my main model, and there will be an administrator and member model to inherit from it.

# app/models/user.rb
class User
        include DataMapper::Resource
         ..
        property :type, Discriminator
end
rake db:automigrate

Next, add your additional models based that will use the single table inheritance. You could create a separate models/model_name.rb for these, but I just lump them in with models/user.rb

# app/models/user.rb
class User
        include DataMapper::Resource
        ..
        property :type, Discriminator
        ..
end

class Editor < User; end
class Administrator < Editor; end

Now you can run commands like User.all, Administrator. all, and Editor.all. And to create a user with an Administrator role do something like the following.

User.create(:login => 'scottmotte', :password => 'password', :password_confirmation => 'password', :email => 'scott@scottmotte.com', :type => 'Administrator')

Finally, you need to add routes for the Editor and Administrator otherwise resource(user) will give you serious generation errors.

# router.rb
resources :users
resources :administrators, :controller => :users

How to remove sources from your gem list

1 gem sources -r http://example.com

Create a merb slice with jeweler

A slice is just a gem so we can create and manage our slice by starting with jeweler.

Jeweler

gem sources -a http://gems.github.com
sudo gem install technicalpickles-jeweler
git config --global user.email johndoe@example.com
git config --global user.name 'John Doe'
git config --global github.user johndoe
git config --global github.token 55555555555555

jeweler --create-repo --summary "Sandy Koufax Slice" sandy_koufax_slice

Slice

Because jeweler is a bit opinionated and creates a structure for us, create your slice in a tmp directory and then copy the app, config, spec, etc files over to our sand_koufax_slice jeweler structure. Yep, a bit sloppy, and there is a more elegant way, but for now copy and paste worked well for me.

Be careful with the Rakefile. It should look something like this:

Once you’ve got standard files copied over let’s start creating the slice. To do so, we first need to set the slice up to work in development mode (this bit of hackery will eventually go away in Merb 1.2 with embeddable apps. I’m excited for that.).

1. Add the following to config/init.rb

require 'config/dependencies.rb'
 
use_orm :datamapper
use_test :rspec
use_template_engine :erb

2. Create config/dependencies.rb

# dependencies are generated using a strict version, don't forget to edit the dependency versions when upgrading.
merb_gems_version = "1.0.10"
dm_gems_version   = "0.9.10"
do_gems_version   = "0.9.11"

# For more information about each component, please read http://wiki.merbivore.com/faqs/merb_components
dependency "merb-core", merb_gems_version 
dependency "merb-action-args", merb_gems_version
dependency "merb-assets", merb_gems_version  
dependency("merb-cache", merb_gems_version) do
  Merb::Cache.setup do
    register(Merb::Cache::FileStore) unless Merb.cache
  end
end
dependency "merb-helpers", merb_gems_version 
dependency "merb-mailer", merb_gems_version  
dependency "merb-slices", merb_gems_version  
dependency "merb-auth-core", merb_gems_version
dependency "merb-auth-more", merb_gems_version
dependency "merb-auth-slice-password", merb_gems_version
dependency "merb-param-protection", merb_gems_version
dependency "merb-exceptions", merb_gems_version

dependency "data_objects", do_gems_version
dependency "do_mysql", do_gems_version # If using another database, replace this
dependency "dm-core", dm_gems_version         
dependency "dm-aggregates", dm_gems_version   
dependency "dm-migrations", dm_gems_version   
dependency "dm-timestamps", dm_gems_version   
dependency "dm-types", dm_gems_version        
dependency "dm-validations", dm_gems_version  
dependency "dm-serializer", dm_gems_version   

dependency "merb_datamapper", merb_gems_version

3. Bundle the dependencies

This turned out to be a big headache when it came to installing the gem. Just use the gems on your machine and install with sudo gem install merb as necessary.

Bundling our dependencies makes development easier – especially if we want someone else to work on our slice. But the only way to bundle is to get thor in our slice, and it is not there by default like in a merb app. So let’s add it.

To get the thor tasks go into a tmp directory and generate a fresh merb application, then just use copy and paste to move /tasks folder and its contents into your slice. It should look something like this:
- tasks/
doc.thor
merb.thor/
app_script.rb
common.rb
gem_ext.rb
main.thor
ops.rb
utils.rb

thor merb:gem:install

Now you should be able to run bin/slice and bin/rake to run and test your slice.

4. Development and Test Database

Create config/database.yml and put in the following content

development:
  adapter:  sqlite3
  database: sample_development.db
test:
  adapter:  sqlite3
  database: sample_test.db
production:
  adapter:  sqlite3
  database: production.db

Create a model and then rake the database

slice -i
DataMapper.auto_migrate!

In spec_helper.rb add the before(:all) auto_migrate line for datamapper

Spec::Runner.configure do |config|
  config.include(Merb::Test::ViewHelper)
  config.include(Merb::Test::RouteHelper)
  config.include(Merb::Test::ControllerHelper)
  config.include(Merb::Test::SliceHelper)
  
  config.before(:all) do
    DataMapper.auto_migrate! if Merb.orm == :datamapper
  end
end

Then you just have to start building your slice – which is tricky. That’s all I’ve got for now.

How to test for subdomains in Merb

I’ve done a couple subdomain applications now in merb. One of the things that continually bothered me was that I was unable to figure out how to test for subdomains. Thanks to Shalon Wood in the Merb google group, I now am able to do so. I don’t think Rails can even do this.

requests/payment_spec.rb

require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe "Authenticated user logged in", :given => "authenticated user" do
  describe "/payment", :given => 'current_site' do
          before(:each) do
            @response = request("http://#{valid_site_attributes[:subdomain]}.example.org/payment")
          end
    it "should respond successfully" do
      @response.should be_successful
    end
  end
end

spec_helper.rb

def valid_site_attributes(options = {})
  {
    :domain => 'http://www.example.org',
    :folder => 'exampleorg',
    :subdomain => 'example',
    :active => true,
    :id => 1
  }.merge(options)
end
given "current_site" do
  Site.all.destroy!
  @current_site = Site.create(valid_site_attributes)
end

app/controllers/application.rb

def get_site
  # uses @current_site to create pages under appropriate site like @current_site.pages.new
  @current_site = Site.first(:subdomain => request.first_subdomain)
  raise NotFound unless @current_site
end

PDF Generation in Merb using HTMLDOC

A good part of this is thanks to this tutorial.

Installation

# Download HTMLDoc from <a href="http://www.htmldoc.org/software.php?VERSION=1.9.x-r1586&FILE=htmldoc/snapshots/htmldoc-1.9.x-r1586.tar.gz">here</a>
tar zxvf htmldoc-1.9.x-r1586.tar.gz
cd htmldoc-1.9.x-r1586
./configure --prefix=/usr/local
make
sudo make install

Install gem

sudo gem install htmldoc

Configure merb app

# dependencies.rb
dependency "htmldoc", "0.2.1"
# init.rb
Merb::BootLoader.after_app_loads do
        # This will get executed after your app's classes have been loaded.
        Merb.add_mime_type(:pdf, :to_pdf, %w[application/pdf], "Content-Encoding" => "gzip")
end

Create an action for the pdf generation

(there must be a way to just put this in the show action instead of a separate action but i had trouble)

# router.rb
resources :proposals, :collection => { :generate => :get }

# proposals.rb controller
def generate(id)
        only_provides :pdf
        pdf = PDF::HTMLDoc.new
        pdf.set_option :bodycolor, :white
        pdf.set_option :toc, false
        pdf.set_option :portrait, true
        pdf.set_option :links, true
        pdf.set_option :webpage, true
        pdf.set_option :left, '2cm'
        pdf.set_option :right, '2cm'
        pdf.set_option :header, "Header here!"
        pdf << "<h1>Title</h1> <p>This is some <strong>bold</strong> text.</p>"
        pdf.footer ".t."
        send_data pdf.generate
end

Then just put a link_to in your view

<%= link_to "Generate PDF", "/proposals/generate/#{@proposal.id}.pdf" %>

Warning: I was never able to get background images to work. I don’t know if this was a limitation with the htmldoc ruby gem, whether it was something with my htmldoc install, or whether it was a stupid coding mistake on my end.

How to install RMagick on Leopard from source

This is the best tutorial I’ve found for installing rmagick on leopard from source. Installing RMagick on Leopard (without MacPorts or Fink)

 1 #!/bin/sh
 2 wget http://download.savannah.gnu.org/releases/freetype/freetype-2.3.5.tar.gz
 3 tar xzvf freetype-2.3.5.tar.gz
 4 cd freetype-2.3.5
 5 ./configure --prefix=/usr/local
 6 make
 7 sudo make install
 8 cd ..
 9 
10 wget http://superb-west.dl.sourceforge.net/sourceforge/libpng/libpng-1.2.22.tar.bz2
11 tar jxvf libpng-1.2.22.tar.bz2
12 cd libpng-1.2.22
13 ./configure --prefix=/usr/local
14 make
15 sudo make install
16 cd ..
17 
18 wget ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz
19 tar xzvf jpegsrc.v6b.tar.gz
20 cd jpeg-6b
21 ln -s `which glibtool` ./libtool
22 export MACOSX_DEPLOYMENT_TARGET=10.5
23 ./configure --enable-shared --prefix=/usr/local
24 make
25 sudo make install
26 cd ..
27 
28 wget ftp://ftp.remotesensing.org/libtiff/tiff-3.8.2.tar.gz
29 tar xzvf tiff-3.8.2.tar.gz
30 cd tiff-3.8.2
31 ./configure --prefix=/usr/local
32 make
33 sudo make install
34 cd ..
35 
36 wget http://jaist.dl.sourceforge.net/sourceforge/wvware/libwmf-0.2.8.4.tar.gz
37 tar xzvf libwmf-0.2.8.4.tar.gz
38 cd libwmf-0.2.8.4
39 make clean
40 ./configure
41 make
42 sudo make install
43 cd ..
44 
45 wget http://www.littlecms.com/lcms-1.17.tar.gz
46 tar xzvf lcms-1.17.tar.gz
47 cd lcms-1.17
48 make clean
49 ./configure
50 make
51 sudo make install
52 cd ..
53 
54 wget http://ufpr.dl.sourceforge.net/sourceforge/ghostscript/ghostscript-8.60.tar.gz
55 tar zxvf ghostscript-8.60.tar.gz
56 cd ghostscript-8.60/
57 ./configure  --prefix=/usr/local
58 make
59 sudo make install
60 cd ..
61 
62 wget http://ufpr.dl.sourceforge.net/sourceforge/ghostscript/ghostscript-fonts-std-8.11.tar.gz
63 tar zxvf ghostscript-fonts-std-8.11.tar.gz
64 sudo mv fonts /usr/local/share/ghostscript
65 
66 wget http://imagemagick.site2nd.org/imagemagick/ImageMagick-6.3.5-9.tar.gz
67 tar xzvf ImageMagick-6.3.5-9.tar.gz
68 cd ImageMagick-6.3.5
69 export CPPFLAGS=-I/usr/local/include
70 export LDFLAGS=-L/usr/local/lib
71 ./configure --prefix=/usr/local --disable-static --with-modules --without-perl --without-magick-plus-plus --with-quantum-depth=8 --with-gs-font-dir=/usr/local/share/ghostscript/fonts
72 make
73 sudo make install
74 cd ..
75 
76 sudo gem install RMagick