From 27a4c1bf2ddd5f5033b24856dd269e80f2116713 Mon Sep 17 00:00:00 2001 From: Maxwell Salzberg Date: Sat, 25 Feb 2012 16:57:14 -0800 Subject: [PATCH] introduce the idea of Federated::Base. this is mostly just renaming and collasping of different federation modules, but also starting a direct hiearchy of these federation classes to make everything easier to refactor --- app/models/account_deletion.rb | 4 +- app/models/comment.rb | 4 +- app/models/conversation.rb | 3 +- app/models/message.rb | 4 +- app/models/photo.rb | 2 + app/models/post.rb | 3 + app/models/profile.rb | 4 +- app/models/request.rb | 3 +- app/models/retraction.rb | 3 +- app/models/signed_retraction.rb | 4 +- lib/diaspora.rb | 1 - lib/diaspora/federated/base.rb | 56 +++++++++ lib/diaspora/federated/shareable.rb | 116 ++++++++++++++++++ lib/diaspora/guid.rb | 4 + lib/diaspora/ostatus_builder.rb | 80 ------------ lib/diaspora/shareable.rb | 108 +--------------- lib/diaspora/webhooks.rb | 38 ------ lib/federated/relayable.rb | 4 +- lib/postzord/dispatcher.rb | 2 +- lib/postzord/presenter.rb | 33 +++++ spec/controllers/users_controller_spec.rb | 2 - ...ebhooks_spec.rb => federated_base_spec.rb} | 4 +- spec/lib/postzord/dispatcher_spec.rb | 2 +- spec/lib/web_hooks_spec.rb | 14 --- 24 files changed, 236 insertions(+), 262 deletions(-) create mode 100644 lib/diaspora/federated/base.rb create mode 100644 lib/diaspora/federated/shareable.rb delete mode 100644 lib/diaspora/ostatus_builder.rb delete mode 100644 lib/diaspora/webhooks.rb create mode 100644 lib/postzord/presenter.rb rename spec/lib/diaspora/{webhooks_spec.rb => federated_base_spec.rb} (83%) delete mode 100644 spec/lib/web_hooks_spec.rb diff --git a/app/models/account_deletion.rb b/app/models/account_deletion.rb index ee99a00bb..581593b04 100644 --- a/app/models/account_deletion.rb +++ b/app/models/account_deletion.rb @@ -3,8 +3,7 @@ # the COPYRIGHT file. class AccountDeletion < ActiveRecord::Base - include ROXML - include Diaspora::Webhooks + include Diaspora::Federated::Base belongs_to :person @@ -15,6 +14,7 @@ class AccountDeletion < ActiveRecord::Base xml_name :account_deletion xml_attr :diaspora_handle + def person=(person) self[:diaspora_handle] = person.diaspora_handle self[:person_id] = person.id diff --git a/app/models/comment.rb b/app/models/comment.rb index a19b09cb9..d608ca27f 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -3,9 +3,9 @@ # the COPYRIGHT file. class Comment < ActiveRecord::Base - include ROXML - include Diaspora::Webhooks + include Diaspora::Federated::Base + include Diaspora::Guid include Diaspora::Relayable diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 437750310..4fc891280 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -1,7 +1,6 @@ class Conversation < ActiveRecord::Base - include ROXML + include Diaspora::Federated::Base include Diaspora::Guid - include Diaspora::Webhooks xml_attr :subject xml_attr :created_at diff --git a/app/models/message.rb b/app/models/message.rb index c8741736a..699bbb934 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -1,9 +1,7 @@ class NotVisibleError < RuntimeError; end class Message < ActiveRecord::Base - include ROXML - + include Diaspora::Federated::Base include Diaspora::Guid - include Diaspora::Webhooks include Diaspora::Relayable xml_attr :text diff --git a/app/models/photo.rb b/app/models/photo.rb index c30e4d26e..54b3a9f6b 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -5,10 +5,12 @@ class Photo < ActiveRecord::Base require 'carrierwave/orm/activerecord' + include Diaspora::Federated::Shareable include Diaspora::Commentable include Diaspora::Shareable + # NOTE API V1 to be extracted acts_as_api api_accessible :backbone do |t| diff --git a/app/models/post.rb b/app/models/post.rb index f171edd8c..8c5b2753e 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -5,10 +5,13 @@ class Post < ActiveRecord::Base include ApplicationHelper + include Diaspora::Federated::Shareable + include Diaspora::Likeable include Diaspora::Commentable include Diaspora::Shareable + has_many :participations, :dependent => :delete_all, :as => :target attr_accessor :user_like, diff --git a/app/models/profile.rb b/app/models/profile.rb index 9bf2025c4..8cb9bb3b5 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -3,10 +3,8 @@ # the COPYRIGHT file. class Profile < ActiveRecord::Base - require File.join(Rails.root, 'lib/diaspora/webhooks') - include Diaspora::Webhooks + include Diaspora::Federated::Base include Diaspora::Taggable - include ROXML attr_accessor :tag_string diff --git a/app/models/request.rb b/app/models/request.rb index 7bed46d73..0b0f446b3 100644 --- a/app/models/request.rb +++ b/app/models/request.rb @@ -4,8 +4,7 @@ # the COPYRIGHT file. class Request - include ROXML - include Diaspora::Webhooks + include Diaspora::Federated::Base include ActiveModel::Validations attr_accessor :sender, :recipient, :aspect diff --git a/app/models/retraction.rb b/app/models/retraction.rb index 6c5aa34b1..3a35c28f2 100644 --- a/app/models/retraction.rb +++ b/app/models/retraction.rb @@ -3,8 +3,7 @@ # the COPYRIGHT file. class Retraction - include ROXML - include Diaspora::Webhooks + include Diaspora::Federated::Base xml_accessor :post_guid xml_accessor :diaspora_handle diff --git a/app/models/signed_retraction.rb b/app/models/signed_retraction.rb index c515b963f..67a6812c1 100644 --- a/app/models/signed_retraction.rb +++ b/app/models/signed_retraction.rb @@ -3,8 +3,8 @@ # the COPYRIGHT file. class SignedRetraction - include ROXML - include Diaspora::Webhooks + include Diaspora::Federated::Base + include Diaspora::Encryptable xml_name :signed_retraction diff --git a/lib/diaspora.rb b/lib/diaspora.rb index 2a836faaf..18d812247 100644 --- a/lib/diaspora.rb +++ b/lib/diaspora.rb @@ -4,5 +4,4 @@ module Diaspora autoload :Parser - autoload :Webhooks end diff --git a/lib/diaspora/federated/base.rb b/lib/diaspora/federated/base.rb new file mode 100644 index 000000000..32054cca6 --- /dev/null +++ b/lib/diaspora/federated/base.rb @@ -0,0 +1,56 @@ +# Copyright (c) 2010-2012, Diaspora Inc. This file is +# licensed under the Affero General Public License version 3 or later. See +# the COPYRIGHT file. + +#the base level federation contract, which right now means that the object +#can be serialized and deserialized from xml, and respond to methods +#in the federation flow + + +#including this module lets you federate an object at the most basic of level + +require 'builder/xchar' + +module Diaspora + module Federated + module Base + def self.included(model) + model.instance_eval do + include ROXML + include Diaspora::Federated::Base::InstanceMethods + end + end + + module InstanceMethods + def to_diaspora_xml + <<-XML + + #{to_xml.to_s} + + XML + end + + def x(input) + input.to_s.to_xs + end + + # @abstract + # @note this must return [Array] + # @return [Array] + def subscribers(user) + raise 'You must override subscribers in order to enable federation on this model' + end + + # @abstract + def receive(user, person) + raise 'You must override receive in order to enable federation on this model' + end + + # @param [User] sender + # @note this is a hook(optional) + def after_dispatch(sender) + end + end + end + end +end diff --git a/lib/diaspora/federated/shareable.rb b/lib/diaspora/federated/shareable.rb new file mode 100644 index 000000000..a2c4ce957 --- /dev/null +++ b/lib/diaspora/federated/shareable.rb @@ -0,0 +1,116 @@ +# Copyright (c) 2012, Diaspora Inc. This file is +# licensed under the Affero General Public License version 3 or later. See +# the COPYRIGHT file. + +#this module attempts to be what you need to mix into +# base level federation objects that are not relayable, and not persistable +#assumes there is an author, author_id, id, +module Diaspora + module Federated + module Shareable + + def self.included(model) + model.instance_eval do + #we are order dependant so you don't have to be! + include Diaspora::Federated::Base + include Diaspora::Federated::Shareable::InstanceMethods + include Diaspora::Guid + + + xml_attr :diaspora_handle + xml_attr :public + xml_attr :created_at + end + end + + module InstanceMethods + def diaspora_handle + read_attribute(:diaspora_handle) || self.author.diaspora_handle + end + + def diaspora_handle=(author_handle) + self.author = Person.where(:diaspora_handle => author_handle).first + write_attribute(:diaspora_handle, author_handle) + end + + # @param [User] user The user that is receiving this shareable. + # @param [Person] person The person who dispatched this shareable to the + # @return [void] + def receive(user, person) + #exists locally, but you dont know about it + #does not exsist locally, and you dont know about it + #exists_locally? + #you know about it, and it is mutable + #you know about it, and it is not mutable + self.class.transaction do + local_shareable = persisted_shareable + + if local_shareable && verify_persisted_shareable(local_shareable) + self.receive_persisted(user, person, local_shareable) + + elsif !local_shareable + self.receive_non_persisted(user, person) + + else + Rails.logger.info("event=receive payload_type=#{self.class} update=true status=abort sender=#{self.diaspora_handle} reason='update not from shareable owner' existing_shareable=#{self.id}") + false + end + end + end + + # The list of people that should receive this Shareable. + # + # @param [User] user The context, or dispatching user. + # @return [Array] The list of subscribers to this shareable + def subscribers(user) + if self.public? + user.contact_people + else + user.people_in_aspects(user.aspects_with_shareable(self.class, self.id)) + end + end + protected + + # @return [Shareable,void] + def persisted_shareable + self.class.where(:guid => self.guid).first + end + + # @return [Boolean] + def verify_persisted_shareable(persisted_shareable) + persisted_shareable.author_id == self.author_id + end + + def receive_persisted(user, person, local_shareable) + known_shareable = user.find_visible_shareable_by_id(self.class.base_class, self.guid, :key => :guid) + if known_shareable + if known_shareable.mutable? + known_shareable.update_attributes(self.attributes) + true + else + Rails.logger.info("event=receive payload_type=#{self.class} update=true status=abort sender=#{self.diaspora_handle} reason=immutable") #existing_shareable=#{known_shareable.id}") + false + end + else + user.contact_for(person).receive_shareable(local_shareable) + user.notify_if_mentioned(local_shareable) + Rails.logger.info("event=receive payload_type=#{self.class} update=true status=complete sender=#{self.diaspora_handle}") #existing_shareable=#{local_shareable.id}") + true + end + end + + def receive_non_persisted(user, person) + if self.save + user.contact_for(person).receive_shareable(self) + user.notify_if_mentioned(self) + Rails.logger.info("event=receive payload_type=#{self.class} update=false status=complete sender=#{self.diaspora_handle}") + true + else + Rails.logger.info("event=receive payload_type=#{self.class} update=false status=abort sender=#{self.diaspora_handle} reason=#{self.errors.full_messages}") + false + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/diaspora/guid.rb b/lib/diaspora/guid.rb index aa8ae8d81..3a7b1d54d 100644 --- a/lib/diaspora/guid.rb +++ b/lib/diaspora/guid.rb @@ -1,9 +1,13 @@ +#implicitly requires roxml + module Diaspora::Guid # Creates a before_create callback which calls #set_guid and makes the guid serialize in to_xml def self.included(model) model.class_eval do before_create :set_guid xml_attr :guid + validates :guid, :uniqueness => true + end end diff --git a/lib/diaspora/ostatus_builder.rb b/lib/diaspora/ostatus_builder.rb deleted file mode 100644 index 56150ee93..000000000 --- a/lib/diaspora/ostatus_builder.rb +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) 2010-2011, Diaspora Inc. This file is -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. - -module Diaspora - - class Director - def initialize - @structure = [:create_headers, :create_endpoints, :create_subject, - :create_body, :create_footer] - end - - def build(builder) - @structure.inject("") do |xml, method| - xml << builder.send(method) if builder.respond_to? method - end - end - end - - - class OstatusBuilder - include Diaspora::Webhooks - include PeopleHelper - - def initialize(user, posts) - @user = user - @posts = posts - end - - def create_headers - <<-XML - - -Diaspora -#{@user.public_url}.atom -#{x(@user.name)}'s Public Feed -Updates from #{x(@user.name)} on Diaspora -#{@user.person.profile.image_url(:thumb_small)} -#{Time.now.xmlschema} - XML - end - - def create_subject - <<-XML - - http://activitystrea.ms/schema/1.0/person - #{x(@user.name)} - #{local_or_remote_person_path(@user.person, :absolute => true)} - #{x(@user.username)} - #{x(@user.person.name)} - - XML - end - - def create_endpoints - <<-XML - - - - - XML - end - - def create_body - @posts.inject("") do |xml,curr| - if curr.respond_to?(:to_activity) - xml + curr.to_activity(:author => @user.person) - else - xml - end - end - end - - def create_footer - <<-XML - - XML - end - end -end diff --git a/lib/diaspora/shareable.rb b/lib/diaspora/shareable.rb index fb7f3c12e..759020825 100644 --- a/lib/diaspora/shareable.rb +++ b/lib/diaspora/shareable.rb @@ -1,15 +1,12 @@ # Copyright (c) 2010, Diaspora Inc. This file is # licensed under the Affero General Public License version 3 or later. See # the COPYRIGHT file. - +#the pont of this object is to centralize the simmilarities of Photo and post, +# as they used to be the same class module Diaspora module Shareable - include Diaspora::Webhooks - def self.included(model) model.instance_eval do - include ROXML - include Diaspora::Guid has_many :aspect_visibilities, :as => :shareable has_many :aspects, :through => :aspect_visibilities @@ -19,7 +16,6 @@ module Diaspora belongs_to :author, :class_name => 'Person' - validates :guid, :uniqueness => true #scopes scope :all_public, where(:public => true, :pending => false) @@ -27,9 +23,10 @@ module Diaspora def self.owned_or_visible_by_user(user) self.joins("LEFT OUTER JOIN share_visibilities ON share_visibilities.shareable_id = posts.id AND share_visibilities.shareable_type = 'Post'"). joins("LEFT OUTER JOIN contacts ON contacts.id = share_visibilities.contact_id"). - where(Contact.arel_table[:user_id].eq(user.id).or( - self.arel_table[:public].eq(true).or( - self.arel_table[:author_id].eq(user.person.id) + where( + Contact.arel_table[:user_id].eq(user.id).or( + self.arel_table[:public].eq(true).or( + self.arel_table[:author_id].eq(user.person.id) ) ) ). @@ -45,56 +42,6 @@ module Diaspora def self.by_max_time(max_time, order='created_at') where("#{self.table_name}.#{order} < ?", max_time).order("#{self.table_name}.#{order} desc") end - - xml_attr :diaspora_handle - xml_attr :public - xml_attr :created_at - end - end - - def diaspora_handle - read_attribute(:diaspora_handle) || self.author.diaspora_handle - end - - def diaspora_handle= nd - self.author = Person.where(:diaspora_handle => nd).first - write_attribute(:diaspora_handle, nd) - end - - # @param [User] user The user that is receiving this shareable. - # @param [Person] person The person who dispatched this shareable to the - # @return [void] - def receive(user, person) - #exists locally, but you dont know about it - #does not exsist locally, and you dont know about it - #exists_locally? - #you know about it, and it is mutable - #you know about it, and it is not mutable - self.class.transaction do - local_shareable = persisted_shareable - - if local_shareable && verify_persisted_shareable(local_shareable) - self.receive_persisted(user, person, local_shareable) - - elsif !local_shareable - self.receive_non_persisted(user, person) - - else - Rails.logger.info("event=receive payload_type=#{self.class} update=true status=abort sender=#{self.diaspora_handle} reason='update not from shareable owner' existing_shareable=#{self.id}") - false - end - end - end - - # The list of people that should receive this Shareable. - # - # @param [User] user The context, or dispatching user. - # @return [Array] The list of subscribers to this shareable - def subscribers(user) - if self.public? - user.contact_people - else - user.people_in_aspects(user.aspects_with_shareable(self.class, self.id)) end end @@ -103,48 +50,5 @@ module Diaspora self.class.where(:id => self.id). update_all(:reshares_count => self.reshares.count) end - - protected - - # @return [Shareable,void] - def persisted_shareable - self.class.where(:guid => self.guid).first - end - - # @return [Boolean] - def verify_persisted_shareable(persisted_shareable) - persisted_shareable.author_id == self.author_id - end - - def receive_persisted(user, person, local_shareable) - known_shareable = user.find_visible_shareable_by_id(self.class.base_class, self.guid, :key => :guid) - if known_shareable - if known_shareable.mutable? - known_shareable.update_attributes(self.attributes) - true - else - Rails.logger.info("event=receive payload_type=#{self.class} update=true status=abort sender=#{self.diaspora_handle} reason=immutable") #existing_shareable=#{known_shareable.id}") - false - end - else - user.contact_for(person).receive_shareable(local_shareable) - user.notify_if_mentioned(local_shareable) - Rails.logger.info("event=receive payload_type=#{self.class} update=true status=complete sender=#{self.diaspora_handle}") #existing_shareable=#{local_shareable.id}") - true - end - end - - def receive_non_persisted(user, person) - if self.save - user.contact_for(person).receive_shareable(self) - user.notify_if_mentioned(self) - Rails.logger.info("event=receive payload_type=#{self.class} update=false status=complete sender=#{self.diaspora_handle}") - true - else - Rails.logger.info("event=receive payload_type=#{self.class} update=false status=abort sender=#{self.diaspora_handle} reason=#{self.errors.full_messages}") - false - end - end - end end diff --git a/lib/diaspora/webhooks.rb b/lib/diaspora/webhooks.rb deleted file mode 100644 index 582b4bd21..000000000 --- a/lib/diaspora/webhooks.rb +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2010-2011, Diaspora Inc. This file is -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. - -module Diaspora - module Webhooks - require 'builder/xchar' - - def to_diaspora_xml - < - #{to_xml.to_s} - -XML - end - - def x(input) - input.to_s.to_xs - end - - # @abstract - # @note this must return [Array] - # @return [Array] - def subscribers(user) - raise 'You must override subscribers in order to enable federation on this model' - end - - # @abstract - def receive(user, person) - raise 'You must override receive in order to enable federation on this model' - end - - # @param [User] sender - # @note this is a hook - def after_dispatch sender - end - end -end diff --git a/lib/federated/relayable.rb b/lib/federated/relayable.rb index 618c24820..d852a4dcd 100644 --- a/lib/federated/relayable.rb +++ b/lib/federated/relayable.rb @@ -3,9 +3,7 @@ module Federated self.abstract_class = true #crazy ordering issues - DEATH TO ROXML - include ROXML - - include Diaspora::Webhooks + include Diaspora::Federated::Base include Diaspora::Guid #seriously, don't try to move this shit around until you have killed ROXML diff --git a/lib/postzord/dispatcher.rb b/lib/postzord/dispatcher.rb index 6e2676bed..6726c39d2 100644 --- a/lib/postzord/dispatcher.rb +++ b/lib/postzord/dispatcher.rb @@ -25,7 +25,7 @@ class Postzord::Dispatcher # @return [Postzord::Dispatcher] Public or private dispatcher depending on the object's intended audience def self.build(user, object, opts={}) unless object.respond_to? :to_diaspora_xml - raise 'This object does not respond_to? to_diaspora xml. Try including Diaspora::Webhooks into your object' + raise 'This object does not respond_to? to_diaspora xml. Try including Diaspora::Federated::Base into your object' end if self.object_should_be_processed_as_public?(object) diff --git a/lib/postzord/presenter.rb b/lib/postzord/presenter.rb new file mode 100644 index 000000000..5d70da3fb --- /dev/null +++ b/lib/postzord/presenter.rb @@ -0,0 +1,33 @@ +#dispatching +#a class that figures out the markup of an object +class Federated::Presenter +end + +#a class that detects the audience of a post +class Federated::Audience +end + +#this class dispatchs the post to services, like facebook, twitter, etc +class ServiceDispatcher + #interacts with a single post, and many provided services +end + +#Receiving Phases + #receive request => check author + #xml payload + #decode + #convert to meta object + #perfom various validations + #recieve! <= save object, if applicable + #after_receive hook + + + Ideas: + Federated objects are delegated stubs of real objects, with converstion constuctors + - this turns receive to "make model level object from payload" + - seperate validations and checking with persistance and noticiation layer + + + + http => deserliaization/decoding/descrypting => meta object => validations => receive => cleanup + diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 554ccea0e..1911ab109 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -47,12 +47,10 @@ describe UsersController do end it 'redirects to a profile page if html is requested' do - Diaspora::OstatusBuilder.should_not_receive(:new) get :public, :username => @user.username response.should be_redirect end it 'redirects to a profile page if mobile is requested' do - Diaspora::OstatusBuilder.should_not_receive(:new) get :public, :username => @user.username, :format => :mobile response.should be_redirect end diff --git a/spec/lib/diaspora/webhooks_spec.rb b/spec/lib/diaspora/federated_base_spec.rb similarity index 83% rename from spec/lib/diaspora/webhooks_spec.rb rename to spec/lib/diaspora/federated_base_spec.rb index 30cda1e38..a3fbcbacc 100644 --- a/spec/lib/diaspora/webhooks_spec.rb +++ b/spec/lib/diaspora/federated_base_spec.rb @@ -4,11 +4,11 @@ require 'spec_helper' -describe Diaspora::Webhooks do +describe Diaspora::Federated::Base do describe '#subscribers' do it 'throws an error if the including module does not redefine it' do class Foo - include Diaspora::Webhooks + include Diaspora::Federated::Base end f = Foo.new diff --git a/spec/lib/postzord/dispatcher_spec.rb b/spec/lib/postzord/dispatcher_spec.rb index f53229b36..c7f5f915f 100644 --- a/spec/lib/postzord/dispatcher_spec.rb +++ b/spec/lib/postzord/dispatcher_spec.rb @@ -42,7 +42,7 @@ describe Postzord::Dispatcher do it 'raises and gives you a helpful message if the object can not federate' do expect { Postzord::Dispatcher.build(alice, []) - }.should raise_error /Diaspora::Webhooks/ + }.should raise_error /Diaspora::Federated::Base/ end end diff --git a/spec/lib/web_hooks_spec.rb b/spec/lib/web_hooks_spec.rb deleted file mode 100644 index d50a403fb..000000000 --- a/spec/lib/web_hooks_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2010-2011, Diaspora Inc. This file is -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. - -require 'spec_helper' - -describe Diaspora::Webhooks do - it "should add the following methods to Post on inclusion" do - user = Factory.build(:user) - post = Factory.build(:status_message, :author => user.person) - - post.respond_to?(:to_diaspora_xml).should be true - end -end