tag:zargony.com,2008-08-01:/articleszargony.comRandom thoughts of a software engineer2015-10-25T21:56:54+01:00Andreas Neuhaushttps://zargony.com/tag:zargony.com,2008-08-01:Article/4c67d5a6785b1a7ff600000b2014-08-10T14:26:49+02:002014-09-19T10:14:34+02:00Automatic versioning in Xcode with git-describe<p>Do you manually set a new version number in Xcode every time you release a new version of your app? Or do you use some tool that updates the Info.plist in your project like <code>agvtool</code> or <code>PlistBuddy</code>? Either way, you probably know that it's a pain to keep track of the version number in the project.</p>
<p>I recently spent some time trying out various methods to automatically get the version number from git and put it into the app that Xcode builds. I found that most of them have drawbacks, but in the end I found a way that I finally like most. Here's how.</p>
<h2>Getting a version number from git</h2>
<p>Why should we choose a version number manually if we're using git to manage all source files anyway. Git is a source code management tool that keeps track of every change and is able to uniquely identify every snapshot of the source by its commit ids. The most obvious idea would be to simply use the commit ids as the version number of your software, but unfortunately (because of the distributed nature of git) commit ids are not very useful to the human reader: you can't tell at once which one is earlier and which is later.</p>
<p>But git has a very useful command called <code>git describe</code> that extracts a human readable version number from the repository. If you check out a specific tagged revision of your code, <code>git describe</code> will print the tag's name. If you check out any another commit, it will go back the commit history to find the latest tag and print its name followed by the number of commits and the current commit id. This is incredible useful to exactly describe the currently checked out version (hence the name of this command).</p>
<p>If you additionally use the <code>--dirty</code> option, <code>git describe</code> will append the string '-dirty' if your working directory isn't clean (i.e. you have uncommitted changes). Perfect!</p>
<p>So if tag all releases of your app (which you should be doing already anyway), it's easy to automatically create a version number with <code>git describe --dirty</code> for any commit, even between releases (e.g. for betas).</p>
<p>Here are some examples of version numbers:</p>
<div class="highlight"><pre><code class="sh">v1.0 // the release version tagged <span class="s1">'v1.0'</span>
v1.0-8-g1234567 // 8 commits after release v1.0, at commit id 1234567
v1.0-8-g1234567-dirty // same as above but with unspecified <span class="nb">local </span>changes <span class="o">(</span>dirty workdir<span class="o">)</span>
</code></pre></div>
<h2>Automatically set the version number in Xcode</h2>
<p>You'll find several ideas how to use automaticly generated version numbers in Xcode projects if you search the net. However most of them have drawbacks that I'd like to avoid. Some ways use a custom build phase to create a header file containing the version number. Besides that this approach gets more complicated with Swift, it only allows you to display the version number in your app, but doesn't set it in the app's Info.plist. Most libraries like crash reporters or analytics will take the version number from Info.plist, so it's useful to have the correct version number in there.</p>
<p>So let's modify the Info.plist using <code>PlistBuddy</code> in a custom build phase. But we don't want to modify the source Info.plist, because that would change a checked-in file and lead to a dirty workdir. We need to modify the Info.plist inside the target build directory (after the ProcessInfoPlistFile build rule ran).</p>
<h2>Instructions</h2>
<ul>
<li>Add a new run script build phase with the below script.</li>
<li>Make sure it runs late during building by moving it to the bottom.</li>
<li>Make sure that the list of input files and output files is empty and that "run script only when installing" is turned off.</li>
</ul>
<div class="highlight"><pre><code class="sh"><span class="c"># This script sets version information in the Info.plist of a target to the version</span>
<span class="c"># as returned by 'git describe'.</span>
<span class="c"># Info: http://zargony.com/2014/08/10/automatic-versioning-in-xcode-with-git-describe</span>
<span class="nb">set</span> -e
<span class="nv">VERSION</span><span class="o">=</span><span class="sb">`</span>git describe --dirty <span class="p">|</span>sed -e <span class="s2">"s/^[^0-9]*//"</span><span class="sb">`</span>
<span class="nb">echo</span> <span class="s2">"Updating Info.plist version to: ${VERSION}"</span>
/usr/libexec/PlistBuddy -c <span class="s2">"Set :CFBundleVersion ${VERSION}"</span> -c <span class="s2">"Set :CFBundleShortVersionString ${VERSION}"</span> <span class="s2">"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"</span>
/usr/bin/plutil -convert <span class="k">${</span><span class="nv">INFOPLIST_OUTPUT_FORMAT</span><span class="k">}</span>1 <span class="s2">"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"</span>
/usr/libexec/PlistBuddy -c <span class="s2">"Set :CFBundleVersion ${VERSION}"</span> -c <span class="s2">"Set :CFBundleShortVersionString ${VERSION}"</span> <span class="s2">"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist"</span>
</code></pre></div>
<h2>Thoughts</h2>
<ul>
<li>By keeping the list of output files empty, Xcode runs the script every time (otherwise it would detect an existing file and skip running the script even if changes were made and the version number may have changed)</li>
<li>Some sed magic strips any leading non-numbers from the version string so that you can use tags like <code>release-1.0</code> or <code>v1.5</code>.</li>
<li><code>PlistBuddy</code> converts the plist to XML, so we're running <code>plutil</code> at the end to convert it back to the desired output format (binary by default)</li>
<li>If you need more information than just the output of <code>git describe</code>, try the excellent "<a href="https://github.com/Autorevision/autorevision">autorevision</a>" script.</li>
</ul>
<p><strong>Update:</strong> A second call to <code>PlistBuddy</code> updates the version information inside the dSYM bundle to match the application version</p>
<p><strong>Update 2:</strong> Locate the dSYM bundle using <code>DWARF_DSYM_FOLDER_PATH</code> since the dSYM bundle isn't always created in the <code>TARGET_BUILD_DIR</code> (e.g. when archiving).</p>
tag:zargony.com,2008-08-01:Article/4c67d5a6785b1a7ff600000a2013-10-13T12:07:18+02:002013-10-13T12:07:18+02:00IPv6 in Docker containers<p><a href="http://www.docker.io">Docker</a> is great. It's a client-server based tool that makes it easy to run something in isolated <a href="http://lxc.sourceforge.net">Linux containers (LXC)</a>. Linux containers usually need some non-trivial configuration to be set up and run. Docker helps with this by providing easy methods to build container images and ready to run LXC configurations for running them. Unfortunately, it ignores IPv6... </p>
<h2>About LXC and Docker</h2>
<p>For those of you who don't know Linux containers: LXC is a lightweight virtual system mechanism in Linux to isolate processes from the host system. The usual virtualization (kvm, qemu, xen) boots a new kernel as a guest system and isolates the guest's access to the hardware. Containers however run on the same kernel as the host system does, but they get a completely isolated view to this kernel. I.e. a container has its own file system, network devices, process space, etc. Therefore, containers are a very lightweight virtualization mechanism witch much less overhead in memory and processing time compared to VMs. Linux containers are comparable to Zones in Solaris or Jails in FreeBSD.</p>
<p><strong>Update:</strong> Since version 0.9, Docker speaks directly to the kernel by default rather than using LXC commands. For the following IPv6 workaround, Docker needs to be running with the LXC execution driver, i.e. start the Docker daemon with <code>--exec-driver=lxc</code> and make sure to have the LXC package installed.</p>
<p>Besides preparing a root filesystem, Docker also sets up networking for a container when you run it. The host running the Docker server gets a new network device (<code>docker0</code>) using the private address range 172.17.42.1/16. This device is bridged with every running container (<code>veth*</code> devices) and provides this host-only network to the running containers. If you want to expose an IP port from a container to the outside (i.e. make a service running inside a container available to access from the internet), Docker can automatically set up iptables forwarding rules to do so.</p>
<p>This works fine with IPv4, but what about IPv6?</p>
<h2>IPv6</h2>
<p>Unfortunately, Docker does not have any support for IPv6. Very unfortunate, because IPv6 is the (long overdue) future internet protocol and much easier to handle (e.g. more than enough addresses and no annoying NAT).</p>
<p>However, there is a way to route IPv6 into containers. You just need to modify the container configuration manually.</p>
<p>Let's assume that the host has the IPv6 address range 1111:2222:3333:4444::2/64. Pick a common subnet address range for all containers, e.g. 1111:2222:3333:4444::8888:1/112. Assign the first subnet address to the <code>docker0</code> device (ensure that IPv6 forwarding is on and that your firewall is configured properly).</p>
<div class="highlight"><pre><code class="sh"><span class="nv">$ </span>ip -6 addr add 1111:2222:3333:4444::8888:1/112 dev docker0
</code></pre></div>
<p>All traffic for this subnet should now be routed to the <code>docker0</code> device. Now you need to make sure that the apropriate containers can handle it. The container's network device needs to get a subnet address too, e.g. 1111:2222:3333:4444::8888:20/112. For this to happen, we need to add the following statements to the LXC configuration of this container:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">lxc.network.flags = up
lxc.network.ipv6 = 1111:2222:3333:4444::8888:20/112
lxc.network.ipv6.gateway = 1111:2222:3333:4444::8888:1
</code></pre></div>
<p>This is equivalent to running <code>ip -6 addr add 1111:2222:3333:4444::8888:20/112 dev eth0</code> and <code>ip -6 route add default via 1111:2222:3333:4444::8888:1 dev eth0</code> inside the container every time it is run.</p>
<p>Docker allows us to specify arbitrary LXC configuration statements when running a container by using the <code>-lxc-conf=</code> option with <code>docker run</code>.</p>
<div class="highlight"><pre><code class="sh"><span class="nv">$ </span>docker run <span class="se">\</span>
-lxc-conf<span class="o">=</span><span class="s2">"lxc.network.flags = up"</span> <span class="se">\</span>
-lxc-conf<span class="o">=</span><span class="s2">"lxc.network.ipv6 = 1111:2222:3333:4444::8888:20/112"</span> <span class="se">\</span>
-lxc-conf<span class="o">=</span><span class="s2">"lxc.network.ipv6.gateway = 1111:2222:3333:4444::8888:1"</span> <span class="se">\</span>
-p 80:80 <span class="se">\</span>
webserver
</code></pre></div>
<p>This runs the container named "webserver", exposes IPv4 port 80 inside the container as port 80 on the host and routes the given IPv6 address into the container.</p>
<p>I admit that the above command is a bit lengthy to type in every time. However, containers with an IPv6 address are most probably started automatically, so these options can easily be added to the upstart configuration, init.d script or whatever.</p>
<h2>Ideas</h2>
<p>It would be nice to have an easier and more flexible way to specify an IPv6 address for a Docker container. Maybe if Docker would recognize one or more <code>ADDRESS</code> statements in a Dockerfile that specify the static address a container should get when being run. This could also cover static IPv4 addresses. However, by assigning static addresses at build time, a container would lose a lot of its flexibility (its host independence).</p>
<p>This reminds me of the same problem when assigning volumes that are shared with the host. You need to make sure that the host has the right volume the container needs and add a command line option to mount it into the container. Same as with IP addresses.</p>
<p>My idea of dealing with this would be a generic resource request/provide mechanism in Docker. Container build files could state named resources they need (need volume "webpages" mounted to /var/www, need IP address "webserver"). Hosts on the other hand could provide those named resources. (e.g. because they are listed in a host-specific configuration file). If a host does not have a resource, an arbitrary script could be run to somehow make the resource available (e.g. by mounting NFS). If a resource can not be provided, the container fails to run.</p>
<h2>My server</h2>
<p>I recently switched my server from running multiple VMs to running Docker containers. I published most of the scripts and configuration files in my <a href="https://github.com/zargony/infrastructure">infrastructure</a> repository on Github.</p>
<p><em>Updated April 22, 2014: Added lxc.network.flags=up and hint on using --exec-driver=lcx</em></p>
tag:zargony.com,2008-08-01:Article/4c67d5a6785b1a7ff60000092012-02-29T21:41:24+01:002012-02-29T21:41:24+01:00Google Charts on your site, the unobtrusive way<p>For a recent project, I needed to visualize some simple backend database statistics to admin users. Nothing exciting, just some simple numbers like count of subscriptions and purchases per day and such. When I started to work on some simple stats calculations, I already had in mind that I want to display the result in nice charts rather than in boring tables with numbers. And I wanted nice, interactive HTML 5 charts that display with almost no load time and fetch their data to display with Ajax.</p>
<p>There are several libraries that offer you to display charts in various ways like generating images to display or embedding Flash objects (yieks) into your page. Nowadays you surely want to use a library that displays interactive charts using Javascript and HTML5 though. In <a href="http://railscasts.com/episodes/223-charts">Railscast #223</a>, there's a nice tutorial how to use a few libraries.</p>
<p>However, I ended up using Google's free chart service <a href="http://code.google.com/apis/chart/">Google Charts</a>, paired with unobtrusive Javascript using jQuery. Here's how I did it:</p>
<p>Btw, there are two kind of Google Charts (which was confusing initially). The <a href="http://code.google.com/apis/chart/image/">Image Charts API</a> can be used to display charts as plain images. You just need to build a URL with query parameters that specify what chart image you want and put it into an <code>img</code> tag on your page. There are gems that provide helper methods to easily display image charts. However, the cool stuff comes with <a href="http://code.google.com/apis/chart/">Google Chart Tools</a> which adds nice, interactive charts using HTML5 to your page.</p>
<p>The way I used it:
1. The markup generated by the view contains an empty <code>div</code> (rendered in almost no time) with a HTML5 data-attribute containing the URL where to fetch the chart data.
2. On the client side, jQuery pulls in the Google Chart library, fetches the chart data with an Ajax call and displays the chart.
3. On the server side a <code>StatisticsController</code> handles these requests and returns chart data in JSON format.</p>
<h2>A simple view helper</h2>
<p>This is the statistics view helper (<code>app/helpers/statistics_helper.rb</code>) used to output a simple <code>div</code> tag in any view. Initially it displays an animated spinner image that displays right after the page loaded and indicates to the user that the chart ist loading. It also sets a height style attribute to size the chart area appropriately before it is loaded to preserve the page layout. The <code>data-chart</code> attribute is set to the URL where the chart data can be GET.</p>
<div class="highlight"><pre><code class="ruby"><span class="k">module</span> <span class="nn">StatisticsHelper</span>
<span class="k">def</span> <span class="nf">chart_tag</span> <span class="p">(</span><span class="n">action</span><span class="p">,</span> <span class="n">height</span><span class="p">,</span> <span class="n">params</span> <span class="o">=</span> <span class="p">{})</span>
<span class="n">params</span><span class="o">[</span><span class="ss">:format</span><span class="o">]</span> <span class="o">||=</span> <span class="ss">:json</span>
<span class="n">path</span> <span class="o">=</span> <span class="n">statistics_path</span><span class="p">(</span><span class="n">action</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span>
<span class="n">content_tag</span><span class="p">(</span><span class="ss">:div</span><span class="p">,</span> <span class="ss">:'data-chart'</span> <span class="o">=></span> <span class="n">path</span><span class="p">,</span> <span class="ss">:style</span> <span class="o">=></span> <span class="s2">"height: </span><span class="si">#{</span><span class="n">height</span><span class="si">}</span><span class="s2">px;"</span><span class="p">)</span> <span class="k">do</span>
<span class="n">image_tag</span><span class="p">(</span><span class="s1">'spinner.gif'</span><span class="p">,</span> <span class="ss">:size</span> <span class="o">=></span> <span class="s1">'24x24'</span><span class="p">,</span> <span class="ss">:class</span> <span class="o">=></span> <span class="s1">'spinner'</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p>Example for usage in a view:</p>
<div class="highlight"><pre><code class="erb"><span class="x">...</span>
<span class="cp"><%=</span> <span class="n">chart_tag</span><span class="p">(</span><span class="s1">'subscriptions_graph'</span><span class="p">,</span> <span class="mi">300</span><span class="p">,</span> <span class="ss">:days</span> <span class="o">=></span> <span class="mi">14</span><span class="p">)</span> <span class="cp">%></span><span class="x"></span>
<span class="x">...</span>
</code></pre></div>
<h2>Unobtrusive Javascript on the client side</h2>
<p>This little Javascript function (<code>app/assets/javascripts/charts.js</code>) can be loaded on every page. It only loads the Google visualization library if there actually is a chart to display (an element with a data-chart attribute exists). It then fetches the chart data from the URL given in the <code>data-chart</code> attribute and creates and displays the chart accordingly to the <a href="http://code.google.com/apis/chart/interactive/docs/reference.html">visualization API</a>.</p>
<div class="highlight"><pre><code class="javascript"><span class="nx">jQuery</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">$</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Load Google visualization library if a chart element exists</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">$</span><span class="p">(</span><span class="s1">'[data-chart]'</span><span class="p">).</span><span class="nx">length</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">getScript</span><span class="p">(</span><span class="s1">'https://www.google.com/jsapi'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="nx">textStatus</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">google</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span><span class="s1">'visualization'</span><span class="p">,</span> <span class="s1">'1.0'</span><span class="p">,</span> <span class="p">{</span> <span class="s1">'packages'</span><span class="o">:</span> <span class="p">[</span><span class="s1">'corechart'</span><span class="p">],</span> <span class="s1">'callback'</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="c1">// Google visualization library loaded</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'[data-chart]'</span><span class="p">).</span><span class="nx">each</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">div</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
<span class="c1">// Fetch chart data</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">getJSON</span><span class="p">(</span><span class="nx">div</span><span class="p">.</span><span class="nx">data</span><span class="p">(</span><span class="s1">'chart'</span><span class="p">),</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Create DataTable from received chart data</span>
<span class="kd">var</span> <span class="nx">table</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">google</span><span class="p">.</span><span class="nx">visualization</span><span class="p">.</span><span class="nx">DataTable</span><span class="p">();</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">each</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">cols</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span> <span class="nx">table</span><span class="p">.</span><span class="nx">addColumn</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="nx">table</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span> <span class="p">});</span>
<span class="nx">table</span><span class="p">.</span><span class="nx">addRows</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">rows</span><span class="p">);</span>
<span class="c1">// Draw the chart</span>
<span class="kd">var</span> <span class="nx">chart</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">google</span><span class="p">.</span><span class="nx">visualization</span><span class="p">.</span><span class="nx">ChartWrapper</span><span class="p">();</span>
<span class="nx">chart</span><span class="p">.</span><span class="nx">setChartType</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">type</span><span class="p">);</span>
<span class="nx">chart</span><span class="p">.</span><span class="nx">setDataTable</span><span class="p">(</span><span class="nx">table</span><span class="p">);</span>
<span class="nx">chart</span><span class="p">.</span><span class="nx">setOptions</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">options</span><span class="p">);</span>
<span class="nx">chart</span><span class="p">.</span><span class="nx">setOption</span><span class="p">(</span><span class="s1">'width'</span><span class="p">,</span> <span class="nx">div</span><span class="p">.</span><span class="nx">width</span><span class="p">());</span>
<span class="nx">chart</span><span class="p">.</span><span class="nx">setOption</span><span class="p">(</span><span class="s1">'height'</span><span class="p">,</span> <span class="nx">div</span><span class="p">.</span><span class="nx">height</span><span class="p">());</span>
<span class="nx">chart</span><span class="p">.</span><span class="nx">draw</span><span class="p">(</span><span class="nx">div</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">}});</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div>
<h2>A controller that provides JSON chart data</h2>
<p>The statistics controller (<code>app/controllers/statistics_controller.rb</code>) provides one or more actions that return chart data in JSON format. You can control any aspect of the displayed chart from here. E.g. <code>type</code> sets the type of the chart, <code>cols</code> describe the data columns and <code>rows</code> contains the actual data points. Refer to the <a href="http://code.google.com/apis/chart/interactive/docs/reference.html#chartwrapperobject">visualization API (ChartWrapper)</a> for a list of all properties.</p>
<div class="highlight"><pre><code class="ruby"><span class="k">class</span> <span class="nc">StatisticsController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">subscriptions_graph</span>
<span class="n">days</span> <span class="o">=</span> <span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:days</span><span class="o">]</span> <span class="o">||</span> <span class="mi">30</span><span class="p">)</span><span class="o">.</span><span class="n">to_i</span>
<span class="n">render</span> <span class="ss">:json</span> <span class="o">=></span> <span class="p">{</span>
<span class="ss">:type</span> <span class="o">=></span> <span class="s1">'AreaChart'</span><span class="p">,</span>
<span class="ss">:cols</span> <span class="o">=></span> <span class="o">[[</span><span class="s1">'string'</span><span class="p">,</span> <span class="s1">'Date'</span><span class="o">]</span><span class="p">,</span> <span class="o">[</span><span class="s1">'number'</span><span class="p">,</span> <span class="s1">'subscriptions'</span><span class="o">]</span><span class="p">,</span> <span class="o">[</span><span class="s1">'number'</span><span class="p">,</span> <span class="s1">'purchases'</span><span class="o">]]</span><span class="p">,</span>
<span class="ss">:rows</span> <span class="o">=></span> <span class="p">(</span><span class="mi">1</span><span class="o">.</span><span class="n">.days</span><span class="p">)</span><span class="o">.</span><span class="n">to_a</span><span class="o">.</span><span class="n">inject</span><span class="p">(</span><span class="o">[]</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">memo</span><span class="p">,</span> <span class="n">i</span><span class="o">|</span>
<span class="n">date</span> <span class="o">=</span> <span class="n">i</span><span class="o">.</span><span class="n">days</span><span class="o">.</span><span class="n">ago</span><span class="o">.</span><span class="n">to_date</span>
<span class="n">t0</span><span class="p">,</span> <span class="n">t1</span> <span class="o">=</span> <span class="n">date</span><span class="o">.</span><span class="n">beginning_of_day</span><span class="p">,</span> <span class="n">date</span><span class="o">.</span><span class="n">end_of_day</span>
<span class="n">subscriptions</span> <span class="o">=</span> <span class="no">Subscription</span><span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="ss">:created_at</span><span class="o">.</span><span class="n">gte</span> <span class="o">=></span> <span class="n">t0</span><span class="p">,</span> <span class="ss">:created_at</span><span class="o">.</span><span class="n">lte</span> <span class="o">=></span> <span class="n">t1</span><span class="p">)</span><span class="o">.</span><span class="n">count</span>
<span class="n">purchases</span> <span class="o">=</span> <span class="no">Purchase</span><span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="ss">:purchased_at</span><span class="o">.</span><span class="n">gte</span> <span class="o">=></span> <span class="n">t0</span><span class="p">,</span> <span class="ss">:purchased_at</span><span class="o">.</span><span class="n">lte</span> <span class="o">=></span> <span class="n">t1</span><span class="p">)</span><span class="o">.</span><span class="n">count</span>
<span class="n">memo</span> <span class="o"><<</span> <span class="o">[</span><span class="n">date</span><span class="p">,</span> <span class="n">subscriptions</span><span class="p">,</span> <span class="n">purchases</span><span class="o">]</span>
<span class="n">memo</span>
<span class="k">end</span><span class="o">.</span><span class="n">reverse</span><span class="p">,</span>
<span class="ss">:options</span> <span class="o">=></span> <span class="p">{</span>
<span class="ss">:chartArea</span> <span class="o">=></span> <span class="p">{</span> <span class="ss">:width</span> <span class="o">=></span> <span class="s1">'90%'</span><span class="p">,</span> <span class="ss">:height</span> <span class="o">=></span> <span class="s1">'75%'</span> <span class="p">},</span>
<span class="ss">:hAxis</span> <span class="o">=></span> <span class="p">{</span> <span class="ss">:showTextEvery</span> <span class="o">=></span> <span class="mi">30</span> <span class="p">},</span>
<span class="ss">:legend</span> <span class="o">=></span> <span class="s1">'bottom'</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p><code>Subscription</code> and <code>Purchase</code> are my model classes. In case you wonder about the syntax, it's <a href="http://mongoid.org/">MongoID</a>, not ActiveRecord.</p>
<h2>Conclusion</h2>
<p>Charts are now like I wanted them. The page loads almost instantly, showing a spinner to the user while the chart data is fetched (which can be a lengthy request). And those interactive HTML5 charts are nice eyecandy. The statistics controller may not scale well doing tons of count queries, but in this case it's used by a handful of admin users only. Also, more complex charts might need some adjustments in how the DataTable object is filled. Anyway, you get the point. ;)</p>
tag:zargony.com,2008-08-01:Article/4c67d5a6785b1a7ff60000072010-08-15T14:34:19+02:002010-08-17T19:44:52+02:00Rails I18N core translation updater and CSS naked plugin as gems<p>It has been quite a while that I created these two Rails plugins, but they still serve their purpose without problems even in newer applications. Since Rails plugins as a gem are much easier to maintain, I took some time to move these two plugins to gems. This makes them even more easy to use and especially more maintainable.</p>
<p>I'm talking about <code>rails-i18n-updater</code>, a plugin that merges and manages Rails core translations in your application — a helper that I don't want to miss in any i18n-enabled application anymore. And <code>css_naked</code>, a simple plugin that disables all stylesheets during the <a href="http://naked.dustindiaz.com/">CSS naked day</a> event once a year.</p>
<p>Both plugins are ready for Rails 3 and work well with Ruby 1.9, btw.</p>
<h2>Rails I18n updater</h2>
<p>The <code>rails-i18n-updater</code> plugin does two things to an application. First, it adds a rake task called <code>i18n:update</code> to your applications. This task can be called to download the most current <a href="http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale/">translation files</a> for the Rails core. Second, it prepends these translation files to the I18n load path at runtime.</p>
<p>This means, that you don't have to fiddle with Rails core translations anymore in your application. Things like date formats, weekday names, month names, time distances in words and ActiveRecord validation error messages are translated for you — and moreover updating them is as easy as calling <code>rake i18n:update</code>. Because the downloaded translations are <em>prepended</em> to your load path, you're still free to override them in your own locale files.</p>
<p>This seems simple, but becomes really useful if you don't want to do every core translation yourself and don't want to manually merge core translation updates. To use the rails-i18n-updater plugin as a gem, just add <code>gem 'rails-i18n-updater'</code> to your application's Gemfile (environments.rb for Rails 2).</p>
<p>rails-i18n-updater plugin: <a href="http://rubygems.org/gems/rails-i18n-updater">RubyGems</a> <a href="http://github.com/zargony/rails-i18n-updater">Github</a></p>
<h2>CSS naked</h2>
<p><code>css_naked</code> is a very simple plugin that just disables the <code>stylesheet_include_tag</code> helper during the <a href="http://naked.dustindiaz.com/">CSS Naked Day event</a> each year. Installing and using it is now as easy as adding <code>gem 'css_naked'</code> to an application's Gemfile (environments.rb for Rails 2).</p>
<p>css_naked plugin: <a href="http://rubygems.org/gems/css_naked">RubyGems</a> <a href="http://github.com/zargony/css_naked">Github</a></p>
tag:zargony.com,2008-08-01:Article/4c53792c785b1a0a500000482009-08-11T06:10:39+02:002010-08-17T16:42:49+02:00A Rails plugin to use core translations<p>If you're writing an i18n-enabled Rails application, you have to deal with the translation of your own application as well as with the translation of several Rails core strings (like validation error messages, date formats, and so on). You usually don't need to translate the Rails core strings yourself, since there's a big <a href="http://github.com/svenfuchs/rails-i18n">repository</a> of user-contributed core translations you can pull into your application. However keeping this translations up to date in your application can quickly become cumbersome.</p>
<p>For this reason, I refactored the <a href="http://github.com/zargony/rails-i18n">rails-i18n</a> repository a while ago and made it a Rails plugin. You could install the plugin to your application and it provided Rails core translations for you. Unfortunately it became annoying to keep the fork up to date with the latest translation changes since every rebase or merge was a pain because of the moved file locations.</p>
<p>So my latest solution is another small plugin called <a href="http://github.com/zargony/rails-i18n-updater">rails-i18n-updater</a>, which does not contain the translations itself but downloads them from the above mentioned repository of core translations.</p>
<h2>Installing the plugin</h2>
<p>Installation of the new plugin works as usual:</p>
<div class="highlight"><pre><code class="sh">./script/plugin install git://github.com/zargony/rails-i18n-updater.git
</code></pre></div>
<p>In case you used <code>rails-i18n</code> as a plugin, you should remove it:</p>
<div class="highlight"><pre><code class="sh">./script/plugin remove rails-i18n
</code></pre></div>
<h2>Updating translations</h2>
<p>Whenever you want to update to the latest core translations, simply run</p>
<div class="highlight"><pre><code class="sh">rake i18n:update
</code></pre></div>
<p>This will use <code>git</code> to download the latest translations from <a href="http://github.com/svenfuchs/rails-i18n">rails-i18n</a> and put them to <code>vendor/rails-locales</code>. The plugin then, on startup, loads core translations for locales you used in your application by modifying <code>I18n.load_path</code>. These core translations are prepended to the load path, which means that you get them as default translations that can be overridden in your own translation files. Now, updating to the latest translations is as easy as running <code>rake i18n:update</code>.</p>
tag:zargony.com,2008-08-01:Article/4c53792c785b1a0a500000462009-07-24T12:17:52+02:002010-08-17T19:13:45+02:00Ruby 1.9 and file encodings<p>Just out of curiosity, I took two of my recent Rails applications today and tried them with Ruby 1.9. It was surprisingly easy to make all tests pass without any warnings or errors. Rails 2.3 already has quite good support for Ruby 1.9. The main gotcha however was about file encodings. If you have source files which contain non-ASCII characters, Ruby 1.9 now needs to know which encoding the file was saved with. If you don't specify the encoding for a file with non-ASCII characters, you'll get an <code>invalid multibyte char (US-ASCII)</code> error message.</p>
<h2>File encoding</h2>
<p>So Ruby 1.9 rejects to parse any file with non-ASCII characters if you don't specify the encoding. You can do so by adding a Ruby comment at the top of the file:</p>
<div class="highlight"><pre><code class="ruby"><span class="c1"># encoding: utf-8</span>
</code></pre></div>
<p>This tells the Ruby parser to interpret the file content using UTF-8 encoding. Of course, you need to specify the correct encoding (meaningly the encoding your editor used when the file was saved). Most Posix systems like Linux and Mac use UTF-8 by default nowadays. Windows however defaults to Latin-1 (ISO-8859-1).</p>
<h2>Why bother?</h2>
<p>Previously, Ruby didn't care about the encoding. The Ruby parser read everything in the source file byte by byte. E.g. try asking for the length of a string that contains Unicode characters. With Ruby 1.8, it returns the number of bytes the string occopies:</p>
<div class="highlight"><pre><code class="ruby"><span class="s2">"ä"</span><span class="o">.</span><span class="n">length</span> <span class="c1"># returns 2 since 'ä' occupies 2 bytes in UTF-8</span>
</code></pre></div>
<p>If you put <code>"ä".length</code> into a text file and save it using UTF-8 encoding, the letter 'ä' is stored as the two-bytes-sequence C3,A4 (that's how 'ä' is represented in UTF-8). So since Ruby 1.8 doesn't know encodings and just reads bytes from the file, it sees two "letters" when parsing the file. In Ruby 1.8, bytes and characters are the same (meaning one character is considered one byte and vice versa). This is fine for ASCII, but doesn't work with multibyte encodings like Unicode UTF-8. If you would save the same file using Latin-1 encoding, the letter 'ä' would be stored as a single byte E4 and Ruby 1.8 would return 1 when asking for the length.</p>
<p>Ruby 1.9 however distinguishes between bytes and characters. If the parser encounters the byte sequence C3,A4 in a file, and you told it before that this file uses UTF-8 encoding, the parser knows that these two bytes are the representation of the single character 'ä'. Therefore Ruby 1.9 can correctly count the number of characters in a string, even if it contains multibyte characters:</p>
<div class="highlight"><pre><code class="ruby"><span class="c1"># encoding: utf-8</span>
<span class="s2">"ä"</span><span class="o">.</span><span class="n">length</span> <span class="c1"># returns 1 since Ruby 1.9 knows the encoding</span>
</code></pre></div>
<p>Since there are several encoding standards, Ruby needs to know which one the file was saved with to correctly parse multibyte characters.</p>
<p>This also means that with different encodings, the length of a string is not anymore equal to the number of bytes the string occupies. However this is by design, since with every multibyte encoding, a character can not neccessarily be represented by a single byte.</p>
<p>In Ruby applications, you usually don't care about the number of bytes a string occupies. The most useful length measurement to know is the number of characters a string has. So <code>String#length</code> and <code>String#size</code> both return the number of characters in Ruby 1.9, not the number of bytes like Ruby 1.8 did before. To get the number of bytes a string occupies, Ruby 1.9 has as <code>String#bytesize</code> method (which however is rarely used, I suppose).</p>
<h2>Does it affect Rails?</h2>
<p>Rarely, I'd say. I tried two Rails applications I wrote a while ago (which heavily use UTF-8 strings for German characters) and didn't notice any problems. <strike>However, there could be some minor differences in behavior, e.g. if using <code>validates_length_of</code> in a model class, it previously checked against the byte count while with Ruby 1.9, the real character count is checked.</strike> If the underlying storage/database engine isn't aware of the encoding, this may lead strange length problems.</p>
<p>I'm a little surprised that Ruby 1.9 does not use a default encoding of UTF-8 (convention over configuration -- UTF-8 seems to be the most wide spread used encoding nowadays and it would be nice if it would be assumed the default). But then, this might lead to weird problems if you don't use UTF-8 and Ruby doesn't detect it (detecting non-ASCII is easy, detecting non-UTF-8 however wouldn't be reliable).</p>
<p>Btw, besides file encodings, there are a lot of other changes in Ruby 1.9. Check out the <a href="http://eigenclass.org/hiki/Changes+in+Ruby+1.9">Changelog</a> to find out more. And beware that you can do some <a href="http://blog.nuclearsquid.com/writings/ruby-1-9-encodings">strange</a> <a href="http://pragdave.blogs.pragprog.com/pragdave/2008/04/fun-with-ruby-1.html">things</a> with encodings.</p>
tag:zargony.com,2008-08-01:Article/4c53792c785b1a0a500000432009-05-05T07:35:41+02:002010-08-17T17:37:55+02:00Routing parameters with a dot<p>Last week I received an interesting bug report for an application I'm working on at the office. It has a controller with an index action that displays a list of items which can be filtered by tags. A tester reported that every time he chooses to filter the list by a tag that contains a dot, the site returns an error. First this seems strange since tags with a dot worked perfectly fine in tests.</p>
<p>But after digging deeper, I found a surprising reason for this strange error: To make URLs look nicer, I defined an extra route like this:</p>
<div class="highlight"><pre><code class="ruby"><span class="n">map</span><span class="o">.</span><span class="n">things</span> <span class="s1">'things/:tag'</span><span class="p">,</span> <span class="ss">:controller</span> <span class="o">=></span> <span class="s1">'things'</span><span class="p">,</span> <span class="ss">:action</span> <span class="o">=></span> <span class="s1">'index'</span><span class="p">,</span>
<span class="ss">:tag</span> <span class="o">=></span> <span class="kp">nil</span>
</code></pre></div>
<p>The intention was, to have <code>/things/foo</code> instead of <code>/things&tag=foo</code> as the URL. This actually worked fine -- except for cases when the tag contained a dot.</p>
<p>Rails seems to magically append <code>:format</code> to every rule so that different result formats are possible with one rule (like <code>/things.html</code> and <code>/things.json</code>). If a tag contained a dot, the URL requested was <code>/items/foo.bar</code>, which Rails parsed into <code>:tag => 'foo', :format => 'bar'</code>.</p>
<p>So the controller's index action was requested to display all entries for tag <code>foo</code> and output results in format <code>bar</code> -- which is unknown and unsupported and therefore resulted in an error page.</p>
<p>This behaviour seems to be hardcoded into Rails and I couldn't find a way to disable it. But luckily a workaround is pretty easy. The trick is to make the tag parameter's regular expression greedy, so that it covers the format part as well. Using the <code>:requirements</code> option to a route, we can set the regexp for a parameter:</p>
<div class="highlight"><pre><code class="ruby"><span class="n">map</span><span class="o">.</span><span class="n">things</span> <span class="s1">'things/:tag'</span><span class="p">,</span> <span class="ss">:controller</span> <span class="o">=></span> <span class="s1">'things'</span><span class="p">,</span> <span class="ss">:action</span> <span class="o">=></span> <span class="s1">'index'</span><span class="p">,</span>
<span class="ss">:tag</span> <span class="o">=></span> <span class="kp">nil</span><span class="p">,</span> <span class="ss">:requirements</span> <span class="o">=></span> <span class="p">{</span> <span class="ss">:tag</span> <span class="o">=></span> <span class="sr">/.+/</span> <span class="p">}</span>
</code></pre></div>
<p>Now it works as intended. <code>:format</code> is always ignored now and the whole string is put into <code>params[:tag]</code>.</p>
tag:zargony.com,2008-08-01:Article/4c53792c785b1a0a500000402009-03-30T15:37:14+02:002011-07-18T21:11:13+02:00Testing Cookies in Rails 2.3<p>While moving some applications to Rails 2.3 recently, I stumbled across some problems with testing cookies. E.g. this blog uses cookies to remember your name, email address and web url if you leave a comment. This way, you don't need to re-enter these information the next time you leave a comment. These cookies are set by the controller action that creates new comments (in <code>CommentsController#create</code>):</p>
<div class="highlight"><pre><code class="ruby"><span class="n">cookies</span><span class="o">[</span><span class="s1">'blog_visitor_name'</span><span class="o">]</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">:value</span> <span class="o">=></span> <span class="vi">@comment</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="ss">:expires</span> <span class="o">=></span> <span class="mi">1</span><span class="o">.</span><span class="n">year</span><span class="o">.</span><span class="n">from_now</span> <span class="p">}</span>
</code></pre></div>
<p>This is the extended form of setting a cookie (a hash is used to not only set the cookies value but also the expiration time). There are several more hash options available which are described in the <code>ActionController#cookies</code> documentation. The basic form (which only sets a simple session cookie that is valid until the visitor closes the browser) uses a simple string:</p>
<div class="highlight"><pre><code class="ruby"><span class="n">cookies</span><span class="o">[</span><span class="s1">'cookie_name'</span><span class="o">]</span> <span class="o">=</span> <span class="s1">'cookie_value'</span>
</code></pre></div>
<p>This form of setting and getting cookies in a controller has been there for quite some time and actually hasn't changed in Rails 2.3. But what has changed, is the availability of cookies in functional tests. Before Rails 2.3, you needed to create a <code>CGI::Cookie</code> object for each cookie of a request. After the request, you could then check the <code>cookies</code> accessor and assert correct attributes of a cookie:</p>
<div class="highlight"><pre><code class="ruby"><span class="k">def</span> <span class="nf">test_comment_creation_overwrites_visitor_cookies</span>
<span class="vi">@request</span><span class="o">.</span><span class="n">cookies</span><span class="o">[</span><span class="s1">'blog_visitor_name'</span><span class="o">]</span> <span class="o">=</span> <span class="no">CGI</span><span class="o">::</span><span class="no">Cookie</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s1">'blog_visitor_name'</span><span class="p">,</span> <span class="s1">'Fred F.'</span><span class="p">)</span>
<span class="n">post</span> <span class="ss">:create</span><span class="p">,</span> <span class="p">{</span> <span class="o">.</span><span class="n">.</span><span class="o">.</span> <span class="p">}</span>
<span class="n">assert_equal</span> <span class="s1">'Fred'</span><span class="p">,</span> <span class="n">cookies</span><span class="o">[</span><span class="s1">'blog_visitor_name'</span><span class="o">].</span><span class="n">value</span>
<span class="n">assert_equal</span> <span class="s1">'/'</span><span class="p">,</span> <span class="n">cookies</span><span class="o">[</span><span class="s1">'blog_visitor_name'</span><span class="o">].</span><span class="n">path</span>
<span class="n">assert</span> <span class="n">cookies</span><span class="o">[</span><span class="s1">'blog_visitor_name'</span><span class="o">].</span><span class="n">expires</span> <span class="o">></span> <span class="mi">364</span><span class="o">.</span><span class="n">days</span><span class="o">.</span><span class="n">from_now</span>
<span class="k">end</span>
</code></pre></div>
<p>As you can see, this test first sets a cookie by creating a <code>CGI:Cookie</code> object and requests the create action. This simulates a user who previously used "Fred F." as his name and now posts a new comment using the name "Fred". After processing the request, the test checks if the user's cookie is update to the new name he gave. Furthermore it tests that the newly created cookie attributes (path and expire time) are properly set.</p>
<p>With Rails 2.3, usage of cookies in tests were modified to match usage in the controller itself. The above test now looks like this:</p>
<div class="highlight"><pre><code class="ruby"><span class="k">def</span> <span class="nf">test_comment_creation_overwrites_visitor_cookies</span>
<span class="vi">@request</span><span class="o">.</span><span class="n">cookies</span><span class="o">[</span><span class="s1">'blog_visitor_name'</span><span class="o">]</span> <span class="o">=</span> <span class="s1">'Fred F.'</span>
<span class="n">post</span> <span class="ss">:create</span><span class="p">,</span> <span class="p">{</span> <span class="o">.</span><span class="n">.</span><span class="o">.</span> <span class="p">}</span>
<span class="n">assert_equal</span> <span class="s1">'Fred'</span><span class="p">,</span> <span class="n">cookies</span><span class="o">[</span><span class="s1">'blog_visitor_name'</span><span class="o">]</span>
<span class="c1">#assert_equal '/', cookies['blog_visitor_name']...???</span>
<span class="c1">#assert cookies['blog_visitor_name']...??? > 364.days.from_now</span>
<span class="k">end</span>
</code></pre></div>
<p>Since the cookies accessor now returns the cookie value only (like in controllers), it is easier to test wether a cookie was correctly set to the expected value. However, extended information like the cookie path and the expiration time are not available anymore and therefore we cannot test anymore if these attributes were correctly set by the controller.</p>
<p>So how can you test cookie attributes with Rails 2.3? Unfortunately you can't. The <code>cookies</code> accessor in tests parses the content of the <code>Set-Cookie</code> header of a response and builds a hash of cookies which were set by the controller action. Unfortunately, it only gathers the cookie name and value and does not parse the rest of the data. The only way that I know of to test cookie attributes is to manually parse the contents of the <code>Set-Cookie</code> header for now.</p>
tag:zargony.com,2008-08-01:Article/4c53792c785b1a0a5000003f2009-02-28T13:01:05+01:002010-08-17T18:25:16+02:00CSS Naked Day 2009: a Rails plugin<p>On April 9th 2009, the fourth <a href="http://naked.dustindiaz.com/">CSS Naked Day</a> event will take place. The idea behind this event is to promote web standards (like proper semantic markup and a good hierarchy structure). On April 9th, participants are encouraged to completely remove all stylesheets from their site, stripping it entirely of its design. If your site has proper semantic markup, it'll stay well usable and understandable even without styles. If not, you better hide and don't take part in this event :)</p>
<p>To make it as easy as possible, I created a little <a href="http://github.com/zargony/css_naked">Rails plugin</a> that, once installed to an app, simply disables the <code>stylesheet_link_tag</code> helper for the duration of the event. It's as easy as install and forget (assuming that you used <code>stylesheet_link_tag</code> in your layouts and didn't add inline styles or stylesheet links manually).</p>
<p>So let's see how many sites will join this funny but yet <a href="http://www.guitarangel.net/internet/why-sans-css/">expressive</a> event this year. It's time to show off your <body> ;-)</p>
tag:zargony.com,2008-08-01:Article/4c53792b785b1a0a5000003e2009-01-22T14:06:40+01:002010-08-17T18:24:58+02:00Let browsers cache static files to greatly speed up your site<p>There are many factors that determine how long a page takes to show up in a browser. When a page is requested by a browser, the server needs some time to compute the page contents (controller, model/database, view) and returns HTML to the browser. Besides network latency and throughput (which can be optimized by choosing a good hosting provider), there are several ways to speed up the page generation itself (like <a href="http://www.railway.at/articles/2008/09/20/a-guide-to-memoization">memoization</a>, <a href="http://www.railsenvy.com/2007/3/20/ruby-on-rails-caching-tutorial-part-2">action/fragment/query-caching</a>, using memcached, and so on).</p>
<p>But even after the HTML page itself has been transferred to the browser, the page is not necessarily ready to display yet. A page usually references more resources like stylesheets, javascripts and images that need to be requested and transferred. These are mostly static files (located in the <code>public</code> folder of a Rails application) and are directly served by the webserver without invoking the Rails application. So these files takes very little computation time on the server, but they still need some time and bandwith to transfer, which can be optimized.</p>
<h2>Reducing the number of static file requests per host</h2>
<p>A browser usually limits the number of concurrent requests it sends to a single IP address. If an application returns pages with many references, page loading will slow down because the browser needs to serialize requests. To speed up the loading of many static files, it's common practice to reduce the number of static files, e.g. by bundling multiple stylesheets and javascripts into a single file (see the <code>:cache</code> option of <code>javascript_include_tag</code> and <code>stylesheet_link_tag</code>), or to <a href="http://spattendesign.com/2007/10/24/setting-up-multiple-asset-hosts-in-rails">set up multiple asset hosts</a> so that more requests can be done concurrently.</p>
<h2>Reducing the number of static file requests at all</h2>
<p>Modern browsers usually don't request all static files again for each page. Files are cached between requests and they're only transferred again, if they have changed on the server. For this purpose, the browser asks the server if its cached version of a file is still valid by adding a special HTTP header (like <code>If-Modified-Since</code>) to the request. If the file wasn't changed, it doesn't need to be transferred again and the server answers with a <code>304 Not modified</code> result without sending the file again. As far as I know, this behaviour is configured by default on todays default webserver installations.</p>
<p>Btw, you can use this for your HTML content as well, if you add <a href="http://ryandaigle.com/articles/2008/8/14/what-s-new-in-edge-rails-simpler-conditional-get-support-etags">conditional get support</a> to your controllers.</p>
<p>Conditional get support prevents unnecessary data transfer and therefore saves you quite some bandwith. However, browsers still contact the server for every static file to ask if their cached version is up to date. This can take some time and slows down loading of pages with many images. Since static files don't ever change (unless you deploy a new version of your application), it would be ok for browsers to don't ask the server at all and just use their locally cached version. This greatly speeds up page loading since the browser can skip requesting any referenced static file (except for the first time a visitor comes to your site).</p>
<p>For Apache, simply enable <code>mod_expires</code> and add the following to your Apache configuration:</p>
<div class="highlight"><pre><code class="apache"><span class="nb">ExpiresActive</span> <span class="k">On</span>
<span class="nt"><FilesMatch</span> <span class="s">"\.(ico|gif|jpe?g|png|js|css)$"</span><span class="nt">></span>
<span class="nb">ExpiresDefault</span> <span class="s2">"access plus 1 year"</span>
<span class="nt"></FilesMatch></span>
</code></pre></div>
<p>Using this configuration statements, Apache adds response headers for static files that instruct browsers to cache the files for up to one year without further checking.</p>
<p>You don't even need to worry about cache invalidation if you deploy a new version of your app. Rails automatically adds timestamps to all references to static files if you use the according helper methods (like <code>stylesheet_link_tag</code>, <code>javascript_include_tag</code> and <code>image_tag</code>). This means, that the URL of a static file is changed if the file's timestamp is changed. This way, browsers automatically request the new file.</p>