Allowing time cutoff for Group.most_active and customizable time cutoff User.most_act...
[gitorious:yousource.git] / app / models / group.rb
1 # encoding: utf-8
2 #--
3 #   Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
4 #
5 #   This program is free software: you can redistribute it and/or modify
6 #   it under the terms of the GNU Affero General Public License as published by
7 #   the Free Software Foundation, either version 3 of the License, or
8 #   (at your option) any later version.
9 #
10 #   This program is distributed in the hope that it will be useful,
11 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #   GNU Affero General Public License for more details.
14 #
15 #   You should have received a copy of the GNU Affero General Public License
16 #   along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #++
18
19 class Group < ActiveRecord::Base
20   has_many :committerships, :as => :committer, :dependent => :destroy
21   has_many :participated_repositories, :through => :committerships, 
22     :source => :repository, :class_name => 'Repository'  
23   belongs_to :creator, :class_name => "User", :foreign_key => "user_id"
24   has_many :memberships, :dependent => :destroy
25   has_many :members, :through => :memberships, :source => :user
26   has_many :repositories, :as => :owner, :conditions => ["kind != ?", Repository::KIND_WIKI],
27     :dependent => :destroy
28   has_many :projects, :as => :owner
29   
30   attr_protected :public, :role_id, :user_id
31   
32   NAME_FORMAT = /[a-z0-9\-]+/.freeze
33   validates_presence_of :name
34   validates_uniqueness_of :name
35   validates_format_of :name, :with => /^#{NAME_FORMAT}$/, 
36     :message => "Must be alphanumeric, and optional dash"
37     
38   before_validation :downcase_name
39   
40   Paperclip::Attachment.interpolations['group_name'] = lambda{|attachment,style| attachment.instance.name}
41   
42   avatar_local_path = '/system/group_avatars/:group_name/:style/:basename.:extension'
43   has_attached_file :avatar, 
44     :default_url  =>'/images/default_group_avatar.png',
45     :styles => { :normal => "300x300>", :medium => "64x64>", :thumb => '32x32>', :icon => '16x16>' },
46     :url => avatar_local_path,
47     :path => ":rails_root/public#{avatar_local_path}"
48   
49   
50   def self.human_name
51     I18n.t("activerecord.models.group")
52   end
53   
54   def self.all_participating_in_projects(projects, limit = 5)
55     mainline_ids = projects.map do |project|
56       project.repositories.mainlines.map{|r| r.id }
57     end.flatten
58     Committership.groups.find(:all, :conditions => {
59       :repository_id => mainline_ids
60     }, :limit => limit).map{|c| c.committer }.uniq
61   end
62   
63   # Finds the most active groups by activity in repositories they're committers in
64   def self.most_active(limit = 10, cutoff = 7.days.ago)
65     Rails.cache.fetch("groups:most_active:#{limit}", :expires_in => 1.hour) do
66       # FIXME: there's a certain element of approximation in here
67       find(:all, :joins => [{:committerships => {:repository => :events}}],
68         :select => %Q{groups.*, committerships.repository_id, 
69           repositories.id, events.id, events.target_id, events.target_type, 
70           count(events.id) as event_count},
71         :group => "groups.id",
72         :conditions => ["committerships.repository_id = events.target_id and 
73                         events.target_type = ? AND events.created_at > ?", "Repository", cutoff],
74         :order => "event_count desc",
75         :limit => limit)
76     end
77   end
78   
79   def all_related_project_ids
80     all_project_ids = projects.map{|p| p.id }
81     all_project_ids << repositories.map{|r| r.project_id }
82     all_project_ids << committerships.map{|p| p.repository.project_id }
83     all_project_ids.flatten!.uniq!
84     all_project_ids
85   end
86   
87   def to_param
88     name
89   end
90   
91   def to_param_with_prefix
92     "+#{to_param}"
93   end
94   
95   def title
96     name
97   end
98   
99   def breadcrumb_parent
100     nil
101   end
102   
103   # is this +user+ a member of this group?
104   def member?(user)
105     members.include?(user)
106   end
107   
108   # returns the Role of +user+ in this group
109   def role_of_user(candidate)
110     if !candidate || candidate == :false
111       return
112     end
113     membership = memberships.find_by_user_id(candidate.id)
114     return unless membership
115     membership.role
116   end
117   
118   # is +candidate+ an admin in this group?
119   def admin?(candidate)
120     role_of_user(candidate) == Role.admin
121   end
122   
123   # is +candidate+ a committer (or admin) in this group?
124   def committer?(candidate)
125     [Role.admin, Role.member].include?(role_of_user(candidate))
126   end
127   
128   # Adds +a_user+ as a member to this group with a role of +a_role+
129   def add_member(a_user, a_role)
130     memberships.create!(:user => a_user, :role => a_role)
131   end
132   
133   def deletable?
134     members.count <= 1 && projects.blank?
135   end
136   
137   protected
138     def downcase_name
139       name.downcase! if name
140     end
141 end