Rails: Separating Asset Folders by Module
Like a lot of people writing apps with a public frontend and a private admin section I like to separate public and private controllers. This makes it easy to do a number of things, like authentication and keeping code clean. In order to do this, you can use or generate modules like so:
$ ./script/generate scaffold admin::frobnicator
This will generate (among other things)
- app/controllers/admin/frobnicators_controller.rb
- app/views/admin/frobnicators/
It becomes a pain when your frontend images and admin images start to intermingle. One approach for example is to put assets into their own subfolders like so
- public/images/admin
- public/javascripts/admin
- public/stylesheets/admin
This works to keep your asset folders from looking like a clusterfuck getting to mixed up.
The Problem
The problem really comes when you start mucking around in views. You’ve now got to prefix every resource like so:
Then do this in your stylesheets like so:
Ok, so here’s it’s not so bad, do this with a half a dozen controllers and the views (and every link_to) and stylesheets that come along and it starts to become a pain in the ass to prefix everything with admin.
Solution
Fortunately there is a simple solution, overflow compute_public_path. By overflowing compute_pubic_path you can create folders for modules, then reference them as usual.
In my module controllers, I will usually extend them like so:
This allows me to do fun stuff like specify an admin layout for all the admin controllers
layout ‘admin’
...
This has the nice side effect of looking for the admin helper ( helpers/admin_helper.rb ) with all of the module controllers. You can now overflow compute_public_path to your hearts content.
def compute_public_path(source, dir, ext)
dir = "admin/#{dir}"
super
end
...
Results
Use a subfolder for your module and keep it clean.
- public/admin/images
- public/admin/javascripts
- public/admin/stylesheets
Your stylesheets get a little cleaner and…
And so do your views…
You can almost go on thinking the module is a separate app. No more intermingled assets.
Time Ago Method for Ruby on Rails
I recently searched for a Ruby or Ruby on Rails method to display date and time ago ala Typo, ex: “Posted 10 minutes ago”. As it turns out Typo uses Javascript to handle this.
Typo’s js_distance_of_time_in_words_to_now
Typo uses the method js_distance_of_time_in_words_to_now which results in something like this:
<abbr class="published" title="2007-04-05T12:37:00-04:00"><span class="typo_date" title="Thu, 05 Apr 2007 16:37:00 GMT">Thu, 05 Apr 2007 16:37:00 GMT</span></abbr>
This is then parsed out to the users local time using javascript.
Rails Time Conversion API
I was surprised that Rails didn’t include something like this out of the box. The API docs refer to a number of convenience methods for handling time like since, ago, months_ago, years_ago, etc. but these don’t do what I want, instead the may it easy to use Numeric time conversions, for example: 5.month_ago returns a Time object set to 5 months ago.
Time as an adjective
What I really needed was time described in human readable terms with an adjective. Like 5 days ago, 10 minutes ago, 3 years ago, etc. So I googled around and found a reference to a timeago module written for Drupal http://zertox.com/topic/drupal/drupal_module_time_ago. I ported it to Ruby and placed it in my application helper, and it seems to work as intended.
# :start_date, sets the time to measure against, defaults to now
# :later, changes the adjective and measures time forward
# :round, sets the unit of measure 1 = seconds, 2 = minutes, 3 hours, 4 days, 5 weeks, 6 months, 7 years (yuck!)
# :max_seconds, sets the maximimum practical number of seconds before just referring to the actual time
# :date_format, used with <tt>to_formatted_s<tt>
def timeago(original, options = {})
start_date = options.delete(:start_date) || Time.now
later = options.delete(:later) || false
round = options.delete(:round) || 7
max_seconds = options.delete(:max_seconds) || 32556926
date_format = options.delete(:date_format) || :default
# array of time period chunks
chunks = [
[60 * 60 * 24 * 365 , "year"],
[60 * 60 * 24 * 30 , "month"],
[60 * 60 * 24 * 7, "week"],
[60 * 60 * 24 , "day"],
[60 * 60 , "hour"],
[60 , "minute"],
[1 , "second"]
]
if later
since = original.to_i – start_date.to_i
else
since = start_date.to_i – original.to_i
end
time = []
if since < max_seconds
# Loop trough all the chunks
totaltime = 0
for chunk in chunks[0..round]
seconds = chunk[0]
name = chunk[1]
count = ((since – totaltime) / seconds).floor
time << pluralize(count, name) unless count == 0
totaltime += count * seconds
end
if time.empty?
"less than a #{chunks[round-1][1]} ago"
else
"#{time.join(’, ‘)} #{later ? ‘later’ : ‘ago’}"
end
else
original.to_formatted_s(date_format)
end
end

This yields results like 1 week, 18 hours ago. I think it works pretty well but I wanted it a little more vague like how Typo does it, so I ported the Typo methods from Javascript to Ruby.
Ruby on Rails Time Ago
# :start_date, sets the time to measure against, defaults to now
# :date_format, used with <tt>to_formatted_s<tt>, default to :default
def timeago(time, options = {})
start_date = options.delete(:start_date) || Time.new
date_format = options.delete(:date_format) || :default
delta_minutes = (start_date.to_i – time.to_i).floor / 60
if delta_minutes.abs <= (8724*60) # eight weeks… I’m lazy to count days for longer than that
distance = distance_of_time_in_words(delta_minutes);
if delta_minutes < 0
"#{distance} from now"
else
"#{distance} ago"
end
else
return "on #{system_date.to_formatted_s(date_format)}"
end
end
def distance_of_time_in_words(minutes)
case
when minutes < 1
"less than a minute"
when minutes < 50
pluralize(minutes, "minute")
when minutes < 90
"about one hour"
when minutes < 1080
"#{(minutes / 60).round} hours"
when minutes < 1440
"one day"
when minutes < 2880
"about one day"
else
"#{(minutes / 1440).round} days"
end
end

The result is a nice vague string that rounds to the nearest unit of measure without being overly specific.
Enjoy!
Rails Automatic Scoping ala Userstamp
I am working on an app to keep track of my customer base, projects, tasks, communications with customers, and some other functionality that you might find in CRM & Project Management apps. So I have a simple interface with a select box of all customers which I have placed in the header. Selecting a customer there acts as a global filter meaning it should do the following:
- Limit the results I get in my lists of projects, contact reports, people, and tasks so that I can focus on one customer at a time.
- I would also like to be able to leave that variable empty so that if I need to look at complete lists I can.
- One last thing, it should do this without adding tons of code all over the place and without brute force.
Userstamp Method
The Userstamp plugin gives us a good example of setting a current_user variable using the application controller and a session variable.
cattr_accessor :current_user
end
The following code allows our models to refer to the User model because the session variable cannot be accessed from within models.
before_filter do |c|
User.current_user = User.find(c.session[:user].id) unless c.session[:user].nil?
end
end
Well, in my case I am not working with a User but a Customer. So we’ll take what we’ve learned then apply it. Since my method is a cheap and easy ripoff of Userstamp I’ll name my method Trampstamp (because it’s cheap and easy).
Trampstamp Method
In my method the first thing that needs to be setup is the model to receive the current_customer. I’ll set that up in my Customer class.
cattr_accessor :current_customer
has_many :people
has_many :projects
...
Now I need a way to store that value.
def filter
if params[:customer_id]
session[:customer] = Customer.find(params[:customer_id])
else
session[:customer] = nil
end
redirect_to :back
end
...
And a way to retrieve the session value and set the current customer.
before_filter do |c|
Customer.current_customer = c.session[:customer] unless c.session[:customer].nil?
end
...
And finally a way to limit my search results without having to add conditions all over the application.
belongs_to :customer
def self.find(*args)
unless Customer.current_customer.nil?
self.with_scope(:find => { :conditions => [‘customer_id = ?’, Customer.current_customer.id] }) { super }
else
super
end
end
end
Result – Judo Style automatic scoping
When I have a current_customer set,Project.find(:all)will yield the following query:
SELECT * FROM projects WHERE (customer_id = 1)
When no current_customer is set, it is business as usual. I suppose it would be really easy to add another method to automatically add :customer_id to project.create, and others. Maybe I’ll release that as a plugin so it’s even DRYer than this.
Here is the complete contact report list with no customer set.

This is the same list with the customer set.



