muck-services 0.1.34 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 -%>