robertgauld/osm

View on GitHub
lib/osm/activity.rb

Summary

Maintainability
D
2 days
Test Coverage
module Osm

  class Activity < Osm::Model
    class Badge; end # Ensure the constant exists for the validators
    class File; end # Ensure the constant exists for the validators
    class Version; end # Ensure the constant exists for the validators

    SORT_BY = [:id, :version]

    # @!attribute [rw] id
    #   @return [Fixnum] the id for the activity
    # @!attribute [rw] version
    #   @return [Fixnum] the version of the activity
    # @!attribute [rw] group_id
    #   @return [Fixnum] the group_id
    # @!attribute [rw] user_id
    #   @return [Fixnum] the user_id of the creator of the activity
    # @!attribute [rw] title
    #   @return [String] the activity's title
    # @!attribute [rw] description
    #   @return [String] the description of the activity
    # @!attribute [rw] resources
    #   @return [String] resources required to do the activity
    # @!attribute [rw] instructions
    #   @return [String] instructions for doing the activity
    # @!attribute [rw] running_time
    #   @return [Fixnum] duration of the activity in minutes
    # @!attribute [rw] location
    #   @return [Symbol] :indoors, :outdoors or :both
    # @!attribute [rw] shared
    #   @return [Fixnum] 2 - Public, 0 - Private
    # @!attribute [rw] rating
    #   @return [Fixnum] ?
    # @!attribute [rw] editable
    #   @return [Boolean] Wether the current API user can edit this activity
    # @!attribute [rw] deletable
    #   @return [Boolean] Wether the current API user can delete this activity
    # @!attribute [rw] used
    #   @return [Fixnum] How many times this activity has been used (total accross all of OSM)
    # @!attribute [rw] versions
    #   @return [Array<Osm::Activity::Version>]
    # @!attribute [rw] sections
    #   @return [Array<Symbol>] the sections the activity is appropriate for
    # @!attribute [rw] tags
    #   @return [Array<String>] the tags attached to the activity
    # @!attribute [rw] files
    #   @return [Array<Osm::Activity::File>
    # @!attribute [rw] badges
    #   @return [Array<Osm::Activity::Badge>

    attribute :id, :type => Integer
    attribute :version, :type => Integer
    attribute :group_id, :type => Integer
    attribute :user_id, :type => Integer
    attribute :title, :type => String
    attribute :description, :type => String
    attribute :resources, :type => String
    attribute :instructions, :type => String
    attribute :running_time, :type => Integer
    attribute :location
    attribute :shared, :type => Integer
    attribute :rating, :type => Integer
    attribute :editable, :type => Boolean, :default => true
    attribute :deletable, :type => Boolean, :default => true
    attribute :used, :type => Integer
    attribute :versions, :default => []
    attribute :sections, :default => []
    attribute :tags, :default => []
    attribute :files, :default => []
    attribute :badges, :default => []

    if ActiveModel::VERSION::MAJOR < 4
      attr_accessible :id, :version, :group_id, :user_id, :title, :description,
                      :resources, :instructions, :running_time, :location,
                      :shared, :rating, :editable, :deletable, :used, :versions,
                      :sections, :tags, :files, :badges
    end

    validates_numericality_of :id, :only_integer=>true, :greater_than=>0
    validates_numericality_of :version, :only_integer=>true, :greater_than_or_equal_to=>0, :allow_nil=>true
    validates_numericality_of :group_id, :only_integer=>true, :greater_than=>0, :allow_nil=>true
    validates_numericality_of :user_id, :only_integer=>true, :greater_than=>0, :allow_nil=>true
    validates_numericality_of :running_time, :only_integer=>true, :greater_than_or_equal_to=>0
    validates_numericality_of :shared, :only_integer=>true, :greater_than_or_equal_to=>0, :allow_nil=>true
    validates_numericality_of :rating, :only_integer=>true, :allow_nil=>true
    validates_numericality_of :used, :only_integer=>true, :allow_nil=>true
    validates_presence_of :title
    validates_presence_of :description
    validates_presence_of :resources
    validates_presence_of :instructions
    validates_inclusion_of :editable, :in => [true, false]
    validates_inclusion_of :deletable, :in => [true, false]
    validates_inclusion_of :location, :in => [:indoors, :outdoors, :both], :message => 'is not a valid location'

    validates :sections, :array_of => {:item_type => Symbol}
    validates :tags, :array_of => {:item_type => String}
    validates :badges, :array_of => {:item_type => Osm::Activity::Badge, :item_valid => true}
    validates :files, :array_of => {:item_type => Osm::Activity::File, :item_valid => true}
    validates :versions, :array_of => {:item_type => Osm::Activity::Version, :item_valid => true}


    # Get activity details
    # @param [Osm::Api] api The api to use to make the request
    # @param [Fixnum] activity_id The activity ID
    # @param [Fixnum] version The version of the activity to retreive, if nil the latest version will be assumed
    # @!macro options_get
    # @return [Osm::Activity]
    def self.get(api, activity_id, version=nil, options={})
      cache_key = ['activity', activity_id]

      if !options[:no_cache] && cache_exist?(api, [*cache_key, version])
        activity = cache_read(api, [*cache_key, version])
        if (activity.shared == 2) || (activity.user_id == api.user_id) ||  # Shared or owned by this user
        Osm::Section.get_all(api).map{ |s| s.group_id }.uniq.include?(activity.group_id)  # user belomngs to the group owning the activity
          return activity
        else
          return nil
        end
      end

      data = nil
      if version.nil?
        data = api.perform_query("programme.php?action=getActivity&id=#{activity_id}")
      else
        data = api.perform_query("programme.php?action=getActivity&id=#{activity_id}&version=#{version}")
      end

      attributes = {}
      attributes[:id] = Osm::to_i_or_nil(data['details']['activityid'])
      attributes[:version] = data['details']['version'].to_i
      attributes[:group_id] = Osm::to_i_or_nil(data['details']['groupid'])
      attributes[:user_id] = Osm::to_i_or_nil(data['details']['userid'])
      attributes[:title] = data['details']['title']
      attributes[:description] = data['details']['description']
      attributes[:resources] = data['details']['resources']
      attributes[:instructions] = data['details']['instructions']
      attributes[:running_time] = Osm::to_i_or_nil(data['details']['runningtime'])
      attributes[:location] = data['details']['location'].to_sym
      attributes[:shared] = Osm::to_i_or_nil(data['details']['shared'])
      attributes[:rating] = data['details']['rating'].to_i
      attributes[:editable] = data['editable']
      attributes[:deletable] = data['deletable'] ? true : false
      attributes[:used] = data['used'].to_i
      attributes[:sections] = data['sections'].is_a?(Array) ? data['sections'].map(&:to_sym) : []
      attributes[:tags] = data['tags'].is_a?(Array) ? data['tags'] : []
      attributes[:versions] = []
      attributes[:files] = []
      attributes[:badges] = []

      # Populate Arrays
      (data['files'].is_a?(Array) ? data['files'] : []).each do |file_data|
        attributes[:files].push File.new(
          :id => Osm::to_i_or_nil(file_data['fileid']),
          :activity_id => Osm::to_i_or_nil(file_data['activityid']),
          :file_name => file_data['filename'],
          :name => file_data['name']
        )
      end
      (data['badges'].is_a?(Array) ? data['badges'] : []).each do |badge_data|
        attributes[:badges].push Badge.new(
          :badge_type => badge_data['badgetype'].to_sym,
          :badge_section => badge_data['section'].to_sym,
          :badge_name => badge_data['badgeLongName'],
          :badge_id => Osm::to_i_or_nil(badge_data['badge_id']),
          :badge_version => Osm::to_i_or_nil(badge_data['badge_version']),
          :requirement_id => Osm::to_i_or_nil(badge_data['column_id']),
          :requirement_label => badge_data['columnnameLongName'],
          :data => badge_data['data'],
        )
      end
      (data['versions'].is_a?(Array) ? data['versions'] : []).each do |version_data|
        attributes[:versions].push Version.new(
          :version => Osm::to_i_or_nil(version_data['value']),
          :created_by => Osm::to_i_or_nil(version_data['userid']),
          :created_by_name => version_data['firstname'],
          :label => version_data['label']
        )
      end

      activity = Osm::Activity.new(attributes)

      cache_write(api, [*cache_key, nil], activity) if version.nil?
      cache_write(api, [*cache_key, version], activity)
      return activity
    end


    # @!method initialize
    #   Initialize a new Term
    #   @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)


    # Get the link to display this activity in OSM
    # @return [String] the link for this member's My.SCOUT
    # @raise [Osm::ObjectIsInvalid] If the Activity is invalid
    def osm_link
      raise Osm::ObjectIsInvalid, 'activity is invalid' unless valid?
      return "https://www.onlinescoutmanager.co.uk/?l=p#{self.id}"
    end

    # Add this activity to the programme in OSM
    # @param [Osm::Api] api The api to use to make the request
    # @param [Osm::Section, Fixnum, #to_i] section The Section (or it's ID) to add the Activity to
    # @param [Date, DateTime] date The date of the Evening to add the Activity to (OSM will create the Evening if it doesn't already exist)
    # @param [String] notes The notes which should appear for this Activity on this Evening
    # @return [Boolean] Whether the activity was successfully added
    def add_to_programme(api, section, date, notes="")
      require_ability_to(api, :write, :programme, section)

      data = api.perform_query("programme.php?action=addActivityToProgramme", {
        'meetingdate' => date.strftime(Osm::OSM_DATE_FORMAT),
        'activityid' => id,
        'sectionid' => section.to_i,
        'notes' => notes,
      })

      if (data == {'result'=>0})
        # The cached activity will be out of date - remove it
        cache_delete(api, ['activity', self.id])
        return true
      else
        return false
      end
    end

    # Update this activity in OSM
    # @param [Osm::Api] api The api to use to make the request
    # @param [Osm::Section, Fixnum, #to_i] section The Section (or it's ID)
    # @param [Boolean] secret_update Whether this is a secret update
    # @return [Boolean] Whether the activity was successfully added
    # @raise [Osm::ObjectIsInvalid] If the Activity is invalid
    # @raise [Osm::Forbidden] If the Activity is not editable
    def update(api, section, secret_update=false)
      raise Osm::ObjectIsInvalid, 'activity is invalid' unless valid?
      raise Osm::Forbidden, "You are not allowed to update this activity" unless self.editable

      data = api.perform_query("programme.php?action=update", {
        'title' => title,
        'description' => description,
        'resources' => resources,
        'instructions' => instructions,
        'id' => id,
        'files' => files.map{|f| f.id }.join(','),
        'time' => running_time.to_s,
        'location' => location,
        'sections' => sections.to_json,
        'tags' => tags.to_json,
        'links' => badges.map{ |b|
          {
            'badge_id' => b.badge_id.to_s,
            'badge_version' => b.badge_version.to_s,
            'column_id' => b.requirement_id.to_s,
            'badge' => nil,
            'badgeLongName' => b.badge_name,
            'columnname' => nil,
            'columnnameLongName' => b.requirement_label,
            'data' => b.data,
            'section' => b.badge_section,
            'sectionLongName' => nil,
            'sections' => sections.map{ |s| s.to_s },
            'badgetype' => b.badge_type.to_s,
            'badgetypeLongName' => nil,
          }
        }.to_json,
        'shared' => shared,
        'sectionid' => section.to_i,
        'secretEdit' => secret_update,
      })

      if (data == {'result'=>true})
        # The cached activity will be out of date - remove it
        cache_delete(api, ['activity', self.id])
        return true
      else
        return false
      end
    end


    private
    class File
      include ActiveModel::MassAssignmentSecurity if ActiveModel::VERSION::MAJOR < 4
      include ActiveAttr::Model

      # @!attribute [rw] id
      #   @return [Fixnum] the OSM ID for the file
      # @!attribute [rw] activity_id
      #   @return [Fixnum] the OSM ID for the activity
      # @!attribute [rw] file_name
      #   @return [String] the file name of the file
      # @!attribute [rw] name
      #   @return [String] the name of the file (more human readable than file_name)

      attribute :id, :type => Integer
      attribute :activity_id, :type => Integer
      attribute :file_name, :type => String
      attribute :name, :type => String

      if ActiveModel::VERSION::MAJOR < 4
        attr_accessible :id, :activity_id, :file_name, :name
      end

      validates_numericality_of :id, :only_integer=>true, :greater_than=>0
      validates_numericality_of :activity_id, :only_integer=>true, :greater_than=>0
      validates_presence_of :file_name
      validates_presence_of :name

      # @!method initialize
      #   Initialize a new Term
      #   @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)

      # Compare File based on activity_id then name
      def <=>(another)
        result = self.activity_id <=> another.try(:activity_id)
        result = self.name <=> another.try(:name) if result == 0
        return result
      end

    end # Class Activity::File

    class Badge
      include ActiveModel::MassAssignmentSecurity if ActiveModel::VERSION::MAJOR < 4
      include ActiveAttr::Model

      # @!attribute [rw] badge_type
      #   @return [Symbol] the type of badge
      # @!attribute [rw] badge_section
      #   @return [Symbol] the section type that the badge belongs to
      # @!attribute [rw] requirement_label
      #   @return [String] human firendly requirement label
      # @!attribute [rw] data
      #   @return [String] what to put in the column when the badge records are updated
      # @!attribute [rw] badge_name
      #   @return [String] the badge's name
      # @!attribute [rw] badge_id
      #   @return [Fixnum] the badge's ID in OSM
      # @!attribute [rw] badge_version
      #   @return [Fixnum] the version of the badge
      # @!attribute [rw] requirement_id
      #   @return [Fixnum] the requirement's ID in OSM

      attribute :badge_type, :type => Object
      attribute :badge_section, :type => Object
      attribute :requirement_label, :type => String
      attribute :data, :type => String
      attribute :badge_name, :type => String
      attribute :badge_id, :type => Integer
      attribute :badge_version, :type => Integer
      attribute :requirement_id, :type => Integer

      if ActiveModel::VERSION::MAJOR < 4
        attr_accessible :badge_type, :badge_section, :requirement_label, :data, :badge_name, :badge_id, :badge_version, :requirement_id
      end

      validates_presence_of :badge_name
      validates_inclusion_of :badge_section, :in => [:beavers, :cubs, :scouts, :explorers, :staged]
      validates_inclusion_of :badge_type, :in => [:core, :staged, :activity, :challenge]
      validates_numericality_of :badge_id, :only_integer=>true, :greater_than=>0
      validates_numericality_of :badge_version, :only_integer=>true, :greater_than_or_equal_to=>0
      validates_numericality_of :requirement_id, :only_integer=>true, :greater_than=>0, :allow_nil=>true

      # @!method initialize
      #   Initialize a new Meeting::Activity
      #   @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)

      # Compare BadgeLink based on section, type, badge_name, requirement_label, data
      def <=>(another)
        [:badge_section, :badge_type, :badge_name, :requirement_label].each do |attribute|
          result = self.try(:data) <=> another.try(:data)
          return result unless result == 0
        end
        return self.try(:data) <=> another.try(:data)
      end

    end # Class Activity::Badge

    class Version
      include ActiveModel::MassAssignmentSecurity if ActiveModel::VERSION::MAJOR < 4
      include ActiveAttr::Model

      # @!attribute [rw] version
      #   @return [Fixnum] the version of the activity
      # @!attribute [rw] created_by
      #   @return [Fixnum] the OSM user ID of the person who created this version
      # @!attribute [rw] created_by_name
      #   @return [String] the aname of the OSM user who created this version
      # @!attribute [rw] label
      #   @return [String] the human readable label to use for this version

      attribute :version, :type => Integer
      attribute :created_by, :type => Integer
      attribute :created_by_name, :type => String
      attribute :label, :type => String

      if ActiveModel::VERSION::MAJOR < 4
        attr_accessible :version, :created_by, :created_by_name, :label
      end

      validates_numericality_of :version, :only_integer=>true, :greater_than_or_equal_to=>0
      validates_numericality_of :created_by, :only_integer=>true, :greater_than=>0
      validates_presence_of :created_by_name
      validates_presence_of :label

      # @!method initialize
      #   Initialize a new Version
      #   @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)

      # Compare Version based on activity_id then version
      def <=>(another)
        result = self.activity_id <=> another.try(:activity_id)
        result = self.version <=> another.try(:version) if result == 0
        return result
      end

    end # Class Activity::Version

  end # Class Activity

end # Module