osm 1.2.17 → 1.2.18.dev

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZTc1N2JkYjExMjkyOGE2YTRhMWViOTUyMjU0ZDEyM2Y4MTI5ZTA5Zg==
4
+ OTBkZTQxM2Y1NmMwZjMxZmFiMmFhMTk2ZDUyNjYyZTg4YWFiNDMzMw==
5
5
  data.tar.gz: !binary |-
6
- NGYzOGM2NTgyMTdlOTMyNzY4MGUwYWYxZDQwNzRiY2QxM2ExNTdhNg==
6
+ YmJmNjIzODQ1Mjg5MGNhODE4MGRjMzg0MjE2ZTY4N2YxZDVlYzJmYQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZGY4NGNiNzgwNTY3ODk1YTdlZjUxNTZmM2NhMzk3ZDJhZDQ0OGE4NTI0ZTIz
10
- ZThjYTM0ZTc0YjA1ODVhZTUxYWEwZmQ0ZDEwMmQzYzQyODc2ZmNiNzJiMzY5
11
- NzhlY2QxZDI5MTg4MDZlOTU2MzQ4Y2Q2ZDI1ODIzOGQ1ZjQyOGI=
9
+ ZWViMDM1YjhmMTk1MjRkYzBmYzkxM2E4OTcyMzQ1MDg1NDcwZmY1ODRlODAw
10
+ ODU1YWFkMmU0Njk2YWQ2YWZjNGViNDJmZGRhNWQ2N2YwNDFlZjcxZTU2Mjll
11
+ YjM3MTFkOGYyZmYzZjJjZGJhYWE3MWMxYTIwNjAwODFiYTczZmY=
12
12
  data.tar.gz: !binary |-
13
- MmYxYjc2YzU4NDVjZjc0YjkwNzdjNTk0ZWEwODI2ZjZhODcyOWM3NDU5ODMx
14
- ZDE4M2JmNmE2Nzc0NjQ1NTM5N2E2OWUxYmZlNTM5Y2U3OGYwNmZmOWViMzc3
15
- NDcxYmRiMDc5MzkyOGYxZWM4ZjcwNjliMmY1M2UwYmNlYWYyY2M=
13
+ ZmM5ZWMyMzc2NzdkZWYxOGIzYTJmYzMwMGYzYTEyM2U0MjA0MTJkMDQyNjhj
14
+ MzQxNTI5NjVkZDFlOGYwNzRhZDVkMGVkZGFjMTEyZWRmODI5YjMxZmYzNzhj
15
+ MjAwZDcyZjM1MWYyYzE4NzY4MzBiZGRmM2IwMjI3MWMxZDE3MTA=
data/CHANGELOG.md CHANGED
@@ -1,3 +1,38 @@
1
+ ## Version 1.2.18
2
+
3
+ * THIS IS A DEVELOPMENT VERSION OF THIS RELEASE. IT EXISTS ONLY SO YOU CAN PLAY WITH THE NEW STRUCTIRE OF MEMBERS YOU'LL GET WHEN OSM CHANGES. WRITING CHANGES BACK TO OSM HAS NOT YET BEEN UPDATED.
4
+ * Osm::Section - removal of column_names, fields, intouch_fields, mobile_fields attributes
5
+ * Osm::Member - lots of changes to reflect OSM's new structure for member's details (main ones listed below):
6
+ * Removal of type attribute (I never could work out what it represented)
7
+ * Removal of data attributes except:
8
+ * id
9
+ * section_id
10
+ * first_name
11
+ * last_name
12
+ * date_of_birth
13
+ * grouping_id
14
+ * grouping_leader
15
+ * age
16
+ * Addition of attibutes:
17
+ * gender (Symbol - :male, :female, :other or :unspecified)
18
+ * finished_section (Date, nil)
19
+ * custom (DirtyHashy) - The customisable data part from OSM
20
+ * custom_labels (DirtyHashy) - The labels which belong to the data in custom
21
+ * primary_contact, secondary_contact, emergency_contact and doctor - the relevant parts from OSM
22
+ * grouping_label
23
+ * grouping_leader_label
24
+ * Renamed attributes:
25
+ * started becomes joined_movement
26
+ * joined becomes started_section
27
+ * New helper methods:
28
+ * male?
29
+ * female?
30
+ * current?(date=Date.today) - is the member a member of the section on the passed date
31
+ * all_emails, all_emails_with_name, enabled_emails, enabled_emails_with_name
32
+ * all_phones, enabled_phones
33
+ * Validation changes:
34
+ * age should now be in the format 'yy / mm' not 'yy/mm'
35
+
1
36
  ## Version 1.2.17
2
37
 
3
38
  * Update to match how OSM now lists activities within meetings.
data/gemfiles/rails3 CHANGED
@@ -6,7 +6,7 @@ gem 'activesupport', '~> 3.2'
6
6
  gem 'dirty_hashy', '~> 0.2.1'
7
7
  gem 'httparty', '~> 0.9'
8
8
  gem 'rake', '~> 10.1'
9
- gem 'rspec', '~> 2.0', '>= 2.14.1'
9
+ gem 'rspec', '>= 2.14.1', '< 4'
10
10
  gem 'fakeweb', '~> 1.3.0'
11
11
  gem 'osm', :path=>'../'
12
12
  gem 'coveralls', '~> 0.7'
@@ -4,20 +4,28 @@ class HashValidator < ActiveModel::EachValidator
4
4
 
5
5
  value.each do |k, v|
6
6
  if options[:key_type]
7
- record.errors.add(attribute, "keys must be a #{options[:key_type].name}") unless k.is_a?(options[:key_type])
7
+ unless k.is_a?(options[:key_type])
8
+ record.errors.add(attribute, "keys must be a #{options[:key_type].name} (#{k.inspect} is not).")
9
+ end
8
10
  end
9
11
 
10
12
  if options[:key_in]
11
- record.errors.add(attribute, "keys must be in #{options[:key_in].inspect}") unless options[:key_in].include?(k)
13
+ unless options[:key_in].include?(k)
14
+ record.errors.add(attribute, "keys must be in #{options[:key_in].inspect} (#{k.inspect} is not).")
15
+ end
12
16
  end
13
17
 
14
18
 
15
19
  if options[:value_type]
16
- record.errors.add(attribute, "values must be a #{options[:value_type].name}") unless v.is_a?(options[:value_type])
20
+ unless v.is_a?(options[:value_type])
21
+ record.errors.add(attribute, "values must be a #{options[:value_type].name} (#{v.inspect} for key #{k.inspect} is not).")
22
+ end
17
23
  end
18
24
 
19
25
  if options[:value_in]
20
- record.errors.add(attribute, "values must be in #{options[:value_in].inspect}") unless options[:value_in].include?(v)
26
+ unless options[:value_in].include?(v)
27
+ record.errors.add(attribute, "values must be in #{options[:value_in].inspect} (#{v.inspect} for key #{k.inspect} is not).")
28
+ end
21
29
  end
22
30
  end
23
31
  end
data/lib/osm/api.rb CHANGED
@@ -30,9 +30,9 @@ module Osm
30
30
  # @option options [Boolean] :debug if true debugging info is output (optional, default = false)
31
31
  # @return nil
32
32
  def self.configure(options)
33
- raise ArgumentError, ':default_site does not exist in options hash or is invalid, this should be set to either :osm or :ogm' unless [:osm, :ogm, :osm_staging].include?(options[:default_site])
33
+ raise ArgumentError, ':default_site does not exist in options hash or is invalid, this should be set to either :osm or :ogm' unless Osm::Api::BASE_URLS.keys.include?(options[:default_site])
34
34
  raise ArgumentError, ":#{options[:default_site]} does not exist in options hash" if options[options[:default_site]].nil?
35
- [:osm, :ogm, :osm_staging].each do |api_key|
35
+ Osm::Api::BASE_URLS.keys.each do |api_key|
36
36
  if options[api_key]
37
37
  api_data = options[api_key]
38
38
  raise ArgumentError, ":#{api_key} must be a Hash" unless api_data.is_a?(Hash)
@@ -73,7 +73,7 @@ module Osm
73
73
  def initialize(user_id, secret, site=@@site)
74
74
  raise ArgumentError, 'You must pass a secret (get this by using the authorize method)' if secret.nil?
75
75
  raise ArgumentError, 'You must pass a user_id (get this by using the authorize method)' if user_id.nil?
76
- raise ArgumentError, 'site is invalid, if passed it should be either :osm or :ogm, if not passed then you forgot to run Api.configure' unless [:osm, :ogm, :osm_staging].include?(site)
76
+ raise ArgumentError, 'site is invalid, if passed it should be either :osm or :ogm, if not passed then you forgot to run Api.configure' unless Osm::Api::BASE_URLS.keys.include?(site)
77
77
 
78
78
  @site = site
79
79
  set_user(user_id, secret)
@@ -145,7 +145,7 @@ module Osm
145
145
  # @param [Symbol] site For OSM or OGM (:osm or :ogm)
146
146
  # @return [String] The base URL for requests
147
147
  def self.base_url(site=@@site)
148
- raise ArgumentError, "Invalid site" unless [:osm, :ogm].include?(site)
148
+ raise ArgumentError, "Invalid site" unless Osm::Api::BASE_URLS.keys.include?(site)
149
149
  BASE_URLS[site]
150
150
  end
151
151
 
@@ -153,7 +153,6 @@ module Osm
153
153
  # @param site For OSM or OGM (:osm or :ogm), defaults to the default for this api object
154
154
  # @return [String] The base URL for requests
155
155
  def base_url(site=@site)
156
- raise ArgumentError, "Invalid site" unless [:osm, :ogm].include?(site)
157
156
  self.class.base_url(site)
158
157
  end
159
158
 
@@ -211,7 +210,7 @@ module Osm
211
210
  # @raise [Osm::Error] If an error was returned by OSM
212
211
  # @raise [Osm::ConnectionError] If an error occured connecting to OSM
213
212
  def self.perform_query(site, url, api_data={})
214
- raise ArgumentError, 'site is invalid, this should be set to either :osm or :ogm' unless [:osm, :ogm, :osm_staging].include?(site)
213
+ raise ArgumentError, 'site is invalid, this should be set to either :osm or :ogm' unless Osm::Api::BASE_URLS.keys.include?(site)
215
214
 
216
215
  data = api_data.merge({
217
216
  'apiid' => @@api_details[site][:id],
data/lib/osm/member.rb CHANGED
@@ -1,151 +1,130 @@
1
1
  module Osm
2
2
 
3
3
  class Member < Osm::Model
4
+ # Constants for group id
5
+ GID_PRIMARY_CONTACT = 1
6
+ GID_SECONDARY_CONTACT = 2
7
+ GID_EMERGENCY_CONTACT = 3
8
+ GID_DOCTOR_CONTACT = 4
9
+ GID_CUSTOM = 5
10
+ GID_MEMBER_CONTACT = 6
11
+ GID_FLOATING = 7
12
+
13
+ # Constants for column id
14
+ CUSTOM_FIELD_IDS_START_AT = 55
15
+ CORE_FIELD_IDS_FINISH_AT = CUSTOM_FIELD_IDS_START_AT - 1
16
+ CORE_FIELD_IDS = (1..54).to_a
17
+ CID_FIRST_NAME = 2
18
+ CID_LAST_NAME = 3
19
+ CID_ADDRESS_1 = 7
20
+ CID_ADDRESS_2 = 8
21
+ CID_ADDRESS_3 = 9
22
+ CID_ADDRESS_4 = 10
23
+ CID_POSTCODE = 11
24
+ CID_EMAIL_1 = 12
25
+ CID_RECIEVE_EMAIL_1 = 13
26
+ CID_EMAIL_2 = 14
27
+ CID_RECIEVE_EMAIL_2 = 15
28
+ CID_PHONE_1 = 18
29
+ CID_RECIEVE_PHONE_1 = 19
30
+ CID_PHONE_2 = 20
31
+ CID_RECIEVE_PHONE_2 = 21
32
+ CID_GENDER = 34
33
+ CID_SURGERY = 54
4
34
 
5
35
  # @!attribute [rw] id
6
36
  # @return [Fixnum] the id for the member
7
37
  # @!attribute [rw] section_id
8
38
  # @return [Fixnum] the section the member belongs to
9
- # @!attribute [rw] type
10
- # @return [String] ?
11
39
  # @!attribute [rw] first_name
12
40
  # @return [String] the member's first name
13
41
  # @!attribute [rw] last_name
14
42
  # @return [String] the member's last name
15
- # @!attribute [rw] email1
16
- # @return [String] the 1st email address for the member
17
- # @!attribute [rw] email2
18
- # @return [String] the 2nd email address for the member
19
- # @!attribute [rw] email3
20
- # @return [String] the 3rd email address for the member
21
- # @!attribute [rw] email4
22
- # @return [String] the 4th email address for the member
23
- # @!attribute [rw] phone1
24
- # @return [String] the 1st phone number for the member
25
- # @!attribute [rw] phone2
26
- # @return [String] the 2nd phone number for the member
27
- # @!attribute [rw] phone3
28
- # @return [String] the 3rd phone number for the member
29
- # @!attribute [rw] phone4
30
- # @return [String] the 4th phone number for the member
31
- # @!attribute [rw] address
32
- # @return [String] the member's address
33
- # @!attribute [rw] address2
34
- # @return [String] the member's 2nd address
35
- # @!attribute [rw] date_of_birth
36
- # @return [Date] the member's date of birth
37
- # @!attribute [rw] started
38
- # @return [Date] when the member started Scouting
39
- # @!attribute [rw] joining_in_years
40
- # @return [Fixnum] ?
41
- # @!attribute [rw] parents
42
- # @return [String] the member's parent's names
43
- # @!attribute [rw] notes
44
- # @return [String] notes relating to the member
45
- # @!attribute [rw] medical
46
- # @return [String] the member's key medical details
47
- # @!attribute [rw] religion
48
- # @return [String] the member's religion
49
- # @!attribute [rw] school
50
- # @return [String] the school the member attends
51
- # @!attribute [rw] ethnicity
52
- # @return [String] the member's ethnicity
53
- # @!attribute [rw] subs
54
- # @return [String] details about the member's subs
55
- # @!attribute [rw] custom1
56
- # @return [String] the custom1 data for the member
57
- # @!attribute [rw] custom2
58
- # @return [String] the custom2 data for the member
59
- # @!attribute [rw] custom3
60
- # @return [String] the custom3 data for the member
61
- # @!attribute [rw] custom4
62
- # @return [String] the custom4 data for the member
63
- # @!attribute [rw] custom5
64
- # @return [String] the custom5 data for the member
65
- # @!attribute [rw] custom6
66
- # @return [String] the custom6 data for the member
67
- # @!attribute [rw] custom7
68
- # @return [String] the custom7 data for the member
69
- # @!attribute [rw] custom8
70
- # @return [String] the custom8 data for the member
71
- # @!attribute [rw] custom9
72
- # @return [String] the custom9 data for the member
73
43
  # @!attribute [rw] grouping_id
74
- # @return [Fixnum] the grouping within the section that the member belongs to
44
+ # @return [Fixnum] the ID of the grouping within the section that the member belongs to
45
+ # @!attribute [rw] grouping_label
46
+ # @return [String] the name of the grouping within the section that the member belongs to
75
47
  # @!attribute [rw] grouping_leader
76
- # @return [Fixnum] whether the member is the grouping leader (0=no, 1=seconder/APL, 2=sixer/PL)
77
- # @!attribute [rw] joined
78
- # @return [Date] when the member joined the section
48
+ # @return [Fixnum] whether the member is the grouping leader (0=no, 1=seconder/APL, 2=sixer/PL, 3=senior PL)
49
+ # @!attribute [rw] grouping_leader_label
50
+ # @return [String] whether the member is the grouping leader
79
51
  # @!attribute [rw] age
80
- # @return [String] the member's current age (yy/mm)
81
- # @!attribute [rw] joined_years
82
- # @return [Fixnum] how many years the member has been in Scouting
83
- # @!attribute [rw] has_photo
84
- # @return [Boolean] whether the scout has a photo in OSM
52
+ # @return [String] the member's current age (yy/mm)
53
+ # @!attribute [rw] gender
54
+ # @return [Symbol] the member's gender (:male, :female, :other or :unspecified)
55
+ # @!attribute [rw] date_of_birth
56
+ # @return [Date] the member's date of birth
57
+ # @!attribute [rw] started_section
58
+ # @return [Date] when the member started the section they were retrieved for
59
+ # @!attribute [rw] finished_section
60
+ # @return [Date] when the member finished the section they were retrieved for
61
+ # @!attribute [rw] joined_movement
62
+ # @return [Date] when the member joined the movement
63
+ # @!attribute [rw] custom
64
+ # @return [DirtyHashy] the custom data (key is OSM's variable name, value is the data)
65
+ # @!attribute [rw] custom_labels
66
+ # @return [DirtyHashy] the labels for the custom data (key is OSM's variable name, value is the label)
67
+ # @!attribute [rw] contact
68
+ # @return [Osm::Member::MemberContact] the member's contact details
69
+ # @!attribute [rw] primary_contact
70
+ # @return [Osm::Member::PrimaryContact] the member's primary contact (primary contact 1 in OSM)
71
+ # @!attribute [rw] secondary_contact
72
+ # @return [Osm::Member::PrimaryContact] the member's secondary contact (primary contact 2 in OSM)
73
+ # @!attribute [rw] emergency_contact
74
+ # @return [Osm::Member::EmergencyContact] the member's emergency contact
75
+ # @!attribute [rw] doctor
76
+ # @return [Osm::Member::DoctorContact] the member's doctor
85
77
 
86
78
  attribute :id, :type => Integer
87
79
  attribute :section_id, :type => Integer
88
- attribute :type, :type => String
89
80
  attribute :first_name, :type => String
90
81
  attribute :last_name, :type => String
91
- attribute :email1, :type => String, :default => ''
92
- attribute :email2, :type => String, :default => ''
93
- attribute :email3, :type => String, :default => ''
94
- attribute :email4, :type => String, :default => ''
95
- attribute :phone1, :type => String, :default => ''
96
- attribute :phone2, :type => String, :default => ''
97
- attribute :phone3, :type => String, :default => ''
98
- attribute :phone4, :type => String, :default => ''
99
- attribute :address, :type => String, :default => ''
100
- attribute :address2, :type => String, :default => ''
101
- attribute :date_of_birth, :type => Date
102
- attribute :started, :type => Date
103
- attribute :joining_in_years, :type => Integer
104
- attribute :parents, :type => String, :default => ''
105
- attribute :notes, :type => String, :default => ''
106
- attribute :medical, :type => String, :default => ''
107
- attribute :religion, :type => String, :default => ''
108
- attribute :school, :type => String, :default => ''
109
- attribute :ethnicity, :type => String, :default => ''
110
- attribute :subs, :type => String, :default => ''
111
- attribute :custom1, :type => String, :default => ''
112
- attribute :custom2, :type => String, :default => ''
113
- attribute :custom3, :type => String, :default => ''
114
- attribute :custom4, :type => String, :default => ''
115
- attribute :custom5, :type => String, :default => ''
116
- attribute :custom6, :type => String, :default => ''
117
- attribute :custom7, :type => String, :default => ''
118
- attribute :custom8, :type => String, :default => ''
119
- attribute :custom9, :type => String, :default => ''
120
82
  attribute :grouping_id, :type => Integer
83
+ attribute :grouping_label, :type => String
121
84
  attribute :grouping_leader, :type => Integer
122
- attribute :joined, :type => Date
85
+ attribute :grouping_leader_label, :type => String
123
86
  attribute :age, :type => String
124
- attribute :joined_years, :type => Integer
125
- attribute :has_photo, :type => Boolean, :default => false
87
+ attribute :date_of_birth, :type => Date
88
+ attribute :started_section, :type => Date
89
+ attribute :finished_section, :type => Date
90
+ attribute :joined_movement, :type => Date
91
+ attribute :gender, :type => Object
92
+ attribute :custom, :type => Object, :default => DirtyHashy.new
93
+ attribute :custom_labels, :type => Object, :default => DirtyHashy.new
94
+ attribute :contact, :type => Object
95
+ attribute :primary_contact, :type => Object
96
+ attribute :secondary_contact, :type => Object
97
+ attribute :emergency_contact, :type => Object
98
+ attribute :doctor, :type => Object
126
99
 
127
100
  if ActiveModel::VERSION::MAJOR < 4
128
- attr_accessible :id, :section_id, :type, :first_name, :last_name, :email1, :email2, :email3, :email4,
129
- :phone1, :phone2, :phone3, :phone4, :address, :address2, :date_of_birth, :started,
130
- :joining_in_years, :parents, :notes, :medical, :religion, :school, :ethnicity, :subs,
131
- :custom1, :custom2, :custom3, :custom4, :custom5, :custom6, :custom7, :custom8, :custom9,
132
- :grouping_id, :grouping_leader, :joined, :age, :joined_years,
133
- :has_photo
101
+ attr_accessible :id, :section_id, :first_name, :last_name, :grouping_id, :grouping_leader,
102
+ :date_of_birth, :started_section, :finished_section, :joined_movement, :age,
103
+ :grouping_label, :grouping_leader_label, :gender, :custom, :custom_labels,
104
+ :contact, :primary_contact, :secondary_contact, :emergency_contact, :doctor
134
105
  end
135
106
 
136
107
  validates_numericality_of :id, :only_integer=>true, :greater_than=>0, :unless => Proc.new { |r| r.id.nil? }
137
108
  validates_numericality_of :section_id, :only_integer=>true, :greater_than=>0
138
109
  validates_numericality_of :grouping_id, :only_integer=>true, :greater_than_or_equal_to=>-2
139
- validates_numericality_of :grouping_leader, :only_integer=>true, :greater_than_or_equal_to=>0, :less_than_or_equal_to=>2
140
- validates_numericality_of :joined_years, :only_integer=>true, :greater_than_or_equal_to=>-1, :allow_nil=>true
141
- validates_numericality_of :joining_in_years, :only_integer=>true, :greater_than_or_equal_to=>-1, :allow_nil=>true
110
+ validates_numericality_of :grouping_leader, :only_integer=>true, :greater_than_or_equal_to=>0, :less_than_or_equal_to=>14, :allow_nil => true
142
111
  validates_presence_of :first_name
143
112
  validates_presence_of :last_name
113
+ validates_presence_of :grouping_label, :allow_blank => true
114
+ validates_presence_of :grouping_leader_label, :allow_blank => true
115
+ validates_presence_of :custom, :allow_blank => true
116
+ validates_presence_of :custom_labels, :allow_blank => true
144
117
  validates_presence_of :date_of_birth
145
- validates_presence_of :started
146
- validates_presence_of :joined
147
- validates_format_of :age, :with => /\A[0-9]{2}\/(0[0-9]|1[012])\Z/, :message => 'age is not in the correct format (yy/mm)', :allow_blank => true
148
- validates_inclusion_of :has_photo, :in => [true, false]
118
+ validates_presence_of :started_section
119
+ validates_presence_of :finished_section, :allow_nil=>true
120
+ validates_presence_of :joined_movement
121
+ validates_format_of :age, :with => /\A[0-9]{1,3} \/ (?:0?[0-9]|1[012])\Z/, :message => 'age is not in the correct format (yy / mm)', :allow_blank => true
122
+ validates_inclusion_of :gender, :in => [:male, :female, :other, :unspecified], :allow_nil => true
123
+ validates :contact, :validity=>true
124
+ validates :primary_contact, :validity=>true
125
+ validates :secondary_contact, :validity=>true
126
+ validates :emergency_contact, :validity=>true
127
+ validates :doctor, :validity=>true
149
128
 
150
129
 
151
130
  # Get members for a section
@@ -156,65 +135,148 @@ module Osm
156
135
  # @return [Array<Osm::Member>]
157
136
  def self.get_for_section(api, section, term=nil, options={})
158
137
  require_ability_to(api, :read, :member, section, options)
159
- section = Osm::Section.get(api, section) if section.is_a?(Fixnum)
160
- term = -1 if section.waiting?
161
- term_id = term.nil? ? Osm::Term.get_current_term_for_section(api, section).id : term.to_i
162
- cache_key = ['members', section.id, term_id]
138
+ if term.nil?
139
+ section = Osm::Section.get(api, section) if section.is_a?(Fixnum)
140
+ term = section.waiting? ? -1 : Osm::Term.get_current_term_for_section(api, section)
141
+ end
142
+ cache_key = ['members', section.to_i, term.to_i]
163
143
 
164
144
  if !options[:no_cache] && cache_exist?(api, cache_key)
165
145
  return cache_read(api, cache_key)
166
146
  end
167
147
 
168
- data = api.perform_query("users.php?action=getUserDetails&sectionid=#{section.id}&termid=#{term_id}")
169
- summary_data = api.perform_query("ext/members/contact/?action=getListOfMembers&sort=patrolid&sectionid=#{section.id}&termid=#{term_id}&section=#{section.type}") || {}
148
+ result = Array.new
170
149
 
171
- summary_data = summary_data['items'] || []
172
- summary_data = Hash[summary_data.map { |i| [i['scoutid'].to_i, i] }]
150
+ api_response = api.perform_query('ext/members/contact/grid/?action=getMembers', {
151
+ 'section_id' => section.to_i,
152
+ 'term_id' => term.to_i,
153
+ })
154
+
155
+ data = api_response['data'].is_a?(Hash) ? api_response['data'].values : []
156
+ structure = (api_response['meta'] || {})['structure'] || []
157
+ structure = Hash[ structure.map{ |i| [i['group_id'].to_i, i ] } ] # Make a hash of identifier to group data hash
158
+
159
+ custom_labels = {}
160
+ structure.each do |gid, group|
161
+ columns = group['columns']
162
+ columns.map!{ |c| [c['column_id'].to_i, c['label']] }
163
+ columns.select!{ |a| (gid == GID_CUSTOM) || (a[0] > CORE_FIELD_IDS_FINISH_AT) }
164
+ labels = DirtyHashy[ columns ]
165
+ custom_labels[gid.to_i] = labels
166
+ end
167
+
168
+ data.each do |item|
169
+ item_data = Hash[ item['custom_data'].map{ |k,v| [k.to_i, v] } ]
170
+ member_contact = Hash[ item_data[GID_MEMBER_CONTACT].map{ |k,v| [k.to_i, v] }.select{ |i| i[0] < CUSTOM_FIELD_IDS_START_AT } ]
171
+ primary_contact = Hash[ item_data[GID_PRIMARY_CONTACT].map{ |k,v| [k.to_i, v] }.select{ |i| i[0] < CUSTOM_FIELD_IDS_START_AT } ]
172
+ secondary_contact = Hash[ item_data[GID_SECONDARY_CONTACT].map{ |k,v| [k.to_i, v] }.select{ |i| i[0] < CUSTOM_FIELD_IDS_START_AT } ]
173
+ emergency_contact = Hash[ item_data[GID_EMERGENCY_CONTACT].map{ |k,v| [k.to_i, v] }.select{ |i| i[0] < CUSTOM_FIELD_IDS_START_AT } ]
174
+ doctor_contact = Hash[ item_data[GID_DOCTOR_CONTACT].map{ |k,v| [k.to_i, v] }.select{ |i| i[0] < CUSTOM_FIELD_IDS_START_AT } ]
175
+ floating_data = Hash[ item_data[GID_FLOATING].map{ |k,v| [k.to_i, v] }.select{ |i| i[0] < CUSTOM_FIELD_IDS_START_AT } ]
173
176
 
174
- result = Array.new
175
- data['items'].each do |item|
176
- id = Osm::to_i_or_nil(item['scoutid'])
177
177
  result.push Osm::Member.new(
178
- :section_id => section.id,
179
- :id => id,
180
- :type => item['type'],
181
- :first_name => item['firstname'],
182
- :last_name => item['lastname'],
183
- :email1 => item['email1'],
184
- :email2 => item['email2'],
185
- :email3 => item['email3'],
186
- :email4 => item['email4'],
187
- :phone1 => item['phone1'],
188
- :phone2 => item['phone2'],
189
- :phone3 => item['phone3'],
190
- :phone4 => item['phone4'],
191
- :address => item['address'],
192
- :address2 => item['address2'],
193
- :date_of_birth => Osm::parse_date(item['dob'], :ignore_epoch => true),
194
- :started => Osm::parse_date(item['started']),
195
- :joining_in_years => item['joining_in_yrs'].to_i,
196
- :parents => item['parents'],
197
- :notes => item['notes'],
198
- :medical => item['medical'],
199
- :religion => item['religion'],
200
- :school => item['school'],
201
- :ethnicity => item['ethnicity'],
202
- :subs => item['subs'],
203
- :custom1 => item['custom1'],
204
- :custom2 => item['custom2'],
205
- :custom3 => item['custom3'],
206
- :custom4 => item['custom4'],
207
- :custom5 => item['custom5'],
208
- :custom6 => item['custom6'],
209
- :custom7 => item['custom7'],
210
- :custom8 => item['custom8'],
211
- :custom9 => item['custom9'],
212
- :grouping_id => Osm::to_i_or_nil(item['patrolid']),
213
- :grouping_leader => Osm::to_i_or_nil(item['patrolleader']),
214
- :joined => Osm::parse_date(item['joined']),
215
- :age => item['age'].gsub(' ', ''),
216
- :joined_years => item['yrs'].to_i,
217
- :has_photo => summary_data[id]['pic']
178
+ :id => Osm::to_i_or_nil(item['member_id']),
179
+ :section_id => Osm::to_i_or_nil(item['section_id']),
180
+ :first_name => item['first_name'],
181
+ :last_name => item['last_name'],
182
+ :grouping_id => Osm::to_i_or_nil(item['patrol_id']),
183
+ :grouping_label => item['patrol'],
184
+ :grouping_leader => item['patrol_role_level'],
185
+ :grouping_leader_label => item['patrol_role_level_label'],
186
+ :age => item['age'],
187
+ :date_of_birth => Osm::parse_date(item['date_of_birth'], :ignore_epoch => true),
188
+ :started_section => Osm::parse_date(item['joined']),
189
+ :finished_section => Osm::parse_date(item['end_date']),
190
+ :joined_movement => Osm::parse_date(item['started']),
191
+ :gender => {'male'=>:male, 'female'=>:female, 'other'=>:other, 'unspecified'=>:unspecified}[floating_data[CID_GENDER].downcase],
192
+ :contact => MemberContact.new(
193
+ first_name: item['first_name'],
194
+ last_name: item['last_name'],
195
+ address_1: member_contact[CID_ADDRESS_1],
196
+ address_2: member_contact[CID_ADDRESS_2],
197
+ address_3: member_contact[CID_ADDRESS_3],
198
+ address_4: member_contact[CID_ADDRESS_4],
199
+ postcode: member_contact[CID_POSTCODE],
200
+ phone_1: member_contact[CID_PHONE_1],
201
+ phone_2: member_contact[CID_PHONE_2],
202
+ email_1: member_contact[CID_EMAIL_1],
203
+ email_2: member_contact[CID_EMAIL_2],
204
+ receive_phone_1: member_contact[CID_RECIEVE_PHONE_1],
205
+ receive_phone_2: member_contact[CID_RECIEVE_PHONE_2],
206
+ receive_email_1: member_contact[CID_RECIEVE_EMAIL_1],
207
+ receive_email_2: member_contact[CID_RECIEVE_EMAIL_2],
208
+ custom: DirtyHashy[ item_data[GID_MEMBER_CONTACT].map{ |k,v| [k.to_i, v] }.select{ |i| i[0] > CORE_FIELD_IDS_FINISH_AT } ],
209
+ custom_labels: custom_labels[GID_MEMBER_CONTACT],
210
+ ),
211
+ :primary_contact => PrimaryContact.new(
212
+ first_name: primary_contact[CID_FIRST_NAME],
213
+ last_name: primary_contact[CID_LAST_NAME],
214
+ address_1: primary_contact[CID_ADDRESS_1],
215
+ address_2: primary_contact[CID_ADDRESS_2],
216
+ address_3: primary_contact[CID_ADDRESS_3],
217
+ address_4: primary_contact[CID_ADDRESS_4],
218
+ postcode: primary_contact[CID_POSTCODE],
219
+ phone_1: primary_contact[CID_PHONE_1],
220
+ phone_2: primary_contact[CID_PHONE_2],
221
+ email_1: primary_contact[CID_EMAIL_1],
222
+ email_2: primary_contact[CID_EMAIL_2],
223
+ receive_phone_1: primary_contact[CID_RECIEVE_PHONE_1],
224
+ receive_phone_2: primary_contact[CID_RECIEVE_PHONE_2],
225
+ receive_email_1: primary_contact[CID_RECIEVE_EMAIL_1],
226
+ receive_email_2: primary_contact[CID_RECIEVE_EMAIL_2],
227
+ custom: DirtyHashy[ item_data[GID_PRIMARY_CONTACT].map{ |k,v| [k.to_i, v] }.select{ |i| i[0] > CORE_FIELD_IDS_FINISH_AT } ],
228
+ custom_labels: custom_labels[GID_PRIMARY_CONTACT],
229
+ ),
230
+ :secondary_contact => PrimaryContact.new(
231
+ first_name: secondary_contact[CID_FIRST_NAME],
232
+ last_name: secondary_contact[CID_LAST_NAME],
233
+ address_1: secondary_contact[CID_ADDRESS_1],
234
+ address_2: secondary_contact[CID_ADDRESS_2],
235
+ address_3: secondary_contact[CID_ADDRESS_3],
236
+ address_4: secondary_contact[CID_ADDRESS_4],
237
+ postcode: secondary_contact[CID_POSTCODE],
238
+ phone_1: secondary_contact[CID_PHONE_1],
239
+ phone_2: secondary_contact[CID_PHONE_2],
240
+ email_1: secondary_contact[CID_EMAIL_1],
241
+ email_2: secondary_contact[CID_EMAIL_2],
242
+ receive_phone_1: secondary_contact[CID_RECIEVE_PHONE_1],
243
+ receive_phone_2: secondary_contact[CID_RECIEVE_PHONE_2],
244
+ receive_email_1: secondary_contact[CID_RECIEVE_EMAIL_1],
245
+ receive_email_2: secondary_contact[CID_RECIEVE_EMAIL_2],
246
+ custom: DirtyHashy[ item_data[GID_SECONDARY_CONTACT].map{ |k,v| [k.to_i, v] }.select{ |i| i[0] > CORE_FIELD_IDS_FINISH_AT } ],
247
+ custom_labels: custom_labels[GID_SECONDARY_CONTACT],
248
+ ),
249
+ :emergency_contact => EmergencyContact.new(
250
+ first_name: emergency_contact[CID_FIRST_NAME],
251
+ last_name: emergency_contact[CID_LAST_NAME],
252
+ address_1: emergency_contact[CID_ADDRESS_1],
253
+ address_2: emergency_contact[CID_ADDRESS_2],
254
+ address_3: emergency_contact[CID_ADDRESS_3],
255
+ address_4: emergency_contact[CID_ADDRESS_4],
256
+ postcode: emergency_contact[CID_POSTCODE],
257
+ phone_1: emergency_contact[CID_PHONE_1],
258
+ phone_2: emergency_contact[CID_PHONE_2],
259
+ email_1: emergency_contact[CID_EMAIL_1],
260
+ email_2: emergency_contact[CID_EMAIL_2],
261
+ custom: DirtyHashy[ item_data[GID_EMERGENCY_CONTACT].map{ |k,v| [k.to_i, v] }.select{ |i| i[0] > CORE_FIELD_IDS_FINISH_AT } ],
262
+ custom_labels: custom_labels[GID_EMERGENCY_CONTACT],
263
+ ),
264
+ :doctor => DoctorContact.new(
265
+ first_name: doctor_contact[CID_FIRST_NAME],
266
+ last_name: doctor_contact[CID_LAST_NAME],
267
+ surgery: doctor_contact[CID_SURGERY],
268
+ address_1: doctor_contact[CID_ADDRESS_1],
269
+ address_2: doctor_contact[CID_ADDRESS_2],
270
+ address_3: doctor_contact[CID_ADDRESS_3],
271
+ address_4: doctor_contact[CID_ADDRESS_4],
272
+ postcode: doctor_contact[CID_POSTCODE],
273
+ phone_1: doctor_contact[CID_PHONE_1],
274
+ phone_2: doctor_contact[CID_PHONE_2],
275
+ custom: DirtyHashy[ item_data[GID_DOCTOR_CONTACT].map{ |k,v| [k.to_i, v] }.select{ |i| i[0] > CORE_FIELD_IDS_FINISH_AT } ],
276
+ custom_labels: custom_labels[GID_DOCTOR_CONTACT],
277
+ ),
278
+ custom: DirtyHashy[ item_data[GID_CUSTOM].map{ |k,v| [k.to_i, v] } ],
279
+ custom_labels: custom_labels[GID_CUSTOM],
218
280
  )
219
281
  end
220
282
 
@@ -392,6 +454,57 @@ module Osm
392
454
  grouping_id > 0
393
455
  end
394
456
 
457
+ # Check if the member is male
458
+ # @return [Boolean]
459
+ def male?
460
+ gender == :male
461
+ end
462
+
463
+ # Check if the member is male
464
+ # @return [Boolean]
465
+ def female?
466
+ gender == :female
467
+ end
468
+
469
+ # Check if this is a current member of the section they were retrieved for
470
+ # @param [Date] date The date to check membership status for
471
+ # @return [Boolean]
472
+ def current?(date=Date.today)
473
+ if finished_section.nil?
474
+ return (started_section <= date)
475
+ else
476
+ return (started_section <= date) && (finished_section >= date)
477
+ end
478
+ end
479
+
480
+ # @!method all_emails
481
+ # Get an array of all email addresses from all contacts for the member (not emergency or doctor)
482
+ # @return [Array<String>]
483
+ # @!method all_emails_with_name
484
+ # Get an array of all email addresses from all contacts for the member in a format which includes the contact's name (not emergency or doctor)
485
+ # @return [Array<String>]
486
+ # @!method enabled_emails
487
+ # Get an array of all email addresses from all contacts for the member (not emergency or doctor)
488
+ # @return [Array<String>]
489
+ # @!method enabled_emails_with_name
490
+ # Get an array of all email addresses from all contacts for the member in a format which includes the contact's name (not emergency or doctor)
491
+ # @return [Array<String>]
492
+ # @!method all_phones
493
+ # Get an array of all phone numbers from all contacts for the member (not emergency or doctor)
494
+ # @return [Array<String>]
495
+ # @!method enabled_phones
496
+ # Get an array of enabled phone numbers from all contacts for the member (not emergency or doctor)
497
+ # @return [Array<String>]
498
+ [:all_emails, :all_emails_with_name, :enabled_emails, :enabled_emails_with_name, :all_phones, :enabled_phones].each do |meth|
499
+ define_method meth do
500
+ items = []
501
+ [:contact, :primary_contact, :secondary_contact].each do |cont|
502
+ items.push *send(cont).send(meth)
503
+ end
504
+ return items
505
+ end
506
+ end
507
+
395
508
  # Get the Key to use in My.SCOUT links for this member
396
509
  # @param [Osm::Api] api The api to use to make the request
397
510
  # @return [String] the key
@@ -413,15 +526,14 @@ module Osm
413
526
 
414
527
  # Get the member's photo
415
528
  # @param [Osm::Api] api The api to use to make the request
416
- # @param [Boolean] black_and_white Whether you want the photo in blank and white
529
+ # @param [Boolean] black_and_white Whether you want the photo in blank and white (defaults to false unless the member is not active)
417
530
  # @!macro options_get
418
- # @raise [Osm:Error] if the member has no photo or doesn't exist in OSM
531
+ # @raise [Osm:Error] if the member doesn't exist in OSM
419
532
  # @return the photo of the member
420
- def get_photo(api, black_and_white=false, options={})
533
+ def get_photo(api, black_and_white=!current?, options={})
421
534
  raise Osm::ObjectIsInvalid, 'member is invalid' unless valid?
422
535
  require_ability_to(api, :read, :member, section_id)
423
536
  raise Osm::Error, 'the member does not already exist in OSM' if id.nil?
424
- raise Osm::Error, "the member doesn't have a photo in OSM" unless has_photo
425
537
 
426
538
  cache_key = ['member_photo', self.id, black_and_white]
427
539
 
@@ -455,7 +567,7 @@ module Osm
455
567
  return link
456
568
  end
457
569
 
458
- # Compare Activity based on section_id, grouping_id, grouping_leader (descending), last_name then first_name
570
+ # Compare member based on section_id, grouping_id, grouping_leader (descending), last_name then first_name
459
571
  def <=>(another)
460
572
  result = self.section_id <=> another.try(:section_id)
461
573
  result = self.grouping_id <=> another.try(:grouping_id) if result == 0
@@ -465,6 +577,226 @@ module Osm
465
577
  return result
466
578
  end
467
579
 
580
+
581
+ module EmailableContact
582
+ # Get an array of all emails for the contact
583
+ # @return [Array<String>]
584
+ def all_emails
585
+ [email_1, email_2].select{ |e| !e.blank? }
586
+ end
587
+
588
+ # Get an array of enabled emails for the contact
589
+ # @return [Array<String>]
590
+ def enabled_emails
591
+ emails = []
592
+ emails.push email_1 if receive_email_1
593
+ emails.push email_2 if receive_email_2
594
+ emails.select{ |e| !e.blank? }
595
+ end
596
+
597
+ # Get an array of all emails for the contact in a format which includes their name
598
+ # @return [Array<String>]
599
+ def all_emails_with_name
600
+ [email_1, email_2].select{ |e| !e.blank? }.map{ |e| "\"#{name}\" <#{e}>" }
601
+ end
602
+
603
+ # Get an array of enabled emails for the contact in a format which includes their name
604
+ # @return [Array<String>]
605
+ def enabled_emails_with_name
606
+ emails = []
607
+ emails.push email_1 if receive_email_1
608
+ emails.push email_2 if receive_email_2
609
+ emails.select{ |e| !e.blank? }.map{ |e| "\"#{name}\" <#{e}>" }
610
+ end
611
+ end
612
+
613
+ module PhoneableContact
614
+ # Get an array of enabled phone numbers for the contact
615
+ def enabled_phones
616
+ phones = []
617
+ phones.push phone_1.gsub(/[^\d\+]/, '') if receive_phone_1
618
+ phones.push phone_2.gsub(/[^\d\+]/, '') if receive_phone_2
619
+ phones.select{ |n| !n.blank? }.map{ |n| n }
620
+ end
621
+ end
622
+
623
+
624
+ class Contact < Osm::Model
625
+ # @!attribute [rw] first_name
626
+ # @return [String] the contact's first name
627
+ # @!attribute [rw] last_name
628
+ # @return [String] the contact's last name
629
+ # @!attribute [rw] address_1
630
+ # @return [String] the 1st line of the address
631
+ # @!attribute [rw] address_2
632
+ # @return [String] the 2nd line of the address
633
+ # @!attribute [rw] address_3
634
+ # @return [String] the 3rd line of the address
635
+ # @!attribute [rw] address_4
636
+ # @return [String] the 4th line of the address
637
+ # @!attribute [rw] postcode
638
+ # @return [String] the postcode of the address
639
+ # @!attribute [rw] phone_1
640
+ # @return [String] the primary phone number
641
+ # @!attribute [rw] phone_2
642
+ # @return [String] the secondary phone number
643
+ # @!attribute [rw] custom
644
+ # @return [DirtyHashy] the custom data (key is OSM's variable name, value is the data)
645
+ # @!attribute [rw] custom_labels
646
+ # @return [DirtyHashy] the labels for the custom data (key is OSM's variable name, value is the label)
647
+
648
+ attribute :first_name, :type => String
649
+ attribute :last_name, :type => String
650
+ attribute :address_1, :type => String
651
+ attribute :address_2, :type => String
652
+ attribute :address_3, :type => String
653
+ attribute :address_4, :type => String
654
+ attribute :postcode, :type => String
655
+ attribute :phone_1, :type => String
656
+ attribute :phone_2, :type => String
657
+ attribute :custom, :type => Object, :default => DirtyHashy.new
658
+ attribute :custom_labels, :type => Object, :default => DirtyHashy.new
659
+
660
+ if ActiveModel::VERSION::MAJOR < 4
661
+ attr_accessible :first_name, :last_name, :address_1, :address_2, :address_3, :address_4,
662
+ :postcode, :phone_1, :phone_2, :custom, :custom_labels
663
+ end
664
+
665
+ # @!method initialize
666
+ # Initialize a new Contact
667
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
668
+
669
+ # Get the full name
670
+ # @param [String] seperator What to split the scout's first name and last name with
671
+ # @return [String] this scout's full name seperated by the optional seperator
672
+ def name(seperator=' ')
673
+ return "#{first_name}#{seperator.to_s}#{last_name}"
674
+ end
675
+
676
+ # Get an array of all phone numbers for the contact
677
+ # @return [Array<String>]
678
+ def all_phones
679
+ [phone_1, phone_2].select{ |n| !n.blank? }.map{ |n| n.gsub(/[^\d\+]/, '') }
680
+ end
681
+ end
682
+
683
+
684
+ class MemberContact < Osm::Member::Contact
685
+ include EmailableContact
686
+ include PhoneableContact
687
+ # @!attribute [rw] email_1
688
+ # @return [String] the primary email address for the member
689
+ # @!attribute [rw] email_2
690
+ # @return [String] the secondary email address for the member
691
+ # @!attribute [rw] receive_email_1
692
+ # @return [Boolean] whether the member should receive emails from leaders on their primary email address
693
+ # @!attribute [rw] receive_email_2
694
+ # @return [Boolean] whether the member should receive emails from leaders on their secondary email address
695
+ # @!attribute [rw] receive_phone_1
696
+ # @return [Boolean] whether the member should receive SMSs from leaders on their primary phone number
697
+ # @!attribute [rw] receive_phone_2
698
+ # @return [Boolean] whether the member should receive SMSs from leaders on their secondary phone number
699
+
700
+ attribute :email_1, :type => String
701
+ attribute :receive_email_1, :type => Boolean, :default => false
702
+ attribute :email_2, :type => String
703
+ attribute :receive_email_2, :type => Boolean, :default => false
704
+ attribute :receive_phone_1, :type => Boolean, :default => false
705
+ attribute :receive_phone_2, :type => Boolean, :default => false
706
+
707
+ if ActiveModel::VERSION::MAJOR < 4
708
+ attr_accessible :email_1, :email_2, :receive_email_1, :receive_email_2,
709
+ :receive_phone_1, :receive_phone_2
710
+ end
711
+
712
+ validates_inclusion_of :receive_email_1, :in => [true, false]
713
+ validates_inclusion_of :receive_email_2, :in => [true, false]
714
+ validates_inclusion_of :receive_phone_1, :in => [true, false]
715
+ validates_inclusion_of :receive_phone_2, :in => [true, false]
716
+ end
717
+
718
+
719
+ class PrimaryContact < Osm::Member::Contact
720
+ include EmailableContact
721
+ include PhoneableContact
722
+ # @!attribute [rw] email_1
723
+ # @return [String] the primary email address for the contact
724
+ # @!attribute [rw] email_2
725
+ # @return [String] the secondary email address for the contact
726
+ # @!attribute [rw] receive_email_1
727
+ # @return [Boolean] whether the contact should receive emails from leaders on their primary email address
728
+ # @!attribute [rw] receive_email_2
729
+ # @return [Boolean] whether the contact should receive emails from leaders on their secondary email address
730
+ # @!attribute [rw] receive_phone_1
731
+ # @return [Boolean] whether the contact should receive SMSs from leaders on their primary phone number
732
+ # @!attribute [rw] receive_phone_2
733
+ # @return [Boolean] whether the contact should receive SMSs from leaders on their secondary phone number
734
+
735
+ attribute :email_1, :type => String
736
+ attribute :receive_email_1, :type => Boolean, :default => false
737
+ attribute :email_2, :type => String
738
+ attribute :receive_email_2, :type => Boolean, :default => false
739
+ attribute :receive_phone_1, :type => Boolean, :default => false
740
+ attribute :receive_phone_2, :type => Boolean, :default => false
741
+
742
+ if ActiveModel::VERSION::MAJOR < 4
743
+ attr_accessible :email_1, :email_2,
744
+ :receive_email_1, :receive_email_2, :receive_phone_1, :receive_phone_2
745
+ end
746
+
747
+ validates_inclusion_of :receive_email_1, :in => [true, false]
748
+ validates_inclusion_of :receive_email_2, :in => [true, false]
749
+ validates_inclusion_of :receive_phone_1, :in => [true, false]
750
+ validates_inclusion_of :receive_phone_2, :in => [true, false]
751
+ end # class PrimaryContact
752
+
753
+
754
+ class EmergencyContact < Osm::Member::Contact
755
+ # @!attribute [rw] email_1
756
+ # @return [String] the primary email address for the contact
757
+ # @!attribute [rw] email_2
758
+ # @return [String] the secondary email address for the contact
759
+
760
+ attribute :email_1, :type => String
761
+ attribute :email_2, :type => String
762
+
763
+ if ActiveModel::VERSION::MAJOR < 4
764
+ attr_accessible :email_1, :email_2
765
+ end
766
+
767
+ # Get the full name
768
+ # @param [String] seperator What to split the scout's first name and last name with
769
+ # @return [String] this scout's full name seperated by the optional seperator
770
+ def name(seperator=' ')
771
+ return "#{first_name}#{seperator.to_s}#{last_name}"
772
+ end
773
+ end # class EmergencyContact
774
+
775
+
776
+ class DoctorContact < Osm::Member::Contact
777
+ # @!attribute [rw] first_name
778
+ # @return [String] the contact's first name
779
+ # @!attribute [rw] last_name
780
+ # @return [String] the contact's last name
781
+ # @!attribute [rw] surgery
782
+ # @return [String] the surgery name
783
+
784
+ attribute :first_name, :type => String
785
+ attribute :last_name, :type => String
786
+ attribute :surgery, :type => String
787
+
788
+ if ActiveModel::VERSION::MAJOR < 4
789
+ attr_accessible :first_name, :last_name, :surgery
790
+ end
791
+
792
+ # Get the full name
793
+ # @param [String] seperator What to split the scout's first name and last name with
794
+ # @return [String] this scout's full name seperated by the optional seperator
795
+ def name(seperator=' ')
796
+ return "Dr. #{first_name}#{seperator.to_s}#{last_name}"
797
+ end
798
+ end # class DoctorContact
799
+
468
800
  end # Class Member
469
801
 
470
802
  end # Module