Many Web Applications these days consist of two main parts: the public facing front-end, and the private, secure administration area. When developing with Ruby on Rails, the convention is to try to write code as DRY (don't repeat yourself) as possible and use one single controller for both the public side and the administration side of the site. For certain circumstances this is fine, but I think there is some flexibility here, and recently I've noticed that many times the Admin logic is quite a bit different that the public facing functionality — enough so to warrant its own set of admin controllers and views.
With Rails 2.0+, you can use namespacing to keep the admin controller logic and views separate from the public controller logic and views. I've come to really like the way this helps to keep both sections of the site separate and organized.
I'll demonstrate a very simple example of a web app that provides a way for an Administrator to create, edit, and delete articles with one controller, and for a visitor to view those articles with a separate controller.
Create Your Controllers
After you've created your new rails app, we can proceed by generating our two controllers to access our articles:
# our private admin controller uses namespacing. Notice the Admin:: prefix ./script/generate controller Admin::Articles
# our public controller is created normally ./script/generate controller Articles
We now have two controllers named articles_controller.rb. The public controller lives in app/controllers/ and the private controller lives in app/controllers/admin/ with its respective views in app/views/admin/articles. Once you've created the necessary views, you should have a file structure similar to the image below. Our public controller only needs two methods at this point: 'index' and 'show', while our admin controller will require all 7 standard REST methods.

Create Your Routes
Before either of these controllers can function, we need to setup our routes in the config/routes.rb file.
map.namespace :admin do |admin| admin.resources :articles end map.resources :articles
Take a second and run rake routes on the command line to view all of your current routes. You'll notice you now have 2 sets of articles routes, one of which targets your admin controller, and one which targets your public controller.
Handling Links and Forms
So how does this change how links are handled in the views? It turns out it's not all that complicated thanks to Rails creating a set of admin specific URL generation methods:
# Create a link to the public article page '/articles/:id' <%= link_to 'View Article', article_url(@article) %> # Create a link to view the article in the Admin area '/admin/articles/:id' <%= link_to 'View Article', admin_article_url(@article) %> # Create a link to edit the article in the Admin area '/admin/articles/:id/edit' <%= link_to 'Edit Article', edit_admin_article_url(@article) %>
The same goes for using forms in the Admin area. Instead of using <% form_for(@article) do |f| %> you simply add in the admin namespace, and voila, your forms submit to the /admin/articles controller:
# Create a form that submits to the /admin/articles controller <% form_for([:admin, @article]) do |f| %> .... <% end %>
Securing and Styling Your Admin Controllers
You will no doubt want to secure all of your Admin controllers and require an Admin user to be logged in. If you use a plugin like the popular RESTUL Authentication, you can simply apply the before_filter :login_required to the top of each Admin controller.
Most of the time the Administration area is also styled differently than the public facing area of the site. Here we can simply put something like layout 'admin' at the top of our Admin controllers to use a separate admin.html.erb layout file instead of the public application.html.erb layout file.
class Admin::ArticlesController < ApplicationController before_filter :login_required layout 'admin' ... REST methods here ... end
Wrapping Things Up...
You should now have the beginnings of a web app with a set of self-contained, password-protected Admin CMS controllers prefixed with /admin, and a set publicly viewable front-end controllers. If you're feeling adventurous, you could even extract the controllers, views, routes, and stylesheets into a plugin or a generator if many of the CMS features will be used throughout a variety of different applications.
Developers don't argue. If disagreements do occasionally occur, the topic is usually settled or dropped in favor of something more productive. And designers? These girls have so much work they don't even have the chance to realize that the workplace is a social setting.
However, this firm is constantly expanding, and even a few tiffs may eventually snowball into arguments of unnecessarily enormous proportions. With this said, Ben had the excellent foresight to offer me a chance at educating myself on diffusing difficult situations in the workplace. I found the seminar (Dealing with Difficult People, sponsored by CareerTrack) so enlightening, I'd like to share with everyone some tidbits I have learned.
Approximately 75% of long term job success stems directly from people skills; only 25% derives from technical knowledge. This is huge! So what can you do to ensure successful people skills?
Listen
This is an incredibly simple act, and yet few people take the time to actually stop what they are doing, direct all their attention onto others, and really listen. In order to do this, you need 3 things: the capacity to listen (focus), the desire (or willingness) to listen, and the ability to evaluate what you hear. In other words, you need to WANT to grasp what it is others are saying to you, and go out of your way to do so. People really only want 2 things out of their transactions with others, and that is to be valued and understood. You can show both of these by hearing them out to completion, and then going over what you heard in your own words. A good example of a listener at KSC, and I think the devs will back me up on this one, is Neal; when someone has a question, no matter how minute, he will take out both headphones, turn his chair towards you and give you ALL of his attention. This indirectly lets people know that they matter and creates feelings of appreciation and respect among employees.
Body Language
Interestingly, body language (55% of communication) and tone of voice (38% of communication) affect how people react to you much more than what you actually say (only 7% of communication). The best way to communicate, is to follow the anagram SOFTEN:
Smile
Open Stance
Forward lean
Territory
Eye contact
Nod head
The quickest way to anger someone is to fold your arms, look past him, and lean back in your chair as soon as he walks into your office. The best way to diffuse anger is by modeling behavior you would like others to exhibit. Nothing crossed implies honesty and openness, leaning forward demonstrates interest, and maintaining eye contact builds trust. Speaking softly and slowly, and avoiding personal space and pointing are also good ways to communicate effectively. To learn more, you can visit www.changingminds.org/techniques/body/bodylanguage.htm.
Don't Be Scared!
The best way to handle interactions with people is to do so assertively. If you aren't sure what being assertive entails, keep in mind this basic message: "This is what I think. This is what I feel. This is how I see the situation." It respectfully brings the issue out on the table to be dealt with quickly and with everyone's side of the situation in mind. You can't possibly solve a problem if you aren't even sure what it is! And don't back down! The more you stick to your guns, the more respect you'll get, I promise. If you want a situation to change, you really have to confront it immediately and persistently (as scary as that sounds). A good way to assert yourself with others is by following this sentence pattern:
"For the sake of (name the relationship), when you (name the problem) and this happens (what happens when the problem occurs), I feel (name honest feelings). What I want is (name what you would rather happen). Can we agree?"
What you are looking to do is change the behavior, not the person. It will never help to attack the person who is causing problems, but behaviors can be modified easily. It is also important to add "Can we agree?" at the end of the sentence; you are then in oral contract with the person, and can bring this up if he engages in problematic behavior again. Here's a more detailed example:
"For the sake of our friendship as neighbors, when you let your dog poop all over my yard and then I accidentally roll in it when I play with my kids, I get extremely irritated. What I want is for you to pick up the poop immediately after you let the dog out. Can we agree?"
Remember, it takes at least 6 tries and 21 days to form a new habit, so be patient with those "lazy neighbors."
The most interesting part of the seminar was hearing about CareerTrack's version of the 4 different personality types: thinkers, relaters, directors and socializers. For reference, these would be found in the Myers-Briggs personality test as Introvert Thinker, Introvert Feeler, Extrovert Thinker and Extrovert Feeler, respectively.
Curious as to where you fit in?? Here's a little test you can take to get an idea:


Thinkers
These guys are the meticulous task-oriented individuals of the world. Organized, focused and on-point, thinkers need things to be correct more than anything. In fact, their main goal in the workplace is ACCURACY. Because they can be perfectionists, thinkers are not very good at receiving constructive criticism, and may complain if they have a problem they can't seem to fix. The best way to work with them is to provide them with as much information as you can, the "who, what, where, why, and how" of the situation, so that they can see and understand your process. Also, help them move into "problem-solving mode" by directing their concerns toward working on a solution. Negativity may rear its ugly head when things go wrong, and the best way to counteract it is to bring the thinker back to the task at hand: instead of what CAN'T be done, focus on what CAN.
Relaters
Can't we all just get along?? This is the relaters' motto; their goal is STABILITY and harmony within their relationships. Sensitivity and a need to be liked drives their actions, and they tend to shut down, submit, and exhibit passive aggressive behavior when pressured. When speaking with relaters, be casual and sincere, slow down and listen to how they feel about the issue, make honesty safe by being open and patient to what they have to say, and reinforce to them the fact that you want to get along with them, and that the relationship is important to you. All of these things will help create a safe haven for confidence and honest transactions, and will prevent the break-downs in communication which lead to arguments.
Directors
Also known as people who "get it done," directors seek to be in CONTROL, and always know what they want and how to get it. Life is a giant comprehensive checklist, and great pains are taken to remain focused, direct and blunt on each task and with each individual they come into contact. Trust and respect are very important, and at times, if you don't gain either, you may become invisible. They tend to react aggressively to pressure: they may raise their voices, bully others and become irritable and impatient. When working with them, try to be proactive, task-oriented, and direct in your speech. Also, don't be afraid to stand your ground! They respect those who command it. Show them that you understand and support their goals, and they will become more flexible with your ideas.
Socializers
This one is my favorite. Socializers are direct, charismatic, energetic, creative, people-oriented and adore RECOGNITION. On the flip-side, they are disorganized, sensitive, dramatic, horrible procrastinators and cannot function without recognition. When asking them to do something, focus more on the results rather than the process; they enjoy creative freedom when working on projects. Show flexibility and positive affirmation when talking to them, and don't forget to let them talk as well; this will help them feel valued, allowing them to open up to criticism and suggestions and utilize their enthusiasm and optimism to the max.
Of course, not everyone will fit neatly into one specific category, but you can definitely use these descriptions to better understand yourself and how to communicate with fellow coworkers effectively.
A story about building an application
I was recently working on a Ruby on Rails application that had a section for
sending messages. This sounds pretty easy, right? I started with a
User model and a Message model and some basic
associations:
class User < ActiveRecord::Base has_many :messages end class Message < ActiveRecord::Base belongs_to :user end
Fig.1 - initial User and Message models
But when it came time to actually start building the application, I found this simple model code was not
enough. The devil is in the details, as they say. There was a lot of functionality
I needed to add beyond just a list of messages connected to a User.
1) Filtered views
I needed different views of the messages such as sent messages, drafts, and deleted messages.
How do I determine 'draft' status? Well one way is to fill in a delivered_at date
whenever a message is sent. Then a draft is just a Message
with no delivered_at date.
So after adding that field (and a sender_id and receiver_id) to the database
I went to my messages_controller.rb file and added a few methods that looked sort of like this:
def index @messages = user.messages.find(:all, :conditions => ['delivered_at is not NULL and recipient_id = ?', user.id]) end def sent_mail @messages = user.messages.find(:all, :conditions => ['delivered_at is not NULL and sender_id = ?', user.id]) end def drafts @messages = user.messages.find(:all, :conditions => ['delivered_at is NULL and sender_id = ?', user.id]) end
Fig.2 - initial fragment from messages_controller.rb
2) Pagination
Nobody wants to load a page of 1000 messages at a time, so I needed to be able to break up that list into limited
sized chunks.
I used the excellent plugin will_paginate
for that purpose. Then my controllers methods got a little more verbose:
def index @messages = user.messages.find(:all, :conditions => ['delivered_at is not NULL and recipient_id = ?', user.id] ).paginate(:page => (params[:page] == "" ? 1 : params[:page])) end def sent_mail @messages = user.messages.find(:all, :conditions => ['delivered_at is not NULL and sender_id = ?', user.id] ).paginate(:page => (params[:page] == "" ? 1 : params[:page])) end def drafts @messages = user.messages.find(:all, :conditions => ['delivered_at is NULL and sender_id = ?', user.id] ).paginate(:page => (params[:page] == "" ? 1 : params[:page])) end
Fig.3 - fragment from messages_controller.rb with pagination
3) The ability to flag content (i.e. spam, objectionable content etc...)
What if someone gets spam in the message system - or something objectionable in some other way. Well I need
to filter that stuff out. I added a Flag model and connected that to messages like so:
class Message < ActiveRecord::Base has_many :flags belongs_to :user end
Fig.4 - Message model with flags added
However, at this point my controller methods are starting to look like this:
def sent_mail @messages = user.messages.find(:all, :conditions => ['delivered_at is not NULL and flags.flagged_item_id is NULL and recipient_id = ?', user.id], :include => :flags ).paginate(:page => (params[:page] == "" ? 1 : params[:page])) end def sent_mail @messages = user.messages.find(:all, :conditions => ['delivered_at is not NULL and flags.flagged_item_id is NULL and sender_id = ?', user.id], :include => :flags ).paginate(:page => (params[:page] == "" ? 1 : params[:page])) end def drafts @messages = user.messages.find(:all, :conditions => ['delivered_at is NULL and flags.flagged_item_id is NULL and sender_id = ?', user.id], :include => :flags ).paginate(:page => (params[:page] == "" ? 1 : params[:page])) end
Fig.5 - fragment from messages_controller.rb with flags
I'm looking at some ugly code - with a lot of repetition. How do I pare this down?
Begin Pruning
My first thought is that if anything in my application can be flagged, I should be able to do a little meta-programming to create a find method that will give me only un-flagged items. Ideally I could even send in all the rest of the find arguments exactly the same.
There is the
named_scope
addition to Rails 2.x that does just that - but I also want
something I can add to any class as a Mixin. That way I can write code like this:
Message.unflagged_items.find(:all, :conditions => ['delivered_at is not NULL']) SomeOtherThing.unflagged_items.find(:all, :conditions => ...)
Fig.6 - call to imagined method unflagged_items
The method
with_scope is a good candidate for sending in
some pre-determined find conditions - but leaving it open to add more later. I'm wanting to
add the following method to all my classes that need to be flagged:
def unflagged_items(*args) self.with_scope(:find => { :conditions => 'flags.flagged_item_id is NULL', :include => :flags}) do self.find(*args) end end
Fig.7 - code for imaginary unflagged_items method
How do I do that? Well, I can turn that code into a Module and add it to any class automatically using a little metaprogramming:
module Flaggable def self.included(base) base.class_eval do has_many :flags, :as => :flagged_item, :dependent => :destroy end base.extend(ClassMethods) end module ClassMethods def unflagged_items(*args) self.with_scope(:find => { :conditions => 'flags.flagged_item_id is NULL', :include => :flags}) do self.find(*args) end end end end
Fig.8 - Flaggable module
Any model I put the line include Flaggable in will have that method available.
So if I include it in the User class I've added a method
user.messages.unflagged_items which returns a sort of incomplete version of the find function - with all
the necessary logic to limit the list to unflagged items already filled in. I still have to fill in the :all
or :first or any other :conditions I want. But the function is sort of half-called.
This is a useful thing - getting half-called functions. In functional programming it's called currying. I'll come
back to that in a moment.
Anyway, So now my controller methods now look like this:
def index @messages = user.messages.unflagged_items(:all, :conditions => ['delivered_at is not NULL and recipient_id = ?', user.id] ).paginate(:page => (params[:page] == "" ? 1 : params[:page])) end def sent_mail @messages = user.messages.unflagged_items(:all, :conditions => ['delivered_at is not NULL and sender_id = ?', user.id] ).paginate(:page => (params[:page] == "" ? 1 : params[:page])) end def drafts @messages = user.messages.unflagged_items(:all, :conditions => ['delivered_at is NULL and sender_id = ?', user.id] ).paginate(:page => (params[:page] == "" ? 1 : params[:page])) end
Fig.9 - fragment from new messages_controller.rb
Continue Pruning
It's getting better, but isn't there some way I can pare it down even more? Now I'll go to the
User model. Instead of simply using has_many :messages - since
has_many
supports blocks - I can add some more convenience methods to the User class:
class User < ActiveRecord::Base has_many :received_messages, :foreign_key => 'recipient_id', :class_name => 'Message' do def delivered_and_unflagged(page=1) unflagged_items(:all, :conditions => 'delivered_at IS NOT NULL' ).paginate(:page => page, :per_page => @messages_per_page) end end has_many :sent_messages, :foreign_key => 'sender_id', :class_name => 'Message' do def delivered_and_unflagged(page=1) unflagged_items(:all, :conditions => 'delivered_at IS NOT NULL' ).paginate(:page => page, :per_page => @messages_per_page) end end has_many :draft_messages, :foreign_key => 'sender_id', :class_name => 'Message', :conditions => 'delivered_at IS NULL' do def paginated(page=1) paginate(:page => page, :per_page => @messages_per_page) end end def inbox(page=1) self.received_messages.delivered_and_unflagged(page) end def sent_mail(page=1) self.sent_messages.delivered_and_unflagged(page) end def drafts(page=1) self.draft_messages.paginated(page) end end
Fig.10 - more developed User model
I'm doing pretty well with reduction of code in my controller now. The only ugly bit of code leftover
is the params[:page]... bit - but I can make that slightly better too by factoring it out.
I would like to use params[:page] || 1 but params[:page] returns an empty
string if there is no matching parameter and will_paginate interprets an empty string
as a request for page 0 and returns an error. So I have to use the longer statement with the ternary operator.
Now my controller code looks like this:
def index @messages = user.inbox(figure_page) end def sent_mail @messages = user.sent_messages(figure_page) end def drafts @messages = user.drafts(figure_page) end def figure_page params[:page] == "" ? 1 : params[:page] end
Fig.11 - pruned fragment from messages_controller.rb
I'm happy enough with that. I've made different lists of messages
for the currently logged on User that automatically paginate and filter out flagged items
with just one line of code per method.
3) Next and Previous Message
I'm not done yet though - because the view page of a message needs a next and previous link.
So if the user is looking at a draft - next should be the next draft - not the next sent message - and previous
should be the previous draft - not the previous sent message. Make sense?
One way I could do this
is to have a show_draft method, a show_sent_item method etc... and just call the correct
link from the correct listing page (i.e. the list of all drafts page has links to show_draft,
the sent items page has links to show_sent_item etc...).
There are 2 problems with this though. 1) That is creating several methods for basically one 'show' action. So they
will all be virtually the same code over and over again. 2) I'm using a partial to render the list of messages - so
I'd have to send in some way to create a different link based on the type of filter ('drafts', 'sent mail' etc...)
but I'd rather just call render :partial => "message", :collection => @messages.
I don't want the partial to have to worry about what particular filtered list of messages it happens to
be rendering.
I'm sure there are a lot of ways to solve this. What I came up with was to add a 'from' value as a
parameter for each link_to :action => 'show'
in the partial. That way I could just append
params[:action] to every url and by the time the controller gets the request, it knows where the
request is coming from. This gives me the information I need to respond differently to the show
action depending on that parameter. And leaves that logic out of the view.
In order to get the next and previous messages though,
I needed to be able to identify and generate a list of messages based on the value of
a string (i.e. value of params[:from]).
The code I wrote at first looked something like this and was in the controller:
def show @message = Message.find(params[:id]) # need @messages for previous, next case params[:from] when 'sent_mail' @messages = user.sent_messages(figure_page) when 'drafts' @messages = user.drafts(figure_page) #... end def bulk_action # ... do bulk action # need @messages for previous, next case params[:from] when 'sent_mail' @messages = user.sent_messages(figure_page) when 'drafts' @messages = user.drafts(figure_page) #... end
Fig.12 - fragment of messages_controller.rb with new code
So I've lost some of my simplicity, I'm repeating myself again and my code is in need of pruning.
What I need is a function that returns a function waiting to receive arguments. This is similar to the with_scope
method I mentioned earlier, and the idea of function currying. I need a function that's partially filled out - but not called yet - waiting for
some parameters. This is a good place
to use the the fact that a Method is just another object in Ruby - and create a method to
return whichever User method I want.
A method that returns a method
def get_messages_function(param) # special case of 'index' action if param == 'index' self.method('inbox') else self.method(param) end end
Fig.13 - fragment from User model
returns a method as an object waiting for arguments. So I can put that
code in my User class and I can call it like this in my controller:
def index @messages = user.get_messages_function(params[:action]).call(figure_page) end def sent_mail @messages = user.get_messages_function(params[:action]).call(figure_page) end def drafts @messages = user.get_messages_function(params[:action]).call(figure_page) end def show @message = Message.find(params[:id]) @messages = user.get_messages_function(params[:from]).call(figure_page) # ... end
Fig.14 - fragment from new messages_controller.rb
One last trick
I'm almost done. But I can go one step further in minimization of code. Taking
advantages of the fact that a method can be converted to a block by putting an &
in front of it. In the controller, since all the returned methods are taking that same figure_page
parameter - I can factor that out as a method accepting a block and do something like this:
def index @messages = find_messages(&user.get_messages_function(:inbox)) end def sent_mail @messages = find_messages(&user.get_messages_function(:sent_mail)) end def show @message = Message.find(params[:id]) @messages = find_messages(&user.get_messages_function(params[:from])) # ... end def figure_page params[:page] == "" ? 1 : params[:page] end private def find_messages(&func) yield(figure_page) end
Fig.15 - fragment from another revision to messages_controller.rb
It's odd looking, I admit. I've lost a little readability for the sake of density. But I've left myself very little code in the controller and nothing specific about controllers in the model. That much I like.
Conclusion
So if you ever writing a Ruby on Rails application that has messages that need to be filtered, paginated and include a detail view with a previous, next link - you might be able to glean some code from the article to help get started. Also, today's lesson is that it's sometimes handy to pass around functions as objects.
NOTE: I've included a zip
file of various items related to this article. It includes
some Ruby code as a demonstration which requires a sqlite3 installation.
Also, I used Python to generate this document with all the color-coded sections.
I've included that in case it is of interest
to anyone. It requires the Mako and Pygments packages.

