profile_suggestion.rb
7.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
class ProfileSuggestion < ActiveRecord::Base
belongs_to :person
belongs_to :suggestion, :class_name => 'Profile', :foreign_key => :suggestion_id
attr_accessible :person, :suggestion, :suggestion_type, :categories, :enabled
before_create do |profile_suggestion|
profile_suggestion.suggestion_type = self.suggestion.class.to_s
end
acts_as_having_settings :field => :categories
validate :must_be_a_valid_category, :on => :create
def must_be_a_valid_category
if categories.keys.map { |cat| self.respond_to?(cat)}.include?(false)
errors.add(:categories, 'Category must be valid')
end
end
validates_uniqueness_of :suggestion_id, :scope => [ :person_id ]
scope :of_person, :conditions => { :suggestion_type => 'Person' }
scope :of_community, :conditions => { :suggestion_type => 'Community' }
scope :enabled, :conditions => { :enabled => true }
# {:category_type => ['category-icon', 'category-label']}
CATEGORIES = {
:common_friends => ['menu-people', _('Friends in common')],
:common_communities => ['menu-community',_('Communities in common')],
:common_tags => ['edit', _('Tags in common')]
}
CATEGORIES.keys.each do |category|
settings_items category.to_sym
attr_accessible category.to_sym
end
def category_icon(category)
'icon-' + ProfileSuggestion::CATEGORIES[category][0]
end
def category_label(category)
ProfileSuggestion::CATEGORIES[category][1]
end
RULES = %w[
people_with_common_friends
people_with_common_communities
people_with_common_tags
communities_with_common_friends
communities_with_common_tags
]
# Number of suggestions
N_SUGGESTIONS = 30
# Number max of attempts
MAX_ATTEMPTS = N_SUGGESTIONS * 2
# Number of friends in common
COMMON_FRIENDS = 2
# Number of communities in common
COMMON_COMMUNITIES = 2
# Number of friends in common
COMMON_TAGS = 2
def self.register_suggestions(person, suggested_profiles, rule)
counter = rule.split(/.*_with_/).last
suggested_profiles.find_each do |suggested_profile|
suggestion = person.profile_suggestions.find_or_initialize_by_suggestion_id(suggested_profile.id)
suggestion.send(counter+'=', suggested_profile.common_count.to_i)
suggestion.save!
end
end
def self.calculate_suggestions(person)
ProfileSuggestion::RULES.each do |rule|
register_suggestions(person, ProfileSuggestion.send(rule, person), rule)
end
end
# If you are about to rewrite the following sql queries, think twice. After
# that make sure that whatever you are writing to replace it should be faster
# than how it is now. Yes, sqls are ugly but are fast! And fast is what we
# need here.
def self.people_with_common_friends(person)
person_friends = person.friends.map(&:id)
person.environment.people.
select("profiles.*, suggestions.count AS common_count").
joins("
INNER JOIN (SELECT person_id, count(person_id) FROM
friendships WHERE friend_id IN (#{person_friends.join(',')}) AND
person_id NOT IN (#{(person_friends << person.id).join(',')})
GROUP BY person_id
HAVING count(person_id) >= #{COMMON_FRIENDS}) AS suggestions
ON profiles.id = suggestions.person_id")
end
def self.people_with_common_communities(person)
person_communities = person.communities.map(&:id)
person.environment.people.
select("profiles.*, suggestions.count AS common_count").
joins("
INNER JOIN (SELECT common_members.accessor_id, count(common_members.accessor_id) FROM
(SELECT DISTINCT accessor_id, resource_id FROM
role_assignments WHERE role_assignments.resource_id IN (#{person_communities.join(',')}) AND
role_assignments.accessor_id != #{person.id} AND role_assignments.resource_type = 'Profile' AND
role_assignments.accessor_type = 'Profile') AS common_members
GROUP BY common_members.accessor_id
HAVING count(common_members.accessor_id) >= #{COMMON_COMMUNITIES})
AS suggestions ON profiles.id = suggestions.accessor_id")
end
def self.people_with_common_tags(person)
profile_tags = person.articles.select('tags.id').joins(:tags).map(&:id)
person.environment.people.
select("profiles.*, suggestions.count as common_count").
joins("
INNER JOIN (
SELECT results.profiles_id as profiles_id, count(results.profiles_id) FROM (
SELECT DISTINCT tags.id, profiles.id as profiles_id FROM profiles
INNER JOIN articles ON articles.profile_id = profiles.id
INNER JOIN taggings ON taggings.taggable_id = articles.id AND taggings.context = ('tags') AND taggings.taggable_type = 'Article'
INNER JOIN tags ON tags.id = taggings.tag_id
WHERE (tags.id in (#{profile_tags.join(',')}) AND profiles.id != #{person.id})) AS results
GROUP BY results.profiles_id
HAVING count(results.profiles_id) >= #{COMMON_TAGS})
as suggestions on profiles.id = suggestions.profiles_id")
end
def self.communities_with_common_friends(person)
person_friends = person.friends.map(&:id)
person.environment.communities.
select("profiles.*, suggestions.count AS common_count").
joins("
INNER JOIN (SELECT common_communities.resource_id, count(common_communities.resource_id) FROM
(SELECT DISTINCT accessor_id, resource_id FROM
role_assignments WHERE role_assignments.accessor_id IN (#{person_friends.join(',')}) AND
role_assignments.accessor_id != #{person.id} AND role_assignments.resource_type = 'Profile' AND
role_assignments.accessor_type = 'Profile') AS common_communities
GROUP BY common_communities.resource_id
HAVING count(common_communities.resource_id) >= #{COMMON_FRIENDS})
AS suggestions ON profiles.id = suggestions.resource_id")
end
def self.communities_with_common_tags(person)
profile_tags = person.articles.select('tags.id').joins(:tags).map(&:id)
person.environment.communities.
select("profiles.*, suggestions.count AS common_count").
joins("
INNER JOIN (
SELECT results.profiles_id AS profiles_id, count(results.profiles_id) FROM (
SELECT DISTINCT tags.id, profiles.id AS profiles_id FROM profiles
INNER JOIN articles ON articles.profile_id = profiles.id
INNER JOIN taggings ON taggings.taggable_id = articles.id AND taggings.context = ('tags') AND taggings.taggable_type = 'Article'
INNER JOIN tags ON tags.id = taggings.tag_id
WHERE (tags.id IN (#{profile_tags.join(',')}) AND profiles.id != #{person.id})) AS results
GROUP BY results.profiles_id
HAVING count(results.profiles_id) >= #{COMMON_TAGS})
AS suggestions ON profiles.id = suggestions.profiles_id")
end
def disable
self.enabled = false
self.save
end
def self.generate_all_profile_suggestions
Delayed::Job.enqueue(ProfileSuggestion::GenerateAllJob.new) unless ProfileSuggestion::GenerateAllJob.exists?
end
def self.generate_profile_suggestions(person_id)
Delayed::Job.enqueue ProfileSuggestionsJob.new(person_id) unless ProfileSuggestionsJob.exists?(person_id)
end
class GenerateAllJob
def self.exists?
Delayed::Job.by_handler("--- !ruby/object:ProfileSuggestion::GenerateAllJob {}\n").count > 0
end
def perform
Person.find_each {|person| ProfileSuggestion.generate_profile_suggestions(person.id) }
end
end
end