Tigraine
Daniel Hoelbling talks about programming

Removing delete and destroy in Rails models

February 3rd, 2012 . by Daniel Hölbling

Today I had an interesting feature request to implement: Get rid of all deletions and replace them with a deleted flag in the DB.

It’s the usual story: Nobody really does deletes but rather everything is put into the DB in case it’s needed at some later point. Unfortunately I didn’t know about this particular feature until after I had finished most of the coding for the Rails application so going through the code and removing all destroy code from controllers was pretty much out of the question.

Instead I remembered reading about Modules in Ruby and how they can bolt on functionality to Classes.

Turns out it’s totally trivial to remove deletion from Rails Models with 10 odd lines of code in a completely transparent an unobtrusive way:

module NotDeleteable
  def destroy
    unless self.respond_to? :deleted
      raise MissingMigrationException
    end

    self.deleted = true
    self.save
  end

  def delete
    self.destroy
  end

  def self.included(base)
    base.class_eval do
      default_scope where( :deleted => false )
    end
  end
end

class MissingMigrationException < Exception
  def message
    "Model is lacking the deleted boolean field for NotDeleteable to work"
  end
end

This will override the default destroy/delete method provided by ActiveRecord::Base and also install a default_scope into the Model class so Rails will by default append WHERE deleted = false to all SQL queries made through the ActiveRecord Query Interface

You use this by simply including this module inside your Model class:

class User < ActiveRecord::Base
  include NotDeleteable
end

Did I mention that I really like Ruby?

Word of warning:
I have no clue if I am breaking :dependant => :destroy on ActiveRecord relations in any way, but I suspect it should still be alright.


New team member on dotless: Luke Page

December 12th, 2011 . by Daniel Hölbling

You may have noticed that my blog is filling itself slowly with stuff about Ruby and Unix in general. This has to do with the fact that for now 3 months I am working on Rails full time with very little to no .NET work in between.

While this is awesome for me and I am really enjoying it – it also means that I don’t have a .NET dev environment available at work and quickly merging in a pull request and testing it has become quite a hassle. So dotless has had quite some open tickets that where already fixed but where not yet merged into the mainline due to me lagging behind on responding to pull requests.

Fortunately, most/all pull requests have come from our very active Luke Page who has been busily fixing bugs and contributing features – so adding him to the dotless core team is a logical choice to cut down on the lag I or James where inducing into the process.

Since James felt the same we are happy to announce that Luke Page is now part of the dotless team with commit access and everything. So, welcome onboard Luke! Thanks for being part of dotless :)


Rails .to_json nested includes and methods

November 17th, 2011 . by Daniel Hölbling

Defining assocations to be included in the rendered Json in Rails is pretty easy. You just use the options hash to define an include in to_json and by voodoo magic to_json will also traverse the ActiveRecord assocation and render the associated entity as a nested element in the Json. The code in question is quite simple:

@object.to_json({:include => :assocation_a})

By default Rails will only include attributes in to_json so if you want to also serialize the return value from a method (for example if you want to concatenate some attributes or compute something) you can do so through the options hash:

@object.to_json({:methods => :my_method})

You can also combine them easily if needed:

@object.to_json({:include => :assocation_a, :methods => :my_method})

But this calls the my_method on @object. How do you tell to_json to invoke my_method on @object.assocation_a?

Not trivial, but it works:

@object.to_json({:include => { :assocation_a => { :methods => :my_method }})

It gets spicy when you already had multiple includes in an array like this:

@object.to_json {:include => [ :assocation_a, :assocation_b ]}

And now you only want to include my_method in assocation_a and not assocation b.
simply replacing :assocation_a by a hash like before isn’t going to work.
It took me some time but this is what I came up with:

@object.to_json {:include => { :assocation_a => { :methods => :my_method }, :assocation_b => {} }}

Did I mention that I am amazed by how flexible the syntax is here?


Invoking rails i18n.translate with pluralization

November 14th, 2011 . by Daniel Hölbling

One very nice thing about the internationalization library in Rails is it’s support for pluralization by default.
Instead of having to figure out how to display a plural or a singular when selecting what message translation you display you can just call translate and it will figure out if the plural or the singular form should be used.

Defining the two forms is done inside your config/locale/.yml like this:

found:
  one: "Found one result"
  other: "Found %{count} results"

This works out of the box for things like validation errors or ActiveRecord models. But it refused to work on me for my own little custom array I was outputting.

Turns out I was just not calling it correctly.
I18n.translate simply takes a hash of options – expecting one of these options to be count so it can perform the pluralization check (by default this is entry[count == 1 ? 0 : 1]).

So in order to use this with a custom array you need to write something like this:

I18n.translate :found, :count => myarray.length

Since this allows you to pass any value you want through the arguments hash you can easily construct translations like this:

found:
  one: We could not find anything matching your query %{query}
  other: We found %{count} items

I18n.translate :found, :count => myarray.length, :query => "your query"

Execute callback once multiple ajax requests finish with jQuery

September 26th, 2011 . by Daniel Hölbling

I just stumbled upon the problem of having to load multiple resources during page initialization. During the initial load I wanted a way to prevent any animations and other fancy stuff from happening but rather only create the page as soon as possible.

Since I couldn’t find any way in jQuery (there are no combined callbacks) I decided to implement this using the deferred promise concept jQuery 1.5 introduced.

Deferred basically means “I represent a ajax call that has happened or is happening – ask me about it and I’ll tell you when it’s done or was done already“.
So implementing my little wait functions was rather trivial:

I used CoffeeScript for this, but the resulting JavaScript is also quite readable and you can use that too.

What this does is provide you with the awkwardly named function waitForAjaxRequestsToComplete that takes one callback parameter and an array of jQuery promises.
Once all promises succeed or fail the callback will be called and the first parameter will contain information about failures.

Sample usage:

waitForAjaxRequestsToComplete(function (info) {
  console.log("requests failed " + info.failures)
  console.log("requests succeeded " + info.successes)
  console.log(info.failed) //contains all failed promises
}, [ $.ajax(...), $.ajax(...), $.ajax(...) ]);

Securely managing database.yml when deploying with Capistrano

September 25th, 2011 . by Daniel Hölbling

The more I venture into Ruby land the more magic Unicorns I find on the way. The wonders of SSH still seem totally outlandish to someone used to do deployments by RDPing into a server and xcopying a directory structure into your IIS folder.

But here I am and learning the ways of Capistrano and how deployments to multiple servers really should work.

Naturally I ran into issues I’ll detail a bit later, but one of my major problems with my Rails deployment was the different database.yml between my production and my dev environment.
Since the repository is in a shared location I could not put the production server mysql password into the config as it would be available to anyone with read access to the repository.
This may be something you can get away with in a corporate environment, but if you plan on ever open-sourcing your project you should make sure you don’t put production passwords into your repository :) .

My solution to that problem is quite simple: I ssh’d into my server and put a “production” database.yml into the home directory of my deployment user and added the following task to my Capfile:

namespace :db do
  task :db_config, :except => { :no_release => true }, :role => :app do
    run "cp -f ~/database.yml #{release_path}/config/database.yml"
  end
end

after "deploy:finalize_update", "db:db_config"

The after statement tells Capistrano to run the db_config task right before finishing the code update, but before running any migrations in case you run cap deploy:migrations (capistrano process).
And during every deployment I overwrite the database.yml from the repo with the one on the server.

I also added a assets:precompile task since Capistrano won’t run the precompilation of Rails assets out of the box (you need RVM integration for this though):

  task :precompile, :role => :app do
    run "cd #{release_path}/ && rake assets:precompile"
  end
after "deploy:finalize_update", "deploy:precompile"

Et voilá: I can now run cap deploy:migrations from my dev machine and it will automatically connect to my release server, pull the code out of the git repository, compile the assets and migrate the database to a new version.
And it will even roll-back to the old version if something goes wrong along the way.

Ps: I also struggled at one point with the SSH keys for the git repository. Since the deployment user on the server has no own private key I was inclined to generate one and add it to my git server’s allowed keys list.
But that’s apparently the wrong way to go about things. The right thing to do here is to simply enable agent forwarding so the server will forward any questions about keys to your dev machine that should have the appropriate set of keys available.

ssh_options[:forward_agent] = true


The right thing to do: Rails deployment on Passenger

September 22nd, 2011 . by Daniel Hölbling

Today I deployed my very first Ruby on Rails application to a production server and failed. And I didn’t fail so much because Rails is hard to deploy (the opposite is true) – but rather because Rails prevented me from doing something stupid!

But I digress, let’s start at the beginning:
I already wrote about my server setup last week and today was the time to deploy a Rails3 application to my Apache/Passenger.

The Passenger documentation really made things easy. I just cloned my repository to a folder and setup everything accordingly. Then I did the obvious chown -R www-data:www-data . so the www-data user Apache uses has full access to the whole application directory and fired up my browser.

And it didn’t work. Passenger was obviously running as I was able to load files from the /public folder without a problem, but the routes inside my application only returned errorcode 500.

Why? Simple: Passenger by default assumes you are running in a production environment so it will load that up from your database.yml. So I had to make sure that those settings where correct (easy one).
Next problem on the list was that any rake commands you are running on the server still assume you are using the development environment. So in order to create your databases and migrate the schema you have to do a:

  rake db:create RAILS_ENV="production"
  rake db:migrate RAILS_ENV="production"

Otherwise db:create and db:migrate will always run against the development environment settings inside database.yml.

But once that problem was solved, I was still getting 500s.. So I opened the /log/production.log and found another gem I really liked:
Rails refused to start at all because I was running in production without having precompiled the assets (javascript/css)!

A simple rake assets:precompile fixed that, but boy was I impressed.
I knew about the different environments in Rails and saw how they would affect deployment scenarios. But having Rails just throw exceptions at me if I try to do something so obviously stupid as to not have JavaScript and CSS minified and merged together into one file is really awesome.
Especially when coming from a .NET environment where up to this date the <= .. => syntax does not safely URL-Encode the value (and it took them 10 years to release the safe <: ..> alternative, having a framework that throws you into the pit of success is exhilarating.


jQuery.validate and multiple forms on one page

August 31st, 2011 . by Daniel Hölbling

I like generic, convention based approaches to infrastructure so in most of my projects you’ll find something like this in the main javascript file:

$('form').validate();

There is only one slight problem: jQuery validate doesn’t work like most jQuery plugins and won’t operate on the matched set, but rather only on the first element.

So if you want to validate multiple forms you have to call each form individually through each

$('form').each() {
    $(this).validate();
}

jQuery.validate and Microsofts unobtrusive validation don’t play well together

August 26th, 2011 . by Daniel Hölbling

I was banging my head against the wall for more than three hours, reading jQuery.validate‘s code, trying to figure out why my supplied errorPlacement or highlight callbacks where not called.

The code in question looked perfectly fine and came pretty much straight out of the docs:

$('form').validate({
    debug: true,
    errorPlacement: function () {
        console.log("errorPlacement", this, arguments);
    },
    highlight: function () {
        console.log("highlight", this, arguments);
    },
    success: function () {
        console.log("success", this, arguments);
    }
});

As you can see I was still stuck in the “let’s see what we can do from these callbacks” phase of implementing my own automatic validation scheme for a project.
Guess what the above did? Nothing! And I mean really nothing. The validation worked as expected with my validation classes, but none of my callbacks got ever called (meanwhile supplied arguments like messages etc worked like a charm).

After 3 hours I finally came onto something when I couldn’t find the validation classnames that where put on my elements inside the jQuery.validation code I was looking at.
Turns out: Microsoft’s jquery.validation.unobtrusive.js cripples jQuery.validation so that most of it’s configuration options simply don’t work any more. Neither with the $.validate.setDefaults() nor with the $(‘#element’).validate() methods.

Needless to say that I almost broke into tears when my stuff was working fine once I removed the unobtrusive script from the page.

I do like the unobtrusive stuff – it serves it’s purpose inside the MS MVC niche. But in this case I was not using unobtrusive on that form at all! So I simply did not expect the library to mess with jQuery.validate in any way as there was no unobtrusive validation stuff going on.


Localized Fullcalendar options

August 23rd, 2011 . by Daniel Hölbling

I usually try to avoid using too many jQuery plugins, especially for rather simple stuff like this. But displaying a calendar is pretty tricky (believe me – I wrote one using GDI+) and Fullcalendar has saved me countless hours so far.

One issue that cropped up now is that we needed our Fullcalendar to be localized in German. As with most things I came across so far, Fullcalendar already supports it – but the documentation is a bit cryptic on the issue so here is my localized options hash:

var localOptions = {
	buttonText: {
		today: 'Heute',
		month: 'Monat',
		day: 'Tag',
		week: 'Woche'
	},
	monthNames: ['Jänner','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'],
	monthNamesShort: ['Jän','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sept','Okt','Nov','Dez'],
	dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'],
	dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa']
}

$('#calendar').fullCalendar($.extend({ ... your options ... }, localOptions));

« Previous Entries