Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions _posts/2011-04-07-using-lockfiles.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
layout: post
title: Using lockfiles in your Ruby based projects
author: Francisco Guzman
email: [email protected]
avatar: c000ffd0c4ed3e23d09cae624d24b525
---

## Good code risk free

How many times have you found yourself in situations where you have to prevent some code from executing? Maybe you have a background task that runs automatically but also can be fired up manually; or chances are that someone tries to run a migration when a deployment script is halfway done. Rings a bell?

You might try to create your in-code solution or guidelines to prevent this from happening, but also, you can use one simple yet safe solution to make your life easier (which is something us Rails and Ruby in general developers love)

Your best friend for a situation like this is a *lockfile*...

### Lockfile to the rescue!!!

Lockfiles are temporary files that are created to work as a flag to indicate that a file is open or a process is running, that simple. So if you're going to run a critical task you really really want to use one of these.

There are a few gems out there that will handle this for you that also require quite a few lines of code to integrate with your code almost seamlessly; the one that I use in my current project is [cleverua-lockfile](http://github.com/cleverua/lockfile).

## Installation

### Rails 2.x

Add the following line to your _config/environment.rb_ file

{% highlight ruby %}
gem config.gem 'cleverua-lockfile'
{% endhighlight %}

Run *rake gems:install* from the command line

### Rails 3.x

Add this line to your _Gemfile_

{% highlight ruby %}
gem 'cleverua-lockfile'
{% endhighlight %}

And run *bundle install*

## Implementation

Now that you have the setup, it's time to add the functionality to your code; say your Rakefile... first, you have to:

{% highlight ruby %}
include Lockfile
{% endhighlight %}

and then, wrap your code with:

{% highlight ruby %}
with_lockfile(PATH_TO_YOUR_LOCKFILE) do
your code ...
end
{% endhighlight %}

Example:

{% highlight ruby %}
include Lockfile

namespace :deploy do
with_lockfile(PATH_TO_YOUR_LOCKFILE) do
desc "Restarting mod_rails with restart.txt"
task :restart, :roles => :app, :except => { :no_release => true } do
run "touch #{current_path}/tmp/restart.txt"
end

[:start, :stop].each do |t|
desc "#{t} task is a no-op with mod_rails"
task t, :roles => :app do ; end
end

desc "invoke the db migration"
task:migrate, :roles => :app do
send(run_method, "cd #{current_path} && rake db:migrate RAILS_ENV=#{stage} ")
end
end
end
{% endhighlight %}

This will create a lock file on this before it yields to your code and will not allow to yield the block again until the first one finishes (or the lockfile no longer exists). Obviously, when code finishes, it will remove the lockfile. That easy.

## Limitations

This will only work in \*nix based systems (but of course that is not a problem for you, is it?).

## Other choices

[lisa's ruby-lockfile](http://github.com/lisa/ruby-lockfile)

[ahoward's lockfile](http://github.com/ahoward/lockfile)

[joshnesbitt's lockfile](http://github.com/joshnesbitt/lockfile)

[seamusabshere's lock_method](http://github.com/seamusabshere/lock_method)

[jcoby's lockfile](http://github.com/jcoby/lockfile)

## Enjoy!!!

147 changes: 147 additions & 0 deletions _posts/2011-10-07-multi-table-models.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
layout: post
title: Multi-table models
author: Francisco Guzman
email: [email protected]
avatar: c000ffd0c4ed3e23d09cae624d24b525
---

## ActiveRecord classes modeled after more than one table

This is an uncommon case, that's for sure. But at any moment, you could find yourself needing to have a model whose columns belong to different tables. This doesn't sound very Rails-ey, but this is ActiveRecord we're talking about, and I hope this helps you if you came here looking for this solution.

### default_scope

The definition in [ActiveRecord::Base documentation](http://api.rubyonrails.org/classes/ActiveRecord/Base.html#method-c-default_scope) shows that all operations on a model will use the defined scope. This is the key for modeling our AR class after more than one table.

## Let's see an example...

First off, let's pretend we absolutely need to do this. With that said, let's assume we have two tables: *persons* and *addresses* and we NEED to have an ActiveRecord class (model) called *customer*

Our ruby class will start by looking like this:

{% highlight ruby %}
class Customer < ActiveRecord::Base
set_table_name :persons
end
{% endhighlight %}

Next, we *SELECT* the fields we want to use in our model, a _here document_ comes in handy:

{% highlight ruby %}
class Customer < ActiveRecord::Base
set_table_name :persons

SELECT_STATEMENT = <<-SELECT
person.id AS id,
person.name as name
person.email as email
addresses.street as street
addresses.city as city
addresses.state as state
SELECT

end
{% endhighlight %}

The other component in our class modeling is the *JOIN* part of our statement:

{% highlight ruby %}
class Customer < ActiveRecord::Base
set_table_name :persons

SELECT_STATEMENT = <<-SELECT
person.id AS id,
person.name as name
person.email as email
addresses.street as street
addresses.city as city
addresses.state as state
SELECT

DEFAULT_JOIN = <<-JOIN
LEFT JOIN addresses ON
addresses.person_id = persons.id
JOIN

end
{% endhighlight %}

Last, we put it all together as follows:

{% highlight ruby %}
class Customer < ActiveRecord::Base
set_table_name :persons

SELECT_STATEMENT = <<-SELECT
person.id AS id,
person.name as name
person.email as email
addresses.street as street
addresses.city as city
addresses.state as state
SELECT

DEFAULT_JOIN = <<-JOIN
LEFT JOIN addresses ON
addresses.person_id = persons.id
JOIN

default_scope select(SELECT_STATEMENT).joins(DEFAULT_JOIN)

end
{% endhighlight %}

And _voilà!!!_, our ActiveRecord class will behave as if its columns were all in the same table.

{% highlight ruby %}
> Customer
=> Customer(id:integer, name:string, email:string, street:string, city:string, state:string)
{% endhighlight %}

Of course, this is not limited to only two tables, you can use as many columns from any tables you may need; let's add a third table

{% highlight ruby %}
class Customer < ActiveRecord::Base
set_table_name :persons

SELECT_STATEMENT = <<-SELECT
person.id AS id,
person.name as name
person.email as email
addresses.street as street
addresses.city as city
addresses.state as state
account.name as account_name
account.owner_id as owner
SELECT

DEFAULT_JOIN = <<-JOIN
LEFT JOIN addresses ON
addresses.person_id = persons.id
LEFT JOIN accounts ON
accounts.owner_id = person.id
JOIN

default_scope select(SELECT_STATEMENT).joins(DEFAULT_JOIN)

end
{% endhighlight %}

{% highlight ruby %}
> Customer
=> Customer(id:integer, name:string, email:string, street:string, city:string, state:string, account_name:string, owner:integer)
{% endhighlight %}

### Constraints

Creating and deleting rows is not conventional. We have to write a bit of code to perform, for example, row deletion (you can not delet rows from a multitable query):

{% highlight ruby %}
Customer.send(:with_exclusive_scope) { Customer.delete_all }
{% endhighlight %}

_with_exclusive_scope_ will 'bypass' or discard the *default_scope* and will give us access to the columns in the *persons* table to perform the operations we otherwise couldn't perform because of the default_scope

## Enjoy!!!