osm 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +25 -0
- data/README.md +11 -11
- data/lib/osm.rb +1 -0
- data/lib/osm/badge.rb +357 -0
- data/lib/osm/badges.rb +149 -0
- data/lib/osm/flexi_record.rb +17 -7
- data/lib/osm/section.rb +0 -23
- data/osm.gemspec +2 -1
- data/spec/osm/badge_spec.rb +452 -0
- data/spec/osm/badges_spec.rb +113 -0
- data/spec/osm/flexi_record_spec.rb +4 -2
- data/spec/osm/section_spec.rb +0 -16
- data/version.rb +1 -1
- metadata +36 -23
- data/lib/osm/due_badges.rb +0 -102
- data/spec/osm/due_badges_spec.rb +0 -65
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,28 @@
|
|
1
|
+
## Version 0.3.0
|
2
|
+
|
3
|
+
* Removal of DueBadges
|
4
|
+
* Removal of get\_badge\_stock method from sections
|
5
|
+
* Addition of Badges model:
|
6
|
+
* With get\_due\_badges(api, section, options={}) method
|
7
|
+
* With get\_stock(api, section, options={}) method
|
8
|
+
* With update\_stock(api, section, badge\_key, stock\_level) method
|
9
|
+
* Addition of Badge models:
|
10
|
+
* CoreBadge
|
11
|
+
* ChallengeBadge
|
12
|
+
* StagedBadge
|
13
|
+
* ActivityBadge
|
14
|
+
* All:
|
15
|
+
* Inherit from Badge (do not use this class directly)
|
16
|
+
* With get\_badges\_for\_section(api, section, options={}) method
|
17
|
+
* With get\_badge\_data\_for\_section(api, section, badge, term=nil, options={}) method
|
18
|
+
* Addition of Badge::Requirements class
|
19
|
+
* Addition of Badge::Data class
|
20
|
+
* With update(api) method
|
21
|
+
* With total\_gained method
|
22
|
+
* With sections\_gained method
|
23
|
+
* With gained\_in\_sections method
|
24
|
+
* FlexiRecord::Data now updates only changed fields
|
25
|
+
|
1
26
|
## Version 0.2.2
|
2
27
|
|
3
28
|
* Add comparing and sorting (using <=>, <, <=, >, >= and between?) to each model
|
data/README.md
CHANGED
@@ -24,7 +24,7 @@ Use the [Online Scout Manager](https://www.onlinescoutmanager.co.uk) API.
|
|
24
24
|
Add to your Gemfile and run the `bundle` command to install it.
|
25
25
|
|
26
26
|
```ruby
|
27
|
-
gem 'osm'
|
27
|
+
gem 'osm', '~> 0.3.0'
|
28
28
|
```
|
29
29
|
|
30
30
|
Configure the gem during the initalization of the app (e.g. if using rails then config/initializers/osm.rb would look like):
|
@@ -41,7 +41,7 @@ ActionDispatch::Callbacks.to_prepare do
|
|
41
41
|
},
|
42
42
|
},
|
43
43
|
:cache => {
|
44
|
-
:cache
|
44
|
+
:cache => Rails.cache,
|
45
45
|
},
|
46
46
|
)
|
47
47
|
end
|
@@ -53,7 +53,7 @@ end
|
|
53
53
|
In order to use the OSM API you first need to authorize the api to be used by the user, to do this use the {Osm::Api#authorize} method to get a userid and secret.
|
54
54
|
|
55
55
|
```ruby
|
56
|
-
Osm::Api.
|
56
|
+
Osm::Api.authorize(users_email_address, users_osm_password)
|
57
57
|
```
|
58
58
|
|
59
59
|
Once you have done this you should store the userid and secret somewhere, you can then create an {Osm::Api} object to start acting as the user.
|
@@ -77,7 +77,11 @@ however it should be noted that when the OSM API adds a feature it can be diffic
|
|
77
77
|
* Activity
|
78
78
|
* API Access
|
79
79
|
* API Access for our app
|
80
|
-
*
|
80
|
+
* Badges (Silver required for activity, Bronze for core, challenge and staged):
|
81
|
+
* Which requirements each member has met
|
82
|
+
* Details for each badge
|
83
|
+
* Requirements for evening
|
84
|
+
* Badge stock levels
|
81
85
|
* Due Badges
|
82
86
|
* Evening
|
83
87
|
* Event (Silver required)
|
@@ -101,6 +105,8 @@ however it should be noted that when the OSM API adds a feature it can be diffic
|
|
101
105
|
|
102
106
|
### Update
|
103
107
|
* Activity
|
108
|
+
* Badges (Silver required for activity, Bronze for core, challenge and staged):
|
109
|
+
* Which requirements each member has met
|
104
110
|
* Evening
|
105
111
|
* Event (Silver required)
|
106
112
|
* Event Attendance (Silver required)
|
@@ -114,6 +120,7 @@ however it should be noted that when the OSM API adds a feature it can be diffic
|
|
114
120
|
### Create
|
115
121
|
* Evening
|
116
122
|
* Event (Silver required)
|
123
|
+
* Event Column (Silver required)
|
117
124
|
* Member
|
118
125
|
* Flexi Record Column
|
119
126
|
|
@@ -130,13 +137,6 @@ however it should be noted that when the OSM API adds a feature it can be diffic
|
|
130
137
|
|
131
138
|
## Parts of the OSM API currently NOT supported (may not be an exhaustive list):
|
132
139
|
|
133
|
-
* Badges (Silver required for activity, Bronze for core, challenge and staged):
|
134
|
-
* Which requirements each member has met:
|
135
|
-
* Retreive [issue 21]
|
136
|
-
* Update [issue 22]
|
137
|
-
* Retrieve details for each badge (stock, short column names etc.) [issue 20]
|
138
|
-
* Update badge stock [issue 56]
|
139
|
-
* Event - Create column (Silver required)
|
140
140
|
* SMS:
|
141
141
|
* Retrieval of delivery reports [issue 54]
|
142
142
|
* Sending a message [issue 54]
|
data/lib/osm.rb
CHANGED
data/lib/osm/badge.rb
ADDED
@@ -0,0 +1,357 @@
|
|
1
|
+
module Osm
|
2
|
+
|
3
|
+
class Badge < Osm::Model
|
4
|
+
class Requirement; end # Ensure the constant exists for the validators
|
5
|
+
|
6
|
+
# @!attribute [rw] name
|
7
|
+
# @return [String] the name of the badge
|
8
|
+
# @!attribute [rw] requirement_notes
|
9
|
+
# @return [String] a description of the badge
|
10
|
+
# @!attribute [rw] key
|
11
|
+
# @return [String] the key for the badge in OSM
|
12
|
+
# @!attribute [rw] sections_needed
|
13
|
+
# @return [Fixnum]
|
14
|
+
# @!attribute [rw] total_needed
|
15
|
+
# @return [Fixnum]
|
16
|
+
# @!attribute [rw] needed_from_section
|
17
|
+
# @return [Hash]
|
18
|
+
# @!attribute [rw] requirements
|
19
|
+
# @return [Array<Osm::Badge::Requirement>]
|
20
|
+
|
21
|
+
attribute :name, :type => String
|
22
|
+
attribute :requirement_notes, :type => String
|
23
|
+
attribute :osm_key, :type => String
|
24
|
+
attribute :sections_needed, :type => Integer
|
25
|
+
attribute :total_needed, :type => Integer
|
26
|
+
attribute :needed_from_section, :type => Object
|
27
|
+
attribute :requirements, :type => Object
|
28
|
+
|
29
|
+
attr_accessible :name, :requirement_notes, :osm_key, :sections_needed, :total_needed, :needed_from_section, :requirements
|
30
|
+
|
31
|
+
validates_numericality_of :sections_needed, :only_integer=>true, :greater_than_or_equal_to=>-1
|
32
|
+
validates_numericality_of :total_needed, :only_integer=>true, :greater_than_or_equal_to=>-1
|
33
|
+
validates_presence_of :name
|
34
|
+
validates_presence_of :requirement_notes
|
35
|
+
validates_presence_of :osm_key
|
36
|
+
validates :needed_from_section, :hash => {:key_type => String, :value_type => Fixnum}
|
37
|
+
validates :requirements, :array_of => {:item_type => Osm::Badge::Requirement, :item_valid => true}
|
38
|
+
|
39
|
+
|
40
|
+
# @!method initialize
|
41
|
+
# Initialize a new Badge
|
42
|
+
# @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
|
43
|
+
|
44
|
+
|
45
|
+
# Get badges
|
46
|
+
# @param [Osm::Api] api The api to use to make the request
|
47
|
+
# @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the due badges for
|
48
|
+
# @!macro options_get
|
49
|
+
# @return [Array<Osm::Badge>]
|
50
|
+
def self.get_badges_for_section(api, section, options={})
|
51
|
+
raise Error, 'This method must be called on one of the subclasses (CoreBadge, ChallengeBadge, StagedBadge or ActivityBadge)' if badge_type.nil?
|
52
|
+
require_ability_to(api, :read, :badge, section, options)
|
53
|
+
section = Osm::Section.get(api, section, options) unless section.is_a?(Osm::Section)
|
54
|
+
cache_key = ['badges', section.type, badge_type]
|
55
|
+
|
56
|
+
if !options[:no_cache] && Osm::Model.cache_exist?(api, cache_key)
|
57
|
+
return cache_read(api, cache_key)
|
58
|
+
end
|
59
|
+
|
60
|
+
term_id = Osm::Term.get_current_term_for_section(api, section, options).to_i
|
61
|
+
badges = []
|
62
|
+
|
63
|
+
data = api.perform_query("challenges.php?action=getInitialBadges&type=#{badge_type}§ionid=#{section.id}§ion=#{section.type}&termid=#{term_id}")
|
64
|
+
badge_order = data["badgeOrder"].to_s.split(',')
|
65
|
+
structures = data["structure"] || {}
|
66
|
+
details = data["details"] || {}
|
67
|
+
badge_order.each do |b|
|
68
|
+
structure = structures[b]
|
69
|
+
detail = details[b]
|
70
|
+
config = ActiveSupport::JSON.decode(detail['config'] || '{}')
|
71
|
+
|
72
|
+
badge = new(
|
73
|
+
:name => detail['name'],
|
74
|
+
:requirement_notes => detail['description'],
|
75
|
+
:osm_key => detail['shortname'],
|
76
|
+
:sections_needed => config['sectionsneeded'].to_i,
|
77
|
+
:total_needed => config['totalneeded'].to_i,
|
78
|
+
:needed_from_section => (config['sections'] || {}).inject({}) { |h,(k,v)| h[k] = v.to_i; h },
|
79
|
+
)
|
80
|
+
|
81
|
+
requirements = []
|
82
|
+
((structure[1] || {})['rows'] || []).each do |r|
|
83
|
+
requirements.push Osm::Badge::Requirement.new(
|
84
|
+
:badge => badge,
|
85
|
+
:name => r['name'],
|
86
|
+
:description => r['tooltip'],
|
87
|
+
:field => r['field'],
|
88
|
+
:editable => r['editable'].eql?('true'),
|
89
|
+
)
|
90
|
+
end
|
91
|
+
badge.requirements = requirements
|
92
|
+
|
93
|
+
badges.push badge
|
94
|
+
end
|
95
|
+
|
96
|
+
cache_write(api, cache_key, badges)
|
97
|
+
return badges
|
98
|
+
end
|
99
|
+
|
100
|
+
# Get a list of badge requirements met by members
|
101
|
+
# @param [Osm::Api] api The api to use to make the request
|
102
|
+
# @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the due badges for
|
103
|
+
# @param [Osm::Badge] badge The badge to get data for
|
104
|
+
# @param [Osm::Term, Fixnum, #to_i, nil] term The term (or its ID) to get the due badges for, passing nil causes the current term to be used
|
105
|
+
# @!macro options_get
|
106
|
+
# @return [Array<Osm::Badge::Data>]
|
107
|
+
def self.get_badge_data_for_section(api, section, badge, term=nil, options={})
|
108
|
+
raise Error, 'This method must be called on one of the subclasses (CoreBadge, ChallengeBadge, StagedBadge or ActivityBadge)' if badge_type.nil?
|
109
|
+
Osm::Model.require_ability_to(api, :read, :badge, section, options)
|
110
|
+
section = Osm::Section.get(api, section, options) unless section.is_a?(Osm::Section)
|
111
|
+
term_id = (term.nil? ? Osm::Term.get_current_term_for_section(api, section, options) : term).to_i
|
112
|
+
cache_key = ['badge_data', section.id, term_id, badge.osm_key]
|
113
|
+
|
114
|
+
if !options[:no_cache] && cache_exist?(api, cache_key)
|
115
|
+
return cache_read(api, cache_key)
|
116
|
+
end
|
117
|
+
|
118
|
+
datas = []
|
119
|
+
data = api.perform_query("challenges.php?termid=#{term_id}&type=#{badge_type}§ion=#{section.type}&c=#{badge.osm_key}§ionid=#{section.id}")
|
120
|
+
data['items'].each do |d|
|
121
|
+
datas.push Osm::Badge::Data.new(
|
122
|
+
:member_id => d['scoutid'],
|
123
|
+
:completed => d['completed'].eql?('1'),
|
124
|
+
:awarded_date => Osm.parse_date(d['awardeddate']),
|
125
|
+
:requirements => d.select{ |k,v| k.include?('_') },
|
126
|
+
:section_id => section.id,
|
127
|
+
:badge => badge,
|
128
|
+
)
|
129
|
+
end
|
130
|
+
|
131
|
+
cache_write(api, cache_key, datas)
|
132
|
+
return datas
|
133
|
+
end
|
134
|
+
|
135
|
+
# Compare Badge based on name then osm_key
|
136
|
+
def <=>(another)
|
137
|
+
result = self.name <=> another.try(:name)
|
138
|
+
result = self.osm_key <=> another.try(:osm_key) if result == 0
|
139
|
+
return result
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
private
|
144
|
+
def self.badge_type
|
145
|
+
nil
|
146
|
+
end
|
147
|
+
def self.subscription_required
|
148
|
+
:bronze
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
class Requirement
|
153
|
+
include ::ActiveAttr::MassAssignmentSecurity
|
154
|
+
include ::ActiveAttr::Model
|
155
|
+
|
156
|
+
# @!attribute [rw] badge
|
157
|
+
# @return [Osm::Badge] the badge the requirement belongs to
|
158
|
+
# @!attribute [rw] name
|
159
|
+
# @return [String] the name of the badge
|
160
|
+
# @!attribute [rw] description
|
161
|
+
# @return [String] a description of the badge
|
162
|
+
# @!attribute [rw] field
|
163
|
+
# @return [String] the field for the requirement (passed to OSM)
|
164
|
+
# @!attribute [rw] editable
|
165
|
+
# @return [Boolean]
|
166
|
+
|
167
|
+
attribute :badge, :type => Object
|
168
|
+
attribute :name, :type => String
|
169
|
+
attribute :description, :type => String
|
170
|
+
attribute :field, :type => String
|
171
|
+
attribute :editable, :type => Boolean
|
172
|
+
|
173
|
+
attr_accessible :name, :description, :field, :editable, :badge
|
174
|
+
|
175
|
+
validates_presence_of :name
|
176
|
+
validates_presence_of :description
|
177
|
+
validates_presence_of :field
|
178
|
+
validates_presence_of :badge
|
179
|
+
validates_inclusion_of :editable, :in => [true, false]
|
180
|
+
|
181
|
+
# @!method initialize
|
182
|
+
# Initialize a new Badge
|
183
|
+
# @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
|
184
|
+
|
185
|
+
# Compare Badge::Requirement based on badge then field
|
186
|
+
def <=>(another)
|
187
|
+
result = self.badge <=> another.try(:badge)
|
188
|
+
result = self.field <=> another.try(:field) if result == 0
|
189
|
+
return result
|
190
|
+
end
|
191
|
+
|
192
|
+
end # Class Requirement
|
193
|
+
|
194
|
+
|
195
|
+
class Data
|
196
|
+
include ::ActiveAttr::MassAssignmentSecurity
|
197
|
+
include ::ActiveAttr::Model
|
198
|
+
|
199
|
+
# @!attribute [rw] member_id
|
200
|
+
# @return [Fixnum] ID of the member this data relates to
|
201
|
+
# @!attribute [rw] completed
|
202
|
+
# @return [Boolean] whether this badge has been completed (i.e. it is due?)
|
203
|
+
# @!attribute [rw] awarded_date
|
204
|
+
# @return [Date] when the badge was awarded
|
205
|
+
# @!attribute [rw] requirements
|
206
|
+
# @return [DirtyHashy] the data for each badge requirement
|
207
|
+
# @!attribute [rw] section_id
|
208
|
+
# @return [Fixnum] the ID of the section the member belongs to
|
209
|
+
# @!attribute [rw] badge
|
210
|
+
# @return [Osm::Badge] the badge that the data belongs to
|
211
|
+
|
212
|
+
attribute :member_id, :type => Integer
|
213
|
+
attribute :completed, :type => Boolean
|
214
|
+
attribute :awarded_date, :type => Date
|
215
|
+
attribute :requirements, :type => Object, :default => DirtyHashy.new
|
216
|
+
attribute :section_id, :type => Integer
|
217
|
+
attribute :badge, :type => Object
|
218
|
+
|
219
|
+
attr_accessible :member_id, :completed, :awarded_date, :requirements, :section_id, :badge
|
220
|
+
|
221
|
+
validates_presence_of :badge
|
222
|
+
validates_inclusion_of :completed, :in => [true, false]
|
223
|
+
validates_numericality_of :member_id, :only_integer=>true, :greater_than=>0
|
224
|
+
validates_numericality_of :section_id, :only_integer=>true, :greater_than=>0
|
225
|
+
validates :requirements, :hash => {:key_type => String, :value_type => String}
|
226
|
+
|
227
|
+
# @!method initialize
|
228
|
+
# Initialize a new Badge
|
229
|
+
# @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
|
230
|
+
# Override initialize to set @orig_attributes
|
231
|
+
old_initialize = instance_method(:initialize)
|
232
|
+
define_method :initialize do |*args|
|
233
|
+
ret_val = old_initialize.bind(self).call(*args)
|
234
|
+
self.requirements = DirtyHashy.new(self.requirements)
|
235
|
+
self.requirements.clean_up!
|
236
|
+
return ret_val
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
# Get the total number of gained requirements
|
241
|
+
# @return [Fixnum] the total number of requirements considered gained
|
242
|
+
def total_gained
|
243
|
+
count = 0
|
244
|
+
requirements.each do |field, data|
|
245
|
+
next if data.blank? || data.downcase[0].eql?('x')
|
246
|
+
count += 1
|
247
|
+
end
|
248
|
+
return count
|
249
|
+
end
|
250
|
+
|
251
|
+
# Get the total number of sections gained
|
252
|
+
# @return [Hash]
|
253
|
+
def sections_gained
|
254
|
+
required = badge.needed_from_section
|
255
|
+
gained = gained_in_sections
|
256
|
+
count = 0
|
257
|
+
|
258
|
+
required.each do |section, needed|
|
259
|
+
next if gained[section] >= needed
|
260
|
+
count += 1
|
261
|
+
end
|
262
|
+
return count
|
263
|
+
end
|
264
|
+
|
265
|
+
# Get the number of requirements gained in each section
|
266
|
+
# @return [Hash]
|
267
|
+
def gained_in_sections
|
268
|
+
count = {}
|
269
|
+
requirements.each do |field, data|
|
270
|
+
field = field.split('_')[0]
|
271
|
+
count[field] ||= 0
|
272
|
+
next if data.blank? || data.downcase[0].eql?('x')
|
273
|
+
count[field] += 1
|
274
|
+
end
|
275
|
+
return count
|
276
|
+
end
|
277
|
+
|
278
|
+
# Update data in OSM
|
279
|
+
# @param [Osm::Api] api The api to use to make the request
|
280
|
+
# @return [Boolean] whether the data was updated in OSM
|
281
|
+
# @raise [Osm::ObjectIsInvalid] If the Data is invalid
|
282
|
+
def update(api)
|
283
|
+
raise Osm::ObjectIsInvalid, 'data is invalid' unless valid?
|
284
|
+
section = Osm::Section.get(api, section_id)
|
285
|
+
Osm::Model.require_ability_to(api, :write, :badge, section)
|
286
|
+
|
287
|
+
updated = true
|
288
|
+
editable_fields = badge.requirements.select{ |r| r.editable }.map{ |r| r.field}
|
289
|
+
requirements.changes.each do |field, (was,now)|
|
290
|
+
if editable_fields.include?(field)
|
291
|
+
result = api.perform_query("challenges.php?type=#{badge.class.badge_type}§ion=#{section.type}", {
|
292
|
+
'action' => 'updatesingle',
|
293
|
+
'id' => member_id,
|
294
|
+
'col' => field,
|
295
|
+
'value' => now,
|
296
|
+
'chal' => badge.osm_key,
|
297
|
+
'sectionid' => section_id,
|
298
|
+
})
|
299
|
+
updated = false unless result.is_a?(Hash) &&
|
300
|
+
(result['sid'].to_i == member_id) &&
|
301
|
+
(result[field] == now)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
if updated
|
307
|
+
requirements.clean_up!
|
308
|
+
end
|
309
|
+
|
310
|
+
return updated
|
311
|
+
end
|
312
|
+
|
313
|
+
# Compare Badge::Data based on badge, section_id then member_id
|
314
|
+
def <=>(another)
|
315
|
+
result = self.badge <=> another.try(:badge)
|
316
|
+
result = self.section_id <=> another.try(:section_id) if result == 0
|
317
|
+
result = self.member_id <=> another.try(:member_id) if result == 0
|
318
|
+
return result
|
319
|
+
end
|
320
|
+
|
321
|
+
end # Class Data
|
322
|
+
|
323
|
+
end # Class Badge
|
324
|
+
|
325
|
+
|
326
|
+
class CoreBadge < Osm::Badge
|
327
|
+
private
|
328
|
+
def self.badge_type
|
329
|
+
:core
|
330
|
+
end
|
331
|
+
end # Class CoreBadge
|
332
|
+
|
333
|
+
class ChallengeBadge < Osm::Badge
|
334
|
+
private
|
335
|
+
def self.badge_type
|
336
|
+
:challenge
|
337
|
+
end
|
338
|
+
end # Class ChallengeBadge
|
339
|
+
|
340
|
+
class StagedBadge < Osm::Badge
|
341
|
+
private
|
342
|
+
def self.badge_type
|
343
|
+
:staged
|
344
|
+
end
|
345
|
+
end # Class StagedBadge
|
346
|
+
|
347
|
+
class ActivityBadge < Osm::Badge
|
348
|
+
private
|
349
|
+
def self.badge_type
|
350
|
+
:activity
|
351
|
+
end
|
352
|
+
def self.subscription_required
|
353
|
+
:silver
|
354
|
+
end
|
355
|
+
end # Class ActivityBadge
|
356
|
+
|
357
|
+
end # Module
|