This is just a friendly reminder that Time.now on Heroku will give you server time, ie: PDT/PST, and not the local time for your app.
Since usually you want time calculations to be done with the correct zone for your app, just make sure to include 'zone' in your call for Time.now, like so:
Time.zone.now
I have the following in my config:
config/application.rb
config.time_zone = 'Mountain Time (US & Canada)'
Here's the difference for me in Heroku's console:
Time.now #=> 2011-08-20 18:53:06 -0700
Time.zone.now #=> Sat, 20 Aug 2011 19:53:08 MDT -06:00
So, remember, always use Time.zone.now instead of Time.now.
Happy time travel!
Posted by Mike Pack on 08/20/2011 at 07:56PM
When it comes to processing Excel files in Ruby, your options are slim. A quick Google or Github search might reveal roo. roo is an interesting beast. It appears that the RubyGems.org gem file has been taken over and there's some large inconsistencies between the official gem and the Github codebase. The official gem is at version 1.9.5 as of this writing and the Github repo is still stuck at 1.3.11. Don't you hate when that happens?
This guide doesn't involve using the actual roo API, see the Github page or rubyforge page for that.
Without tweaking, roo doesn't work on Heroku. It makes the (bad) assumption that the file system is writable. Check out this line of library to see where the temporary directory gets set. This line assigns a directory within the current working directory as the temporary file store.
On Heroku, that won't be possible. While the roo gem is packed in the dyno, it's directory is read only.
Directly after the linked line is another line that refers to the environment variable ROO_TMP. In version 1.2.0, ROO_TMP was added to alleviate issues where the current directory is inadequate to store temporary files.
To get roo to work on Heroku, create an initializer and set the environment variable to the Rails tmp directory (the only writable directory on Heroku):
config/initializers/roo.rb
ENV['ROO_TMP'] = "#{RAILS_ROOT}/tmp/"
Now, when your app is run either on Heroku or locally, roo will use the Rails tmp directory to store it's files.
Happy rooing!
Posted by Mike Pack on 08/02/2011 at 08:21PM
CanCan is awesome. It lets you manage user abilities easily and provides ways to define complex scenarios. I highly recommend using it for anyone who has more than one user type (like Troll).
Devise is great for authentication. When you have more than one user type as distinct classes, Devise will create current_* to be used in your controllers and views. So, User class corresponds to current_user. Admin class corresponds to current_admin. Troll class (used to identify Trolls under your application's bridge) corresponds to current_troll.
CanCan doesn't work with current_admin and current_troll out-of-the-box. It assumes that current_user is defined and current_user's abilities are defined in the Ability class. What if you want to break this paradigm? It turns out CanCan makes this pretty easy. Here are the current_user and Ability assumptions I am referring to:
def current_ability
@current_ability ||= ::Ability.new(current_user)
end
CanCan defines current_ability on your controller. This grabs an instance of the Ability class for the current user. So it assumes that you have current_user set and you have an Ability class defined. When your user types get more complex than what can be handled by one User model, it's time to make some changes.
Up front, your project might not require many different user types that vary greatly from one another. It might make sense to use Rail's nifty STI (Single Table Inheritance) and add all your abilities to one class. This can be nice in some respect. For instance, all users, no matter which type, can be reference by current_user.
When your user types get too complex to use one User model, your Ability class is too complex as well. In it's most simple form, say you have an Ability class that looks as follows:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.is_a? Admin
# Admin abilities
elsif user.is_a? Troll
# Troll abilities
elsif user.new_record?
# Guest abilities
else
# Basic user abilities
end
end
end
This structure gives you some flexibility in how you define your abilities but it's on it's way to Maintenance Hell, a deep dark place with no exit.
It would be best to define your abilities in different classes. Here we define UserAbility, AdminAbility, TrollAbility, and GuestAbility.
class UserAbility
include CanCan::Ability
def initialize(user)
# Basic user abilities
end
end
class AdminAbility
include CanCan::Ability
def initialize
# Admin abilities
end
end
class TrollAbility
include CanCan::Ability
def initialize(user)
# Troll abilities
end
end
class GuestAbility include CanCan::Ability def initialize # Guest abilities end end
Keep in mind that if your abilities are a subset of another user's abilities, you can inherit from other ability class. So in our case a Troll is a user who lives under a bridge. We don't want the Trolls to talk, so we limit their ability to post comments. Otherwise, they can do everything a User can do.
class TrollAbility < UserAbility
def initialize(user)
super(user)
cannot :create, Comment # More Troll abilities end
end
When you use CanCan's "can? :create, Comment" method, it refers to current_ability to determine whether the given abilities include :create, Comment.
Since CanCan makes the assumption we're working with current_user and strictly Ability, we need to extend the built-in functionality. We do this by instantiating the new Ability classes based on the current user type (defined by Devise). CanCan has a brief wiki post on this topic.
def current_ability
@current_ability ||= case
when current_user
UserAbility.new(current_user)
when current_admin
AdminAbility.new when current_troll TrollAbility.new(current_troll)
else
GuestAbility.new
end
end
Now, when CanCan needs to check abilities (when you call "can? :create, Comment"), your current_ability method will return the appropriate Ability class.
Happy CanCaning!
Posted by Mike Pack on 07/21/2011 at 09:01PM