portablecontacts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +48 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/lib/portable_contacts.rb +218 -0
- data/lib/portablecontacts.rb +1 -0
- data/portablecontacts.gemspec +63 -0
- data/spec/fixtures/multiple.json +96 -0
- data/spec/fixtures/single.json +88 -0
- data/spec/portable_contacts_spec.rb +234 -0
- data/spec/spec_helper.rb +9 -0
- metadata +98 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Pelle Braendgaard
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= Portable Contacts
|
2
|
+
|
3
|
+
This is a ruby client implementation of Portable Contacts a standard for exchanging profile and address book data.
|
4
|
+
|
5
|
+
The current draft of the standard is http://portablecontacts.net/draft-spec.html
|
6
|
+
|
7
|
+
Portable Contacts is currently supported by Plaxo, Google, Yahoo, and most OpenSocial containers.
|
8
|
+
|
9
|
+
This client uses OAuth and JSON exclusively. There are no plans for supporting basic authentication or xml.
|
10
|
+
|
11
|
+
To use it you need an OAuth AccessToken (see http://oauth.rubyforge.org/). If you are using Rails, you may find the easiest way of doing this as using the OAuth Plugin http://stakeventures.com/articles/2009/07/21/consuming-oauth-intelligently-in-rails
|
12
|
+
|
13
|
+
The Gem requires:
|
14
|
+
|
15
|
+
* ActiveSupport
|
16
|
+
* OAuth Gem
|
17
|
+
* JSON gem
|
18
|
+
|
19
|
+
== Example Code
|
20
|
+
|
21
|
+
@access_token = ... # instantiate access token
|
22
|
+
|
23
|
+
@client = PortableContacts::Client.new "http://www-opensocial.googleusercontent.com/api/people", @access_token
|
24
|
+
|
25
|
+
# Get users profile
|
26
|
+
|
27
|
+
@profile = @client.me
|
28
|
+
|
29
|
+
puts @profile.display_name
|
30
|
+
=> "Bob Sample"
|
31
|
+
|
32
|
+
# Get users contacts
|
33
|
+
@contacts = @client.all
|
34
|
+
|
35
|
+
== Note on Patches/Pull Requests
|
36
|
+
|
37
|
+
* Fork the project.
|
38
|
+
* Make your feature addition or bug fix.
|
39
|
+
* Add tests for it. This is important so I don't break it in a
|
40
|
+
future version unintentionally.
|
41
|
+
* Commit, do not mess with rakefile, version, or history.
|
42
|
+
(if you want to have your own version, that is fine but
|
43
|
+
bump version in a commit by itself I can ignore when I pull)
|
44
|
+
* Send me a pull request. Bonus points for topic branches.
|
45
|
+
|
46
|
+
== Copyright
|
47
|
+
|
48
|
+
Copyright (c) 2009 Pelle Braendgaard. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "portablecontacts"
|
8
|
+
gem.summary = %Q{Portable Contacts client for Ruby}
|
9
|
+
gem.description = %Q{A client library for the portable contacts standard}
|
10
|
+
gem.email = "pelleb@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/pelle/portablecontacts"
|
12
|
+
gem.authors = ["Pelle Braendgaard"]
|
13
|
+
gem.rubyforge_project = "portablecontact"
|
14
|
+
gem.add_dependency('oauth', '>= 0.3.6')
|
15
|
+
gem.add_dependency('json')
|
16
|
+
gem.add_development_dependency "rspec"
|
17
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
|
+
end
|
19
|
+
Jeweler::RubyforgeTasks.new do |rubyforge|
|
20
|
+
rubyforge.doc_task = "rdoc"
|
21
|
+
end
|
22
|
+
rescue LoadError
|
23
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'spec/rake/spectask'
|
27
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
29
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
30
|
+
end
|
31
|
+
|
32
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
33
|
+
spec.libs << 'lib' << 'spec'
|
34
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
35
|
+
spec.rcov = true
|
36
|
+
end
|
37
|
+
|
38
|
+
task :spec => :check_dependencies
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require 'rake/rdoctask'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
if File.exist?('VERSION')
|
45
|
+
version = File.read('VERSION')
|
46
|
+
else
|
47
|
+
version = ""
|
48
|
+
end
|
49
|
+
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
51
|
+
rdoc.title = "portablecontacts #{version}"
|
52
|
+
rdoc.rdoc_files.include('README*')
|
53
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
54
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'json'
|
3
|
+
require 'activesupport'
|
4
|
+
module PortableContacts
|
5
|
+
|
6
|
+
# This is the main PortableContacts Client.
|
7
|
+
#
|
8
|
+
# == query options
|
9
|
+
#
|
10
|
+
# The library supports the various filter and sorting parameters. The format of this may change for this library, so limit use to :
|
11
|
+
# :count and :start_index for now.
|
12
|
+
#
|
13
|
+
class Client
|
14
|
+
attr :base_url, :access_token
|
15
|
+
|
16
|
+
# First parameter is the portable contacts base_url. Find this on your PortableContact providers documentation or through XRDS.
|
17
|
+
#
|
18
|
+
# * Google's is http://www-opensocial.googleusercontent.com/api/people
|
19
|
+
# * Yahoo's is http://appstore.apps.yahooapis.com/social/rest/people
|
20
|
+
#
|
21
|
+
# The second parameter is an OAuth::AccessToken instantiated for the provider.
|
22
|
+
#
|
23
|
+
def initialize(base_url,access_token)
|
24
|
+
@base_url=base_url
|
25
|
+
@access_token=access_token
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the AccessToken users contact details. Note this requests all fields from provider
|
29
|
+
# Returns an PortableContacts::Person object
|
30
|
+
def me(options={})
|
31
|
+
single(get("/@me/@self",options.reverse_merge(:fields=>:all)))
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the contacts of the user. It defaults to all fields and 100 entries
|
35
|
+
#
|
36
|
+
# @contacts = @client.all # return 100 contacts
|
37
|
+
# @contacts = @client.all :count=>10 # return 10 contacts
|
38
|
+
# @contacts = @client.all :count=>10, :start_index=>10 # returns the second page of 10 contacts
|
39
|
+
# puts @contacts.total_entries # returns the total amount of contacts on the server
|
40
|
+
#
|
41
|
+
# Returns a PortableContacts::Collection which is a subclass of Array
|
42
|
+
def all(options={})
|
43
|
+
collection(get("/@me/@all",options.reverse_merge(:fields=>:all,:count=>100)))
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the full contact infor for a particular userid. TODO This is not tested well
|
47
|
+
# Returns an PortableContacts::Person object
|
48
|
+
def find(id, options={})
|
49
|
+
single(get("/@me/@all/#{id}",options.reverse_merge(:fields=>:all)))
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def get(path,options={})
|
55
|
+
parse(@access_token.get( url_for(path)+options_for(options), {'Accept' => 'application/json'}))
|
56
|
+
end
|
57
|
+
|
58
|
+
def url_for(path)
|
59
|
+
"#{@base_url}#{path}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def options_for(options={})
|
63
|
+
return "" if options.nil? || options.empty?
|
64
|
+
options.symbolize_keys! if options.respond_to? :symbolize_keys!
|
65
|
+
"?#{(fields_options(options[:fields])+filter_options(options[:filter])+sort_options(options[:sort])+pagination_options(options)).sort.join("&")}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def single(data)
|
69
|
+
if data.is_a?(Hash) && data['entry']
|
70
|
+
PortableContacts::Person.new(data['entry'])
|
71
|
+
else
|
72
|
+
data
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def collection(data)
|
77
|
+
if data.is_a?(Hash)
|
78
|
+
PortableContacts::Collection.new data
|
79
|
+
else
|
80
|
+
data
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def parse(response)
|
85
|
+
return false unless response
|
86
|
+
if ["200","201"].include? response.code
|
87
|
+
unless response.body.blank?
|
88
|
+
JSON.parse(response.body)
|
89
|
+
else
|
90
|
+
true
|
91
|
+
end
|
92
|
+
else
|
93
|
+
false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def fields_options(options=nil)
|
98
|
+
return [] if options.nil?
|
99
|
+
if options.is_a? Symbol
|
100
|
+
return ["fields=#{(options==:all ? "@all": URI.escape(options.to_s))}"]
|
101
|
+
elsif options.respond_to?(:collect)
|
102
|
+
["fields=#{options.collect{|f|f.to_s}.join(',')}"]
|
103
|
+
else
|
104
|
+
[]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def sort_options(options=nil)
|
109
|
+
return [] if options.nil?
|
110
|
+
if options.is_a? Symbol
|
111
|
+
return ["sortBy=#{URI.escape(options.to_s)}"]
|
112
|
+
end
|
113
|
+
if options.is_a?(Hash) and options[:by]||options['by']
|
114
|
+
return to_query_list(options,"sort")
|
115
|
+
end
|
116
|
+
return []
|
117
|
+
end
|
118
|
+
|
119
|
+
def pagination_options(options=nil)
|
120
|
+
return [] if options.nil? || options.empty?
|
121
|
+
params=[]
|
122
|
+
if options[:count]
|
123
|
+
params<<"count=#{options[:count]}"
|
124
|
+
end
|
125
|
+
if options[:start_index]
|
126
|
+
params<<"startIndex=#{options[:start_index]}"
|
127
|
+
end
|
128
|
+
params
|
129
|
+
end
|
130
|
+
|
131
|
+
def filter_options(options={})
|
132
|
+
return [] if options.nil? || options.empty?
|
133
|
+
options[:op] ||= "equals"
|
134
|
+
to_query_list(options,"filter")
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_query_list(params,pre_fix='')
|
138
|
+
params.collect{ |k,v| "#{pre_fix}#{k.to_s.capitalize}=#{URI.escape(v.to_s)}"}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class Person
|
143
|
+
|
144
|
+
# Encapsulates a person. Each of the portable contact and opensocial contact fields has a rubyfied (underscored) accessor method.
|
145
|
+
#
|
146
|
+
# @person = @person.display_name
|
147
|
+
#
|
148
|
+
|
149
|
+
def initialize(data={})
|
150
|
+
@data=data
|
151
|
+
end
|
152
|
+
|
153
|
+
SINGULAR_FIELDS = [
|
154
|
+
# Portable contacts singular fields
|
155
|
+
:id, :display_name, :name, :nickname, :published, :updated, :birthday, :anniversary,
|
156
|
+
:gender, :note, :preferred_username, :utc_offset, :connected,
|
157
|
+
|
158
|
+
# OpenSocial singular fields
|
159
|
+
:about_me, :body_type, :current_location, :drinker, :ethnicity, :fashion, :happiest_when,
|
160
|
+
:humor, :living_arrangement, :looking_for, :profile_song, :profile_video, :relationship_status,
|
161
|
+
:religion, :romance, :scared_of, :sexual_orientation, :smoker, :status
|
162
|
+
]
|
163
|
+
PLURAL_FIELDS = [
|
164
|
+
# Portable contacts plural fields
|
165
|
+
:emails, :urls, :phone_numbers, :ims, :photos, :tags, :relationships, :addresses,
|
166
|
+
:organizations, :accounts,
|
167
|
+
|
168
|
+
# OpenSocial plural fields
|
169
|
+
:activities, :books, :cars, :children, :food, :heroes, :interests, :job_interests,
|
170
|
+
:languages, :languages_spoken, :movies, :music, :pets, :political_views, :quotes,
|
171
|
+
:sports, :turn_offs, :turn_ons, :tv_shows
|
172
|
+
]
|
173
|
+
|
174
|
+
ENTRY_FIELDS = SINGULAR_FIELDS + PLURAL_FIELDS
|
175
|
+
|
176
|
+
|
177
|
+
def [](key)
|
178
|
+
@data[key.to_s.camelize(:lower)]
|
179
|
+
end
|
180
|
+
|
181
|
+
# primary email address
|
182
|
+
def email
|
183
|
+
@email||= begin
|
184
|
+
(emails.detect {|e| e['primary']=='true '} || emails.first)["value"] unless emails.empty?
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def id
|
189
|
+
self["id"]
|
190
|
+
end
|
191
|
+
|
192
|
+
protected
|
193
|
+
|
194
|
+
def method_missing(method,*args)
|
195
|
+
if respond_to?(method)
|
196
|
+
return self[method]
|
197
|
+
end
|
198
|
+
super
|
199
|
+
end
|
200
|
+
|
201
|
+
def respond_to?(method)
|
202
|
+
ENTRY_FIELDS.include?(method) || @data.has_key?(method.to_s.camelize(:lower)) || super
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class Collection < Array
|
207
|
+
attr_reader :total_entries, :per_page, :start_index
|
208
|
+
|
209
|
+
def initialize(data)
|
210
|
+
super data["entry"].collect{|e| PortableContacts::Person.new(e) }
|
211
|
+
@total_entries=data["totalResults"].to_i
|
212
|
+
@per_page=data["itemsPerPage"].to_i
|
213
|
+
@start_index=data["startIndex"].to_i
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__),'portable_contacts')
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{portablecontacts}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Pelle Braendgaard"]
|
12
|
+
s.date = %q{2009-10-02}
|
13
|
+
s.description = %q{A client library for the portable contacts standard}
|
14
|
+
s.email = %q{pelleb@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/portable_contacts.rb",
|
27
|
+
"lib/portablecontacts.rb",
|
28
|
+
"portablecontacts.gemspec",
|
29
|
+
"spec/fixtures/multiple.json",
|
30
|
+
"spec/fixtures/single.json",
|
31
|
+
"spec/portable_contacts_spec.rb",
|
32
|
+
"spec/spec_helper.rb"
|
33
|
+
]
|
34
|
+
s.homepage = %q{http://github.com/pelle/portablecontacts}
|
35
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
s.rubyforge_project = %q{portablecontact}
|
38
|
+
s.rubygems_version = %q{1.3.5}
|
39
|
+
s.summary = %q{Portable Contacts client for Ruby}
|
40
|
+
s.test_files = [
|
41
|
+
"spec/portable_contacts_spec.rb",
|
42
|
+
"spec/spec_helper.rb"
|
43
|
+
]
|
44
|
+
|
45
|
+
if s.respond_to? :specification_version then
|
46
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
47
|
+
s.specification_version = 3
|
48
|
+
|
49
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
50
|
+
s.add_runtime_dependency(%q<oauth>, [">= 0.3.6"])
|
51
|
+
s.add_runtime_dependency(%q<json>, [">= 0"])
|
52
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
53
|
+
else
|
54
|
+
s.add_dependency(%q<oauth>, [">= 0.3.6"])
|
55
|
+
s.add_dependency(%q<json>, [">= 0"])
|
56
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
57
|
+
end
|
58
|
+
else
|
59
|
+
s.add_dependency(%q<oauth>, [">= 0.3.6"])
|
60
|
+
s.add_dependency(%q<json>, [">= 0"])
|
61
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
{
|
2
|
+
"startIndex": 10,
|
3
|
+
"itemsPerPage": 10,
|
4
|
+
"totalResults": 12,
|
5
|
+
"entry": [
|
6
|
+
{
|
7
|
+
"id": "123",
|
8
|
+
"displayName": "Minimal Contact"
|
9
|
+
},
|
10
|
+
{
|
11
|
+
"id": "703887",
|
12
|
+
"displayName": "Mork Hashimoto",
|
13
|
+
"name": {
|
14
|
+
"familyName": "Hashimoto",
|
15
|
+
"givenName": "Mork"
|
16
|
+
},
|
17
|
+
"birthday": "0000-01-16",
|
18
|
+
"gender": "male",
|
19
|
+
"drinker": "heavily",
|
20
|
+
"tags": [
|
21
|
+
"plaxo guy",
|
22
|
+
"favorite"
|
23
|
+
],
|
24
|
+
"emails": [
|
25
|
+
{
|
26
|
+
"value": "mhashimoto-04@plaxo.com",
|
27
|
+
"type": "work",
|
28
|
+
"primary": "true"
|
29
|
+
},
|
30
|
+
{
|
31
|
+
"value": "mhashimoto-04@plaxo.com",
|
32
|
+
"type": "home"
|
33
|
+
},
|
34
|
+
{
|
35
|
+
"value": "mhashimoto@plaxo.com",
|
36
|
+
"type": "home"
|
37
|
+
}
|
38
|
+
],
|
39
|
+
"urls": [
|
40
|
+
{
|
41
|
+
"value": "http://www.seeyellow.com",
|
42
|
+
"type": "work"
|
43
|
+
},
|
44
|
+
{
|
45
|
+
"value": "http://www.angryalien.com",
|
46
|
+
"type": "home"
|
47
|
+
}
|
48
|
+
],
|
49
|
+
"phoneNumbers": [
|
50
|
+
{
|
51
|
+
"value": "KLONDIKE5",
|
52
|
+
"type": "work"
|
53
|
+
},
|
54
|
+
{
|
55
|
+
"value": "650-123-4567",
|
56
|
+
"type": "mobile"
|
57
|
+
}
|
58
|
+
],
|
59
|
+
"photos": [
|
60
|
+
{
|
61
|
+
"value": "http://sample.site.org/photos/12345.jpg",
|
62
|
+
"type": "thumbnail"
|
63
|
+
}
|
64
|
+
],
|
65
|
+
"ims": [
|
66
|
+
{
|
67
|
+
"value": "plaxodev8",
|
68
|
+
"type": "aim"
|
69
|
+
}
|
70
|
+
],
|
71
|
+
"addresses": [
|
72
|
+
{
|
73
|
+
"type": "home",
|
74
|
+
"streetAddress": "742 Evergreen Terrace\nSuite 123",
|
75
|
+
"locality": "Springfield",
|
76
|
+
"region": "VT",
|
77
|
+
"postalCode": "12345",
|
78
|
+
"country": "USA",
|
79
|
+
"formatted": "742 Evergreen Terrace\nSuite 123\nSpringfield, VT 12345 USA"
|
80
|
+
}
|
81
|
+
],
|
82
|
+
"organizations": [
|
83
|
+
{
|
84
|
+
"name": "Burns Worldwide",
|
85
|
+
"title": "Head Bee Guy"
|
86
|
+
}
|
87
|
+
],
|
88
|
+
"accounts": [
|
89
|
+
{
|
90
|
+
"domain": "plaxo.com",
|
91
|
+
"userid": "2706"
|
92
|
+
}
|
93
|
+
]
|
94
|
+
}
|
95
|
+
]
|
96
|
+
}
|
@@ -0,0 +1,88 @@
|
|
1
|
+
{
|
2
|
+
"entry":
|
3
|
+
{
|
4
|
+
"id": "703887",
|
5
|
+
"displayName": "Mork Hashimoto",
|
6
|
+
"name": {
|
7
|
+
"familyName": "Hashimoto",
|
8
|
+
"givenName": "Mork"
|
9
|
+
},
|
10
|
+
"birthday": "0000-01-16",
|
11
|
+
"gender": "male",
|
12
|
+
"drinker": "heavily",
|
13
|
+
"tags": [
|
14
|
+
"plaxo guy",
|
15
|
+
"favorite"
|
16
|
+
],
|
17
|
+
"emails": [
|
18
|
+
{
|
19
|
+
"value": "mhashimoto-04@plaxo.com",
|
20
|
+
"type": "work",
|
21
|
+
"primary": "true"
|
22
|
+
},
|
23
|
+
{
|
24
|
+
"value": "mhashimoto-04@plaxo.com",
|
25
|
+
"type": "home"
|
26
|
+
},
|
27
|
+
{
|
28
|
+
"value": "mhashimoto@plaxo.com",
|
29
|
+
"type": "home"
|
30
|
+
}
|
31
|
+
],
|
32
|
+
"urls": [
|
33
|
+
{
|
34
|
+
"value": "http://www.seeyellow.com",
|
35
|
+
"type": "work"
|
36
|
+
},
|
37
|
+
{
|
38
|
+
"value": "http://www.angryalien.com",
|
39
|
+
"type": "home"
|
40
|
+
}
|
41
|
+
],
|
42
|
+
"phoneNumbers": [
|
43
|
+
{
|
44
|
+
"value": "KLONDIKE5",
|
45
|
+
"type": "work"
|
46
|
+
},
|
47
|
+
{
|
48
|
+
"value": "650-123-4567",
|
49
|
+
"type": "mobile"
|
50
|
+
}
|
51
|
+
],
|
52
|
+
"photos": [
|
53
|
+
{
|
54
|
+
"value": "http://sample.site.org/photos/12345.jpg",
|
55
|
+
"type": "thumbnail"
|
56
|
+
}
|
57
|
+
],
|
58
|
+
"ims": [
|
59
|
+
{
|
60
|
+
"value": "plaxodev8",
|
61
|
+
"type": "aim"
|
62
|
+
}
|
63
|
+
],
|
64
|
+
"addresses": [
|
65
|
+
{
|
66
|
+
"type": "home",
|
67
|
+
"streetAddress": "742 Evergreen Terrace\nSuite 123",
|
68
|
+
"locality": "Springfield",
|
69
|
+
"region": "VT",
|
70
|
+
"postalCode": "12345",
|
71
|
+
"country": "USA",
|
72
|
+
"formatted": "742 Evergreen Terrace\nSuite 123\nSpringfield, VT 12345 USA"
|
73
|
+
}
|
74
|
+
],
|
75
|
+
"organizations": [
|
76
|
+
{
|
77
|
+
"name": "Burns Worldwide",
|
78
|
+
"title": "Head Bee Guy"
|
79
|
+
}
|
80
|
+
],
|
81
|
+
"accounts": [
|
82
|
+
{
|
83
|
+
"domain": "plaxo.com",
|
84
|
+
"userid": "2706"
|
85
|
+
}
|
86
|
+
]
|
87
|
+
}
|
88
|
+
}
|
@@ -0,0 +1,234 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe PortableContacts::Client do
|
4
|
+
|
5
|
+
def access_token
|
6
|
+
@access_token ||= mock("accesstoken")
|
7
|
+
end
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
@client = PortableContacts::Client.new "http://sample.com/portable_contacts", access_token
|
11
|
+
end
|
12
|
+
|
13
|
+
["/@me/@all", "/@me/@all/(id)", "/@me/@self"].each do |path|
|
14
|
+
|
15
|
+
it "should generate url for #{path}" do
|
16
|
+
@client.send( :url_for, path).should=="http://sample.com/portable_contacts#{path}"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return empty string for no options" do
|
22
|
+
@client.send(:options_for).should==""
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "fields" do
|
26
|
+
it "should handle all" do
|
27
|
+
@client.send(:options_for, :fields=>:all).should=="?fields=@all"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should handle single field" do
|
31
|
+
@client.send(:options_for, :fields=>:name).should=="?fields=name"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should handle array of fields" do
|
35
|
+
@client.send(:options_for, :fields=>[:name,:emails,:nickname,:id]).should=="?fields=name,emails,nickname,id"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "filtering" do
|
41
|
+
|
42
|
+
it "should handle default operation" do
|
43
|
+
@client.send(:options_for, :filter=>{:by=>:name,:value=>"Bob"}).should=="?filterBy=name&filterOp=equals&filterValue=Bob"
|
44
|
+
end
|
45
|
+
|
46
|
+
["displayName","id","nickname"].each do |field|
|
47
|
+
["equals", "contains", "startsWith", "present"].each do |op|
|
48
|
+
|
49
|
+
it "should create filter for #{field} with #{op}" do
|
50
|
+
@client.send(:options_for, :filter=>{:by=>field, :op=>op, :value=>"bb"}).should=="?filterBy=#{field}&filterOp=#{op}&filterValue=bb"
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "sorting" do
|
58
|
+
|
59
|
+
it "should handle straight sort" do
|
60
|
+
@client.send(:options_for, :sort=>:name).should=="?sortBy=name"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should handle sort as a hash" do
|
64
|
+
@client.send(:options_for, :sort=>{:by=>:name}).should=="?sortBy=name"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should handle sort as a hash" do
|
68
|
+
@client.send(:options_for, :sort=>{:by=>:name,:order=>:descending}).should=="?sortBy=name&sortOrder=descending"
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "paging" do
|
74
|
+
|
75
|
+
it "should handle count" do
|
76
|
+
@client.send(:options_for, :count=>133).should=="?count=133"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should handle startIndex" do
|
80
|
+
@client.send(:options_for, :start_index=>20).should=="?startIndex=20"
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should handle count with startIndex" do
|
84
|
+
@client.send(:options_for, :count=>10,:start_index=>40).should=="?count=10&startIndex=40"
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should handle all parameters at once" do
|
90
|
+
@client.send(:options_for,
|
91
|
+
:fields=>[:name,:emails,:nickname,:id],
|
92
|
+
:filter=>{:by=>:name,:value=>"Bob"},
|
93
|
+
:sort=>{:by=>:name,:order=>:descending},
|
94
|
+
:count=>10,:start_index=>40).should=="?count=10&fields=name,emails,nickname,id&filterBy=name&filterOp=equals&filterValue=Bob&sortBy=name&sortOrder=descending&startIndex=40"
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "Rails Specific" do
|
98
|
+
require 'activesupport'
|
99
|
+
|
100
|
+
it "should handle all parameters at once with string keys" do
|
101
|
+
@client.send(:options_for,
|
102
|
+
'fields'=>['name','emails','nickname','id'],
|
103
|
+
'filter'=>{'by'=>'name','value'=>"Bob"},
|
104
|
+
'sort'=>{'by'=>'name','order'=>'descending'},
|
105
|
+
'count'=>10,'start_index'=>40).should=="?count=10&fields=name,emails,nickname,id&filterBy=name&filterOp=equals&filterValue=Bob&sortBy=name&sortOrder=descending&startIndex=40"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "parsing" do
|
110
|
+
|
111
|
+
def parse_json_file(name)
|
112
|
+
File.open File.join(File.dirname(__FILE__),'fixtures', name) do |f|
|
113
|
+
JSON.parse f.read
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "single entry response" do
|
118
|
+
before(:each) do
|
119
|
+
@entry = @client.send(:single,parse_json_file('single.json'))
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should contain displayName" do
|
123
|
+
@entry.display_name.should=="Mork Hashimoto"
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should contain tags" do
|
127
|
+
@entry.tags.should == [
|
128
|
+
"plaxo guy",
|
129
|
+
"favorite"
|
130
|
+
]
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should emails" do
|
134
|
+
@entry.emails.should == [
|
135
|
+
{
|
136
|
+
"value"=> "mhashimoto-04@plaxo.com",
|
137
|
+
"type"=> "work",
|
138
|
+
"primary"=> "true"
|
139
|
+
},
|
140
|
+
{
|
141
|
+
"value"=> "mhashimoto-04@plaxo.com",
|
142
|
+
"type"=> "home"
|
143
|
+
},
|
144
|
+
{
|
145
|
+
"value"=> "mhashimoto@plaxo.com",
|
146
|
+
"type"=> "home"
|
147
|
+
}
|
148
|
+
]
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should have email" do
|
152
|
+
@entry.email.should=="mhashimoto-04@plaxo.com"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "multiple entries" do
|
157
|
+
before(:each) do
|
158
|
+
@entries = @client.send(:collection,parse_json_file('multiple.json'))
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should have correct start index" do
|
162
|
+
@entries.start_index.should == 10
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should have correct per page" do
|
166
|
+
@entries.per_page.should == 10
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should have correct total entries" do
|
170
|
+
@entries.total_entries.should == 12
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should have 2 enties" do
|
174
|
+
@entries.length.should==2
|
175
|
+
end
|
176
|
+
|
177
|
+
describe "first entry" do
|
178
|
+
|
179
|
+
before(:each) do
|
180
|
+
@entry=@entries.first
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should have correct id" do
|
184
|
+
@entry.id.should=="123"
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should contain displayName" do
|
188
|
+
@entry.display_name.should=="Minimal Contact"
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
# The same as data in single entry
|
194
|
+
describe "last entry" do
|
195
|
+
|
196
|
+
before(:each) do
|
197
|
+
@entry=@entries.last
|
198
|
+
end
|
199
|
+
it "should contain displayName" do
|
200
|
+
@entry.display_name.should=="Mork Hashimoto"
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should contain tags" do
|
204
|
+
@entry.tags.should == [
|
205
|
+
"plaxo guy",
|
206
|
+
"favorite"
|
207
|
+
]
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should emails" do
|
211
|
+
@entry.emails.should == [
|
212
|
+
{
|
213
|
+
"value"=> "mhashimoto-04@plaxo.com",
|
214
|
+
"type"=> "work",
|
215
|
+
"primary"=> "true"
|
216
|
+
},
|
217
|
+
{
|
218
|
+
"value"=> "mhashimoto-04@plaxo.com",
|
219
|
+
"type"=> "home"
|
220
|
+
},
|
221
|
+
{
|
222
|
+
"value"=> "mhashimoto@plaxo.com",
|
223
|
+
"type"=> "home"
|
224
|
+
}
|
225
|
+
]
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should have email" do
|
229
|
+
@entry.email.should=="mhashimoto-04@plaxo.com"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: portablecontacts
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pelle Braendgaard
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-02 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: oauth
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.3.6
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: json
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
description: A client library for the portable contacts standard
|
46
|
+
email: pelleb@gmail.com
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- LICENSE
|
53
|
+
- README.rdoc
|
54
|
+
files:
|
55
|
+
- .document
|
56
|
+
- .gitignore
|
57
|
+
- LICENSE
|
58
|
+
- README.rdoc
|
59
|
+
- Rakefile
|
60
|
+
- VERSION
|
61
|
+
- lib/portable_contacts.rb
|
62
|
+
- lib/portablecontacts.rb
|
63
|
+
- portablecontacts.gemspec
|
64
|
+
- spec/fixtures/multiple.json
|
65
|
+
- spec/fixtures/single.json
|
66
|
+
- spec/portable_contacts_spec.rb
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
has_rdoc: true
|
69
|
+
homepage: http://github.com/pelle/portablecontacts
|
70
|
+
licenses: []
|
71
|
+
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options:
|
74
|
+
- --charset=UTF-8
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: "0"
|
82
|
+
version:
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: "0"
|
88
|
+
version:
|
89
|
+
requirements: []
|
90
|
+
|
91
|
+
rubyforge_project: portablecontact
|
92
|
+
rubygems_version: 1.3.5
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: Portable Contacts client for Ruby
|
96
|
+
test_files:
|
97
|
+
- spec/portable_contacts_spec.rb
|
98
|
+
- spec/spec_helper.rb
|