Naked Objects, Point - Counterpoint 2
The Pragmatic Programmer folks have a new book out called Domain-Driven Design Using Naked Objects which caught my attention. The title caught my attention, and I figured the author was using Naked Objects in the same vein as Jamie Oliver as “The Naked Chef” (old series on Food Network). Essentially, the ingredients are used to their full potential, complimenting each other without the heavy use of spice. So I decided to do some research on where this came from. My suspicions were confirmed, and made even more sense when I found the original thesis came from someone at Trinity College, Dublin.
I found the original thesis by Richard Pawson entitled Naked objects where he details the principles behind the concept. The thesis is very readable, as theses go. It is broken up into an introduction, a case study, guiding principles, etc. What I found more interesting was the forward written by the pioneer of the MVC pattern, Trygve Reenskaug. The concept of Naked Objects isn’t exactly new, and it should be lauded for its intent of getting back to the proper intent of object oriented design and programming. Of course, as a technology, and as some of the design constraints of naked objects, the thesis is not without detractors. For example a short paper by Larry Constantine called The Emperor Has No Clothes: Naked Objects Meet the Interface .
The true value in something like Naked Objects is to get you to adjust the way you are thinking. The main concept is to build the complete logic of the system using a finite set of domain objects. The framework is designed to take care of database persistence and user interface. According to the forward in the thesis, the spirit behind MVC is that each view is mapped to only one object, although each object might be mapped to many views. The controller is responsible for mapping the events of the view (inputs, etc.) to the domain model. Essentially the domain model (or model) uses views for output and controllers for input. This is different from the way it was originally described to me and I originally understood the pattern. Pawson argues that the framework can generate the user interface views and controllers automatically. Further advances in the concept also automatically maps the domain model to a relational database using Hibernate .
The software developer side of me likes this concept. It’s less plumbing to worry about. I don’t have to know how to code a user interface. I can get my work done quicker. However, the user interface design side of me loathes the concept because the user interface (admittedly by Pawson) is not easily grasped without training, nor is it particularly accessible. The architect in me is thinking about how I can have my cake and eat it too. Ignoring the problem of database mapping for a moment, the real challenge is in the view/controller (VC) layer. Pawson sites arguments from advocates of Object Oriented User Interface (OOUI) design that there is only one true correct way of representing an object. Yet turns around and presents two: an icon to represent the object and a dialog box to represent the content in the object. In my own project I am working on now, there are at least two representations of every object: the view in a list, and the view of the full content of the object. Nevertheless, there still remains concepts I can leverage.
In some respects Wicket would be an ideal candidate for dynamic generation of VC code. Or at the very least, due to its attempt to treat the view layer in an object oriented manner, some extensions to the application can dynamically generate the controller side. I have some reservations about pursuing that too far at the moment. The real conundrum is in the presentation layer. Managing information and behavior is something that object oriented languages are designed to handle. It is right and good to take advantage of the features of your language to properly model the business domain. However, representing that same information to the user in a way that makes sense to the user is a completely different discipline. I can argue against the principles in OOUI till I’m blue in the face, but that doesn’t solve the fundamental problem.
What we need is a way for the programmers to create the functionally complete object oriented domain model, while your user interface specialists concentrate on their responsibility. While frameworks such as Wicket have tried to address that very problem, it is my personal opinion that they fall a little short. I don’t think the fault lies with Wicket. The fault lies within the current set of W3C standards and differing levels of browser compliance. The W3C is still stuck on a model that prefers static information. If the W3C were to truly pursue a model where the user interface layer is bound to certain objects and the browser makes calls to the server to render these objects we might have a better solution. We’ve already started down this path with AJAX and the myriad of Javascript frameworks to make this work. Needless to say that there is a lot of future work that has to be done in order to truly see a synergy from functionally complete domain models and an object oriented user interfaces.
The goal of such an endeavor should be to allow user interface designers these freedoms:- Create the representations of the objects as they see fit
- Create the rules of how to select the correct view from the different possibilities.
The controller logic should be built into the browser already, in terms of invoking the domain model (or representations of a remote object).
While I’m on the subject of Naked Objects and domain models, I’d like to make a minor rant on Object/Relational Mapping tools. One of the problems is that ORM tools tend to require accessor and mutator methods (getters and setters) for every field that is going to be persisted to the database. While you are technically encapsulating the internal state of the object, in 99.44% of the cases there is no difference in using the accessor and mutator methods and directly accessing the underlying attributes of the class. In a properly designed object, you only need to expose information via accessors that the user is allowed to see, and you only provide mutator methods for what the user is allowed to change. ORM tools require you to violate those principles if you want to persist the information down to the database. Some ORM tools (ActiveRecord) generates these accessors and mutators dynamically for you. That’s great for convenience, but terrible for a properly designed domain model. For the time being, there really is no alternative unless you write the ORM layer yourself. Not recommended if you can help it.
Role Based Authorization in Ruby on Rails
I'm rereleasing all the articles on D-Haven so that when I upgrade the site they have a home.
...
Ruby on Rails is a great web framework, and it does simplify many aspects of writing a dynamic web application. While rails does have a login generator, it does not provide support for role based authorization. This article details how to extend the login to include role based authorization.
Role based authorization provides protection in the sense of enabling users who are trusted at different levels access to what they need at their trust level. For example, you may have an article submission system with all users able to add comments, some users able to submit and edit articles, and administrators able to delete articles. This level of access control enables the system to protect itself from accidental abuse.
Step 1: Enable logins
You will need to install the "login" generator for rails. Just follow the instructions on the LoginGenerator page. At that point, you just need to generate the login with a command similar to this:
$ script/generate login User
Next, set up your database with a table for the logins. The table name needs to be "users", with the set of columns listed below.
- id, integer, auto-increment, and primary key
- login, varchar(20+)
- password, char(40)
Note, you can add additional columns if you want to track more information about the user. I like to have a "Name" and an "Email" column in adition to the required columns. That way I can have a better login message than "Welcome bloritsch" and I can also add a process to reset passwords if the user forgot it. That's a subject for another time.
Another additional thing you should do at this time is to change the "salt" value. The login generator allows an application to salt the passwords so that it is not as trivial as finding the SHA1 hash of the password itself. You'll see a line with @@salt = 'change-me'. All you have to do is to replace the contents of the string with your new salt value.
Step 2: User specific authorization
In my article submission scheme, I wanted to make it that the user who submitted an article is the only one who can edit it. Call it an experiment in ownership. I very well may relax that requirement and make it a role based authorization. However, this is the first step in showing how authorization could work.
To enable user specific authorization, you have to associate a user with a controlled item. We will assume you have a model named "Article" which maps to the database table named "Articles". To make the association, you need to add in the reference from Articles to User. You do that by adding the "user_id" column to the "Articles" table. Next, modify your Article model which should be in app/models/Article.rb. Add the line "belongs_to :user" to the top of the class. You should also add to the "validates_presence_of" list the parameter ":user". The articles class will look something like this:
class Article < ActiveRecord::Base validates_presence_of :title, :user belongs_to :user end
Since it is very inconvenient for your user to have to explicitly add themselves to an article we need to alter the controller that manages the articles to automatically associate the logged in user to the article. If you generated the scaffolding already for your articles, this is fairly simple to do. We need to ensure the user is logged in, and then we need to alter the "create" method to make the association.
- Add the line
before_filter :login_required, :except => [:index, :list, :show]to the top of the class. This will ensure the user is logged in for any action that would alter the data in the articles without forcing them to be logged in to see the articles. - Add the line
@article.user = @session[:user]directly after the line@article = Article.new(params[:article])that was generated from the scaffolding code.
The resulting changes to the Article's Controller should look similar to the following code snippet:
class ArticlesController < ApplicationController
before_filter :login_required, :except => [:index, :list, :show]
# ... skip other generated code ...
def create
@article = Article.new(params[:article])
@article.user = @session[:user]
if @article.save
flash[:notice] = 'Article was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end
# ... skip other generated code ...
end
So far, we have a user automatically associated with new articles. So far so good, but we aren't really protecting against just anyone altering my article. To do that we need to alter the "edit" method to validate the logged in user is the same as what is already associated with an article. To do that we need to add conditional to the code that is already there. For brevity's sake let's take a look at what it should look like:
def edit
@article = Article.find(params[:id])
if @article.user != @session[:user]
flash[:notice] = 'You cannot edit an article you didn\'t upload.'
redirect_to :action => 'show', :id => @article
end
end
What we did here was add an if statement that checks to see if the logged in user matches the article's author. If not, the controller flashes the notice "You cannot edit an article you didn't upload." and redirects the user back to showing the article. If you don't like the flash notice approach (it can get lost at the top of a long article) you can adapt the code to your purposes. So far we are performing a certain level of control over the process. What we are trying to do, of course, is to provide a way to protect against spammers or people with nothing better to do than to destroy your hard work.
Step 3: Adding Roles to the Mix
Now that we have authorization happening and some level of user control, we want to have more granular support so that you have to be an "Admin" role to delete an article. Just to make things interesting we will allow the "Admin" role to alter any article just in case it is largely OK, but the original author had some objectionable language in there.
We need to generate a model for "Role" and two tables in the database. We need two tables to provide for a many to many relationship between users and roles. Any role can belong to many users, and any user can have many roles. The database tables should look like this:
Table: Roles- id, int, auto-increment, primary key
- role, varchar(20), not null
- user_id, int, primary key
- role_id, int, primary key
To allow rails to understand the way they relate to each other in the database, we need to add the directives to associate them. We do this with the line has_and_belongs_to_many and then we cross reference the users with roles and roles with users. It should look something like this:
class Role < ActiveRecord::Base has_and_belongs_to_many :users #add validation for good measure validates_presence_of :role validates_uniqueness_of :role endModel: User
class User < ActiveRecord::Base has_and_belongs_to_many :roles # ... skip remaining code ... end
That's all well and good, but as it stands right now we have to do some fancy checking to see if your user has a particlur role. After all we need to look up the Role object we need, and then see if the array of roles for the User object includes that role. That's alot to ask for the consumer of the system. So lets make it a little easier.
First, let's make it easier to find the Role object that we need. We will assume that we look up a role by the name of the role instead of needing to know what the id is. It makes the code more understandable. To do that we will add a new method to the Role's model. The easiest way to do that is to create a static method overriding the bracket operator like this:
def self.[] (role)
find_first(["role = ?", role])
end
That code allows us to find any role with a very simple call: Role["Admin"]. But wouldn't it be nicer to be a little more Rubyesque in our API and us a symbol for the role? It's really easy to modify the above method to use symbols so that the call looks like Role[:Admin]. Just change the find_first call like this:
find_first(["role = ?", role.id2name])
Now we just want to find out if our user has a particular role. To do that we need a new method in our User model to check that fact. To keep things easy to use we will call it "is_in_role?" The method should look like this:
def is_in_role?(role)
if role.nil?
return false;
end
return roles.include?(Role[role])
end
That way we guard against an empty role and we can validate if the role exists. Instead of asking the developer to constantly find the proper role himself, we include that logic in the method. That way we can validate if the user is in a particular role like this: @user.is_in_role? (:Admin).
Using the Role Checking in Code
Now that we have effectivley added the role checking code we can start using it. Let's start by extending the user specific checking for editing an article to allow anyone with the role "Admin" to edit the article. This is a relatively simple thing to do. First, we need a role with the "Admin" role defined. Next we need a user with the "Admin" privilege associated. At this point you'll have to add those directly to the database.
Blocking actions in the controller
Now, let's take a second look at the "edit" method in the articles controller. All we need to do is add an additional clause to the if statement to check to see if the role is not in "Admin". The altered if statement should look like this:
if @article.user != @session[:user] and not @session[:user].is_in_role? (:Admin)
Hiding parts of the view based on role
Let's say that we want to suppress the "Edit" link in the show.rhtml view for the article if the user does not have the "Edit" role. To do that we need to perform our check in the show.rhtml file. If you recall, anyone can view an article without being logged in, so we do need to verify if the user is logged in before we check their role. I did the check like this:
<% if not @user.nil? and @user.is_in_role? (:Edit) %> <%= link_to 'Edit', :action => 'edit', :id => @article %> | <% end %> <%= link_to 'Back', :action => 'list' %>
To enable @user to work, you'll have to add the line @user = session[:user] to the "show" method in the articles generator. It does make it a little easier to handle.
Protecting known URLs
The last thing we need to do is protect against just anyone deleting any article. If they recognize your app as a Rails based app, they will know that the "destroy" method will delete the article. Its best to protect any action before we take it. To do that we need to alter the articles controller again, this time with the "destroy" method. Something that the scaffold generator doesn't do is provide any flash notices whether a delete was successful or not. We are going to fix this oversight at the same time. The method should look like this when it is done:
def destroy
if session[:user].is_in_role? (:Admin)
Article.find(params[:id]).destroy
flash[:notice] = 'Article was successfully deleted.'
else
flash[:notice] = 'Only an administrator can delete the article.'
end
redirect_to :action => 'list'
end
What we just did was to validate if the user is in the "Admin" role, and if they were we destroyed the article and flashed the success message. If they were not an Admin, they are notified that they can't delete the article.
We're Done
We have a thing of beauty. We can provide fine grained control, and we have a very flexible role based system we can extend. What we didn't do was user administration or adding a way to manage who gets assigned what roles. That's handled manually in the database. Its up to you to write the rest of that. The good news is that you have the ability to make your administration maintained by only authorized users.
