eMaximeWeb Development & Ruby on Rails bloghttps://emaxime.com/2018-10-21T00:00:00+02:00Maxime GarciaHow to uninstall all Ruby gemshttps://emaxime.com/2018/how-to-uninstall-all-ruby-gems.html2018-10-21T00:00:00+02:002021-03-06T11:24:48+01:00Maxime Garcia<p>Sometimes you want to remove all installed gems from your computer.
This was the case for me when I upgraded my projects from Ruby 2.5.1 to 2.5.3 in order to clean
things up in 2.5.1 and regain disk space. </p>
<p>Lots of Google entries for such a search point to:</p>
<div class="highlight"><pre class="highlight shell"><code>gem list <span class="nt">--no-version</span> | xargs gem uninstall <span class="nt">-aIx</span>
</code></pre></div>
<p>But you end up with this kind of error:</p>
<div class="highlight"><pre class="highlight shell"><code>ERROR: While executing gem ... <span class="o">(</span>Gem::InstallError<span class="o">)</span>
gem <span class="s2">"bigdecimal"</span> cannot be uninstalled because it is a default gem
</code></pre></div>
<p>Playing Whac-A-Mole filtering default gems:</p>
<div class="highlight"><pre class="highlight shell"><code>gem list <span class="nt">--no-version</span> | <span class="nb">grep</span> <span class="nt">-vE</span> <span class="s2">"^(bigdecimal|bundler|cmath|csv|date|dbm|etc|fcntl|fiddle|fileutils|gdbm|io-console|ipaddr|openssl|psych|rdoc|scanf|sdbm|stringio|strscan|webrick|zlib)$"</span> | xargs gem uninstall <span class="nt">-aIx</span>
</code></pre></div>
<h2>The final command</h2>
<p>Since Rubygems 2.1, without gem names, it just removes all of them! So it’s simply:</p>
<div class="highlight"><pre class="highlight shell"><code>gem uninstall <span class="nt">-aIx</span>
</code></pre></div>
<p>Details of options:</p>
<ul>
<li><code>-a</code> Uninstall all matching versions</li>
<li><code>-I</code> Ignore dependency requirements while uninstalling</li>
<li><code>-x</code> Uninstall applicable executables without confirmation</li>
</ul>
<p>And voilà.</p>
Adding a staging environment to Railshttps://emaxime.com/2014/adding-a-staging-environment-to-rails.html2014-09-29T00:00:00+02:002021-03-06T11:24:48+01:00Maxime Garcia<p>A staging environment is meant to track as closely as possible the production
environment in order for your app to be entirely tested under the condition of
production. Let’s dig into this. </p>
<p>It must be a perfect isolated copy of production, including its data. Everything that happen in production should happen in staging.</p>
<h2>Why having a staging environment ?</h2>
<p>A staging environment is a perfect place to battle test your code under the
conditions of production, but with the advantage that you can crash it, hard.
Very useful, especially when you handle a big code migration, such as a Rails
version update, a change of background processing system, refactoring, new
features…</p>
<p>You’ll be able to test your deployment process. You’ll be able to run your
ActiveRecord migrations against production data, to see how much time it would
take, and how your app behaves while.</p>
<p>It can be a place where you elaborate and test your software updates. You want
to upgrade your Ruby version from 1.9.3 to 2.1.3, you can see the full impact on
your app. You can elaborate the steps needed to upgrade the production
environment, and test them, again and again until everything is smooth. Same
thing for every piece of your stack: ElasticSearch… And moreover, outside of
software updates, you can test what happens if your stop your ElasticSearch
cluster, kill a MongoDB replica set…</p>
<p>It also allows you to launch a benchmark suite hitting your full stack, from
Nginx, Passenger / Unicorn… to MySQL / MongoDB, ElasticSearch…</p>
<p>En bref, you deploy your branch into staging, and you’re more peaceful when you
merge it into master and deploy it into production.</p>
<h2>Where to place it ?</h2>
<p>In an ideal world, you have a separate infrastructure for staging. But you can
have one or a couple of dedicated servers / VMs, with everything on them. That’s
more than enough.</p>
<p>You can setup staging alongside production, but it’s less safe with every piece
shared between both staging and production. Make sure not to overload your DB
server with staging requests, that could kill performance of your production !
And dealing with your softwares’ version would be quite tight, if you plan to
diverge for testing. It’s enough if you only plan to battle test your code, but
have the limitation in mind.</p>
<h2>The URL</h2>
<p>You have to choose an URL. Here are some examples if your app domain is
<code>myapphq.com</code>:</p>
<ul>
<li><code>staging.myapphq.com</code></li>
<li><code>myapphq.net</code></li>
<li><code>myapphq.info</code></li>
<li><code>myapphq.com:4242</code></li>
<li><code>myapphq-staging.com</code></li>
</ul>
<p>And if you use subdomains to handle multi-tenancy, considering
<code>customer1.myapphq.com</code>:</p>
<ul>
<li><code>customer1.staging.myapphq.com</code></li>
<li><code>customer1.myapphq.net</code></li>
<li><code>customer1.myapphq.info</code></li>
<li><code>customer1.myapphq.com:4242</code></li>
<li><code>customer1.myapphq-staging.com</code></li>
</ul>
<p>Don’t forget about SSL. If you have SSL in production, you should have SSL in
staging ! You might need an extra SSL certificate, depending on the URLs you
choose. Have in mind that wildcard certificate on <code>*.domain.com</code> works for
<code>bar.domain.com</code>, but doesn’t work for <code>foo.bar.domain.com</code>; you need a wildcard
certificate for <code>*.bar.domain.com</code>.</p>
<h2>Let’s start</h2>
<p>The first step is to duplicate <code>config/environments/production.rb</code> into
<code>config/environments/staging.rb</code> and adapt it.</p>
<p>Look into all your <code>config/*.yml</code> files to add a staging environment:
<code>database.yml</code>… I strongly advise you to have different credentials and
namespaces for staging. It can spare you an “I deleted some (or worst all)
production data” when working on staging. This is true for the database, Amazon
S3… For Redis, consider using a different database (<code>/1</code> instead of <code>/0</code>) and
a namespace including the environment for the extra safety.</p>
<p>If you use Capistrano 3, multi-stage is included. For Capistrano 2, there is a
gem to add. Duplicate <code>config/deploy/production.rb</code> into
<code>config/deploy/staging.rb</code> and adapt it. It may be the occasion to move stage
specific settings from <code>config/deploy.rb</code> to the stage files.</p>
<p>Finally, search your code for <code>Rails.env.production?</code> and more generally
<code>production</code>.</p>
<h2>The data</h2>
<p>The idea is to have the production data loaded into your staging environment.</p>
<p>Create a <code>lib/tasks/dump.rake</code> file:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="nb">require</span> <span class="s2">"erb"</span>
<span class="nb">require</span> <span class="s2">"yaml"</span>
<span class="n">namespace</span> <span class="ss">:dump</span> <span class="k">do</span>
<span class="n">desc</span> <span class="s2">"Fails if FILE doesn’t exists"</span>
<span class="n">task</span> <span class="ss">:barrier</span> <span class="k">do</span>
<span class="n">file</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">[</span><span class="s2">"FILE"</span><span class="p">]</span>
<span class="k">raise</span> <span class="s2">"Need a FILE"</span> <span class="k">unless</span> <span class="n">file</span>
<span class="no">File</span><span class="p">.</span><span class="nf">exists?</span><span class="p">(</span><span class="n">file</span><span class="p">)</span> <span class="n">or</span> <span class="k">raise</span> <span class="s2">"No file found (path given by FILE)"</span>
<span class="k">end</span>
<span class="n">desc</span> <span class="s2">"Retrieve the dump file"</span>
<span class="n">task</span> <span class="ss">:retrieve</span> <span class="k">do</span>
<span class="n">remote</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">[</span><span class="s2">"REMOTE"</span><span class="p">]</span>
<span class="k">raise</span> <span class="s2">"Need a REMOTE file"</span> <span class="k">unless</span> <span class="n">remote</span>
<span class="n">file</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">[</span><span class="s2">"FILE"</span><span class="p">]</span>
<span class="k">raise</span> <span class="s2">"Need a FILE"</span> <span class="k">unless</span> <span class="n">file</span>
<span class="c1"># here you copy remote into file</span>
<span class="c1"># via scp (prefered) or http GET</span>
<span class="n">do_whatever_is_needed</span> <span class="n">or</span> <span class="k">raise</span> <span class="s2">"Can’t retrieve </span><span class="si">#{</span><span class="n">remote</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="n">desc</span> <span class="s2">"Export the database"</span>
<span class="n">task</span> <span class="ss">:export</span> <span class="k">do</span>
<span class="n">file</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">[</span><span class="s2">"FILE"</span><span class="p">]</span>
<span class="k">raise</span> <span class="s2">"Need a FILE"</span> <span class="k">unless</span> <span class="n">file</span>
<span class="n">env</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">[</span><span class="s2">"RAILS_ENV"</span><span class="p">]</span>
<span class="k">raise</span> <span class="s2">"Need a RAILS_ENV"</span> <span class="k">unless</span> <span class="n">env</span>
<span class="n">db_config</span> <span class="o">=</span> <span class="n">current_db_config</span><span class="p">(</span><span class="n">env</span><span class="p">)</span>
<span class="nb">system</span> <span class="s2">"</span><span class="si">#{</span><span class="n">mysqldump</span><span class="p">(</span><span class="n">db_config</span><span class="p">)</span><span class="si">}</span><span class="s2"> | gzip -c > </span><span class="si">#{</span><span class="n">file</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="n">desc</span> <span class="s2">"Import a database"</span>
<span class="n">task</span> <span class="ss">:import</span> <span class="o">=></span> <span class="ss">:barrier</span> <span class="k">do</span>
<span class="n">file</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">[</span><span class="s2">"FILE"</span><span class="p">]</span>
<span class="k">raise</span> <span class="s2">"Need a FILE"</span> <span class="k">unless</span> <span class="n">file</span>
<span class="n">env</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">[</span><span class="s2">"RAILS_ENV"</span><span class="p">]</span>
<span class="k">raise</span> <span class="s2">"Need a RAILS_ENV"</span> <span class="k">unless</span> <span class="n">env</span>
<span class="k">raise</span> <span class="s2">"Import on production is forbidden"</span> <span class="k">if</span> <span class="n">env</span> <span class="o">==</span> <span class="s2">"production"</span>
<span class="n">db_config</span> <span class="o">=</span> <span class="n">current_db_config</span><span class="p">(</span><span class="n">env</span><span class="p">)</span>
<span class="nb">system</span> <span class="s2">"gzip -d -c </span><span class="si">#{</span><span class="n">file</span><span class="si">}</span><span class="s2"> | </span><span class="si">#{</span><span class="n">mysql</span><span class="p">(</span><span class="n">db_config</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">current_db_config</span><span class="p">(</span><span class="n">env</span><span class="p">)</span>
<span class="no">YAML</span><span class="o">::</span><span class="nb">load</span><span class="p">(</span><span class="no">ERB</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="no">IO</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="no">File</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="no">File</span><span class="p">.</span><span class="nf">dirname</span><span class="p">(</span><span class="kp">__FILE__</span><span class="p">),</span> <span class="s2">"../../config/database.yml"</span><span class="p">))).</span><span class="nf">result</span><span class="p">)[</span><span class="n">env</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">mysql</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
<span class="n">sql_cmd</span><span class="p">(</span><span class="s2">"mysql"</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">mysqldump</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
<span class="n">sql_cmd</span><span class="p">(</span><span class="s2">"mysqldump"</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span> <span class="o">+</span> <span class="s2">" --add-drop-table --extended-insert=TRUE --disable-keys --complete-insert=FALSE --triggers=FALSE"</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">sql_cmd</span><span class="p">(</span><span class="n">sql_command</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
<span class="s2">""</span><span class="p">.</span><span class="nf">tap</span> <span class="k">do</span> <span class="o">|</span><span class="n">cmd</span><span class="o">|</span>
<span class="n">cmd</span> <span class="o"><<</span> <span class="n">sql_command</span>
<span class="n">cmd</span> <span class="o"><<</span> <span class="s2">" "</span>
<span class="n">cmd</span> <span class="o"><<</span> <span class="s2">"-u</span><span class="si">#{</span><span class="n">config</span><span class="p">[</span><span class="s2">"username"</span><span class="p">]</span><span class="si">}</span><span class="s2"> "</span> <span class="k">if</span> <span class="n">config</span><span class="p">[</span><span class="s2">"username"</span><span class="p">]</span>
<span class="n">cmd</span> <span class="o"><<</span> <span class="s2">"-p</span><span class="si">#{</span><span class="n">config</span><span class="p">[</span><span class="s2">"password"</span><span class="p">]</span><span class="si">}</span><span class="s2"> "</span> <span class="k">if</span> <span class="n">config</span><span class="p">[</span><span class="s2">"password"</span><span class="p">]</span>
<span class="n">cmd</span> <span class="o"><<</span> <span class="s2">"-h</span><span class="si">#{</span><span class="n">config</span><span class="p">[</span><span class="s2">"host"</span><span class="p">]</span><span class="si">}</span><span class="s2"> "</span> <span class="k">if</span> <span class="n">config</span><span class="p">[</span><span class="s2">"host"</span><span class="p">]</span>
<span class="n">cmd</span> <span class="o"><<</span> <span class="s2">"-P</span><span class="si">#{</span><span class="n">config</span><span class="p">[</span><span class="s2">"port"</span><span class="p">]</span><span class="si">}</span><span class="s2"> "</span> <span class="k">if</span> <span class="n">config</span><span class="p">[</span><span class="s2">"port"</span><span class="p">]</span>
<span class="n">cmd</span> <span class="o"><<</span> <span class="s2">"--default-character-set utf8 "</span>
<span class="n">cmd</span> <span class="o"><<</span> <span class="n">config</span><span class="p">[</span><span class="s2">"database"</span><span class="p">]</span> <span class="k">if</span> <span class="n">config</span><span class="p">[</span><span class="s2">"database"</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p>Here is how to use it:</p>
<div class="highlight"><pre class="highlight shell"><code><span class="c"># on production</span>
<span class="nv">RAILS_ENV</span><span class="o">=</span>production <span class="nv">FILE</span><span class="o">=</span>/tmp/dump_production.sql.gz bin/rake dump:export
<span class="c"># on staging</span>
<span class="nv">RAILS_ENV</span><span class="o">=</span>staging <span class="nv">REMOTE</span><span class="o">=</span>app.myapp.com:/tmp/dump_production.sql.gz <span class="nv">FILE</span><span class="o">=</span>/tmp/dump_production.sql.gz bin/rake dump:retrieve dump:barrier maintenance:enable db:drop db:create dump:import db:migrate maintenance:restart maintenance:disable
</code></pre></div>
<p>The export process is simply :</p>
<ul>
<li><code>dump:export</code> simply use mysqldump to export the data.</li>
</ul>
<p>The full import process is :</p>
<ul>
<li><code>dump:retrieve</code> gets the dump file from production (not necessary if same
server, or if the dump is stored on a shared file system).</li>
<li><code>dump:barrier</code> fails if there is no file to import. This avoids dropping the
database if we can’t import right after.</li>
<li><code>maintenance:enable</code> starts the maintenance mode.</li>
<li><code>db:drop</code> drops the current staging database.</li>
<li><code>db:create</code> creates a fresh new empty staging database.</li>
<li><code>dump:import</code> does import the production data.</li>
<li><code>db:migrate</code> runs the needed migrations. Your code deployed in staging may be
in advance from the production code: there may be some migrations not applied
since we just imported the production database.</li>
<li><code>maintenance:restart</code> restarts the app.</li>
<li><code>maintenance:disable</code> ends the maintenance mode.</li>
</ul>
<p>You noticed the <code>maintenance:enable</code> and <code>maintenance:disable</code> tasks. I like to
add them to prevent access to the staging app, because during the import, weird
things can happen as your data is not fully loaded yet, tables are missing… Here
is the <code>lib/tasks/maintenance.rake</code> file:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">namespace</span> <span class="ss">:maintenance</span> <span class="k">do</span>
<span class="no">MAINTENANCE_FILE</span> <span class="o">=</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">root</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s2">"public/system/maintenance.html"</span><span class="p">)</span>
<span class="no">RESTART_FILE</span> <span class="o">=</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">root</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s2">"tmp/restart"</span><span class="p">)</span>
<span class="n">desc</span> <span class="s2">"Start the maintenance mode"</span>
<span class="n">task</span> <span class="ss">:enable</span> <span class="o">=></span> <span class="ss">:environment</span> <span class="k">do</span>
<span class="k">if</span> <span class="o">!</span><span class="no">File</span><span class="p">.</span><span class="nf">exists?</span><span class="p">(</span><span class="no">MAINTENANCE_FILE</span><span class="p">)</span>
<span class="n">dir</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">dirname</span><span class="p">(</span><span class="no">MAINTENANCE_FILE</span><span class="p">)</span>
<span class="nb">system</span> <span class="s2">"mkdir -p </span><span class="si">#{</span><span class="n">dir</span><span class="si">}</span><span class="s2"> && echo </span><span class="se">\"</span><span class="s2">Website is on maintenance. We’ll be back in a few seconds...</span><span class="se">\"</span><span class="s2"> > </span><span class="si">#{</span><span class="no">MAINTENANCE_FILE</span><span class="si">}</span><span class="s2">"</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s2">"[MAINTENANCE] App is now DOWN"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">desc</span> <span class="s2">"Stop the maintenance mode"</span>
<span class="n">task</span> <span class="ss">:disable</span> <span class="o">=></span> <span class="ss">:environment</span> <span class="k">do</span>
<span class="k">if</span> <span class="no">File</span><span class="p">.</span><span class="nf">exists?</span><span class="p">(</span><span class="no">MAINTENANCE_FILE</span><span class="p">)</span>
<span class="k">if</span> <span class="no">File</span><span class="p">.</span><span class="nf">unlink</span><span class="p">(</span><span class="no">MAINTENANCE_FILE</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s2">"[MAINTENANCE] App is now UP"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">desc</span> <span class="s2">"Restart the application"</span>
<span class="n">task</span> <span class="ss">:restart</span> <span class="o">=></span> <span class="ss">:environment</span> <span class="k">do</span>
<span class="no">FileUtils</span><span class="p">.</span><span class="nf">touch</span><span class="p">(</span><span class="no">RESTART_FILE</span><span class="p">)</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s2">"[MAINTENANCE] App has restarted"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p>And here is the part to add into the <code>server</code> part of your app in your
<code>nginx.conf</code>:</p>
<div class="highlight"><pre class="highlight nginx"><code><span class="k">error_page</span> <span class="mi">503</span> <span class="s">@maintenance</span><span class="p">;</span>
<span class="k">location</span> <span class="s">@maintenance</span> <span class="p">{</span>
<span class="kn">try_files</span> <span class="n">/503.html</span> <span class="n">/system/maintenance.html</span> <span class="mi">503</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="s">(-f</span> <span class="nv">$document_root</span><span class="n">/system/maintenance.html</span><span class="s">)</span> <span class="p">{</span>
<span class="kn">return</span> <span class="mi">503</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>The maintenance thing is pretty standard. Feel free to adapt or to use a
dedicated gem (for Capistrano 3 for example).</p>
<p>There are other thing to consider. If you have attachments, you have to take
care of them. If they are stored locally, you’ll have to export them from
production to import them into staging. If they are stored on Amazon S3, you can
copy them as well every night from the production bucket to the staging bucket.
Or you can setup an advanced workflow: all PUT, DELETE… hit the staging bucket
(allowing them to fail, especially for the DELETE), but for the GET requests, it
firstly tries the staging bucket, and if nothing is found, it falls back to
getting the object from the production bucket. It spares you the copy step, and
the extra cost of storage.</p>
<h3>Magic happens at night</h3>
<p>Time to automate things: every night, the staging gets a fresh start via a cron.</p>
<p>I came to like the <a href="https://github.com/javan/whenever">whenever</a> gem to handle the app’s crons. Refer to
the gem documentation for how to setup a multi-stage configuration (with
Capistrano). Here is an example of config:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">set</span> <span class="ss">:production_dump_file</span><span class="p">,</span> <span class="s2">"/tmp/production_dump_daily.sql.gz"</span>
<span class="n">set</span> <span class="ss">:remote_dump_file</span><span class="p">,</span> <span class="s2">"dumper@app.mydomain.com:/tmp/production_dump_daily.sql.gz"</span>
<span class="n">set</span> <span class="ss">:staging_dump_file</span><span class="p">,</span> <span class="s2">"/tmp/production_dump_daily.sql.gz"</span>
<span class="k">if</span> <span class="n">environment</span> <span class="o">==</span> <span class="s2">"production"</span> <span class="o">||</span> <span class="n">environment</span> <span class="o">==</span> <span class="s2">"staging"</span>
<span class="c1"># normal crons goes here</span>
<span class="k">end</span>
<span class="k">if</span> <span class="n">environment</span> <span class="o">==</span> <span class="s2">"production"</span>
<span class="c1"># Export daily dump</span>
<span class="n">every</span> <span class="ss">:day</span><span class="p">,</span> <span class="ss">at: </span><span class="s2">"12:00am"</span> <span class="k">do</span>
<span class="n">command</span> <span class="s2">"cd </span><span class="si">#{</span><span class="n">path</span><span class="si">}</span><span class="s2"> && </span><span class="si">#{</span><span class="n">environment_variable</span><span class="si">}</span><span class="s2">=</span><span class="si">#{</span><span class="n">environment</span><span class="si">}</span><span class="s2"> FILE=</span><span class="si">#{</span><span class="n">production_dump_file</span><span class="si">}</span><span class="s2"> bin/rake dump:export"</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">if</span> <span class="n">environment</span> <span class="o">==</span> <span class="s2">"staging"</span>
<span class="c1"># Import daily dump</span>
<span class="n">every</span> <span class="ss">:day</span><span class="p">,</span> <span class="ss">at: </span><span class="s2">"12:30am"</span> <span class="k">do</span>
<span class="n">command</span> <span class="s2">"cd </span><span class="si">#{</span><span class="n">path</span><span class="si">}</span><span class="s2"> && </span><span class="si">#{</span><span class="n">environment_variable</span><span class="si">}</span><span class="s2">=</span><span class="si">#{</span><span class="n">environment</span><span class="si">}</span><span class="s2"> REMOTE=</span><span class="si">#{</span><span class="n">remote_dump_file</span><span class="si">}</span><span class="s2"> FILE=</span><span class="si">#{</span><span class="n">staging_dump_file</span><span class="si">}</span><span class="s2"> bin/rake dump:retrieve dump:barrier maintenance:enable db:drop db:create dump:import db:migrate maintenance:restart maintenance:disable"</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p>The export is done on production at 12:00am, and the import happens at 12:30am.
Adjust the import time according to the time taken by the export and the
transfer, plus an extra margin. It depends on your amount of data. Don’t forget
to run this outside any heavy processing period if any (like a statistics
processing tasks or a machine learning model update…).</p>
<h2>The outside world</h2>
<h3>Mails</h3>
<p>Let’s begin with your mailers. You can’t have your users to receive mails from
the staging environment. Imagine that you try to answer to something and they
get a notification about it.</p>
<p>If you don’t have SMTP servers, and you don’t want to use your mail provider
services, create a dedicated Gmail account, and configure it in your
<code>config/environments/staging.rb</code> file:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># Email configuration</span>
<span class="n">config</span><span class="p">.</span><span class="nf">action_mailer</span><span class="p">.</span><span class="nf">delivery_method</span> <span class="o">=</span> <span class="ss">:smtp</span>
<span class="n">config</span><span class="p">.</span><span class="nf">action_mailer</span><span class="p">.</span><span class="nf">smtp_settings</span> <span class="o">=</span> <span class="p">{</span>
<span class="ss">address: </span><span class="s2">"smtp.gmail.com"</span><span class="p">,</span>
<span class="ss">port: </span><span class="mi">587</span><span class="p">,</span>
<span class="ss">domain: </span><span class="s2">"gmail.com"</span><span class="p">,</span>
<span class="ss">authentication: </span><span class="s2">"login"</span><span class="p">,</span>
<span class="ss">enable_starttls_auto: </span><span class="kp">true</span><span class="p">,</span>
<span class="ss">user_name: </span><span class="s2">"my-app-staging@gmail.com"</span><span class="p">,</span>
<span class="ss">password: </span><span class="s2">"my-password"</span>
<span class="p">}</span>
</code></pre></div>
<p>Now, let’s hijack the mailers. The purpose is to send all the mails into a
dedicated mail account. It could be the above Gmail account, or a mailing list
or a private Google group that your developers subscribe to.</p>
<p>We’ll use the <a href="https://github.com/pboling/sanitize_email">sanitize_email</a> gem. Add it to your <code>Gemfile</code> and configure it in
your <code>config/environments/staging.rb</code> file:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">STAGING_EMAIL</span> <span class="o">=</span> <span class="s2">"my-app-staging@gmail.com"</span>
<span class="no">SanitizeEmail</span><span class="o">::</span><span class="no">Config</span><span class="p">.</span><span class="nf">configure</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
<span class="n">config</span><span class="p">[</span><span class="ss">:sanitized_to</span><span class="p">]</span> <span class="o">=</span> <span class="no">STAGING_EMAIL</span><span class="p">.</span><span class="nf">gsub</span><span class="p">(</span><span class="s2">"@"</span><span class="p">,</span> <span class="s2">"+to@"</span><span class="p">)</span>
<span class="n">config</span><span class="p">[</span><span class="ss">:sanitized_cc</span><span class="p">]</span> <span class="o">=</span> <span class="no">STAGING_EMAIL</span><span class="p">.</span><span class="nf">gsub</span><span class="p">(</span><span class="s2">"@"</span><span class="p">,</span> <span class="s2">"+cc@"</span><span class="p">)</span>
<span class="n">config</span><span class="p">[</span><span class="ss">:sanitized_bcc</span><span class="p">]</span> <span class="o">=</span> <span class="no">STAGING_EMAIL</span><span class="p">.</span><span class="nf">gsub</span><span class="p">(</span><span class="s2">"@"</span><span class="p">,</span> <span class="s2">"+bcc@"</span><span class="p">)</span>
<span class="n">config</span><span class="p">[</span><span class="ss">:use_actual_email_prepended_to_subject</span><span class="p">]</span> <span class="o">=</span> <span class="kp">true</span>
<span class="n">config</span><span class="p">[</span><span class="ss">:use_actual_environment_prepended_to_subject</span><span class="p">]</span> <span class="o">=</span> <span class="kp">true</span>
<span class="n">config</span><span class="p">[</span><span class="ss">:use_actual_email_as_sanitized_user_name</span><span class="p">]</span> <span class="o">=</span> <span class="kp">true</span>
<span class="n">config</span><span class="p">[</span><span class="ss">:activation_proc</span><span class="p">]</span> <span class="o">=</span> <span class="no">Proc</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">env</span><span class="p">.</span><span class="nf">staging?</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre></div>
<p>Note that this can also be applied to development, with or without a dedicated
gmail account, but with the refinement that you can send the emails to the
current git user: <code>git_user = `git config user.email`.squish</code></p>
<h3>Other stuff</h3>
<p>You have to think about the interactions with the outside world: the external
APIs, the different providers (including your subscription provider…).
Especially when thinking about the related data that come from production: you
may have tokens, client_ids… Are they valid in production ? Can it be used ?
Should you use those APIs in staging (if you cancel a subscription from staging,
uhoh or okay ?)</p>
<p>For the Social Connect (Facebook, Twitter, Google+, Github…), it’s okay, just
add the callback URLs of your staging env in their developers console, along
side with the production ones. But for using their APIs, it must be more
thoroughly examined. The stored tokens are valid, so if you publish on someone’s
wall from staging, it will work. Do you have to have a different app_id ? You
have probably answered to all those kind of question for your development
environment.</p>
<h2>Final note</h2>
<p>The staging environment is not just a tool, it should be a must stop for your
code before entering in production. As a serious piece of your workflow and
infrastructure, you have to monitor it, have exceptions be sent / tracked. Any
event happening in the staging environment is a forerunner announcement of an
issue in production. Better ruin the staging env than being sorry in prod.</p>
<p>And what about you ? How did you set up such an environment ?</p>
Revamping this bloghttps://emaxime.com/2013/revamping-this-blog.html2013-11-13T11:40:00+01:002021-03-06T11:24:48+01:00Maxime Garcia<p>Service announcement: the blog was revamped! I decided to make a few changes
and to resume writing articles.
</p>
<p>First of all, I moved the blog from Wordpress to a pure static one, generated
with the excellent <a href="http://middlemanapp.com">Middleman</a>. Articles are written in markdown, they have a
few metadata (date, tags…), there are templates, layouts, helpers, routing,
assets… And everything is compiled, minified on build. And the build is
deployed via rsync over ssh. And the site is served statically.</p>
<p>To have comments in a static website, I use <a href="http://www.disqus.com">Disqus</a>: it’s a piece of
javascript, easy to drop in the articles layout, that allows for commenters to
write, subscribe, be notified, star discussion, and for me to manage all of it.</p>
<p>I took advantage of the change to restyle the blog: simple, content first,
no more boring sidebar, responsive.</p>
<p>I’ll reserve some time to write articles again, on a regular basis.
And I’ll continue the <a href="/tags/enlarge-your-ruby.html">Enlarge Your Ruby</a> series of articles.</p>
ActiveCleaner to clean your fields in your Rails modelshttps://emaxime.com/2013/activecleaner-to-clean-your-fields-in-your-rails-models.html2013-03-14T10:40:00+01:002021-03-06T11:24:48+01:00Maxime Garcia<p>Some time ago, I released a tiny gem that helps me to clean some user generated
fields: <a href="https://github.com/maximeg/activecleaner">ActiveCleaner</a>.
</p>
<p>An app or a website allows user to enter some data through forms. In my models,
I was constantly doing this:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">before_validation</span> <span class="ss">:clean_title</span>
<span class="k">def</span> <span class="nf">clean_title</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">title</span> <span class="o">=</span> <span class="n">title</span><span class="p">.</span><span class="nf">squish</span> <span class="k">unless</span> <span class="n">title</span><span class="p">.</span><span class="nf">nil?</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">title</span> <span class="o">=</span> <span class="kp">nil</span> <span class="k">if</span> <span class="n">title</span><span class="p">.</span><span class="nf">blank?</span>
<span class="kp">true</span>
<span class="k">end</span>
</code></pre></div>
<p>Why ? Extra spaces mean extra storage. And it could ruin your indexes and your
sortings. And I want that blank fields turn into nil.</p>
<p>I was bored to write this same stuff all over the models, I decided to write a
tiny gem that does the job:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">Post</span>
<span class="kp">include</span> <span class="no">Mongoid</span><span class="o">::</span><span class="no">Document</span>
<span class="n">field</span> <span class="ss">:title</span>
<span class="n">clean</span> <span class="ss">:title</span><span class="p">,</span> <span class="ss">nilify: </span><span class="kp">true</span>
<span class="k">end</span>
</code></pre></div>
<p>And voilà. It runs as a before_validation callback. As for now, it supports
three cleaners:</p>
<ul>
<li><code>:string</code>, StringCleaner, the default one: cleans all the space characters.
It turns <code>" A \n \t title \t "</code> into <code>"A title"</code>.</li>
<li><code>:text</code>, TextCleaner: like <code>:string</code>, but preserves new lines (with a max
of two successive new lines). Useful when the field is rendered with the
<code>simple_format</code> Rails helper.</li>
<li><code>:markdown</code>, MarkdownCleaner: like <code>:text</code>, but preserves spaces in the
beginning of lines (the indentation). Useful for… markdown!</li>
</ul>
<p>You can write custom cleaners, and several more are to come. Don’t hesitate to
tell me the useful missing ones.</p>
<p>The <code>:nilify</code> is an option, that which default is false.
When true, it turns resulting blank string into nil.</p>
<p>The project pages:</p>
<ul>
<li>Github: <a href="https://github.com/maximeg/activecleaner">https://github.com/maximeg/activecleaner</a></li>
<li>RubyGems: <a href="https://rubygems.org/gems/activecleaner">https://rubygems.org/gems/activecleaner</a></li>
</ul>
EYR - Refactoring from good to great (and live)https://emaxime.com/2012/eyr-refactoring-from-good-to-great-and-live.html2012-11-23T16:37:00+01:002021-03-06T11:24:48+01:00Maxime Garcia<p>Friday afternoon, <a href="/tags/enlarge-your-ruby.html">Enlarge Your Ruby</a> is back.
So, is your Friday afternoon boring ? What about refactoring ? It’s time to
learn having fun doing that, turning pretty bad code into good one and even
into great one.</p>
<p>Ben Orenstein (<a href="https://twitter.com/r00k">@r00k</a>) did a great live refactoring
on stage at Aloha Ruby Conf in October 2012. Worth seeing it.
</p>
<blockquote>
<p>Most developers know enough about refactoring to write code that’s pretty
good. They create short methods, and classes with one responsibility. They’re
also familiar with a good handful of refactorings, and the code smells that
motivate them.</p>
<p>This talk is about the next level of knowledge: the things advanced developers
know that let them turn good code into great. Code that’s easy to read and a
breeze to change.</p>
<p>These topics will be covered solely by LIVE CODING; no slides. We’ll boldly
refactor right on stage, and pray the tests stay green. You might even learn
some vim tricks as well as an expert user shows you his workflow.</p>
</blockquote>
<p>Video files to download (720p… or only audio) are on
<a href="http://www.confreaks.com/videos/1233-aloharuby2012-refactoring-from-good-to-great">Confreaks</a>.</p>
EYR - Wrangling large Rails codebaseshttps://emaxime.com/2012/eyr-wrangling-large-rails-codebases.html2012-11-16T17:21:00+01:002021-03-06T11:24:48+01:00Maxime Garcia<p>This is the first post in <a href="/tags/enlarge-your-ruby.html">Enlarge Your Ruby</a>,
let’s start with an heavy one. Ruby on Rails gives a structure that helps
organize our code. But when the app grows, we drown with hundreds of controllers,
models, views. All this stuff is mixed. Tests take forever to run.</p>
<p>Let’s see how to Divide and Conquer with embedded gems and engines.
</p>
<p>Stephan Hagemann, working at <a href="http://pivotallabs.com/">Pivotal Labs</a>, shared about that during the
Rocky Mountain Ruby event, in September 2012:</p>
<blockquote>
<p>As Rails applications grow they see a lot of the same problems that any
enterprise level software project will see at some point: longer running test
suites, more complex code interaction, and a rising requirement to keep
“all that stuff” in your head at once.</p>
<p>Rails may have been a framework for single applications in the past, but it
nowadays has some features that allow you to tackle bigger projects with more
ease. We’ll cover Rails code structuring techniques like unbuilt gems and
engines that you can use to get faster test suites, cleaner structures, and
more flexible apps.</p>
</blockquote>
<p>Video files to download (720p… or only audio) are on <a href="http://www.confreaks.com/videos/1263-rockymtnruby2012-wrangling-large-rails-codebases">Confreaks</a>.</p>
<h3>Further reading</h3>
<ul>
<li><a href="https://github.com/shageman/the_next_big_thing">TheNextBigThing github repository</a> (the example in the conf)</li>
<li><a href="http://pivotallabs.com/users/shagemann/blog/articles/1994-migrating-from-a-single-rails-app-to-a-suite-of-rails-engines">Migrating from a single Rails app to a suite of Rails engines</a>
from Stephan Hagemann too</li>
</ul>
A series of Ruby videos each Friday that improves yourselfhttps://emaxime.com/2012/a-series-of-ruby-videos-each-friday-that-improves-yourself.html2012-11-14T22:05:00+01:002021-03-06T11:24:48+01:00Maxime Garcia<p>Friday afternoon, time passes slowly… What about watching a good talk or
conference ? Something technical. Something great. Something about Ruby, Rails
or Web development in general. Something that improves yourself.
</p>
<p>These days, I’ve been looking for Redis with Rails use case talks. Though I
heard about a few Ruby conferences, I discovered that there are plenty of these
events, worldwide, nearly each month.</p>
<p>These conferences are often filmed and many of them are quite interesting for
the Ruby developer that wants to learn new things.</p>
<p>I’m going to share the interesting ones I’ve found, ideally with a post a week
on Friday.</p>
<p>I’m still hesitating about the naming : “Enlarge your knowledge” or
“Enlarge your skills”. Tell me what do you think.</p>
<p>See you Friday !</p>
Managing (private) settings with SettingsLogichttps://emaxime.com/2012/managing-private-settings-with-settingslogic.html2012-11-14T19:51:00+01:002021-03-06T11:24:48+01:00Maxime Garcia<p>In a Ruby on Rails app, there are settings. Many settings. Some can vary from
development to production. Some are private as they may be passwords,
secret tokens…</p>
<p>This is how I deal with those, using the tiny lib SettingsLogic.
</p>
<p>A French political party (far and ultra left wing, Gasp!) recently released the
<a href="https://github.com/LePartiDeGauche/pgonror">source code</a> of their main website under GPL license on Github.
As it is made with Rails, I was curious. I found a few flaws: they
<a href="https://github.com/LePartiDeGauche/pgonror/issues/2">unintentionally leaked</a> some credentials and security settings.
This is the occasion for talking about SettingsLogic, and how to deal with
sensitive settings.</p>
<h2>SettingsLogic</h2>
<p><a href="https://github.com/binarylogic/settingslogic">SettingsLogic</a> lets you have yaml files to store your settings.</p>
<p>First of all, let’s add the gem in the <code>Gemfile</code>:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">gem</span> <span class="s2">"settingslogic"</span>
</code></pre></div>
<p>Let’s create the settings class. In <code>app/models/settings.rb</code>:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># encoding: utf-8</span>
<span class="k">class</span> <span class="nc">Settings</span> <span class="o"><</span> <span class="no">Settingslogic</span>
<span class="n">source</span> <span class="s2">"</span><span class="si">#{</span><span class="no">Rails</span><span class="p">.</span><span class="nf">root</span><span class="si">}</span><span class="s2">/config/settings.yml"</span>
<span class="n">namespace</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">env</span>
<span class="k">end</span>
</code></pre></div>
<p>Pretty simple. I let you create the corresponding yaml file as specified in
the class. Here is a template with an example:</p>
<div class="highlight"><pre class="highlight yaml"><code><span class="na">defaults</span><span class="pi">:</span> <span class="nl">&defaults</span>
<span class="na">pagination</span><span class="pi">:</span>
<span class="na">comments</span><span class="pi">:</span> <span class="m">10</span>
<span class="na">development</span><span class="pi">:</span>
<span class="s"><<</span><span class="pi">:</span> <span class="nv">*defaults</span>
<span class="na">test</span><span class="pi">:</span>
<span class="s"><<</span><span class="pi">:</span> <span class="nv">*defaults</span>
<span class="na">production</span><span class="pi">:</span>
<span class="s"><<</span><span class="pi">:</span> <span class="nv">*defaults</span>
</code></pre></div>
<p>It lets you managing different values according to the environment. To use it
where you need to, just call:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Settings</span><span class="p">.</span><span class="nf">pagination</span><span class="p">.</span><span class="nf">comments</span>
<span class="c1"># An example:</span>
<span class="vi">@post</span><span class="p">.</span><span class="nf">comments</span><span class="p">.</span><span class="nf">desc</span><span class="p">(</span><span class="ss">:published_at</span><span class="p">).</span><span class="nf">page</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:page</span><span class="p">]).</span><span class="nf">per</span><span class="p">(</span><span class="no">Settings</span><span class="p">.</span><span class="nf">pagination</span><span class="p">.</span><span class="nf">comments</span><span class="p">)</span>
</code></pre></div>
<p>Voilà, the basics. Now let’s see how I manage the settings_private.yml file.</p>
<h2>The private file</h2>
<p>I’m used to separate general settings (pagination values…) from private
settings (credentials, secret tokens…) as I don’t want the later ones to be
included in my CVS that’s backed on Github or Bitbucket. These credentials
are plenty: SMTP, Amazon S3, Facebook, Twitter… Besides the many tokens which
include the “pepper” of Devise. And the great forgotten: the <code>secret_token</code>
(located in <code>config/initializers/secret_token.rb</code>) that secures your cookies !</p>
<p>Doing that in <code>app/models/settings_private.rb</code>:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># encoding: utf-8</span>
<span class="k">class</span> <span class="nc">SettingsPrivate</span> <span class="o"><</span> <span class="no">Settingslogic</span>
<span class="n">source</span> <span class="s2">"</span><span class="si">#{</span><span class="no">Rails</span><span class="p">.</span><span class="nf">root</span><span class="si">}</span><span class="s2">/config/settings_private.yml"</span>
<span class="n">namespace</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">env</span>
<span class="k">end</span>
</code></pre></div>
<p>And you have a <code>settings_private.yml</code> file. An idea is to have a
<code>settings_private-sample.yml</code> that is synced with the real one.
I use dummy values but the keys are synced, and it appears in my CVS.
It’s cleaner and it permits to quickly generate the real one without browsing
the source code of the whole app.</p>
<p>As it actually contains the stuff I use in dev and prod, I prevent the
<code>settings_private.yml</code> file from being in my CVS by adding it to the
<code>.gitignore</code> file:</p>
<div class="highlight"><pre class="highlight shell"><code><span class="c"># Do not include by mistake the private settings</span>
/config/settings_private.yml
</code></pre></div>
<p>If you use capistrano, you can add a task that setup your private file while
deploying. In my deploy path, next to the <code>releases</code> and <code>shared</code> directories,
I create a <code>private_files</code> directory and manually place a <code>settings_private.yml</code>
file that is setup for production. And I add a dedicated task in the
<code>config/deploy.rb</code> capistrano’s file. It creates a symbolic link in the right
place in the release path to the private file:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">namespace</span> <span class="ss">:deploy</span> <span class="k">do</span>
<span class="n">task</span> <span class="ss">:setup_private_files</span> <span class="k">do</span>
<span class="p">[</span><span class="s2">"settings_private.yml"</span><span class="p">,</span> <span class="s2">"mongoid.yml"</span><span class="p">].</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">file</span><span class="o">|</span>
<span class="n">run</span> <span class="s2">"rm -fr </span><span class="si">#{</span><span class="n">release_path</span><span class="si">}</span><span class="s2">/config/</span><span class="si">#{</span><span class="n">file</span><span class="si">}</span><span class="s2">"</span>
<span class="n">run</span> <span class="s2">"cd </span><span class="si">#{</span><span class="n">release_path</span><span class="si">}</span><span class="s2">/config && ln -sf </span><span class="si">#{</span><span class="n">shared_path</span><span class="si">}</span><span class="s2">/../private_files/</span><span class="si">#{</span><span class="n">file</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">file</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">before</span> <span class="s2">"deploy:finalize_update"</span><span class="p">,</span> <span class="s2">"deploy:setup_private_files"</span>
</code></pre></div>
<p>You can see that I’m doing the same thing with my <code>mongoid.yml</code> or <code>database.yml</code>
files.</p>
<p>Enjoy a simple, more secure and deployment-proof way to deal with your settings,
even the secret ones.</p>
Hello, World!https://emaxime.com/2012/hello-world.html2012-04-03T21:00:00+02:002021-03-06T11:24:48+01:00Maxime Garcia<p>I decided to create a dedicated blog, in English, to talk about this framework
that I like more and more. Did you guess its name ? Yeah, Ruby on Rails.
</p>
<p>Concise, elegant, logical: it’s a pleasure to develop stuff so quickly.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">PostsController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="n">respond_to</span> <span class="ss">:html</span><span class="p">,</span> <span class="ss">:json</span>
<span class="k">def</span> <span class="nf">index</span>
<span class="vi">@posts</span> <span class="o">=</span> <span class="no">Post</span><span class="p">.</span><span class="nf">all</span><span class="p">.</span><span class="nf">page</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:page</span><span class="p">]).</span><span class="nf">per</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
<span class="n">respond_with</span> <span class="vi">@posts</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p>Most of all, what’s nice with Rails, it’s his community and the many great
projects that grow around it. So I’ll show some gems I use or want to use.
And of course, I’ll talk about plain Ruby too, from time to time.</p>
<p>See ya.</p>