motion-addressbook 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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