stbaldricks 1.5.1.pre

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.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/lib/stbaldricks.rb +2 -0
  4. data/lib/stbaldricks/api.rb +7 -0
  5. data/lib/stbaldricks/api_connector.rb +47 -0
  6. data/lib/stbaldricks/client.rb +7 -0
  7. data/lib/stbaldricks/configuration.rb +47 -0
  8. data/lib/stbaldricks/default_logger.rb +24 -0
  9. data/lib/stbaldricks/endpoints/communicate.rb +67 -0
  10. data/lib/stbaldricks/endpoints/config.rb +18 -0
  11. data/lib/stbaldricks/endpoints/contact.rb +12 -0
  12. data/lib/stbaldricks/endpoints/event.rb +39 -0
  13. data/lib/stbaldricks/endpoints/event_application.rb +32 -0
  14. data/lib/stbaldricks/endpoints/fund.rb +24 -0
  15. data/lib/stbaldricks/endpoints/kid.rb +24 -0
  16. data/lib/stbaldricks/endpoints/kid_honor.rb +17 -0
  17. data/lib/stbaldricks/endpoints/lib/entity.rb +247 -0
  18. data/lib/stbaldricks/endpoints/message.rb +27 -0
  19. data/lib/stbaldricks/endpoints/newsletter_recipient.rb +41 -0
  20. data/lib/stbaldricks/endpoints/participant.rb +66 -0
  21. data/lib/stbaldricks/endpoints/photo.rb +51 -0
  22. data/lib/stbaldricks/endpoints/recurring_gift.rb +18 -0
  23. data/lib/stbaldricks/endpoints/search.rb +63 -0
  24. data/lib/stbaldricks/endpoints/shave_schedule.rb +28 -0
  25. data/lib/stbaldricks/endpoints/user.rb +106 -0
  26. data/lib/stbaldricks/entities/batch.rb +29 -0
  27. data/lib/stbaldricks/entities/campaign.rb +37 -0
  28. data/lib/stbaldricks/entities/communicate.rb +20 -0
  29. data/lib/stbaldricks/entities/config.rb +41 -0
  30. data/lib/stbaldricks/entities/contact.rb +40 -0
  31. data/lib/stbaldricks/entities/contact_group.rb +29 -0
  32. data/lib/stbaldricks/entities/document_library.rb +19 -0
  33. data/lib/stbaldricks/entities/document_library_category.rb +14 -0
  34. data/lib/stbaldricks/entities/donation.rb +372 -0
  35. data/lib/stbaldricks/entities/donation/donor_type.rb +13 -0
  36. data/lib/stbaldricks/entities/donation/how_created.rb +22 -0
  37. data/lib/stbaldricks/entities/donation/payment_type.rb +17 -0
  38. data/lib/stbaldricks/entities/donation/promo_code.rb +22 -0
  39. data/lib/stbaldricks/entities/donation/recipient_type.rb +17 -0
  40. data/lib/stbaldricks/entities/donation/special_donation_type.rb +78 -0
  41. data/lib/stbaldricks/entities/donation/status.rb +12 -0
  42. data/lib/stbaldricks/entities/donation/submitter_type.rb +13 -0
  43. data/lib/stbaldricks/entities/donor.rb +15 -0
  44. data/lib/stbaldricks/entities/event.rb +173 -0
  45. data/lib/stbaldricks/entities/event/fund_relationship_type.rb +15 -0
  46. data/lib/stbaldricks/entities/event/how_created.rb +14 -0
  47. data/lib/stbaldricks/entities/event/promo_code.rb +11 -0
  48. data/lib/stbaldricks/entities/event/status.rb +13 -0
  49. data/lib/stbaldricks/entities/event/sub_type.rb +16 -0
  50. data/lib/stbaldricks/entities/event/type.rb +12 -0
  51. data/lib/stbaldricks/entities/event_application.rb +135 -0
  52. data/lib/stbaldricks/entities/event_application/model_type.rb +15 -0
  53. data/lib/stbaldricks/entities/event_application/status.rb +23 -0
  54. data/lib/stbaldricks/entities/event_donation_summary.rb +18 -0
  55. data/lib/stbaldricks/entities/event_participant_summary.rb +17 -0
  56. data/lib/stbaldricks/entities/event_supporter.rb +24 -0
  57. data/lib/stbaldricks/entities/fund.rb +98 -0
  58. data/lib/stbaldricks/entities/fundraiser.rb +124 -0
  59. data/lib/stbaldricks/entities/international_partner.rb +58 -0
  60. data/lib/stbaldricks/entities/kid.rb +190 -0
  61. data/lib/stbaldricks/entities/kid_honor.rb +123 -0
  62. data/lib/stbaldricks/entities/lib/address.rb +25 -0
  63. data/lib/stbaldricks/entities/lib/base.rb +505 -0
  64. data/lib/stbaldricks/entities/lib/cacheable.rb +43 -0
  65. data/lib/stbaldricks/entities/lib/default_cacheable.rb +17 -0
  66. data/lib/stbaldricks/entities/lib/email_address.rb +15 -0
  67. data/lib/stbaldricks/entities/lib/error.rb +38 -0
  68. data/lib/stbaldricks/entities/lib/fundraising_page.rb +20 -0
  69. data/lib/stbaldricks/entities/lib/geo_location.rb +9 -0
  70. data/lib/stbaldricks/entities/lib/location.rb +27 -0
  71. data/lib/stbaldricks/entities/lib/name.rb +17 -0
  72. data/lib/stbaldricks/entities/lib/not_implemented_object.rb +32 -0
  73. data/lib/stbaldricks/entities/lib/opt_out_settings.rb +21 -0
  74. data/lib/stbaldricks/entities/lib/payment.rb +86 -0
  75. data/lib/stbaldricks/entities/lib/permissions.rb +33 -0
  76. data/lib/stbaldricks/entities/lib/phone.rb +29 -0
  77. data/lib/stbaldricks/entities/lib/third_party_media.rb +20 -0
  78. data/lib/stbaldricks/entities/lib/top_level.rb +99 -0
  79. data/lib/stbaldricks/entities/lib/venue.rb +18 -0
  80. data/lib/stbaldricks/entities/memorial.rb +61 -0
  81. data/lib/stbaldricks/entities/message.rb +65 -0
  82. data/lib/stbaldricks/entities/newsletter_recipient.rb +17 -0
  83. data/lib/stbaldricks/entities/organization.rb +90 -0
  84. data/lib/stbaldricks/entities/page.rb +49 -0
  85. data/lib/stbaldricks/entities/participant.rb +216 -0
  86. data/lib/stbaldricks/entities/person.rb +403 -0
  87. data/lib/stbaldricks/entities/photo.rb +260 -0
  88. data/lib/stbaldricks/entities/recurring_gift.rb +215 -0
  89. data/lib/stbaldricks/entities/response.rb +37 -0
  90. data/lib/stbaldricks/entities/search.rb +223 -0
  91. data/lib/stbaldricks/entities/section.rb +15 -0
  92. data/lib/stbaldricks/entities/shave_schedule.rb +69 -0
  93. data/lib/stbaldricks/entities/summary.rb +32 -0
  94. data/lib/stbaldricks/entities/team.rb +95 -0
  95. data/lib/stbaldricks/entities/user.rb +76 -0
  96. data/lib/stbaldricks/enums/country.rb +223 -0
  97. data/lib/stbaldricks/enums/feature.rb +29 -0
  98. data/lib/stbaldricks/enums/league_status.rb +16 -0
  99. data/lib/stbaldricks/enums/model_type.rb +39 -0
  100. data/lib/stbaldricks/enums/permission.rb +223 -0
  101. data/lib/stbaldricks/enums/permission_model_type.rb +21 -0
  102. data/lib/stbaldricks/enums/province.rb +39 -0
  103. data/lib/stbaldricks/enums/state.rb +137 -0
  104. data/lib/stbaldricks/enums/top_level_category_type.rb +25 -0
  105. data/lib/stbaldricks/errors.rb +18 -0
  106. data/lib/stbaldricks/log.rb +16 -0
  107. data/lib/stbaldricks/patches/array.rb +6 -0
  108. data/lib/stbaldricks/patches/boolean.rb +25 -0
  109. data/lib/stbaldricks/patches/class.rb +13 -0
  110. data/lib/stbaldricks/patches/float.rb +9 -0
  111. data/lib/stbaldricks/patches/hash.rb +16 -0
  112. data/lib/stbaldricks/patches/integer.rb +9 -0
  113. data/lib/stbaldricks/patches/nil.rb +25 -0
  114. data/lib/stbaldricks/patches/string.rb +54 -0
  115. data/lib/stbaldricks/request.rb +146 -0
  116. data/lib/stbaldricks/version.rb +5 -0
  117. metadata +215 -0
@@ -0,0 +1,58 @@
1
+ require 'stbaldricks/entities/lib/address'
2
+ require 'stbaldricks/entities/lib/name'
3
+ require 'stbaldricks/entities/lib/fundraising_page'
4
+ require 'stbaldricks/entities/person'
5
+ require 'stbaldricks/entities/lib/top_level'
6
+
7
+ module SBF
8
+ module Client
9
+ class InternationalPartner < SBF::Client::TopLevelEntity
10
+ actions DEFAULT_CRUD_ACTIONS
11
+ blacklist_action :create
12
+ blacklist_action :update
13
+ blacklist_action :delete
14
+
15
+ disallow_instantiation
16
+
17
+ module Status
18
+ ACTIVE = 'active'
19
+ PENDING = 'pending'
20
+ end
21
+
22
+ class WebPage < SBF::Client::BaseEntity
23
+ attr_accessor :url
24
+ attr_accessor :custom_url_segment
25
+ end
26
+
27
+ class Link < SBF::Client::BaseEntity
28
+ attr_accessor :url
29
+ attr_accessor :text
30
+ end
31
+ end
32
+
33
+ class PartialInternationalPartner < SBF::Client::InternationalPartner
34
+ attr_accessor :id
35
+ end
36
+
37
+ class FullInternationalPartner < SBF::Client::InternationalPartner
38
+ attr_accessor :id
39
+ attr_accessor :status
40
+ attr_accessor :year
41
+ attr_accessor :country
42
+ attr_accessor :country_name
43
+ attr_accessor :name
44
+ attr_accessor :message
45
+ attr_accessor :description
46
+ entity_attr_accessor :web_page, 'SBF::Client::InternationalPartner::WebPage'
47
+ entity_attr_accessor :link, 'SBF::Client::InternationalPartner::Link'
48
+ attr_accessor :donate_url
49
+ attr_accessor :base_photo_url
50
+
51
+ attr_accessor :created_at
52
+
53
+ def active?
54
+ status == SBF::Client::InternationalPartner::Status::ACTIVE
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,190 @@
1
+ require 'stbaldricks/endpoints/kid'
2
+ require 'stbaldricks/entities/lib/address'
3
+ require 'stbaldricks/entities/lib/name'
4
+ require 'stbaldricks/entities/lib/fundraising_page'
5
+ require 'stbaldricks/entities/person'
6
+ require 'stbaldricks/entities/lib/base'
7
+ require 'stbaldricks/entities/lib/default_cacheable'
8
+
9
+ module SBF
10
+ module Client
11
+ class Diagnosis < SBF::Client::BaseEntity
12
+ attr_accessor :date
13
+ attr_accessor :cancer_type
14
+ attr_accessor :other
15
+
16
+ module CancerType
17
+ ACUTE_LYMPHOBLASTIC_LEUKEMIA = 'acute_lymphoblastic_leukemia'
18
+ ACUTE_MYELOID_LEUKEMIA = 'acute_myeloid_leukemia'
19
+ BRAIN_OR_SPINAL_CORD_TUMOR = 'brain_or_spinal_cord_tumor'
20
+ KIDNEY_SARCOMA = 'kidney_sarcoma'
21
+ EWING_SARCOMA = 'ewing_sarcoma'
22
+ GERM_CELL_TUMOR = 'germ_cell_tumor'
23
+ HEPATOBLASTOMA = 'hepatoblastoma'
24
+ HODGKIN_LYMPHOMA = 'hodgkin_lymphoma'
25
+ LYMPHOMA = 'lymphoma'
26
+ NEUROBLASTOMA = 'neuroblastoma'
27
+ NON_HODGKIN_LYMPHOMA = 'non_hodgkin_lymphoma'
28
+ OSTEOSARCOMA = 'osteosarcoma'
29
+ RETINOBLASTOMA = 'retinoblastoma'
30
+ RHABDOMYOSARCOMA = 'rhabdomyosarcoma'
31
+ WILMS_OR_OTHER_KIDNEY_TUMOR = 'wilms_or_other_kidney_tumor'
32
+ ADULT_WITH_CHILDHOOD_CANCER = 'adult_with_childhood_cancer'
33
+ OTHER_CHILDHOOD_CANCER = 'other_childhood_cancer'
34
+ end
35
+ end
36
+
37
+ class Guardian < SBF::Client::BaseEntity
38
+ module RelationshipType
39
+ SELF_ADULT = 'adult'
40
+ SELF_MINOR = 'minor'
41
+ MOTHER = 'mother'
42
+ FATHER = 'father'
43
+ SISTER = 'sister'
44
+ BROTHER = 'brother'
45
+ AUNT = 'aunt'
46
+ UNCLE = 'uncle'
47
+ GRANDMOTHER = 'grandmother'
48
+ GRANDFATHER = 'grandfather'
49
+ STEP_MOTHER = 'stepmother'
50
+ STEP_FATHER = 'stepfather'
51
+ FRIEND = 'friend'
52
+ COUSIN = 'cousin'
53
+ end
54
+
55
+ attr_accessor :name
56
+ attr_accessor :relationship
57
+ entity_attr_accessor :name_pieces, 'SBF::Client::NamePieces'
58
+ entity_attr_accessor :email_address, 'SBF::Client::EmailAddress'
59
+ entity_attr_accessor :phone, 'SBF::Client::Phone'
60
+ end
61
+
62
+ class TreatmentStatus < SBF::Client::BaseEntity
63
+ attr_accessor :state
64
+ attr_accessor :other
65
+
66
+ module SurvivorState
67
+ IN_TREATMENT = 'in_treatment'
68
+ IN_MAINTENANCE = 'in_maintenance'
69
+ IN_REMISSION = 'in_remission'
70
+ NED = 'no_evidence_of_disease'
71
+ CANCER_FREE = 'cancer_free'
72
+ OTHER_LIVING = 'other_living'
73
+ end
74
+
75
+ module DeceasedState
76
+ ANGEL = 'angel'
77
+ DECEASED = 'deceased'
78
+ PASSED_AWAY = 'passed_away'
79
+ OTHER_DECEASED = 'other_deceased'
80
+ end
81
+ end
82
+
83
+ class KidURL < SBF::Client::BaseEntity
84
+ attr_accessor :url
85
+ end
86
+
87
+ class Photos < SBF::Client::BaseEntity
88
+ attr_reader :avatar, :default
89
+ end
90
+
91
+ class KidInstitution < SBF::Client::BaseEntity
92
+ attr_reader :id
93
+ attr_reader :display_name
94
+ attr_reader :city
95
+ attr_reader :state
96
+ attr_reader :zip_code
97
+ attr_reader :country
98
+ attr_reader :created_at
99
+ attr_reader :modified_at
100
+ end
101
+
102
+ class Kid < SBF::Client::TopLevelEntity
103
+ include Entities::DefaultCacheable
104
+ endpoint SBF::Client::KidEndpoint
105
+ actions DEFAULT_CRUD_ACTIONS
106
+ action :find_random
107
+ blacklist_action :create
108
+ blacklist_action :delete
109
+
110
+ disallow_instantiation
111
+
112
+ module Status
113
+ ACTIVE = 'active'
114
+ PENDING = 'pending'
115
+ SUSPENDED = 'suspended'
116
+ end
117
+
118
+ class Relationship < SBF::Client::BaseEntity
119
+ module Type
120
+ SELF_ADULT = 'adult'
121
+ SELF_MINOR = 'minor'
122
+ MOTHER = 'mother'
123
+ FATHER = 'father'
124
+ SISTER = 'sister'
125
+ BROTHER = 'brother'
126
+ AUNT = 'aunt'
127
+ UNCLE = 'uncle'
128
+ GRANDMOTHER = 'grandmother'
129
+ GRANDFATHER = 'grandfather'
130
+ STEP_MOTHER = 'stepmother'
131
+ STEP_FATHER = 'stepfather'
132
+ FRIEND = 'friend'
133
+ COUSIN = 'cousin'
134
+ end
135
+
136
+ module GuardianType
137
+ LEGAL_GUARDIAN = 'legal_guardian'
138
+ NONCUSTODIAL_GUARDIAN = 'non_custodial_guardian'
139
+ OTHER = 'other'
140
+ end
141
+
142
+ class Permissions < SBF::Client::BaseEntity
143
+ attr_accessor :is_editor
144
+ end
145
+
146
+ attr_accessor :kid_id
147
+ entity_attr_accessor :person, 'SBF::Client::FullPerson', 'SBF::Client::PartialPerson'
148
+ attr_accessor :type
149
+ attr_accessor :guardian_type
150
+ attr_accessor :is_submitter
151
+ entity_attr_accessor :permissions, 'SBF::Client::Kid::Relationship::Permissions'
152
+ end
153
+ end
154
+
155
+ class PartialKid < SBF::Client::Kid
156
+ attr_accessor :id
157
+ end
158
+
159
+ class FullKid < SBF::Client::Kid
160
+ attr_accessor :id
161
+ attr_accessor :status
162
+ attr_accessor :gender
163
+ attr_accessor :birthday
164
+ attr_accessor :ethnicity
165
+ attr_accessor :date_of_passing
166
+ attr_accessor :display_name
167
+ attr_accessor :display_name_override
168
+ attr_accessor :name
169
+ entity_attr_accessor :name_pieces, 'SBF::Client::NamePieces'
170
+ entity_attr_accessor :address, 'SBF::Client::Address'
171
+ entity_attr_accessor :diagnosis, 'SBF::Client::Diagnosis'
172
+ entity_attr_accessor :treatment_status, 'SBF::Client::TreatmentStatus'
173
+ attr_accessor :web_message
174
+ entity_attr_accessor :photos, 'SBF::Client::Photos'
175
+ entity_attr_accessor :web_page, 'SBF::Client::WebPage'
176
+ entity_attr_accessor :guardian, 'SBF::Client::Guardian'
177
+ entity_collection_attr_accessor :relationships, 'SBF::Client::Kid::Relationship'
178
+ entity_collection_attr_accessor :urls, 'SBF::Client::KidURL'
179
+ entity_collection_attr_reader :institutions, 'SBF::Client::KidInstitution'
180
+
181
+ attr_accessor :created_at
182
+ attr_accessor :modified_at
183
+ attr_accessor :modified_by
184
+
185
+ def active?
186
+ status == SBF::Client::Kid::Status::ACTIVE
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,123 @@
1
+ require 'stbaldricks/endpoints/kid_honor'
2
+ require 'stbaldricks/entities/kid'
3
+ require 'stbaldricks/entities/event'
4
+ require 'stbaldricks/entities/team'
5
+ require 'stbaldricks/entities/participant'
6
+ require 'stbaldricks/entities/fundraiser'
7
+ require 'stbaldricks/entities/lib/top_level'
8
+
9
+ module SBF
10
+ module Client
11
+ class KidHonor < SBF::Client::TopLevelEntity
12
+ endpoint SBF::Client::KidHonorEndpoint
13
+ actions DEFAULT_CRUD_ACTIONS
14
+ blacklist_action :get
15
+ blacklist_action :update
16
+
17
+ attr_accessor :honored_on
18
+ entity_attr_accessor :kid, 'SBF::Client::KidHonor::FullKid', 'SBF::Client::KidHonor::PartialKid'
19
+ multitype_attr_accessor(
20
+ :honorer,
21
+ [
22
+ [
23
+ ->(v) { v[:model_type] == SBF::Client::KidHonor::HonorerType::PARTICIPANT },
24
+ 'SBF::Client::KidHonor::FullParticipant',
25
+ 'SBF::Client::KidHonor::PartialParticipant'
26
+ ],
27
+ [
28
+ ->(v) { v[:model_type] == SBF::Client::KidHonor::HonorerType::FUNDRAISER },
29
+ 'SBF::Client::KidHonor::FullFundraiser',
30
+ 'SBF::Client::KidHonor::PartialFundraiser'
31
+ ],
32
+ [
33
+ ->(v) { v[:model_type] == SBF::Client::KidHonor::HonorerType::TEAM },
34
+ 'SBF::Client::KidHonor::FullTeam',
35
+ 'SBF::Client::KidHonor::PartialTeam'
36
+ ],
37
+ [
38
+ ->(v) { v[:model_type] == SBF::Client::KidHonor::HonorerType::EVENT },
39
+ 'SBF::Client::KidHonor::FullEvent',
40
+ 'SBF::Client::KidHonor::PartialEvent'
41
+ ]
42
+ ]
43
+ )
44
+
45
+ module HonorerType
46
+ EVENT = 'event'
47
+ TEAM = 'team'
48
+ PARTICIPANT = 'participant'
49
+ FUNDRAISER = 'fundraiser'
50
+ end
51
+
52
+ class PartialKid < SBF::Client::PartialKid
53
+ end
54
+
55
+ class FullKid < SBF::Client::FullKid
56
+ end
57
+
58
+ class FullParticipant < SBF::Client::FullParticipant
59
+ attr_reader :model_type
60
+
61
+ def model_type
62
+ SBF::Client::KidHonor::HonorerType::PARTICIPANT
63
+ end
64
+ end
65
+
66
+ class PartialParticipant < SBF::Client::PartialParticipant
67
+ attr_reader :model_type
68
+
69
+ def model_type
70
+ SBF::Client::KidHonor::HonorerType::PARTICIPANT
71
+ end
72
+ end
73
+
74
+ class FullFundraiser < SBF::Client::FullFundraiser
75
+ attr_reader :model_type
76
+
77
+ def model_type
78
+ SBF::Client::KidHonor::HonorerType::FUNDRAISER
79
+ end
80
+ end
81
+
82
+ class PartialFundraiser < SBF::Client::PartialFundraiser
83
+ attr_reader :model_type
84
+
85
+ def model_type
86
+ SBF::Client::KidHonor::HonorerType::FUNDRAISER
87
+ end
88
+ end
89
+
90
+ class FullTeam < SBF::Client::FullTeam
91
+ attr_reader :model_type
92
+
93
+ def model_type
94
+ SBF::Client::KidHonor::HonorerType::TEAM
95
+ end
96
+ end
97
+
98
+ class PartialTeam < SBF::Client::PartialTeam
99
+ attr_reader :model_type
100
+
101
+ def model_type
102
+ SBF::Client::KidHonor::HonorerType::TEAM
103
+ end
104
+ end
105
+
106
+ class FullEvent < SBF::Client::FullEvent
107
+ attr_reader :model_type
108
+
109
+ def model_type
110
+ SBF::Client::KidHonor::HonorerType::EVENT
111
+ end
112
+ end
113
+
114
+ class PartialEvent < SBF::Client::PartialEvent
115
+ attr_reader :model_type
116
+
117
+ def model_type
118
+ SBF::Client::KidHonor::HonorerType::EVENT
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,25 @@
1
+ require 'stbaldricks/entities/lib/base'
2
+
3
+ module SBF
4
+ module Client
5
+ class Address < SBF::Client::BaseEntity
6
+ module Type
7
+ HOME = 'home'
8
+ BUSINESS = 'business'
9
+ end
10
+
11
+ attr_accessor :type
12
+ attr_accessor :business_name
13
+ attr_accessor :address1, :address2, :city, :state, :state_name, :zip, :country, :country_name
14
+ attr_accessor :latitude, :longitude
15
+
16
+ def city_state_zip
17
+ csz = ''
18
+ csz += @city + ', ' unless @city.nil?
19
+ csz += @state + ' ' unless @state.nil?
20
+ csz += @zip unless @zip.nil?
21
+ csz.strip
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,505 @@
1
+ # Require all monkey patches
2
+ patch_dir = File.expand_path('../../../patches', __FILE__)
3
+ Dir["#{patch_dir}/*.rb"].each { |file| require file }
4
+
5
+ require 'set'
6
+ require 'stbaldricks/client'
7
+ require 'stbaldricks/entities/lib/error'
8
+ require 'active_model'
9
+ require 'securerandom'
10
+
11
+ module SBF
12
+ module Client
13
+ class BaseEntity
14
+ extend ActiveModel::Naming
15
+ extend ActiveModel::Translation
16
+ include ActiveModel::Conversion
17
+
18
+ attr_reader :errors
19
+
20
+ ELSE = ->(_) { true }
21
+
22
+ # Class level instance variable to keep track of defined attributes
23
+ @attributes = nil
24
+ @optional_attributes = nil
25
+ @entity_attributes = nil
26
+ @not_provided_attributes = nil
27
+ @disallow_instantiation = nil
28
+
29
+ def initialize(data = {})
30
+ # If disallow instantiation has been set, raise an error if someone is trying to call initilaize
31
+ raise SBF::Client::Error, 'Initialize is not valid on a base object. Use the full or partial version' unless self.class.allow_instantiation?
32
+
33
+ super()
34
+
35
+ @errors = ActiveModel::Errors.new(self)
36
+ self.attributes = data
37
+ end
38
+
39
+ # Overridden from ActiveModel::Naming to remove namespace and Full/Partial
40
+ def model_name
41
+ @_model_name ||= ActiveModel::Name.new(self, SBF::Client, self.class.name.gsub(/::(Full|Partial)/, '::'))
42
+ end
43
+
44
+ def persisted?
45
+ true
46
+ end
47
+
48
+ def attributes=(data)
49
+ data ||= {}
50
+ data.each do |key, value|
51
+ # Use the setter if one is available, otherwise set the variable directly
52
+ setter = "#{key}=".to_sym
53
+ if respond_to?(setter, true)
54
+ send(setter, value)
55
+
56
+ else
57
+ instance_variable_set("@#{key}".to_sym, value)
58
+
59
+ end
60
+ end
61
+
62
+ # For each attribute that may be optional, call mark_attribute_not_provided
63
+ # if the data set does not contain a key with the optional attribute's name
64
+ self.class.optional_attributes.each { |name| mark_attribute_not_provided(name) unless data.key?(name) }
65
+ end
66
+
67
+ def add_errors(error)
68
+ errors.clear
69
+ count = error.fields.length
70
+ error.fields.each do |field|
71
+ field = field.gsub('_id', '') # Hacky, since API returns DB field that errored instead of View Field
72
+ errors.add(field, error.details) if count > 1
73
+ errors.add(:base, "#{field}: #{error.details}") if count == 1
74
+ end
75
+ errors.add(:base, "#{error.type}: #{error.details}") if count == 0
76
+ end
77
+
78
+ def mark_attribute_not_provided(name)
79
+ return if not_provided_attributes.include?(name)
80
+
81
+ # Put the method in the list of not_provided methods
82
+ not_provided_attributes << name
83
+
84
+ # Back up the original version of the setter method if it doesn't already exist
85
+ setter = setter_method_name(name)
86
+ setter_backup = setter_backup_method_name(name)
87
+ singleton_class.send(:alias_method, setter_backup, setter) unless respond_to?(setter_backup, true)
88
+
89
+ # Back up the original version of the getter method if it doesn't already exist
90
+ getter = getter_method_name(name)
91
+ getter_backup = getter_backup_method_name(name)
92
+ singleton_class.send(:alias_method, getter_backup, getter) unless respond_to?(getter_backup, true)
93
+
94
+ # If we set the value in a not_provided method, that means we now have requested the data and we
95
+ # should replace the original getter/setter and set the value using the original method
96
+ define_singleton_method(setter) { |v|
97
+ unmark_attribute_not_provided(name)
98
+ super(v)
99
+ }
100
+
101
+ # If we try to get the value in a not_provided method, we should raise an error
102
+ define_singleton_method(getter) {
103
+ raise(
104
+ SBF::Client::DataNotProvidedError,
105
+ "Data not provided for the #{name} attribute. This is usually due to the attribute not being requested in the 'with' declaration"
106
+ )
107
+ }
108
+ end
109
+ private :mark_attribute_not_provided
110
+
111
+ def unmark_attribute_not_provided(name)
112
+ return unless not_provided_attributes.include?(name)
113
+
114
+ # Remove the method from the list of not_provided methods
115
+ not_provided_attributes.delete(name)
116
+
117
+ # Replace the original version of the setter method using the backup
118
+ setter = setter_method_name(name)
119
+ setter_backup = setter_backup_method_name(name)
120
+ singleton_class.send(:alias_method, setter, setter_backup)
121
+
122
+ # Replace the original version of the getter method using the backup
123
+ getter = getter_method_name(name)
124
+ getter_backup = getter_backup_method_name(name)
125
+ singleton_class.send(:alias_method, getter, getter_backup)
126
+ end
127
+ private :unmark_attribute_not_provided
128
+
129
+ def setter_method_name(name)
130
+ "#{name}=".to_sym
131
+ end
132
+ private :setter_method_name
133
+
134
+ def getter_method_name(name)
135
+ name.to_sym
136
+ end
137
+ private :getter_method_name
138
+
139
+ def setter_backup_method_name(name)
140
+ "_#{name}=".to_sym
141
+ end
142
+ private :setter_backup_method_name
143
+
144
+ def getter_backup_method_name(name)
145
+ "_#{name}".to_sym
146
+ end
147
+ private :getter_backup_method_name
148
+
149
+ # So this method is meant to ensure that you don't do SBF::Client::Participant.new and are forced, instead,
150
+ # to be explicit about calling either SBF::Client::FullParticipant.new or SBF::Client::PartialParticipant.new
151
+ def self.disallow_instantiation
152
+ @disallow_instantiation = true
153
+ end
154
+ private_class_method :disallow_instantiation
155
+
156
+ # Class method which retrieves the
157
+ def self.allow_instantiation?
158
+ !@disallow_instantiation
159
+ end
160
+
161
+ # Class method to retrieve the not_provided_attributes attribute
162
+ def not_provided_attributes
163
+ @not_provided_attributes ||= Set.new
164
+ end
165
+
166
+ ###########################################################################################
167
+ # Override for the ruby built-in attribute accessors. Creates a list of attributes that can be
168
+ # accessed at will and adds some helper methods.
169
+ def self.attr_reader(*vars)
170
+ attributes.merge(vars)
171
+ super(*vars)
172
+ add_boolean_methods(vars)
173
+ end
174
+
175
+ def self.attr_writer(*vars)
176
+ attributes.merge(vars)
177
+ super(*vars)
178
+ add_boolean_methods(vars, true)
179
+ end
180
+
181
+ def self.attr_accessor(*vars)
182
+ attributes.merge(vars)
183
+ super(*vars)
184
+ add_boolean_methods(vars, true)
185
+ end
186
+ ###########################################################################################
187
+
188
+ ###########################################################################################
189
+ # Multitype Attribute accessors. These methods take an array of type to class mappings.
190
+ # Expected form is:
191
+ # [[<lamda method which evals to true if this type should be used>, <full class>, <partial class>], [...]]
192
+ def self.multitype_attr_reader(attribute, class_mappings, optional = false)
193
+ entity_attributes << attribute
194
+ optional_attributes << attribute if optional
195
+ attr_reader(attribute)
196
+ add_multitype_setter_method(attribute, class_mappings, true)
197
+ end
198
+
199
+ def self.multitype_attr_writer(attribute, class_mappings, optional = false)
200
+ entity_attributes << attribute
201
+ optional_attributes << attribute if optional
202
+ attr_writer(attribute)
203
+ add_multitype_setter_method(attribute, class_mappings)
204
+ end
205
+
206
+ def self.multitype_attr_accessor(attribute, class_mappings, optional = false)
207
+ entity_attributes << attribute
208
+ optional_attributes << attribute if optional
209
+ attr_accessor(attribute)
210
+ add_multitype_setter_method(attribute, class_mappings)
211
+ end
212
+ ###########################################################################################
213
+
214
+ ###########################################################################################
215
+ # Entity attr accessors are simpler to define/easier to read but they really just create a type mapping
216
+ # for the given inputs and then function the same as the multitype attribute accessors do.
217
+ def self.entity_attr_reader(attribute, full_class, partial_class = nil, optional = false)
218
+ mapping_for_single_class = [[ELSE, full_class, partial_class]]
219
+ multitype_attr_reader(attribute, mapping_for_single_class, optional)
220
+ end
221
+
222
+ def self.entity_attr_writer(attribute, full_class, partial_class = nil, optional = false)
223
+ mapping_for_single_class = [[ELSE, full_class, partial_class]]
224
+ multitype_attr_writer(attribute, mapping_for_single_class, optional)
225
+ end
226
+
227
+ def self.entity_attr_accessor(attribute, full_class, partial_class = nil, optional = false)
228
+ mapping_for_single_class = [[ELSE, full_class, partial_class]]
229
+ multitype_attr_accessor(attribute, mapping_for_single_class, optional)
230
+ end
231
+ ###########################################################################################
232
+
233
+ ###########################################################################################
234
+ # Multitype Collection accessors. These methods take an array of type to class mappings.
235
+ # Expected form is:
236
+ # [[<lamda method which evals to true if this type should be used>, <full class>, <partial class>], [...]]
237
+ def self.multitype_collection_attr_reader(attribute, class_mappings, optional = false)
238
+ collection_attributes << attribute
239
+ optional_attributes << attribute if optional
240
+ attr_reader(attribute)
241
+ add_multitype_collection_setter_method(attribute, class_mappings, true)
242
+ end
243
+
244
+ def self.multitype_collection_attr_writer(attribute, class_mappings, optional = false)
245
+ collection_attributes << attribute
246
+ optional_attributes << attribute if optional
247
+ attr_writer(attribute)
248
+ add_multitype_collection_setter_method(attribute, class_mappings)
249
+ end
250
+
251
+ def self.multitype_collection_attr_accessor(attribute, class_mappings, optional = false)
252
+ collection_attributes << attribute
253
+ optional_attributes << attribute if optional
254
+ attr_accessor(attribute)
255
+ add_multitype_collection_setter_method(attribute, class_mappings)
256
+ end
257
+ ###########################################################################################
258
+
259
+ ###########################################################################################
260
+ # Entity attr accessors are simpler to define/easier to read but they really just create a type mapping
261
+ # for the given inputs and then function the same as the multitype attribute accessors do.
262
+ def self.entity_collection_attr_reader(attribute, full_class, partial_class = nil, optional = false)
263
+ mapping_for_single_class = [[ELSE, full_class, partial_class]]
264
+ multitype_collection_attr_reader(attribute, mapping_for_single_class, optional)
265
+ end
266
+
267
+ def self.entity_collection_attr_writer(attribute, full_class, partial_class = nil, optional = false)
268
+ mapping_for_single_class = [[ELSE, full_class, partial_class]]
269
+ multitype_collection_attr_writer(attribute, mapping_for_single_class, optional)
270
+ end
271
+
272
+ def self.entity_collection_attr_accessor(attribute, full_class, partial_class = nil, optional = false)
273
+ mapping_for_single_class = [[ELSE, full_class, partial_class]]
274
+ multitype_collection_attr_accessor(attribute, mapping_for_single_class, optional)
275
+ end
276
+ ###########################################################################################
277
+
278
+ def self.attributes
279
+ @attributes ||= Set.new
280
+ end
281
+
282
+ def self.optional_attributes
283
+ @optional_attributes ||= Set.new
284
+ end
285
+
286
+ def self.entity_attributes
287
+ @entity_attributes ||= Set.new
288
+ end
289
+
290
+ def self.collection_attributes
291
+ @collection_attributes ||= Set.new
292
+ end
293
+
294
+ def self.add_boolean_methods(vars, setter = false)
295
+ vars.each do |attribute|
296
+ split_attribute = attribute.to_s.split('is_')
297
+ next if split_attribute.length <= 1
298
+ define_method(:"#{split_attribute.last}?") { send(attribute) }
299
+ define_method(:"#{attribute}=") { |val| instance_variable_set("@#{attribute}".to_sym, val.to_s.to_b) } if setter
300
+ end
301
+ end
302
+ private_class_method :add_boolean_methods
303
+
304
+ # Creates a private method which resolves the class type based on the class mappings and the value
305
+ # rubocop:disable MethodLength
306
+ def self.add_class_selector_method(attribute, class_mappings)
307
+ method_name = :"select_#{attribute}_class"
308
+
309
+ define_singleton_method(method_name) do |value|
310
+ parsed_class_mappings = get_mappings(attribute, class_mappings)
311
+
312
+ # No class conversion if the value is nil
313
+ return nil if value.nil?
314
+
315
+ if value.is_a?(Hash)
316
+ # Allow setting of the value using a hash
317
+ return NilClass if value.empty?
318
+
319
+ # Find the first class mapping whose check returns true
320
+ check, full_class, partial_class = parsed_class_mappings.find { |check, _, _| check.call(value) }
321
+ raise SBF::Client::Error, "unknown #{attribute} type" if check.nil?
322
+
323
+ # If a user specifies exactly the partial fields, then this should be a partial.
324
+ return partial_class if partial_class && (value.keys - partial_class.attributes.to_a).empty?
325
+
326
+ # Otherwise
327
+ return full_class if full_class
328
+
329
+ else
330
+ # No class conversion if the value is already an entity
331
+ return nil if parsed_class_mappings.any? { |_, c1, c2|
332
+ [c1, c2].compact.any? { |allowed_class|
333
+ if allowed_class.instance_of?(Class)
334
+ # Otherwise, return true if the value is an instance of the allowed class
335
+ value.is_a?(allowed_class)
336
+
337
+ elsif allowed_class.instance_of?(Module)
338
+ # If we specified a module, assume this is an enum and return true if the value
339
+ # is one of the valid constant values for the module
340
+ allowed_class.constants.any? { |const_name| allowed_class.const_get(const_name) == value }
341
+
342
+ end
343
+ }
344
+ }
345
+
346
+ # Convert to child class if given class is a parent of the same name
347
+ parsed_class_mappings.each { |_, c1, c2|
348
+ [c1, c2].compact.each { |allowed_class|
349
+ return allowed_class if allowed_class.ancestors.find { |a| a == value.class && a.demodulize == value.class.demodulize }
350
+ }
351
+ }
352
+
353
+ # Don't know how to handle whatever the user entered. Raise an error.
354
+ # (Nil and Hash are always valid values)
355
+ valid_classes = ['NilClass', Hash]
356
+ parsed_class_mappings.each do |_, class1, class2|
357
+ valid_classes << class1
358
+ valid_classes << class2 unless class2.nil?
359
+ end
360
+
361
+ raise SBF::Client::Error, "Unsupported value for #{name}.#{attribute}: #{value}. Should be one of #{valid_classes.join(', ')}"
362
+ end
363
+ end
364
+
365
+ private_class_method method_name
366
+ end
367
+ # rubocop:enable MethodLength
368
+ private_class_method :add_class_selector_method
369
+
370
+ def self.get_mappings(attribute, class_mappings)
371
+ # Parse the mappings information and save it off
372
+ mapping_var = :"@#{attribute}_class_mappings"
373
+ unless instance_variable_defined?(mapping_var)
374
+ instance_variable_set(mapping_var, parse_class_mappings(class_mappings))
375
+ end
376
+
377
+ instance_variable_get(mapping_var)
378
+ end
379
+ private_class_method :get_mappings
380
+
381
+ # Validates the input structure of the class mappings array and returns a new array with the class strings
382
+ # converted to actual classes
383
+ def self.parse_class_mappings(mappings)
384
+ raise SBF::Client::Error, "Invalid class mapping structure: #{mappings.class}" unless mappings.is_a?(Array)
385
+
386
+ mappings.map do |check, full_class, partial_class|
387
+ raise SBF::Client::Error, "Invalid class mapping check: #{check.class}" unless check.is_a?(Proc)
388
+
389
+ full_class = full_class.to_class
390
+ unless full_class.nil? || full_class.is_a?(NilClass) || full_class.ancestors.include?(BaseEntity) || full_class.instance_of?(Module)
391
+ raise SBF::Client::Error, "Invalid class mapping full class: #{full_class}"
392
+ end
393
+
394
+ partial_class = partial_class.to_class
395
+ unless partial_class.nil? || partial_class.is_a?(NilClass) || partial_class.ancestors.include?(BaseEntity)
396
+ raise SBF::Client::Error, "Invalid class mapping partial class: #{partial_class}"
397
+ end
398
+
399
+ [check, full_class, partial_class]
400
+ end
401
+ end
402
+ private_class_method :parse_class_mappings
403
+
404
+ # Convert the value to an instance of the appropriate class.
405
+ def convert_value(attribute, value)
406
+ entity_class_selector_method = :"select_#{attribute}_class"
407
+ entity_class = self.class.send(entity_class_selector_method, value)
408
+
409
+ # If the entity_class is nil, then that means don't convert anything
410
+ return value if entity_class.nil?
411
+
412
+ # If the entity_class is the NilClass, return nil
413
+ return nil if entity_class == NilClass
414
+
415
+ # If we are converting to a subclass, we need to first grab the hash of data from the parent object
416
+ value = value.to_hash if !value.is_a?(Hash) && value.respond_to?(:to_hash)
417
+
418
+ # Convert to an instance of the appropriate entity class.
419
+ entity_class.new(value)
420
+ end
421
+ private :convert_value
422
+
423
+ # Base entity setter. Allows for the property to be an array of types.
424
+ def self.add_multitype_setter_method(attribute, class_mappings, private_method = false)
425
+ add_class_selector_method(attribute, class_mappings)
426
+
427
+ method_name = :"#{attribute}="
428
+ define_method(method_name) do |value|
429
+ instance_variable_set(:"@#{attribute}", convert_value(attribute, value))
430
+ end
431
+
432
+ # Make the setter private if requested (so you can set through the constructor but not anywhere else)
433
+ private method_name if private_method
434
+ end
435
+ private_class_method :add_multitype_setter_method
436
+
437
+ # Base collection setter. Allows for the collection to be an array types of types.
438
+ def self.add_multitype_collection_setter_method(attribute, class_mappings, private_method = false)
439
+ add_class_selector_method(attribute, class_mappings)
440
+
441
+ define_method(:"#{attribute}=") do |values|
442
+ values ||= []
443
+ raise SBF::Client::Error, "#{attribute} must be an array" unless values.is_a?(Array)
444
+ converted_collection = values.map { |value| convert_value(attribute, value) }
445
+ instance_variable_set(:"@#{attribute}", converted_collection)
446
+ end
447
+
448
+ # Make the setter private if requested (so you can set through the constructor but not anywhere else)
449
+ private :"#{attribute}=" if private_method
450
+ end
451
+ private_class_method :add_multitype_collection_setter_method
452
+
453
+ # Recursively converts an entity into a Hash
454
+ def to_hash
455
+ entity_hash = {}
456
+ self.class.attributes.each { |key|
457
+ # If the method was not retrieved, do not include it in the hash
458
+ next if not_provided_attributes.include?(key)
459
+
460
+ # Get the value
461
+ value = send(key)
462
+
463
+ # If the value is an Entity, call to_hash on it (recursively)
464
+ if value.is_a?(BaseEntity)
465
+ entity_hash[key] = value.to_hash
466
+
467
+ # If the value is an Array, need to try to call to_hash on each item
468
+ elsif value.is_a?(Array)
469
+ entity_hash[key] = value.map { |element|
470
+ if element.is_a?(BaseEntity)
471
+ next element.to_hash
472
+ else
473
+ next element
474
+ end
475
+ }
476
+
477
+ # Collections should return empty array rather than nil
478
+ elsif value.nil? && self.class.collection_attributes.include?(key)
479
+ entity_hash[key] = []
480
+
481
+ # Just set the value
482
+ else
483
+ entity_hash[key] = value
484
+
485
+ end
486
+ }
487
+
488
+ # Return the hash
489
+ entity_hash
490
+ end
491
+
492
+ # Recursively convert an entity into json
493
+ def to_json(*a)
494
+ to_hash.to_json(*a)
495
+ end
496
+
497
+ def self.inherited(base)
498
+ # Add all of our attributes to the class that is inheriting us.
499
+ base.attributes.merge(attributes) unless attributes.empty?
500
+ base.optional_attributes.merge(optional_attributes) unless optional_attributes.empty?
501
+ base.entity_attributes.merge(entity_attributes) unless entity_attributes.empty?
502
+ end
503
+ end
504
+ end
505
+ end