<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Simone Carletti&#039;s Blog &#187; routes</title>
	<atom:link href="http://www.simonecarletti.com/blog/tags/routes/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.simonecarletti.com/blog</link>
	<description>Simone Carletti&#039;s personal ramblings on programming, syndication, search engines &#38; marketing.</description>
	<lastBuildDate>Thu, 12 Jan 2012 09:16:49 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>How to pass request context to ActionMailer and supply the :host value to url_for</title>
		<link>http://www.simonecarletti.com/blog/2009/10/actionmailer-and-host-value/</link>
		<comments>http://www.simonecarletti.com/blog/2009/10/actionmailer-and-host-value/#comments</comments>
		<pubDate>Wed, 28 Oct 2009 15:50:47 +0000</pubDate>
		<dc:creator>Simone Carletti</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[actionmailer]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[routes]]></category>

		<guid isPermaLink="false">http://www.simonecarletti.com/blog/?p=663</guid>
		<description><![CDATA[How to pass Rails request context to ActionMailer and extract the :host value for generating URL in ActionMailer.]]></description>
			<content:encoded><![CDATA[<p>The epic battle me against <code>ActionMailer</code> has finally come to an end and I&#8217;m quite satisfied with the final result.</p>
<p>Have you ever tried to generate URLs within an <code>ActionMailer</code> template? If you did at least once, then you are probably familiar with the following error:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">ActionView::TemplateError (Missing host to link to! Please provide :host parameter or set default_url_options[:host])</div></td></tr></tbody></table></div>
<p>This happens because <code>ActionMailer</code> instance doesn’t have any context about the incoming request so you’ll need to provide the <code>:host</code>, <code>:controller</code>, and <code>:action:</code>. If you use a named route, <code>ActionPack</code> provides controller and action names for you. Otherwise, with the url_for helper you need to pass all the parameters.</p>
<div class="codecolorer-container text default code-ruby" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">&lt;%= message_url %&gt;<br />
&lt;%= url_for :controller =&gt; &quot;messages&quot;, :action =&gt; &quot;index&quot; %&gt;</div></td></tr></tbody></table></div>
<p>Regardless your choice, you always need to provide the host option to generate an URL in <code>ActionMailer</code>. As shown by the <a href="http://guides.rubyonrails.org/action_mailer_basics.html#generating-urls-in-action-mailer-views">ActionMailer guide</a>, you basically have two ways to pass the host value to <code>ActionMailer</code>:</p>
<ol>
<li>set a global value</li>
<li>pass the option each time you generate an URL</li>
</ol>
<p>This works for almost the most part of basic Rails applications but never really worked for me.<span id="more-663"></span></p>
<h2>Scenario</h2>
<p>You have a medium complex Rails application and you need to send emails in different environments including development, staging and production. Each environment is usually hosted on a specific domain:</p>
<ul>
<li><code>development</code> environment runs on <code>localhost</code></li>
<li><code>staging</code> environment runs on <code>staging.example.com</code></li>
<li><code>production</code> environment runs on <code>example.com</code></li>
</ul>
<p>A single application instance serves different languages. Each language is hosted on a specific subdomain. So, for example</p>
<ul>
<li>www.example.com (English)</li>
<li>it.example.com (Italian)</li>
<li>fr.example.com (French)</li>
</ul>
<p>Of course, each environment follows the same conventions. This is the staging environment</p>
<ul>
<li>www.staging.example.com (English)</li>
<li>it.staging.example.com (Italian)</li>
<li>fr.staging.example.com (French)</li>
</ul>
<p>And here&#8217;s the development environment. In this case, the locale is passed via querystring instead of using a subdomain.</p>
<ul>
<li>localhost?locale=en (English)</li>
<li>localhost?locale=it (Italian)</li>
<li>localhost?locale=fr (French)</li>
</ul>
<p>The locale detection system is quite complex but I&#8217;m not going to show it here. It doesn&#8217;t play a key role in this article.</p>
<h2>Problem</h2>
<p>As you can guess, none of the solutions mentioned in the guide work for this scenario. The problem here is that I can&#8217;t provide a default option because the host vary depending on external variables. Also, I don&#8217;t want to manually pass the host option each time I generate an URL because it would require to pass the request object as email argument each time.</p>
<p>I tried at least 5 different solutions in the past but, unfortunately, each of them has some problem. The &#8220;almost perfect one&#8221; was to store the request object as an <code>ApplicationController</code> class variable each time a visitor requested a page, unfortunately this solution didn&#8217;t work in a multithreaded environment. The same issue affect an other <a href="http://pivotallabs.com/users/nick/blog/articles/281-how-i-learned-to-stop-hating-and-love-action-mailer">similar solution based on Ruby global variables</a>.</p>
<h2>Solution</h2>
<p>The final solution I worked on is now <a href="http://github.com/weppos/actionmailer_with_request">available as a plugin</a>. I haven&#8217;t written any test yet because I just extracted it from a real world application. The plugin provides the following features:</p>
<ol>
<li>It&#8217;s thread-safe</li>
<li>Makes the request context available to action mailer</li>
<li>Automatically extracts request host and port and pass them as default_url_options</li>
<li>Works with other existing default_url_options</li>
</ol>
<p>If you just need it, install the plugin and enjoy the power of Rails.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">ruby script/plugin install git://github.com/weppos/actionmailer_with_request.git</div></td></tr></tbody></table></div>
<p>If you want to know something more about how it works, continue to read.</p>
<p>The idea behind the plugin is to store the request instance somewhere and then access it from <code>ActionMailer</code>.</p>
<p>The first part of the problem is quite simple. The only thread-safe place where the instance can be saved is the current thread itself. This is almost straightforward, you just need to append a new <code>before_filter</code> at the lowest-level of your application, that is <code>ApplicationController</code>.</p>
<div class="codecolorer-container text default code-ruby" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">module ControllerMixin<br />
<br />
&nbsp; def self.included(base)<br />
&nbsp; &nbsp; base.class_eval do<br />
&nbsp; &nbsp; &nbsp; before_filter :store_request<br />
&nbsp; &nbsp; end<br />
&nbsp; end<br />
<br />
&nbsp; def store_request<br />
&nbsp; &nbsp; Thread.current[:request] = request<br />
&nbsp; end<br />
<br />
end</div></td></tr></tbody></table></div>
<p>The second part of the problem is this quite complex because:</p>
<ol>
<li><code>ActionMailer::Base.default_url_options</code> is expected to be a <code>Hash</code> and it&#8217;s automatically initialized to an empty <code>Hash</code></li>
<li><code>ActionMailer::Base.default_url_options</code> is a class variable and is shared across the entire application.</li>
</ol>
<p>You need a way to convert <code>Hash</code> value into a runtime-evaluated expression. Of course a <code>lambda</code> would be perfect, but <code>ActionMailer::Base.default_url_options</code> can&#8217;t be a lambda!</p>
<p>For this reason I created an options proxy taking advantage of Ruby <strong>duck typing</strong>. <code>default_url_options</code> doesn&#8217;t necessary need to be a <code>Hash</code>, in order to work it just need to <strong>acts like a <code>Hash</code></strong>.</p>
<p>The <code>OptionsProxy</code> class is basically a proxy for a <code>Hash</code> instance. The only different between the <code>Hash</code> class and <code>OptionsProxy</code> is that the latter merges some default values to the base <code>Hash</code> each time on each method call. Where do these default values come from? But from the <code>OptionsProxy.defaults</code> labmda, of course!</p>
<p>Ok, let me show you an example.</p>
<div class="codecolorer-container text default code-ruby" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">hash = { :foo =&gt; &quot;1&quot; }<br />
hash.keys # =&gt; [:foo]<br />
<br />
hash = OptionsProxy.new({ :foo =&gt; &quot;1&quot; })<br />
OptionsProxy.defaults = lambda { Hash.new }<br />
hash.keys # =&gt; [:foo]<br />
OptionsProxy.defaults = lambda { Hash.new(:bar =&gt; 2) }<br />
hash.keys # =&gt; [:foo, :bar]</div></td></tr></tbody></table></div>
<p>Now let&#8217;s go back to our Rails application. Each time a method is called on the <code>OptionsProxy</code> instance, <code>OptionsProxy</code> automatically merges the result of <code>OptionsProxy.defaults</code> and finally executes the method on the resulting Hash. Because <code>OptionsProxy.defaults</code> is evaluated at runtime, you can access the current thread and read extract the default url options from the request context.</p>
<div class="codecolorer-container text default code-ruby" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br />23<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">class OptionsProxy<br />
<br />
&nbsp; mattr_accessor :defaults<br />
<br />
&nbsp; self.defaults = lambda do<br />
&nbsp; &nbsp; host = Thread.current[:request].try(:host) || &quot;www.example.com&quot;<br />
&nbsp; &nbsp; port = Thread.current[:request].try(:port) || 80<br />
<br />
&nbsp; &nbsp; returning({}) do |params|<br />
&nbsp; &nbsp; &nbsp; params[:host] = host<br />
&nbsp; &nbsp; &nbsp; params[:port] = port if port != 80<br />
&nbsp; &nbsp; end<br />
&nbsp; end<br />
<br />
&nbsp; def initialize(params = {})<br />
&nbsp; &nbsp; @params = params<br />
&nbsp; end<br />
<br />
&nbsp; def method_missing(name, *args, &amp;block)<br />
&nbsp; &nbsp; @params.merge(defaults.call).send(name, *args, &amp;block)<br />
&nbsp; end<br />
<br />
end</div></td></tr></tbody></table></div>
<p>The last step is as easy as drinking a glass of water. You need to replace the <code>ActionMailer::Base.default_url_options</code> <code>Hash</code> with an <code>OptionsProxy</code> instance. This can be done in any Rails environment file, but because I wanted the plugin to be atomic I decided to let the plugin inject itself into <code>ActionMailer</code>.</p>
<div class="codecolorer-container text default code-ruby" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">module MailerMonkeyPatch<br />
<br />
&nbsp; def self.included(base)<br />
&nbsp; &nbsp; base.default_url_options = ActionMailerWithRequest::OptionsProxy.new(base.default_url_options)<br />
&nbsp; end<br />
<br />
end</div></td></tr></tbody></table></div>
<p>The final result is <a href="http://github.com/weppos/actionmailer_with_request">available here</a>. Feel free to post here your feedback. Patches welcome.</p>
<p>Related posts<ol>
<li><a href='http://www.simonecarletti.com/blog/2010/01/ruby-superstruct/' rel='bookmark' title='Ruby SuperStruct: enhanced version of the standard Ruby Struct'>Ruby SuperStruct: enhanced version of the standard Ruby Struct</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.simonecarletti.com/blog/2009/10/actionmailer-and-host-value/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
	</channel>
</rss>

