App Store Pricing

Kicked off by a great series written by Michael Jurewitz (links below) a new discussion about pricing on the App Store has been spawned. Here’s my take on it, along with a few experiences.

TL;DR If you have an app on the App Store, do yourself and your fellow devs a favor, pause reading now, go to iTunes Connect and raise the price of your app(s) to what you honestly feel you deserve.

I assume there is room for improvement and that you can raise the price, because when you initially set the price you most likely fell into the trap of not being couragous enough to charge what it might be worth. Go for it – in case you are hesitant, just make it an experiment for the next two weeks. I did the same half a year ago, and I’m writing this to encourage you. Here are my findings…

A little bit of background…

All the experiences I’m talking about are based on what happened to iOctocat - GitHub for iOS within the last six months. Up to then it’s been a part time project of mine, which started four years ago and got developed further whenever I found the time and energy in my spare time. When the iPhone 5 came out I made the decision to either put it aside or develop it on a more serious basis, because the state back then was unsatisfying for both parties, customers and me: Important features were lacking and bugs took a long time to get fixed, because when you are a father of two, you got some more important things to do on your evenings and weekends.

Up to then the price was $4 (which was already high compared to the race-to-the-bottom prices we see all over the App Store) and I the income I made from that was far from covering the costs of development and support - and to be honest, somewhere along the way the cost of development neared zero, because I was just lacking the motivation to put more time into it.

So I decided to raise the price to around $10 (I experimented with variations over a couple of weeks) and see what happens: In case it takes off and I can make at least some more money off of it, I’ll start investing my time again - if not, I’ll let it sink, because there does not seem to be a market that allows sustainable development of that kind of thing anyway.

I personally did not really expect what happend, but having read so far, you might already guess the direction it went in…

More income

The amount of units sold stayed nearly the same, at first they dropped off a little – but that does not matter, because even if you double your price and only sell half the amount, you are winning: At least in theory there should be less costs for support and you validated that there still is a market for that kind of product in a higher price range. Of course this depends on the actual units sold, but you get the idea…

Then, after a while the sales started to increase. Not like they went through the roof, but the numbers improved. Don’t get me wrong: I’m not talking about wanting to become rich from the app (and even if I would, I’d be still far from it) - my point is that I wanted to find a price point that allows to develop/run/maintain the app on a sustainable basis, which you just cannot do for 89 cents as an independent developer.

iOctocat - just like most other apps - is an app with a one time fee: You pay it once and get all the updates for free. I thought about introducing other models while working on support for push notifications (that just landed btw), but I decided to keep everything as it is, because at that price point I can offer the additional services without charging an extra amount for push, which would have felt somewhat strange anyways.

Now I cover the costs by charging what I think the app is worth, which gives me the chance to also reinvest the money for things that will improve the customers experience, like services as HockeyApp, development apps like Deploymate or even giving back to people who contribute on GitHub - win win.

More loyal users

Besides the increased value on my side, it also feels like the customers value it more. Of course the perceived value automatically increases when you pay more for something, but first and foremost I am talking about that the customers tend to feel more involved in the app. I don’t think that iOctocat being open source makes it a special case, but now I even get sales and reviews that are like “Wanted to support the project, cheers!” (actual review from the App Store).

The feedback and reviews I get really improved when I increased the price. One factor for that seems to be that the higher price sieves out the people that spontaneously bought it and then were annoyed, that it does not also offer a code editor and the whole file management via Git.

Instead, customers seem to take the time to see whether or not it fits their needs and reach out to me via Twitter or ADN to ask if it supports what they expect. This way I have more contact to (potential) customers and can directly react to feedback and questions. I could go into a seperate discussion about how the current state of the App Store not allowing to answer reviews or offering trials does otherwise make this a problem in the first place, but that’s another topic.

The customers also feel like they have a say - and of course they do. I try to respond as best as I can to feature requests, issues and feedback I get, which brings me to my last point…

More motivation

Now that I can at least justify putting my spare time into the app, I really enjoy it again. I guess a look at the contributions chart on GitHub says it best…

iOctocat GitHub Contributions

There is a lot more coming and I’m really looking forward to dedicate at least one month of full time work to iOctocat this summer. The list of things I want to add and improve is still long - and hacking on the project is fun again.

So were do we go from here?

Well, we cannot sit and wait for Apple (or even expect them) to fix the pricing on the App Store. That ship has sailed and somehow we developers need to educate users and customers that running an app needs a sustainable basis that just cannot be achieved by charging 89 cents. And it already has begun, see the list of selected posts on that topic below, that were prior to this one.

At least from my experience people like to hand you their money if you make their life easier or more fun. This makes for a great conclusion too: Please don’t choose to compete on price, make quality and dedication to your product the main differentiator - your customers and fellow devs will thank you.

Further reading

UITableViewCell height with UITextView

You will find lots of resources on the internet about calculating the height of a UITableViewCell based on an inner UITextView. Most of what I’ve found when I was trying to find a solution was either incomplete or wrong. Here are my learnings and the approach I’m taking to tame what gave me headaches for some time now.

Prerequisites

I wanted this to work for both table view styles within universal apps that support autorotation. The solution should also be flexible enough to allow for subclassing and configuration of the custom table view cell so that I can reuse the code for different kinds of cells.

UITableViewCell

To calculate the height of the cell (most likely to be used in tableView: heightForRowAtIndexPath:) you have to know its width so that you can calculate the size of the UITextView with the method sizeWithFont:forWidth: lineBreakMode: provided by the NSString class. That is because the naive approach of adding the cell to the table view and asking for the frame size of the text view does not work - tableView:heightForRowAtIndexPath: gets called before tableView:cellForRowAtIndexPath: so that you have to do the calculation on your own.

UITextView

First of all we have to notice that an UITextView has an inset of 8px on each side. I have not found official documetation about this and event though its contentInset property is set to UIEdgeInsetZero by default, it has this 16px horizontal padding we have to subtract when calculating with the text views width.

Note: On StackOverflow and elsewhere you can find remarks about reduzing the padding by applying a custom inset with negative values, like this:

textView.contentInset = UIEdgeInsetsMake(-8,-8,0,0);

This indeed unsets the visual padding, but from what I have seen and tested it does not make the text run wider - you still have to subtract the 16px. With this example the text just seems to start 8px more to the left, but still wraps at the original width.

The approach

There is one thing I don’t really like about the solution I’ve found and that is passing in the table view to the method - (CGFloat)heightForTableView:(UITableView *)tableView that does the calculation. However I did not find a way to avoid this, because we need the width of the cells outer frame, because it will give us a way to get to the cells width - this way it works with autorotation and cell reuse.

The code for the custom UITableViewCell subclass (TextCell) looks like this:

@implementation TextCell

// UITextView has an inset of 8px on each side
- (CGFloat)textInset {
    return 8.0f;
}

// Sets the text and adjusts the frame height
// so that the text view does not scroll
- (void)setContentText:(NSString *)theText {
    self.textView.text = theText;
    CGRect frame = self.textView.frame;
    CGFloat cHeight = self.textView.contentSize.height;
    frame.size.height = cHeight + self.textInset;
    self.textView.frame = frame;
}

// Calculates the text views width by subtracting
// the horizontal insets and margins
- (CGFloat)textWidthForOuterWidth:(CGFloat)outerWidth {
    CGFloat textInset = self.textInset * 2;
    CGFloat marginH = self.marginLeft + self.marginRight;
    CGFloat width = outerWidth - marginH;
    CGFloat textWidth = width - textInset;
    return textWidth;
}

- (CGFloat)heightForTableView:(UITableView *)tableView {
    // calculate the outer width of the cell
    // based on the tableView style
    CGFloat outerWidth = tableView.frame.size.width;
    if (tableView.style == UITableViewStyleGrouped) {
        // the grouped table inset is 20px on
        // the iPhone and 90px on the iPad
        BOOL iPad = [UIDevice currentDevice].userInterfaceIdiom ==
          UIUserInterfaceIdiomPhone;
        outerWidth -= iPad ? 20.0f : 90.0f;
    }
    CGFloat textInset = self.textInset * 2;
    CGFloat marginV = self.marginTop + self.marginBottom;
    CGFloat textWidth = [self textWidthForOuterWidth:outerWidth];
    // use large value to avoid scrolling
    CGFloat maxHeight = 50000.0f;
    CGSize constraint = CGSizeMake(textWidth, maxHeight);
    CGSize size = [self.textView.text
        sizeWithFont:self.textView.font
        constrainedToSize:constraint
        lineBreakMode:UILineBreakModeWordWrap];
    CGFloat height = size.height + textInset + marginV;
    return height;
}

@end

You may have noticed, that the code above refers to getters for the margin. I use these to configure the coordinates that text views in different kinds of TextCell subclasses may have.

Cellconfiguration in Interface Builder

For example I would use these settings in a concrete subclass when the UITextView in Interface Builder is set like in the screenshot above.

- (CGFloat)marginTop {
    return 65.0f;
}

- (CGFloat)marginBottom {
    return 40.0f;
}

- (CGFloat)marginLeft {
    return 1.0f;
}

- (CGFloat)marginRight {
    return 1.0f;
}

The code and examples for this are taken from the iOctocat codebase.

Rails-Hosting auf Uberspace

Uberspace - Hosting on Asteroids Ich bin großer Fan der einladenden Atmosphäre, Einfachheit und dem großartigen Support auf Uberspace. Konsequenterweise hab ich nun auch meine letzten privaten Projekte dorthin umgezogen und mich dabei auch mit dem Rails-Deployment auf Uberspace auseinander gesetzt.

Für die ersten Schritte findet man im Wiki eine Anleitung mit allen generellen Einzelheiten zum Aufsetzen einer Rails-Anwendung auf Uberspace. Der dort beschriebene Ansatz setzt der Einfachheit halber auf das manuelle Ausführen der einzelnen Schritte und das Ausliefern der Anwendung über FastCGI. Da das nicht unbedingt etwas ist, was man aus dem täglichen Rails-Kontext kennt, hier ein paar Hinweise und Learnings zur Automatisierung des Deployments und zur Verwendung alternativer Applikationsserver.

Automatisierung des Deployments

Wer seine Anwendungen mit Capistrano deployed, sollte einen Blick auf uberspacify werfen, ein Gem das alle nötigen Capistrano-Scripte mitbringt, um den eigenen Uberspace startklar für das Rails-Hosting zu machen. Als Fan von Vlad habe ich mich von uberspacify inspirieren lassen und die nötigen Uberspace-Tasks für Vlad geschrieben. Das Script enthält die wichtigsten Anmerkungen und Konfigurationshinweise, hier noch ein paar generelle Dinge:

Die von Vlad/Capistrano benutzte Shell ist nicht die interaktive Shell, die man selbst beim Login über SSH bekommt. Um sicherzustellen, dass auch diese Tools die gleiche Konfiguration (bspw. den Pfad zu einer angepassten Ruby-Version) haben, sollten die Anweisungen dafür in die .bashrc statt in die .bash_profile geschrieben werden.

Die Anwendung selbst muss unterhalb /var/www/virtual/username/ liegen, damit der Apache darauf zugreifen kann. Im einfachsten Fall würde man einfach in das html-Verzeichnis dort deployen, was aber mit der von den Deployment-Tools generierten Struktur (current, releases, shared) ungünstig ist, da diese dann im DocumentRoot des Webservers liegen. Ich mache es daher so, die Anwendung in /var/www/virtual/username/rails/myapp zu deployen und einen Symlink mit dem Hostnamen der Domain auf das darin liegende current/public zu setzen. Damit funktioniert dann auch der Zugriff aus die Assets der Anwendung problemlos, ohne weitere Einstellungen in der .htaccess machen zu müssen. Im Zusammenhang mit den so verwendeten Domains sollte man den Hinweis zum angepassten DocumentRoot beachten.

Alternative Applikationsserver

Statt FastCGI benutze ich Thin, generell treffen die nachfolgenden Dinge aber auch zu, sollte man sich für die Verwendung von Unicorn, Puma, Mongrel oder Passenger Standalone entscheiden. Wichtig ist eigentlich nur, dass man den Applikationsserver auf hohen Port laufen lässt und ihn mittels Daemontools überwacht, so dass er auch unabhängig von Webserver-Restarts persistent läuft.

Das run-Script dafür, sieht dann beispielsweise folgendermaßen aus:

#!/bin/bash
export HOME=/home/username
source $HOME/.bash_profile
cd /var/www/virtual/username/rails/myapp/current
exec /home/username/.gem/ruby/1.9.1/bin/bundle exec thin start -p 43862 -e production 2>&1

Zu guter letzt benötigt man unter current/public noch eine .htaccess-Datei, die eine RewriteRule enthält, um die Requests an den Applikationsserver-Proxy weiterzuleiten. Im einfachsten Fall reicht folgendes:

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ http://localhost:43862/$1 [P]

Darüber hinaus gibt es hier auch noch eine Variante der .htaccess mit Asset-Caching und Wartungsmodus.

Sowohl uberspacify als auch das Vlad-Script kümmern sich darum diese Dateien entsprechend der gegebenen Konfiguration zu generieren. Sollte jemand das Vlad-Script ausprobieren oder Anmerkungen zu den hier gegebenen Tipps haben freue ich mich natürlich über Feedback dazu :)

Bitte beachten!

Uberspace hat ein SSH-Connection Limit, was auch gut so ist. Vlad wiederum öffnet für jedes Kommando eine neue SSH-Verbindung, so dass man schnell mit dem Connection Limit kollidiert. Daher ist es sinnvoll, SSH-Multiplexing zu aktivieren, was man mit folgenden Zeilen in der ~/.ssh/config tun kann:

Host *
  ControlMaster auto
  ControlPath /tmp/ssh-master-%r@%h:%p
  ControlPersist 15m

Dies führt dazu, dass bei jedem weiteren Befehl die bereits bestehende SSH-Verbindung genutzt wird. Man hat somit auch gleich ein schnelleres Deployment, daher wird auch in der Vlad FAQ generell dazu geraten, SSH-Multiplexing zu verwenden.

Ploppcasts

Ploppcasts Zusammen mit den Hamburger Kollegen Daniel Harrington, Jan Krutisch und Peter Schröder sitze ich nun ein bis zweiwöchentlich Donnerstag abends virtuell beisammen, um Ploppcasts aufzunehmen: Unser Beitrag zu deutschsprachigem Podcasting mit Themenschwerpunkt rund um alles was mit Ruby, Rails, Webentwicklung und Freelancertum zu tun hat. Zu finden ist das Resultat dann in iTunes, bzw. lässt sich auch mit jeden anderen Feedreader abonnieren. Über Feedback und Bewertungen in iTunes freuen wir uns natürlich riesig!

Setup

Da ich auf der Suche nach einem geeigneten Setup zur Aufnahme bis auf einen Post auf MacWorld nur wenig gefunden habe, hier auch noch ein paar Worte zu unserem Setup: Wir treffen uns in Skype zu einem Gruppengespräch und nehmen mit dem CallRecorder die jeweiligen Tonspuren auf. CallRecorder beinhaltet mehrere Anwendungen, unter anderem auch eine, um die einzelnen Aufnahmen in zwei Tonspuren aufzuteilen: Die eigene und die der anderen Gesprächsteilnehmer.

Die Einzelspuren werden dann in GarageBand übernommen und mit einer der Gesamtspuren abgeglichen, so dass alle Teilnehmerspuren am Ende synchron sind. In GarageBand lässt sich auf der Rest eines Podcasts ganz gut produzieren, wie beispielsweise Bilder und Shownotes einpflegen. Wer es richtig rund mag, könnte auch noch endlos abgehen und Versprecher etc. rausschneiden, das ist uns aber ehrlich gesagt zu viel Aufwand und wir hoffen, dass wir uns mit der Zeit was solche Dinge angeht einfach genügend verbessern, um die Ähs und Öhs zu reduzieren ;)

Generell ist es ratsam, die Aufnahme mit einem vernünftigen externen Mikrofon zu machen, wie beispielsweise dem Go Mic von Samson. Das interne MacBook Mikrofon geht zwar auch, ist aber eben auch anfälliger für Störgeräusche die durch’s nebenbei tippen oder das Rauschen der Festplatte entstehen. Absolut wichtig ist es, dass jede der aufnehmenden Parteien Kopfhörer auf haben, ansonsten nimmt die eigene Spur auch das mit auf, was man selbst von den anderen empfängt.

Wer sich auskennt, darf uns gerne wissen lassen, wie wir die Aufnahme darüber hinaus noch verbessern könnten!

Danke

Vielen Dank an dieser Stelle auch an SoundCloud, die uns auf ihrer wunderbaren Plattform beherbergen und uns den Speicherplatz sponsorn. Außerdem geht ein dickes Dankeschön an Benjamin Rabe, der uns das schnieke Logo gefingermalt hat. Wer uns folge möchte, findet und natürlich auch auf Twitter und Facebook.

Deployment with Vlad

When I ask people about how they deploy their Rails apps, most of them answer that they are using good old Capistrano. It is the most popular solution because it has been around since almost when people started deploying Rails applications and it works well in most cases. However, if you want to do a lot of custom tasks and need to look under the hood to extend it, Capistrano seems somewhat complicated. At least it did at the time I left it in favor of Vlad - an imho more intuitive deployment tool.

Though one might not like the offense against Capistrano that was shown when Vlad was released in 2007, it addresses some valid points when arguing against needless complexity. But enough of the history, this was almost five years ago, both of the tools have improved, made their way and all I wanted to share is some snippets and insights I got from using Vlad over the last years.

The main thing I like abot Vlad is that it is based on Rake, which makes it so easy to learn, understand and extend. If you want to do something custom, all you have to do is to write a Rake task. And as you will want to use them across different projects, you can even bundle them up and distribute them as a gem, like I’ve done with vlad-extras, a set of extensions for tools like DelayedJob, ThinkingSphinx, Monit, Whenever and so on.

Here is what a simple config/deploy.rb file might look like:

# Custom variables used in the following config
set :application, "appname"

# Required variables
set :domain, "#{application}.com"
set :deploy_to, "/var/www/#{application}"
set :repository, "git@github.com:myaccount/#{application}.git"

# Optional variables
set :user, "deploy" # if different from your current login
set :bundle_cmd, "/usr/local/bin/bundle"
set :rake_cmd, "#{bundle_cmd} exec rake"
set :revision, "origin/master"

# vlad-extras config
set :copy_shared, {
  'config/maintenance.html' => 'config/maintenance.html',
  'config/database.yml'     => 'config/database.yml' }
set :symlinks, {
  'assets'              => 'public/assets',
  'config/database.yml' => 'config/database.yml' }
set :deploy_tasks, %w(
  vlad:maintenance:on
  vlad:update
  vlad:symlink
  vlad:bundle:install
  vlad:assets:precompile
  vlad:migrate
  vlad:start_app
  vlad:maintenance:off
  vlad:cleanup)

# Bundler ships with vlad integration you'll have to require
# in order to use the vlad:bundle tasks
require 'bundler/vlad'

# Require custom vlad tasks or recipes from vlad-extras
require 'vlad/maintenance'

# Some custom tasks, can be included directly in the deploy file
namespace :vlad do
  namespace :custom_script do
    %w(start stop restart).each do |task|
      desc "#{task.capitalize} the custom script"
      remote_task task, :roles => :app do
        run "cd #{current_release}; RAILS_ENV=#{rails_env}
          #{bundle_cmd} exec ruby script/custom #{task}"
      end
    end
  end
end

Here is an example of how easy it is for instance to add staging support:

task :staging do
  set :rails_env, "staging"
  set :deploy_to, "/var/www/#{application}-#{rails_env}"
end

task :production do
  set :rails_env, "production"
  set :deploy_to, "/var/www/#{application}-#{rails_env}"
end

All you need to do is to change some variables based on the environment that is set with a simple Rake task. You’d invoke it by running

$ bundle exec rake staging vlad:deploy

Gemfile

You can include Vlad and it’s requirements in the development group, because it you do not need it in any other environment. Requiring is done in the Rakefile like shown below.

group :development do
  # Deployment
  gem 'vlad', :require => false
  gem 'vlad-git', :require => false
  gem 'vlad-extras', :require => false
end

Rakefile

Add this snippet to your Rakefile, just before MyApp::Application.load_tasks

if Rails.env.development?
  begin
    require 'vlad'
    require 'vlad-extras'
    Vlad.load(scm: :git, web: :nginx, app: :passenger, type: :rails)
  rescue LoadError
    puts 'Could not load Vlad'
  end
end

You might have to configure the options given to Vlad.load.

If you want to get started, take a look at the documentation to find out about the parts that this post leaves out.

Troubleshooting

This part is about some trouble I’ve had and it is not really Vlad specific. You might encounter these problems when trying to deploy to your production server and maybe these tips and links will help.

The deployment shell environment

If Vlad does not find some commands, try the following:

To enable per user PATH environments for ssh logins you need to add to your sshd_config:

PermitUserEnvironment yes

After that, restart sshd!

Then in your users ssh home directory (~/.ssh/environment), add something to this effect (your mileage will vary):

PATH=/opt/ruby-1.9.3/bin:/usr/local/bin:/bin:/usr/bin

For details on that, see this article on setting the deployment shell environment

SSH Agent Forwarding

Maybe you also need to configure SSH Agent Forwarding:

$ ssh-add ~/.ssh/<private_keyname>

Edit your ~/.ssh/config file and add something like this:

Host <name>
  HostName <ip or host>
  User <username>
  IdentityFile ~/.ssh/<filename>
  ForwardAgent yes

For details on that see this article on SSH Agent Forwarding

Seeds for different environments

Here’s a little Rails tip for splitting up seed data for various environments: Create the folder db/seeds/. For each environment you want to seed put a file in there named after the env. Your general seeds are put into db/seeds/all.rb. Here’s what it might look like:

|___seeds
| |___all.rb
| |___development.rb
| |___staging.rb
| |___production.rb
|___seeds.rb

Change the content of your db/seeds.rb to something like this:

['all', Rails.env].each do |seed|
  seed_file = "#{Rails.root}/db/seeds/#{seed}.rb"
  if File.exists?(seed_file)
    puts "*** Loading #{seed} seed data"
    require seed_file
  end
end

Running rake db:seed will now load up the seed data defined in db/seeds/all.rb and the current environment.

Configuring Nginx for the Asset Pipeline

Following up on the series of posts about the Rails Asset Pipeline, here are some things to keep in mind when you are using Nginx as your webserver.

Installation via Passenger

When you are installing Nginx automatically with the script that comes with Passenger, you will not be able to serve the gzipped file variants that get precompiled. That’s because Passenger currently does not configure Nginx with the http_gzip_static_module that would be necessary for including the directive gzip_static on. I’ve opened a pull request for that, but for now you’ll have to choose the manual installation, if that’s what you are after - and why wouldn’t you?

# download and untar nginx
wget -O /tmp/nginx.tar.gz http://www.nginx.org/download/nginx-1.0.10.tar.gz
tar xzvf /tmp/nginx.tar.gz
rm /tmp/nginx.tar.gz

# install nginx via passenger install script, already includes
# --with-http_ssl_module
# --add-module for passenger
passenger-install-nginx-module --auto --prefix='/opt/nginx' --nginx-source-dir='/tmp/nginx-1.0.10' --extra-configure-flags="--with-http_gzip_static_module"

You might want to change the version and prefix to fit your needs. Like mentioned in the comments you don’t have to take care of including the passenger or ssl module, because the installation script adds them automatically.

Nginx configuration

This is already part of the Asset Pipeline Guide, but I’ll mention it here again, so that this article is complete - here are the directives for the assets location config:

location ~ ^/(assets)/  {
  root /path/to/site;
  gzip_static on;
  expires max;
  add_header Cache-Control public;
  # access_log /dev/null;
}

I’ve also disabled access logging, so that asset requests do not clutter up the log.

Mathias brought up a good point, why one might not want to do that.

Sendfile Header

In your app’s production.rb you should uncomment to following line, to let Nginx send static files:

config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'

Verify that it’s working

After deploying the changes, you can check if Nginx is now serving the gzipped compressed versions of your asset files. For that you can use tools like this HTTP Compression Tester and enter the address of your application.js and application.css

Referencing Rails Assets in CoffeeScript

Sometimes you’ll want to refer to your image assets from inside of your CoffeeScript (or JavaScript). While we have nice helpers for that in SASS (namely image-path and image-url), we would have to resort to appending .erb to every .js or .coffee file, we want to reference images in, so that we could use something like this in there:

icon = <%= image_path('icon.png') %>

I didn’t like it that way, because ERB inside of CoffeeScript looks odd and having the file end with .erb messes up syntax highlighting. But we can circumvent this easily by adding this (admittedly not the most beautiful) piece of code to one single file that ends on .erb (i.e. images.js.coffee.erb) and let it provide the helper method we need:

<%
imgs = {}
Dir.chdir("#{Rails.root}/app/assets/images/") do
  imgs = Dir["**"].inject({}) {|h,f| h.merge! f => image_path(f)}
end
%>

window.image_path = (name) ->
  <%= imgs.to_json %>[name]

This adds up all images inside of app/assets/images and makes them available via the global helper method image_path - your milage may vary, but you get the idea. From there on it’s simply

icon = image_path('icon.png')

inside of your CoffeeScript (or JavaScript) files and it also works in production as the returned filename contains the digest.

OpenData Bremen

Im Rahmen des OpenData-Wettbewerbs Apps4Deutschland fand in Bremen am Freitag, den 11. November 2011 die Auftaktveranstaltung zu Apps4Bremen statt. Nach dem Vorbild Berlin macht nun auch das Land Bremen Teile der Daten staatlicher Stellen öffentlich und läd dazu ein, diese zu verwerten und sinnvoll aufzubereiten - auf der Apps4Deutschland-Website heißt es dazu:

Behörden in Deutschland erheben, speichern und verarbeiten viele interessante Daten. Dazu gehören Wetterdaten ebenso wie Informationen über die Luftqualität oder über die Verwendung der Steuergelder. Diese Daten sind von öffentlichem Interesse und bergen ein großes Potenzial für unsere Gesellschaft, weil sie Transparenz, Beteiligung, Innovationen und neue Dienstleistungen fördern.

Mit dem Wettbewerb „Apps für Deutschland“ laden wir Designer, Entwickler, Journalisten, Forscher und die breite Öffentlichkeit ein, Anwendungen zu schaffen, um diese Daten nutzbar zu machen.

Da ich das Thema Open Government spannend finde und für sehr wichtig halte, war der Wettberwerb ein guter Anlass, um mir eine neue Spielwiese für die offenen Daten Bremens zu schaffen. Das Projekt ist mittlerweile nicht mehr online, lässt sich aber auf dem bisherigen Stand gut erweitern, da es bereits Beispiele für Datenimporter, Geokodierung und Darstellung von Orten auf einer Google Map enthält. Basis dafür sind Ruby on Rails und Backbone.js und der Quelltext ist auf GitHub verfügbar, so dass das Projekt gerne erweitert werden kann. Gerne lasse ich mich auch auf Ideen, Verbesserungswünsche oder Fehler hinweisen.

Wer sich inspirieren lassen möchte, kann auch mal einen Blick in den OpenData Showroom werfen, der Projekte aus aller Welt enthält, die sich offener Daten bedienen. Würde mich freuen, wenn sich viele weitere Leute finden, die mit den Daten was nettes auf die Beine stellen.

Precompiling Rails Assets for Development

First off: I love Rails’ asset pipeline from the bottom of my heart. It’s a great thing and makes frontend development such a lovely task. There are two little drawbacks though when using it in development: It slows down page reloads and clutters up the log output. Here I’ll show you how to get rid of that.

When you are mainly doing backend development or most of your apps frontend code doesn’t change too much do yourself a favor and precompile your assets for the development environment:

$ RAILS_ENV=development bundle exec rake assets:precompile

This really speeds up page reloads as Rails does not need to look up and precompile the asset files every time a request hits the app anymore.

Another change you might want to make is to set config.assets.debug to false in your development environment. This makes Sprocket concatenate all your files (users.css, search.css, …) into one javascript and stylesheet file (application.css):

config.assets.debug = false

This also cleans up the noisy logging info concerning the asset files that were previously loaded one by one.

When you need to precompile additional assets that aren’t already referenced in you application.js or application.css you’ll want to move the config.assets.precompile directive from production.rb to application.rb:

config.assets.precompile += %w( search.js )

This isn’t directly related but while you are at speeding up our development experience you might also want to have a look at the rails-dev-tweaks gem. This gem defines rules for managing what files get reloaded on page requests in development.

Setting up Ubuntu with Teleport

Some time ago I came across Teleport which pitches itself as a lightweight way to set up Ubuntu machines. Having failed at mastering the steep learning curve that full-blown tools like Chef or Puppet provide, I was willing to give Teleport a shot. Teleport stays simple by focusing on Ubuntu and providing just the right set of tools to set up the whole Ruby/Rails eco system. This makes it less powerful than the other aforemention tools, but in my case it’s just what I was looking for.

Some facts: Teleport …

  • consists of a configuration (Telfile) plus files you want to be present on the target
  • copies itself onto the target via ssh and then runs itself there (hence the name)
  • works idempotently - run it over and over again without breaking the target

My setup

For each project I store everything that belongs to Teleport in a specific deployment folder. This folder might look something like this:

|____Telfile
|____recipes
| |____install_passenger_nginx.rb
|____files
| |____etc
| | |____gemrc
| | |____init.d
| | | |____nginx
| |____Gemfile
| |____Gemfile.lock
| |____opt
| | |____nginx
| | | |____conf
| | | | |____nginx.conf

The configuration is done inside the Telfile:

user "deploy"
ruby "1.9.3"
server "my-own-vps"

apt "deb http://ppa.launchpad.net/chris-lea/node.js/ubuntu lucid main", key: "C7917B12"

packages %w(openssl libreadline5 libreadline5-dev curl
  git-core libopenssl-ruby libcurl4-openssl-dev imagemagick
  libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev
  libmysqlclient-dev mysql-client mysql-server nodejs)

recipes %w(
  install_passenger_nginx.rb)

user defines the name of the account you will later use to deploy your app. This user gets added to the sudoers and your ssh key is uploaded for password-less login. ruby tells Teleport which version to install and server is the name you gave to the machine in your ~/.ssh/config.

As you can see you can also define customs apts and packages that should get installed. For performing special setup routines or invoking more complex tasks you can utilize the recipes feature. Recipes are standalone files that can be written in Ruby and allow for sharing and reuse. Here’s an example of how to install nginx via the passenger gem:

version_gem = gem_version("passenger")
version_installed = `passenger-config --version`.strip

if !version_gem
  warn "Passenger is not installed. Please add it to your Gemfile."
elsif version_installed == version_gem.to_s
  banner "Passenger #{version_installed} is already installed"
else
  banner "Installing Nginx via Passenger (installed: #{version_installed}, gem version: #{version_gem})"
  run "passenger-install-nginx-module", %w(--auto --auto-download --prefix=/opt/nginx)  # install without prompting
  run "update-rc.d nginx defaults"                                                      # activate autostart
  run "service nginx restart"                                                           # restart nginx
end

The files directory contains files and directories that get copied over to the target machine - not much magic about that. To install system-wide gems you can place a Gemfile and Gemfile.lock inside the files directory.

Finally you can run the setup with

$ teleport my-own-vps

Keep in mind that you can re-run this every time you make a change to the Telfile or add new files. Teleport strives to be idempotent and can be run repeatedly without breaking the target machine(unless you explicitely messed up something ;)).

Let’s go…

There is much more to Teleport than what I could cover in this short post. For example you can define multiple servers with different roles or reverse engineering existing servers which makes it very easy to generate a Telfile from your current setup. In case you want to get started head over to the project wiki which contains a lot of information and examples.

Ältere Artikel im Archiv