lib/osm/badge.rb in osm-1.2.15.dev.1 vs lib/osm/badge.rb in osm-1.2.15
- old
+ new
@@ -1,16 +1,19 @@
module Osm
class Badge < Osm::Model
class Requirement; end # Ensure the constant exists for the validators
+ class RequirementModule; end # Ensure the constant exists for the validators
# @!attribute [rw] name
# @return [String] the name of the badge
# @!attribute [rw] requirement_notes
# @return [String] a description of the badge
# @!attribute [rw] requirements
# @return [Array<Osm::Badge::Requirement>] the requirements of the badge
+ # @!attribute [rw] modules
+ # @return [Array<Hash>] Details of the modules which make up the badge
# @!attribute [rw] id
# @return [Fixnum] the badge's id in OSM
# @!attribute [rw] version
# @return [Fixnum] the version of the badge
# @!attribute [rw] identifier
@@ -23,10 +26,26 @@
# @return [Symbol] the sharing status of this badge (:draft, :private, :optin, :default_locked, :optin_locked)
# @!attribute [rw] user_id
# @return [Fixnum] the OSM user who created this (version of the) badge
# @!attribute [rw] levels
# @return [Array<Fixnum>, nil] the levels available, nil if it's a single level badge
+ # @!attribute [rw] min_modules_required
+ # @return [Fixnum] the minimum number of modules which must be completed to earn the badge
+ # @!attribute [rw] min_requirements_required
+ # @return [Fixnum] the minimum number of requirements which must be completed to earn the badge
+ # @!attribute [rw] add_columns_to_module
+ # @return [Fixnum, nil] the module to add columns to for nights away type badges
+ # @!attribute [rw] level_requirement
+ # @return [Fixnum, nil] the column which stores the currently earnt level of nights away type badges
+ # @!attribute [rw] requires_modules
+ # @return [Array<Array<String>>, nil] the module letters required to gain the badge, at least one from each inner Array
+ # @!attribute [rw] other_requirements_required
+ # @return [Array<Hash>] the requirements (from other badges) required to complete this badge, {id: field ID, min: the minimum numerical value of the field's data}
+ # @!attribute [rw] badges_required
+ # @return [Array<Hash>] the other badges required to complete this badge, {id: The ID of the badge, version: The version of the badge}
+ # @!attribute [rw] show_level_letters
+ # @return [Boolean] Whether to show letters not numbers for the levels of a staged badge
attribute :name, :type => String
attribute :requirement_notes, :type => String
attribute :requirements, :type => Object
attribute :id, :type => Integer
@@ -35,26 +54,40 @@
attribute :group_name, :type => String
attribute :latest, :type => Boolean
attribute :sharing, :type => Object
attribute :user_id, :type => Integer
attribute :levels, :type => Object
- attribute :completion_criteria, :type => Object
+ attribute :modules, :type => Object
+ attribute :min_modules_required, :type => Integer
+ attribute :min_requirements_required, :type => Integer
+ attribute :add_columns_to_module, :type => Integer
+ attribute :level_requirement, :type => Integer
+ attribute :requires_modules, :type => Object
+ attribute :other_requirements_required, :type => Object
+ attribute :badges_required, :type => Object
+ attribute :show_level_letters, :type => Boolean
if ActiveModel::VERSION::MAJOR < 4
- attr_accessible :name, :requirement_notes, :requirements, :id, :version, :identifier, :group_name, :latest, :sharing, :user_id, :levels, :completion_criteria
+ attr_accessible :name, :requirement_notes, :requirements, :id, :version, :identifier, :group_name, :latest, :sharing, :user_id, :levels, :modules, :min_modules_required, :min_requirements_required, :add_columns_to_module, :level_requirement, :requires_modules, :other_requirements_required, :badges_required, :show_level_letters
end
validates_presence_of :name
validates_presence_of :requirement_notes
- validates_presence_of :id
- validates_presence_of :version
+ validates_numericality_of :id, :only_integer=>true, :greater_than_or_equal_to=>1
+ validates_numericality_of :version, :only_integer=>true, :greater_than_or_equal_to=>0
validates_presence_of :identifier
validates_inclusion_of :sharing, :in => [:draft, :private, :optin, :optin_locked, :default_locked]
validates_presence_of :user_id
validates :requirements, :array_of => {:item_type => Osm::Badge::Requirement, :item_valid => true}
+ validates :modules, :array_of => {:item_type => Osm::Badge::RequirementModule, :item_valid => true}
validates_inclusion_of :latest, :in => [true, false]
validates :levels, :array_of => {:item_type => Fixnum}, :allow_nil => true
+ validates_numericality_of :min_modules_required, :only_integer=>true, :greater_than_or_equal_to=>0
+ validates_numericality_of :min_requirements_required, :only_integer=>true, :greater_than_or_equal_to=>0
+ validates_numericality_of :add_columns_to_module, :only_integer=>true, :greater_than=>0, :allow_nil=>true
+ validates_numericality_of :level_requirement, :only_integer=>true, :greater_than=>0, :allow_nil=>true
+ validates_inclusion_of :show_level_letters, :in => [true, false]
# @!method initialize
# Initialize a new Badge
# @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
@@ -105,35 +138,36 @@
:group_name => detail['group_name'],
:latest => detail['latest'].to_i.eql?(1),
:sharing => badge_sharing_map[detail['sharing']],
:user_id => Osm.to_i_or_nil(detail['userid']),
:levels => config['levelslist'],
- :completion_criteria => {
- :min_modules_required => config['numModulesRequired'].to_i,
- :fields_required => (config['columnsRequired'] || []).map{ |i| {id: Osm.to_i_or_nil(i['id']), min: i['min'].to_i} },
- :badges_required => (config['badgesRequired'] || []).map{ |i| {id: Osm.to_i_or_nil(i['id']), version: i['version'].to_i} },
- :min_requirements_completed => config['minRequirementsCompleted'].to_i,
- :requires => config['requires'],
- :add_columns_to_module => Osm.to_i_or_nil(config['addcolumns']),
- :levels_column => Osm.to_i_or_nil(config['levels_column_id']),
- :show_letters => !!config['shownumbers'],
- },
+ :min_modules_required => config['numModulesRequired'].to_i,
+ :min_requirements_required => config['minRequirementsCompleted'].to_i,
+ :add_columns_to_module => Osm.to_i_or_nil(config['addcolumns']),
+ :level_requirement => Osm.to_i_or_nil(config['levels_column_id']),
+ :requires_modules => config['requires'],
+ :other_requirements_required => (config['columnsRequired'] || []).map{ |i| {id: Osm.to_i_or_nil(i['id']), min: i['min'].to_i} },
+ :badges_required => (config['badgesRequired'] || []).map{ |i| {id: Osm.to_i_or_nil(i['id']), version: i['version'].to_i} },
+ :show_level_letters => !!config['shownumbers'],
)
+ modules = module_completion_data(api, badge, options)
+ badge.modules = modules
+ modules = Hash[*modules.map{|m| [m.letter, m]}.flatten]
+
requirements = []
((structure[1] || {})['rows'] || []).each do |r|
requirements.push Osm::Badge::Requirement.new(
:badge => badge,
:name => r['name'],
:description => r['tooltip'],
- :module_letter => r['module'],
- :field => Osm::to_i_or_nil(r['field']),
+ :mod => modules[r['module']],
+ :id => Osm::to_i_or_nil(r['field']),
:editable => r['editable'].to_s.eql?('true'),
)
end
badge.requirements = requirements
- badge.completion_criteria[:modules] = module_completion_data(api, badge, options)
badges.push badge
end
cache_write(api, cache_key, badges)
@@ -245,37 +279,37 @@
def has_levels?
!levels.nil?
end
+ def add_columns?
+ !add_columns_to_module.nil?
+ end
+
def module_map
@module_map ||= Hash[
- *completion_criteria[:modules].map{ |m|
- [m[:module_id], m[:module_letter], m[:module_letter], m[:module_id]]
+ *modules.map{ |m|
+ [m.id, m.letter, m.letter, m.id]
}.flatten
].except('z')
end
def needed_per_module
- @needed_per_module ||= Hash[*completion_criteria[:modules].map{ |m|
- [m[:module_id], m[:min_required], m[:module_letter], m[:min_required]]
+ @needed_per_module ||= Hash[*modules.map{ |m|
+ [m.id, m.min_required, m.letter, m.min_required]
}.flatten].except('z')
end
def module_letters
- @module_letters ||= completion_criteria[:modules].map{ |m| m[:module_letter] }.sort
+ @module_letters ||= modules.map{ |m| m.letter }.sort
end
def module_ids
- @module_ids ||= completion_criteria[:modules].map{ |m| m[:module_id] }.sort
+ @module_ids ||= modules.map{ |m| m.id }.sort
end
- def modules
- completion_criteria[:modules] || []
- end
-
# Compare Badge based on name then id then version (desc)
def <=>(another)
result = self.name <=> another.try(:name)
result = self.id <=> another.try(:id) if result == 0
result = another.try(:version) <=> self.version if result == 0
@@ -301,10 +335,12 @@
data = @module_completion_data[badge.id]
fetched_this_time = true
end
data = data[badge.version]
raise ArgumentError, "That badge does't exist (bad version)." if data.nil?
+
+ data.each{ |i| i.badge = badge }
return data
end
# Return a 2 dimensional hash/array (badge ID, badge version) of hashes representing the modules
def self.get_module_completion_data(api, options={})
@@ -314,29 +350,30 @@
end
osm_data = api.perform_query('ext/badges/records/?action=_getModuleDetails')
osm_data = (osm_data || {})['items'] || []
osm_data.map! do |i|
- {
- badge_id: Osm.to_i_or_nil(i['badge_id']),
- badge_version: Osm.to_i_or_nil(i['badge_version']),
- module_id: Osm.to_i_or_nil(i['module_id']),
- module_letter: i['module_letter'],
- min_required: i['num_required'].to_i,
- custom_columns: i['custom_columns'].to_i,
- completed_into_column: i['completed_into_column_id'].to_i.eql?(0) ? nil : i['completed_into_column_id'].to_i,
- numeric_into_column: i['numeric_into_column_id'].to_i.eql?(0) ? nil : i['numeric_into_column_id'].to_i,
- add_column_id_to_numeric: i['add_column_id_to_numeric'].to_i.eql?(0) ? nil : i['add_column_id_to_numeric'].to_i,
- }
+ [
+ Osm.to_i_or_nil(i['badge_id']),
+ Osm.to_i_or_nil(i['badge_version']),
+ Osm::Badge::RequirementModule.new({
+ id: Osm.to_i_or_nil(i['module_id']),
+ letter: i['module_letter'],
+ min_required: i['num_required'].to_i,
+ custom_columns: i['custom_columns'].to_i,
+ completed_into_column: i['completed_into_column_id'].to_i.eql?(0) ? nil : i['completed_into_column_id'].to_i,
+ numeric_into_column: i['numeric_into_column_id'].to_i.eql?(0) ? nil : i['numeric_into_column_id'].to_i,
+ add_column_id_to_numeric: i['add_column_id_to_numeric'].to_i.eql?(0) ? nil : i['add_column_id_to_numeric'].to_i,
+ })
+ ]
end
data = {}
- osm_data.each do |i|
- id, version = i.values_at(:badge_id, :badge_version)
+ osm_data.each do |id, version, m|
data[id] ||= []
data[id][version] ||= []
- data[id][version].push i
+ data[id][version].push m
end
cache_write(api, cache_key, data, {expires_in: 864000}) # Expire in 24 hours as this data changes really slowly
return data
end
@@ -363,54 +400,118 @@
include ActiveAttr::Model
# @!attribute [rw] badge
# @return [Osm::Badge] the badge the requirement belongs to
# @!attribute [rw] name
- # @return [String] the name of the badge
+ # @return [String] the name of the badge requirement
# @!attribute [rw] description
- # @return [String] a description of the badge
- # @!attribute [rw] field
- # @return [Fixnum] the field for the requirement (passed to OSM)
+ # @return [String] a description of the badge requirement
+ # @!attribute [rw] id
+ # @return [Fixnum] the id for the requirement (passed to OSM)
+ # @!attribute [rw] mod
+ # @return [Osm::Badge::RequirementModule] the module the requirement belongs to
# @!attribute [rw] editable
# @return [Boolean]
attribute :badge, :type => Object
attribute :name, :type => String
attribute :description, :type => String
- attribute :module_letter, :type => String
- attribute :field, :type => Integer
+ attribute :mod, :type => Object
+ attribute :id, :type => Integer
attribute :editable, :type => Boolean
if ActiveModel::VERSION::MAJOR < 4
- attr_accessible :name, :description, :field, :editable, :badge, :module_letter
+ attr_accessible :name, :description, :id, :editable, :badge, :mod
end
validates_presence_of :name
validates_presence_of :description
- validates_presence_of :module_letter
- validates_presence_of :field
+ validates_presence_of :mod
+ validates_numericality_of :id, :only_integer=>true, :greater_than=>0
validates_presence_of :badge
validates_inclusion_of :editable, :in => [true, false]
# @!method initialize
- # Initialize a new Badge
+ # Initialize a new Badge::Requirement
# @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
- # Compare Badge::Requirement based on badge then field
+ # Compare Badge::Requirement based on badge then requirement
def <=>(another)
result = self.badge <=> another.try(:badge)
- result = self.field <=> another.try(:field) if result == 0
+ result = self.id <=> another.try(:id) if result == 0
return result
end
def inspect
- Osm.inspect_instance(self, options={:replace_with => {'badge' => :identifier}})
+ Osm.inspect_instance(self, {:replace_with => {'badge' => :identifier}})
end
end # Class Requirement
+ class RequirementModule
+ include ActiveModel::MassAssignmentSecurity if ActiveModel::VERSION::MAJOR < 4
+ include ActiveAttr::Model
+
+ # @!attribute [rw] badge
+ # @return [Osm::Badge] the badge the requirement module belongs to
+ # @!attribute [rw] letter
+ # @return [String] the letter of the module
+ # @!attribute [rw] id
+ # @return [Fixnum] the id for the module
+ # @!attribute [rw] min_required
+ # @return [Fixnum] the minimum number of requirements which must be met to achieve this module
+ # @!attribute [rw] custom_columns
+ # @return [Fixnum, nil] ?
+ # @!attribute [rw] completed_into_column
+ # @return [Fixnum, nil] ?
+ # @!attribute [rw] numeric_into_column
+ # @return [Fixnum, nil] ?
+ # @!attribute [rw] add_column_id_to_numeric
+ # @return [Fixnum, nil] ?
+
+ attribute :badge, :type => Object
+ attribute :letter, :type => String
+ attribute :id, :type => Integer
+ attribute :min_required, :type => Integer
+ attribute :custom_columns, :type => Integer
+ attribute :completed_into_column, :type => Integer
+ attribute :numeric_into_column, :type => Integer
+ attribute :add_column_id_to_numeric, :type => Integer
+
+ if ActiveModel::VERSION::MAJOR < 4
+ attr_accessible :badge, :letter, :id, :min_required, :custom_columns, :completed_into_column, :numeric_into_column, :add_column_id_to_numeric
+ end
+
+ validates_presence_of :badge
+ validates_presence_of :letter
+ validates_numericality_of :id, :only_integer=>true, :greater_than=>0
+ validates_numericality_of :min_required, :only_integer=>true, :greater_than_or_equal_to=>0
+ validates_numericality_of :custom_columns, :only_integer=>true, :greater_than_or_equal_to=>0, :allow_nil=>true
+ validates_numericality_of :completed_into_column, :only_integer=>true, :greater_than=>0, :allow_nil=>true
+ validates_numericality_of :numeric_into_column, :only_integer=>true, :greater_than=>0, :allow_nil=>true
+ validates_numericality_of :add_column_id_to_numeric, :only_integer=>true, :greater_than=>0, :allow_nil=>true
+
+ # @!method initialize
+ # Initialize a new Badge::RequirementModule
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
+
+ # Compare Badge::RequirementModule based on badge then letter
+ def <=>(another)
+ result = self.badge <=> another.try(:badge)
+ result = self.letter <=> another.try(:letter) if result == 0
+ result = self.id <=> another.try(:id) if result == 0
+ return result
+ end
+
+ def inspect
+ Osm.inspect_instance(self, {:replace_with => {'badge' => :identifier}})
+ end
+
+ end # Class RequirementModule
+
+
class Data < Osm::Model
# @!attribute [rw] member_id
# @return [Fixnum] ID of the member this data relates to
# @!attribute [rw] first_name
# @return [Fixnum] the member's first name
@@ -452,11 +553,11 @@
validates_numericality_of :section_id, :only_integer=>true, :greater_than=>0
validates :requirements, :hash => {:key_type => Fixnum, :value_type => String}
# @!method initialize
- # Initialize a new Badge
+ # Initialize a new Badge::Data
# @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
# Override initialize to set @orig_attributes
old_initialize = instance_method(:initialize)
define_method :initialize do |*args|
ret_val = old_initialize.bind(self).call(*args)
@@ -469,42 +570,42 @@
# Get the total number of gained requirements
# @return [Fixnum] the total number of requirements considered gained
def total_gained
count = 0
badge.requirements.each do |requirement|
- next unless requirement_met?(requirement.field)
+ next unless requirement_met?(requirement.id)
count += 1
end
return count
end
- # Get the number of modules gained
- # @return [Fixnum]
+ # Get the letters of modules gained
+ # @return [Array<Stirng>]
def modules_gained
- needed = badge.needed_per_module
- modules = []
-
- gained_in_modules.each do |module_id, gained|
- next unless module_id.is_a?(Fixnum)
- next if gained < needed[module_id]
- module_letter = badge.module_map[module_id]
- modules.push module_letter unless module_letter >= 'y'
+ g_i_m = gained_in_modules
+ gained = []
+ badge.modules.each do |mod|
+ next if g_i_m[mod.id] < mod.min_required
+ gained.push mod.letter
end
-
- return modules
+ gained
end
# Get the number of requirements gained in each module
# @return [Hash]
def gained_in_modules
count = {}
+ badge.modules.each do |mod|
+ count[mod.id] ||= 0
+ count[mod.letter] ||= 0
+ end
badge.requirements.each do |requirement|
- count[requirement.module_letter] ||= 0
- next unless requirement_met?(requirement.field)
- count[requirement.module_letter] += 1
+ next unless requirement_met?(requirement.id)
+ count[requirement.mod.id] += 1
+ count[requirement.mod.letter] += 1
end
- Hash[*count.map{ |k,v| [badge.module_map[k], v, k, v] }.flatten]
+ count
end
# Check if this badge has been earnt
# @return [Boolean] whether the badge has been earnt (ignores other badge's and their requirements which might be needed)
@@ -513,35 +614,36 @@
return earnt > awarded
else
return false if (due.eql?(1) && awarded.eql?(1))
return true if (due.eql?(1) && awarded.eql?(0))
- criteria = badge.completion_criteria
- earnt = true
- if criteria[:min_modules_required] > 0
- earnt &= (modules_gained.size >= criteria[:min_modules_required])
+ if badge.min_modules_required > 0
+ return false unless modules_gained.size >= badge.min_modules_required
end
- if criteria[:min_requirements_completed] > 0
- earnt &= (total_gained >= criteria[:min_requirements_completed])
+ if badge.min_requirements_required > 0
+ return false unless total_gained >= badge.min_requirements_required
end
- if criteria[:requires]
+ if badge.requires_modules
# [['a'], ['b', 'c']] = a and (b or c)
- requires = criteria[:requires].clone
+ requires = badge.requires_modules.clone
modules = modules_gained
requires.map!{ |a| a.map{ |b| modules.include?(b) } } # Replace letters with true/false
requires.map!{ |a| a.include?(true) } # Replace each combination with true/false
- earnt &= !requires.include?(false) # Only earnt if all combinations are met
+ return false if requires.include?(false) # Only earnt if all combinations are met
end
- criteria[:badges_required].each do |b|
+ badge.other_requirements_required.each do |c|
+ # {:id => ###, :min => #}
+ if requirements.has_key?(c[:id]) # Only check it if the data is in the requirements Hash
+ return false unless requirement_met?(c[:id])
+ return false if requirements[c[:id]].to_i < c[:min]
+ end
+ end
+ badge.badges_required.each do |b|
# {:id => ###, :version => #}
#TODO
end
- criteria[:fields_required].each do |c|
- # {:id => ###, :min => #}
- #TODO
- end
- return earnt
+ return true
end
end
# Get what stage which has most recently been earnt
@@ -550,12 +652,12 @@
def earnt
unless badge.has_levels?
return earnt? ? 1 : 0
end
- levels_column = badge.completion_criteria[:levels_column_id]
- unless badge.completion_criteria[:show_letters] # It's a hikes, nights type badge
+ levels_column = badge.level_requirement
+ unless badge.show_level_letters # It's a hikes, nights type badge
badge.levels.reverse_each do |level|
return level if requirements[levels_column].to_i >= level
end
else # It's an activity type badge
modules = modules_gained
@@ -586,13 +688,13 @@
# @return [Fixnum] which stage of the badge has been started by the member (lowest)
def started
unless badge.has_levels?
return started? ? 1 : 0
end
- unless badge.completion_criteria[:show_letters]
+ unless badge.show_level_letters
# Nights, Hikes or Water
- done = requirements[badge.completion_criteria[:levels_column_id]].to_i
+ done = requirements[badge.level_requirement].to_i
levels = badge.levels # e.g. [0,1,2,3,4,5,10]
return 0 if levels.include?(done) # Has achieved a level (and not started next )
return 0 if done >= levels[-1] # No more levels to do
(1..(levels.size-1)).to_a.reverse_each do |i| # indexes from last to 2nd
this_level = levels[i]
@@ -605,12 +707,12 @@
letters = ('a'..'z').to_a
top_level = badge.levels[-1]
return 0 if due == top_level || awarded == top_level # No more levels to do
((due + 1)..top_level).reverse_each do |level|
badge.requirements.each do |requirement|
- next unless requirement.module_letter.eql?(letters[level - 1]) # Not interested in other levels
- return level if requirement_met?(requirement.field)
+ next unless requirement.mod.letter.eql?(letters[level - 1]) # Not interested in other levels
+ return level if requirement_met?(requirement.id)
end
end
return 0 # No levels started
end
end
@@ -698,24 +800,24 @@
section = Osm::Section.get(api, section_id)
require_ability_to(api, :write, :badge, section)
# Update requirements that changed
requirements_updated = true
- editable_fields = badge.requirements.select{ |r| r.editable }.map{ |r| r.field }
- requirements.changes.each do |field, (was,now)|
- if editable_fields.include?(field)
+ editable_requirements = badge.requirements.select{ |r| r.editable }.map{ |r| r.id }
+ requirements.changes.each do |requirement, (was,now)|
+ if editable_requirements.include?(requirement)
result = api.perform_query("ext/badges/records/?action=updateSingleRecord", {
'scoutid' => member_id,
'section_id' => section_id,
'badge_id' => badge.id,
'badge_version' => badge.version,
- 'field' => field,
+ 'field' => requirement,
'value' => now
})
requirements_updated = false unless result.is_a?(Hash) &&
(result['scoutid'].to_i == member_id) &&
- (result[field.to_s] == now)
+ (result[requirement.to_s] == now)
end
end
if requirements_updated
requirements.clean_up!
@@ -748,17 +850,17 @@
result = self.member_id <=> another.try(:member_id) if result == 0
return result
end
def inspect
- Osm.inspect_instance(self, options={:replace_with => {'badge' => :name}})
+ Osm.inspect_instance(self, {:replace_with => {'badge' => :name}})
end
# Work out if the requirmeent has been met
- # @param [Fixnum, #to_i] field_id The id of the field to evaluate (e.g. "12", "xSomething", "Yes" or "")
+ # @param [Fixnum, #to_i] requirement_id The id of the requirement to evaluate (e.g. "12", "xSomething", "Yes" or "")
# @return [Boolean] whether the requirmeent has been met
- def requirement_met?(field_id)
- data = requirements[field_id.to_i].to_s
+ def requirement_met?(requirement_id)
+ data = requirements[requirement_id.to_i].to_s
return false if data == '0'
!(data.blank? || data[0].downcase.eql?('x'))
end
end # Class Data