Updating Pagination When Deleting Items with AJAX In Ruby on Rails

Ruby on Rails & AJAXRuby on Rails

Lately, I seem to be on a tear here with my Ruby on Rails development related posts. I suppose its more for my own documentation, but if it helps someone else out with their own development struggles, even better.

Today, I wanted to find a solution to updating pagination using AJAX. My issue was I use AJAX to update the DOM to remove an item when its deleted. However, the pagination doesn’t update and the listings don’t adjust as you delete them from the middle of the list. My solution, while not rocket science, I think is pretty cool. Basically, just keep using AJAX!

The first thing I need is a div to encapsulate my list. Something like:


     <div id="my_list">
          <%= render :partial => "items", :collection => items %>
          <%= will_paginate items, :renderer => 'ItemLinkRenderer' %>
     </div>

Obviously, that’s my original list inside the my_list div. I’ll get to what ItemLinkRenderer is in a big.

I delete my items from the list by updating the dom from within the delete action. So, something like this:


    @item = Item.find(params[:id])
    if !@item.nil?
        @item.destroy
        render(:update) { |page|
            page.remove dom_id(@item)
        }
    end

This should look pretty straight forward. Delete the item from the database, then remove it from the current document.

But what about updating the pagination and the list? We can remove an item, but how do we adjust the displayed list? Well, just fetch the list of items again.


    @items = Item.paginate :all, :page => params[:page], :per_page => 10
    if @items.length > 0
        page.replace_html "my_items", :partial => "items", :locals => {:items => @items}
    else
        page.replace_html "my_items", "<p>You have no items.</p>"
    end

Ok, so we can render the items. But, if you put this in, then start deleting items, you’ll notice your pagination links get messed up and you have your delete action in the URL. This isn’t good, but, like I mentioned earlier, this is where ItemLinkRenderer comes in. You can define a helper class called ItemLinkRenderer (in item_link_renderer.rb) to render your links properly.


    class ItemLinkRenderer < WillPaginate::LinkRenderer
        def page_link_or_span(page, span_class = 'current', text = nil)
            text ||= page.to_s
            if page and page != current_page
                @template.link_to text, :controller => "items", :action => "list", :page => page
            else
                @template.content_tag :span, text, :class => span_class
            end
        end
    end

This will render your pagination links properly. Hopefully this works out well for anyone who stumbles upon this. Let me know if it does or if you find any errors with what I’ve presented.

Accessing “Child” Associations in Ruby on Rails

Ruby on Rails

My good buddy Dan of SecondRotation.com helped me out with a Rails problem last night. I wanted to access the associations defined on an association of one of my model classes when calling find, in essence, accessing a “child” association. I looked high and low for this, but with no luck so Dan was able to come to my rescue. He said you can do this:


@collection = MyClass.find(:all, :conditions => ["id = ?", params[:id]],
      :include => [:foo => [:bar]], :order => sort)

I guess using :include like this is will tell Rails and ActiveRecord that you want to include the Bar class association in the JOIN you’re doing in SQL so you can enhance your query. Makes sense, but too bad it seems to be hardly documented!

AJAX Pagination and Sorting in Ruby On Rails

I recently decided the account section of a Rails application I’m working on will be completely AJAX. Every link the user would click on would just replace a section of the account page giving them the impression that they’ve never left their account section because the URL isn’t changing. However, I needed to do pagination and sorting for lists of different things. So hunting on Google I went.

I first came across this article over at Rails on the Run about how to do it with will_paginate, prototype, and low pro. I mucked around with it for about 30 minutes and definitely had some struggles, so I went back to Google. I eventually came across this article at Redline Software’s Weblog about an easier way to do it with will_paginate. The nuts and bolts of it is you can use a helper class to write your pagination links for you very easily (I won’t steal their code and post it here. Check out the link to grab it.)!

But I’m not done yet. Remember, I also need to be able to sort my lists of data. So I ran with the same idea presented by Redline Software and came up with this way to write out the anchor tags for my sort links:


def sort_remote_url(text, value)
    value += "_reverse" if params[:sort] == value
    @template.link_to_remote text, {:url => params.merge(:sort => value)}
end

What sort_remote_url does is take in the text for the link and the value of the sort parameter. When you click on the sort link, it works just like the pagination and updates the current view.

Sorting Geographical Based Search Results In Ruby on Rails

Ruby on Rails

I’ve spent the last few months on a Ruby on Rails project for a client. I’m integrating a lot of different applications into it, creating quite the “mashup”. One part of the project requires the ability to search the system for results that fit within a radius of a given postal code. So to do this, I need some sort of searching algorithm or application and a geocoding application for zip code relationships. The search results need to be able to be sorted by the different attributes on the results.

So this meant I needed several pieces. One was a searching module. I found ferret and the acts_as_ferret Rails plugin to do full text searching. From what I could gather online, this was one of the best solutions out there. I want to be able to display distances between zip codes, which I can do using GeoKit. So I find myself off and running.

I was able to do everything successfully from getting the right results back to calculating distances (Side note. If you need a start with acts_as_ferret, there’s a good article here at Rails Envy). However, because the distances between zip codes are calculated and not part of the ferret index, I can’t sort by results. Uh oh…

The solution was, in my opinion, a hack, but it works. What I did was let acts_as_ferret handle sorting for everything except distances (it couldn’t do it anyway, so fine). After I get my results back, I decided, well, I guess I can sort them again, right? So, let’s do this:


@total, search_results = MyModel.full_text_search(@search_term,
  {:sort => s},
  {:include => [:zip_code],
    :conditions => conditions})

This gets me my search results. What about distances? Well, this can be done, even though its an issue performance wise:


for sr in search_results
   sr.destination_distance = round_to(sr.zip_code.distance_to(@search_zip_code), 2)
end

So now each result knows what its distance is from the searched upon zip code. Now what about sorting?


if params[:sort] == "distance"
    search_results = search_results.sort
    @total = search_results.length
elsif params[:sort] == "distance_reverse"
    search_results = search_results.sort
    search_results = search_results.reverse
    @total = search_results.length
end

So now you’re thinking, ok, but how do you know how to sort MyModel? Easy, I decided I’d override <=> for the MyModel class so that a MyModel was less than, greater than, or equal to another MyModel based on distance. So I did this:


def <=>(item)
    if self.destination_distance < item.destination_distance
      return -1
    elsif self.destination_distance > item.destination_distance
      return 1
    else
      return 0
    end
end

So you can see with the example above, I can sort by just calling sort. To reverse the sort, just call reverse after sorting.

So there you go, sorting by distance values. There are definitely drawbacks with this method. First, you have to iterate over all of the search results to set the distance on them. Second, what if I need to sort by some other calculated value? Since I overroad <=> for distance, I can’t really do it for another value. But for now, this works. Maybe I, or someone else, can come up with a better solution.

Uploading and Resizing Images in Ruby on Rails

ImageMagick

In my previous article on using ImageMagick and Mini-Magick to manipulate images in Ruby on Rails, I talked about how to install all of the goodies you’d need to work with images in Rails. I thought I’d expand on this a little bit more and give an example on how I used this cool stuff to upload images and resize them in my Rails application.

You’ll need to set up some HTML code to upload the file to the server. Something like this will suffice:


<% form_tag :action => 'upload', :multipart => true, :id => 'upload_form' do -%>
     <input style="margin-left: 5px;" type="file" id="imageone_file" name="imageone[file]" />
<% end -%>

Now you’ll want to build a model (based on ActiveRecord or not) to save your image for you. For my use, I did based my image model on an ActiveRecord class since I wanted to at least store the file name of the image in my database. But doing that is up to you. Anyway, on to saving the image. In your class, you want to grab the data for the image file in the posted form and save it to the file system. Something like this will suffice:


def image_save(file)
    @file = file
    @content_type = file.content_type.chomp
    @original_filename = base_part_of(file.original_filename)
    @extension = @original_filename[@original_filename.rindex(".") .. @original_filename.length].strip.chomp

    self.file_name = "#{epoch_time()}#{@extension}"

    is_saved = false
    begin
      if self.file
        if self.content_type =~ /^image/
          # Make the directory for the id of the listing if it doesn't exist
          Dir.mkdir("#{RAILS_ROOT}/public/images/originals/") unless File.exists?("#{RAILS_ROOT}/public/images/originals/")

          # What's the new file name?

          # Create the temporary file
          File.open("#{RAILS_ROOT}/public/images/originals/#{self.file_name}", "wb") do |f|
            f.write(@file.read)
            f.close
          end

          # Crop the image to the sizes we need
          crop()

          is_saved = true
        end
      end
    rescue
    end

    return is_saved
end

So what are we doing here? First, we grab the content type of the file, its original file name, and the extension of the file. We save this information out to attributes defined on the model itself, i.e.


attr_accessor :file, :content_type, :original_filename, :extension

Then we check to make sure the directory where we want to save the original file exists, and if not, create it. Then we save the file itself. Once we have the file saved, you’ll notice I call a method called crop(). This is my method that resizes the original image and saves the resized images to the file system. How do I do that? Check this out:


  def crop()
    image = MiniMagick::Image.from_file("#{RAILS_ROOT}/public/images/originals/#{self.file_name}")
    if !image.nil?
      # Resize to 360x360
      image.resize "360x360"
      image.write("#{RAILS_ROOT}/public/images/360x360/#{self.file_name}")

      # Resize to 240x240
      image.resize "240x240"
      image.write("#{RAILS_ROOT}/public/images/240x240/#{self.file_name}")

      # Resize to 120x120
      image.resize "120x120"
      image.write("#{RAILS_ROOT}/public/images/120x120/#{self.file_name}")

      # Resize to 80x80
      image.resize "80x80"
      image.write("#{RAILS_ROOT}/public/images/80x80/#{self.file_name}")

      # Resize to 40x40
      image.resize "40x40"
      image.write("#{RAILS_ROOT}/public/images/40x40/#{self.file_name}")
    end
  end

As you can tell, I needed several different image sizes. You start with the lowest size and work your way down. Not doing this gets you funky sized images. MiniMagick makes it really easy to just open the file and set the new size for the image and then just write it out to where you want it. Nice!

Ruby on Rails Image Manipulation with ImageMagick and Mini-Magick on OS X

I’m working on a Ruby on Rails project that requires users to be able to upload images. At the same time, we want to resize images so that they appear as we want them to on site. To do this, I found that the best solution would be to use the Ruby Gem Mini-Magick which uses ImageMagick. The benefits of using Mini-Magick is that its lighter weight that just using RMagick. So away I went…

I develop my Rails applications on OS X, so I thought I might have most of the stuff already that I’d need since XCode was installed. I had ImageMagick installed from a previous PHP project I had worked on, so I figured I’d just install the Ruby Gem Mini-Magick.. I did and hooked it up to my application. When running it though, it didn’t work. Bummer. I kept getting cryptic errors like:

ImageMagick command (identify "/tmp/minimagick99445-0.jpg") failed: Error Given 256

Great, what does that mean? So I did some sleuthing on Google and didn’t have much luck for about an hour or so. Then I came across this post at MattKing.org. How could I have missed this? A no brainer that you need to have the image libraries installed before compiling ImageMagick (and thus mini-magick).

So with out further ado, here are the steps you need to perform to get this to work!

Step One – Download Image Libaries

Before compiling and installing ImageMagick, you need to have all of the image libraries installed for the image types you want to manipulate. In my case, I wanted to support JPG, GIF, PNG, and TIFF. So I had to install the following libraries:

Once you have those downloaded, you can go on to Step 2

Step Two – Install Image Libraries

Now that you have all of the image libraries downloaded, its time to install them. Extract them using tar and install them!

JPEG

tar xzvf jpegsrc.v6b.tar.gz
cd jpeg-6b
./configure
make
sudo make install

LibPNG

tar xzvf libpng-1.2.24.tar.gz
cd libpng-1.2.24
./configure
make
sudo make install

GifLib

tar xzvf giflib-4.1.4.tar.gz
cd giflib-4.1.4
.configure
make
sudo make install

LibTiff

tar xzvf tiff-3.8.2.tar.gz
cd tiff-3.8.2
.configure
make
sudo make install

Step Three – Download & Install ImageMagick

With all of the image libraries installed, you can download ImageMagick. I installed mine to the /usr/ directory, but I think /usr/local/ would work as well.

tar xzvf ImageMagick-6.3.8-9.tar.gz
cd ImageMagick-6.3.8
./configure --prefix=/usr/
make
sudo make install


Step Four – Install ImageMagick

You’re almost there! Now that you have all of the image libraries installed and ImageMagick installed, you can go ahead and install the Ruby gem Mini-Magick

sudo gem install mini_magick

One note that I found very helpful on Matt King’s blog post was to check to make sure the configure script for ImageMagick could find the image libraries you wanted to be able to use. Check the output at the end of the configure script and make sure yes is present next to each image library you want to use.

You should be ready to rock now! To use mini-magick, insert the following lines into your environment.rb file:

require 'rubygems'
gem 'mini_magick'
require 'mini_magick'


Now that you’re all set up, you can load up a file and do whatever you want to it using Mini-Magick. Perhaps something like this!

filename = "path to your file"
fileout = "path to output file"
image = MiniMagick::Image.from_file(filename)
image.resize "120x120"
image.write(fileout)


There you go! If something doesn’t work, the first thing I’d check is to make sure all of the libraries and ImageMagick were installed properly. Also check file permissions in case for some reason the directory you’re trying to save your file is locked down so the web server user can’t write to it.

One more side note before I finish. I was able to use these exact steps to install ImageMagick and Mini-Magick on my Debian server which I’m using for my public test environment. So, that cool thing about using these steps installing everything from source allows you to duplicate on other *NIX systems.