<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5076094400626696593</id><updated>2011-11-18T00:04:46.462-08:00</updated><title type='text'>Rubythings</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://rubythings.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://rubythings.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>chrispanda</name><uri>http://www.blogger.com/profile/12125474773909981989</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://4.bp.blogspot.com/_kMD1zfd8szM/SWDqJJGte-I/AAAAAAAAAAU/0-N2_k5VFIA/S220/mecurly2.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>9</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5076094400626696593.post-4420390456650131602</id><published>2011-10-04T08:35:00.000-07:00</published><updated>2011-10-04T08:42:06.703-07:00</updated><title type='text'>Ajax redirects with devise</title><content type='html'>When devise times out, the user should be forced to log in again.  This works fine when the user attempts to access my app using an html request, but ajax requests were bringing up the http_authentication pop-up box from the browser.  Googling around suggested various ways of dealing with this, all of which seemed to involve a lot of unnecessary work.  Just putting&lt;div&gt;&lt;pre&gt;// Set up a global AJAX error handler to handle the 401&lt;div&gt;// unauthorized responses. If a 401 status code comes back,&lt;/div&gt;&lt;div&gt;// the user is no longer logged-into the system and can not&lt;/div&gt;&lt;div&gt;// use it properly.&lt;/div&gt;&lt;div&gt;$.ajaxSetup({&lt;/div&gt;&lt;div&gt;statusCode: {&lt;/div&gt;&lt;div&gt;401: function(){&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;// Redirect the to the login page.&lt;/div&gt;&lt;div&gt;location.href = "/";;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;}&lt;/div&gt;&lt;div&gt;}&lt;/div&gt;&lt;div&gt;});&lt;/div&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;in the application.js file solved the problem.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5076094400626696593-4420390456650131602?l=rubythings.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rubythings.blogspot.com/feeds/4420390456650131602/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5076094400626696593&amp;postID=4420390456650131602' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/4420390456650131602'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/4420390456650131602'/><link rel='alternate' type='text/html' href='http://rubythings.blogspot.com/2011/10/ajax-redirects-with-devise.html' title='Ajax redirects with devise'/><author><name>chrispanda</name><uri>http://www.blogger.com/profile/12125474773909981989</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://4.bp.blogspot.com/_kMD1zfd8szM/SWDqJJGte-I/AAAAAAAAAAU/0-N2_k5VFIA/S220/mecurly2.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5076094400626696593.post-1875104454763305500</id><published>2011-09-13T00:06:00.000-07:00</published><updated>2011-09-13T00:13:15.253-07:00</updated><title type='text'>Starting the console ready for rails</title><content type='html'>Every time I start my computer I spend a couple of minutes getting the console set up the way I want it - I need spork and autotest running, I need the rails console and server - oh and I like to run both dev and production versions of these. It all takes time.  Time to make it better.&lt;br /&gt;&lt;br /&gt;In response to a question on stackexchange I got an answer that I've adapted. This requires adding the line &lt;br /&gt;&lt;code&gt;&lt;br /&gt;eval "$BASH_POST_RC" &lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;at the end of .bashrc&lt;br /&gt;&lt;br /&gt;Then in System &gt; Preferences &gt; Startup Applications&lt;br /&gt;click on 'Add'&lt;br /&gt;Name: Rails console&lt;br /&gt;Command:&lt;br /&gt;&lt;br /&gt;gnome-terminal --working-directory="/home/chris/development/bureaurails/trunk/server" --tab --title=Terminal --profile=Rails --tab --profile=Rails --title="Dev Console" -e 'bash -c "export BASH_POST_RC=\"rails console\"; exec bash"'  --tab --profile=Rails --title="Dev Server" -e 'bash -c "export BASH_POST_RC=\"rails server\"; exec bash"' --tab --profile=Rails --title=Tail -e 'bash -c "export BASH_POST_RC=\"tail -f log/development.log\"; exec bash"'   --tab --profile=Rails --title=Autotest -e 'bash -c "export BASH_POST_RC=\"bundle exec autotest\"; exec bash"' --tab --profile=Rails --title=Console -e 'bash -c "export BASH_POST_RC=\"rails console production\"; exec bash"'  --tab --profile=Rails --title=Server -e 'bash -c "export BASH_POST_RC=\"rails server -e production -p 4000\"; exec bash"'  --tab --profile=Rails --title=Spork -e 'bash -c "export BASH_POST_RC=\"bundle exec spork\"; exec bash"'&lt;br /&gt;&lt;br /&gt;Comment: All you ever wanted in a rails console&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5076094400626696593-1875104454763305500?l=rubythings.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rubythings.blogspot.com/feeds/1875104454763305500/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5076094400626696593&amp;postID=1875104454763305500' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/1875104454763305500'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/1875104454763305500'/><link rel='alternate' type='text/html' href='http://rubythings.blogspot.com/2011/09/starting-console-ready-for-rails.html' title='Starting the console ready for rails'/><author><name>chrispanda</name><uri>http://www.blogger.com/profile/12125474773909981989</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://4.bp.blogspot.com/_kMD1zfd8szM/SWDqJJGte-I/AAAAAAAAAAU/0-N2_k5VFIA/S220/mecurly2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5076094400626696593.post-1426258241876795415</id><published>2011-05-07T03:52:00.001-07:00</published><updated>2011-05-07T05:34:52.502-07:00</updated><title type='text'>Creating word documents in rails</title><content type='html'>This week, I needed to create a word document from data in a rails app. Needless to say, there is not a windows machine in sight.  After a bit of googling around and thinking about maybe trying to use OpenOffice to do some of the heavy lifting I came across a couple of posts that suggested it might be possible to do what I needed by creating a docx file that could be used as a template, and then editing it.  After all, a docx file is just a zip file with a bunch of xml files inside ...&lt;br /&gt;&lt;br /&gt;Levente Bagi has a  &lt;a href="https://github.com/bagilevi/docx_builder"&gt;nice solution&lt;/a&gt;  but it didn't really meet my needs, and seemed overly complicated in places.  There was also &lt;a href="http://http//tomasvarsavsky.com/2009/04/04/simple-word-document-templating-using-ruby-and-xml/"&gt;this&lt;/a&gt; blog article which outlined the technique but didn't have a lot of detail.  My problem was that I had to extract a bunch of stuff from an active record object (an Event) and then iterate through several associated objects (Event has_many Days, has_many Providers).  So I ended up rolling my own - hopefully these notes will help anyone following on behind.&lt;br /&gt;&lt;br /&gt;First lesson - don't try and use rubyzip or zipruby to compress the files when creating the docx file.  For reasons I didn't really investigate, they don't work.  I'm guessing the default compression is wrong for docx files, but don't have the stamina to wade through the documentation.  Use system zip instead.&lt;br /&gt;&lt;br /&gt;The approach I took was this:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Create a template.  Using MS Word, make a document that is the sort of thing you want to create programatically.  I originally wanted to add images but this complicates things unnecessarily.  &lt;/li&gt;&lt;li&gt;Save this as a docx file.&lt;/li&gt;&lt;li&gt;Unzip the docx file.  You get a folder containing several subfolders.  One of these is called word, and inside that is a file called document.xml.  Open it up with something that will format xml nicely - I used netbeans. First I found the data that needs to be extracted from the Event object.  I replaced that with a new xml node containing the name of the method I wanted to call on the Event object as text so in place of&lt;/li&gt;&lt;p&gt;&amp;lt;w:t&amp;gt;My event&amp;lt;/w:t&amp;gt; &lt;/p&gt;I had &lt;p&gt;&amp;lt;w:t&amp;gt;&amp;lt;insert&amp;gt;fd_event_name&amp;lt;/insert&amp;gt;&amp;lt;/w:t&amp;gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;Continue with the same node name for all the methods to be called on this object&lt;br /&gt;&lt;li&gt; Next find the chunk of html that represents the associated object.  We are going to need to cut this out and put it in a new xml document so that we can iterate over it.  So we create a new empty document with the same namespace definitions as in document.xml, add a new node called &amp;lt;fragment/&amp;gt; and then paste the text you cut from the template document inside. In place of the cut text in the master template, add a new node - in my case since the cut text will display information about the each day of the event, I called the node &amp;lt;days/&amp;gt; Now work through the fragment and add a new xml node containing the name of the method I wanted to call on the Day object as text so in place of&lt;p&gt;&amp;lt;w:t&amp;gt;Sat May 7th 2011&amp;lt;/w:t&amp;gt;&lt;/p&gt;I had&lt;p&gt;&amp;lt;w:t&amp;gt;&amp;lt;insert&amp;gt;date&amp;lt;/insert&amp;gt;&amp;lt;/w:t&amp;gt;&lt;/p&gt;One refinement I needed to make was to pass an index and count for each associated object so that I could have headings like "Day 1 of 5" - just as before, I added nodes to the template where I needed these to appear.&lt;/li&gt;&lt;li&gt;Repeat for other associated objects&lt;/li&gt;&lt;li&gt;Now we need to create a new word document using these pieces.  I created a method on the Event object&lt;pre&gt; def create_docx&lt;br /&gt;f=File.read("lib/docx_sections/template.xml")&lt;br /&gt;#substitute fields in main template&lt;br /&gt;doc = substitute(f,self)&lt;br /&gt;f=File.read("lib/docx_sections/day.xml")&lt;br /&gt;&lt;br /&gt;self.days.each_with_index do |day, i|&lt;br /&gt;doc.xpath("//days").before(substitute(f,day, i, self.days.size).xpath("//fragment").children)&lt;br /&gt;end&lt;br /&gt;f=File.read("lib/docx_sections/provider.xml")&lt;br /&gt;self.providers.each_with_index do |provider, i|&lt;br /&gt;doc.xpath("//providers").before(substitute(f,provider, i, self.providers.size).xpath("//fragment").children)&lt;br /&gt;end&lt;br /&gt;doc.xpath("//days").remove&lt;br /&gt;doc.xpath("//providers").remove&lt;br /&gt;doc = doc.to_s.gsub(/(\n|\t|\r)/, ' ').gsub(/&amp;gt;\s*&lt;!--, '--&gt;&amp;lt;').squeeze(' ')     build_docx(doc)   &lt;br /&gt;end&lt;/pre&gt;&lt;br /&gt;Let's go through this line by line.  We read in the template.xml file, and call substitute with the file and self as parameters - we'll look at that method later.  Then we do the same with the associations - read the template, iterate over the associated objects, call substitute. Then we remove the marker tags, compress the xml file to remove any whitespace we don't need, and build the docx file.  Easy.&lt;br /&gt;&lt;br /&gt;So what about the substitute method. It could hardly be simpler.  Nokogiri makes it easy to replace the marker nodes we added with the content we want.  Find the "insert" node, get the text it contains, call the method of that name on the object and replace the node with the result.  Similarly, replace the index and count nodes with the parameters we passed in.&lt;br /&gt;&lt;pre&gt;def substitute(xmlstring,obj, i = 0, count = 1)&lt;br /&gt;doc= Nokogiri::XML(xmlstring.clone)&lt;br /&gt;doc.xpath("//insert").each do |n|&lt;br /&gt;n.parent.content= obj.send(n.text.to_sym)&lt;br /&gt;end&lt;br /&gt;doc.xpath("//index").each do |n|&lt;br /&gt;n.parent.content= i + 1&lt;br /&gt;end&lt;br /&gt;doc.xpath("//count").each do |n|&lt;br /&gt;n.parent.content= count&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;doc&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;Finally, the build_docx method is essentially stolen from Levente Bagi.&lt;pre&gt;def build_docx(content)&lt;br /&gt;    filename="#{self.event_organiser.fd_name}_#{self.fd_event_name}".gsub(/\s*/, '')&lt;br /&gt;    in_temp_dir do |temp_dir|&lt;br /&gt;      system("cp -r lib/word_template_files #{temp_dir}/plan_report")&lt;br /&gt;      open("#{temp_dir}/plan_report/word/document.xml", "w") do |file|&lt;br /&gt;        file.write(content)&lt;br /&gt;      end&lt;br /&gt;      system("cd #{temp_dir}/plan_report; zip -r ../#{filename}.docx *")&lt;br /&gt;      system("cp #{temp_dir}/#{filename}.docx /home/chaser/downloads")&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def in_temp_dir&lt;br /&gt;    temp_dir = "/tmp/docx_#{Time.now.to_f.to_s}"&lt;br /&gt;    Dir.mkdir(temp_dir)&lt;br /&gt;    yield(temp_dir)&lt;br /&gt;    system("rm -Rf #{temp_dir}")&lt;br /&gt;  end&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;As mentioned at the start of this post - I originally hoped to be able to add images to this document - but that would require understanding enough about the way docx files handle assets and frankly the users will probably want to change the images and layout to suit their needs so it's almost certainly not worth it.  It would be nice to try though ...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5076094400626696593-1426258241876795415?l=rubythings.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rubythings.blogspot.com/feeds/1426258241876795415/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5076094400626696593&amp;postID=1426258241876795415' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/1426258241876795415'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/1426258241876795415'/><link rel='alternate' type='text/html' href='http://rubythings.blogspot.com/2011/05/creating-word-documents-in-rails.html' title='Creating word documents in rails'/><author><name>chrispanda</name><uri>http://www.blogger.com/profile/12125474773909981989</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://4.bp.blogspot.com/_kMD1zfd8szM/SWDqJJGte-I/AAAAAAAAAAU/0-N2_k5VFIA/S220/mecurly2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5076094400626696593.post-1716415055472073756</id><published>2010-09-16T01:02:00.001-07:00</published><updated>2010-09-22T10:11:33.071-07:00</updated><title type='text'>licenceable</title><content type='html'>I've been working quite a bit with Devise over the last week or so. I'm rewriting something that used to be a desktop application and turning it into a web app.  The users currently have a seat-based licencing arrangement - 5 users, 10 users etc.  Keeping this arrangement in a web application has proved a bit tricky.  There are several posts in the devise group that suggest this is a fairly common requirement, but no obvious solutions.  So here's my first hack at it - I've simply added one more method call to the resource inside the DatabaseAuthenticatable authenticate! method&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;require 'devise/strategies/authenticatable'&lt;br /&gt;&lt;br /&gt;module Devise&lt;br /&gt;  module Strategies&lt;br /&gt;    # Default strategy for signing in a user, based on his email and password in the database.&lt;br /&gt;    class DatabaseAuthenticatable &amp;lt; Authenticatable&lt;br /&gt;      def authenticate!&lt;br /&gt;        resource = valid_password? &amp;&amp; mapping.to.find_for_database_authentication(authentication_hash)&lt;br /&gt;&lt;br /&gt;        if resource &amp;&amp; resource.licenced?  &amp;&amp; validate(resource){ resource.valid_password?(password) }&lt;br /&gt;          resource.after_database_authentication&lt;br /&gt;          success!(resource)&lt;br /&gt;        else&lt;br /&gt;          fail(:invalid)&lt;br /&gt;        end&lt;br /&gt;      end&lt;br /&gt;    &lt;br /&gt;&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;Warden::Strategies.add(:database_authenticatable, Devise::Strategies::DatabaseAuthenticatable)&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Then in the resource model - in this case the User model - I can have a  licenced?  method that checks whether the user has a licence to access the system. The method looks like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;  def licenced?&lt;br /&gt;    not_logged_in? and has_licence?&lt;br /&gt;  end&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next I added a 'last_signed_out_at' field to the resource record and override the destroy method in the Devise::SessionsController.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt; def destroy&lt;br /&gt;   current_user.update_attribute(:last_signed_out_at, Time.now) rescue nil&lt;br /&gt;   set_flash_message :notice, :signed_out if signed_in?(resource_name)&lt;br /&gt;   sign_out_and_redirect(resource_name)&lt;br /&gt; end&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;On its own this is not enough to check whether the user is logged in, because they may just have closed the browser or their session may have timed out. We also need the fields added by the Trackable and Timeoutable modules. Together these changes allow us to write a not_logged_in? method in the User model&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;def not_logged_in?&lt;br /&gt;    ok  = current_sign_in_at.nil? || last_request_at.nil?&lt;br /&gt;    ok = ok || last_request_at.to_i &amp;lt; Time.now.to_i - Devise.timeout_in.to_i rescue nil&lt;br /&gt;    ok=  ok || last_signed_out_at &amp;gt; current_sign_in_at rescue nil&lt;br /&gt;    ok&lt;br /&gt;  end&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Finally we need to decide whether the user is licenced.  This will vary depending on the application of course. In our case, an event can have any number of users but only licence_count users can be logged in at the same time, so in the User model&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt; def has_licence?&lt;br /&gt;    event.logged_in_users &amp;lt; event.licence_count&lt;br /&gt;  end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;And finally, we need to know how many users are logged in for a given event. In our case an event has_many users, and in the Event model I just iterate through all the users and count the ones that are signed in&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt; def logged_in_users&lt;br /&gt;   users.inject(0){|count, u|&lt;br /&gt;    count+= 1 unless u.not_logged_in?&lt;br /&gt;   }&lt;br /&gt; end&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt; If I can find the time and there is any interest I will try and bundle this up into a stand-alone extension but for now just putting it out there ...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5076094400626696593-1716415055472073756?l=rubythings.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rubythings.blogspot.com/feeds/1716415055472073756/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5076094400626696593&amp;postID=1716415055472073756' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/1716415055472073756'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/1716415055472073756'/><link rel='alternate' type='text/html' href='http://rubythings.blogspot.com/2010/09/licenceable.html' title='licenceable'/><author><name>chrispanda</name><uri>http://www.blogger.com/profile/12125474773909981989</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://4.bp.blogspot.com/_kMD1zfd8szM/SWDqJJGte-I/AAAAAAAAAAU/0-N2_k5VFIA/S220/mecurly2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5076094400626696593.post-8152355974135781659</id><published>2010-08-25T02:21:00.000-07:00</published><updated>2010-08-25T02:33:32.162-07:00</updated><title type='text'>Testing with devise</title><content type='html'>Today I ran across a problem getting tests to pass with Devise.  There were in fact two issues.  First, in rails 3 the Gemfile does not stipulate the order in which gems are loaded and this can mess things up a bit.  Especially with mocha, which I use for mocking and stubbing.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://yehudakatz.com/2010/04/17/ruby-require-order-problems/"&gt;This post&lt;/a&gt; describes the issue.&lt;br /&gt;&lt;br /&gt;To solve the problem I put&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;require 'mocha'&lt;br /&gt;Bundler.require(:test)&lt;br /&gt; include Devise::TestHelpers &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;at the end of test/helper.rb&lt;br /&gt;&lt;br /&gt;But the sets still didn't pass ....&lt;br /&gt;&lt;br /&gt;The other problem to which the solution was more obvious when I actually thought about it was because I am using confirmable.  So the setup needed to be:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; def setup&lt;br /&gt;    @user = users(:one)&lt;br /&gt;    sign_in @user&lt;br /&gt;    @user.confirm!&lt;br /&gt;  end&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5076094400626696593-8152355974135781659?l=rubythings.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rubythings.blogspot.com/feeds/8152355974135781659/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5076094400626696593&amp;postID=8152355974135781659' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/8152355974135781659'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/8152355974135781659'/><link rel='alternate' type='text/html' href='http://rubythings.blogspot.com/2010/08/testing-with-devise.html' title='Testing with devise'/><author><name>chrispanda</name><uri>http://www.blogger.com/profile/12125474773909981989</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://4.bp.blogspot.com/_kMD1zfd8szM/SWDqJJGte-I/AAAAAAAAAAU/0-N2_k5VFIA/S220/mecurly2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5076094400626696593.post-6843132938523816991</id><published>2010-08-20T00:27:00.000-07:00</published><updated>2010-08-20T00:42:16.097-07:00</updated><title type='text'>i18n in rails 3</title><content type='html'>Well it's been a long time, but I'm going to make an effort to do more blogging now that rails 3 is almost here - partly to keep track of what I need to remember - but also because it might be useful to others.&lt;br /&gt;&lt;br /&gt;I'm upgrading a big app to rails 3 before we go live with it - and of course there are a few glitches.  Mostly these are to do with gems and plugins that don't work - more on them when I get to them.  But the first big hiccup was internationalization. The app I'm upgrading is fully internationalized - but when I got the front page up i got things like&lt;br /&gt;&lt;code&gt;&lt;br /&gt;translation missing: 'en'::character varying, devise, sessions, user, signed_in&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;instead of the nice translations i was expecting.&lt;br /&gt;&lt;br /&gt;The cause seems to lie in the I18n gem - where it gets the configuration object&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt; class &lt;&lt; self&lt;br /&gt;    # Gets I18n configuration object.&lt;br /&gt;    def config&lt;br /&gt;      Thread.current[:i18n_config] ||= I18n::Config.new&lt;br /&gt;    end&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I patched this by dropping a file in the lib directory and requiring it from application.rb&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;module I18n&lt;br /&gt;  class &lt;&lt; self&lt;br /&gt;    def config&lt;br /&gt;      # don't pick up the config object from the current thread - &lt;br /&gt;      # it returns 'en'::character varying&lt;br /&gt;    I18n::Config.new&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;My guess is that this might slow things down a touch - creating a new config object instead of using an existing one - but it works for me so far&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5076094400626696593-6843132938523816991?l=rubythings.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rubythings.blogspot.com/feeds/6843132938523816991/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5076094400626696593&amp;postID=6843132938523816991' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/6843132938523816991'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/6843132938523816991'/><link rel='alternate' type='text/html' href='http://rubythings.blogspot.com/2010/08/i18n-in-rails-3.html' title='i18n in rails 3'/><author><name>chrispanda</name><uri>http://www.blogger.com/profile/12125474773909981989</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://4.bp.blogspot.com/_kMD1zfd8szM/SWDqJJGte-I/AAAAAAAAAAU/0-N2_k5VFIA/S220/mecurly2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5076094400626696593.post-1630923856777167858</id><published>2009-01-04T07:59:00.000-08:00</published><updated>2009-01-05T04:14:41.334-08:00</updated><title type='text'>rcov bugs</title><content type='html'>There is a really irritating bug in the ruby bindings for Rcov that has been reported by Brian Candler &lt;a href="http://groups.google.com/group/ruby-talk-google/browse_thread/thread/c2e2f3b9a6ffac50?pli=1"&gt;here&lt;/a&gt; among other places.  Applying Brian's patches solves part of the problem (I think they are now in trunk) but the problem that throws up the error &lt;pre&gt;/usr/local/lib/ruby/1.8/rexml/formatters/pretty.rb:131:in `[]': no&lt;br /&gt;implicit conversion from nil to integer (TypeError) &lt;/pre&gt;&lt;br /&gt;mentioned later in the post is more annoying - not least because it came back after I already got rid of it once!  I have a hunch that this error can be triggered by the content of the html being generated by rcov, and in my case it was triggered by a test requiring a missing file.  At first I didn't realise this was the problem of course, and the trace didn't give me a clue about which test caused the problem.  The proximate cause lies in a method called wrap in /usr/local/lib/ruby/1.8/rexml/formatters/pretty.rb.  &lt;br /&gt;&lt;br /&gt;After messing around for a bit i found I that changing the wrap method to return just a string worked fine&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def wrap(string, width)&lt;br /&gt;        # Recursively wrap string at width.&lt;br /&gt;        return string &lt;br /&gt;        &lt;br /&gt;      end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This gave me an error that identified that culprit test.  I could then fix the path to the missing file, change pretty.rb back to the original version, and carry on ...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5076094400626696593-1630923856777167858?l=rubythings.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rubythings.blogspot.com/feeds/1630923856777167858/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5076094400626696593&amp;postID=1630923856777167858' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/1630923856777167858'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/1630923856777167858'/><link rel='alternate' type='text/html' href='http://rubythings.blogspot.com/2009/01/rcov-bugs.html' title='rcov bugs'/><author><name>chrispanda</name><uri>http://www.blogger.com/profile/12125474773909981989</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://4.bp.blogspot.com/_kMD1zfd8szM/SWDqJJGte-I/AAAAAAAAAAU/0-N2_k5VFIA/S220/mecurly2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5076094400626696593.post-2330698293641198679</id><published>2008-12-29T02:45:00.000-08:00</published><updated>2009-01-04T08:32:39.400-08:00</updated><title type='text'>The muddle that is selenium</title><content type='html'>I have a bit of a love hate relationship with selenium, and always have.  It's great for testing ajax and for integration testing, but there are so many ways of setting it up that I'm never sure if I'm using best practice.  There's Selenium core, SeleniumRC, Selenium client - and then there's selenium_fu, selenium_on_rails, and polonium.  Not to mention a bunch of competing things like Watir.  So over christmas I sat down and tried to get my head around what was out there, what works with rails 2.2, and what might be good practice, if not best practice.&lt;br /&gt;&lt;br /&gt;Up to now I've been using Selenium in firefox, with tests written in rselenese.   While this works, I've only been able to use it to test on firefox, and it's meant firing up the browser to run the tests.  I'm a bit lazy about this, and would rather be able to run the tests as a rake task.  Sure they're slow, but if I run them while I'm making coffee or having lunch that's better than not at all.&lt;br /&gt;&lt;br /&gt;I'm not sure the solution I'm outlining here is the best - but it has the advantage of being quite simple to implement, and not being dependent on a whole bunch of plugins that may or may not be compatible with rails in future.&lt;br /&gt;&lt;br /&gt;So I've been looking at changing over to Selenium client and Selenium RC instead. First step was to download the selenium-client gem&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;sudo gem install selenium-client&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is now the 'official Ruby driver for [&lt;a href="http://selenium-client.rubyforge.org/classes/Selenium.html"&gt;Selenium&lt;/a&gt; Remote Control](&lt;a href="http://selenium-rc.openqa.org/"&gt;selenium-rc.openqa.org&lt;/a&gt;) '&lt;br /&gt;&lt;br /&gt;I also found &lt;a href="http://johnmudd.infogami.com/blog/5be6"&gt;this&lt;/a&gt; and &lt;a href="http://www.spacevatican.org/2008/9/27/selenium-and-firefox-3"&gt;this&lt;/a&gt; helpful. Crucially, I downloaded a version of selenium RC that works with firefox 3 from &lt;a href="http://edwardotis.net/public_files/selenium/selenium_server_FF3/selenium-server.jar"&gt;here&lt;/a&gt;. Then fired up the selenium RC server with&lt;br /&gt;&lt;br /&gt;java -jar selenium-server.jar -interactive&lt;br /&gt;&lt;br /&gt;I created a helper file, and stuck it in the test/selenium directory.  There's still a lot of stuff hard coded in here that should be pulled out into maybe environment variables - but it's a start.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;dir = File.dirname(__FILE__)&lt;br /&gt;require dir + "/../test_helper"&lt;br /&gt;require 'test/unit'&lt;br /&gt;require "rubygems"&lt;br /&gt;gem 'selenium-client'&lt;br /&gt;require 'selenium'&lt;br /&gt;module Chaser&lt;br /&gt; class SeleniumTestCase &lt; counter="0" additional_args="['-interactive'," background="true" host="0.0.0.0" port="4444" timeout="300000" wait_until_up_and_running="true" remote_control =" Selenium::RemoteControl::RemoteControl.new(@@host," jar_file =" File.dirname(__FILE__)+" additional_args =" @@additional_args" background =""&gt; @@background&lt;br /&gt;   &lt;br /&gt;     if @@background &amp;amp;&amp;amp; @@wait_until_up_and_running&lt;br /&gt;       puts "Waiting for Remote Control to be up and running..."&lt;br /&gt;       TCPSocket.wait_for_service :host =&gt; @@host, :port =&gt; @@port&lt;br /&gt;       puts 'continuing ...'&lt;br /&gt;     end&lt;br /&gt;     puts "Selenium Remote Control at #{@@host}:#{@@port} ready"&lt;br /&gt;&lt;br /&gt;   end&lt;br /&gt;   def self.terminate_server&lt;br /&gt;#whether the pid turns up in f1 or f2 seems to be indeterminate - this bit of code looks in both&lt;br /&gt;#and sort out which contains an integer as a way of reliably returning the pid&lt;br /&gt;     puts "Terminating server..."&lt;br /&gt;    f1= `ps axo pid -o command | egrep 'java.*?selenium|mongrel.*?3001' | grep -v egrep | cut -d' ' -f1 `&lt;br /&gt;    f2=  `ps axo pid -o command | egrep 'java.*?selenium|mongrel.*?3001' | grep -v egrep | cut -d' ' -f2`&lt;br /&gt;    "#{(f1||f2).to_i}  kill -9"&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   def self.running_server&lt;br /&gt;     f1=`ps axo pid -o command | egrep 'java.*?selenium|mongrel.*?3001' | grep -v egrep | cut -d' ' -f1`&lt;br /&gt;     f2=`ps axo pid -o command | egrep 'java.*?selenium|mongrel.*?3001' | grep -v egrep | cut -d' ' -f2`&lt;br /&gt;return (f1||f2).to_i &gt; 0&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;  &lt;br /&gt;   def setup&lt;br /&gt;     SeleniumTestCase.start_selenium unless SeleniumTestCase.running_server&lt;br /&gt;     TCPSocket.wait_for_service :host =&gt; @@host, :port =&gt; @@port&lt;br /&gt;     @screenshotdir='bureau_screenshots'&lt;br /&gt;     @browser = Selenium::Client::Driver.new(@@host, @@port, "*chrome /home/chris/firefox/firefox/firefox-bin", "http://localhost:3001", 30000);&lt;br /&gt;     @browser.start_new_browser_session&lt;br /&gt;     @browser.open('/')&lt;br /&gt;   &lt;br /&gt;     #This is app specific - logs the user out if they are already logged in   so that we have a&lt;br /&gt;#clean startup&lt;br /&gt;     assert_equal "Chaser Bureau", @browser.title&lt;br /&gt;     if !! Thread.current[:user]&lt;br /&gt;       browser.click "link=Log out", :wait_for =&gt; :page&lt;br /&gt;     end&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   def teardown&lt;br /&gt;     @browser.close_current_browser_session if @browser&lt;br /&gt;     SeleniumTestCase.terminate_server&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   # Shadowed methods, so they aren't passed to method_missing&lt;br /&gt;   def open(addr)&lt;br /&gt;     @browser.open(addr)&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   def type(inputLocator, value)&lt;br /&gt;     @browser.type(inputLocator, value)&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   def select(inputLocator, optionLocator)&lt;br /&gt;     @browser.select(inputLocator, optionLocator)&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   def make_dir(name)&lt;br /&gt;     Dir.mkdir("#{@screenshotdir}") unless File.exists?("#{@screenshotdir}")&lt;br /&gt;     Dir.mkdir("#{@screenshotdir}/#{name}") unless File.exists?("#{@screenshotdir}/#{name}")&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   def click(*args)&lt;br /&gt;     make_dir( self.method_name)&lt;br /&gt;     @browser.capture_entire_page_screenshot("#{RAILS_ROOT}/#{@screenshotdir}/#{ self.method_name}/screenshot_#{@@counter}.png","background=#CCFFDD")&lt;br /&gt;     @@counter+=1&lt;br /&gt;     my_file = File.new("#{RAILS_ROOT}/#{@screenshotdir}/#{ self.method_name}/body_#{@@counter}.html", "w")&lt;br /&gt;     my_file.puts(@browser.get_html_source)&lt;br /&gt;     my_file.close&lt;br /&gt;     @browser.click(*args)&lt;br /&gt;   end&lt;br /&gt;   # Passes all missing methods to browser&lt;br /&gt;   def method_missing(method_name, *args)&lt;br /&gt;     if @browser.respond_to?(method_name)&lt;br /&gt;       if args.empty?&lt;br /&gt;         @browser.send(method_name)&lt;br /&gt;       else&lt;br /&gt;         @browser.send(method_name, *args)&lt;br /&gt;       end&lt;br /&gt;     else&lt;br /&gt;       super&lt;br /&gt;     end&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;&lt;br /&gt; end&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;then I have some tests that look like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;require File.expand_path(File.dirname(__FILE__) + "/selenium_helper")&lt;br /&gt;class CreateContact &lt; wait_for =""&gt; :page&lt;/pre&gt;&lt;br /&gt;.... and so on&lt;br /&gt;&lt;br /&gt;Next, I wanted a rake task to run the tests.  Selenium_fu has a long list of rake tasks that start and stop the selenium server, and do all sorts of other stuff - but they didn't work out of the box for me. I also wanted that whenever I ran the selenium tests I also ran the w3c validation tests.  Then I got to thinking it would be nice to have a screen dump of each page before leaving it - this might be useful for debugging, and also for screenshots for documentation.  And while we're at it, why not run rcov as well .... all things that take a long time, but are quite handy if run regularly. &lt;br /&gt;&lt;br /&gt;Anyway, after far to much hacking about, and fixing things like &lt;a href="http://rubythings.blogspot.com/2009/01/rcov-bugs.html"&gt;rcov bugs&lt;/a&gt; - I ended up with a big rakefile ...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;namespace :test do&lt;br /&gt; desc "run selenium tests"&lt;br /&gt;&lt;br /&gt; task :selenium  do&lt;br /&gt;   #system "mongrel_rails stop"&lt;br /&gt;    RAILS_ENV = ENV['RAILS_ENV'] = 'test'&lt;br /&gt;   system "mongrel_rails start -d -e test -p 3001" unless "tmp/mongrel-test.pid"&lt;br /&gt;   ENV['screenshot']='true'&lt;br /&gt; &lt;br /&gt;   Rake::TestTask.new("all_tests") do |t|&lt;br /&gt;     t.libs &lt;&lt; 'test'      &lt;br /&gt;   t.test_files = FileList['test/selenium/*_test.rb']      &lt;br /&gt;   t.verbose = true    &lt;br /&gt;   end    &lt;br /&gt;&lt;br /&gt; task("all_tests").execute  &lt;br /&gt; end   &lt;br /&gt;&lt;br /&gt; task :validator do    &lt;br /&gt;   desc "run functional tests with w3c validation"   &lt;br /&gt;   p 'running validator tests'    &lt;br /&gt;   ENV['validator']='true'    &lt;br /&gt;   task("test:functionals").execute  &lt;br /&gt; end     &lt;br /&gt;&lt;br /&gt; task :all =&gt; [ 'test:units', 'test:validator','mongrel:test:start','test:selenium'] do&lt;br /&gt;   desc "Runs all tests - including selenium and validator tests"&lt;br /&gt; end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Setting the validator key in the environment means that I can run the w3c validator tests by having the following code in my test_helper.rb file:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;if ENV.has_key?'validator'&lt;br /&gt;#ignore some warnings i don't care about ...&lt;br /&gt;    Html::Test::Validator.tidy_ignore_list=[/&amp;lt;table&amp;gt; lacks "summary" attribute/,&lt;br /&gt;   /Warning: replacing invalid character code 130/,#€ has a very bad character&lt;br /&gt;   /Warning: replacing invalid character code 152/,  #star char&lt;br /&gt;   /Warning: trimming empty &amp;lt;dd&amp;gt;/,&lt;br /&gt;   /end tag for "ul" which is not finished/&lt;br /&gt;&lt;br /&gt; ]&lt;br /&gt;#set up the validator&lt;br /&gt;   Html::Test::Validator.w3c_show_source = "0"&lt;br /&gt;   ApplicationController.validate_all = true&lt;br /&gt;   ApplicationController.validators = [:w3c]&lt;br /&gt;   ApplicationController.check_urls = false&lt;br /&gt;   ApplicationController.check_redirects = true&lt;br /&gt; end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;It's a lot of work to set all this up and get the bugs out, but now I can run selenium and w3c validation tests and get a screen dump of every page of the app while making lunch.  So probably worth it in the end ...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5076094400626696593-2330698293641198679?l=rubythings.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rubythings.blogspot.com/feeds/2330698293641198679/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5076094400626696593&amp;postID=2330698293641198679' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/2330698293641198679'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/2330698293641198679'/><link rel='alternate' type='text/html' href='http://rubythings.blogspot.com/2008/12/muddle-that-is-selenium.html' title='The muddle that is selenium'/><author><name>chrispanda</name><uri>http://www.blogger.com/profile/12125474773909981989</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://4.bp.blogspot.com/_kMD1zfd8szM/SWDqJJGte-I/AAAAAAAAAAU/0-N2_k5VFIA/S220/mecurly2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5076094400626696593.post-2390418962133058814</id><published>2008-07-02T03:11:00.000-07:00</published><updated>2008-07-02T08:58:22.784-07:00</updated><title type='text'>Full text search in rails with postgres</title><content type='html'>﻿  &lt;h1 class="western"&gt;&lt;span style="font-family:Nimbus Sans L, sans-serif;"&gt;Full text search in rails with postgres&lt;/span&gt;&lt;/h1&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-size:180%;"&gt;Summary&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;There are basically 4 options:- Ferret, Solr, Sphinx and native postgres search (which used to be called tsearch2 but is now compiled into the db.) Each of course has advantages and disadvantages.&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;Ferret&lt;/span&gt; - advantages&lt;br /&gt;1.     Fast indexing&lt;br /&gt;2.     Indexing on active record save&lt;br /&gt;3.     set boost values independently per field and per record&lt;br /&gt;4.     write custom text tokenizers, stemmers and stop lists (and use different ones per field even)&lt;br /&gt;5.     highlight matches in results using the same engine that does the searching&lt;br /&gt;6.     manage my own indexes, merging them at will, or just merging results from them.&lt;br /&gt;7.     Index content generated on the fly, without having to store it in my sql database (pull in all the associated tags for a post as you index it for example).&lt;br /&gt;8.     Store original data in the index (though most people use it to index an SQL database anyway).&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Ferret - disadvantages&lt;/span&gt;&lt;br /&gt;1. Corrupts indexes if used with Transactions in your apps because of its after_update filter.(It updates the index before the actual save to&lt;br /&gt;the database)&lt;br /&gt;&lt;br /&gt;2. Unstable on the production server if you use some load balancing techniques like round-robbin scheme and you have instances of mongrel on&lt;br /&gt;different machines.&lt;br /&gt;(Added burden to use a separate dRB server)&lt;br /&gt;3. slow searching.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Solr&lt;/span&gt; - advantages&lt;br /&gt;1.     Index update with activerecord save&lt;br /&gt;2.     In-built support for highlighting search keywords like you see in Google Search and many more advanced features.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Solr - disadvantages&lt;/span&gt;&lt;br /&gt;1.     Runs on Jboss or some other java stack&lt;br /&gt;2.     Slow to reindex and query wrt sphinx and uses 50x more memory&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Sphinx - advantages&lt;/span&gt;&lt;br /&gt;1.     Very fast to search and index, slow to update&lt;br /&gt;2.     searching and ranking across multiple models&lt;br /&gt;3.     delta index support&lt;br /&gt;4.     excerpt highlighting&lt;br /&gt;5.     Google-style query parser&lt;br /&gt;6.     spellcheck&lt;br /&gt;7.     faceting on text, date, and numeric fields&lt;br /&gt;8.     field weighting, merging, and aliasing&lt;br /&gt;9.     geodistance&lt;br /&gt;10.   belongs_to and has_many includes&lt;br /&gt;11.  drop-in compatibility with will_paginate&lt;br /&gt;12.  drop-in compatibility with Interlock&lt;br /&gt;13.  multiple deployment environments&lt;br /&gt;14.  comprehensive Rake tasks&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Sphinx - disadvantages&lt;/span&gt;&lt;br /&gt;1.   Closely tied to mysql, php – can run with postgres but needs to be compiled&lt;br /&gt;2.   Difficult to integrate as compared to Ferret or Solr&lt;br /&gt;3.   You have to write a lot of sql code in the configuration file for indexing and searching data&lt;br /&gt;4.   Not hooked with the ActiveRecord save or the life cycle of an object, so you need a cron job to rebuild the index periodically (But plugins use delta indexes so model changes are automatically added to the live indexes but regular periodic reindexing is still needed&lt;br /&gt;6.  'Shared hosts do not support sphinx'&lt;br /&gt;7.  No automatic updates – must use cron job to update index&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;postgres&lt;/span&gt; - advantages&lt;br /&gt;1.  Can use triggers to index on save&lt;br /&gt;2.  No overhead of another system&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;postgres&lt;/span&gt; - disadvantages&lt;br /&gt;1.  Limited plugin support (we will need to write our own)&lt;br /&gt;2.  Will need to hand code pagination and search term highlighting (there are functions for search term highlighting built in to postgres, but must be called via sql)&lt;br /&gt;3.  Not hooked in to active record automatically&lt;br /&gt;&lt;br /&gt;Requirements:&lt;br /&gt;Stemming&lt;br /&gt;Stop words&lt;br /&gt;Wildcards&lt;br /&gt;Search across multiple fields in multiple tables and rank by specific fields&lt;br /&gt;paginate results&lt;br /&gt;highlight search terms in text&lt;br /&gt;&lt;br /&gt;Notes&lt;br /&gt;&lt;br /&gt;Postgres native search (used to be tsearch2)&lt;br /&gt;http://groups.google.com/group/acts_as_tsearch/browse_thread/thread/6437f86a2540f406&lt;br /&gt;and&lt;br /&gt;http://www.pervasivecode.com/blog/2008/01/24/acts_as_tsearch-adjustments-needed-for-postgresql-83rc2/&lt;br /&gt;&lt;br /&gt;looks like only minor changes to get acts_as_tsearch working with postgres 8.3&lt;br /&gt;&lt;br /&gt;Ferret generally gets a bad press – I thought the problems went away after moving to Drb, but apparently not.&lt;br /&gt;&lt;br /&gt;We’ve used ferret on past projects… and now use sphinx. We’re not&lt;br /&gt;likely going back to ferret. ;-)&lt;br /&gt;&lt;br /&gt;and lots more comments like this in&lt;br /&gt;http://www.ruby-forum.com/topic/137629&lt;br /&gt;And most convincing of all ...&lt;br /&gt;http://deadprogrammersociety.blogspot.com/2008/05/in-search-of-search.html&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Shinx plugins:&lt;br /&gt;Ultrasphinx (only works with rails 2.0)&lt;br /&gt;Thinking Sphinx&lt;br /&gt;acts_as_sphinx&lt;br /&gt;sphinctor&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5076094400626696593-2390418962133058814?l=rubythings.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rubythings.blogspot.com/feeds/2390418962133058814/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5076094400626696593&amp;postID=2390418962133058814' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/2390418962133058814'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5076094400626696593/posts/default/2390418962133058814'/><link rel='alternate' type='text/html' href='http://rubythings.blogspot.com/2008/07/summary-there-are-basically-4-options.html' title='Full text search in rails with postgres'/><author><name>chrispanda</name><uri>http://www.blogger.com/profile/12125474773909981989</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://4.bp.blogspot.com/_kMD1zfd8szM/SWDqJJGte-I/AAAAAAAAAAU/0-N2_k5VFIA/S220/mecurly2.jpg'/></author><thr:total>1</thr:total></entry></feed>
