Commit 85120639fc2e8390552315217b56db79c56a551f

Authored by Braulio Bhavamitra
Committed by Luciano Prestes
1 parent 8404c52d

open_graph: refactor to save activity independently of publication

plugins/open_graph/db/migrate/20150814200324_add_story_and_published_at_to_open_graph_plugin_activity.rb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +class AddStoryAndPublishedAtToOpenGraphPluginActivity < ActiveRecord::Migration
  2 +
  3 + def change
  4 + add_column :open_graph_plugin_tracks, :published_at, :datetime
  5 + add_column :open_graph_plugin_tracks, :story, :string
  6 + add_index :open_graph_plugin_tracks, :published_at
  7 + add_index :open_graph_plugin_tracks, :story
  8 + end
  9 +
  10 +end
... ...
plugins/open_graph/lib/open_graph_plugin.rb
... ... @@ -17,5 +17,9 @@ module OpenGraphPlugin
17 17 Thread.current[:open_graph_context] = value
18 18 end
19 19  
  20 + def self.debug? actor=nil
  21 + !Rails.env.production?
  22 + end
  23 +
20 24 end
21 25  
... ...
plugins/open_graph/lib/open_graph_plugin/publisher.rb
1 1  
2 2 class OpenGraphPlugin::Publisher
3 3  
4   - attr_accessor :actions
5   - attr_accessor :objects
6   -
7 4 def self.default
8 5 @default ||= self.new
9 6 end
10 7  
11 8 def initialize attributes = {}
12   - # defaults
13   - self.actions = OpenGraphPlugin::Stories::DefaultActions
14   - self.objects = OpenGraphPlugin::Stories::DefaultObjects
15   -
16 9 attributes.each do |attr, value|
17 10 self.send "#{attr}=", value
18 11 end
19 12 end
20 13  
21   - def publish actor, story_defs, object_data_url
22   - raise 'abstract method called'
23   - end
24   -
25 14 def publish_stories object_data, actor, stories
26 15 stories.each do |story|
27 16 begin
28 17 self.publish_story object_data, actor, story
29 18 rescue => e
  19 + raise unless Rails.env.production?
30 20 ExceptionNotifier.notify_exception e
31 21 end
32 22 end
33 23 end
34 24  
35   - def update_delay
36   - 1.day
37   - end
38   -
39   - # only publish recent objects to avoid multiple publications
40   - def recent_publish? actor, object_type, object_data_url
41   - activity_params = {actor_id: actor.id, object_type: object_type, object_data_url: object_data_url}
42   - activity = OpenGraphPlugin::Activity.where(activity_params).first
43   - activity.present? and activity.created_at <= self.update_delay.from_now
44   - end
45   -
46 25 def publish_story object_data, actor, story
47   - OpenGraphPlugin.context = self.context
48   - defs = OpenGraphPlugin::Stories::Definitions[story]
49   - passive = defs[:passive]
50   -
51   - print_debug "open_graph: publish_story #{story}" if debug? actor
52   - match_criteria = if (ret = self.call defs[:criteria], object_data, actor).nil? then true else ret end
53   - return unless match_criteria
54   - print_debug "open_graph: #{story} match criteria" if debug? actor
55   - match_condition = if (ret = self.call defs[:publish_if], object_data, actor).nil? then true else ret end
56   - return unless match_condition
57   - print_debug "open_graph: #{story} match publish_if" if debug? actor
58   -
59   - actors = self.story_trackers defs, actor, object_data
60   - return if actors.blank?
61   - print_debug "open_graph: #{story} has enabled trackers" if debug? actor
62   -
63   - if publish = defs[:publish]
64   - begin
65   - instance_exec actor, object_data, &publish
66   - rescue => e
67   - print_debug "open_graph: can't publish story: #{e.message}" if debug? actor
68   - ExceptionNotifier.notify_exception e
69   - end
70   - else
71   - # force profile identifier for custom domains and fixed host. see og_url_for
72   - object_profile = self.call(story_defs[:object_profile], object_data) || object_data.profile rescue nil
73   - extra_params = if object_profile then {profile: object_profile.identifier} else {} end
74   -
75   - custom_object_data_url = self.call defs[:object_data_url], object_data, actor
76   - object_data_url = if passive then self.passive_url_for object_data, custom_object_data_url, defs, extra_params else self.url_for object_data, custom_object_data_url, extra_params end
77   -
78   - actors.each do |actor|
79   - print_debug "open_graph: start publishing" if debug? actor
80   - begin
81   - self.publish actor, defs, object_data_url
82   - rescue => e
83   - print_debug "open_graph: can't publish story: #{e.message}" if debug? actor
84   - ExceptionNotifier.notify_exception e
85   - end
86   - end
87   - end
88   - end
89   -
90   - def story_trackers story_defs, actor, object_data
91   - passive = story_defs[:passive]
92   - trackers = []
93   -
94   - track_configs = Array(story_defs[:track_config]).compact.map(&:constantize)
95   - return if track_configs.empty?
96   - print_debug "open_graph: using configs: #{track_configs.map(&:name).inspect}" if debug? actor
97   -
98   - if passive
99   - object_profile = self.call(story_defs[:object_profile], object_data) || object_data.profile rescue nil
100   - return unless object_profile
101   -
102   - track_configs.each do |c|
103   - trackers.concat c.trackers_to_profile(object_profile)
104   - end.flatten
105   -
106   - trackers.select! do |t|
107   - track_configs.any?{ |c| c.enabled? self.context, t }
108   - end
109   - else #active
110   - object_actor = self.call(story_defs[:object_actor], object_data) || object_data.profile rescue nil
111   - return unless object_actor and object_actor.person?
112   - custom_actor = self.call(story_defs[:custom_actor], object_data)
113   - actor = custom_actor if custom_actor
114   -
115   - match_track = track_configs.any? do |c|
116   - c.enabled?(self.context, actor) and
117   - actor.send("open_graph_#{c.track_name}_track_configs").where(object_type: story_defs[:object_type]).first
118   - end
119   - trackers << actor if match_track
120   - end
121   -
122   - trackers
  26 + OpenGraphPlugin.context = OpenGraphPlugin::Activity.context
  27 + a = OpenGraphPlugin::Activity.new object_data: object_data, actor: actor, story: story
  28 + a.dispatch_publications
  29 + a.save
123 30 end
124 31  
125 32 protected
126 33  
127   - include MetadataPlugin::UrlHelper
128   -
129   - def register_publish attributes
130   - OpenGraphPlugin::Activity.create! attributes
131   - end
132   -
133   - # Call don't ask: move to a og_url method inside object
134   - def url_for object, custom_url=nil, extra_params={}
135   - return custom_url if custom_url.is_a? String
136   - url = custom_url || if object.is_a? Profile then og_profile_url object else object.url end
137   - # for profile when custom domain is used
138   - url.merge! profile: object.profile.identifier if object.respond_to? :profile
139   - url.merge! extra_params
140   - self.og_url_for url
141   - end
142   -
143   - def passive_url_for object, custom_url, story_defs, extra_params={}
144   - object_type = story_defs[:object_type]
145   - extra_params.merge! og_type: MetadataPlugin.og_types[object_type]
146   - self.url_for object, custom_url, extra_params
147   - end
148   -
149   - def call p, *args
150   - p and instance_exec *args, &p
151   - end
152   -
153   - def context
154   - :open_graph
155   - end
156   -
157   - def print_debug msg
158   - puts msg
159   - Delayed::Worker.logger.debug msg
160   - end
161   - def debug? actor=nil
162   - !Rails.env.production?
163   - end
164   -
165 34 end
166 35  
... ...
plugins/open_graph/lib/open_graph_plugin/stories.rb
... ... @@ -99,13 +99,6 @@ class OpenGraphPlugin::Stories
99 99 publish_if: proc do |article, actor|
100 100 article.published?
101 101 end,
102   - object_data_url: proc do |article, actor|
103   - url = article.url
104   - if og_type = MetadataPlugin::og_types[:forum]
105   - url[:og_type] = og_type
106   - end
107   - url
108   - end,
109 102 },
110 103  
111 104 # these a published as passive to give focus to the enterprise
... ...
plugins/open_graph/lib/open_graph_plugin/url_helper.rb 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +module OpenGraphPlugin::UrlHelper
  2 +
  3 + protected
  4 +
  5 + include MetadataPlugin::UrlHelper
  6 +
  7 + # Call don't ask: move to a og_url method inside object
  8 + def url_for object, custom_url=nil, extra_params={}
  9 + return custom_url if custom_url.is_a? String
  10 + url = custom_url || if object.is_a? Profile then og_profile_url object else object.url end
  11 + # for profile when custom domain is used
  12 + url.merge! profile: object.profile.identifier if object.respond_to? :profile
  13 + url.merge! extra_params
  14 + self.og_url_for url
  15 + end
  16 +
  17 + def passive_url_for object, custom_url, story_defs, extra_params={}
  18 + object_type = story_defs[:object_type]
  19 + og_type = MetadataPlugin.og_types[object_type]
  20 + extra_params.merge! og_type: og_type if og_type.present?
  21 + self.url_for object, custom_url, extra_params
  22 + end
  23 +
  24 +end
... ...
plugins/open_graph/models/open_graph_plugin/activity.rb
1 1 # This is a log of activities, unlike ActivityTrack that is a configuration
2 2 class OpenGraphPlugin::Activity < OpenGraphPlugin::Track
3 3  
  4 + Defs = OpenGraphPlugin::Stories::Definitions
  5 +
  6 + UpdateDelay = 1.day
  7 +
  8 + class_attribute :actions, :objects
  9 + self.actions = OpenGraphPlugin::Stories::DefaultActions
  10 + self.objects = OpenGraphPlugin::Stories::DefaultObjects
  11 +
  12 + validates_presence_of :action
  13 + validates_presence_of :object_type
  14 +
4 15 # subclass this to define (e.g. FbAppPlugin::Activity)
5 16 def scrape
  17 + raise NotImplementedError
  18 + end
  19 + def publish! actor = self.actor
  20 + self.published_at = Time.now
  21 + print_debug "open_graph: published with success" if debug? actor
  22 + end
  23 +
  24 + def defs
  25 + @defs ||= Defs[self.story.to_sym]
  26 + end
  27 + def object_profile
  28 + @object_profile ||= self.call(self.defs[:object_profile], self.object_data) || self.object_data.profile rescue nil
  29 + end
  30 + def track_configs
  31 + @track_configs ||= Array(self.defs[:track_config]).compact.map(&:constantize)
  32 + end
  33 + def match_criteria?
  34 + if (ret = self.call self.defs[:criteria], self.object_data, self.actor).nil? then true else ret end
  35 + end
  36 + def match_publish_if?
  37 + if (ret = self.call self.defs[:publish_if], self.object_data, self.actor).nil? then true else ret end
  38 + end
  39 + def custom_object_data_url
  40 + @custom_object_data_url ||= self.call defs[:object_data_url], self.object_data, self.actor
  41 + end
  42 + def object_actor
  43 + @object_actor ||= self.call(self.defs[:object_actor], self.object_data) || self.object_data.profile rescue nil
  44 + end
  45 + def custom_actor
  46 + @custom_actor ||= self.call self.defs[:custom_actor], self.object_data
  47 + end
  48 +
  49 + def set_object_data_url
  50 + # force profile identifier for custom domains and fixed host. see og_url_for
  51 + extra_params = if self.object_profile then {profile: self.object_profile.identifier} else {} end
  52 +
  53 + self.object_data_url = if self.defs[:passive] then self.passive_url_for self.object_data, self.custom_object_data_url, self.defs, extra_params else self.url_for self.object_data, self.custom_object_data_url, extra_params end
  54 + end
  55 +
  56 + def dispatch_publications
  57 + print_debug "open_graph: dispatch_publications of #{story}" if debug? self.actor
  58 +
  59 + return unless self.match_criteria?
  60 + print_debug "open_graph: #{story} match criteria" if debug? self.actor
  61 + return unless self.match_publish_if?
  62 + print_debug "open_graph: #{story} match publish_if" if debug? self.actor
  63 + return unless (actors = self.trackers).present?
  64 + print_debug "open_graph: #{story} has enabled trackers" if debug? self.actor
  65 +
  66 + self.set_object_data_url
  67 + self.action = self.class.actions[self.defs[:action]]
  68 + self.object_type = self.class.objects[self.defs[:object_type]]
  69 +
  70 + print_debug "open_graph: start publishing" if debug? actor
  71 + unless (publish = self.defs[:publish]).present?
  72 + actors.each do |actor|
  73 + begin
  74 + self.publish! actor
  75 + rescue => e
  76 + print_debug "open_graph: can't publish story: #{e.message}" if debug? actor
  77 + raise unless Rails.env.production?
  78 + ExceptionNotifier.notify_exception e
  79 + end
  80 + end
  81 + else # custom publish proc
  82 + begin
  83 + instance_exec self.actor, self.object_data, &publish
  84 + rescue => e
  85 + print_debug "open_graph: can't publish story: #{e.message}" if debug? self.actor
  86 + raise unless Rails.env.production?
  87 + ExceptionNotifier.notify_exception e
  88 + end
  89 + end
  90 + end
  91 +
  92 + def trackers
  93 + @trackers ||= begin
  94 + return if self.track_configs.empty?
  95 + trackers = []
  96 +
  97 + print_debug "open_graph: using configs: #{self.track_configs.map(&:name).inspect}" if debug? self.actor
  98 +
  99 + if self.defs[:passive]
  100 + return unless self.object_profile
  101 +
  102 + self.track_configs.each do |c|
  103 + trackers.concat c.trackers_to_profile(self.object_profile)
  104 + end.flatten
  105 +
  106 + trackers.select! do |t|
  107 + self.track_configs.any?{ |c| c.enabled? self.context, t }
  108 + end
  109 + else #active
  110 + return unless self.object_actor and self.object_actor.person?
  111 + actor = self.custom_actor || self.actor
  112 +
  113 + match_track = self.track_configs.any? do |c|
  114 + c.enabled?(self.context, actor) and
  115 + actor.send("open_graph_#{c.track_name}_track_configs").where(object_type: self.defs[:object_type]).first
  116 + end
  117 + trackers << actor if match_track
  118 + end
  119 +
  120 + trackers
  121 + end
  122 + end
  123 +
  124 + protected
  125 +
  126 + include OpenGraphPlugin::UrlHelper
  127 +
  128 + def update_delay
  129 + UpdateDelay
  130 + end
  131 +
  132 + # only publish recent objects to avoid multiple publications
  133 + def recent_publish? actor, object_type, object_data_url
  134 + activity_params = {actor_id: actor.id, object_type: object_type, object_data_url: object_data_url}
  135 + activity = OpenGraphPlugin::Activity.where(activity_params).first
  136 + activity.present? and activity.created_at <= self.update_delay.from_now
  137 + end
  138 +
  139 + def call p, *args
  140 + p and instance_exec *args, &p
6 141 end
7 142  
8 143 end
... ...
plugins/open_graph/models/open_graph_plugin/track.rb
1 1 class OpenGraphPlugin::Track < ActiveRecord::Base
2 2  
  3 + class_attribute :context
  4 + self.context = :open_graph
  5 +
3 6 attr_accessible :type, :context, :tracker_id, :tracker, :actor_id, :action,
4   - :object_type, :object_data, :object_data_id, :object_data_type, :object_data_url
  7 + :object_type, :object_data_id, :object_data_type, :object_data_url,
  8 + :story, :object_data, :actor
5 9  
6 10 belongs_to :tracker, class_name: 'Profile'
7 11 belongs_to :actor, class_name: 'Profile'
8 12 belongs_to :object_data, polymorphic: true
9 13  
10   - validates_presence_of :context
11 14 before_validation :set_context
12 15  
13 16 def self.objects
... ... @@ -21,7 +24,15 @@ class OpenGraphPlugin::Track &lt; ActiveRecord::Base
21 24 protected
22 25  
23 26 def set_context
24   - self.context = OpenGraphPlugin.context
  27 + self[:context] = self.class.context
  28 + end
  29 +
  30 + def print_debug msg
  31 + puts msg
  32 + Delayed::Worker.logger.debug msg
  33 + end
  34 + def debug? actor=nil
  35 + OpenGraphPlugin.debug? actor
25 36 end
26 37  
27 38 end
... ...
plugins/open_graph/plugin.yml
... ... @@ -1,3 +0,0 @@
1   -name: open_graph
2   -dependencies:
3   - - metadata
plugins/open_graph/test/unit/open_graph_graph/publisher_test.rb
... ... @@ -2,14 +2,16 @@ require &quot;test_helper&quot;
2 2  
3 3 class OpenGraphPlugin::PublisherTest < ActiveSupport::TestCase
4 4  
  5 + include OpenGraphPlugin::UrlHelper
  6 +
5 7 def setup
6 8 @actor = create_user.person
7 9 User.current = @actor.user
8   - @stories = OpenGraphPlugin::Stories::Definitions
9 10 @publisher = OpenGraphPlugin::Publisher.new
10 11 OpenGraphPlugin::Stories.stubs(:publishers).returns([@publisher])
11   - @publisher.stubs(:context).returns(:open_graph)
12   - @publisher.stubs(:og_domain).returns('noosfero.net')
  12 + # for MetadataPlugin::UrlHelper#og_url_for
  13 + stubs(:og_domain).returns('noosfero.net')
  14 + OpenGraphPlugin::Activity.any_instance.stubs(:og_domain).returns('noosfero.net')
13 15 end
14 16  
15 17 should "publish only tracked stuff" do
... ... @@ -46,66 +48,70 @@ class OpenGraphPlugin::PublisherTest &lt; ActiveSupport::TestCase
46 48  
47 49 # active
48 50 User.current = @actor.user
  51 + user = User.current.person
  52 +
  53 + blog = Blog.create! profile: user, name: 'blog'
  54 + blog_post = TinyMceArticle.create! profile: user, parent: blog, name: 'blah', author: user
  55 + assert_last_activity user, :create_an_article, url_for(blog_post)
  56 +
  57 + gallery = Gallery.create! name: 'gallery', profile: user
  58 + image = UploadedFile.create! uploaded_data: fixture_file_upload('/files/rails.png', 'image/png'), parent: gallery, profile: user
  59 + assert_last_activity user, :add_an_image, url_for(image, image.url.merge(view: true))
  60 +
  61 + document = UploadedFile.create! uploaded_data: fixture_file_upload('/files/doctest.en.xhtml', 'text/html'), profile: user
  62 + assert_last_activity user, :add_a_document, url_for(document, document.url.merge(view: true))
  63 +
  64 + event = Event.create! name: 'event', profile: user
  65 + assert_last_activity user, :create_an_event, url_for(event)
  66 +
  67 + forum = Forum.create! name: 'forum', profile: user
  68 + topic = TinyMceArticle.create! profile: user, parent: forum, name: 'blah2', author: user
  69 + assert_last_activity user, :start_a_discussion, url_for(topic, topic.url.merge(og_type: MetadataPlugin.og_types[:forum]))
49 70  
50   - blog = Blog.create! profile: @actor, name: 'blog'
51   - blog_post = TinyMceArticle.new profile: User.current.person, parent: blog, name: 'blah', author: User.current.person
52   - @publisher.expects(:publish).with(User.current.person, @stories[:create_an_article], @publisher.send(:url_for, blog_post))
53   - blog_post.save!
54   -
55   - gallery = Gallery.create! name: 'gallery', profile: User.current.person
56   - image = UploadedFile.new uploaded_data: fixture_file_upload('/files/rails.png', 'image/png'), parent: gallery, profile: User.current.person
57   - @publisher.expects(:publish).with(User.current.person, @stories[:add_an_image], @publisher.send(:url_for, image, image.url.merge(view: true)))
58   - image.save!
59   -
60   - document = UploadedFile.new uploaded_data: fixture_file_upload('/files/doctest.en.xhtml', 'text/html'), profile: User.current.person
61   - @publisher.expects(:publish).with(User.current.person, @stories[:add_a_document], @publisher.send(:url_for, document, document.url.merge(view: true)))
62   - document.save!
63   -
64   - event = Event.new name: 'event', profile: User.current.person
65   - @publisher.expects(:publish).with(User.current.person, @stories[:create_an_event], @publisher.send(:url_for, event))
66   - event.save!
67   -
68   - forum = Forum.create! name: 'forum', profile: User.current.person
69   - topic = TinyMceArticle.new profile: User.current.person, parent: forum, name: 'blah2', author: User.current.person
70   - @publisher.expects(:publish).with(User.current.person, @stories[:start_a_discussion], @publisher.send(:url_for, topic, topic.url.merge(og_type: MetadataPlugin.og_types[:forum])))
71   - topic.save!
72   -
73   - @publisher.expects(:publish).with(@actor, @stories[:make_friendship_with], @publisher.send(:url_for, @other_actor)).twice
74   - @publisher.expects(:publish).with(@other_actor, @stories[:make_friendship_with], @publisher.send(:url_for, @actor)).twice
75   - AddFriend.create!(person: @actor, friend: @other_actor).finish
76   - Friendship.remove_friendship @actor, @other_actor
  71 + AddFriend.create!(person: user, friend: @other_actor).finish
  72 + #assert_last_activity user, :make_friendship_with, url_for(@other_actor)
  73 + Friendship.remove_friendship user, @other_actor
77 74 # friend verb is groupable
78   - AddFriend.create!(person: @actor, friend: @other_actor).finish
  75 + AddFriend.create!(person: user, friend: @other_actor).finish
  76 + #assert_last_activity @other_actor, :make_friendship_with, url_for(user)
79 77  
80   - @publisher.expects(:publish).with(User.current.person, @stories[:favorite_a_sse_initiative], @publisher.send(:url_for, @enterprise))
81   - @enterprise.fans << User.current.person
  78 + @enterprise.fans << user
  79 + assert_last_activity user, :favorite_a_sse_initiative, url_for(@enterprise)
82 80  
83 81 # active but published as passive
84 82 User.current = @actor.user
  83 + user = User.current.person
85 84  
86   - blog_post = TinyMceArticle.new profile: @enterprise, parent: @enterprise.blog, name: 'blah', author: User.current.person
87   - story = @stories[:announce_news_from_a_sse_initiative]
88   - @publisher.expects(:publish).with(User.current.person, story, @publisher.send(:passive_url_for, blog_post, nil, story))
89   - blog_post.save!
  85 + blog_post = TinyMceArticle.create! profile: @enterprise, parent: @enterprise.blog, name: 'blah', author: user
  86 + story = :announce_news_from_a_sse_initiative
  87 + assert_last_activity user, story, passive_url_for(blog_post, nil, OpenGraphPlugin::Stories::Definitions[story])
90 88  
91 89 # passive
92 90 User.current = @other_actor.user
  91 + user = User.current.person
93 92  
94 93 # fan
95   - blog_post = TinyMceArticle.new profile: @enterprise, parent: @enterprise.blog, name: 'blah2', author: User.current.person
96   - story = @stories[:announce_news_from_a_sse_initiative]
97   - @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/coop/blog/blah2')
98   - blog_post.save!
  94 + blog_post = TinyMceArticle.create! profile: @enterprise, parent: @enterprise.blog, name: 'blah2', author: user
  95 + assert_last_activity user, :announce_news_from_a_sse_initiative, 'http://noosfero.net/coop/blog/blah2'
99 96 # member
100   - blog_post = TinyMceArticle.new profile: @myenterprise, parent: @myenterprise.blog, name: 'blah2', author: User.current.person
101   - story = @stories[:announce_news_from_a_sse_initiative]
102   - @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/mycoop/blog/blah2')
103   - blog_post.save!
104   -
105   - blog_post = TinyMceArticle.new profile: @community, parent: @community.blog, name: 'blah', author: User.current.person
106   - story = @stories[:announce_news_from_a_community]
107   - @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/comm/blog/blah')
108   - blog_post.save!
  97 + blog_post = TinyMceArticle.create! profile: @myenterprise, parent: @myenterprise.blog, name: 'blah2', author: user
  98 + assert_last_activity user, :announce_news_from_a_sse_initiative, 'http://noosfero.net/mycoop/blog/blah2'
  99 +
  100 + blog_post = TinyMceArticle.create! profile: @community, parent: @community.blog, name: 'blah', author: user
  101 + assert_last_activity user, :announce_news_from_a_community, 'http://noosfero.net/comm/blog/blah'
  102 + end
  103 +
  104 + protected
  105 +
  106 + def assert_activity activity, actor, story, object_data_url
  107 + assert_equal actor, activity.actor, actor
  108 + assert_equal story.to_s, activity.story
  109 + assert_equal object_data_url, activity.object_data_url
  110 + end
  111 +
  112 + def assert_last_activity actor, story, object_data_url
  113 + a = OpenGraphPlugin::Activity.order('id DESC').first
  114 + assert_activity a, actor, story, object_data_url
109 115 end
110 116  
111 117 end
... ...