class Osm::Badge

Public Class Methods

get_badges_for_section(api, section, section_type=nil, options={}) click to toggle source

Get badges @param [Osm::Api] api The api to use to make the request @param [Osm::Section, Fixnum, to_i] section The section (or its ID) to get the due badges for @param [Symbol] section_type The type of section to get badges for (if nil uses the type of the section param) @!macro options_get @return [Array<Osm::Badge>]

# File lib/osm/badge.rb, line 101
def self.get_badges_for_section(api, section, section_type=nil, options={})
  raise Error, 'This method must be called on one of the subclasses (CoreBadge, ChallengeBadge, StagedBadge or ActivityBadge)' if type.nil?
  require_ability_to(api, :read, :badge, section, options)
  section = Osm::Section.get(api, section, options) unless section.is_a?(Osm::Section)
  section_type ||= section.type
  cache_key = ['badges', section_type, type]

  if !options[:no_cache] && Osm::Model.cache_exist?(api, cache_key)
    return cache_read(api, cache_key)
  end

  term_id = Osm::Term.get_current_term_for_section(api, section, options).to_i
  badges = []
  badge_sharing_map = {
    'draft' => :draft,
    'private' => :private,
    'optin' => :optin,
    'optin-locked' => :optin_locked,
    'default-locked' => :default_locked
  }

  data = api.perform_query("ext/badges/records/?action=getBadgeStructureByType&section=#{section_type}&type_id=#{type_id}&term_id=#{term_id}&section_id=#{section.id}")
  badge_order = data["badgeOrder"].to_s.split(',')
  structures = data["structure"] || {}
  details = data["details"] || {}

  badge_order.each do |b|
    structure = structures[b]
    detail = details[b]
    config = ActiveSupport::JSON.decode(detail['config'] || '{}')

    badge = new(
      :id => detail['badge_id'],
      :version => detail['badge_version'],
      :identifier => detail['badge_identifier'],
      :name => detail['name'],
      :requirement_notes => detail['description'],
      :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'],
      :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'],
        :mod => modules[r['module']],
        :id => Osm::to_i_or_nil(r['field']),
        :editable => r['editable'].to_s.eql?('true'),
      )
    end
    badge.requirements = requirements

    badges.push badge
  end

  cache_write(api, cache_key, badges)
  return badges
end
get_summary_for_section(api, section, term=nil, options={}) click to toggle source

Get a summary of badges earnt by members @param [Osm::Api] api The api to use to make the request @param [Osm::Section, Fixnum, to_i] section The section (or its ID) to get the due badges for @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 @!macro options_get @return [Array<Hash>]

# File lib/osm/badge.rb, line 183
def self.get_summary_for_section(api, section, term=nil, options={})
  raise Error, 'This method must NOT be called on one of the subclasses(CoreBadge, ChallengeBadge, StagedBadge or ActivityBadge)' unless type.nil?
  require_ability_to(api, :read, :badge, section, options)
  section = Osm::Section.get(api, section, options) unless section.is_a?(Osm::Section)
  term_id = (term.nil? ? Osm::Term.get_current_term_for_section(api, section, options) : term).to_i
  cache_key = ['badge-summary', section.id, term_id]

  if !options[:no_cache] && Osm::Model.cache_exist?(api, cache_key)
    return cache_read(api, cache_key)
  end

  summary = []
  data = api.perform_query("ext/badges/records/summary/?action=get&mode=verbose&section=#{section.type}&sectionid=#{section.id}&termid=#{term_id}")
  data['items'].each do |item|
    new_item = {
      :first_name => item['firstname'],
      :last_name => item['lastname'],
      :name => "#{item['firstname']} #{item['lastname']}",
      :member_id => Osm.to_i_or_nil(item['scout_id']),
    }

    badge_data = Hash[item.to_a.select{ |k,v| !!k.match(/\d+_\d+/) }]
    badge_data.each do |badge_identifier, status|
      if status.is_a?(String)
        # Possible statuses:
        # 'Started',
        # 'Due', 'Due Lvl 2'
        # 'Awarded', 'Awarded Lvl 2', '01/02/2003', '02/03/2004 (Lvl 2)'
        if status.eql?('Started')
          new_item[badge_identifier] = :started
        elsif status.eql?('Due')
          new_item[badge_identifier] = :due
        elsif match_data = status.match(/\ADue Lvl (\d+)\Z/)
          new_item[badge_identifier] = :due
          new_item["#{badge_identifier}_level"] = match_data[1].to_i
        elsif status.eql?('Awarded')
          new_item[badge_identifier] = :awarded
        elsif match_data = status.match(/\AAwarded Lvl (\d+)\Z/)
          new_item[badge_identifier] = :awarded
          new_item["#{badge_identifier}_level"] = match_data[1].to_i
        elsif match_data = status.match(Osm::OSM_DATE_REGEX)
          new_item[badge_identifier] = :awarded
          new_item["#{badge_identifier}_date"] = Osm.parse_date(match_data[0])
        elsif match_data = status.match(/\A(#{Osm::OSM_DATE_REGEX_UNANCHORED.to_s}) \(Lvl (\d+)\)\Z/)
          new_item[badge_identifier] = :awarded
          new_item["#{badge_identifier}_date"] = Osm.parse_date(match_data[1])
          new_item["#{badge_identifier}_level"] = match_data[2].to_i
        end
      end
    end

    summary.push new_item
  end

  cache_write(api, cache_key, summary)
  return summary
end
type() click to toggle source
# File lib/osm/badge.rb, line 382
def self.type
  nil
end

Private Class Methods

get_module_completion_data(api, options={}) click to toggle source

Return a 2 dimensional hash/array (badge ID, badge version) of hashes representing the modules

# File lib/osm/badge.rb, line 346
def self.get_module_completion_data(api, options={})
  cache_key = ['badge_module_completion_data']
  if !options[:no_cache] && cache_exist?(api, cache_key)
    return cache_read(api, cache_key)
  end

  osm_data = api.perform_query('ext/badges/records/?action=_getModuleDetails')
  osm_data = (osm_data || {})['items'] || []
  osm_data.map! do |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 |id, version, m|
    data[id] ||= []
    data[id][version] ||= []
    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
module_completion_data(api, badge, options={}) click to toggle source

return an array of hashes representing the modules of the badge

# File lib/osm/badge.rb, line 322
def self.module_completion_data(api, badge, options={})
  fetched_this_time = @module_completion_data.nil? # Flag to ensure we only get the data once (at most) per invocation
  @module_completion_data = get_module_completion_data(api, options) if fetched_this_time

  if @module_completion_data[badge.id].nil? && !fetched_this_time
    @module_completion_data = get_module_completion_data(api, options)
    fetched_this_time = true
  end
  data = @module_completion_data[badge.id]
  raise ArgumentError, "That badge doesn't exist (bad ID #{badge.id})." if data.nil?

  if data[badge.version].nil? && !fetched_this_time
    @module_completion_data = get_module_completion_data(api, options)
    data = @module_completion_data[badge.id]
    fetched_this_time = true
  end
  data = data[badge.version]
  raise ArgumentError, "That badge doesn't exist (bad version #{badge.version} of #{badge.id})." if data.nil?

  data.each{ |i| i.badge = badge }
  return data
end
subscription_required() click to toggle source
# File lib/osm/badge.rb, line 390
def self.subscription_required
  :bronze
end

Public Instance Methods

<=>(another) click to toggle source

Compare Badge based on name then id then version (desc)

# File lib/osm/badge.rb, line 312
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
  return result
end
add_columns?() click to toggle source
# File lib/osm/badge.rb, line 284
def add_columns?
  !add_columns_to_module.nil?
end
get_data_for_section(api, section, term=nil, options={}) click to toggle source

Get a list of badge requirements met by members @param [Osm::Api] api The api to use to make the request @param [Osm::Section, Fixnum, to_i] section The section (or its ID) to get the due badges for @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 @!macro options_get @return [Array<Osm::Badge::Data>]

# File lib/osm/badge.rb, line 247
def get_data_for_section(api, section, term=nil, options={})
  raise Error, 'This method must be called on one of the subclasses (CoreBadge, ChallengeBadge, StagedBadge or ActivityBadge)' if type.nil?
  Osm::Model.require_ability_to(api, :read, :badge, section, options)
  section = Osm::Section.get(api, section, options) unless section.is_a?(Osm::Section)
  term_id = (term.nil? ? Osm::Term.get_current_term_for_section(api, section, options) : term).to_i
  cache_key = ['badge_data', section.id, term_id, id, version]

  if !options[:no_cache] && cache_exist?(api, cache_key)
    return cache_read(api, cache_key)
  end

  datas = []
  data = api.perform_query("ext/badges/records/?action=getBadgeRecords&term_id=#{term_id}&section=#{section.type}&badge_id=#{id}&section_id=#{section.id}&badge_version=#{version}")

  data['items'].each do |d|
    datas.push Osm::Badge::Data.new(
      :member_id => d['scoutid'],
      :first_name => d['firstname'],
      :last_name => d['lastname'],
      :due => d['completed'].to_i,
      :awarded => d['awarded'].to_i,
      :awarded_date => Osm.parse_date(d['awardeddate']),
      :requirements => Hash[d.map{ |k,v| [k.to_i, v] }].except(0),
      :section_id => section.id,
      :badge => self,
    )
  end

  cache_write(api, cache_key, datas)
  return datas
end
has_levels?() click to toggle source
# File lib/osm/badge.rb, line 280
def has_levels?
  !levels.nil?
end
module_ids() click to toggle source
# File lib/osm/badge.rb, line 306
def module_ids
  @module_ids ||= modules.map{ |m| m.id }.sort
end
module_letters() click to toggle source
# File lib/osm/badge.rb, line 302
def module_letters
  @module_letters ||= modules.map{ |m| m.letter }.sort
end
module_map() click to toggle source
# File lib/osm/badge.rb, line 288
def module_map
  @module_map ||= Hash[
    *modules.map{ |m| 
      [m.id, m.letter, m.letter, m.id]
    }.flatten
  ].except('z')
end
needed_per_module() click to toggle source
# File lib/osm/badge.rb, line 296
def needed_per_module
  @needed_per_module ||= Hash[*modules.map{ |m|
    [m.id, m.min_required, m.letter, m.min_required]
  }.flatten].except('z')
end
type() click to toggle source
# File lib/osm/badge.rb, line 385
def type
  self.class.type
end

Private Instance Methods

subscription_required() click to toggle source
# File lib/osm/badge.rb, line 393
def subscription_required
  self.class.subscription_required
end