From 3fdbbc295e8cfb7a137c2c12c4091a81930ee479 Mon Sep 17 00:00:00 2001 From: Braulio Bhavamitra Date: Sat, 15 Aug 2015 21:11:15 -0300 Subject: [PATCH] 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 | 10 ++++++++++ plugins/open_graph/lib/open_graph_plugin.rb | 4 ++++ plugins/open_graph/lib/open_graph_plugin/publisher.rb | 141 +++++---------------------------------------------------------------------------------------------------------------------------------------- plugins/open_graph/lib/open_graph_plugin/stories.rb | 7 ------- plugins/open_graph/lib/open_graph_plugin/url_helper.rb | 24 ++++++++++++++++++++++++ plugins/open_graph/models/open_graph_plugin/activity.rb | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ plugins/open_graph/models/open_graph_plugin/track.rb | 17 ++++++++++++++--- plugins/open_graph/plugin.yml | 3 --- plugins/open_graph/test/unit/open_graph_graph/publisher_test.rb | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------- 9 files changed, 248 insertions(+), 199 deletions(-) create mode 100644 plugins/open_graph/db/migrate/20150814200324_add_story_and_published_at_to_open_graph_plugin_activity.rb create mode 100644 plugins/open_graph/lib/open_graph_plugin/url_helper.rb delete mode 100644 plugins/open_graph/plugin.yml diff --git a/plugins/open_graph/db/migrate/20150814200324_add_story_and_published_at_to_open_graph_plugin_activity.rb b/plugins/open_graph/db/migrate/20150814200324_add_story_and_published_at_to_open_graph_plugin_activity.rb new file mode 100644 index 0000000..4b18741 --- /dev/null +++ b/plugins/open_graph/db/migrate/20150814200324_add_story_and_published_at_to_open_graph_plugin_activity.rb @@ -0,0 +1,10 @@ +class AddStoryAndPublishedAtToOpenGraphPluginActivity < ActiveRecord::Migration + + def change + add_column :open_graph_plugin_tracks, :published_at, :datetime + add_column :open_graph_plugin_tracks, :story, :string + add_index :open_graph_plugin_tracks, :published_at + add_index :open_graph_plugin_tracks, :story + end + +end diff --git a/plugins/open_graph/lib/open_graph_plugin.rb b/plugins/open_graph/lib/open_graph_plugin.rb index 7c239f1..04e6197 100644 --- a/plugins/open_graph/lib/open_graph_plugin.rb +++ b/plugins/open_graph/lib/open_graph_plugin.rb @@ -17,5 +17,9 @@ module OpenGraphPlugin Thread.current[:open_graph_context] = value end + def self.debug? actor=nil + !Rails.env.production? + end + end diff --git a/plugins/open_graph/lib/open_graph_plugin/publisher.rb b/plugins/open_graph/lib/open_graph_plugin/publisher.rb index ebf19f1..1eac32f 100644 --- a/plugins/open_graph/lib/open_graph_plugin/publisher.rb +++ b/plugins/open_graph/lib/open_graph_plugin/publisher.rb @@ -1,166 +1,35 @@ class OpenGraphPlugin::Publisher - attr_accessor :actions - attr_accessor :objects - def self.default @default ||= self.new end def initialize attributes = {} - # defaults - self.actions = OpenGraphPlugin::Stories::DefaultActions - self.objects = OpenGraphPlugin::Stories::DefaultObjects - attributes.each do |attr, value| self.send "#{attr}=", value end end - def publish actor, story_defs, object_data_url - raise 'abstract method called' - end - def publish_stories object_data, actor, stories stories.each do |story| begin self.publish_story object_data, actor, story rescue => e + raise unless Rails.env.production? ExceptionNotifier.notify_exception e end end end - def update_delay - 1.day - end - - # only publish recent objects to avoid multiple publications - def recent_publish? actor, object_type, object_data_url - activity_params = {actor_id: actor.id, object_type: object_type, object_data_url: object_data_url} - activity = OpenGraphPlugin::Activity.where(activity_params).first - activity.present? and activity.created_at <= self.update_delay.from_now - end - def publish_story object_data, actor, story - OpenGraphPlugin.context = self.context - defs = OpenGraphPlugin::Stories::Definitions[story] - passive = defs[:passive] - - print_debug "open_graph: publish_story #{story}" if debug? actor - match_criteria = if (ret = self.call defs[:criteria], object_data, actor).nil? then true else ret end - return unless match_criteria - print_debug "open_graph: #{story} match criteria" if debug? actor - match_condition = if (ret = self.call defs[:publish_if], object_data, actor).nil? then true else ret end - return unless match_condition - print_debug "open_graph: #{story} match publish_if" if debug? actor - - actors = self.story_trackers defs, actor, object_data - return if actors.blank? - print_debug "open_graph: #{story} has enabled trackers" if debug? actor - - if publish = defs[:publish] - begin - instance_exec actor, object_data, &publish - rescue => e - print_debug "open_graph: can't publish story: #{e.message}" if debug? actor - ExceptionNotifier.notify_exception e - end - else - # force profile identifier for custom domains and fixed host. see og_url_for - object_profile = self.call(story_defs[:object_profile], object_data) || object_data.profile rescue nil - extra_params = if object_profile then {profile: object_profile.identifier} else {} end - - custom_object_data_url = self.call defs[:object_data_url], object_data, actor - 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 - - actors.each do |actor| - print_debug "open_graph: start publishing" if debug? actor - begin - self.publish actor, defs, object_data_url - rescue => e - print_debug "open_graph: can't publish story: #{e.message}" if debug? actor - ExceptionNotifier.notify_exception e - end - end - end - end - - def story_trackers story_defs, actor, object_data - passive = story_defs[:passive] - trackers = [] - - track_configs = Array(story_defs[:track_config]).compact.map(&:constantize) - return if track_configs.empty? - print_debug "open_graph: using configs: #{track_configs.map(&:name).inspect}" if debug? actor - - if passive - object_profile = self.call(story_defs[:object_profile], object_data) || object_data.profile rescue nil - return unless object_profile - - track_configs.each do |c| - trackers.concat c.trackers_to_profile(object_profile) - end.flatten - - trackers.select! do |t| - track_configs.any?{ |c| c.enabled? self.context, t } - end - else #active - object_actor = self.call(story_defs[:object_actor], object_data) || object_data.profile rescue nil - return unless object_actor and object_actor.person? - custom_actor = self.call(story_defs[:custom_actor], object_data) - actor = custom_actor if custom_actor - - match_track = track_configs.any? do |c| - c.enabled?(self.context, actor) and - actor.send("open_graph_#{c.track_name}_track_configs").where(object_type: story_defs[:object_type]).first - end - trackers << actor if match_track - end - - trackers + OpenGraphPlugin.context = OpenGraphPlugin::Activity.context + a = OpenGraphPlugin::Activity.new object_data: object_data, actor: actor, story: story + a.dispatch_publications + a.save end protected - include MetadataPlugin::UrlHelper - - def register_publish attributes - OpenGraphPlugin::Activity.create! attributes - end - - # Call don't ask: move to a og_url method inside object - def url_for object, custom_url=nil, extra_params={} - return custom_url if custom_url.is_a? String - url = custom_url || if object.is_a? Profile then og_profile_url object else object.url end - # for profile when custom domain is used - url.merge! profile: object.profile.identifier if object.respond_to? :profile - url.merge! extra_params - self.og_url_for url - end - - def passive_url_for object, custom_url, story_defs, extra_params={} - object_type = story_defs[:object_type] - extra_params.merge! og_type: MetadataPlugin.og_types[object_type] - self.url_for object, custom_url, extra_params - end - - def call p, *args - p and instance_exec *args, &p - end - - def context - :open_graph - end - - def print_debug msg - puts msg - Delayed::Worker.logger.debug msg - end - def debug? actor=nil - !Rails.env.production? - end - end diff --git a/plugins/open_graph/lib/open_graph_plugin/stories.rb b/plugins/open_graph/lib/open_graph_plugin/stories.rb index 27633d3..d9dd9aa 100644 --- a/plugins/open_graph/lib/open_graph_plugin/stories.rb +++ b/plugins/open_graph/lib/open_graph_plugin/stories.rb @@ -99,13 +99,6 @@ class OpenGraphPlugin::Stories publish_if: proc do |article, actor| article.published? end, - object_data_url: proc do |article, actor| - url = article.url - if og_type = MetadataPlugin::og_types[:forum] - url[:og_type] = og_type - end - url - end, }, # these a published as passive to give focus to the enterprise diff --git a/plugins/open_graph/lib/open_graph_plugin/url_helper.rb b/plugins/open_graph/lib/open_graph_plugin/url_helper.rb new file mode 100644 index 0000000..16b6fb4 --- /dev/null +++ b/plugins/open_graph/lib/open_graph_plugin/url_helper.rb @@ -0,0 +1,24 @@ +module OpenGraphPlugin::UrlHelper + + protected + + include MetadataPlugin::UrlHelper + + # Call don't ask: move to a og_url method inside object + def url_for object, custom_url=nil, extra_params={} + return custom_url if custom_url.is_a? String + url = custom_url || if object.is_a? Profile then og_profile_url object else object.url end + # for profile when custom domain is used + url.merge! profile: object.profile.identifier if object.respond_to? :profile + url.merge! extra_params + self.og_url_for url + end + + def passive_url_for object, custom_url, story_defs, extra_params={} + object_type = story_defs[:object_type] + og_type = MetadataPlugin.og_types[object_type] + extra_params.merge! og_type: og_type if og_type.present? + self.url_for object, custom_url, extra_params + end + +end diff --git a/plugins/open_graph/models/open_graph_plugin/activity.rb b/plugins/open_graph/models/open_graph_plugin/activity.rb index c311c0f..a3ec84e 100644 --- a/plugins/open_graph/models/open_graph_plugin/activity.rb +++ b/plugins/open_graph/models/open_graph_plugin/activity.rb @@ -1,8 +1,143 @@ # This is a log of activities, unlike ActivityTrack that is a configuration class OpenGraphPlugin::Activity < OpenGraphPlugin::Track + Defs = OpenGraphPlugin::Stories::Definitions + + UpdateDelay = 1.day + + class_attribute :actions, :objects + self.actions = OpenGraphPlugin::Stories::DefaultActions + self.objects = OpenGraphPlugin::Stories::DefaultObjects + + validates_presence_of :action + validates_presence_of :object_type + # subclass this to define (e.g. FbAppPlugin::Activity) def scrape + raise NotImplementedError + end + def publish! actor = self.actor + self.published_at = Time.now + print_debug "open_graph: published with success" if debug? actor + end + + def defs + @defs ||= Defs[self.story.to_sym] + end + def object_profile + @object_profile ||= self.call(self.defs[:object_profile], self.object_data) || self.object_data.profile rescue nil + end + def track_configs + @track_configs ||= Array(self.defs[:track_config]).compact.map(&:constantize) + end + def match_criteria? + if (ret = self.call self.defs[:criteria], self.object_data, self.actor).nil? then true else ret end + end + def match_publish_if? + if (ret = self.call self.defs[:publish_if], self.object_data, self.actor).nil? then true else ret end + end + def custom_object_data_url + @custom_object_data_url ||= self.call defs[:object_data_url], self.object_data, self.actor + end + def object_actor + @object_actor ||= self.call(self.defs[:object_actor], self.object_data) || self.object_data.profile rescue nil + end + def custom_actor + @custom_actor ||= self.call self.defs[:custom_actor], self.object_data + end + + def set_object_data_url + # force profile identifier for custom domains and fixed host. see og_url_for + extra_params = if self.object_profile then {profile: self.object_profile.identifier} else {} end + + 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 + end + + def dispatch_publications + print_debug "open_graph: dispatch_publications of #{story}" if debug? self.actor + + return unless self.match_criteria? + print_debug "open_graph: #{story} match criteria" if debug? self.actor + return unless self.match_publish_if? + print_debug "open_graph: #{story} match publish_if" if debug? self.actor + return unless (actors = self.trackers).present? + print_debug "open_graph: #{story} has enabled trackers" if debug? self.actor + + self.set_object_data_url + self.action = self.class.actions[self.defs[:action]] + self.object_type = self.class.objects[self.defs[:object_type]] + + print_debug "open_graph: start publishing" if debug? actor + unless (publish = self.defs[:publish]).present? + actors.each do |actor| + begin + self.publish! actor + rescue => e + print_debug "open_graph: can't publish story: #{e.message}" if debug? actor + raise unless Rails.env.production? + ExceptionNotifier.notify_exception e + end + end + else # custom publish proc + begin + instance_exec self.actor, self.object_data, &publish + rescue => e + print_debug "open_graph: can't publish story: #{e.message}" if debug? self.actor + raise unless Rails.env.production? + ExceptionNotifier.notify_exception e + end + end + end + + def trackers + @trackers ||= begin + return if self.track_configs.empty? + trackers = [] + + print_debug "open_graph: using configs: #{self.track_configs.map(&:name).inspect}" if debug? self.actor + + if self.defs[:passive] + return unless self.object_profile + + self.track_configs.each do |c| + trackers.concat c.trackers_to_profile(self.object_profile) + end.flatten + + trackers.select! do |t| + self.track_configs.any?{ |c| c.enabled? self.context, t } + end + else #active + return unless self.object_actor and self.object_actor.person? + actor = self.custom_actor || self.actor + + match_track = self.track_configs.any? do |c| + c.enabled?(self.context, actor) and + actor.send("open_graph_#{c.track_name}_track_configs").where(object_type: self.defs[:object_type]).first + end + trackers << actor if match_track + end + + trackers + end + end + + protected + + include OpenGraphPlugin::UrlHelper + + def update_delay + UpdateDelay + end + + # only publish recent objects to avoid multiple publications + def recent_publish? actor, object_type, object_data_url + activity_params = {actor_id: actor.id, object_type: object_type, object_data_url: object_data_url} + activity = OpenGraphPlugin::Activity.where(activity_params).first + activity.present? and activity.created_at <= self.update_delay.from_now + end + + def call p, *args + p and instance_exec *args, &p end end diff --git a/plugins/open_graph/models/open_graph_plugin/track.rb b/plugins/open_graph/models/open_graph_plugin/track.rb index 697e9b5..6f8e475 100644 --- a/plugins/open_graph/models/open_graph_plugin/track.rb +++ b/plugins/open_graph/models/open_graph_plugin/track.rb @@ -1,13 +1,16 @@ class OpenGraphPlugin::Track < ActiveRecord::Base + class_attribute :context + self.context = :open_graph + attr_accessible :type, :context, :tracker_id, :tracker, :actor_id, :action, - :object_type, :object_data, :object_data_id, :object_data_type, :object_data_url + :object_type, :object_data_id, :object_data_type, :object_data_url, + :story, :object_data, :actor belongs_to :tracker, class_name: 'Profile' belongs_to :actor, class_name: 'Profile' belongs_to :object_data, polymorphic: true - validates_presence_of :context before_validation :set_context def self.objects @@ -21,7 +24,15 @@ class OpenGraphPlugin::Track < ActiveRecord::Base protected def set_context - self.context = OpenGraphPlugin.context + self[:context] = self.class.context + end + + def print_debug msg + puts msg + Delayed::Worker.logger.debug msg + end + def debug? actor=nil + OpenGraphPlugin.debug? actor end end diff --git a/plugins/open_graph/plugin.yml b/plugins/open_graph/plugin.yml deleted file mode 100644 index b63a516..0000000 --- a/plugins/open_graph/plugin.yml +++ /dev/null @@ -1,3 +0,0 @@ -name: open_graph -dependencies: - - metadata diff --git a/plugins/open_graph/test/unit/open_graph_graph/publisher_test.rb b/plugins/open_graph/test/unit/open_graph_graph/publisher_test.rb index 8c7748f..57d05cf 100644 --- a/plugins/open_graph/test/unit/open_graph_graph/publisher_test.rb +++ b/plugins/open_graph/test/unit/open_graph_graph/publisher_test.rb @@ -2,14 +2,16 @@ require "test_helper" class OpenGraphPlugin::PublisherTest < ActiveSupport::TestCase + include OpenGraphPlugin::UrlHelper + def setup @actor = create_user.person User.current = @actor.user - @stories = OpenGraphPlugin::Stories::Definitions @publisher = OpenGraphPlugin::Publisher.new OpenGraphPlugin::Stories.stubs(:publishers).returns([@publisher]) - @publisher.stubs(:context).returns(:open_graph) - @publisher.stubs(:og_domain).returns('noosfero.net') + # for MetadataPlugin::UrlHelper#og_url_for + stubs(:og_domain).returns('noosfero.net') + OpenGraphPlugin::Activity.any_instance.stubs(:og_domain).returns('noosfero.net') end should "publish only tracked stuff" do @@ -46,66 +48,70 @@ class OpenGraphPlugin::PublisherTest < ActiveSupport::TestCase # active User.current = @actor.user + user = User.current.person + + blog = Blog.create! profile: user, name: 'blog' + blog_post = TinyMceArticle.create! profile: user, parent: blog, name: 'blah', author: user + assert_last_activity user, :create_an_article, url_for(blog_post) + + gallery = Gallery.create! name: 'gallery', profile: user + image = UploadedFile.create! uploaded_data: fixture_file_upload('/files/rails.png', 'image/png'), parent: gallery, profile: user + assert_last_activity user, :add_an_image, url_for(image, image.url.merge(view: true)) + + document = UploadedFile.create! uploaded_data: fixture_file_upload('/files/doctest.en.xhtml', 'text/html'), profile: user + assert_last_activity user, :add_a_document, url_for(document, document.url.merge(view: true)) + + event = Event.create! name: 'event', profile: user + assert_last_activity user, :create_an_event, url_for(event) + + forum = Forum.create! name: 'forum', profile: user + topic = TinyMceArticle.create! profile: user, parent: forum, name: 'blah2', author: user + assert_last_activity user, :start_a_discussion, url_for(topic, topic.url.merge(og_type: MetadataPlugin.og_types[:forum])) - blog = Blog.create! profile: @actor, name: 'blog' - blog_post = TinyMceArticle.new profile: User.current.person, parent: blog, name: 'blah', author: User.current.person - @publisher.expects(:publish).with(User.current.person, @stories[:create_an_article], @publisher.send(:url_for, blog_post)) - blog_post.save! - - gallery = Gallery.create! name: 'gallery', profile: User.current.person - image = UploadedFile.new uploaded_data: fixture_file_upload('/files/rails.png', 'image/png'), parent: gallery, profile: User.current.person - @publisher.expects(:publish).with(User.current.person, @stories[:add_an_image], @publisher.send(:url_for, image, image.url.merge(view: true))) - image.save! - - document = UploadedFile.new uploaded_data: fixture_file_upload('/files/doctest.en.xhtml', 'text/html'), profile: User.current.person - @publisher.expects(:publish).with(User.current.person, @stories[:add_a_document], @publisher.send(:url_for, document, document.url.merge(view: true))) - document.save! - - event = Event.new name: 'event', profile: User.current.person - @publisher.expects(:publish).with(User.current.person, @stories[:create_an_event], @publisher.send(:url_for, event)) - event.save! - - forum = Forum.create! name: 'forum', profile: User.current.person - topic = TinyMceArticle.new profile: User.current.person, parent: forum, name: 'blah2', author: User.current.person - @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]))) - topic.save! - - @publisher.expects(:publish).with(@actor, @stories[:make_friendship_with], @publisher.send(:url_for, @other_actor)).twice - @publisher.expects(:publish).with(@other_actor, @stories[:make_friendship_with], @publisher.send(:url_for, @actor)).twice - AddFriend.create!(person: @actor, friend: @other_actor).finish - Friendship.remove_friendship @actor, @other_actor + AddFriend.create!(person: user, friend: @other_actor).finish + #assert_last_activity user, :make_friendship_with, url_for(@other_actor) + Friendship.remove_friendship user, @other_actor # friend verb is groupable - AddFriend.create!(person: @actor, friend: @other_actor).finish + AddFriend.create!(person: user, friend: @other_actor).finish + #assert_last_activity @other_actor, :make_friendship_with, url_for(user) - @publisher.expects(:publish).with(User.current.person, @stories[:favorite_a_sse_initiative], @publisher.send(:url_for, @enterprise)) - @enterprise.fans << User.current.person + @enterprise.fans << user + assert_last_activity user, :favorite_a_sse_initiative, url_for(@enterprise) # active but published as passive User.current = @actor.user + user = User.current.person - blog_post = TinyMceArticle.new profile: @enterprise, parent: @enterprise.blog, name: 'blah', author: User.current.person - story = @stories[:announce_news_from_a_sse_initiative] - @publisher.expects(:publish).with(User.current.person, story, @publisher.send(:passive_url_for, blog_post, nil, story)) - blog_post.save! + blog_post = TinyMceArticle.create! profile: @enterprise, parent: @enterprise.blog, name: 'blah', author: user + story = :announce_news_from_a_sse_initiative + assert_last_activity user, story, passive_url_for(blog_post, nil, OpenGraphPlugin::Stories::Definitions[story]) # passive User.current = @other_actor.user + user = User.current.person # fan - blog_post = TinyMceArticle.new profile: @enterprise, parent: @enterprise.blog, name: 'blah2', author: User.current.person - story = @stories[:announce_news_from_a_sse_initiative] - @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/coop/blog/blah2') - blog_post.save! + blog_post = TinyMceArticle.create! profile: @enterprise, parent: @enterprise.blog, name: 'blah2', author: user + assert_last_activity user, :announce_news_from_a_sse_initiative, 'http://noosfero.net/coop/blog/blah2' # member - blog_post = TinyMceArticle.new profile: @myenterprise, parent: @myenterprise.blog, name: 'blah2', author: User.current.person - story = @stories[:announce_news_from_a_sse_initiative] - @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/mycoop/blog/blah2') - blog_post.save! - - blog_post = TinyMceArticle.new profile: @community, parent: @community.blog, name: 'blah', author: User.current.person - story = @stories[:announce_news_from_a_community] - @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/comm/blog/blah') - blog_post.save! + blog_post = TinyMceArticle.create! profile: @myenterprise, parent: @myenterprise.blog, name: 'blah2', author: user + assert_last_activity user, :announce_news_from_a_sse_initiative, 'http://noosfero.net/mycoop/blog/blah2' + + blog_post = TinyMceArticle.create! profile: @community, parent: @community.blog, name: 'blah', author: user + assert_last_activity user, :announce_news_from_a_community, 'http://noosfero.net/comm/blog/blah' + end + + protected + + def assert_activity activity, actor, story, object_data_url + assert_equal actor, activity.actor, actor + assert_equal story.to_s, activity.story + assert_equal object_data_url, activity.object_data_url + end + + def assert_last_activity actor, story, object_data_url + a = OpenGraphPlugin::Activity.order('id DESC').first + assert_activity a, actor, story, object_data_url end end -- libgit2 0.21.2