Asshole driven development!
This link was sent around the office a couple of days ago, and I just couldn’t resist posting it. Very amusing. I’ve definitely witnessed a some of these methodologies in practice… http://www.scottberkun.com/blog/2007/asshole-driven-development/
acts_as_emailable
Quite often when building a site, I find that it would be nice if users could email each other within the site. In my previous post I explained how to set up the models and relationships for this. Today I’ve gone one step further and packaged them up into a plugin: acts_as_emailable. Here is how to use it:
Setup
=====
First up you need to install the plugin. This can be done like so:
script/plugin install svn://matt-beedle.com:3396/acts_as_emailable
Then generate the model and migration for the emails table.
script/generate acts_as_emailable_model Email
rake db:migrate
Now you are ready to go. Just add the following line of code to your User model:
class User < ActiveRecord::Base
acts_as_emailable
#
# The rest of you code
end
Usage
=====
After the plugin is installed the following methods become available to your User class.
These methods are the associations. They return what you would expect:
user.users_whom_i_have_emailed
user.users_who_have_emailed_me
At the moment there is only bare-bones functionality, but if any interest is shown or I feel the need, I will build in some more useful methods and make it more complete…
UPDATE
I am really pleased with all the interest and feedback I have received, especially from egze who basically did all the work for these changes. I have now added in three new functions with two aliases as I couldn’t decide on the which I preferred:
These two are really the same, they both return unread mail, although you need to make sure you update the ‘read_at’ field in the emails table when an email is read by the receiver.
user.unread_mailoruser.new_mail
These two return all read emails, although I’m not sure if there will be much use for it.
user.read_mailoruser.old_mail
Finally this returns all users who have emailed or been emailed by the user.
user.all_mail
I have a couple more ideas for new functions but suggestions are welcome. I was also intending to write some example code but haven’t had the chance. I’ll try to do that this week.
UPDATE: EXAMPLE CODE
In a REST based application the code may look something like this to send an email. The current_user that is referred to in the create method of the emails controller can be whatever way you get the current user. In my case I usually use Rick Olson’s Restful Authentication plugin which does this for me.
app/views/emails/new.rhtml
<% form_for :email, @email, :url => emails_path(:user_id => params[:user_id]) do |f| %>
<%= render :partial => ‘form’, :locals => {:f => f} %>
<%= submit_tag 'Send' %>
<% end %>
app/views/emails/_form.rhtml
<fieldset>
<legend>Email</legend>
<%= f.hidden_field :receiver_id %>
<div>
<label for="subject">Subject</label><br/>
<%= f.text_field :subject %>
</div>
<div>
<label for="body">Message</label><br/>
<%= f.text_area :body %>
</div>
</fieldset>
emails_controller.rb
def index
@emails = User.find(params[:user_id]).all_mail
end
def new
@email = Email.new
end
def create
current_user.send_mail(User.find(params[:user_id]), Email.new(params[:email]))
respond_to do |format|
format.html do
flash[:notice] = 'Email Sent'
redirect_to emails_path
false;
end
end
rescue ActiveRecord::RecordInvalid
render :action => 'new'
end
How to model an internal emailing system using self-referential has_many :through associations
I am currently working on a flat sharing site. One of the requirements is that users should be able to email each other within the site, a sort of internal emailing system. When one user emails another user that user has an email sent to their actual email address saying that they have an email waiting for them on the site. The intention being to keep them locked in to the site for as long as possible, and keep those adsense clicks coming in, and to provide a useful service of course! In order to do this a user model needs to be able to get all users who have emailed them and also carry through associated email details.
Anyway, here is the migration and model code to do this:
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.column 'username', :string
t.column 'forename', :string
t.column 'surname', :string
t.column 'email', :string
end
end
def self.down
drop_table :users
end
end
class CreateEmails < ActiveRecord::Migration
def self.up
create_table :emails do |t|
t.column 'sender_id', :integer, :null => false
t.column ‘receiver_id’, :integer, :null => false
t.column ’subject’, :string
t.column ‘body’, :text
t.column ‘created_at’, :datetime
end
add_index(’emails’, ’sender_id’)
add_index(’emails’, ‘receiver_id’)
end
def self.down
drop_table :emails
end
end
The important thing to notice in the User model below is that the “users_who_emailed_me” goes through “emails_as_receiver” with :source => :sender and “users_whom_i_have_emailed” goes through “emails_as_sender” with :source => :receiver. It took me a while to figure that out.
class User < ActiveRecord::Base
has_many :emails_as_sender,
:foreign_key => ’sender_id’,
:class_name => ‘Email’
has_many :emails_as_receiver,
:foreign_key => ‘receiver_id’,
:class_name => ‘Email’
has_many :users_who_emailed_me,
:through => :emails_as_receiver,
:source => :sender
has_many :users_whom_i_have_emailed,
:through => :emails_as_sender,
:source => :receiver
end
class Email < ActiveRecord::Base
belongs_to :sender,
:foreign_key => ’sender_id’,
:class_name => ‘User’
belongs_to :receiver,
:foreign_key => ‘receiver_id’,
:class_name => ‘User’
end
I adapted this code from an excellent article on the excellent blog of Josh Susser which you can find here. If fact this is pretty much the same. However, after reading his post I did not notice the way a couple of the joins worked, which I have tried to point out here using possibly a more relevant example.
UPDATE:
I have now packaged all of this code up into a plugin. For install instructions read this post.