motion-addressbook 1.0.0 → 1.2.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/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: objective-c
2
+ before_install:
3
+ - source /Users/travis/.bash_profile
4
+ - rvm use 1.9.3
5
+ install: bundle install
6
+ script: bundle exec rake spec
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Addressbook for RubyMotion
1
+ # Addressbook for RubyMotion[![Build Status](https://secure.travis-ci.org/alexrothenberg/motion-addressbook.png)](http://travis-ci.org/alexrothenberg/motion-addressbook) [![Code Climate](https://codeclimate.com/github/alexrothenberg/motion-addressbook.png)](https://codeclimate.com/github/alexrothenberg/motion-addressbook) [![Gem Version](https://badge.fury.io/rb/motion-addressbook.png)](http://badge.fury.io/rb/motion-addressbook)
2
2
 
3
3
  A RubyMotion wrapper around the iOS Address Book framework for RubyMotion apps.
4
4
 
@@ -1,5 +1,5 @@
1
1
  module Motion
2
2
  module Addressbook
3
- VERSION = "1.0.0"
3
+ VERSION = "1.2.0"
4
4
  end
5
5
  end
@@ -4,7 +4,10 @@ BubbleWrap.require 'motion/address_book.rb' do
4
4
  file('motion/address_book.rb').uses_framework('AddressBook')
5
5
  end
6
6
  BW.require 'motion/address_book/multi_value.rb'
7
+ BW.require 'motion/address_book/addr_book.rb'
7
8
  BW.require 'motion/address_book/person.rb'
9
+ BW.require 'motion/address_book/group.rb'
10
+ BW.require 'motion/address_book/multi_valued.rb'
8
11
  BW.require 'motion/address_book/picker.rb' do
9
12
  file('motion/address_book/picker.rb').uses_framework('AddressBookUI')
10
13
  end
@@ -0,0 +1,44 @@
1
+ module AddressBook
2
+ class AddrBook
3
+ attr_reader :ab
4
+
5
+ def initialize
6
+ @ab = AddressBook.address_book
7
+ end
8
+ def people
9
+ ABAddressBookCopyArrayOfAllPeople(ab).map do |ab_person|
10
+ AddressBook::Person.new({}, ab_person, :address_book => ab)
11
+ end
12
+ end
13
+ def count
14
+ ABAddressBookGetPersonCount(@ab)
15
+ end
16
+ def new_person(attributes)
17
+ Person.new(attributes, nil, :address_book => @ab)
18
+ end
19
+ def create_person(attributes)
20
+ p = Person.new(attributes, nil, :address_book => @ab)
21
+ p.save
22
+ p
23
+ end
24
+ def person(id)
25
+ (p = ABAddressBookGetPersonWithRecordID(ab, id)) && Person.new(nil, p, :address_book => ab)
26
+ end
27
+
28
+ def groups
29
+ ABAddressBookCopyArrayOfAllGroups(@ab).map do |ab_group|
30
+ AddressBook::Group.new(:ab_group => ab_group, :address_book => @ab)
31
+ end
32
+ end
33
+ def new_group(attributes)
34
+ AddressBook::Group.new(:attributes => attributes, :address_book => @ab)
35
+ end
36
+ def group(id)
37
+ (g = ABAddressBookGetGroupWithRecordID(ab, id)) && Group.new(:ab_group => g, :address_book => @ab)
38
+ end
39
+
40
+ def notify_changes(callback, context)
41
+ ABAddressBookRegisterExternalChangeCallback(ab, callback, context)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,99 @@
1
+ # Wrapper for iOS ABGroup
2
+ #
3
+ # * groups are saved to the database immediately upon new()
4
+ # * members are added with <<
5
+ #
6
+ module AddressBook
7
+ class Group
8
+ attr_reader :attributes, :error
9
+
10
+ def initialize(opts)
11
+ @address_book = opts[:address_book]
12
+ if opts[:ab_group]
13
+ # import existing
14
+ @ab_group = opts[:ab_group]
15
+ @attributes = nil
16
+ else
17
+ # create new
18
+ @ab_group = nil
19
+ @attributes = opts[:attributes]
20
+ end
21
+ end
22
+
23
+ def address_book
24
+ @address_book ||= AddressBook.address_book
25
+ end
26
+
27
+ def save
28
+ ABAddressBookAddRecord(address_book, ab_group, error)
29
+ ABAddressBookSave(address_book, error)
30
+ @attributes = nil
31
+ @new_record = false
32
+ self
33
+ end
34
+
35
+ def new?
36
+ uid == KABRecordInvalidID
37
+ end
38
+
39
+ def delete!
40
+ unless new?
41
+ ABAddressBookRemoveRecord(address_book, ab_group, error)
42
+ ABAddressBookSave(address_book, error)
43
+ @ab_group = nil
44
+ self
45
+ end
46
+ end
47
+
48
+ def ab_group
49
+ @ab_group || convert_dict_to_ab
50
+ end
51
+ alias :ab_record :ab_group
52
+
53
+ def uid
54
+ ABRecordGetRecordID(ab_group)
55
+ end
56
+
57
+ def name
58
+ ABRecordCopyValue(ab_group, KABGroupNameProperty)
59
+ end
60
+
61
+ def size
62
+ members.count
63
+ end
64
+
65
+ def members
66
+ (ABGroupCopyArrayOfAllMembers(ab_group) || []).map do |ab_record|
67
+ case rectype = ABRecordGetRecordType(ab_record)
68
+ when KABPersonType
69
+ AddressBook::Person.new({}, ab_record, :address_book => address_book)
70
+ when KABGroupType
71
+ AddressBook::Group.new(:ab_group => ab_record, :address_book => address_book)
72
+ else
73
+ warn "Unrecognized record type #{rectype} in AB group #{name}"
74
+ nil
75
+ end
76
+ end
77
+ end
78
+
79
+ def <<(person_or_group)
80
+ raise "Must save member before adding to group" if person_or_group.new?
81
+ ABGroupAddMember(ab_group, person_or_group.ab_record, error)
82
+ end
83
+
84
+ private
85
+
86
+ def convert_dict_to_ab
87
+ @ab_group = ABGroupCreate()
88
+
89
+ # groups only have a single regular attribute (name)
90
+ if v = @attributes[:name]
91
+ ABRecordSetValue(@ab_group, KABGroupNameProperty, v, error)
92
+ end
93
+
94
+ save
95
+
96
+ @ab_group
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,91 @@
1
+ module AddressBook
2
+ class MultiValued
3
+ def initialize(opts)
4
+ unless opts.one?
5
+ raise ArgumentError, "MultiValued requires :attributes *or* :ab_multi_value argument"
6
+ end
7
+
8
+ if opts[:ab_multi_value]
9
+ # @ab_multi_value = ABMultiValueCreateMutableCopy(opts[:ab_multi_value])
10
+ @ab_multi_value = opts[:ab_multi_value]
11
+ else
12
+ @attributes = opts[:attributes]
13
+ end
14
+ end
15
+
16
+ def count
17
+ ABMultiValueGetCount(ab_multi_value)
18
+ end
19
+ alias :size :count
20
+
21
+ def attributes
22
+ @attributes ||= convert_multi_value_into_dictionary
23
+ end
24
+
25
+ def convert_multi_value_into_dictionary
26
+ count.times.map do |i|
27
+ label = ABMultiValueCopyLabelAtIndex(@ab_multi_value, i)
28
+ label_val = ABAddressBookCopyLocalizedLabel(label)
29
+ data = ab_record_to_dict(ABMultiValueCopyValueAtIndex(@ab_multi_value, i))
30
+ data.merge(:label => label_val)
31
+ end
32
+ end
33
+
34
+ def ab_multi_value
35
+ @ab_multi_value ||= convert_dictionary_into_multi_value
36
+ end
37
+
38
+ def convert_dictionary_into_multi_value
39
+ if @attributes.find {|rec| rec[:value]}
40
+ mv = ABMultiValueCreateMutable(KABMultiStringPropertyType)
41
+ @attributes.each do |rec|
42
+ ABMultiValueAddValueAndLabel(mv, rec[:value], rec[:label], nil)
43
+ end
44
+ mv
45
+ else
46
+ mv = ABMultiValueCreateMutable(KABMultiDictionaryPropertyType)
47
+ @attributes.each do |rec|
48
+ ABMultiValueAddValueAndLabel(mv, dict_to_ab_record(rec), rec[:label], nil)
49
+ end
50
+ mv
51
+ end
52
+ end
53
+
54
+ # these are for mapping fields in a kABMultiDictionaryPropertyType record
55
+ # to keys in a standard hash (NSDictionary)
56
+ @@attribute_map = {
57
+ KABPersonAddressStreetKey => :street,
58
+ KABPersonAddressCityKey => :city,
59
+ KABPersonAddressStateKey => :state,
60
+ KABPersonAddressZIPKey => :postalcode,
61
+ KABPersonAddressCountryKey => :country,
62
+ KABPersonAddressCountryCodeKey => :country_code,
63
+
64
+ KABPersonSocialProfileURLKey => :url,
65
+ KABPersonSocialProfileServiceKey => :service,
66
+ KABPersonSocialProfileUsernameKey => :username,
67
+ KABPersonSocialProfileUserIdentifierKey => :userid,
68
+
69
+ # these keys are identical to the SocialProfile keys above
70
+ KABPersonInstantMessageServiceKey => :service,
71
+ KABPersonInstantMessageUsernameKey => :username
72
+ }
73
+
74
+ def dict_to_ab_record(h)
75
+ @@attribute_map.each_with_object({}) do |(ab_key, attr_key), ab_record|
76
+ ab_record[ab_key] = h[attr_key] if h[attr_key]
77
+ end
78
+ end
79
+
80
+ def ab_record_to_dict(ab_record)
81
+ case ab_record
82
+ when String
83
+ {:value => ab_record}
84
+ else
85
+ @@attribute_map.each_with_object({}) do |(ab_key, attr_key), dict|
86
+ dict[attr_key] = ab_record[ab_key] if ab_record[ab_key]
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -1,24 +1,25 @@
1
1
  module AddressBook
2
2
  class Person
3
- attr_reader :attributes, :error, :ab_person
3
+ attr_reader :error
4
4
 
5
- def initialize(attributes={}, existing_ab_person = nil)
6
- @attributes = attributes
5
+ def initialize(attributes={}, existing_ab_person = nil, opts = {})
6
+ @address_book = opts[:address_book]
7
7
  if existing_ab_person.nil?
8
- @new_record = true
8
+ @ab_person = nil
9
+ @attributes = attributes
9
10
  else
10
11
  @ab_person = existing_ab_person
11
- load_ab_person
12
- @new_record = false
12
+ @attributes = nil
13
13
  end
14
14
  end
15
15
 
16
16
  def self.all
17
- ab_people = ABAddressBookCopyArrayOfAllPeople(AddressBook.address_book)
17
+ ab = AddressBook.address_book
18
+ ab_people = ABAddressBookCopyArrayOfAllPeople(ab)
18
19
  return [] if ab_people.nil?
19
20
 
20
21
  people = ab_people.map do |ab_person|
21
- new({}, ab_person)
22
+ new({}, ab_person, :address_book => ab)
22
23
  end
23
24
  people.sort! { |a,b| "#{a.first_name} #{a.last_name}" <=> "#{b.first_name} #{b.last_name}" }
24
25
  people
@@ -32,9 +33,26 @@ module AddressBook
32
33
 
33
34
  def save
34
35
  ABAddressBookAddRecord(address_book, ab_person, error)
35
- ABAddressBookSave(address_book, error )
36
- @address_book = nil #force refresh
37
- @new_record = false
36
+ ABAddressBookSave(address_book, error)
37
+ @attributes = nil # force refresh
38
+ uid
39
+ end
40
+
41
+ def attributes
42
+ @attributes || import_ab_person
43
+ end
44
+
45
+ def ab_person
46
+ if @ab_person.nil?
47
+ @ab_person = ABPersonCreate()
48
+ load_ab_person
49
+ end
50
+ @ab_person
51
+ end
52
+ alias :ab_record :ab_person
53
+
54
+ def uid
55
+ ABRecordGetRecordID(ab_person)
38
56
  end
39
57
 
40
58
  def self.where(conditions)
@@ -45,16 +63,27 @@ module AddressBook
45
63
 
46
64
  def meets?(conditions)
47
65
  conditions.keys.all? do |attribute|
48
- send(attribute) == conditions[attribute]
66
+ case attribute
67
+ when :email
68
+ emails.attributes.map {|rec| rec[:value]}.any? {|v| v == conditions[attribute]}
69
+ else
70
+ send(attribute) == conditions[attribute]
71
+ end
49
72
  end
50
73
  end
51
74
 
52
75
  def self.attribute_map
53
- { :first_name => KABPersonFirstNameProperty,
76
+ {
77
+ :first_name => KABPersonFirstNameProperty,
78
+ :middle_name => KABPersonMiddleNameProperty,
54
79
  :last_name => KABPersonLastNameProperty,
80
+ :suffix => KABPersonSuffixProperty,
81
+ :nickname => KABPersonNicknameProperty,
55
82
  :job_title => KABPersonJobTitleProperty,
56
83
  :department => KABPersonDepartmentProperty,
57
- :organization => KABPersonOrganizationProperty
84
+ :organization => KABPersonOrganizationProperty,
85
+ # :dob => KABPersonBirthdayProperty,
86
+ :note => KABPersonNoteProperty
58
87
  }
59
88
  end
60
89
  def attribute_map
@@ -136,14 +165,26 @@ module AddressBook
136
165
  attributes[attribute_name.to_sym] = value
137
166
  end
138
167
 
168
+ def self.find_by_uid(criteria)
169
+ find_by :uid, criteria
170
+ end
171
+
139
172
  def self.find_all_by(attribute_name, criteria)
140
173
  where({attribute_name.to_sym => criteria})
141
174
  end
142
175
  def self.find_by(attribute_name, criteria)
143
176
  find_all_by(attribute_name, criteria).first
144
177
  end
178
+ def self.new_by(attribute_name, criteria)
179
+ case attr_sym = attribute_name.to_sym
180
+ when :email
181
+ new({:emails => [{:value => criteria}]})
182
+ else
183
+ new({attr_sym => criteria})
184
+ end
185
+ end
145
186
  def self.find_or_new_by(attribute_name, criteria)
146
- find_by(attribute_name, criteria) || new({attribute_name.to_sym => criteria})
187
+ find_by(attribute_name, criteria) || new_by(attribute_name, criteria)
147
188
  end
148
189
 
149
190
  def photo
@@ -154,39 +195,51 @@ module AddressBook
154
195
  ABPersonSetImageData(ab_person, photo_data, error)
155
196
  end
156
197
 
198
+ def get_multi_valued(field)
199
+ MultiValued.new(:ab_multi_value => ABRecordCopyValue(ab_person, field))
200
+ end
157
201
 
158
- def phone_numbers
159
- get_multi_field(KABPersonPhoneProperty)
202
+ def phones
203
+ get_multi_valued(KABPersonPhoneProperty)
160
204
  end
161
205
 
162
- def phone_number_values
163
- phone_numbers.values
206
+ def phone_values
207
+ phones.attributes.map {|r| r[:value]}
164
208
  end
165
209
 
166
210
  def emails
167
- get_multi_field(KABPersonEmailProperty )
211
+ get_multi_valued(KABPersonEmailProperty)
168
212
  end
169
213
 
170
214
  def email_values
171
- emails.values
215
+ emails.attributes.map {|r| r[:value]}
172
216
  end
173
217
 
174
- # UGH - kinda arbitrary way to deal with multiple values. DO SOMETHING BETTER.
175
- def email
176
- @attributes[:email] ||= email_values.first
218
+ def addresses
219
+ get_multi_valued(KABPersonAddressProperty)
177
220
  end
178
- def phone_number
179
- @attributes[:phone_number] ||= phone_number_values.first
221
+
222
+ def urls
223
+ get_multi_valued(KABPersonURLProperty)
180
224
  end
181
225
 
182
- def ab_person
183
- if @ab_person.nil?
184
- @ab_person = ABPersonCreate()
185
- load_ab_person
186
- end
187
- @ab_person
226
+ def social_profiles
227
+ get_multi_valued(KABPersonSocialProfileProperty)
228
+ end
229
+
230
+ def im_profiles
231
+ get_multi_valued(KABPersonInstantMessageProperty)
232
+ end
233
+
234
+ def related_names
235
+ get_multi_valued(KABPersonRelatedNamesProperty)
188
236
  end
189
237
 
238
+ def email; email_values.first; end
239
+ def phone; phone_values.first; end
240
+ def url; urls.attributes.first[:value]; end
241
+ def address; addresses.attributes.first; end
242
+
190
243
  def find_or_new
191
244
  if new_record?
192
245
  new_ab_person
@@ -196,45 +249,129 @@ module AddressBook
196
249
  end
197
250
 
198
251
  def new_record?
199
- !!@new_record
252
+ uid == KABRecordInvalidID
200
253
  end
254
+ alias :new? :new_record?
201
255
  def exists?
202
256
  !new_record?
203
257
  end
204
- def inspect
205
- # ensure all attributes loaded
206
- attribute_map.keys.each do |attribute|
207
- self.send(attribute)
258
+
259
+ def delete!
260
+ unless new_record?
261
+ ABAddressBookRemoveRecord(address_book, ab_person, error)
262
+ ABAddressBookSave(address_book, error)
263
+ @ab_person = nil
264
+ self
208
265
  end
266
+ end
209
267
 
210
- super
268
+ def composite_name
269
+ ABRecordCopyCompositeName(ab_person)
270
+ end
271
+
272
+ def person?
273
+ get_field(KABPersonKindProperty) == KABPersonKindPerson
274
+ end
275
+ def organization?
276
+ get_field(KABPersonKindProperty) == KABPersonKindOrganization
277
+ end
278
+
279
+ # must stash date values in instance variables or RubyMotion throws a malloc error
280
+ def creation_date
281
+ @creation_date = get_field(KABPersonCreationDateProperty)
282
+ end
283
+
284
+ def modification_date
285
+ @modification_date = get_field(KABPersonModificationDateProperty)
211
286
  end
212
287
 
213
288
  private
214
289
 
290
+ def single_value_property_map
291
+ {
292
+ KABPersonFirstNameProperty => :first_name,
293
+ KABPersonLastNameProperty => :last_name,
294
+ KABPersonMiddleNameProperty => :middle_name,
295
+ KABPersonSuffixProperty => :suffix,
296
+ KABPersonNicknameProperty => :nickname,
297
+ KABPersonJobTitleProperty => :job_title,
298
+ KABPersonDepartmentProperty => :department,
299
+ KABPersonOrganizationProperty => :organization,
300
+ # KABPersonBirthdayProperty => :dob,
301
+ KABPersonNoteProperty => :note
302
+ }
303
+ end
304
+
305
+ def multi_value_property_map
306
+ {
307
+ KABPersonPhoneProperty => :phones,
308
+ KABPersonEmailProperty => :emails,
309
+ KABPersonAddressProperty => :addresses,
310
+ KABPersonURLProperty => :urls,
311
+ KABPersonSocialProfileProperty => :social_profiles,
312
+ KABPersonInstantMessageProperty => :im_profiles,
313
+ KABPersonRelatedNamesProperty => :related_names
314
+ }
315
+ end
316
+
317
+ # instantiates ABPerson record from attributes
215
318
  def load_ab_person
216
- set_field(KABPersonFirstNameProperty, attributes[:first_name ]) unless attributes[:first_name ].nil?
217
- set_field(KABPersonLastNameProperty, attributes[:last_name ]) unless attributes[:last_name ].nil?
218
- set_field(KABPersonJobTitleProperty, attributes[:job_title ]) unless attributes[:job_title ].nil?
219
- set_field(KABPersonDepartmentProperty, attributes[:department ]) unless attributes[:department ].nil?
220
- set_field(KABPersonOrganizationProperty, attributes[:organization]) unless attributes[:organization].nil?
221
- set_multi_field(KABPersonPhoneProperty, :mobile => attributes[:mobile_phone], :work => attributes[:office_phone])
222
- set_multi_field(KABPersonEmailProperty, :work => attributes[:email])
319
+ @attributes ||= {}
320
+
321
+ single_value_property_map.each do |ab_property, attr_key|
322
+ if attributes[attr_key]
323
+ set_field(ab_property, attributes[attr_key])
324
+ end
325
+ end
326
+
327
+ if attributes[:is_org]
328
+ set_field(KABPersonKindProperty, KABPersonKindOrganization)
329
+ else
330
+ set_field(KABPersonKindProperty, KABPersonKindPerson)
331
+ end
332
+
333
+ multi_value_property_map.each do |ab_property, attr_key|
334
+ if attributes[attr_key]
335
+ set_multi_valued(ab_property, attributes[attr_key])
336
+ end
337
+ end
338
+ end
339
+
340
+ def import_ab_person
341
+ @attributes = {}
342
+ single_value_property_map.each do |ab_property, attr_key|
343
+ if value = get_field(ab_property)
344
+ @attributes[attr_key] = value
345
+ end
346
+ end
347
+
348
+ if organization?
349
+ @attributes[:is_org] = true
350
+ end
351
+
352
+ multi_value_property_map.each do |ab_property, attr_key|
353
+ if (value = get_multi_valued(ab_property).attributes) && value.any?
354
+ @attributes[attr_key] = value
355
+ end
356
+ end
357
+
358
+ @attributes
223
359
  end
224
360
 
225
361
  def set_field(field, value)
226
- ABRecordSetValue(ab_person, field, value, error)
362
+ if value
363
+ ABRecordSetValue(ab_person, field, value, error)
364
+ end
227
365
  end
228
366
  def get_field(field)
229
367
  ABRecordCopyValue(ab_person, field)
230
368
  end
231
369
 
232
- def set_multi_field(field, values)
233
- multi_field = MultiValue.new(values, ABRecordCopyValue(ab_person, field))
234
- ABRecordSetValue(ab_person, field, multi_field.ab_multi_values, error )
235
- end
236
- def get_multi_field(field)
237
- MultiValue.new({}, ABRecordCopyValue(ab_person, field))
370
+ def set_multi_valued(field, values)
371
+ if values && values.any?
372
+ multi_field = MultiValued.new(:attributes => values)
373
+ ABRecordSetValue(ab_person, field, multi_field.ab_multi_value, nil)
374
+ end
238
375
  end
239
376
 
240
377
  def existing_records
@@ -253,6 +390,5 @@ module AddressBook
253
390
  def address_book
254
391
  @address_book ||= AddressBook.address_book
255
392
  end
256
-
257
393
  end
258
394
  end
@@ -9,6 +9,10 @@ module AddressBook
9
9
  end
10
10
  end
11
11
 
12
+ def count
13
+ ABAddressBookGetPersonCount(address_book)
14
+ end
15
+
12
16
  def ios6_create
13
17
  error = nil
14
18
  @address_book = ABAddressBookCreateWithOptions(nil, error)
@@ -17,7 +21,7 @@ module AddressBook
17
21
  end
18
22
 
19
23
  def ios5_create
20
- ABAddressBookCreate()
24
+ @address_book = ABAddressBookCreate()
21
25
  end
22
26
 
23
27
  def request_authorization(&block)
@@ -0,0 +1,59 @@
1
+ describe AddressBook::Group do
2
+ before do
3
+ @ab = AddressBook::AddrBook.new
4
+ end
5
+
6
+ describe 'an empty group' do
7
+ before do
8
+ @group = @ab.new_group(:name => 'Test Group')
9
+ end
10
+ after do
11
+ @group.delete!
12
+ end
13
+
14
+ it "should have a name" do
15
+ @group.name.should.equal 'Test Group'
16
+ end
17
+
18
+ it "should be empty" do
19
+ @group.members.should.be.empty
20
+ @group.size.should.equal 0
21
+ end
22
+
23
+ it "should not be new" do
24
+ @group.should.not.be.new
25
+ end
26
+ end
27
+
28
+ describe 'a group with members' do
29
+ before do
30
+ @p1 = @ab.new_person({:first_name => 'Alice', :emails => [{:label => 'home', :value => 'alice@example.com'}]})
31
+ @p1.save
32
+ @p2 = @ab.new_person({:first_name => 'Bob', :emails => [{:label => 'home', :value => 'bob@example.com'}]})
33
+ @p2.save
34
+ @group = @ab.new_group(:name => 'Test Group')
35
+ @group << @p1
36
+ @group << @p2
37
+ @group.save
38
+ end
39
+ after do
40
+ @group.delete!
41
+ @p1.delete!
42
+ @p2.delete!
43
+ end
44
+
45
+ it "should have 2 members" do
46
+ @group.size.should.equal 2
47
+ end
48
+
49
+ it "should have the expected members" do
50
+ @group.members.map {|person| person.first_name}.should.equal ['Alice', 'Bob']
51
+ end
52
+
53
+ it "should search by id" do
54
+ @ab.group(@group.uid).name.should.equal @group.name
55
+ end
56
+ end
57
+
58
+ # TODO: nested groups
59
+ end
@@ -0,0 +1,66 @@
1
+ describe AddressBook::MultiValued do
2
+ describe 'a string multi-value' do
3
+ before do
4
+ @attributes = [
5
+ {
6
+ :label => 'home page',
7
+ :value => "http://www.mysite.com/"
8
+ }, {
9
+ :label => 'work',
10
+ :value => 'http://dept.bigco.com/'
11
+ }, {
12
+ :label => 'school',
13
+ :value => 'http://state.edu/college'
14
+ }
15
+ ]
16
+ @mv = AddressBook::MultiValued.new(:attributes => @attributes)
17
+ end
18
+
19
+ it 'should be countable' do
20
+ @mv.count.should.equal 3
21
+ end
22
+
23
+ it 'should be reversible' do
24
+ abmv = @mv.ab_multi_value
25
+ mv2 = AddressBook::MultiValued.new(:ab_multi_value => abmv)
26
+ mv2.attributes.should.equal @attributes
27
+ end
28
+ end
29
+
30
+ describe 'a dictionary multi-value' do
31
+ before do
32
+ @attributes = [
33
+ {
34
+ :label => 'home',
35
+ :street => '123 Boring St',
36
+ :city => 'Snoozeville',
37
+ :state => "CA"
38
+ }, {
39
+ :label => 'work',
40
+ :street => '99 Exciting Ave, Suite #200',
41
+ :city => 'Las Vegas',
42
+ :state => "NV"
43
+ }
44
+ ]
45
+ @mv = AddressBook::MultiValued.new(:attributes => @attributes)
46
+ end
47
+
48
+ it 'should count the records' do
49
+ @mv.count.should.equal 2
50
+ @mv.size.should.equal 2
51
+ end
52
+
53
+ it 'should store the initial data correctly' do
54
+ @mv.attributes.should.equal @attributes
55
+ end
56
+
57
+ it 'should have correct internal representation' do
58
+ internal = @mv.ab_multi_value
59
+ ABMultiValueGetCount(internal).should.equal 2
60
+ ABMultiValueCopyLabelAtIndex(internal, 0).should.equal "home"
61
+ ABMultiValueCopyLabelAtIndex(internal, 1).should.equal "work"
62
+ ABMultiValueCopyValueAtIndex(internal, 0).keys.count.should.equal 3
63
+ ABMultiValueCopyValueAtIndex(internal, 1).keys.count.should.equal 3
64
+ end
65
+ end
66
+ end
@@ -1,28 +1,54 @@
1
1
  describe AddressBook::Person do
2
+ before do
3
+ @ab = AddressBook::AddrBook.new
4
+ end
2
5
  describe 'ways of creating and finding people' do
3
6
  describe 'new' do
4
7
  before do
5
- @alex = AddressBook::Person.new(:first_name => 'Alex', :last_name => 'Testy', :email => 'alex_testy@example.com')
8
+ @data = new_alex
9
+ @alex = @ab.new_person(@data)
6
10
  end
7
11
  it 'should create but not save in the address book' do
8
12
  @alex.should.be.new_record
13
+ end
14
+ it 'should have initial values' do
9
15
  @alex.first_name.should == 'Alex'
10
16
  @alex.last_name.should == 'Testy'
11
- @alex.email_values.should == ['alex_testy@example.com']
17
+ @alex.email_values.should == [@data[:emails][0][:value]]
18
+ @alex.email.should == @data[:emails][0][:value]
19
+ end
20
+ it "should round-trip attributes without loss" do
21
+ @alex.attributes.should.equal @data
22
+ end
23
+ it 'should have a composite name' do
24
+ @alex.composite_name.should == 'Alex Testy'
12
25
  end
13
26
  end
14
27
 
15
28
  describe 'existing' do
16
29
  before do
17
30
  @email = unique_email
18
- @alex = AddressBook::Person.create(:first_name => 'Alex', :last_name => 'Testy', :email => @email)
31
+ @alex = @ab.create_person(new_alex(@email))
32
+ end
33
+ after do
34
+ @alex.delete!
19
35
  end
20
- describe '.find_by_all_email' do
36
+ describe '.find_by_uid' do
37
+ it 'should find match' do
38
+ alex = @ab.person(@alex.uid)
39
+ alex.uid.should == @alex.uid
40
+ alex.email_values.should.include? @email
41
+ alex.first_name.should == 'Alex'
42
+ alex.last_name.should == 'Testy'
43
+ alex.attributes.should.equal @alex.attributes
44
+ end
45
+ end
46
+ describe '.find_all_by_email' do
21
47
  it 'should find matches' do
22
48
  alexes = AddressBook::Person.find_all_by_email @email
23
49
  alexes.should.not.be.empty
24
50
  alexes.each do |alex|
25
- alex.email.should == @email
51
+ alex.email_values.should.include? @email
26
52
  alex.first_name.should == 'Alex'
27
53
  alex.last_name.should == 'Testy'
28
54
  end
@@ -35,7 +61,7 @@ describe AddressBook::Person do
35
61
  describe '.find_by_email' do
36
62
  it 'should find match' do
37
63
  alex = AddressBook::Person.find_by_email @email
38
- alex.email.should == @email
64
+ alex.email_values.should.include? @email
39
65
  alex.first_name.should == 'Alex'
40
66
  alex.last_name.should == 'Testy'
41
67
  end
@@ -49,7 +75,7 @@ describe AddressBook::Person do
49
75
  alexes = AddressBook::Person.where(:email => @email)
50
76
  alexes.should.not.be.empty
51
77
  alexes.each do |alex|
52
- alex.email.should == @email
78
+ alex.email_values.should.include? @email
53
79
  alex.first_name.should == 'Alex'
54
80
  alex.last_name.should == 'Testy'
55
81
  end
@@ -62,16 +88,17 @@ describe AddressBook::Person do
62
88
 
63
89
  describe '.all' do
64
90
  it 'should have the person we created' do
65
- all_names = AddressBook::Person.all.map do |person|
91
+ all_names = @ab.people.map do |person|
66
92
  [person.first_name, person.last_name]
67
93
  end
68
94
  all_names.should.include? [@alex.first_name, @alex.last_name]
69
95
  end
70
96
 
71
97
  it 'should get bigger when we create another' do
72
- initial_people_count = AddressBook::Person.all.size
73
- @person = AddressBook::Person.create({:first_name => 'Alex2', :last_name=>'Rothenberg2'})
74
- AddressBook::Person.all.size.should == (initial_people_count + 1)
98
+ initial_people_count = @ab.people.size
99
+ @person = @ab.create_person({:first_name => 'Alex2', :last_name=>'Rothenberg2'})
100
+ @ab.people.size.should == (initial_people_count + 1)
101
+ @person.delete!
75
102
  end
76
103
  end
77
104
  end
@@ -79,37 +106,64 @@ describe AddressBook::Person do
79
106
  describe '.find_or_new_by_XXX - new or existing' do
80
107
  before do
81
108
  @email = unique_email
82
- AddressBook::Person.create(:first_name => 'Alex', :last_name => 'Testy', :email => @email)
109
+ @alex = @ab.create_person(new_alex(@email))
110
+ end
111
+ after do
112
+ @alex.delete!
83
113
  end
114
+
84
115
  it 'should find an existing person' do
85
116
  alex = AddressBook::Person.find_or_new_by_email(@email)
86
117
  alex.should.not.be.new_record
87
- alex.email.should == @email
88
118
  alex.first_name.should == 'Alex'
89
119
  alex.last_name.should == 'Testy'
120
+ alex.emails.attributes.map{|r| r[:value]}.should == [@email]
90
121
  end
91
122
  it 'should return new person when no match found' do
92
123
  never_before_used_email = unique_email
93
- alex = AddressBook::Person.find_or_new_by_email(never_before_used_email)
94
- alex.should.be.new_record
95
- alex.email.should == never_before_used_email
96
- alex.first_name.should == nil
124
+ new_person = AddressBook::Person.find_or_new_by_email(never_before_used_email)
125
+ new_person.should.be.new_record
126
+ new_person.email_values.should == [never_before_used_email]
127
+ new_person.first_name.should == nil
97
128
  end
98
129
  end
99
130
  end
100
131
 
101
132
  describe 'save' do
102
133
  before do
103
- @attributes = {:first_name=>'Alex', :last_name=>'Testy',
104
- :job_title => 'Developer', :department => 'Development', :organization => 'The Company',
105
- :mobile_phone => '123 456 7890', :office_phone => '987 654 3210',
106
- :email => unique_email
107
- }
134
+ @attributes = {
135
+ :first_name=>'Alex',
136
+ :middle_name=>'Q.',
137
+ :last_name=>'Testy',
138
+ :suffix => 'III',
139
+ :nickname => 'Geekster',
140
+ :job_title => 'Developer',
141
+ :department => 'Development',
142
+ :organization => 'The Company',
143
+ :note => 'some important guy',
144
+ # :mobile_phone => '123 456 7890', :office_phone => '987 654 3210',
145
+ :phones => [
146
+ {:label => 'mobile', :value => '123 456 7899'},
147
+ {:label => 'office', :value => '987 654 3210'}
148
+ ],
149
+ # :email => unique_email,
150
+ :emails => [
151
+ {:label => 'work', :value => unique_email}
152
+ ],
153
+ :addresses => [
154
+ {:label => 'home', :city => 'Dogpatch', :state => 'KY'}
155
+ ],
156
+ :urls => [
157
+ { :label => 'home page', :value => "http://www.mysite.com/" },
158
+ { :label => 'work', :value => 'http://dept.bigco.com/' },
159
+ { :label => 'school', :value => 'http://state.edu/college' }
160
+ ]
161
+ }
108
162
  end
109
163
 
110
164
  describe 'a new person' do
111
165
  before do
112
- @ab_person = AddressBook::Person.new(@attributes)
166
+ @ab_person = @ab.new_person(@attributes)
113
167
  end
114
168
 
115
169
  it 'should not be existing' do
@@ -120,9 +174,21 @@ describe AddressBook::Person do
120
174
  it 'should be able to get each of the single value fields' do
121
175
  @ab_person.first_name.should.equal @attributes[:first_name ]
122
176
  @ab_person.last_name.should.equal @attributes[:last_name ]
177
+ @ab_person.middle_name.should.equal @attributes[:middle_name ]
178
+ @ab_person.suffix.should.equal @attributes[:suffix ]
179
+ @ab_person.nickname.should.equal @attributes[:nickname ]
123
180
  @ab_person.job_title.should.equal @attributes[:job_title ]
124
181
  @ab_person.department.should.equal @attributes[:department ]
125
182
  @ab_person.organization.should.equal @attributes[:organization]
183
+ @ab_person.note.should.equal @attributes[:note]
184
+ @ab_person.should.be.person?
185
+ end
186
+
187
+ it 'should get a value back for singular requests against multi-value attributes' do
188
+ @ab_person.email.should.equal @attributes[:emails].first[:value]
189
+ @ab_person.phone.should.equal @attributes[:phones].first[:value]
190
+ @ab_person.url.should.equal @attributes[:urls].first[:value]
191
+ @ab_person.address.should.equal @attributes[:addresses].first
126
192
  end
127
193
 
128
194
  describe 'setting each field' do
@@ -147,7 +213,7 @@ describe AddressBook::Person do
147
213
  @ab_person.organization.should.equal 'new organization'
148
214
  end
149
215
 
150
- it 'should be able to set the phot' do
216
+ it 'should be able to set the photo' do
151
217
  image = CIImage.emptyImage
152
218
  data = UIImagePNGRepresentation(UIImage.imageWithCIImage image)
153
219
  @ab_person.photo = data
@@ -155,31 +221,90 @@ describe AddressBook::Person do
155
221
  end
156
222
  end
157
223
 
158
- it 'should be able to get the phone numbers' do
159
- @ab_person.phone_number_values.should.equal [@attributes[:mobile_phone], @attributes[:office_phone] ]
224
+ it 'should be able to count & get the phone numbers' do
225
+ @ab_person.phones.size.should.equal 2
226
+ @ab_person.phones.attributes.should.equal @attributes[:phones]
227
+ end
228
+
229
+ it 'should be able to count & get the emails' do
230
+ @ab_person.emails.size.should.equal 1
231
+ @ab_person.emails.attributes.should.equal @attributes[:emails]
160
232
  end
161
233
 
162
- it 'should be able to get the emails' do
163
- @ab_person.email_values.should.equal [@attributes[:email] ]
234
+ it 'should be able to count & inspect the addresses' do
235
+ @ab_person.addresses.count.should.equal 1
236
+ @ab_person.addresses.attributes.should.equal @attributes[:addresses]
164
237
  end
165
- describe 'saving' do
238
+
239
+ it 'should be able to count & inspect the URLs' do
240
+ @ab_person.urls.count.should.equal 3
241
+ @ab_person.urls.attributes.should.equal @attributes[:urls]
242
+ end
243
+
244
+ describe 'once saved' do
166
245
  before do
246
+ @before_count = AddressBook.count
167
247
  @ab_person.save
168
248
  end
169
- it 'after saving it should not be existing' do
249
+ after do
250
+ @ab_person.delete!
251
+ end
252
+
253
+ it 'should no longer be new' do
170
254
  @ab_person.should.not.be.new_record
171
255
  @ab_person.should.be.exists
172
256
  end
257
+
258
+ it "should increment the count" do
259
+ AddressBook.count.should.equal @before_count+1
260
+ end
261
+
262
+ it 'should round-trip all attributes without loss' do
263
+ @ab_person.attributes.should.equal @attributes
264
+ end
265
+
266
+ it 'should have scalar properties' do
267
+ [:first_name, :middle_name, :last_name, :job_title, :department, :organization, :note].each do |attr|
268
+ @ab_person.send(attr).should.equal @attributes[attr]
269
+ end
270
+ end
271
+
272
+ it 'should have a composite name' do
273
+ @ab_person.composite_name.should == 'Alex Q. Testy III'
274
+ end
275
+
276
+ it 'should be able to count the emails' do
277
+ @ab_person.emails.size.should.equal 1
278
+ end
279
+
280
+ it 'should be able to count the addresses' do
281
+ @ab_person.addresses.count.should.equal 1
282
+ end
283
+
284
+ it 'should be able to retrieve the addresses' do
285
+ @ab_person.addresses.attributes.should.equal @attributes[:addresses]
286
+ end
287
+ end
288
+
289
+ describe 'once deleted' do
290
+ before do
291
+ @ab_person.save
292
+ @ab_person.delete!
293
+ end
294
+ it 'should no longer exist' do
295
+ @ab_person.should.not.be.exists
296
+ # @ab_person.should.be.new_record
297
+ end
173
298
  end
174
299
  end
175
300
 
176
- describe 'updating an existing person' do
301
+ describe 'an existing person' do
177
302
  before do
178
- AddressBook::Person.new(@attributes).save
179
- @attributes[:job_title ] = 'i got promoted'
180
- @attributes[:office_phone] = '111 222 3333'
181
- @attributes[:department ] = nil
182
- @ab_person = AddressBook::Person.find_or_new_by_email(@attributes[:email])
303
+ @orig_ab_person = @ab.create_person(@attributes)
304
+ @ab_person = AddressBook::Person.find_or_new_by_email(@attributes[:emails][0][:value])
305
+ end
306
+ after do
307
+ @ab_person.delete!
183
308
  end
184
309
 
185
310
  it 'should know it is not new' do
@@ -189,23 +314,75 @@ describe AddressBook::Person do
189
314
  @ab_person.department.should == 'Development'
190
315
  end
191
316
 
192
- describe 'updating' do
193
- it 'should be able to get each of the single value fields' do
317
+ it 'should not change ID' do
318
+ @ab_person.uid.should.equal @orig_ab_person.uid
319
+ end
320
+
321
+ describe 'when updated' do
322
+ before do
323
+ @ab_person.first_name = 'New First Name'
194
324
  @ab_person.save
195
- @new_ab_person = AddressBook::Person.find_by_email @ab_person.email
196
- @new_ab_person.first_name = 'New First Name'
197
- @new_ab_person.save
198
- AddressBook::Person.find_by_email(@ab_person.email).first_name.should == 'New First Name'
199
325
  end
326
+
327
+ it 'should be able to get each of the single value fields' do
328
+ @match = AddressBook::Person.find_by_email(@ab_person.email_values.first)
329
+ @match.first_name.should == 'New First Name'
330
+ @match.uid.should.equal @ab_person.uid
331
+ end
332
+ end
333
+ end
334
+
335
+ describe "input with bad attributes" do
336
+ before do
337
+ @attributes[:junk] = 'this should be ignored'
338
+ @attributes[:last_name] = nil
339
+ @attributes[:urls] = [
340
+ { :value => "http://www.mysite.com/" },
341
+ { :label => 'work' },
342
+ { :label => 'work', :url => 'http://state.edu/college' }
343
+ ]
344
+ @ab_person = @ab.create_person(@attributes)
345
+ end
346
+ after do
347
+ @ab_person.delete!
348
+ end
349
+
350
+ # entries with missing label should be OK
351
+ # entries with missing value should be ignored
352
+ # entries with illegal fields should raise an exception
353
+ it "should save without errors" do
354
+ @ab_person.should.be.exists
355
+ end
356
+
357
+ it "should have the expected values" do
358
+ @ab_person.urls.count.should.equal 1
359
+ urldata = [{:value => "http://www.mysite.com/", :label => ""}]
360
+ @ab_person.urls.attributes.should.equal urldata
361
+ @ab_person.attributes.keys.should.not.include?(:junk)
362
+ @ab_person.last_name.should.equal nil
200
363
  end
364
+ end
365
+ end
201
366
 
367
+ describe "organization record" do
368
+ before do
369
+ @person = @ab.new_person(
370
+ :first_name => 'John',
371
+ :last_name => 'Whorfin',
372
+ :organization => 'Acme Inc.',
373
+ :is_org => true,
374
+ :note => 'big important company'
375
+ )
202
376
  end
203
377
 
378
+ it "should know that it is an organization" do
379
+ @person.should.be.organization?
380
+ end
204
381
  end
205
382
 
206
383
  describe 'method missing magic' do
207
384
  before do
208
- @person = AddressBook::Person.new
385
+ @person = @ab.new_person({})
209
386
  end
210
387
  describe 'getters' do
211
388
  it 'should have a getter for each attribute' do
@@ -236,6 +413,9 @@ describe AddressBook::Person do
236
413
  @person.setter?('find_all_by_email').should.be falsey
237
414
  @person.setter?('find_by_email' ).should.be falsey
238
415
  end
416
+ it 'should not have a setter for uid' do
417
+ @person.setter?('uid=').should.be falsey
418
+ end
239
419
  end
240
420
  describe 'all_finders' do
241
421
  it 'should have a finder for each attribute' do
@@ -2,3 +2,11 @@ def unique_email(email='alex_testy@example.com')
2
2
  name, domain = email.split('@')
3
3
  "#{name}_#{Time.now.to_i}_#{rand(1000)}@#{domain}"
4
4
  end
5
+
6
+ def new_alex(emailaddr = unique_email)
7
+ {
8
+ :first_name => 'Alex',
9
+ :last_name => 'Testy',
10
+ :emails => [{:label => 'home', :value => emailaddr}]
11
+ }
12
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motion-addressbook
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-14 00:00:00.000000000 Z
12
+ date: 2013-04-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bubble-wrap
@@ -67,6 +67,7 @@ extensions: []
67
67
  extra_rdoc_files: []
68
68
  files:
69
69
  - .gitignore
70
+ - .travis.yml
70
71
  - Gemfile
71
72
  - LICENSE
72
73
  - README.md
@@ -75,10 +76,15 @@ files:
75
76
  - lib/motion-addressbook/version.rb
76
77
  - motion-addressbook.gemspec
77
78
  - motion/address_book.rb
79
+ - motion/address_book/addr_book.rb
80
+ - motion/address_book/group.rb
78
81
  - motion/address_book/multi_value.rb
82
+ - motion/address_book/multi_valued.rb
79
83
  - motion/address_book/person.rb
80
84
  - motion/address_book/picker.rb
85
+ - spec/address_book/group_spec.rb
81
86
  - spec/address_book/multi_value_spec.rb
87
+ - spec/address_book/multi_valued_spec.rb
82
88
  - spec/address_book/person_spec.rb
83
89
  - spec/address_book/picker_spec.rb
84
90
  - spec/helpers/bacon_matchers.rb
@@ -98,7 +104,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
98
104
  version: '0'
99
105
  segments:
100
106
  - 0
101
- hash: 3801596602075157460
107
+ hash: 3256350264444301144
102
108
  required_rubygems_version: !ruby/object:Gem::Requirement
103
109
  none: false
104
110
  requirements:
@@ -107,7 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
113
  version: '0'
108
114
  segments:
109
115
  - 0
110
- hash: 3801596602075157460
116
+ hash: 3256350264444301144
111
117
  requirements: []
112
118
  rubyforge_project:
113
119
  rubygems_version: 1.8.24
@@ -115,7 +121,9 @@ signing_key:
115
121
  specification_version: 3
116
122
  summary: A RubyMotion wrapper around the iOS Address Book framework
117
123
  test_files:
124
+ - spec/address_book/group_spec.rb
118
125
  - spec/address_book/multi_value_spec.rb
126
+ - spec/address_book/multi_valued_spec.rb
119
127
  - spec/address_book/person_spec.rb
120
128
  - spec/address_book/picker_spec.rb
121
129
  - spec/helpers/bacon_matchers.rb