Merge branch 'master' of github.com:diaspora/diaspora into production

Conflicts:
	app/models/user.rb
	spec/models/aspect_spec.rb
This commit is contained in:
Raphael 2010-09-15 17:24:00 -07:00
commit 33048fe6db
32 changed files with 240 additions and 101 deletions

View file

@ -1,9 +1,16 @@
## Commit Guidlines
You are welcome to contribute, add and extend Diaspora however you see fit. We will do our best to incorporate everything that meets our guidelines.
All commits must be tested, and after each commit, all tests should be green before a pull request is sent. Please write your tests in Rspec or Test-Unit.
GEMS: We would like to keep external dependencies unduplicated. We're using Nokogiri, and Mongomapper, and EM::HttpRequest as much as possible. We have a few gems in the project we'd rather not use, but if you can, use dependencies we already have.
# Diaspora
The privacy aware, personally controlled, do-it-all, open source social network.
**DISCLAIMER: THIS IS PRE-ALPHA SOFTWARE AND SHOULD BE TREATED ACCORDINGLY.**
These instructions are for machines running [Ubuntu](http://www.ubuntu.com/) or Mac OS X.
These instructions are for machines running [Ubuntu](http://www.ubuntu.com/) or Mac OS X. We are developing Diaspora for the latest and greatest browsers, so please update your Firefox, Chrome or Safari to the latest and greatest.
## Preparing your system
In order to run Diaspora, you will need to download the following dependencies (specific instructions follow):
@ -109,14 +116,12 @@ If you have never used github before, their [help desk](http://help.github.com/)
## Running Diaspora
### Install required gems
To start the app server for the **first time**, Bundler needs to grab Diaspora's gem depencencies. To allow this, run `bundle install` from Diaspora's root directory.
It is important to run a bundle install every so often, in the event of a new gem dependency. We will make sure to make an announcement in the event of a gem change.
To start the app server for the first time, you need to use Bundler to install Diaspora's gem depencencies. Run `bundle install` from Diaspora's root directory. Bundler will also warn you if there is a new dependency and you need to bundle install again.
### Start Mongo
After installing the above, run `sudo mongod` from where mongo is installed to start mongo.
Diaspora will **not run** unless mongo is running. Mongo will not run by default, and will need to be started every time you wish to use or run the test suite for Diaspora.
Diaspora will not run unless mongo is running. Mongo will not run by default, and will need to be started every time you wish to use or run the test suite for Diaspora.
### Run the app server
Once mongo is running and bundler has finished, run `bundle exec thin start` from the root Diaspora directory. This will start the app server in development mode[.](http://bit.ly/9mwtUw)
@ -124,10 +129,9 @@ Once mongo is running and bundler has finished, run `bundle exec thin start` fro
### Testing
Diaspora's test suite uses [rspec](http://rspec.info/), a behavior driven testing framework. In order to run the tests, run `bundle exec rspec spec`.
## Resources
We are maintaining a [public tracker project](http://www.pivotaltracker.com/projects/61641) and a [wishlist](#).
We are maintaining a [public tracker project](http://www.pivotaltracker.com/projects/61641) and a [roadmap](https://github.com/diaspora/diaspora/wiki/Roadmap). Also, you can file [bug reports](https://github.com/diaspora/diaspora/issues) right here on github.
Ongoing discussion:

View file

@ -0,0 +1,30 @@
# Copyright (c) 2010, Disapora Inc. This file is
# licensed under the Affero General Public License version 3. See
# the COPYRIGHT file.
class RegistrationsController < Devise::RegistrationsController
def new
super
end
def create
begin
user = User.instantiate!(params[:user])
rescue MongoMapper::DocumentNotValid => e
user = nil
flash[:error] = e.message
end
if user
#set_flash_message :notice, :signed_up
flash[:notice] = "You've joined Diaspora!"
#redirect_to root_url
sign_in_and_redirect(:user, user)
else
redirect_to "/get_to_the_choppa"
end
end
def update
super
end
end

View file

@ -11,13 +11,13 @@ class Person
include Encryptor::Public
xml_accessor :_id
xml_accessor :email
xml_accessor :diaspora_handle
xml_accessor :url
xml_accessor :profile, :as => Profile
xml_reader :exported_key
key :url, String
key :email, String, :unique => true
key :diaspora_handle, String, :unique => true
key :serialized_key, String
key :owner_id, ObjectId
@ -30,13 +30,13 @@ class Person
before_destroy :remove_all_traces
before_validation :clean_url
validates_presence_of :email, :url, :profile, :serialized_key
validates_presence_of :url, :profile, :serialized_key
validates_format_of :url, :with =>
/^(https?):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*(\.[a-z]{2,5})?(:[0-9]{1,5})?(\/.*)?$/ix
def self.search(query)
Person.all('$where' => "function() { return this.email.match(/^#{query}/i) ||
Person.all('$where' => "function() { return this.diaspora_handle.match(/^#{query}/i) ||
this.profile.first_name.match(/^#{query}/i) ||
this.profile.last_name.match(/^#{query}/i); }")
end
@ -79,7 +79,8 @@ class Person
end
def self.by_webfinger( identifier )
local_person = Person.first(:email => identifier.gsub('acct:', ''))
local_person = Person.first(:diaspora_handle => identifier.gsub('acct:', ''))
if local_person
local_person
elsif !identifier.include?("localhost")
@ -102,7 +103,7 @@ class Person
guid = profile.links.select{|x| x.rel == 'http://joindiaspora.com/guid'}.first.href
new_person.id = guid
new_person.email = identifier
new_person.diaspora_handle = identifier
hcard = HCard.find profile.hcard.first[:href]
@ -124,7 +125,7 @@ class Person
:person => {
:id => self.id,
:name => self.real_name,
:email => self.email,
:diaspora_handle => self.diaspora_handle,
:url => self.url,
:exported_key => exported_key
}

View file

@ -23,6 +23,8 @@ class User
key :visible_post_ids, Array
key :visible_person_ids, Array
key :url, String
one :person, :class_name => 'Person', :foreign_key => :owner_id
many :friends, :in => :friend_ids, :class_name => 'Person'
@ -32,11 +34,9 @@ class User
many :aspects, :class_name => 'Aspect'
after_validation_on_create :setup_person
after_create :seed_aspects
before_validation :do_bad_things
before_save :downcase_username
before_validation_on_create :downcase_username
def self.find_for_authentication(conditions={})
if conditions[:username] =~ /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i # email regex
@ -49,7 +49,6 @@ class User
######## Making things work ########
key :email, String
ensure_index :email
def method_missing(method, *args)
self.person.send(method, *args)
@ -286,9 +285,18 @@ class User
###Helpers############
def self.instantiate!( opts = {} )
opts[:person][:email] = opts[:email]
opts[:person][:diaspora_handle] = "#{opts[:username]}@#{opts[:url]}"
opts[:person][:serialized_key] = generate_key
User.create!( opts)
User.create!(opts)
end
def seed_aspects
aspect(:name => "Family")
aspect(:name => "Work")
end
def self.create(opts ={})
puts opts.inspect
end
def terse_url
@ -301,25 +309,6 @@ class User
"#{self.username}@#{self.terse_url}"
end
def do_bad_things
self.password_confirmation = self.password
end
def seed_aspects
aspect(:name => "Acquaintances")
aspect(:name => "Family")
aspect(:name => "Work")
end
protected
def setup_person
self.person.serialized_key ||= User.generate_key.export
self.person.email ||= email
self.person.save!
end
def downcase_username
username.downcase!
end

View file

@ -13,7 +13,7 @@
= stylesheet_link_tag "sessions"
/= javascript_include_tag"http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"
= javascript_include_tag 'jquery142', 'google'
= javascript_include_tag 'jquery142'
= javascript_include_tag 'jquery.infieldlabel'
:javascript

View file

@ -18,7 +18,7 @@
%table
%tr
%th real name
%th email
%th diaspora handle
%th url
- for person in @people
%tr
@ -27,7 +27,7 @@
- else
%td= person.real_name
%td= person.email
%td= person.diaspora_handle
%td= person.url
-if current_user.friends.include? person
@ -46,5 +46,5 @@
%td
= form_for Request.new do |f|
= f.select(:aspect_id, @aspects_dropdown_array)
= f.hidden_field :destination_url, :value => person.email
= f.hidden_field :destination_url, :value => person.diaspora_handle
= f.submit "add friend"

View file

@ -8,9 +8,9 @@
= form_for @person do |f|
= f.error_messages
%p
= f.label :email
= f.label :diaspora_handle
%br
= f.text_field :email
= f.text_field :diaspora_handle
%p
= f.label :url
%br

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
<Subject>acct:<%=@person.email%></Subject>
<Subject>acct:<%=@person.diaspora_handle%></Subject>
<Alias>"<%= @person.url %>"</Alias>
<Link rel="http://microformats.org/profile/hcard" type="text/html" href="<%=@person.url%>hcard/users/<%=@person.id%>"/>
<Link rel="http://joindiaspora.com/seed_location" type = 'text/html' href="<%=@person.url%>"/>

View file

@ -2,7 +2,7 @@
= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f|
= devise_error_messages!
= f.hidden_field :url, :value => request.host
%p
= f.label :username
= f.text_field :username

View file

@ -4,6 +4,7 @@
#friend_pictures
= owner_image_link
- for friend in @friends
= person_image_link(friend)

View file

@ -9,7 +9,7 @@
%li{:id => aspect.id, :class => ("selected" if current_aspect?(aspect))}
= link_for_aspect aspect
%ul{ :style => "position:absolute;right:0;bottom:0;"}
%ul{ :style => "position:absolute;right:0;bottom:0.01em;"}
%li{:class => ("selected" if @aspect == :all)}
= link_to "All Aspects", root_url

View file

@ -17,9 +17,11 @@
%h3 Picture
%div#image_picker
= p.hidden_field :image_url, :value => @profile.image_url.sub(@user.url,'/'), :id => 'image_url_field'
= p.hidden_field :image_url, :value => (@profile.image_url.sub(@user.url,'/') if @profile.image_url), :id => 'image_url_field'
- unless @photos.nil? || @photos.empty?
- for photo in @photos
- if photo.url(:thumb_medium) == @profile.image_url.sub(@user.url,'/')
- if @profile.image_url && (photo.url(:thumb_medium) == @profile.image_url.sub(@user.url,'/'))
%div.small_photo{:id => photo.url(:thumb_medium), :class=>'selected'}
= check_box_tag 'checked_photo', true, true
= link_to image_tag(photo.url(:thumb_medium)), "#"
@ -27,8 +29,16 @@
%div.small_photo{:id => photo.url(:thumb_medium)}
= check_box_tag 'checked_photo'
= link_to image_tag(photo.url(:thumb_medium)), "#"
- else
You don't have any photos! Go to the
= link_to "albums", albums_path(:aspect => 'all')
page to upload some.
=will_paginate @photos
%br
%h3 Info
%p

View file

@ -26,10 +26,10 @@ Diaspora::Application.routes.draw do
#routes for devise, not really sure you will need to mess with this in the future, lets put default,
#non mutable stuff in anohter file
devise_for :users
devise_for :users, :controllers => {:registrations => "registrations"}
match 'login', :to => 'devise/sessions#new', :as => "new_user_session"
match 'logout', :to => 'devise/sessions#destroy', :as => "destroy_user_session"
match 'get_to_the_choppa', :to => 'devise/registrations#new', :as => "new_user_registration"
match 'get_to_the_choppa', :to => 'registrations#new', :as => "new_user_registration"
#public routes
#

View file

@ -21,16 +21,18 @@ def create
backer_number = YAML.load_file(Rails.root.join('config','backer_number.yml'))[:seed_number].to_i
# Create seed user
username = backer_info[backer_number]['username'].gsub(/ /,'').downcase
user = User.create( :email => "#{username}@#{username}.joindiaspora.com",
user = User.instantiate!(:email => "#{username}@#{username}.joindiaspora.com",
:username => username,
:password => "#{username+backer_info[backer_number]['pin'].to_s}",
:password_confirmation => "#{username+backer_info[backer_number]['pin'].to_s}",
:url=> "http://#{username}.joindiaspora.com/",
:person => Person.new(
:email => "#{username}@#{username}.joindiaspora.com",
:diaspora_handle => "#{username}@#{username}.joindiaspora.com",
:profile => Profile.new( :first_name => backer_info[backer_number]['given_name'], :last_name => backer_info[backer_number]['family_name'],
:image_url => "http://#{username}.joindiaspora.com/images/user/#{username}.jpg"),
:url=> "http://#{username}.joindiaspora.com/")
)
user.person.save
user.person.save!
user.aspect(:name => "Presidents")
end

View file

@ -8,12 +8,15 @@ require 'config/environment'
host = "localhost:3000"
url = "http://#{host}/"
username = "tom"
# Create seed user
user = User.instantiate!( :email => "tom@tom.joindiaspora.com",
:username => "tom",
:password => "evankorth",
:password_confirmation => "evankorth",
:url=> "http://#{username}.joindiaspora.com/"
:person => Person.new(
:email => "tom@tom.joindiaspora.com",
:diaspora_handle => "tom@tom.joindiaspora.com",
:url => url,
:profile => Profile.new( :first_name => "Alexander", :last_name => "Hamiltom" ))
)
@ -21,8 +24,10 @@ user.person.save!
user2 = User.instantiate!( :email => "korth@tom.joindiaspora.com",
:username => "korth",
:url=> "http://#{username}.joindiaspora.com/"
:password => "evankorth",
:person => Person.new( :email => "korth@tom.joindiaspora.com",
:password_confirmation => "evankorth",
:person => Person.new( :diaspora_handle => "korth@tom.joindiaspora.com",
:url => url,
:profile => Profile.new( :first_name => "Evan",
:last_name => "Korth")))

View file

@ -12,8 +12,10 @@ remote_url = "http://localhost:3000/"
user = User.instantiate!( :email => "tom@tom.joindiaspora.com",
:username => "tom",
:password => "evankorth",
:password_confirmation => "evankorth",
:url => remote_url,
:person => {
:email => "tom@tom.joindiaspora.com",
:diaspora_handle => "tom@tom.joindiaspora.com",
:url => remote_url,
:profile => { :first_name => "Alexander", :last_name => "Hamiltom",
:image_url => "http://tom.joindiaspora.com/images/user/tom.jpg"}}
@ -22,8 +24,10 @@ user.person.save!
user2 = User.instantiate!( :email => "korth@tom.joindiaspora.com",
:password => "evankorth",
:password_confirmation => "evankorth",
:username => "korth",
:person => { :email => "korth@tom.joindiaspora.com",
:url => remote_url,
:person => { :diaspora_handle => "korth@tom.joindiaspora.com",
:url => remote_url,
:profile => { :first_name => "Evan",
:last_name => "Korth",

View file

@ -82,7 +82,7 @@ module Salmon
<entry xmlns='http://www.w3.org/2005/Atom'>
<author>
<name>#{@author.real_name}</name>
<uri>acct:#{@author.email}</uri>
<uri>acct:#{@author.diaspora_handle}</uri>
</author>
#{@magic_sig.to_xml}
</entry>

View file

@ -442,8 +442,6 @@ h1.big_text {
display: none; }
#aspect_nav {
z-index: 4;
position: relative;
color: black;
margin-top: 8px;
margin-bottom: 1px; }
@ -460,10 +458,11 @@ h1.big_text {
display: inline;
margin-right: 0.5em; }
#aspect_nav ul > li a {
line-height: 22px;
background-color: #444444;
border: 1px solid #555555;
padding: 3px 8px;
padding-bottom: 2px;
padding-bottom: 3px;
color: #999999; }
#aspect_nav ul > li a:hover {
background-color: #4e4e4e;
@ -471,12 +470,12 @@ h1.big_text {
#aspect_nav ul > li.selected a {
text-shadow: 0 2px 0 white;
padding-top: 4px;
padding-bottom: 4px;
padding-bottom: 5px;
line-height: 18px;
font-weight: bold;
background-color: #eeeeee;
border: 1px solid white;
border-bottom: none;
border-bottom: 1px solid #eeeeee;
color: black; }
#aspect_nav ul > li.selected a:hover {
background-color: #efefef; }
@ -520,7 +519,7 @@ h1.big_text {
position: absolute;
top: 10px;
right: 0;
display: none; }
display: inline; }
.aspect .aspect_name:hover .tools,
.requests .aspect_name:hover .tools,
.remove .aspect_name:hover .tools {

View file

@ -578,8 +578,6 @@ h1.big_text
:display none
#aspect_nav
:z-index 4
:position relative
:color #000
:margin
:top 8px
@ -606,12 +604,14 @@ h1.big_text
:right 0.5em
a
:line
:height 22px
:background
:color #444
:border 1px solid #555
:padding 3px 8px
:bottom 2px
:bottom 3px
:color #999
&:hover
@ -623,7 +623,7 @@ h1.big_text
:text-shadow 0 2px 0 #fff
:padding
:top 4px
:bottom 4px
:bottom 5px
:line
:height 18px
:font
@ -631,7 +631,7 @@ h1.big_text
:background
:color #eee
:border 1px solid #fff
:bottom none
:bottom 1px solid #eee
:color #000
&:hover
:background
@ -682,7 +682,7 @@ h1.big_text
:position absolute
:top 10px
:right 0
:display none
:display inline
&:hover
.tools

View file

@ -12,6 +12,56 @@
:weight normal
:style normal
#flash_notice,
#flash_error,
#flash_alert
:z-index 100
:top 32px
:position absolute
:color #000
:width 400px
:margin 0 0 0 -200px
:left 50%
:text
:align center
:font
:size 14px
:padding 3px 0
#flash_notice
:background-color #CFC
:border solid 1px #6C6
#flash_error,
#flash_alert
:background-color #FCC
:border solid 1px #C66
.fieldWithErrors
:display inline
.error_messages
:width 400px
:border 2px solid #CF0000
:padding 0
:padding-bottom 12px
:margin-bottom 20px
:background-color #f0f0f0
:font
:size 12px
h2
:text-align left
:padding 5px 5px 5px 15px
:margin 0
:font
:weight bold
:size 12px
:background-color #c00
p
:margin 8px 10px
ul
:margin 0
/* via blueprint */
html
:font

View file

@ -6,6 +6,49 @@
weight: normal;
style: normal; } }
#flash_notice,
#flash_error,
#flash_alert {
z-index: 100;
top: 32px;
color: black;
width: 400px;
text-align: center;
font-size: 14px;
padding: 3px 0; }
#flash_notice {
background-color: #ccffcc;
border: solid 1px #66cc66; }
#flash_error,
#flash_alert {
background-color: #ffcccc;
border: solid 1px #cc6666; }
.fieldWithErrors {
display: inline; }
.error_messages {
width: 400px;
border: 2px solid #cf0000;
padding: 0;
padding-bottom: 12px;
margin-bottom: 20px;
background-color: #f0f0f0;
font-size: 12px; }
.error_messages h2 {
text-align: left;
padding: 5px 5px 5px 15px;
margin: 0;
font-weight: bold;
font-size: 12px;
background-color: #cc0000; }
.error_messages p {
margin: 8px 10px; }
.error_messages ul {
margin: 0; }
/* via blueprint */
html {
font-size: 100.01%; }

View file

@ -60,7 +60,7 @@ describe PublicsController do
end
it 'should add the pending request to the right user if the target person does not exist locally' do
Person.should_receive(:by_webfinger).with(@user2.person.email).and_return(@user2.person)
Person.should_receive(:by_webfinger).with(@user2.person.diaspora_handle).and_return(@user2.person)
@user2.person.delete
@user2.delete
post :receive, :id => @user.person.id, :xml => @xml

View file

@ -15,7 +15,7 @@ Factory.define :profile do |p|
end
Factory.define :person do |p|
p.sequence(:email) {|n| "bob-person-#{n}@aol.com"}
p.sequence(:diaspora_handle) {|n| "bob-person-#{n}@aol.com"}
p.sequence(:url) {|n| "http://google-#{n}.com/"}
p.profile Factory.create(:profile)

View file

@ -5,7 +5,7 @@ Hash: SHA1
<message>jimmy's 1 whales</message>
<_id>4c3b7cf9312f91367f000004</_id>
<person>
<email>bob1@aol.com</email>
<diaspora_handle>bob1@aol.com</diaspora_handle>
<url>http://www.example.com/</url>
<_id>4c3b7c64312f913664000005</_id>
<key_fingerprint>0264242496D4B585297BF236BEEFE6DEBE3407AA</key_fingerprint>

View file

@ -15,7 +15,7 @@ describe Diaspora::Parser do
before do
@user = Factory.create(:user, :email => "bob@aol.com")
@aspect = @user.aspect(:name => 'spies')
@person = Factory.create(:person_with_private_key, :email => "bill@gates.com")
@person = Factory.create(:person_with_private_key, :diaspora_handle => "bill@gates.com")
@user2 = Factory.create(:user)
end
@ -25,7 +25,7 @@ describe Diaspora::Parser do
end
it 'should be able to correctly handle comments with person in db' do
person = Factory.create(:person, :email => "test@testing.com")
person = Factory.create(:person, :diaspora_handle => "test@testing.com")
post = Factory.create(:status_message, :person => @user.person)
comment = Factory.build(:comment, :post => post, :person => person, :text => "Freedom!")
xml = comment.to_diaspora_xml
@ -49,7 +49,7 @@ describe Diaspora::Parser do
parsed_person = Diaspora::Parser::parse_or_find_person_from_xml(xml)
parsed_person.save.should be true
parsed_person.email.should == commenter.person.email
parsed_person.diaspora_handle.should == commenter.person.diaspora_handle
parsed_person.profile.should_not be_nil
end

View file

@ -35,8 +35,9 @@ describe Salmon do
@parsed_salmon.data.should == xml
end
it 'should parse out the author email' do
@parsed_salmon.author_email.should == @user.person.email
it 'should parse out the authors diaspora_handle' do
@parsed_salmon.author_email.should == @user.person.diaspora_handle
end
it 'should reference a local author' do

View file

@ -52,7 +52,7 @@ describe Aspect do
it 'belong to a user' do
@aspect.user.id.should == @user.id
@user.aspects.size.should == 4
@user.aspects.size.should == 3
end
it 'should have people' do

View file

@ -15,8 +15,8 @@ describe Person do
@aspect2 = @user2.aspect(:name => "Abscence of Babes")
end
it 'should not allow two people with the same email' do
person_two = Factory.build(:person, :url => @person.email)
it 'should not allow two people with the same diaspora_handle' do
person_two = Factory.build(:person, :url => @person.diaspora_handle)
person_two.valid?.should == false
end
@ -147,10 +147,10 @@ describe Person do
people.include?(@friend_three).should == false
end
it 'should search by email exactly' do
it 'should search by diaspora_handle exactly' do
stub_success("tom@tom.joindiaspora.com")
Person.by_webfinger(@friend_one.email).should == @friend_one
Person.by_webfinger(@friend_one.diaspora_handle).should == @friend_one
end
it 'should create a stub for a remote user' do

View file

@ -18,7 +18,7 @@ describe Post do
end
it 'should serialize to xml with its person' do
@message.to_xml.to_s.include?(@user.person.email).should == true
@message.to_xml.to_s.include?(@user.person.diaspora_handle).should == true
end
end

View file

@ -24,14 +24,14 @@ describe Request do
xml = request.to_xml.to_s
xml.include?(@user.person.email).should be true
xml.include?(@user.url).should be true
xml.include?(@user.person.diaspora_handle).should be true
xml.include?(@user.person.url).should be true
xml.include?(@user.profile.first_name).should be true
xml.include?(@user.profile.last_name).should be true
end
it 'should allow me to see only friend requests sent to me' do
remote_person = Factory.build(:person, :email => "robert@grimm.com", :url => "http://king.com/")
remote_person = Factory.build(:person, :diaspora_handle => "robert@grimm.com", :url => "http://king.com/")
Request.instantiate(:into => @aspect.id, :from => @user.person, :to => remote_person.receive_url).save
Request.instantiate(:into => @aspect.id, :from => @user.person, :to => remote_person.receive_url).save

View file

@ -16,12 +16,12 @@ describe 'user encryption' do
@person = Factory.create(:person_with_private_key,
:profile => Profile.new(:first_name => 'Remote',
:last_name => 'Friend'),
:email => 'somewhere@else.com',
:diaspora_handle => 'somewhere@else.com',
:url => 'http://distant-example.com/')
@person2 = Factory.create(:person_with_private_key,
:profile => Profile.new(:first_name => 'Second',
:last_name => 'Friend'),
:email => 'elsewhere@else.com',
:diaspora_handle => 'elsewhere@else.com',
:url => 'http://distanter-example.com/')
end