codrlog

måndag 29 juni 2009

Simple role management with nifty authentication

I recently had to implement role based authentication on top of nifty authentication. Messing about with roles is never much fun
and can easily grow into a complex mess of tangled if statements and what not.
The requirement stated that the three roles had to be implemented, Admin, Privileged User and Regular User, and a user can
only be assigned one role.
Looking at the simplest thing that could possibly work, I started of by adding a field to the users table containing an integer
value:

> ./script/generate migration add_role_type_to_users role_type:integer
> rake db:migrate

However, throwing integers around in the code everywhere isn't much fun and this is where a AppConfig file comes in handy
(see my previous post for more information):

development:
admin: 1
privileged_user: 2
regular_user: 3
...

Now instead of asking whether a user has a role_type of 3, we can ask it if it has a role_type of AppConfig.reqular_user.
But that's still not quite good enough though, since we still have to ask wether current_user.role_type ==
AppConfig.regular_user, a nicer api would be to ask the current user object directly, as in
current_user.regular_user?.
Well, that's easy enough, we just have to add the following methods to app/models/user.rb:

def admin?
role_type == AppConfig.admin
end

def privileged_user?
role_type == AppConfig.privileged_user
end

def regular_user?
role_type = AppConfig.regular_user
end

Now since we don't want a bunch of if statements in our views, it's a good idea to add the following methods to our
app/helpers/application_helper.rb

def admin_area(&block)
if current_user
if current_user.admin?
concat content_tag(:span, capture(&block), :class => "admin_area")
end
end
end

def privileged_user_area(&block)
if current_user
if current_user.admin? || current_user.privileged_user?
concat content_tag(:span, capture(&block),
:class => "privileged_user_area")
end
end
end

def regular_user_area(&block)
if current_user
concat content_tag(:span, capture(&block),
:class => "regular_user_area")
end
end

Now you can restrict your views like so:

<html>
<body>
<% admin_area do %>
<p>Content which only an admin can see</p>
<% end %>

<% privileged_user_area do %>
<p>Content which only an admin and a privileged user can see</p>
<% end %>

<% regular_user_area do %>
<p>Content which all logged in users can see</p>
<% end %>
</body>
</html>

But only restricting access to certain parts of your views is hardly enough, more likely you want to restrict access to certain
controller actions.
So lets start by creating the file config/initializers/authorizer.rb and paste the following snippet in there:

module Authorizer
def self.included(controller)
controller.extend ClassMethods
end

def authorized?(*roles)
if current_user
unless valid_user?(*roles)
session[:user_id] = nil
login_required
end
else
login_required
end
end

def valid_user?(*roles)
statement = returning [] do |s|
roles.each { |role| s << current_user.send("#{role}?") }
end.join(" || ")

return eval(statement)
end

module ClassMethods
def ensure_role(*args)
actions, roles = args.extract_options!, args
before_filter(actions) { |c| c.authorized? *roles }
end
end
end

ActionController::Base.send(:include, Authorizer)


This creates a before filter available to all controllers:

class FirstController < ApplicationController
ensure_role :admin, :privileged_user, :regular_user, :except => [
:index, :show
]
...
end

class SecondController < ApplicationController
ensure_role :admin, :privileged_user, :regular_user, :only => [
:create, :update
]
...
end


That's it for now, I might revisit it later and clean it up a bit and maybe turn it into a plugin.

Prettyfied niftyfied app config

In case you did'nt know already, Ryan Bates (of RailsCasts fame) has released a great collection of generators called
nifty-generators. Among a few other generators, there's one for generating an app config file for storing site wide configuration
options which you access like so:

APP_CONFIG[:config_option]

It's great, but not very pretty, I would much rather prefer something like:

AppConfig.config_option

Not that big of a difference but it fells a little bit nicer though, so here's how you go about accomplishing that goal.

Change the contents of the file config/initializers/load_app_config.rb from this:

raw_config = File.read(RAILS_ROOT + "/config/app_config.yml")
APP_CONFIG = YAML.load(raw_config)[RAILS_ENV].symbolize_keys

to this:

require 'ostruct'
require 'yaml'

raw_config = File.read(RAILS_ROOT+"/config/app_config.yml")
config = OpenStruct.new(YAML.load(raw_config))
::AppConfig = OpenStruct.new(config.send(RAILS_ENV))

söndag 5 april 2009

Error installing mysql gem

Ususally when writing rails apps, I always use sqlite3 in development but for a recent project we use the sphinx search engine
which requires that you use either a postgres or mysql database engine.
Now, I've tried setting up postgres in the past and it was, to say the least, a painful experience and I never really got it working
so I figured I'd give mysql a try.
Being the lazy person that I am, I took a little shortcut and instead of building from source, I downloaded the mysql package
and installed it and everything seemed to work just fine, I could access mysql, add accounts, create databases and so on.
So the next step was to install the mysql gem and this is what happened:

Building native extensions. This could take a while...
ERROR: Error installing mysql:
ERROR: Failed to build gem native extension.

/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby extconf.rb install mysql
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lm... yes
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lz... yes
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lsocket... no
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lnsl... no
checking for mysql_query() in -lmysqlclient... no
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of
necessary libraries and/or headers. Check the mkmf.log file for more
details. You may need configuration options.


After some debugging and a lot of googling I managed to fix it by running this:

sudo bash -c "ARCHFLAGS='-arch i386' sudo gem install mysql -- --with-mysql-include=/usr/local/mysql-5.1.33-osx10.5-x86/include --with-mysql-lib=/usr/local/mysql-5.1.33-osx10.5-x86/lib/lib --with-mysql-config=/usr/local/mysql-5.1.33-osx10.5-x86/bin/mysql_config"


onsdag 11 februari 2009

First Post

This is the first post of many to come in this shiny new weblog.

Future posts will focus mainly on ruby, rails and web-development in general, though some actionscript might sneak in from
time to time as well.

Well, that's about it for now, time to get some work done.