Vidar 20 December 2009
Custom libraries with rails helpers

After upgrading from Rails 2.3.2 to 2.3.3 through 2.3.5 I had a lot of strange errors in my console, the latest being Read error: and it was really annoying as you might figure. If I set

config.cache_classes = false
to
true
it would work so I figured it had something to do with rails being unable to to unload or reload classes or constants. After some debugging I found out that the problem was in my custom helper file:
include ActionView::Helpers
module HtmlHelper  
  def self.htmlify(text)
    html = strip_tags(text)
    html = auto_link(html, :link => :urls, :href_options => {:target => '_blank'})
    html = simple_format(html)
    return html
  end
end

In this file I am using the built-in helpers in rails to format input from the user into html for the web application. I accessed it statically like this from my model

result = HtmlHelper::htmlify(text)

Rails does not like this even if it works in the console and if not caching classes (aka in production). I don’t know if it’s a bug or if I’m doing it wrong. I googled around and found this guy which led me onto the

Singleton
class which lets you instantiate one instance of the class, preventing loading of unloadable classes, making sure there is only one instance of the class.

My helper now looks like this

module HtmlHelper
  class Transformer
    include Singleton
    include ActionView::Helpers
    def htmlify(text)
      html = strip_tags(text)
      html = auto_link(html, :link => :urls, :href_options => {:target => '_blank'})
      html = simple_format(html)
      return html
    end
  end
end

And I’m calling it like this

result = HtmlHelper::Transformer.instance.htmlify(text)

or of course
helper = HtmlHelper::Transformer.instance
result = helper.htmlify(text)

0 comments
Vidar 22 October 2009
Simple format and auto link images

I used to use Redcloth and textilize for all my apps until I found out that it allows too much and I wanted something simpler. The built-in simple_format would convert all newlines to break and paragraph tags, but I needed to be able to insert links and images as well. The auto_link builtin text helper in rails converts all links starting with http or www to clickable links. But what about the images? I needed a way to check if the link was to an image, convert it to an image tag and also extract the height and the width of the image to avoid the web-page jumping around when loading big images.

First I tried doing it server side with regular expressions but I couldn’t find a good way of extracting the image height and width, also I would have to redo it every time the page was edited. So I just used Javascript. This is how I did it using JQuery.

On the server to prepare the text and autolink.

def self.htmlify(text)
    html = strip_tags(text)
    html = auto_link(html, :link => :urls, :href_options =>{:target => '_blank'})
    html = simple_format(html)
    if html[0..2] == "<p>" then html = html[3..-1] end
    if html[-4..-1] == "</p>" then html = html[0..-5] end
    return html
  end

On the client

$.fn.showImageLinks = function(){
  links = $(this).find("a").filter(function(){return this.href.match(/(png||gif||jpe?g||bmp)$/i)});
  $.each(links, function(index, element){
    var image = $("<img>").attr("src", element.href);
    image.attr({height: image[0].height, width: image[0].width});
    $(element).replaceWith(image);
  });
  return $(this);
}
// replace core with the element containing the links
$('#core').showImageLinks();

1. Find all the links ending with image extensions.
2. Replace each of them with image tags.
3. Done. Remove RedCloth/Textile. It’s not worth it.

The image[0].width and image[0].height magically contains the image info after you set the src attribute.

 

0 comments
Vidar 26 June 2009
Batch resizing images

Today I needed to send a bunch of images to my friend, but each of them were over 2MB in size and wouldn’t fit in one email. Here is how I resized all the images in a directory from the command line using the convert command shipped with the image manipulation library Imagemagick

for i in *; do convert $i -resize 50% ${i/\.*/}.jpg; done

You can play with the options, just check the man page for convert. Change the .jpg with another extension to convert to another format simultaneously.

0 comments
Vidar 22 May 2009
Custom counter_cache column

Ever since I created my own blogging engine, I have been struggling with enormous amounts of comment spam. I don’t have time to manually delete the spam, so I installed a spam checking service to figure out if the comments are spam.

My post model has a counter_cache column used to keep track on how many comments each post has so I have that information handy when needed and don’t need to ask the database each time. Using rails, you define the coulumn in a belongs_to associative declaration and the counter is updated automatically in the parent model (google counter_cache rails if you want to know about it). The only problem is that the counter_cache column in rails does not support conditional tracking. I found the same discussion on this guy Douglas’ website and a couple of work-arounds can be found there.

What I ended up doing was almost the same but I didn’t created a second column since I only need to know how many non-spam comments I have. I just disabled the automatic counter cache and decided it’s better to just keep track manually. My comment model now looks like this:

belongs_to :post # removed counter_cache => true
named_scope :approved, :conditions => {:approved => true}

after_save :update_comments_count
after_destroy :update_comments_count

def update_comments_count
  self.post.update_attributes :comments_count => self.post.comments.approved.count
end
1 comment
Vidar 26 April 2009
Blogging engine updated

I updated the blogging engine and comments are now re-enabled. The comments are spam checked using the Akismet web service. I also installed Coderay for highlighting code and updated to rails 2.3.2. The corner jQuery plugin is also updated and now displays corners correctly in Safari.

I updated the restful authentication plugin to reflect the most recent changes. Found a gotcha when installing the plugin: When you clone another repository into an existingo repository, github will not save the files on the server but rather try to point to wherever you cloned it from. When using automated deployment with vlad or capistrano, the files will not be deployed as they are ignored when pushed to github. To resolve the problem, delete the .git and .gitignore files in the cloned repository before pushing.

0 comments
Vidar 29 March 2009
Rails 2.3 nested attributes with Ajax

Rails 2.3 was recently released and with it came a nice feature called nested attributes or nested model mass assignment. It allows you to specify code like the following in your your view (taken from the release notes ):

-form_for @customer do |customer_form|
  = customer_form.label :name, 'Customer Name:'
  = customer_form.text_field :name
  - customer_form.fields_for :orders do |order_form|
    = order_form.label :number, 'Order Number:'
    = order_form.text_field :number
    // The allow_destroy option in the model enables deletion of child records
    = order_form.check_box :_delete
  = customer_form.submit

But what if you need to submit a form or some data using Ajax? Then you can’t use the rails helpers. As it turns out it’s pretty easy once you understand how the helpers generate the form data.

The above code indicates we have a Customer model with a one-to-many relationship to Order. If you run the code you will see that the first level generates input fields with a name attribute like this

customer[name]

just like we are used to from before using normal mass assignment. However on level 2 it looks like this

# When new, you only get the attribute
customer[orders_attributes][0][name]
# When an id exists, you also get a hidden field
# to indicate which record we are working with
customer[orders_attributes][0][id]

This can nest infinitely deep. Let’s say we also have a model LineItems with a one-to-many association to Order, you’ll get

customer[orders_attributes][0][line_items_attributes][0][some_field]

customer[orders_attributes][0][line_items_attributes][1][some_field]


The [0] indicates that this is the first element in the list of many records ie customers.first.orders.first, so a [1] would indicate customers.first.orders.second and so on. This preserves the stored order of elements.

Lastly, it’s now possible to mark for deletion. Just do this

customer[orders_attributes][2][_delete]

to delete the third element in customer.orders.

So what’s so ingenious about this? My models in the project I’m working on are nested 5 levels deep. Using this technique this is my controller code

# in create
@product = Product.create(params[:product])

# in update
@product = Product.find(params[:id])
@product.update_attributes(params[:product])

3 lines of code to create and update 5 levels deep nested associations. I get validations for each model, atomic saves and get to remove 100 lines of code in models. Use these conventions to assemble parameters when you are using Ajax and it’ll just work. Beautiful.

5 comments
Vidar 09 February 2009
Custom script/console

UPDATE: This is a somewhat stupid way of doing it since all you need to do is


irb -r application_name.rb

from the root of your sinatra application. You will then have full access to database and everything else in your application in the console. Read on if you want a pure database console.

I recently started my first Sinatra project and I’m using datamapper as an ORM. Using the script/console in Ruby on Rails to manipulate the database and do manual test for my models is something I have come to depend on. Using the bare bone web framework Sinatra, you don’t have this privelege, or do you?

Using Sinatra requires you to manually load all gems and libs that your application needs. In my application I have the following code (init_datamapper.rb):

require 'dm-core'
require 'dm-validations'

DataMapper.setup(:default, “sqlite3:///#{Dir.pwd}/dev.db”)
DataMapper::Logger.new(STDOUT, :debug)
%w( user post comment tag ).each do |lib|
require File.join(File.dirname(FILE), ‘lib’, lib)
end


The code will import the datamapper libraries and setup the connection, load your models (from my lib directory). What’s nice is that this is exactly what I need to create my fully operational database console!

irb
load 'init_datamapper.rb'

Fire up irb and load the file. Done.

0 comments
Vidar 05 February 2009
Merging models with meta

This might not be the cleanest rest technique, but it will replace 4 controllers with 1 and gain DRY leverage. In my application I have 4 models that have the same validations, attributes and consequently all of those models have their own restful controllers. The code in the controllers are more or less identical except for the model name which is Color, Brand, Category or Size. Here is how I created a single controller to handle all of these models restfully.

I added a parameter[:type] to each request which contains “color”, “brand”, “category” or “size”. From that string, all I have to do is to go from here:

@color = Color.find(params[:id])

to

@model = model_name.find(params[:id])

To do that I defined


before_filter :find_class
attr_accessor :model_name, :type 
  
def find_class
  self.type = params[:type]
  self.model_name = params[:type].camelize.constantize
end

This will take the param[:type], which is a lowercase string initially, camel-case it, and turn it into an ActiveRecord constant you can use for database queries. 4 controllers in one.

0 comments
Vidar 03 February 2009
Haml and pre tag indention

I use Haml in most of my applications. It’s a great tool since it makes it so easy to read and develop your view code. Haml uses indentation similar to Python. However, today, I ran into problems. In this blog, if I write some example code, I use <pre> tags to preserve white space. Using Haml, the <pre> code will automatically be indented in addition to white space inside the tag. I used to have this code in my view:

.post_content
  = @post.body_html

This code will give unwanted indentation. I changed it to this, using the :preserve filter in Haml (read about the Haml Filters ) and it behaves as expected.

.post_content
  :preserve
    #{@post.body_html}

Back to what I was supposed to do today…

0 comments
Vidar 01 February 2009
Ajax, JSON and Rails

I’ve heard about JSON for a long time, but I wasn’t really sure how and why to use it, I tried to as far as possible to avoid Ajax or javascript if I could, it’s too time consuming. But in my current project, I actually have a use case for it. Here is how I use Ajax to get a bunch of objects in JSON format and insert their data into the current DOM.


# In my controller I have this method
def get_data
  colors = Color.all
  respond_to do |format|
    format.js{ render :json => colors.to_json }
  end
end

# in my view I have this element hook
<div id="inventory" />

# in my javascript file I have this code (jQuery)
$.fn.fillSizes = function(){
  element = $(this);
  select = $('<select>')
    .addClass('color')
    .append("<option>Wait...</option>");
  element.prepend(select);
	  
  $.getJSON('/products/get_data', null, function(data){
    $.each(data, function(){
      select.append('<option value="' + this.color.id + '">' 
      + this.color.name + '</option>');
    });
    select.find("option:first").remove();
  });
    
  return element;
}

// Invoke the above function on this object
$('#inventory').fillSizes();

This will create a select box populated with the data from the JSON returned from the server. It will also display a “Wait…” message while loading the data. The cool thing about it is that it is compact. There is only data, no tags.

1 comment
Vidar 29 January 2009
Dragon fruit and mandarins

Every day I have some fruit while I’m working and sometimes it strikes me how lucky I am because it’s just so fresh and good and sweet :)

0 comments
Vidar 28 January 2009
DRY and micro frameworks

Today I started working on refactoring my admin interface for e-commerce solution (it’s really just a simple web shop). The admin interface is now a part of the view just turning everything into text fields and select boxes when you are logged in. This is a great approach if you want to have a great admin interface: it forces you to put in time there too.

Moreover, the code is now very compact, but maybe too much so. Edit, new and show actions now share code. Edit and new are identical but I remove the controls with an action/controller test. My models are also identical in that they all just have a “name” attribute and that must be unique. CRUD operations are handled by the same controller on all models. I even use the same javascript.

This has allowed me to reduce the application to very little code, but I wonder if I will need to break it up in the future. Anyway, since I reduced the code base so much I started wondering why I needed Rails at all and checked out Sinatra. It seems interesting, especially since the tests are so tightly integrated and it uses a lot less memory. Think I’ll have to rewrite an application, taking it for a spin :)

“Don’t be a Rails programmer. Be a programmer.”
— By someone I can’t remember

0 comments
Vidar 27 January 2009
jQuery filters and plugins, more love

I’m learning more about jQuery and its ingenious selectors. Let’s say you have a single select box with an option value of ‘red’. How do you set that one to selected?

The guys over at Scriptygoddess suggested a plugin. But why use a plugin when all you have to do is this:

$("select option:contains('red')")[0].selected = true

And you also gotta love the plugins. This is all I did to make this window have rounded corners:

$("#core").corner();

By now, in the project I’m working on while learning jQuery, I don’t have any inline javascript. Unobtrusiveness obtained.

0 comments
Vidar 26 January 2009
jQuery love

A couple of weeks ago I checked out jQuery. I used it in a couple of applications and removed the old Prototype stuff. I immediately saw that it was smarter than any other library I tried before, but there was a couple of things I didn’t like/find out about.

Until now. One things that always annoyed me was that I had to rebind events to objects whenever I inserted something into the DOM and at the same time being “unobtrusive”. Sometimes it was just a line of code, but it was stupid since I obviously wanted the same event as in ready() on the same objects. Today I was banging my head againts the wall when I saw the “live” function in the API. It does just what I want, binding an event to an object forever, or automatically adds the event to an object of the same class.

I now truly love jQuery.

0 comments
Vidar 19 January 2009
Spam and comments disabled

During the course of the holidays I got hundreds of spam comments. The comments have now been disabled before I can enable the spam protection service.

There should be an automatic service to report the ip-addresses of the spam robots and then automatically attack those ip’s and blacklist them. If just everyone can work together on this. Spam breaks my workflow.

0 comments
Vidar 26 December 2008
Server transition complete

Today I completed moving all my old repositories from svn to git. Git is a joy working with and it’s ten times more flexible than svn.

All my applications are now running at Slicehost and everything is just great. Running a small bash script I can now push, deploy and restart the server with just one command: “deploy application_name”. This is also due to using vlad the deployer which I find easier to use than capistrano because it does what you need and nothing more.

Now it’s time to hit the beach for a couple of weeks! Happy new year!

0 comments
Vidar 25 December 2008
It's Christmas!

I suddenly realized it’s Christmas! I really do not have the Christmas spirit in me, but still it’s here. Should have bought a tree, my bonsai tree isn’t really Christmasy.

I deployed my new blog engine today, simplifying the other ones out there to what I need: Somewhere to jot down random thoughts quickly while they are still in my brain.

Anyway, this post was just to test my blog and to wish everyone a merry Christmas and happy holidays!

0 comments