Ransack Gem

Recently, I started using the ransack gem (https://github.com/ernie/ransack) for searching in one of my apps. I'm putting this here as an example, in case I ever need to make any more changes. The basics of ransack are pretty easy to do and work decently well. First step, add a search method to your controller. It should look something like this:

  def search
    @q = Board.search(params[:q])
    @boards = @q.result
    @count = @boards.count

We have @count because we'd like to be able to show the number of results on the page.

Next we have a search view. The start of the form looks like this:

<%= search_form_for @q, :url => search_boards_path, :html => {:method => :post} do |f| %>
	<%= render 'status_fields' %>

In my example, we have a number of different fields that we are searching. With ransack, this is easy because they have a number of different built-in predicates that can be used. So here are two example searches that would be in the form above.

<%= f.label :temp_cycling_true, 'Temperature has been cycled' %>
<%= f.check_box :temp_cycling_true %> <%= f.label :r3v3_lteq, 'R3V3 <= '%> <%= f.text_field :r3v3_lteq, :size => 5 %>

In the first line, I have a checkbox that checks whether the field temp_cycling is set to true. The second line checks that the field r3v3 is less than or equal to the value entered in the box.

I have about 15 of these searches that I use in my search field. However, there was one more search that I wanted to be able to do that was giving me problems.

I have a field called status and it can have one of four different values: good, untested, repaired or broken. I wanted to be able to search, say the good or repaired ones. So, I couldn't use a text field to type in a value. The way to do this was to have checkboxes for each possible value and then OR them in the search query.

I looked at the advanced example provided by the ransack creator, tried to modify it to meet my needs and completely failed. However, I did learn quite a bit about how the syntax of the queries. (I suppose if I were a better programmer, I could have learned this by reading the code. Alas, I had to use the trial-and-error method.)

The params[:q] gives the entire query that is used. In this query, there are other letters:
g = grouping
m = and or or
c = combinator
p = predicate
a = arguments
v = values
which are all hashes that make up the query.

I found that what I wanted to do was add an or grouping to my other search results. I did this using a combination of hidden fields and check boxes. In my main search partial, I render another partial caled status_fields.


<%= hidden_field_tag 'q[g][0][m]', 'or'%> <% ['good', 'repaired', 'untested', 'broken'].each_with_index do |s, i| %> <%= hidden_field_tag "q[g][0][c][#{i}][a][0][name]", 'status'%> <%= hidden_field_tag "q[g][0][c][#{i}][p]", 'eq' %> <% if params[:q].nil? %> <%= check_box_tag "q[g][0][c][#{i}][v][0][value]", "#{s}" %> <% else %> <%= check_box_tag "q[g][0][c][#{i}][v][0][value]", "#{s}", params[:q][:g][:"0"][:c][:"#{i}"][:v] ? true : false %> <% end %> <%= label_tag "q[g][0][c][#{i}][v][0][value]", "#{s}" %>
<% end %>

Line 2 is a hidden field that specifies that this grouping should be or'd together. In line 3 I start a loop that goes through each of my possible values and generates two hidden tags. Line 4 sets the hash "c"=>{"0"=>{"a"=>{"0"=>{"name"=>"status"}} and line 5 adds "p"=>"eq".

Lines 6-10 are putting the check boxes with the options out. The if is necessary for the times when you haven't yet submitted a search. In these cases, none of the boxes will be checked. After you have submitted a search, if a box was used previously, it should be set so you can see what you were searching for. So this bit, adds "v"=>{"0"=>{"value"=>"good"}}} to the query, if the good box was checked.

For a final example, here is what my query looks like when I search for just the good or repaired boards.

Parameters: {"utf8"=>"?", 
"q"=>{"g"=>{"0"=>{"m"=>"or", "c"=>{
"0"=>{"a"=>{"0"=>{"name"=>"status"}}, "p"=>"eq", "v"=>{"0"=>{"value"=>"good"}}}, 
"1"=>{"a"=>{"0"=>{"name"=>"status"}}, "p"=>"eq", "v"=>{"0"=>{"value"=>"repaired"}}}, 
"2"=>{"a"=>{"0"=>{"name"=>"status"}}, "p"=>"eq"}, 
"3"=>{"a"=>{"0"=>{"name"=>"status"}}, "p"=>"eq"}}}}, 
"board_number_eq"=>"", "temp_cycling_true"=>"0", "temp_cycling_false"=>"0",
 "dc_current_without_firmware_gteq"=>"", "dc_current_without_firmware_lteq"=>"", 
"r3v3_gteq"=>"", "r3v3_lteq"=>"", "r2v5_gteq"=>"", "r2v5_lteq"=>"", "r1v2_gteq"=>"", 
"r1v2_lteq"=>"", "dc_current_with_firmware_gteq"=>"", "dc_current_with_firmware_lteq"=>"",
 "rod_slot_eq"=>"", "rod_crate_cont"=>"", "ftk_fiber_eq"=>"", 
"ftk_fiber_destination_cont"=>"", "daq_fiber_eq"=>"", "daq_fiber_destination_cont"=>"", 
"note_cont"=>""}, "commit"=>"Search"}

I am 100% sure that there's a better way to do this, but a better rails programmer will have to tell me what it is. I tried creating my own predicate. But whenever I used a checkbox, I could only get values = 1. I couldn't find a way to make the value = 'good' or 'repaired'.

I found the correct way to do this and I've written a blog post about it here.

Ready for Grout

I didn't feel like posting yet another pictures of the floor in my kitchen. However, it is now done, up to the back door. The next step is grouting. I'm debating whether I should grout or wait until I finish the whole thing before grouting. Either way, the tiles are all in. Took a while, but I think it looks pretty good.

Just Get Started

I could be messing around with arranging tiles on the kitchen floor for years. I finally decided that I just need to set the tile and see how it turns out. If I make a mistake, so be it. I'll figure out a way to fix it. So I spent today setting tile. I have about half of the area done that I want to do. I could have probably done the entire kitchen, but realized that this would block access to my bathroom. This meant that I couldn't get to either bathroom in the house. The upstairs one is blocked because I started sanding the stairs. Blocking the downstairs one just seemed like a stupid idea. So I did half the area, to leave a path. I'll do the other half after I can walk on the tile I just installed. So, here's how things look so far.

The only difficult part was the bit around the floor vent. That is where I accidentally cracked my first tile. So I had to cut a second one. I also had to change the vent because the ones the guys put in didn't fit the vent I had anymore. I took out the bigger vent and messed around with things a bit there. I still need to retape the vent. I thought that I had tape, but couldn't find it. So, later I'll have to finish the vent.

Floor Progress

Here's where I am on the floor.

Last weekend, I started mixing some mortar and attaching the cement boards to the floor. Note the mess. Next time, only buy premixed stuff. It's worth the extra cost. It's way too messy to mix it myself.

Then, I took yesterday off to finish putting down boards, tape all the seams and start laying out tile. It's going to be very hard to have all the seams in the tile line up, so I may change the layout a bit so it's not necessary. But this is basically how it should look.


I'm working on a new app for work. Basically, it's just another app that lets people put up some info and an image to go with it. The difference to this one is that I'm storing information on glass plates, which each have a unique identifier that's not an integer. I didn't want to mess around with changing the primary key from id to something else, because later I'm going to be using it with some other tables. Instead, what I'd like to do is be able to show the image associated with the plate at a url that uses the unique identifier. So, instead of something like plates/12, I could download the info at sample/538-12122. Here's what I did.

First, create a new controller called sample that has one action show. My show method is this:

  def show
    @sample = Plate.find_by_incom_serial_number(params[:incom_serial_number])
    if @sample.nil?
          flash[:notice] = "Sample #{params[:incom_serial_number]} does not exist"
          redirect_to plates_path
      render :layout => nil

Then I changed my routes file with:

  match 'sample/:incom_serial_number' => 'sample#show', :as => :sample

Lastly, I created a sample/show view that just has one line.

<%= image_tag(@sample.scan.url(:original))%>

The other thing I did was change how the image was stored. I really like the paperclip gem, which made it easy for me to store the images in a directory named after the serial number instead of the id. This was done by making some changes in the plate model.

  has_attached_file :scan,
                    :styles => {:thumbnail => '150x150>', :medium => '680x680>'},
                    :path => "#{Rails.root}/public/:attachment/:incom_serial_number/:style/:filename",
                    :url => "/:attachment/:incom_serial_number/:style/:filename"

  # interpolate in paperclip
  Paperclip.interpolates :incom_serial_number do |attachment, style|