Skip to content
Snippets Groups Projects
Commit 14b9f5dc authored by Dennis Collinson's avatar Dennis Collinson
Browse files

move user modules into user namespace.

parent f3c1eff3
No related branches found
No related tags found
No related merge requests found
Showing with 294 additions and 302 deletions
......@@ -103,7 +103,7 @@ class AccountDeleter
end
def normal_ar_person_associates_to_delete
[:posts, :photos, :mentions]
[:posts, :photos, :mentions, :participations]
end
def ignored_or_special_ar_person_associations
......
......@@ -44,6 +44,7 @@ class Person < ActiveRecord::Base
has_many :posts, :foreign_key => :author_id, :dependent => :destroy # This person's own posts
has_many :photos, :foreign_key => :author_id, :dependent => :destroy # This person's own photos
has_many :comments, :foreign_key => :author_id, :dependent => :destroy # This person's own comments
has_many :participations, :through => :posts, :foreign_key => :author_id, :dependent => :destroy
belongs_to :owner, :class_name => 'User'
......
......@@ -9,6 +9,8 @@ class Post < ActiveRecord::Base
include Diaspora::Commentable
include Diaspora::Shareable
has_many :participations, :dependent => :delete_all, :as => :target
attr_accessor :user_like
# NOTE API V1 to be extracted
......
......@@ -2,15 +2,17 @@
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require File.join(Rails.root, 'lib/diaspora/user')
require File.join(Rails.root, 'lib/salmon/salmon')
require File.join(Rails.root, 'lib/postzord/dispatcher')
require 'rest-client'
class User < ActiveRecord::Base
include Diaspora::UserModules
include Encryptor::Private
include Connecting
include Querying
include SocialActions
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:timeoutable, :token_authenticatable, :lockable,
......@@ -33,7 +35,7 @@ class User < ActiveRecord::Base
serialize :hidden_shareables, Hash
has_one :person, :foreign_key => :owner_id
delegate :public_key, :posts, :photos, :owns?, :diaspora_handle, :name, :public_url, :profile, :first_name, :last_name, :to => :person
delegate :public_key, :posts, :photos, :owns?, :diaspora_handle, :name, :public_url, :profile, :first_name, :last_name, :participations, :to => :person
has_many :invitations_from_me, :class_name => 'Invitation', :foreign_key => :sender_id
has_many :invitations_to_me, :class_name => 'Invitation', :foreign_key => :recipient_id
......@@ -284,22 +286,6 @@ class User < ActiveRecord::Base
Salmon::EncryptedSlap.create_by_user_and_activity(self, post.to_diaspora_xml)
end
def comment!(post, text, opts={})
Comment::Generator.new(self.person, post, text).create!(opts)
end
def participate!(target, opts={})
Participation::Generator.new(self.person, target).create!(opts)
end
def like!(target, opts={})
Like::Generator.new(self.person, target).create!(opts)
end
def build_comment(options={})
Comment::Generator.new(self.person, options.delete(:post), options.delete(:text)).build(options)
end
# Check whether the user has liked a post.
# @param [Post] post
def liked?(target)
......
# 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 User::Connecting
# This will create a contact on the side of the sharer and the sharee.
# @param [Person] person The person to start sharing with.
# @param [Aspect] aspect The aspect to add them to.
# @return [Contact] The newly made contact for the passed in person.
def share_with(person, aspect)
contact = self.contacts.find_or_initialize_by_person_id(person.id)
return false unless contact.valid?
unless contact.receiving?
contact.dispatch_request
contact.receiving = true
end
contact.aspects << aspect
contact.save
if notification = Notification.where(:target_id => person.id).first
notification.update_attributes(:unread=>false)
end
register_share_visibilities(contact)
contact
end
# This puts the last 100 public posts by the passed in contact into the user's stream.
# @param [Contact] contact
# @return [void]
def register_share_visibilities(contact)
#should have select here, but proven hard to test
posts = Post.where(:author_id => contact.person_id, :public => true).limit(100)
p = posts.map do |post|
ShareVisibility.new(:contact_id => contact.id, :shareable_id => post.id, :shareable_type => 'Post')
end
ShareVisibility.import(p) unless posts.empty?
nil
end
def remove_contact(contact, opts={:force => false})
posts = contact.posts.all
if !contact.mutual? || opts[:force]
contact.destroy
else
contact.update_attributes(:receiving => false)
end
end
def disconnect(bad_contact, opts={})
person = bad_contact.person
Rails.logger.info("event=disconnect user=#{diaspora_handle} target=#{person.diaspora_handle}")
retraction = Retraction.for(self)
retraction.subscribers = [person]#HAX
Postzord::Dispatcher.build(self, retraction).post
AspectMembership.where(:contact_id => bad_contact.id).delete_all
remove_contact(bad_contact, opts)
end
def disconnected_by(person)
Rails.logger.info("event=disconnected_by user=#{diaspora_handle} target=#{person.diaspora_handle}")
if contact = self.contact_for(person)
remove_contact(contact)
end
end
end
# 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 File.join(Rails.root, 'lib', 'evil_query')
#TODO: THIS FILE SHOULD NOT EXIST, EVIL SQL SHOULD BE ENCAPSULATED IN EvilQueries,
#throwing all of this stuff in user violates demeter like WHOA
module User::Querying
def find_visible_shareable_by_id(klass, id, opts={} )
key = (opts.delete(:key) || :id)
::EvilQuery::VisibleShareableById.new(self, klass, key, id, opts).post!
end
def visible_shareables(klass, opts={})
opts = prep_opts(klass, opts)
shareable_ids = visible_shareable_ids(klass, opts)
klass.where(:id => shareable_ids).select('DISTINCT '+klass.to_s.tableize+'.*').limit(opts[:limit]).order(opts[:order_with_table])
end
def visible_shareable_ids(klass, opts={})
opts = prep_opts(klass, opts)
visible_ids_from_sql(klass, opts)
end
# @return [Array<Integer>]
def visible_ids_from_sql(klass, opts={})
opts = prep_opts(klass, opts)
opts[:klass] = klass
opts[:by_members_of] ||= self.aspect_ids
post_ids = klass.connection.select_values(visible_shareable_sql(klass, opts)).map { |id| id.to_i }
post_ids += klass.connection.select_values(construct_public_followings_sql(opts).to_sql).map {|id| id.to_i }
end
def visible_shareable_sql(klass, opts={})
table = klass.table_name
opts = prep_opts(klass, opts)
opts[:klass] = klass
shareable_from_others = construct_shareable_from_others_query(opts)
shareable_from_self = construct_shareable_from_self_query(opts)
"(#{shareable_from_others.to_sql} LIMIT #{opts[:limit]}) UNION ALL (#{shareable_from_self.to_sql} LIMIT #{opts[:limit]}) ORDER BY #{opts[:order]} LIMIT #{opts[:limit]}"
end
def ugly_select_clause(query, opts)
klass = opts[:klass]
select_clause ='DISTINCT %s.id, %s.updated_at AS updated_at, %s.created_at AS created_at' % [klass.table_name, klass.table_name, klass.table_name]
query.select(select_clause).order(opts[:order_with_table]).where(klass.arel_table[opts[:order_field]].lt(opts[:max_time]))
end
def construct_shareable_from_others_query(opts)
conditions = {
:pending => false,
:share_visibilities => {:hidden => opts[:hidden]},
:contacts => {:user_id => self.id, :receiving => true}
}
conditions[:type] = opts[:type] if opts.has_key?(:type)
query = opts[:klass].joins(:contacts).where(conditions)
if opts[:by_members_of]
query = query.joins(:contacts => :aspect_memberships).where(
:aspect_memberships => {:aspect_id => opts[:by_members_of]})
end
ugly_select_clause(query, opts)
end
def construct_public_followings_sql(opts)
aspects = Aspect.where(:id => opts[:by_members_of])
person_ids = Person.connection.select_values(people_in_aspects(aspects).select("people.id").to_sql)
query = opts[:klass].where(:author_id => person_ids, :public => true, :pending => false)
unless(opts[:klass] == Photo)
query = query.where(:type => opts[:type])
end
ugly_select_clause(query, opts)
end
def construct_shareable_from_self_query(opts)
conditions = {:pending => false }
conditions[:type] = opts[:type] if opts.has_key?(:type)
query = self.person.send(opts[:klass].to_s.tableize).where(conditions)
if opts[:by_members_of]
query = query.joins(:aspect_visibilities).where(:aspect_visibilities => {:aspect_id => opts[:by_members_of]})
end
ugly_select_clause(query, opts)
end
def contact_for(person)
return nil unless person
contact_for_person_id(person.id)
end
def aspects_with_shareable(base_class_name_or_class, shareable_id)
base_class_name = base_class_name_or_class
base_class_name = base_class_name_or_class.base_class.to_s if base_class_name_or_class.is_a?(Class)
self.aspects.joins(:aspect_visibilities).where(:aspect_visibilities => {:shareable_id => shareable_id, :shareable_type => base_class_name})
end
def contact_for_person_id(person_id)
Contact.where(:user_id => self.id, :person_id => person_id).includes(:person => :profile).first
end
# @param [Person] person
# @return [Boolean] whether person is a contact of this user
def has_contact_for?(person)
Contact.exists?(:user_id => self.id, :person_id => person.id)
end
def people_in_aspects(requested_aspects, opts={})
allowed_aspects = self.aspects & requested_aspects
aspect_ids = allowed_aspects.map(&:id)
people = Person.in_aspects(aspect_ids)
if opts[:type] == 'remote'
people = people.where(:owner_id => nil)
elsif opts[:type] == 'local'
people = people.where('people.owner_id IS NOT NULL')
end
people
end
def aspects_with_person person
contact_for(person).aspects
end
def posts_from(person)
::EvilQuery::ShareablesFromPerson.new(self, Post, person).make_relation!
end
def photos_from(person)
::EvilQuery::ShareablesFromPerson.new(self, Photo, person).make_relation!
end
protected
# @return [Hash]
def prep_opts(klass, opts)
defaults = {
:order => 'created_at DESC',
:limit => 15,
:hidden => false
}
defaults[:type] = Stream::Base::TYPES_OF_POST_IN_STREAM if klass == Post
opts = defaults.merge(opts)
opts[:order_field] = opts[:order].split.first.to_sym
opts[:order_with_table] = klass.table_name + '.' + opts[:order]
opts[:max_time] = Time.at(opts[:max_time]) if opts[:max_time].is_a?(Integer)
opts[:max_time] ||= Time.now + 1
opts
end
end
module User::SocialActions
def comment!(post, text, opts={})
participations.where(:target_id => post).first || participate!(post)
Comment::Generator.new(self.person, post, text).create!(opts)
end
def participate!(target, opts={})
Participation::Generator.new(self.person, target).create!(opts)
end
def like!(target, opts={})
participations.where(:target_id => target).first || participate!(target)
Like::Generator.new(self.person, target).create!(opts)
end
def build_comment(options={})
Comment::Generator.new(self.person, options.delete(:post), options.delete(:text)).build(options)
end
end
\ No newline at end of file
require File.join(Rails.root, 'lib/diaspora/user/connecting')
require File.join(Rails.root, 'lib/diaspora/user/querying')
module Diaspora
module UserModules
include Connecting
include Querying
end
end
# 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 UserModules
module Connecting
# This will create a contact on the side of the sharer and the sharee.
# @param [Person] person The person to start sharing with.
# @param [Aspect] aspect The aspect to add them to.
# @return [Contact] The newly made contact for the passed in person.
def share_with(person, aspect)
contact = self.contacts.find_or_initialize_by_person_id(person.id)
return false unless contact.valid?
unless contact.receiving?
contact.dispatch_request
contact.receiving = true
end
contact.aspects << aspect
contact.save
if notification = Notification.where(:target_id => person.id).first
notification.update_attributes(:unread=>false)
end
register_share_visibilities(contact)
contact
end
# This puts the last 100 public posts by the passed in contact into the user's stream.
# @param [Contact] contact
# @return [void]
def register_share_visibilities(contact)
#should have select here, but proven hard to test
posts = Post.where(:author_id => contact.person_id, :public => true).limit(100)
p = posts.map do |post|
ShareVisibility.new(:contact_id => contact.id, :shareable_id => post.id, :shareable_type => 'Post')
end
ShareVisibility.import(p) unless posts.empty?
nil
end
def remove_contact(contact, opts={:force => false})
posts = contact.posts.all
if !contact.mutual? || opts[:force]
contact.destroy
else
contact.update_attributes(:receiving => false)
end
end
def disconnect(bad_contact, opts={})
person = bad_contact.person
Rails.logger.info("event=disconnect user=#{diaspora_handle} target=#{person.diaspora_handle}")
retraction = Retraction.for(self)
retraction.subscribers = [person]#HAX
Postzord::Dispatcher.build(self, retraction).post
AspectMembership.where(:contact_id => bad_contact.id).delete_all
remove_contact(bad_contact, opts)
end
def disconnected_by(person)
Rails.logger.info("event=disconnected_by user=#{diaspora_handle} target=#{person.diaspora_handle}")
if contact = self.contact_for(person)
remove_contact(contact)
end
end
end
end
end
# 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 File.join(Rails.root, 'lib', 'evil_query')
#TODO: THIS FILE SHOULD NOT EXIST, EVIL SQL SHOULD BE ENCAPSULATED IN EvilQueries,
#throwing all of this stuff in user violates demeter like WHOA
module Diaspora
module UserModules
module Querying
def find_visible_shareable_by_id(klass, id, opts={} )
key = (opts.delete(:key) || :id)
::EvilQuery::VisibleShareableById.new(self, klass, key, id, opts).post!
end
def visible_shareables(klass, opts={})
opts = prep_opts(klass, opts)
shareable_ids = visible_shareable_ids(klass, opts)
klass.where(:id => shareable_ids).select('DISTINCT '+klass.to_s.tableize+'.*').limit(opts[:limit]).order(opts[:order_with_table])
end
def visible_shareable_ids(klass, opts={})
opts = prep_opts(klass, opts)
visible_ids_from_sql(klass, opts)
end
# @return [Array<Integer>]
def visible_ids_from_sql(klass, opts={})
opts = prep_opts(klass, opts)
opts[:klass] = klass
opts[:by_members_of] ||= self.aspect_ids
post_ids = klass.connection.select_values(visible_shareable_sql(klass, opts)).map { |id| id.to_i }
post_ids += klass.connection.select_values(construct_public_followings_sql(opts).to_sql).map {|id| id.to_i }
end
def visible_shareable_sql(klass, opts={})
table = klass.table_name
opts = prep_opts(klass, opts)
opts[:klass] = klass
shareable_from_others = construct_shareable_from_others_query(opts)
shareable_from_self = construct_shareable_from_self_query(opts)
"(#{shareable_from_others.to_sql} LIMIT #{opts[:limit]}) UNION ALL (#{shareable_from_self.to_sql} LIMIT #{opts[:limit]}) ORDER BY #{opts[:order]} LIMIT #{opts[:limit]}"
end
def ugly_select_clause(query, opts)
klass = opts[:klass]
select_clause ='DISTINCT %s.id, %s.updated_at AS updated_at, %s.created_at AS created_at' % [klass.table_name, klass.table_name, klass.table_name]
query.select(select_clause).order(opts[:order_with_table]).where(klass.arel_table[opts[:order_field]].lt(opts[:max_time]))
end
def construct_shareable_from_others_query(opts)
conditions = {
:pending => false,
:share_visibilities => {:hidden => opts[:hidden]},
:contacts => {:user_id => self.id, :receiving => true}
}
conditions[:type] = opts[:type] if opts.has_key?(:type)
query = opts[:klass].joins(:contacts).where(conditions)
if opts[:by_members_of]
query = query.joins(:contacts => :aspect_memberships).where(
:aspect_memberships => {:aspect_id => opts[:by_members_of]})
end
ugly_select_clause(query, opts)
end
def construct_public_followings_sql(opts)
aspects = Aspect.where(:id => opts[:by_members_of])
person_ids = Person.connection.select_values(people_in_aspects(aspects).select("people.id").to_sql)
query = opts[:klass].where(:author_id => person_ids, :public => true, :pending => false)
unless(opts[:klass] == Photo)
query = query.where(:type => opts[:type])
end
ugly_select_clause(query, opts)
end
def construct_shareable_from_self_query(opts)
conditions = {:pending => false }
conditions[:type] = opts[:type] if opts.has_key?(:type)
query = self.person.send(opts[:klass].to_s.tableize).where(conditions)
if opts[:by_members_of]
query = query.joins(:aspect_visibilities).where(:aspect_visibilities => {:aspect_id => opts[:by_members_of]})
end
ugly_select_clause(query, opts)
end
def contact_for(person)
return nil unless person
contact_for_person_id(person.id)
end
def aspects_with_shareable(base_class_name_or_class, shareable_id)
base_class_name = base_class_name_or_class
base_class_name = base_class_name_or_class.base_class.to_s if base_class_name_or_class.is_a?(Class)
self.aspects.joins(:aspect_visibilities).where(:aspect_visibilities => {:shareable_id => shareable_id, :shareable_type => base_class_name})
end
def contact_for_person_id(person_id)
Contact.where(:user_id => self.id, :person_id => person_id).includes(:person => :profile).first
end
# @param [Person] person
# @return [Boolean] whether person is a contact of this user
def has_contact_for?(person)
Contact.exists?(:user_id => self.id, :person_id => person.id)
end
def people_in_aspects(requested_aspects, opts={})
allowed_aspects = self.aspects & requested_aspects
aspect_ids = allowed_aspects.map(&:id)
people = Person.in_aspects(aspect_ids)
if opts[:type] == 'remote'
people = people.where(:owner_id => nil)
elsif opts[:type] == 'local'
people = people.where('people.owner_id IS NOT NULL')
end
people
end
def aspects_with_person person
contact_for(person).aspects
end
def posts_from(person)
::EvilQuery::ShareablesFromPerson.new(self, Post, person).make_relation!
end
def photos_from(person)
::EvilQuery::ShareablesFromPerson.new(self, Photo, person).make_relation!
end
protected
# @return [Hash]
def prep_opts(klass, opts)
defaults = {
:order => 'created_at DESC',
:limit => 15,
:hidden => false
}
defaults[:type] = Stream::Base::TYPES_OF_POST_IN_STREAM if klass == Post
opts = defaults.merge(opts)
opts[:order_field] = opts[:order].split.first.to_sym
opts[:order_with_table] = klass.table_name + '.' + opts[:order]
opts[:max_time] = Time.at(opts[:max_time]) if opts[:max_time].is_a?(Integer)
opts[:max_time] ||= Time.now + 1
opts
end
end
end
end
......@@ -17,6 +17,7 @@ module EvilQuery
end
def posts
# Post.joins(:participations).where(:participations => {:author_id => @user.id}).order("posts.interacted_at DESC")
liked_post_ids = fetch_ids!(LikedPosts.new(@user).posts, "posts.id")
commented_post_ids = fetch_ids!(CommentedPosts.new(@user).posts, "posts.id")
Post.where(:id => liked_post_ids + commented_post_ids).order("posts.interacted_at DESC")
......
......@@ -6,7 +6,7 @@ describe EvilQuery::Participation do
end
it "includes posts liked by the user" do
Factory(:like, :target => @status_message, :author => alice.person)
alice.like!(@status_message)
EvilQuery::Participation.new(alice).posts.should include(@status_message)
end
......@@ -35,7 +35,7 @@ describe EvilQuery::Participation do
alice.comment!(@status_messageB, "party")
Timecop.travel time += 1.month
Factory(:like, :target => @status_messageA, :author => alice.person)
alice.like!(@status_messageA)
Timecop.travel time += 1.month
alice.comment!(@photoC, "party")
......
......@@ -4,7 +4,7 @@
require 'spec_helper'
describe Diaspora::UserModules::Connecting do
describe User::Connecting do
let(:aspect) { alice.aspects.first }
let(:aspect1) { alice.aspects.create(:name => 'other') }
......
......@@ -4,8 +4,7 @@
require 'spec_helper'
describe User do
describe User::Querying do
before do
@alices_aspect = alice.aspects.where(:name => "generic").first
@eves_aspect = eve.aspects.where(:name => "generic").first
......
require "spec_helper"
describe User::SocialActions do
describe 'User#like!' do
before do
@bobs_aspect = bob.aspects.where(:name => "generic").first
@status = bob.post(:status_message, :text => "hello", :to => @bobs_aspect.id)
end
it "should be able to like on one's own status" do
like = alice.like!(@status)
@status.reload.likes.first.should == like
end
it "should be able to like on a contact's status" do
like = bob.like!(@status)
@status.reload.likes.first.should == like
end
it "does not allow multiple likes" do
alice.like!(@status)
lambda {
alice.like!(@status)
}.should_not change(@status, :likes)
end
end
end
\ No newline at end of file
......@@ -5,7 +5,6 @@
require 'spec_helper'
describe User do
describe "private key" do
it 'has a key' do
alice.encryption_key.should_not be nil
......@@ -688,30 +687,6 @@ describe User do
@like2 = bob.like!(@message)
end
describe 'User#like' do
before do
@status = bob.post(:status_message, :text => "hello", :to => @bobs_aspect.id)
end
it "should be able to like on one's own status" do
like = alice.like!(@status)
@status.reload.likes.first.should == like
end
it "should be able to like on a contact's status" do
like = bob.like!(@status)
@status.reload.likes.first.should == like
end
it "does not allow multiple likes" do
alice.like!(@status)
lambda {
alice.like!(@status)
}.should_not change(@status, :likes)
end
end
describe '#like_for' do
it 'returns the correct like' do
alice.like_for(@message).should == @like
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment