Favicons for 37signals apps
1 comment Latest by Rene Ruiz Wed, 03 Feb 2010 17:31:22 GMT
If you’re using Highrise or Basecamp and miss not having favicons load in your browser, you can install either of the following greasemonkey scripts that I created.
These will just add a little html to the page to load some favicons that I created from their logos. Will look like this:
Hopefully 37signals will add favicons themselves in the future, but in the meantime. Here you go!
BetterFavicon for Google
Not loving Google’s new favicon too much?
Check out my quick and dirty hack… BetterFavicon for Firefox. (greasemonkey required)
Install it here: http://userscripts.org/scripts/show/40367
Enjoy!
Get to know a gem: Ghost
In my last post, Subdomain accounts with Ruby on Rails explaind, I mentioned that you’d need to modify your /etc/hosts
file to use custom subdomains for development/testing. Apparently, there is a much better way to handle this that I was introduced to by Nathan de Vries. Nathan suggests using a gem that I hadn’t heard of before that bares the name of Ghost (view project on github).
Ghost describes itself as…
“A gem that allows you to create, list, and modify local hostnames in 10.5 with easeā¦”—
If you’ve ever had to modify your /etc/hosts
file for anything local, I highly encourage you to check out this shiny gem.
Installing Ghost
Like most gems, you can just install Ghost with the following command.
~ : sudo gem install ghost
Password:
Successfully installed ghost-0.1.2-universal-darwin-9
1 gem installed
Installing ri documentation for ghost-0.1.2-universal-darwin-9...
Installing RDoc documentation for ghost-0.1.2-universal-darwin-9...
Okay, now that Ghost is installed, let’s see what we can do with it.
Using Ghost for local domains/subdomains
Ghost is fairly straight forward. It’s essentially a friendly wrapper for dscl, which is the Directory Service command line utility for Mac OS X. I’ve never played with that directly, but it seems that with Ghost… I shouldn’t need to. :-)
With Ghost, you can add, modify, and delete entries in the Directory Service by issuing any of the following commands. Let’s start out by running ghost
to see what we have here.
~ : ghost
USAGE: ghost add <hostname> [<ip=127.0.1.1>]
ghost modify <hostname> <ip>
ghost delete <hostname>
ghost list
ghost empty
Okay, let’s see if there is anything already listed.
~ : ghost list
Listing 0 host(s):
Nope. Let’s test this out. First, we’ll try to ping a domain name that we hope doesn’t exist.
~ : ping bigbrown.cow
ping: cannot resolve bigbrown.cow: Unknown host
Alright, now we’ll add bigbrown.cow
with ghost
.
~ : ghost add bigbrown.cow
Password:
[Adding] bigbrown.cow -> 127.0.0.1
As you can see, it required root credentials to do this as it’s system-wide. Let’s now see if we can talk to bigbrown.cow.
~ : ping bigbrown.cow
PING bigbrown.cow (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.047 ms
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.035 ms
^C
--- bigbrown.cow ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.035/0.041/0.047/0.006 ms
Excellent! If we run ghost list
again, we should see this record.
~ : ghost list
Listing 1 host(s):
bigbrown.cow -> 127.0.0.1
We can modify the record to a non-localhost IP as well with ghost modify
.
~ : ghost modify bigbrown.cow 192.168.10.104
[Modifying] bigbrown.cow -> 192.168.10.104
~ : ghost list
Listing 1 host(s):
bigbrown.cow -> 192.168.10.104
I’ll let you play with it yourself as there isn’t much to it. This is a great little addition to my development environment. Thanks to Nathan for pointing it out and to Bodaniel Jeanes for creating this useful gem.
Subdomain accounts with Ruby on Rails explained
DHH recently posted, How to do Basecamp-style subdomains in Rails on SvN and it just happens that I was implementing some similar stuff this last week for a project we’re developing internally.
In our project, not everything needs to be scoped per-account as we are building a namespace for administrators of the application and also want a promotional site for the product. Three different interfaces, with some overlap between them all.
Let’s walk through a few quick steps that you can follow to setup the two interfaces within the same application.
Suppose that we’re going to build a new web-based product and have the following requirements initially.
- We need a promotional site for sign-ups, frequently-asked-questions, support requests, etc.
- When people sign-up for an account, they’ll should have their own unique sub-domain
- There are two different visual layouts (promotional site and the account)
Note: I use RSpec and am going to skip the TDD process here and let you conquer that for yourself. Am using the default Rails commands in this tutorial.
Account model / Database
We’re going to generate a new model for Account, which will be responsible for scoping sub-domains and individual accounts.
account-demo : ruby script/generate model Account subdomain:string
create app/models/
create test/unit/
create test/fixtures/
create app/models/account.rb
create test/unit/account_test.rb
create test/fixtures/accounts.yml
exists db/migrate
create db/migrate/20090111220627_create_accounts.rb
Great, let’s migrate our application.
account-demo : rake db:migrate
== CreateAccounts: migrating =================================================
-- create_table(:accounts)
-> 0.0045s
== CreateAccounts: migrated (0.0052s) ========================================
Before we get too far, let’s make sure that we’re adding an index on this table for the subdomain, as it’ll improve performance in the database as the subdomain will used in SQL conditions quite often.
account-demo : ruby script/generate migration AddIndexToAccountSubdomain
exists db/migrate
create db/migrate/20090111221009_add_index_to_account_subdomain.rb
Let’s open up this new migration file and toss in a UNIQUE INDEX on subdomain
.
class AddIndexToAccountSubdomain < ActiveRecord::Migration
def self.up
add_index :accounts, :subdomain, :unique => true
end
def self.down
remove_index :accounts, :subdomain
end
end
Okay, let’s migrate this bad boy.
account-demo : rake db:migrate
== AddIndexToAccountSubdomain: migrating =====================================
-- add_index(:accounts, :subdomain, {:unique=>true})
-> 0.0047s
== AddIndexToAccountSubdomain: migrated (0.0050s) ============================
Great, we’re now ready to move on to the fun stuff.
Let’s open up app/models/account.rb
and throw some sugar in it.
Data Validation
Because we’re going to be dealing with subdomains, we need to make sure that we’re only allowing people to sign-up with valid data otherwise, there could be issues. URLs need to fit within certain conventions and we need to make it as graceful as possible for our customers.
Let’s make a quick list of what we need to enforce for the subdomain
attributes. This can easily be expanded, but let’s cover the basics.
- Each account should have a
subdomain
- Each
subdomain
should be unique within the application - A
subdomain
should be alpha-numeric with no characters or spaces with the exception of a dash (my requirement) - A
subdomain
should be stored as lowercase
So, let’s update the following default Account model….
class Account < ActiveRecord::Base
end
..and add some basic validations.
class Account < ActiveRecord::Base
validates_presence_of :subdomain
validates_format_of :subdomain, :with => /^[A-Za-z0-9-]+$/, :message => 'The subdomain can only contain alphanumeric characters and dashes.', :allow_blank => true
validates_uniqueness_of :subdomain, :case_sensitive => false
before_validation :downcase_subdomain
protected
def downcase_subdomain
self.subdomain.downcase! if attribute_present?("subdomain")
end
end
Reserved subdomains
In the project that our team is working on, we wanted to reserve several subdomains so that we could use them later on. We tossed in the following validation as well.
validates_exclusion_of :subdomain, :in => %w( support blog www billing help api ), :message => "The subdomain <strong>{{value}}</strong> is reserved and unavailable."
This will prevent people from using those when they sign up.
Controller / Handling Requests
Let’s now think about how we’ll handle requests so that we can scope the application to the current account when a subdomain is being referenced in the URL.
For example, let’s say that our application is going to be: http://purplecowapp.com/
[1]
Customers will get to sign-up and reserve http://customer-name.purplecowapp.com/
. I want my account subdomain to be green.purplecowapp.com
and everything under this subdomain should be related to my instance of the application.
I’ve begun working on my own module, which is inspired mostly by the account_location plugin with some additions to meet some of our product’s requirements.
Here is my attempt to simplify it for you (removed some other project-specific references) and have put this into a Gist for you.
#
# Inspired by
# http://dev.rubyonrails.org/svn/rails/plugins/account_location/lib/account_location.rb
#
module SubdomainAccounts
def self.included( controller )
controller.helper_method(:account_domain, :account_subdomain, :account_url, :current_account, :default_account_subdomain, :default_account_url)
end
protected
# TODO: need to handle www as well
def default_account_subdomain
''
end
def account_url( account_subdomain = default_account_subdomain, use_ssl = request.ssl? )
http_protocol(use_ssl) + account_host(account_subdomain)
end
def account_host( subdomain )
account_host = ''
account_host << subdomain + '.'
account_host << account_domain
end
def account_domain
account_domain = ''
account_domain << request.domain + request.port_string
end
def account_subdomain
request.subdomains.first || ''
end
def default_account_url( use_ssl = request.ssl? )
http_protocol(use_ssl) + account_domain
end
def current_account
Account.find_by_subdomain(account_subdomain)
end
def http_protocol( use_ssl = request.ssl? )
(use_ssl ? "https://" : "http://")
end
end
View gist here (embed wasn’t working right when I tried)
Just include this into your lib/
directory and require
it in config/environment.rb
. (if people think it’s worth moving into a plugin, I could do that)
Including AccountSubdomains
In the main application controller (app/controllers/application.rb
), just include this submodule.
class ApplicationController < ActionController::Base
include SubdomainAccounts
...
end
Now, we’ll want to add a check to verify that the requested subdomain is a valid account. (our code also checks for status on paid memberships, etc… but I’ll just show a basic version without that)
Let’s add in the following to app/controllers/application.rb
. This will only check on the status of the account (via subdomain) if the current subdomain is not the default. For example: purplecowapp.com
is just our promotion site, so we won’t look up the account status and/or worry about the subdomain. Otherwise, we’ll check on the status.
before_filter :check_account_status
protected
def check_account_status
unless account_subdomain == default_account_subdomain
# TODO: this is where we could check to see if the account is active as well (paid, etc...)
redirect_to default_account_url if current_account.nil?
end
end
Current Account meets Project model
When requests are made to an account’s subdomain, we want to be able to scope our controller actions.
WARNING: I’m going to gloss over the following steps because this is just standard Rails development stuff and I want to focus on how to scope your Rails code to account subdomains.
I’ll just say that this product gives each account many projects to do stuff within. I’ll assume that you’ll know how to handle all that and we’ll assume you have a Project model already.
What you will need is to add a foreign key to your table (projects in this example) that references Account. So, make sure that your model has an account_id
attribute with and that the database table column has an INDEX.
We’ll add our associations in the models so that they can reference each other.
# app/models/account.rb
class Account < ActiveRecord::Base
has_many :projects
# ...
end
# app/models/project.rb
class Project < ActiveRecord::Base
belongs_to :account
# ...
end
Okay great… back to our controllers. The SubdomainAccounts module provides you with the current_account
variable, which you can use within your controllers/views. This allows us to do the following in our controllers. For example, if we had a ProjectsController.
class ProjectsController < ApplicationController
def index
@projects = current_account.projects.find(:all)
end
def new
@project = current_account.projects.new
end
def show
@project = current_account.projects.find(params[:id])
end
# ...
end
See, this wasn’t so hard, was it?
Handling layouts
I wanted to highlight one other thing here because I suspect that most projects that fit this will likely need a promotional/resource site where people will sign-up from. In our application, we have two application layouts. One for the main application that customers will interact with via their subdomain and the promotional site layout.
The default layout is just app/views/layouts/application.html.erb
and we have our promotional site layout at app/views/layouts/promo_site.html.erb
. A few of our controllers are specifically for the promotional site while the rest are for the application itself and in some cases, there is some overlap down to individual action within a controller.
What we did was add a few more before filters to our application controller to a) define the proper layout to render, and b) skip login_required on the promo site.
To have the proper layout get rendered, we’re just checking whether the current request was made to the promotional site or not.
class ApplicationController < ActionController::Base
# ...
layout :current_layout_name # sets the proper layout, for promo_site or application
protected
def promo_site?
account_subdomain == default_account_subdomain
end
def current_layout_name
promo_site? ? 'promo_site' : 'application'
end
# ...
end
Our application is using Restful Authentication and we just want to check to see if the current request is made to the promotional site or not. If it is, we’ll skip the login_required
filter. Let’s assume that you have the following before_filter set.
class ApplicationController < ActionController::Base
# ...
before_filter :login_required
We’ll just change this to:
class ApplicationController < ActionController::Base
# ..
before_filter :check_if_login_is_required
protected
def promo_site?
account_subdomain == default_account_subdomain
end
def current_layout_name
promo_site? ? 'promo_site' : 'application'
end
def check_if_login_is_required
login_required unless promo_site?
end
# ...
There we go. We can now render the proper layout given the request and only handle authentication when necessary.
Development with account subdomains
When you begin developing an application like this, you need to move beyond using http://locahost:3000
as we need to be able to develop and test with subdomains. You can open up your /etc/hosts
(within a Unix-based O/S) file and add the following.
127.0.0.1 purplecowapp.dev
127.0.0.1 green.purplecowapp.dev
127.0.0.1 sample.purplecowapp.dev
127.0.0.1 planetargon.purplecowapp.dev
127.0.0.1 lollipops.purplecowapp.dev
127.0.0.1 help.purplecowapp.dev
127.0.0.1 support.purplecowapp.dev
After you edit that file (with root permissions), you can flush your dns cache with dscacheutil -flushcache
(Mac OS X). This will let you make requests to http://purplecowapp.dev:3000/
and http://green.purplecowapp.dev:3000
. This is a convention that our team has begun using for our own projects (TLD ending in .dev
). It’s important to remember that the subdomain must be specified here in order to work for local requests. Unfortunately, hosts files don’t support wildcards (’*’).
Update
You can also use Ghost, which is a gem for managing DNS entries locally with Mac OS X. Read Get to know a gem: Ghost
Summary
I know that I glossed over some sections, but was hoping that the code itself would be the most beneficial for you. Feel free to leave any questions and/or provide some feedback on our approach. Perhaps you have some suggestions that I could incorporate into this so that we can improve on this pattern.
1 yeah, I’ve been reading more Seth Godin recently…
Rails and Business in the 2009 World
The past few months have been difficult for many companies and as a result, some have had layoffs and now there are developers out there looking for new opportunities. I’ve received a few emails from friends and acquaintances in the Ruby on Rails community from people who are hoping to make it as a freelancer until another opportunity comes along. Questions ranging from hourly rates to managing clients has come up. I’m more than happy to offer people advice on this front but always try to invite them to solicit ideas and feedback from a larger group of people. We just happen to have an open forum for all of you that are interested in discussing business-related topics.
Two years ago, I started the Ruby on Rails meets the business world group on Google, which currently consists of nearly 900 members.
So, if you’re an entrepreneur and looking to engage with other business owners, freelancers, or to just listen in on the discussions out of curiosity, don’t hesitate to join the group. There are several of us that would love to share our experiences/lessons with you and also learn from others.
I’d invite you all to check out the discussion archives and start a dialogue with us.
...and as always, if you’re not ready for a bigger group, feel free to drop me a line personally.
Older posts: 1 2