muck-services 0.1.34 → 0.1.35

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.34
1
+ 0.1.35
@@ -42,15 +42,14 @@ class Muck::EntriesController < ApplicationController
42
42
  render_text "Unable to find the specified document"
43
43
  return
44
44
  end
45
- @entry_title = @entry.title + ' (' + @entry.feed.short_title + ')'
46
- @page_title = @entry_title + ' - ' + I18n.t('muck.services.related_resources_title')
47
- # I18n.locale = @entry.language[0..1]
45
+ @entry_title = "#{@entry.title} (#{@entry.feed.short_title})"
46
+ @page_title = "#{@entry_title} - #{I18n.t('muck.services.related_resources_title')}"
48
47
  @limit = params[:limit] ? params[:limit].to_i : 20
49
48
  @limit = 40 if @limit > 40
50
49
 
51
50
  respond_to do |format|
52
51
  format.html do
53
- @recommendations = @entry.recommendations(@limit, 'relevance', params[:details] == 'true')
52
+ @recommendations = @entry.related_entries.top(true, @limit)
54
53
  if params[:details] == 'true'
55
54
  render :template => "entries/details"
56
55
  else
@@ -1,51 +1,45 @@
1
1
  class Muck::RecommendationsController < ApplicationController
2
-
2
+
3
3
  unloadable
4
4
 
5
- # GET /recommendations
6
- # GET /recommendations.xml
7
5
  def index
8
- @details = (params[:details] == 'true')
9
-
6
+ # if a uri isn't specified in params, we assume that they are requesting from the page doing the requesting
10
7
  @uri = params[:u] || request.env['HTTP_REFERER']
11
8
  if @uri.blank? || !allowed_uri(@uri)
12
9
  render :text => '<!-- permission denied -->'
13
10
  return
14
11
  end
15
-
12
+
13
+ # we trim eduCommons urls back to the course so all pages in the course get the same recommendations
16
14
  if params[:educommons]
17
15
  @uri = @uri[%r=http://.*?/.*?/[^/]+=] || @uri
18
16
  params[:title] = true
19
17
  params[:more_link] = true
20
18
  end
21
19
 
22
- # Entry.track_time_on_page(session, @uri)
23
- @entry = Entry.recommender_entry(@uri)
24
- # I18n.locale = @entry.language[0..1] if !@entry.nil?
25
-
20
+ @details = (params[:details] == 'true')
26
21
  @limit = params[:limit] ? params[:limit].to_i : 5
27
22
  @limit = 25 if @limit > 25
28
-
23
+ @omit_feeds = params[:omit_feeds]
24
+ @order = params[:order] || "rank"
25
+
26
+ Entry.track_time_on_page(session, @uri)
27
+ @entry = Entry.recommender_entry(@uri)
28
+ @recommendations = @entry.related_entries.top(@details, @limit, @omit_feeds, @order)
29
+ @app = request.protocol + request.host_with_port
30
+
29
31
  respond_to do |format|
30
32
  format.html do
31
- order = params[:order] || "mixed"
32
33
  if !@entry.id.nil?
33
- redirect_to resource_path(@entry) + "?limit=#{@limit}&order=#{order}&details=#{@details}"
34
+ redirect_to resource_path(@entry) + "?limit=#{@limit}&order=#{@order}&details=#{@details}"
34
35
  else
35
- @recommendations = @entry.ranked_recommendations(@limit, params[:order] || "mixed", @details)
36
- render :template => 'recommendations/index'
36
+ render(:text => t('muck.services.url_not_in_index', :uri => params[:uri]), :layout => true)
37
37
  end
38
38
  end
39
- format.xml {
40
- render('recommendations/index.xml.builder', :layout => false)
41
- }
42
- format.pjs {
43
- @host = "http://#{URI.parse(@uri).host}"
44
- render('recommendations/index.pjs.erb', :layout => false)
45
- }
46
- format.rss {
47
- render(:template => 'recommendations/index.rss.builder', :layout => false)
48
- }
39
+ format.xml { render('recommendations/index.xml.builder', :layout => false) }
40
+ format.pjs { render('recommendations/index.pjs.erb', :layout => false) }
41
+ format.rss { render('recommendations/index.rss.builder', :layout => false) }
42
+ format.js { render('recommendations/index.js.erb', :layout => false) }
49
43
  end
50
44
  end
51
45
 
@@ -9,7 +9,7 @@ class Muck::VisitsController < ApplicationController
9
9
  @page_title = @entry.title
10
10
  @resource_uri = @entry.resource_uri
11
11
  @share = Share.new(:title => @entry.title, :uri => @resource_uri, :entry_id => @entry.id) if GlobalConfig.enable_services_shares
12
- @recommendations = @entry.recommendations(5)
12
+ @recommendations = @entry.related_entries.top
13
13
 
14
14
  if GlobalConfig.enable_services_comments
15
15
  # Show the activities related to this entry
data/app/models/entry.rb CHANGED
@@ -34,18 +34,31 @@ class Entry < ActiveRecord::Base
34
34
  has_many :activities, :as => :attachable, :dependent => :destroy
35
35
  has_many :attentions, :dependent => :destroy
36
36
  has_many :personal_recommendations, :as => :destination
37
+
38
+ has_many :related_to, :foreign_key => 'entry_id', :class_name => 'Recommendation'
39
+ has_many :related_entries, :through => :related_to, :source => :dest_entry do
40
+ def top(details=false, limit=5, omit_feeds=nil, order='rank desc, relevance desc')
41
+ select = 'entries.feed_id, recommendations.id recommendation_id, recommendations.relevance, ' +
42
+ 'entries.title, feeds.short_title collection, recommendations.dest_entry_id '
43
+ select << ', entries.author, entries.published_at, recommendations.clicks, entries.permalink, ' +
44
+ 'entries.direct_link direct_uri, ' +
45
+ 'recommendations.avg_time_at_dest average_time_at_dest, entries.description' if details
46
+ conditions = omit_feeds == nil ? '' : "entries.feed_id NOT IN (#{omit_feeds.gsub(/[^0-9,]/,'')})"
47
+ joins = 'INNER JOIN feeds ON entries.feed_id = feeds.id'
48
+ find(:all, :select => select, :joins => joins, :conditions => conditions, :limit => limit, :order => order)
49
+ end
50
+ end
51
+
37
52
  acts_as_commentable
38
53
  acts_as_taggable
39
54
  acts_as_muck_recommendation
40
55
 
56
+ @@min_secs_tracked = 5
57
+ @@max_secs_tracked = 120
41
58
  @@default_time_on_page = 60.0
42
59
 
43
60
  acts_as_solr({:if => false, :fields => [{:aggregation => :integer}, {:feed_id => :integer}, {:grain_size => :string}, {:tags => :string}]}, {:type_field => :type_s})
44
61
 
45
- def resource_uri
46
- self.direct_link.nil? ? self.permalink : self.direct_link
47
- end
48
-
49
62
  def self.search(search_terms, grain_size = nil, language = "en", limit = 10, offset = 0, operator = :or)
50
63
  raise MuckServices::Exceptions::LanguageNotSupported, I18n.t('muck.services.language_not_supported') unless Recommender::Languages.supported_languages.include?(language)
51
64
  query = ((!grain_size.nil? && grain_size != 'all') ? (search_terms + ") AND (grain_size:#{grain_size}") : search_terms) + ") AND (aggregation:#{Aggregation.global_feeds_id}"
@@ -54,198 +67,36 @@ class Entry < ActiveRecord::Base
54
67
  :joins => "INNER JOIN feeds ON feeds.id = entries.feed_id", :core => language, :operator => operator)
55
68
  end
56
69
 
57
- def self.recommender_entry(uri)
58
- uri = normalized_uri(uri)
59
- Entry.find(:first, :conditions => ['permalink = ? OR direct_link = ?', uri, uri], :order => 'direct_link IS NULL DESC') || Entry.new(:permalink => uri)
60
- end
61
-
62
- def recommendation_entries(limit = 20, order = "relevance", details = false, omit_feeds = nil)
63
- sql = "SELECT recommendations.dest_entry_id AS id, entries.permalink, entries.title, entries.description, entries.direct_link, feeds.short_title AS collection "
64
- sql << ", relevance_calculated_at, relevance, clicks, avg_time_at_dest AS avg_time_on_target, author, published_at " if details == true
65
- sql << "FROM recommendations "
66
- sql << "INNER JOIN entries ON recommendations.dest_entry_id = entries.id "
67
- sql << "INNER JOIN feeds ON entries.feed_id = feeds.id "
68
- sql << "WHERE recommendations.entry_id = ? "
69
- sql << ("AND entries.feed_id NOT IN (" + omit_feeds.gsub(/[^0-9,]/,'') + ") ") if omit_feeds != nil
70
- sql << "ORDER BY " + order + " DESC "
71
- sql << "LIMIT " + limit.to_s
72
- Entry.find_by_sql([sql,self.id])
73
- end
74
-
75
- def recommendations(limit = 20, order = "relevance", details = false, omit_feeds = nil)
76
- sql = "SELECT recommendations.id, dest_entry_id, entries.permalink, entries.title, entries.description, entries.direct_link, feeds.short_title AS collection "
77
- sql << ", relevance_calculated_at, relevance, clicks, avg_time_at_dest AS avg_time_on_target, author, published_at " if details == true
78
- sql << "FROM recommendations "
79
- sql << "INNER JOIN entries ON recommendations.dest_entry_id = entries.id "
80
- sql << "INNER JOIN feeds ON entries.feed_id = feeds.id "
81
- sql << "WHERE recommendations.entry_id = ? "
82
- sql << ("AND entries.feed_id NOT IN (" + omit_feeds.gsub(/[^0-9,]/,'') + ") ") if omit_feeds != nil
83
- sql << "ORDER BY " + order + " DESC "
84
- sql << "LIMIT " + limit.to_s
85
- Entry.find_by_sql([sql,self.id])
86
- end
87
-
88
- def relevant_recommendations(limit = 5, order = "relevance", details = false, omit_feeds = nil)
89
- return self.recommendations(limit, order, details, omit_feeds)
90
- end
91
-
92
- def ranked_recommendations(limit = 5, order = "mixed", details = false, omit_feeds = nil)
93
- return self.recommendations(limit, "clicks DESC, relevance", details, omit_feeds) if order == "clicks"
94
- return self.recommendations(limit, "relevance", details, omit_feeds) if (order == "relevance" || details == true)
95
- return relevant_recommendations_filtered(limit, details, omit_feeds) if omit_feeds != nil
96
-
97
- recs = []
98
- if self.popular != nil && !self.popular.empty?
99
- recs.concat(ActiveSupport::JSON.decode(self.popular).first(limit))
100
- end
101
- if recs.length < limit && self.relevant != nil && !self.relevant.empty?
102
- relevant_recs = randomize(ActiveSupport::JSON.decode(self.relevant))
103
- recs.concat(relevant_recs.first(limit - recs.length))
104
- end
105
- if recs.length < limit && self.relevant != nil && !self.other.empty?
106
- other_recs = randomize(ActiveSupport::JSON.decode(self.other))
107
- recs.concat(other_recs.first(limit - recs.length))
108
- end
109
- return recs
110
- end
111
-
112
- def json_recommendations(limit = 5, order = "mixed", details = false, omit_feeds = nil)
113
- recs = ranked_recommendations(limit, order, details, omit_feeds)
114
- (recs.nil? || recs.empty?) ? "" : ActiveSupport::JSON.encode(recs)
115
- end
116
-
117
- def relevant_recommendations_filtered(limit, details, omit_feeds)
118
- # get recommendations for the entry from the recommendations table
119
- recs = self.recommendations(limit, "mixed", details, omit_feeds)
120
-
121
- # for storing the various lists
122
- popular_recs = []
123
- relevant_recs = []
124
- other_recs = []
125
-
126
- # see where to cut draw the lines for popular and relevant
127
- click_threshold = calc_click_threshold(recs)
128
- relevance_threshold = calc_relevance_threshold(recs)
129
-
130
- # store the recommendations
131
- recs.each do |r|
132
- if (r["clicks"].to_i > click_threshold)
133
- popular_recs << r
134
- elsif (r["relevance"].to_f > relevance_threshold)
135
- relevant_recs << r
136
- else
137
- other_recs << r
138
- end
139
- end
140
-
141
- # order popular items strictly by clicks
142
- popular_recs.sort{|r1,r2| r2["avg_time_on_target"].to_i <=> r1["avg_time_on_target"].to_i}
143
-
144
- return popular_recs[0..limit] if popular_recs.size > limit
145
- return (popular_recs + relevant_recs)[0..limit] if (popular_recs.size + relevant_recs.size) > limit
146
- return (popular_recs + relevant_recs + other_recs)[0..limit]
70
+ def resource_uri
71
+ self.direct_link.nil? ? self.permalink : self.direct_link
147
72
  end
148
73
 
149
- def rank_recommendations
150
- return
151
- # get recommendations for the entry from the recommendations table
152
- recs = self.recommendations
153
-
154
- # for storing the various lists
155
- popular_recs = []
156
- relevant_recs = []
157
- other_recs = []
158
-
159
- # see where to cut draw the lines for popular and relevant
160
- click_threshold = calc_click_threshold(recs)
161
- relevance_threshold = calc_relevance_threshold(recs)
162
-
163
- # store the recommendations
164
- recs.each do |r|
165
- if (r["clicks"].to_i > click_threshold)
166
- popular_recs << r
167
- elsif (r["relevance"].to_f > relevance_threshold)
168
- relevant_recs << r
169
- else
170
- other_recs << r
171
- end
172
- end
173
-
174
- # order popular items strictly by clicks
175
- popular_recs.sort{|r1,r2| r2["avg_time_on_target"].to_i <=> r1["avg_time_on_target"].to_i}
176
-
177
- # cache the JSON for the lists in the entry record
178
- self.popular = ActiveSupport::JSON.encode(popular_recs)
179
- self.relevant = ActiveSupport::JSON.encode(relevant_recs)
180
- self.other = ActiveSupport::JSON.encode(other_recs)
181
- self.save!
182
- end
183
-
184
- def randomize(recs)
185
- i = recs.length
186
- return recs if (i == 0)
187
- while (i > 0)
188
- i = i - 1
189
- j = (rand*(i+1)).floor
190
- ti = recs[i]
191
- tj =recs[j]
192
- recs[i] = tj
193
- recs[j] = ti
194
- end
195
- return recs
74
+ def self.normalized_uri(uri)
75
+ uri.sub(/index.?\.(html|aspx|shtm|htm|asp|php|cfm|jsp|shtml|jhtml)$/, '')
196
76
  end
197
77
 
198
- def calc_relevance_threshold(recs)
199
- sum = 0
200
- recs.each do |r|
201
- sum += r["relevance"].to_f
202
- end
203
- average = sum/recs.length
204
- sum = 0
205
- recs.each do |r|
206
- sum += (r["relevance"].to_f-average)**2
207
- end
208
- standard_deviation = Math.sqrt(sum/recs.length);
209
- return average + standard_deviation
210
- end
211
-
212
- def calc_click_threshold(recs)
213
- sum = 0
214
- recs.each do |r|
215
- sum += r["clicks"].to_f
216
- end
217
- average = sum/recs.length
218
- sum = 0
219
- recs.each do |r|
220
- sum += (r["clicks"].to_f-average)**2
221
- end
222
- standard_deviation = Math.sqrt(sum/recs.length);
223
- threshold = average + standard_deviation
224
- return threshold > 5 ? threshold : 5
78
+ def self.recommender_entry(uri)
79
+ uri = normalized_uri(uri)
80
+ Entry.find(:first, :conditions => ['permalink = ? OR direct_link = ?', uri, uri], :order => 'direct_link IS NULL DESC') || Entry.new(:permalink => uri)
225
81
  end
226
82
 
227
- def self.avg_time(clicks, old_avg, time_on_page)
228
- return (old_avg*(clicks-1) + time_on_page)/clicks
229
- end
230
-
231
83
  def self.track_time_on_page(session, uri)
232
84
  recommendation_id = session[:last_clicked_recommendation]
233
85
  if !recommendation_id.nil?
234
86
  time_on_page = (Time.now - session[:last_clicked_recommendation_time].to_f).to_i
235
87
 
236
- # if they spend longer than two minutes on a page, we don't infer anything
237
- if time_on_page > 5 and time_on_page < 120
88
+ # if they spend less or more than time is reasonable, we don't infer anything
89
+ if time_on_page > @@min_secs_tracked and time_on_page < @@max_secs_tracked
238
90
  if normalized_uri(uri) != session[:last_clicked_recommendation_uri]
239
91
  recommendation = Recommendation.find(recommendation_id)
240
92
  entry = Entry.find(recommendation.entry_id)
241
93
  new_avg = (recommendation.avg_time_at_dest*recommendation.clicks - @@default_time_on_page + time_on_page)/recommendation.clicks
242
94
  recommendation.avg_time_at_dest = new_avg
243
95
  recommendation.save!
244
- entry.rank_recommendations
245
96
  session[:last_clicked_recommendation] = nil
246
97
  end
247
98
  else
248
- session[:last_clicked_recommendation] = nil if time_on_page > 5
99
+ session[:last_clicked_recommendation] = nil if time_on_page > @@min_secs_tracked
249
100
  end
250
101
  end
251
102
  end
@@ -255,10 +106,6 @@ class Entry < ActiveRecord::Base
255
106
  recommendation = Recommendation.find(recommendation_id)
256
107
  return "" if !recommendation
257
108
 
258
- # get the entries being linked from and to
259
- entry = Entry.find(recommendation.entry_id)
260
- target = Entry.find(recommendation.dest_entry_id)
261
-
262
109
  # get the list of recommendations that have been clicked during this session
263
110
  clicks = session[:rids] || Array.new
264
111
 
@@ -286,9 +133,6 @@ class Entry < ActiveRecord::Base
286
133
  session[:last_clicked_recommendation_time] = now
287
134
  session[:last_clicked_recommendation_uri] = redirect
288
135
 
289
- # update the recommendation cache for the entry
290
- entry.rank_recommendations if entry
291
-
292
136
  # track the click in the db
293
137
  Click.create(:recommendation_id => recommendation_id, :when => now, :referrer => referrer, :requester => requester, :user_agent => user_agent)
294
138
  end
@@ -296,28 +140,6 @@ class Entry < ActiveRecord::Base
296
140
  return redirect
297
141
  end
298
142
 
299
- def self.truncate_words(text, length = 30, end_string = ' ...')
300
- words = text.split()
301
- words[0..(length-1)].join(' ') + (words.length > length ? end_string : '')
302
- end
303
-
304
- protected
305
-
306
- # def self.redirect_uri(target, referrer, redirect_type)
307
- # if !target.direct_link.nil? and redirect_type != "metadata"
308
- # return target.direct_link if redirect_type == "direct_link"
309
- # if !referrer.nil?
310
- # domain = "http://" + URI.parse(referrer).host
311
- # return target.direct_link if target.permalink[0..domain.length-1] != domain
312
- # end
313
- # end
314
- # return target.permalink
315
- # end
316
-
317
- def self.normalized_uri(uri)
318
- uri.sub(/index.?\.(html|aspx|shtm|htm|asp|php|cfm|jsp|shtml|jhtml)$/, '')
319
- end
320
-
321
143
  def self.real_time_recommendations(uri, language='en', limit=5, details=false, options = {})
322
144
  raise MuckServices::Exceptions::LanguageNotSupported, I18n.t('muck.services.language_not_supported') unless Recommender::Languages.supported_languages.include?(language)
323
145
  fields = "entries.id, entries.title, entries.permalink, entries.direct_link, feeds.short_title AS collection"
data/app/models/feed.rb CHANGED
@@ -181,7 +181,12 @@ class Feed < ActiveRecord::Base
181
181
 
182
182
  # Looks for feeds from a given url
183
183
  def self.discover_feeds(uri)
184
- Feedbag.find(uri)
184
+ begin
185
+ Feedbag.find(uri)
186
+ rescue URI::InvalidURIError
187
+ # if we aren't able to discover a feed then just return the original uri
188
+ [Struct.new(:url => uri, :title => '')]
189
+ end
185
190
  end
186
191
 
187
192
  # Finds or creates a feed based on the url. Any give feed uri should only exist once in the system
@@ -12,4 +12,6 @@
12
12
  #
13
13
 
14
14
  class Recommendation < ActiveRecord::Base
15
+ belongs_to :entry, :class_name => 'Entry', :foreign_key => 'entry_id'
16
+ belongs_to :dest_entry, :class_name => 'Entry', :foreign_key => 'dest_entry_id'
15
17
  end
@@ -1,4 +1,4 @@
1
1
  <%= link_to related_entry.title + " (" + related_entry.collection + ")", "/r?id=#{related_entry.id}", :class => "title_link", :rel => "nofollow" %>
2
2
  - <%= link_to t('muck.services.related_resources'), resource_path(related_entry.dest_entry_id), :class => "related_resources_link", :rel => "nofollow" %>
3
- <% if !related_entry.direct_link.nil? %> - <%= link_to t('muck.services.metadata'), "/r?id=#{related_entry.id}&target=metadata", :class => "catalog_link" %><% end %>
3
+ <% if !related_entry.direct_uri.nil? %> - <%= link_to t('muck.services.metadata'), "/r?id=#{related_entry.id}&target=metadata", :class => "catalog_link" %><% end %>
4
4
  <% if related_entry.description.length > 0 %><br/><span class="description"><%= truncate_words(related_entry.description) %></span><% end %>
@@ -1,3 +1,4 @@
1
+ <% cache({:locale => Language.locale_id, :format => 'html', :grain_size => @grain_size, :tag_filter => @tag_filter}) do %>
1
2
  <div id="tag-cloud">
2
3
  <fieldset>
3
4
  <legend><%= @tag_filter.nil? ? (@grain_size == 'course' ? t('muck.services.courses_tag_cloud_label') : t('muck.services.resources_tag_cloud_label')) : t('muck.services.narrow_further') %></legend>
@@ -15,4 +16,5 @@
15
16
  <% end %>
16
17
  <% end -%>
17
18
  </fieldset>
18
- </div>
19
+ </div>
20
+ <% end %>
@@ -1,6 +1,6 @@
1
1
  <%= output_errors('', {:class => 'help-box'}) %>
2
2
  <div class="block">
3
- <% if @results.length > 0 %>
3
+ <% if !@results.blank? %>
4
4
  <%= render(:partial => 'entries/result_status') %>
5
5
  <div class="column span-15">
6
6
  <%= render(:partial => 'entries/results') %>
@@ -1,3 +1,5 @@
1
+ <% cache({:locale => Language.locale_id, :format => 'html', :grain_size => @grain_size) do %>
1
2
  <div align="center">
2
3
  <%= render :partial => 'entries/tag_cloud' %>
3
4
  </div>
5
+ <% end %>
@@ -0,0 +1 @@
1
+ <% cache({:locale => Language.locale_id, :format => 'js', :details => @details, :limit => @limit, :order => @order, :id => + @entry.id}, :omit_feeds => @omit_feeds) do %>{"document":{"document_id":<%= @entry.id.to_s %>,"uri":"<%= @entry.permalink %>","recommendations":<%= @recommendations.to_json %>}}<% end %>
@@ -1,12 +1,13 @@
1
+ <% cache({:locale => Language.locale_id, :format => 'pjs', :details => @details, :limit => @limit, :order => @order, :id => + @entry.id, :omit_feeds => @omit_feeds}) do %>
1
2
  <%
2
- @json_recommendations = @entry.json_recommendations(@limit, params[:order] || "mixed", true, params[:omit_feeds] || nil)
3
- if !@json_recommendations.empty?
3
+ @host = "http://#{URI.parse(@uri).host}"
4
+ if !@recommendations.empty?
4
5
  @direct_link_text = params[:direct_link_text] || t('muck.services.direct_link')
5
6
  %>
6
7
  var catalog_page = <%= !@entry.direct_link.nil? and @uri == @entry.permalink %>;
7
8
  var document_host = '<%= @host %>';
8
- var recs = <%= @json_recommendations %>;
9
- var app = "<%= app = request.protocol + request.host_with_port + '/' %>";
9
+ var recs = <%= @recommendations.to_json %>;
10
+ var app = "<%= request.protocol + request.host_with_port + '/' %>";
10
11
  <% if @details == true -%>
11
12
  function truncate(text, length) {
12
13
  nEnd = text.indexOf(" ", 200);
@@ -30,8 +31,8 @@ for(nRec = 0; nRec < recs.length; nRec++) {
30
31
  metadata_link = catalog_page && document_host == r.uri.substring(0, document_host.length);
31
32
  direct_link = metadata_link && r.direct_link;
32
33
  document.write('<p class="oer_recommender_item">');
33
- document.write('<a class="oer_recommender_recommendation_link" href="' + app + 'r?id=' + r.id + (metadata_link ? "&target=metadata" : "") + '">' + r.title + ' (' + r.collection + ')</a>');
34
- if (direct_link) document.write(' <a class="oer_recommender_direct_link" href="' + app + 'r?id=' + r.id + '&target=direct_link"><%= @direct_link_text %></a>');
34
+ document.write('<a class="oer_recommender_recommendation_link" href="' + app + 'r?id=' + r.recommendation_id + (metadata_link ? "&target=metadata" : "") + '">' + r.title + ' (' + r.collection + ')</a>');
35
+ if (direct_link) document.write(' <a class="oer_recommender_direct_link" href="' + app + 'r?id=' + r.recommendation_id + '&target=direct_link"><%= @direct_link_text %></a>');
35
36
  <% if @details == true -%>
36
37
  document.write(' <span class="oer_recommender_published_at">(' + format_date(r.published_at) + ')</span>');
37
38
  document.write(' <span class="oer_recommender_relevance_score"><%= t('muck.services.relevance')%>: ' + Math.round(r.relevance*100)/100 + '</span>');
@@ -44,3 +45,4 @@ document.write('</div>');
44
45
  <% if params[:more_link] %>document.write('<div class="oer_recommender_more_link"><a href="' + app + 'resources/<%= @entry.id %>"><%= t('muck.services.gm_more_prompt') %></a></div>');<% end %>
45
46
  document.write('</div>');
46
47
  <% end %>
48
+ <% end %>
@@ -1,37 +1,37 @@
1
- if @entry.nil?
2
- @recommendations = Array.new
3
- else
4
- @recommendations = @entry.ranked_recommendations(@limit, params[:order] || "mixed")
5
- end
1
+ cache({:locale => Language.locale_id, :format => 'rss', :details => @details, :limit => @limit, :order => @order, :id => + @entry.id, :omit_feeds => @omit_feeds}) do
2
+ headers["Content-Type"] = "application/rss+xml"
3
+ xml.instruct!
6
4
 
7
- headers["Content-Type"] = "application/rss+xml"
8
- xml.instruct!
9
-
10
- xml.rss "version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/", "xmlns:oerr" => "http://www.oerrecommender.org/oerr/elements/1.0/" do
5
+ xml.rss "version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/", "xmlns:oerr" => "http://www.folksemantic.com/oerr/elements/1.0/" do
11
6
  xml.channel do
12
7
 
13
- xml.title "OER Recommender recommendations for " + @entry.permalink
14
- xml.link url_for(request.env["REQUEST_URI"])
15
- # xml.pubDate CGI.rfc1123_date @entries.first.updated_at if @entries.any?
16
- # xml.description "Blog posts for " + @event.title
17
- xml.generator 'OER Recommender'
8
+ xml.title "Folksemantic recommendations for " + @entry.permalink
9
+ xml.link url_for(request.env["REQUEST_URI"])
10
+ xml.generator 'Folksemantic'
18
11
 
19
- @recommendations.each do |recommendation|
20
- xml.item do
21
- xml.link recommendation["uri"]
22
- xml.oerr :clicks, recommendation["clicks"]
23
- xml.oerr :relevance, round(recommendation["relevance"])
24
- xml.title recommendation["title"]
25
- if recommendation["description"] != nil
26
- xml.description "type" => "html" do
27
- xml.text! recommendation["description"]
28
- end
29
- end
30
- # xml.pubDate CGI.rfc1123_date recommendation.created_at
31
- xml.guid "http://www.oerrecommender.org/r?id=" + recommendation["id"].to_s
32
- # xml.author "#{entry.author} (#{entry.author})"
12
+ @recommendations.each do |recommendation|
13
+ xml.item do
14
+ xml.title recommendation["title"]
15
+ xml.link (@app +'/r?id=' + recommendation["recommendation_id"])
16
+ xml.guid (@app +'/r?id=' + recommendation["recommendation_id"])
17
+ xml.oerr :relevance, recommendation["relevance"]
18
+
19
+ if @details == true
20
+ xml.oerr :recommendation_id, recommendation["recommendation_id"]
21
+ xml.oerr :uri, recommendation["permalink"]
22
+ xml.oerr :direct_uri, recommendation["direct_link"]
23
+ if recommendation["description"] != nil
24
+ xml.description "type" => "html" do
25
+ xml.text! recommendation["description"]
26
+ end
27
+ end
28
+ xml.oerr :clicks, recommendation["clicks"]
29
+ xml.oerr :average_time_at_dest, recommendation["avg_time_on_target"]
30
+ xml.pubDate CGI.rfc1123_date recommendation["published_at"]
31
+ xml.author recommendation["author"]
32
+ end
33
+ end
33
34
  end
34
35
  end
35
-
36
36
  end
37
- end
37
+ end
@@ -1,34 +1,25 @@
1
- xml.instruct!
2
-
3
- if @entry.nil?
4
- xml.recommendations
5
- else
6
-
7
- app = request.protocol + request.host_with_port + '/'
8
- @recommendations = @entry.ranked_recommendations(@limit, params[:order] || "mixed", @details)
9
- xml.recommendations(:document_id => @entry.nil? ? "" : @entry.id, :uri => @uri, :title => t("muck.services.gm_title"), :more_prompt => t("muck.services.gm_more_prompt"), :direct_link_text => t("muck.services.direct_link")) do
1
+ cache({:locale => Language.locale_id, :format => 'xml', :details => @details, :limit => @limit, :order => @order, :id => + @entry.id, :omit_feeds => @omit_feeds}) do
2
+ xml.instruct!
3
+ xml.recommendations(:document_id => @entry.id, :uri => @entry.permalink, :title => t("muck.services.gm_title"),
4
+ :more_prompt => t("muck.services.gm_more_prompt"), :direct_link_text => t("muck.services.direct_link")) do
10
5
  @recommendations.each do |recommendation|
11
- xml.recommendation do
12
- xml.title recommendation["title"]
13
- xml.collection recommendation["collection"]
14
- xml.link app + 'r?id=' + recommendation['id'].to_s
15
- xml.has_direct_link "true" if (recommendation["direct_link"] != nil and @uri[0..20] == recommendation["uri"][0..20])
16
-
17
- if @details == true
18
- xml.direct_link app + 'r?id=' + recommendation["id"].to_s + "&u=" + recommendation["direct_link"] if recommendation["direct_link"] != nil
19
- xml.uri recommendation["uri"]
20
- xml.direct_uri recommendation["direct_link"]
21
- xml.description recommendation["description"]
22
- xml.clicks recommendation["clicks"]
23
- xml.average_time_at_dest recommendation["avg_time_on_target"]
24
- xml.relevance round(recommendation["relevance"])
25
- xml.published_at recommendation["published_at"]
26
- xml.author recommendation["author"]
27
- xml.tag_list recommendation["tag_list"]
28
- else
29
- xml.description ""
30
- end
6
+ xml.recommendation do
7
+ xml.title recommendation["title"]
8
+ xml.collection recommendation["collection"]
9
+ xml.link @app + '/r?id=' + recommendation["recommendation_id"].to_s
10
+ xml.relevance recommendation["relevance"]
11
+
12
+ if @details == true
13
+ xml.recommendation_id recommendation["recommendation_id"]
14
+ xml.uri recommendation["permalink"]
15
+ xml.direct_uri recommendation["direct_link"]
16
+ xml.description recommendation["description"]
17
+ xml.clicks recommendation["clicks"]
18
+ xml.average_time_at_dest recommendation["avg_time_on_target"]
19
+ xml.published_at recommendation["published_at"]
20
+ xml.author recommendation["author"]
31
21
  end
22
+ end
32
23
  end
33
- end
24
+ end
34
25
  end
@@ -36,7 +36,7 @@ for(nRec = 0; nRec < recs.length; nRec++) {
36
36
  document.write('<br/><span class="oer_recommender_description">' + truncate(r.description) + '</span>');
37
37
  document.write('<br/><span class="oer_recommender_uri">' + r.uri + '</span>');
38
38
  <% end -%>
39
- document.write('</a></p>');
39
+ document.write('</p>');
40
40
  }
41
41
  document.write('</div>');
42
42
  <% if params[:more_link] %>document.write('<div class="oer_recommender_more_link"><a href="' + app + 'documents/<%= @entry.id %>"><%= t('muck.services.gm_more_prompt') %></a></div>');<% end %>
@@ -39,7 +39,7 @@ module MuckServices
39
39
  }
40
40
  end
41
41
 
42
- desc "Loads some feeds oai endpoints to get things started"
42
+ desc "Loads some feeds and oai endpoints to get things started (this should be run before muck:services:db:create_global_aggregation_feeds)"
43
43
  task :bootstrap => :environment do
44
44
  require 'active_record/fixtures'
45
45
  ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
@@ -78,6 +78,15 @@ module MuckServices
78
78
 
79
79
  end
80
80
 
81
+ desc "Creates a global feeds aggregation and adds all existing feeds to it"
82
+ task :create_global_feeds_aggregation => :environment do
83
+ if Aggregation.find_by_title('global_feeds') == nil
84
+ global_feeds_id = Aggregation.create(:title => 'global_feeds', :terms => 'global_feeds',
85
+ :description => 'Feeds included in the site indexes.').id
86
+ Feed.find(:all).each { |feed| AggregationFeed.create(:feed_id => feed.id, :aggregation_id => global_feeds_id) }
87
+ end
88
+ end
89
+
81
90
  end
82
91
 
83
92
  desc "Sync files from muck services."
@@ -87,21 +96,6 @@ module MuckServices
87
96
  system "rsync -ruv #{path}/public ."
88
97
  end
89
98
 
90
- desc "Create global_feeds aggregation and add feeds to it"
91
- task :create_global_feeds => :environment do
92
- admin_id = User.find_by_login('admin').id
93
- global_aggregation = Aggregation.create(:title => 'global_feeds', :terms => 'global_feeds',
94
- :description => 'Feeds included in the site indexes.',
95
- :ownable_id => admin_id, :ownable_type => 'User')
96
- global_aggregation = Aggregation.find_by_title('global_feeds')
97
- global_aggregation.feeds << Feed.find(:all, :conditions => "uri LIKE 'http://www.oercommons.org%' OR id < 1047364815")
98
- global_aggregation.save!
99
- AggregationFeed.update_all("feed_type = 'Feed'")
100
- OaiEndpoint.find(:all).each do |ep|
101
- AggregationFeed.create(:aggregation_id => global_aggregation.id, :feed_type => 'OaiEndpoint', :feed_id => ep.id)
102
- end
103
- end
104
-
105
99
  desc "Add attention types"
106
100
  task :add_attention_types => :environment do
107
101
  AttentionType.create(:id => AttentionType::WRITE, :name => 'write', :default_weight => 10)
data/locales/en.yml CHANGED
@@ -214,5 +214,6 @@ en:
214
214
  previous: Previous
215
215
  facebook_notes: Facebook Notes
216
216
  opml_generated: Feeds for {{terms}}
217
+ url_not_in_index: The <a href='{{uri}}'>resource you requested recommendations for</a> has not been indexed. Please add a repository containing metadata for the resource to add it to the index.
217
218
  activity_templates:
218
219
  entry_comment: Comment
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{muck-services}
8
- s.version = "0.1.34"
8
+ s.version = "0.1.35"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Joel Duffin", "Justin Ball"]
12
- s.date = %q{2010-01-25}
12
+ s.date = %q{2010-02-08}
13
13
  s.description = %q{This gem contains the rails specific code for dealing with feeds, aggregations and recommendations. It is meant to work with the muck-raker gem.}
14
14
  s.email = %q{justin@tatemae.com}
15
15
  s.extra_rdoc_files = [
@@ -114,7 +114,7 @@ Gem::Specification.new do |s|
114
114
  "app/views/parts/_add_feed.html.erb",
115
115
  "app/views/parts/_select_feed.html.erb",
116
116
  "app/views/recommendations/get_button.html.erb",
117
- "app/views/recommendations/index.html.erb",
117
+ "app/views/recommendations/index.js.erb",
118
118
  "app/views/recommendations/index.pjs.erb",
119
119
  "app/views/recommendations/index.rss.builder",
120
120
  "app/views/recommendations/index.xml.builder",
@@ -505,6 +505,8 @@ Gem::Specification.new do |s|
505
505
  "test/rails_root/db/migrate/20091115011828_add_aggregations_for_personal_recs.rb",
506
506
  "test/rails_root/db/migrate/20091116094447_rename_action_table.rb",
507
507
  "test/rails_root/db/migrate/20091118203605_add_default_feed_type_to_aggregation_feed.rb",
508
+ "test/rails_root/db/migrate/20100123035450_create_access_codes.rb",
509
+ "test/rails_root/db/migrate/20100123233654_create_access_code_requests.rb",
508
510
  "test/rails_root/db/schema.rb",
509
511
  "test/rails_root/features/step_definitions/common_steps.rb",
510
512
  "test/rails_root/features/step_definitions/visit_steps.rb",
@@ -534,6 +536,7 @@ Gem::Specification.new do |s|
534
536
  "test/rails_root/test/unit/identity_feed_test.rb",
535
537
  "test/rails_root/test/unit/oai_endpoint_test.rb",
536
538
  "test/rails_root/test/unit/personal_recommendation_test.rb",
539
+ "test/rails_root/test/unit/recommendation_test.rb",
537
540
  "test/rails_root/test/unit/service_category_test.rb",
538
541
  "test/rails_root/test/unit/service_test.rb",
539
542
  "test/rails_root/test/unit/services_mailer_test.rb",
@@ -1,4 +1,4 @@
1
- RAILS_GEM_VERSION = '2.3.4' unless defined? RAILS_GEM_VERSION
1
+ RAILS_GEM_VERSION = '2.3.5' unless defined? RAILS_GEM_VERSION
2
2
 
3
3
  require File.join(File.dirname(__FILE__), 'boot')
4
4
 
@@ -0,0 +1,19 @@
1
+ class CreateAccessCodes < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :access_codes do |t|
4
+ t.string :code
5
+ t.integer :uses, :default => 0, :null => false
6
+ t.boolean :unlimited, :default => false, :null => false
7
+ t.datetime :expires_at
8
+ t.integer :use_limit, :default => 1, :null => false
9
+ t.timestamps
10
+ end
11
+ add_index :access_codes, :code
12
+ add_column :users, :access_code_id, :integer
13
+ end
14
+
15
+ def self.down
16
+ drop_table :access_codes
17
+ remove_column :users, :access_code_id
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ class CreateAccessCodeRequests < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :access_code_requests do |t|
4
+ t.string :email
5
+ t.datetime :code_sent_at
6
+ t.timestamps
7
+ end
8
+ add_index :access_code_requests, :email
9
+ end
10
+
11
+ def self.down
12
+ drop_table :access_code_requests
13
+ end
14
+ end
@@ -9,7 +9,28 @@
9
9
  #
10
10
  # It's strongly recommended to check this file into your version control system.
11
11
 
12
- ActiveRecord::Schema.define(:version => 20091118203605) do
12
+ ActiveRecord::Schema.define(:version => 20100123233654) do
13
+
14
+ create_table "access_code_requests", :force => true do |t|
15
+ t.string "email"
16
+ t.datetime "code_sent_at"
17
+ t.datetime "created_at"
18
+ t.datetime "updated_at"
19
+ end
20
+
21
+ add_index "access_code_requests", ["email"], :name => "index_access_code_requests_on_email"
22
+
23
+ create_table "access_codes", :force => true do |t|
24
+ t.string "code"
25
+ t.integer "uses", :default => 0, :null => false
26
+ t.boolean "unlimited", :default => false, :null => false
27
+ t.datetime "expires_at"
28
+ t.integer "use_limit", :default => 1, :null => false
29
+ t.datetime "created_at"
30
+ t.datetime "updated_at"
31
+ end
32
+
33
+ add_index "access_codes", ["code"], :name => "index_access_codes_on_code"
13
34
 
14
35
  create_table "activities", :force => true do |t|
15
36
  t.integer "item_id"
@@ -462,6 +483,7 @@ ActiveRecord::Schema.define(:version => 20091118203605) do
462
483
  t.datetime "updated_at"
463
484
  t.string "identity_url"
464
485
  t.string "url_key"
486
+ t.integer "access_code_id"
465
487
  end
466
488
 
467
489
  add_index "users", ["email"], :name => "index_users_on_email"
@@ -3,4 +3,9 @@ Factory.define :share do |f|
3
3
  f.title { Factory.next(:title) }
4
4
  f.shared_by {|a| a.association(:user)}
5
5
  f.entry {|a| a.association(:entry)}
6
- end
6
+ end
7
+
8
+ Factory.define :recommendation do |r|
9
+ r.entry {|a| a.association(:entry)}
10
+ r.dest_entry {|a| a.association(:entry)}
11
+ end
@@ -16,6 +16,7 @@ class ActiveSupport::TestCase
16
16
  TEST_URI = 'http://www.engadget.com'
17
17
  TEST_RSS_URI = 'http://www.engadget.com/rss.xml'
18
18
  TEST_USERNAME_TEMPLATE = 'http://feeds.delicious.com/v2/rss/{username}?count=100'
19
+ TEST_XML_URI = 'http://ocw.mit.edu/OcwWeb/rss/all/mit-allcourses.xml'
19
20
 
20
21
  end
21
22
 
@@ -37,6 +37,40 @@ class EntryTest < ActiveSupport::TestCase
37
37
  subject { @entry }
38
38
 
39
39
  should_belong_to :feed
40
+
41
+
42
+ context "recommender_entry" do
43
+ should "return an entry if the direct_link matches the specified uri" do
44
+ uri = Factory.next(:uri)
45
+ e = Factory.create(:entry, :direct_link => uri)
46
+ assert_equal e.id, Entry.recommender_entry(uri).id
47
+ end
48
+
49
+ should "return an entry if the permalink matches the specified uri" do
50
+ uri = Factory.next(:uri)
51
+ e = Factory.create(:entry, :permalink => uri)
52
+ assert_equal e.id, Entry.recommender_entry(uri).id
53
+ end
54
+
55
+ should "return an empty entry with the specified uri if the specified uri doesn't match" do
56
+ uri = Factory.next(:uri)
57
+ e = Factory.create(:entry, :permalink => uri)
58
+ assert_not_nil Entry.recommender_entry(uri)
59
+ assert_equal uri, Entry.recommender_entry(uri).permalink
60
+ end
61
+ end
62
+
63
+ context "resource_uri" do
64
+ should "return the permalink when no direct_link is specified" do
65
+ @entry.direct_link = nil
66
+ assert_equal @entry.permalink, @entry.resource_uri
67
+ end
68
+
69
+ should "return the direct_link when a direct_link is specified" do
70
+ @entry.direct_link = Factory.next(:uri)
71
+ assert_equal @entry.direct_link, @entry.resource_uri
72
+ end
73
+ end
40
74
 
41
75
  context "search" do
42
76
  # should "search indexes for ruby" do
@@ -48,7 +82,118 @@ class EntryTest < ActiveSupport::TestCase
48
82
  end
49
83
  end
50
84
  end
85
+
86
+ context "normalized_uri" do
87
+ should "remove the trailing file name from any url that ends in index.*" do
88
+ index_uri = 'http://example.com/some_dir/'
89
+ ['html','aspx','shtm','htm','asp','php','cfm','jsp','shtml','jhtml'].each { |ext|
90
+ assert_equal index_uri, Entry.normalized_uri(index_uri + 'index.' + ext)
91
+ }
92
+ end
93
+ end
94
+
95
+ context "recommendation requested omitting certain feeds" do
96
+ should "not contain entries from those feeds" do
97
+ feed1_id = Factory(:feed).id
98
+ feed2_id = Factory(:feed).id
99
+ assert_not_equal feed1_id, feed2_id
100
+ Recommendation.create(:entry_id => @entry.id, :dest_entry_id => Factory.create(:entry, :feed_id => feed1_id).id)
101
+ Recommendation.create(:entry_id => @entry.id, :dest_entry_id => Factory.create(:entry, :feed_id => feed2_id).id)
102
+ r = @entry.related_entries.top(false, 5, feed2_id.to_s)
103
+ assert_equal 1, r.length
104
+ r.each { |e| assert_not_equal feed2_id, e.feed_id}
105
+ end
106
+ end
107
+
108
+ context "with 3 recommendations" do
109
+
110
+ setup do
111
+ 3.times { |n| Recommendation.create(:entry_id => @entry.id,
112
+ :dest_entry_id => Factory(:entry).id, :relevance => n.to_f/10.0) }
113
+ end
114
+
115
+ should "have 3 related_entries" do
116
+ assert_equal 3, @entry.related_entries.top.length
117
+ end
118
+
119
+ should "return only 2 if specified" do
120
+ assert_equal 2, @entry.related_entries.top(true,2).length
121
+ end
122
+
123
+ should "return results in order of relevance by default" do
124
+ related_entries = @entry.related_entries.top
125
+ assert related_entries[1].relevance < related_entries[0].relevance
126
+ assert related_entries[2].relevance < related_entries[1].relevance
127
+ end
128
+
129
+ end
51
130
 
131
+ context "recommendation requested without details" do
132
+ should "not have an author" do
133
+ Recommendation.create(:entry_id => @entry.id,
134
+ :dest_entry_id => Factory.create(:entry, :author => Factory.next(:name)).id,
135
+ :relevance => '.3')
136
+ assert_raise ActiveRecord::MissingAttributeError do
137
+ @entry.related_entries.top.first.author
138
+ end
139
+ end
140
+
141
+ should "have basic fields" do
142
+ title = Factory.next(:name)
143
+ collection = Factory.next(:name)
144
+ feed_id = Factory(:feed, :short_title => collection).id
145
+ dest_entry_id = Factory.create(:entry, :title => title, :feed_id => feed_id).id
146
+ relevance = '.3'
147
+ recommendation_id = Recommendation.create(:entry_id => @entry.id, :relevance => relevance,
148
+ :dest_entry_id => dest_entry_id).id.to_s
149
+ r = @entry.related_entries.top(true).first
150
+ assert_nothing_raised do
151
+ assert_equal recommendation_id, r.recommendation_id
152
+ assert_equal relevance.to_f, r.relevance.to_f
153
+ assert_equal title, r.title
154
+ assert_equal collection, r.collection
155
+ assert_not_nil r.dest_entry_id
156
+ end
157
+ end
158
+ end
159
+
160
+ context "recommendation requested with details" do
161
+ should "have basic and detailed fields" do
162
+ title = Factory.next(:name)
163
+ collection = Factory.next(:name)
164
+ author = Factory.next(:name)
165
+ published_at = DateTime.now
166
+ clicks = 23
167
+ average_time_at_dest = 44
168
+ description = Factory.next(:name)
169
+ permalink = Factory.next(:uri)
170
+ direct_uri = Factory.next(:uri)
171
+ feed_id = Factory(:feed, :short_title => collection).id
172
+ relevance = '.3'
173
+ dest_entry_id = Factory.create(:entry, :feed_id => feed_id, :title => title,
174
+ :author => author, :published_at => published_at, :permalink => permalink,
175
+ :direct_link => direct_uri, :description => description).id
176
+ recommendation_id = Recommendation.create(:entry_id => @entry.id, :relevance => relevance,
177
+ :avg_time_at_dest => average_time_at_dest, :clicks => clicks, :dest_entry_id => dest_entry_id).id.to_s
178
+ r = @entry.related_entries.top(true).first
179
+ assert_nothing_raised do
180
+ assert_equal recommendation_id, r.recommendation_id
181
+ assert_equal relevance.to_f, r.relevance.to_f
182
+ assert_equal title, r.title
183
+ assert_equal collection, r.collection
184
+ assert_not_nil r.dest_entry_id
185
+
186
+ assert_equal author, r.author
187
+ # assert_equal published_at, r.published_at
188
+ assert_equal clicks, r.clicks.to_i
189
+ assert_equal permalink, r.permalink
190
+ assert_equal direct_uri, r.direct_uri
191
+ assert_equal average_time_at_dest, r.average_time_at_dest.to_i
192
+ assert_equal description, r.description
193
+ end
194
+ end
195
+ end
196
+
52
197
  end
53
198
 
54
199
  end
@@ -122,6 +122,9 @@ class FeedTest < ActiveSupport::TestCase
122
122
  should "Discover feeds from url" do
123
123
  Feed.discover_feeds(TEST_URI)
124
124
  end
125
+ should "Discover feeds from xml url" do
126
+ assert_equal TEST_XML_URI, Feed.discover_feeds(TEST_XML_URI)[0].url
127
+ end
125
128
  end
126
129
 
127
130
  context "Harvest feed" do
@@ -0,0 +1,29 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: recommendations
4
+ #
5
+ # id :integer(4) not null, primary key
6
+ # entry_id :integer(4)
7
+ # dest_entry_id :integer(4)
8
+ # rank :integer(4)
9
+ # relevance :decimal(8, 6) default(0.0)
10
+ # clicks :integer(4) default(0)
11
+ # avg_time_at_dest :integer(4) default(60)
12
+ #
13
+
14
+ require File.dirname(__FILE__) + '/../test_helper'
15
+
16
+ class RecommendationTest < ActiveSupport::TestCase
17
+
18
+ context "recommendation" do
19
+ setup do
20
+ @recommendation = Factory(:recommendation)
21
+ end
22
+
23
+ subject { @recommendation }
24
+
25
+ should_belong_to :entry
26
+ should_belong_to :dest_entry
27
+ end
28
+
29
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: muck-services
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.34
4
+ version: 0.1.35
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Duffin
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2010-01-25 00:00:00 -07:00
13
+ date: 2010-02-08 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -240,7 +240,7 @@ files:
240
240
  - app/views/parts/_add_feed.html.erb
241
241
  - app/views/parts/_select_feed.html.erb
242
242
  - app/views/recommendations/get_button.html.erb
243
- - app/views/recommendations/index.html.erb
243
+ - app/views/recommendations/index.js.erb
244
244
  - app/views/recommendations/index.pjs.erb
245
245
  - app/views/recommendations/index.rss.builder
246
246
  - app/views/recommendations/index.xml.builder
@@ -652,6 +652,8 @@ test_files:
652
652
  - test/rails_root/db/migrate/20091115011828_add_aggregations_for_personal_recs.rb
653
653
  - test/rails_root/db/migrate/20091116094447_rename_action_table.rb
654
654
  - test/rails_root/db/migrate/20091118203605_add_default_feed_type_to_aggregation_feed.rb
655
+ - test/rails_root/db/migrate/20100123035450_create_access_codes.rb
656
+ - test/rails_root/db/migrate/20100123233654_create_access_code_requests.rb
655
657
  - test/rails_root/db/schema.rb
656
658
  - test/rails_root/features/step_definitions/common_steps.rb
657
659
  - test/rails_root/features/step_definitions/visit_steps.rb
@@ -681,6 +683,7 @@ test_files:
681
683
  - test/rails_root/test/unit/identity_feed_test.rb
682
684
  - test/rails_root/test/unit/oai_endpoint_test.rb
683
685
  - test/rails_root/test/unit/personal_recommendation_test.rb
686
+ - test/rails_root/test/unit/recommendation_test.rb
684
687
  - test/rails_root/test/unit/service_category_test.rb
685
688
  - test/rails_root/test/unit/service_test.rb
686
689
  - test/rails_root/test/unit/services_mailer_test.rb
@@ -1,5 +0,0 @@
1
- <% if @recommendations.blank? || @recommendations.results.blank? -%>
2
- <%= t('muck.services.no_recommendations') %>
3
- <% else -%>
4
- <%= render :partial => 'entries/result', :collection => @recommendations.results -%>
5
- <% end -%>