tag:ryankinderman.net,2005:/tag/pluginkinderman.net : Tag plugin, everything about plugincode code code2009-11-04T16:58:32-08:00Typotag:ryankinderman.net,2005:Article/1102009-11-01T19:36:22-08:002009-11-04T16:58:32-08:00ryanIntroducing Spectie, a behavior-driven-development library for RSpec<p>I'm a firm believer in the importance of <a href="http://kinderman.net/articles/2007/11/18/testing-on-high-bottom-up-versus-top-down-test-driven-development">top-down</a> and behavior-driven development. I often start writing an integration test as the first step to implementing a story. When I started doing Rails development, the expressiveness of Ruby encouraged me to start building a DSL to easily express the way I most-often wrote integration tests. In the pre-<a href="http://rspec.info/">RSpec</a> days, this was just a subclass of ActionController::IntegrationTest that encapsulated the session management code to simplify authoring tests from the perspective of a single user. As the <a href="http://dannorth.net/introducing-bdd">behavior-driven development</a> idea started taking hold, I adapted the DSL to more-closely match those concepts, and finally integrated it with RSpec. The result of this effort was Spectie (rhymes with necktie).</p>
<p>The primary goal of Spectie is to provide a simple, straight-forward way for developers to write BDD-style integration tests for their projects in a way that is most natural to them, using existing practices and idioms of the Ruby language.</p>
<p>Here is a simple example of the Spectie syntax in a Rails integration test:</p>
<pre><code>Feature "Compelling Feature" do
Scenario "As a user, I would like to use a compelling feature" do
Given :i_have_an_account, :email => "ryan@kinderman.net"
And :i_have_logged_in
When :i_access_a_compelling_feature
Then :i_am_presented_with_stunning_results
end
def i_have_an_account(options)
@user = create_user(options[:email])
end
def i_have_logged_in
log_in_as @user
end
def i_access_a_compelling_feature
get compelling_feature_path
response.should be_success
end
def i_am_presented_with_stunning_results
response.should have_text("Simply stunning!")
end
end
</code></pre>
<h1>Install</h1>
<p>Spectie is available on <a href="http://github.com/ryankinderman/spectie">GitHub</a>, <a href="http://gemcutter.org/gems/spectie">Gemcutter</a>, and <a href="http://rubyforge.org/projects/kinderman/">RubyForge</a>. The following should get it installed quickly for most people:</p>
<pre><code>% sudo gem install spectie
</code></pre>
<p>For more information on using Spectie, visit <a href="http://github.com/ryankinderman/spectie">http://github.com/ryankinderman/spectie</a>.</p>
<h1>Why not Cucumber or Coulda?</h1>
<p>At the time that this is being written, Cucumber is the new hotness in BDD integration testing. My reasons for sticking with Spectie instead of switching to <a href="http://github.com/aslakhellesoy/cucumber">Cucumber</a> like the rest of the world are as follows:</p>
<ul>
<li>Using regular expressions in place of normal Ruby method names seems like a potential maintenance nightmare, above and beyond the usual potential.</li>
<li>The layer of indirection that is created in order to write tests in plain text doesn't seem worth the cost of maintenance in most cases.</li>
<li>Separating a feature from its "step definitions" seems mostly unnecessary. I like keeping my scenarios and steps in one file until the feature becomes sufficiently big that it warrants extra organizational consideration.</li>
</ul>
<p>These reasons are more-or-less the same as those given by Evan Light, who recently published <a href="http://github.com/elight/coulda">Coulda</a>, which is his solution for avoiding the cuke. What sets Spectie apart from Coulda is its reliance on and integration with RSpec. The Spectie 'Feature' statement has the same behavior as an RSpec 'describe' statement, and the 'Scenario' statement is the same as the RSpec 'example' and 'it' statements. By building on RSpec, Spectie can take advantage of the contextual nesting provided by RSpec, and rely on RSpec to provide the BDD-style syntax within what I've been calling a scenario statement (the words after the Given/When/Thens). Coulda is built directly on Test::Unit. I'm a firm believer in code reuse, and RSpec is the de facto standard for writing BDD-style tests. Spectie, then, is a feature-driven skin on top of RSpec for writing BDD-style integration tests. To me, it only makes sense to do things that way; as RSpec evolves, so will Spectie.</p>tag:ryankinderman.net,2005:Article/1092008-11-14T15:33:42-08:002008-11-14T15:33:42-08:00ryanRails Plugin for Mimicking SSL requests and responses<h1>The Short</h1>
<p>I've written a plugin for Ruby on Rails that allows you to test SSL-dependent application behavior that is driven by the ssl_requirement plugin without the need to install and configure a web server with SSL.</p>
<p><a href="http://github.com/ryankinderman/mimic_ssl">Learn more</a></p>
<h1>The Long</h1>
<p>A while back, I wanted the <a href="http://selenium.openqa.org/">Selenium</a> tests for a <a href="http://www.rubyonrails.org/">Ruby on Rails</a> app I was working on to cover the SSL requirements and allowances of certain controller actions in the system, as defined using functionality provided by the <a href="http://github.com/rails/ssl_requirement">ssl_requirement</a> plugin. I also wanted this SSL-dependent behavior to occur when I was running the application on my local development machines. I had two options:</p>
<ol>
<li><p>Get a web server configured with SSL running on my development machines, as well as on the build server.</p></li>
<li><p>Patch the logic used by the system to determine if a request is under SSL or not, as well as the logic for constructing a URL under SSL, so that the system can essentially mimic an SSL request without a server configured for SSL.</p></li>
</ol>
<p>Since I had multiple Selenium builds on the <a href="http://cruisecontrolrb.thoughtworks.com/">build server</a>, <a href="http://www.subelsky.com/2007/11/testing-rails-ssl-requirements-on-your.html">setting up an SSL server</a> involved adding a host name to the loopback for each build, so that Apache could switch between virtual hosts for the different server ports. I also occasionally ran web servers on my development machines on ports other than the default 3000, as did everyone else on the team, so that we'd all have to go through the setup process for multiple servers on those machines as well. We would need to do all of this work in order to test application logic that, strictly speaking, didn't even require the use of an actual SSL server. Given that the only thing that I was interested in testing was that the requests to certain actions either redirected or didn't, depending on their SSL requirements, all I really needed was to make the application mimic an SSL request.</p>
<p>To mimic an SSL request in conjunction with using the ssl_requirement plugin without an SSL server consisted of patching four things:</p>
<ol>
<li><p><code>ActionController::UrlRewriter#rewrite_url</code> - Provides logic for constructing a URL from options and route parameters</p>
<p> If provided, the <code>:protocol</code> option normally serves as the part before the <code>://</code> in the constructed URL.</p>
<p> The method was patched so that the constructed URL always starts with "http://". If <code>:protocol</code> is equal to "https", this causes an "ssl" key to be added to the query string of the constructed URL, with a value of "1".</p></li>
<li><p><code>ActionController::AbstractRequest#protocol</code> - Provides the protocol used for the request.</p>
<p> The normal value is one of "http" or "https", depending on whether the request was made under SSL or not.</p>
<p> The method was patched so that it always returns "http".</p></li>
<li><p><code>ActionController::AbstractRequest#ssl?</code> - Indicates whether or not the request was made under SSL.</p>
<p> The normal value is determined by checking if request header <code>HTTPS</code> is equal to "on" or <code>HTTP\_X\_FORWARDED_PROTO</code> is equal to "https".</p>
<p> The method was patched so that it checks for a query parameter of "ssl" equal to "1".</p></li>
<li><p><code>SslRequirement#ensure\_proper\_protocol</code> - Used as the <code>before\_filter</code> on a controller that includes the ssl_requirement plugin module, which causes the redirection to an SSL or non-SSL URL to occur, depending on the requirements defined by the controller.</p>
<p> This method was patched so that, instead of replacing the protocol used on the URL with "http" or "https", it either adds or removes the "ssl" query parameter.</p></li>
</ol>
<p>For more information, installation instructions, and so on, please refer to the plugin directly at:</p>
<p><a href="http://github.com/ryankinderman/mimic_ssl">http://github.com/ryankinderman/mimic_ssl</a></p>tag:ryankinderman.net,2005:Article/1072008-01-03T15:11:17-08:002010-07-22T14:05:30-07:00ryanPlugin to Support composed_of Aggregations in ActiveRecord Finder Methods<p>In Rails, hooking up an ActiveRecord model to use a value object to aggregate over a set of database fields is a <a href="http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html">piece of cake</a>. With the accessor methods that are created for a <code>composed_of</code> association, you can now deal exclusively with the <code>composed_of</code> field on your model, instead of directly manipulating or querying the individual database fields that it aggregates. Or can you? As long as all you're doing with the aggregate field is getting and setting its value, your aggregated database fields remain encapsulated. However, if you want to retrieve instances of your model from the database through a call to a finder method, you must do so on the individual database fields.</p>
<p>Consider the following ActiveRecord model definition:</p>
<pre><code>class Customer < ActiveRecord::Base
composed_of :balance, :class_name => "Money", :mapping => %w(balance_amount amount)
end
</code></pre>
<p>Given such a model, we can do something like this with no problem:</p>
<pre><code>customer = Customer.new
customer.balance = Money.new(512.08)
customer.balance # returns #<Money:abc @amount=512.08>
customer.save!
</code></pre>
<p>However, now that we've saved the record, we might want to get that record back from the database at some point with code that looks something like:</p>
<pre><code>customer = Customer.find(:all, :conditions => { :balance => Money.new(512.08) })
</code></pre>
<p>or like:</p>
<pre><code>customer = Customer.find_all_by_balance( Money.new(512) )
</code></pre>
<p>This would provide full encapsulation of the aggregated database fields for the purposes of both record creation and retrieval. The problem is, at the time of my posting this article, it doesn't work. Instead, you have to do this:</p>
<pre><code>customer = Customer.find(:all, :conditions => { :balance_amount => 512.08 })
</code></pre>
<p>To deal with this problem, I've submitted <a href="http://dev.rubyonrails.org/ticket/10572">a ticket</a>, which is currently scheduled to be available in Rails 2.1.<br/>
</p>
<p>If you need this functionality, but your project is using a pre-2.1 release of Rails, I've also created a plugin version of the changes I submitted in the aforementioned ticket. To install:</p>
<p><code>script/plugin install git://github.com/ryankinderman/find_conditions_with_aggregation.git</code></p>
<p><em>Addendum</em>: The patch has been committed to changeset <a href="http://dev.rubyonrails.org/changeset/8671">8671</a>. Yay!</p>tag:ryankinderman.net,2005:Article/642007-02-07T22:13:35-08:002010-07-22T14:07:00-07:00ryanPassing Arrays and Nested Params to url_for in Rails<p>A few weeks ago (okay, more than a few weeks ago, it took me a while to write this), I discussed the problems involved with <a href="http://kinderman.net/articles/2006/11/16/nested-hash-params-with-named-routes-in-ruby-on-rails">passing nested hash parameters to named routes in Rails</a>. My <a href="http://www.thoughtworks.com/profiles/Harvie,+Shane.html">coding pair</a> and I discovered another bug (still using <a href="http://dev.rubyonrails.org/changeset/5522">rev 5522</a>) when passing hash parameters to a named route in Rails, this time when the hash contains arrays. For example, consider the following call to a named route:
<div class="CodeRay"><pre><notextile><span class="CodeRay">person_url(<span class="sy">:name</span> => [<span class="s"><span class="dl">'</span><span class="k">Ryan</span><span class="dl">'</span></span>, <span class="s"><span class="dl">'</span><span class="k">Kinderman</span><span class="dl">'</span></span>])</span></notextile></pre></div>
In order for the params hash to get decoded properly on the server, the resulting URL must be encoded to look like this:
<pre>"http://someurl.com/people?name[]=Ryan&name[]=Kinderman"</pre>
Unfortunately, it gets encoded to look like this:
<pre>"http://someurl.com/people?name=Ryan%2FKinderman"</pre>
For those of you unfamiliar with CGI escaping, the %2F translates into the '/' character. So, you end up with a <code>params</code> hash in the controller where <code>params[:name] == ['Ryan/Kinderman']</code>. How disappointing. To get around this in the past, I've chosen to either split the hash value on '/', or use my own encoding of arrays that Rails can handle, and then simply decode them myself within the controller. In the above example, I could have done something like:
<div class="CodeRay"><pre><notextile><span class="CodeRay">person_url(<span class="sy">:name</span> => {<span class="i">0</span> => <span class="s"><span class="dl">'</span><span class="k">Ryan</span><span class="dl">'</span></span>, <span class="i">1</span> => <span class="s"><span class="dl">'</span><span class="k">Kinderman})</span></span></span></notextile></pre></div>
Of course, without the <a href="http://kinderman.net/articles/2006/11/16/nested-hash-params-with-named-routes-in-ruby-on-rails">patch I described</a> a few weeks ago, this kind of thing would not be possible either, because Rails can't encode nested hash parameters.
</p>
<p>
What I present here is a detailed explanation of the problem, with instructions at the end on how to install my plugin patch to fix it. My explanation and patch address the issues for both nested and array parameters. There are a number of methods involved in the solution to this problem. It may be useful at this point for you to refer to Jamis Buck's excellent articles on the gory details of Rails route <a href="http://weblog.jamisbuck.org/2006/10/4/under-the-hood-route-recognition-in-rails">recognition</a> and <a href="http://weblog.jamisbuck.org/2006/10/16/under-the-hood-route-generation-in-rails">generation</a>.
</p>
<p>
When you call <code>link_to</code> or <code>url_for</code>, either explicitly, either explicitly or through the named route *_url and *_path methods, they roughly follow the following call sequence for processing route parameters:
<ul>
<li><code>ActionController::Base.url_for</code> in <a href="http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/base.rb?rev=5522#L504">action_controller/base.rb</a></li>
OR
<li><code>ActionView::Helpers::UrlHelper#link_to</code> in <a href="http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_view/helpers/url_helper.rb?rev=5522#L66">action_view/helpers/url_helper.rb</a></li>
<li><code>RouteSet#generate</code> in <a href="http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/routing.rb?rev=5522#L1190">action_controller/routing.rb</a></li>
<li><code>RouteSet#options_as_params</code> in <a href="http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/routing.rb?rev=5522#L1154">action_controller/routing.rb</a></li>
<li><code>Array#to_param</code> in <a href="http://dev.rubyonrails.org/browser/trunk/activesupport/lib/active_support/core_ext/array/conversions.rb?rev=5522#L25">active_support/core_ext/array/conversions.rb</a></li>
<li><code>Route#generate</code> in <a href="http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/routing.rb?rev=5522#L411">action_controller/routing.rb</a></li>
<li><code>Route#write_generation</code> in <a href="http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/routing.rb?rev=5522#L318">action_controller/routing.rb</a></li>
<li><code>Route#append_query_string</code> in <a href="http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/routing.rb?rev=5522#L423">action_controller/routing.rb</a></li>
<li><code>Route#build_query_string</code> in <a href="http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/routing.rb?rev=5522#L443">action_controller/routing.rb</a></li>
</ul>
The problems start in the call to <code>options_as_params</code>. This method is not recursive, and processing nested parameters is a recursive problem. The next issue with <code>options_as_params</code> is not actually in the method, but in the <code>to_param</code> method that it calls. If you look at the Rails implementation of <a href="http://dev.rubyonrails.org/browser/trunk/activesupport/lib/active_support/core_ext/array/conversions.rb?rev=5522#L25">Array#to_param</a>, you'll see that all it's doing is joining the elements into a '/' separated string. This doesn't get processed back into separate array elements when the request is received by the controller. So, in the case when <code>value</code> is an Array instance during a call to <code>options_as_params</code>, the resulting string is encoded incorrectly.
</p>
<p>
The other specific issue lies in the <code>Route#build_query_string</code> method. Take a look at the method, and notice the part that looks like:
<div class="CodeRay"><pre><notextile><span class="CodeRay"><span class="r">if</span> value.class == <span class="co">Array</span>
key << <span class="s"><span class="dl">'</span><span class="k">[]</span><span class="dl">'</span></span>
<span class="r">else</span>
value = [ value ]
<span class="r">end</span> </span></notextile></pre></div>
The check for the Array class causes a problem when passing an array to <code>url_for</code> as an option parameter when that array comes from the <code>params</code> hash from within a controller action (*whew*, that was a mouthful!). This is because what you thought was an array is actually an instance of ActionController::Routing::PathSegment::Result. To be honest, I don't know why this is happening. I looked at the code and realized that it'd take me longer to figure out than what I wanted to spend at the time. However, if someone could explain it to me, I'd love to hear it. In any case, to solve this particular problem, the conditional needs to be changed from a check for only Array to <i>Array and any subclasses</i> using something like the <code>is_a?</code> method.
</p>
<p>
So, those are the issues involved in why array and nested hash parameters don't work properly in calls to <code>url_for</code>. Rather than going through my solution, I'm offering it as a Rails plugin with full unit test coverage, and plan to submit it as an actual patch to the Rails team, with the code cleaned up a bit more. Maybe there are reasons why this sort of thing isn't supported, but I can't think what they might be. I'll post updates here if and when I get more information on this. If you have comments or questions on this patch or parts of the code, please let me know.
</p>
<p>
You can install the Rails plugin by typing the following into your command-line:
<code style="display:block; margin: 5px 0 5px 0">
ruby script/plugin install <a href="http://github.com/ryankinderman/nested_params_patch">git://github.com/ryankinderman/nested_params_patch.git</a>
</code>
To see the issues I've discussed first-hand, after installing the plugin, take a look at controller_test.rb.
</p>
<p>
<i>Addendum: I checked, and as of revision 6141 of Rails, the issues covered by this article are still present, and the plugin still fixes them.</i></p>
<p>
<i>Addendum (2007/04/03): I've just got around to confirming that, as rwd's commented, the bug has been fixed. If you're using revision 6343 or later of Rails, you probably aren't going to need this patch. Yay!</i>
</p>
tag:ryankinderman.net,2005:Article/352006-11-16T20:41:28-08:002007-09-22T08:43:50-07:00ryanNested Hash Params with Named Routes in Ruby on Rails<p>
I've never (as of <a href="http://dev.rubyonrails.org/changeset/5522">rev 5522</a>) been able to pass a nested hash as parameters to url_for and have it flatten it properly on the generated URL. I encountered the problem again the other day and, having had just about enough, decided to try and nip it in the bud.
</p>
<p>
The problem I was having occurred when tried to make an HTTP request to a named route such as:
<div class="CodeRay"><pre><notextile><span class="CodeRay">people_url(<span class="sy">:person</span> => { <span class="sy">:name</span> => <span class="s"><span class="dl">'</span><span class="k">Bob</span><span class="dl">'</span></span>, <span class="sy">:profession</span> => <span class="s"><span class="dl">'</span><span class="k">Developer</span><span class="dl">'</span></span> })</span></notextile></pre></div>
This translates to a URL such as <samp>http://localhost:3000/people?person=nameBobprofessionDeveloper</samp> for a POST request. This obviously doesn't decode the hash in a format that can be encoded back its original form.
</p>
<p>
When I've encountered this problem in the past, I could never find any information from someone who's actually solved the problem. There are a <a href="http://dev.rubyonrails.org/ticket/3843">few</a> <a href="http://dev.rubyonrails.org/ticket/4947">patches</a> submitted as tickets to the Rails devs, but one seems to be a duplicate of the other, and neither seems to fix this problem on Edge, at least not for my particular use case.
</p>
<p>
The root of this particular problem lies in a method in the <a href="http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/routing.rb?rev=5522">action_controller/routing.rb</a> file within actionpack/actioncontroller, in the <code>options_as_params</code> of the <code>RoutingSet</code> class. The method, without comments, looks like this:
<div class="CodeRay"><pre><notextile><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt><strong>5</strong><tt>
</tt>6<tt>
</tt>7<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="r">def</span> <span class="fu">options_as_params</span>(options)<tt>
</tt> options_as_params = options[<span class="sy">:controller</span>] ? { <span class="sy">:action</span> => <span class="s"><span class="dl">"</span><span class="k">index</span><span class="dl">"</span></span> } : {}<tt>
</tt> options.each <span class="r">do</span> |k, value|<tt>
</tt> options_as_params[k] = value.to_param<tt>
</tt> <span class="r">end</span><tt>
</tt> options_as_params<tt>
</tt><span class="r">end</span></pre></td>
</tr></table>
</notextile></pre></div>
The problem lies on line 4 in the case where <code>value</code> is a Hash. In that case, the <code>to_param</code> method simply converts the Hash instance into its String representation, which is just a pile of keys and values mashed together, as in my <code>people_url</code> example.
</p>
<p>
To solve this problem, I changed the method to look like this:
<div class="CodeRay"><pre><notextile><span class="CodeRay"><span class="r">def</span> <span class="fu">options_as_params</span>(options)
options_as_params = options[<span class="sy">:controller</span>] ? { <span class="sy">:action</span> => <span class="s"><span class="dl">"</span><span class="k">index</span><span class="dl">"</span></span> } : {}
options.parameterize <span class="r">do</span> |param_key, param_value|
options_as_params[param_key] = param_value.to_param
<span class="r">end</span>
options_as_params
<span class="r">end</span></span></notextile></pre></div>
The <code>parameterize</code> method is an extension I made to the Hash class that essentially flattens a hash like:
<div class="CodeRay"><pre><notextile><span class="CodeRay">{
<span class="sy">:person</span> => {
<span class="sy">:name</span> => <span class="s"><span class="dl">'</span><span class="k">Bob</span><span class="dl">'</span></span>,
<span class="sy">:profession</span> => <span class="s"><span class="dl">'</span><span class="k">Developer</span><span class="dl">'</span></span>
}
}</span></notextile></pre></div>
into a new Hash that looks like:
<div class="CodeRay"><pre><notextile><span class="CodeRay">{
<span class="sy"><span class="sy">:</span><span class="dl">'</span><span class="k">person[name]</span><span class="dl">'</span></span> => <span class="s"><span class="dl">'</span><span class="k">Bob</span><span class="dl">'</span></span>,
<span class="sy"><span class="sy">:</span><span class="dl">'</span><span class="k">person[profession]</span><span class="dl">'</span></span> => <span class="s"><span class="dl">'</span><span class="k">Developer</span><span class="dl">'</span></span>
}</span></notextile></pre></div>
The new Hash is in a format that can be decoded into a URL query string that will be properly encoded back into a Hash on the request. My Hash extension for the solution looks like this:
<div class="CodeRay"><pre><notextile><span class="CodeRay"><span class="r">class</span> <span class="cl">Hash</span>
<span class="r">def</span> <span class="fu">flatten</span>(superkey)
flattened_hash = {}
<span class="pc">self</span>.each <span class="r">do</span> |key, value|
flattened_hash[<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>superkey<span class="idl">}</span></span><span class="k">[</span><span class="il"><span class="idl">#{</span>key<span class="idl">}</span></span><span class="k">]</span><span class="dl">"</span></span>.to_sym] = value
<span class="r">end</span>
flattened_hash
<span class="r">end</span>
<span class="r">def</span> <span class="fu">parameterize</span>
<span class="pc">self</span>.each <span class="r">do</span> |k, value|
<span class="r">if</span> value.is_a?(<span class="co">Hash</span>)
value.flatten(k).each { |fk, fv| <span class="r">yield</span> fk, fv }
<span class="r">else</span>
<span class="r">yield</span> k, value
<span class="r">end</span>
<span class="r">end</span>
<span class="r">end</span>
<span class="r">end</span></span></notextile></pre></div>
Note that this has only been tested to work for a single-level nested Hash, as that satisfied my needs at the time I developed the solution.
</p>