Improve browsing of (Ldap)Group.
[gitorious:mainline.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
24   extend GroupBehavior
25   include GroupBehavior::InstanceMethods
26
27   has_many :memberships, :dependent => :destroy
28   has_many :members, :through => :memberships, :source => :user
29   has_many :repositories, :as => :owner, :conditions => ["kind NOT IN (?)",
30                                                          Repository::KINDS_INTERNAL_REPO],
31     :dependent => :destroy
32   has_many :cloneable_repositories, :as => :owner, :class_name => "Repository",
33      :conditions => ["kind != ?", Repository::KIND_TRACKING_REPO]
34   has_many :projects, :as => :owner
35   has_many :content_memberships, :as => :member
36
37   attr_protected :public, :role_id, :user_id
38
39   NAME_FORMAT = /[a-z0-9\-]+/.freeze
40   validates_presence_of :name
41   validates_uniqueness_of :name
42   validates_format_of :name, :with => /^#{NAME_FORMAT}$/,
43     :message => "Must be alphanumeric, and optional dash"
44
45   before_validation :downcase_name
46
47   Paperclip.interpolates('group_name'){|attachment,style| attachment.instance.name}
48
49   avatar_local_path = '/system/group_avatars/:group_name/:style/:basename.:extension'
50   has_attached_file :avatar,
51     :default_url  =>'/images/default_group_avatar.png',
52     :styles => { :normal => "300x300>", :medium => "64x64>", :thumb => '32x32>', :icon => '16x16>' },
53     :url => avatar_local_path,
54     :path => ":rails_root/public#{avatar_local_path}"
55
56   def self.human_name
57     I18n.t("activerecord.models.group")
58   end
59
60   def self.all_participating_in_projects(projects)
61     mainline_ids = projects.map do |project|
62       project.repositories.mainlines.map{|r| r.id }
63     end.flatten
64     Committership.groups.find(:all,
65       :conditions => { :repository_id => mainline_ids }).map{|c| c.committer }.uniq
66   end
67
68   # Finds the most active groups by activity in repositories they're committers in
69   def self.most_active(limit = 10, cutoff = 5)
70     Rails.cache.fetch("groups:most_active:#{limit}:#{cutoff}", :expires_in => 1.hour) do
71       # FIXME: there's a certain element of approximation in here
72       find(:all, :joins => [{:committerships => {:repository => :events}}],
73         :select => %Q{groups.*, committerships.repository_id,
74           repositories.id, events.id, events.target_id, events.target_type,
75           count(events.id) as event_count},
76         :group => "groups.id",
77         :conditions => ["committerships.repository_id = events.target_id and " +
78                         "events.target_type = ? AND events.created_at > ?",
79                         "Repository", cutoff.days.ago],
80         :order => "event_count desc",
81         :limit => limit)
82     end
83   end
84
85   def self.find_fuzzy(query)
86     find(:all,
87          :conditions => ["LOWER(name) LIKE ?", "%" + query.downcase + "%"],
88          :limit => 10)
89   end
90
91   def all_related_project_ids
92     all_project_ids = projects.map{|p| p.id }
93     all_project_ids << repositories.map{|r| r.project_id }
94     all_project_ids << committerships.map{|p| p.repository.project_id }
95     all_project_ids.flatten!.uniq!
96     all_project_ids
97   end
98
99   def to_param
100     name
101   end
102
103   def title
104     name
105   end
106
107   def breadcrumb_parent
108     nil
109   end
110
111   def member?(user)
112     members.include?(user)
113   end
114
115   def user_role(candidate)
116     return if !candidate || candidate == :false
117     membership = memberships.find_by_user_id(candidate.id)
118     return unless membership
119     membership.role
120   end
121
122   def add_member(user, role)
123     memberships.create!(:user => user, :role => role)
124   end
125
126   def deletable?
127     members.count <= 1 && projects.blank?
128   end
129
130   def events(page = 1)
131     Event.top.paginate(:all, :page => page,
132                        :conditions => ["events.user_id in (:user_ids) and events.project_id in (:project_ids)", {
133                                          :user_ids => members.map { |u| u.id },
134                                          :project_ids => all_related_project_ids,
135                                        }],
136                        :order => "events.created_at desc",
137                        :include => [:user, :project])
138   end
139
140   protected
141   def downcase_name
142     name.downcase! if name
143   end
144 end