tekeya 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. data/.document +0 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +3 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +39 -0
  6. data/Guardfile +40 -0
  7. data/LICENSE +22 -0
  8. data/README.md +37 -0
  9. data/Rakefile +13 -0
  10. data/TODO.todo +8 -0
  11. data/app/active_record/tekeya/activity.rb +7 -0
  12. data/app/active_record/tekeya/attachment.rb +5 -0
  13. data/app/active_record/tekeya/notification.rb +5 -0
  14. data/app/mongoid/tekeya/activity.rb +9 -0
  15. data/app/mongoid/tekeya/attachment.rb +6 -0
  16. data/app/mongoid/tekeya/notification.rb +10 -0
  17. data/db/migrate/00_create_activities.rb +11 -0
  18. data/db/migrate/01_create_attachments.rb +13 -0
  19. data/db/migrate/02_create_notifications.rb +14 -0
  20. data/lib/tasks/resque_tasks.rake +3 -0
  21. data/lib/tekeya.rb +81 -0
  22. data/lib/tekeya/configuration.rb +48 -0
  23. data/lib/tekeya/entity.rb +432 -0
  24. data/lib/tekeya/entity/group.rb +22 -0
  25. data/lib/tekeya/errors/tekeya_error.rb +11 -0
  26. data/lib/tekeya/errors/tekeya_fatal.rb +11 -0
  27. data/lib/tekeya/errors/tekeya_non_entity.rb +6 -0
  28. data/lib/tekeya/errors/tekeya_non_group.rb +6 -0
  29. data/lib/tekeya/errors/tekeya_relation_already_exists.rb +6 -0
  30. data/lib/tekeya/errors/tekeya_relation_non_existent.rb +6 -0
  31. data/lib/tekeya/feed/activity.rb +101 -0
  32. data/lib/tekeya/feed/activity/feed_item.rb +58 -0
  33. data/lib/tekeya/feed/activity/resque.rb +56 -0
  34. data/lib/tekeya/feed/activity/resque/activity_fanout.rb +61 -0
  35. data/lib/tekeya/feed/activity/resque/delete_activity.rb +43 -0
  36. data/lib/tekeya/feed/activity/resque/feed_copy.rb +34 -0
  37. data/lib/tekeya/feed/activity/resque/untrack_feed.rb +34 -0
  38. data/lib/tekeya/feed/attachable.rb +15 -0
  39. data/lib/tekeya/feed/attachment.rb +19 -0
  40. data/lib/tekeya/feed/notification.rb +93 -0
  41. data/lib/tekeya/railtie.rb +18 -0
  42. data/lib/tekeya/version.rb +3 -0
  43. data/spec/fabricators/attachment_fabricator.rb +3 -0
  44. data/spec/fabricators/group_fabricator.rb +4 -0
  45. data/spec/fabricators/status_fabricator.rb +3 -0
  46. data/spec/fabricators/user_fabricator.rb +3 -0
  47. data/spec/orm/active_record.rb +14 -0
  48. data/spec/orm/mongoid.rb +6 -0
  49. data/spec/rails_app/Rakefile +7 -0
  50. data/spec/rails_app/app/active_record/group.rb +3 -0
  51. data/spec/rails_app/app/active_record/status.rb +3 -0
  52. data/spec/rails_app/app/active_record/user.rb +3 -0
  53. data/spec/rails_app/app/controllers/application_controller.rb +3 -0
  54. data/spec/rails_app/app/helpers/application_helper.rb +2 -0
  55. data/spec/rails_app/app/mongoid/group.rb +6 -0
  56. data/spec/rails_app/app/mongoid/status.rb +6 -0
  57. data/spec/rails_app/app/mongoid/user.rb +7 -0
  58. data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
  59. data/spec/rails_app/config.ru +4 -0
  60. data/spec/rails_app/config/application.rb +35 -0
  61. data/spec/rails_app/config/boot.rb +8 -0
  62. data/spec/rails_app/config/database.yml +21 -0
  63. data/spec/rails_app/config/environment.rb +5 -0
  64. data/spec/rails_app/config/environments/development.rb +18 -0
  65. data/spec/rails_app/config/environments/production.rb +33 -0
  66. data/spec/rails_app/config/environments/test.rb +33 -0
  67. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  68. data/spec/rails_app/config/initializers/configure_mongoid.rb +6 -0
  69. data/spec/rails_app/config/initializers/inflections.rb +15 -0
  70. data/spec/rails_app/config/initializers/resque.rb +1 -0
  71. data/spec/rails_app/config/initializers/secret_token.rb +7 -0
  72. data/spec/rails_app/config/locales/en.yml +5 -0
  73. data/spec/rails_app/config/routes.rb +58 -0
  74. data/spec/rails_app/db/migrate/100_create_users.rb +9 -0
  75. data/spec/rails_app/db/migrate/101_create_groups.rb +11 -0
  76. data/spec/rails_app/db/migrate/102_create_statuses.rb +9 -0
  77. data/spec/rails_app/db/seeds.rb +7 -0
  78. data/spec/rails_app/public/404.html +26 -0
  79. data/spec/rails_app/public/422.html +26 -0
  80. data/spec/rails_app/public/500.html +25 -0
  81. data/spec/rails_app/public/favicon.ico +0 -0
  82. data/spec/rails_app/public/index.html +241 -0
  83. data/spec/rails_app/public/robots.txt +5 -0
  84. data/spec/rails_app/script/rails +6 -0
  85. data/spec/spec_helper.rb +78 -0
  86. data/spec/tekeya/activity_spec.rb +170 -0
  87. data/spec/tekeya/entity_spec.rb +158 -0
  88. data/spec/tekeya/notification_spec.rb +49 -0
  89. data/spec/tekeya_helper.rb +7 -0
  90. data/spec/tekeya_spec.rb +51 -0
  91. data/tekeya.gemspec +22 -0
  92. metadata +231 -0
File without changes
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ .rvmrc
20
+ *.log
21
+ *.sqlite3
22
+ spec/rails_app/log
23
+ .teamocil
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format nested
3
+ --fail-fast
@@ -0,0 +1 @@
1
+ --no-private --protected lib/**/*.rb - README.md LICENSE
data/Gemfile ADDED
@@ -0,0 +1,39 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem "rails" , "~> 3.2.6"
7
+ gem 'debugger' , '~> 1.2.0'
8
+ gem 'yard' , '~> 0.8.2.1'
9
+ gem 'bluecloth'
10
+ gem 'guard-rspec'
11
+ gem 'ruby_gntp'
12
+ gem 'guard-bundler'
13
+ gem 'rb-fsevent', '~> 0.9.1'
14
+ end
15
+
16
+ group :test do
17
+ gem 'rspec' , '~> 2.11.0'
18
+ gem 'database_cleaner' , '~> 0.8.0'
19
+ gem 'fabrication' , '~> 2.3.0'
20
+ gem 'faker' , '~> 1.1.2'
21
+ gem 'simplecov' , '~> 0.7.0'
22
+ end
23
+
24
+ platforms :jruby do
25
+ gem "activerecord-jdbc-adapter"
26
+ gem "activerecord-jdbcsqlite3-adapter"
27
+ gem "jruby-openssl"
28
+ end
29
+
30
+ platforms :ruby do
31
+ gem "sqlite3"
32
+
33
+ group :mongoid do
34
+ gem "mongo", "~> 1.7.0"
35
+ gem "mongoid", "~> 3.0"
36
+ gem "bson_ext", "~> 1.7.0"
37
+ gem 'mongoid-rspec' , '~> 1.5.4'
38
+ end
39
+ end
@@ -0,0 +1,40 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'bundler' do
5
+ watch('Gemfile')
6
+ watch(/^.+\.gemspec/)
7
+ end
8
+
9
+ guard 'rspec' do
10
+ watch(%r{^spec/.+_spec\.rb$})
11
+
12
+ watch('spec/spec_helper.rb') { "spec" }
13
+
14
+ watch('lib/tekeya.rb') { "spec/tekeya_spec.rb" }
15
+ watch('lib/tekeya/configuration.rb') { "spec/tekeya_spec.rb" }
16
+ watch('lib/tekeya/railtie.rb') { "spec/tekeya_spec.rb" }
17
+
18
+ watch('lib/tekeya/entity.rb') { "spec/tekeya/entity_spec.rb" }
19
+ watch(%r{^lib/tekeya/entity/.+\.rb$}) { "spec/tekeya/entity_spec.rb" }
20
+
21
+ watch(%r{^lib/tekeya/feed/.+\.rb$}) { "spec/tekeya/feed_spec.rb" }
22
+ watch(%r{^lib/tekeya/feed/resque/.+\.rb$}) { "spec/tekeya/feed_spec.rb" }
23
+ end
24
+
25
+
26
+ guard 'rspec', :env => {'TEKEYA_ORM' => 'mongoid'} do
27
+ watch(%r{^spec/.+_spec\.rb$})
28
+
29
+ watch('spec/spec_helper.rb') { "spec" }
30
+
31
+ watch('lib/tekeya.rb') { "spec/tekeya_spec.rb" }
32
+ watch('lib/tekeya/configuration.rb') { "spec/tekeya_spec.rb" }
33
+ watch('lib/tekeya/railtie.rb') { "spec/tekeya_spec.rb" }
34
+
35
+ watch('lib/entity.rb') { "spec/tekeya/entity_spec.rb" }
36
+ watch(%r{^lib/tekeya/entity/.+\.rb$}) { "spec/tekeya/entity_spec.rb" }
37
+
38
+ watch(%r{^lib/tekeya/feed/.+\.rb$}) { "spec/tekeya/feed_spec.rb" }
39
+ watch(%r{^lib/tekeya/feed/resque/.+\.rb$}) { "spec/tekeya/feed_spec.rb" }
40
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Omar Mekky
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,37 @@
1
+ # Tekeya
2
+
3
+ **Tekeya** is a social engine for Rails applications based on Redis and RebatDB.
4
+
5
+ ## Objective
6
+
7
+ We work on social networks all the time and we almost always need to create social graphs, activity feeds and notifications that are efficient, flexible and reliable.
8
+
9
+ ## Name
10
+
11
+ **Tekeya** is an arabic word the describes a place where people meet and converse.
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'tekeya'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install tekeya
26
+
27
+ ## Usage
28
+
29
+ TODO: Write usage instructions here
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create new Pull Request
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec) do |spec|
7
+ spec.pattern = FileList['spec/**/*_spec.rb']
8
+ end
9
+
10
+ task :default => :spec
11
+
12
+ require 'yard'
13
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,8 @@
1
+ + Trim redis feeds
2
+ + Copy feeds when an entity tracks another
3
+ + Delete feeds when entity untracks another
4
+ + Write test scenarios !!!
5
+ + Create a method to handle retrieving the entity's feed (profile and own feed)
6
+ + Delete activity from Redis when deleted from the db
7
+ - test callbacks
8
+ - test notifications
@@ -0,0 +1,7 @@
1
+ module Tekeya
2
+ class Activity < ::ActiveRecord::Base
3
+ include ::Tekeya::Feed::Activity
4
+
5
+ # TODO: create migration for basic activity fields
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Tekeya
2
+ class Attachment < ::ActiveRecord::Base
3
+ include ::Tekeya::Feed::Attachment
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Tekeya
2
+ class Notification < ::ActiveRecord::Base
3
+ include ::Tekeya::Feed::Notification
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module Tekeya
2
+ class Activity
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ include ::Tekeya::Feed::Activity
6
+
7
+ field :activity_type, type: String
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module Tekeya
2
+ class Attachment
3
+ include Mongoid::Document
4
+ include ::Tekeya::Feed::Attachment
5
+ end
6
+ end
@@ -0,0 +1,10 @@
1
+ module Tekeya
2
+ class Notification
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ include ::Tekeya::Feed::Notification
6
+
7
+ field :notification_type, type: String
8
+ field :read, type: Boolean
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ class CreateActivities < ActiveRecord::Migration
2
+ def change
3
+ create_table :activities do |t|
4
+ t.string :activity_type, null: false
5
+ t.integer :entity_id, null: false
6
+ t.string :entity_type, null: false
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ class CreateAttachments < ActiveRecord::Migration
2
+ def change
3
+ create_table :attachments do |t|
4
+ t.integer :attache_id, null: false
5
+ t.string :attache_type, null: false
6
+ t.integer :attachable_id, null: false
7
+ t.string :attachable_type, null: false
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,14 @@
1
+ class CreateNotifications < ActiveRecord::Migration
2
+ def change
3
+ create_table :notifications do |t|
4
+ t.string :notification_type, null: false
5
+ t.integer :subject_id, null: false
6
+ t.string :subject_type, null: false
7
+ t.integer :entity_id, null: false
8
+ t.string :entity_type, null: false
9
+ t.boolean :read, default:false
10
+
11
+ t.timestamps
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ require 'resque/tasks'
2
+
3
+ task 'resque:setup' => :environment
@@ -0,0 +1,81 @@
1
+ require "tekeya/version"
2
+ require "tekeya/railtie"
3
+ require "active_support"
4
+
5
+ module Tekeya
6
+ extend ActiveSupport::Autoload
7
+
8
+ # Dependencies
9
+ autoload :Redis, 'redis'
10
+ autoload :Rebat, 'rebat'
11
+ autoload :Resque, 'resque'
12
+ # Modules
13
+ autoload :Configuration
14
+ autoload :Entity
15
+
16
+ module Entity
17
+ extend ActiveSupport::Autoload
18
+
19
+ autoload :Group
20
+ end
21
+
22
+ module Errors
23
+ extend ActiveSupport::Autoload
24
+
25
+ autoload :TekeyaError
26
+ autoload :TekeyaFatal
27
+ autoload :TekeyaNonEntity
28
+ autoload :TekeyaNonGroup
29
+ autoload :TekeyaRelationAlreadyExists
30
+ autoload :TekeyaRelationNonExistent
31
+ end
32
+
33
+ module Feed
34
+ extend ActiveSupport::Autoload
35
+
36
+ autoload :Activity
37
+ autoload :Attachable
38
+ autoload :Attachment
39
+ autoload :Notification
40
+
41
+ module Activity
42
+ extend ActiveSupport::Autoload
43
+
44
+ autoload :FeedItem
45
+ autoload :Resque
46
+
47
+ module Resque
48
+ extend ActiveSupport::Autoload
49
+
50
+ autoload :ActivityFanout
51
+ autoload :FeedCopy
52
+ autoload :DeleteActivity
53
+ autoload :UntrackFeed
54
+ end
55
+ end
56
+ end
57
+
58
+
59
+ # Configure Tekeya
60
+ #
61
+ # Example::
62
+ #
63
+ # Tekeya.configure do |config|
64
+ # redis_host = "localhost"
65
+ # redis_port = 9200
66
+ # flockdb_host = 9200
67
+ # flockdb_port = 9200
68
+ # end
69
+ def self.configure(&block)
70
+ yield Tekeya::Configuration.instance
71
+ Tekeya::Configuration.instance.setup_databases
72
+ end
73
+
74
+ def self.relations
75
+ return Tekeya::Configuration.instance.rebat
76
+ end
77
+
78
+ def self.redis
79
+ return Tekeya::Configuration.instance.redis
80
+ end
81
+ end
@@ -0,0 +1,48 @@
1
+ require 'erb'
2
+ require 'singleton'
3
+
4
+ module Tekeya
5
+ # Parses the configuration file and holds important configuration attributes
6
+ class Configuration
7
+ include Singleton
8
+
9
+ attr_reader :redis, :rebat
10
+ attr_accessor :redis_host, :redis_port, :rebatdb_host, :rebatdb_port, :feed_storage_orm
11
+
12
+ # @private
13
+ # Initializes a new configuration object
14
+ def initialize
15
+ parse_config_file "#{Rails.root}/config/tekeya.yml"
16
+
17
+ # Setup defaults
18
+ @redis_host ||= "localhost"
19
+ @redis_port ||= "6379"
20
+ @rebatdb_host ||= "localhost"
21
+ @rebatdb_port ||= "2011"
22
+ @feed_storage_orm ||= :active_record
23
+ end
24
+
25
+ def setup_databases
26
+ # Setup redis
27
+ @redis = Redis.new host: @redis_host, port: @redis_port.to_i
28
+
29
+ # Setup resque
30
+ Resque.redis = @redis
31
+
32
+ # Setup rebatdb
33
+ @rebat = Rebat.new "#{@rebatdb_host}", "#{@rebatdb_port}", { tracks: 1, joins: 2, blocks: 3 }
34
+ end
35
+
36
+ # Loads the configuration file
37
+ # @return [nil]
38
+ def parse_config_file(path)
39
+ return unless File.exists?(path)
40
+
41
+ conf = YAML::load(ERB.new(IO.read(path)).result)[Rails.env]
42
+
43
+ conf.each do |key,value|
44
+ self.send("#{key}=", value) if self.respond_to?("#{key}=")
45
+ end unless conf.nil?
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,432 @@
1
+ module Tekeya
2
+ # Represents a tekeya entity, the main building block of the engine
3
+ module Entity
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # Entities are attachable to activities
8
+ include ::Tekeya::Feed::Attachable
9
+ # which field should be used as a primary key
10
+ class_attribute :entity_primary_key
11
+
12
+ # default primary key
13
+ define_tekeya_primary_key :id
14
+
15
+ # define the relation with the activity
16
+ has_many :activities, as: :entity, class_name: "::Tekeya::Activity", dependent: :destroy do
17
+ # Returns activities dating up to 10 days in the past
18
+ def recent
19
+ unless ::Tekeya::Configuration.instance.feed_storage_orm == :mongoid
20
+ where("created_at > ?", 10.days.ago).order('created_at DESC')
21
+ else
22
+ criteria.where(:created_at.gte => 10.days.ago).desc('created_at')
23
+ end
24
+ end
25
+
26
+ # Any method missing invoked on activities is considered a new activity
27
+ def method_missing(meth, *args, &block)
28
+ options = args.extract_options!
29
+ bool_args = args.map{|arg| arg.respond_to?(:is_tekeya_attachable)}.uniq
30
+ is_activity = bool_args.length == 1 && bool_args.first == true
31
+
32
+ if is_activity
33
+ attachments = []
34
+
35
+ args.each do |attachable|
36
+ attachments << ::Tekeya::Attachment.new(attachable: attachable)
37
+ end
38
+
39
+ create(activity_type: meth, attachments: attachments, group_with_recent: options[:group].nil? ? true : options[:group])
40
+ else
41
+ super
42
+ end
43
+ end
44
+ end
45
+
46
+ has_many :notifications, as: :entity, class_name: "::Tekeya::Notification", dependent: :destroy do
47
+ def unread
48
+ unless ::Tekeya::Configuration.instance.feed_storage_orm == :mongoid
49
+ where(read: false).order('created_at DESC')
50
+ else
51
+ criteria.where(read: false).desc('created_at')
52
+ end
53
+ end
54
+
55
+ # Any method missing invoked on activities is considered a new activity
56
+ def method_missing(meth, *args, &block)
57
+ options = args.extract_options!
58
+ bool_args = args.map{|arg| arg.respond_to?(:is_tekeya_attachable)}.uniq
59
+ is_notification = bool_args.length == 1 && bool_args.first == true
60
+
61
+ if is_notification
62
+ actors = []
63
+
64
+ args.each do |attachable|
65
+ actors << ::Tekeya::Attachment.new(attachable: attachable)
66
+ end
67
+
68
+ subject = @association.nil? ? base : @association.owner
69
+ create(notification_type: meth, subject: options[:subject].nil? ? subject : options[:subject], actors: actors, group_with_recent: options[:group].nil? ? true : options[:group])
70
+ else
71
+ super
72
+ end
73
+ end
74
+ end
75
+
76
+ # define some callbacks
77
+ define_callbacks :track_entity, :untrack_entity, :join_group, :leave_group, :block_entity, :unblock_entity
78
+ end
79
+
80
+ module ClassMethods
81
+ # Sets an after callback to be run after an entity is tracked
82
+ #
83
+ # @param [Symbol, String] callback the method to be run
84
+ def after_tracking_entity(callback)
85
+ set_callback :track_entity, :after, callback
86
+ end
87
+
88
+ # Sets an after callback to be run after an entity is untracked
89
+ #
90
+ # @param [Symbol, String] callback the method to be run
91
+ def after_untracking_entity(callback)
92
+ set_callback :untrack_entity, :after, callback
93
+ end
94
+
95
+ # Sets an after callback to be run after a group is joined
96
+ #
97
+ # @param [Symbol, String] callback the method to be run
98
+ def after_joining_group(callback)
99
+ set_callback :join_group, :after, callback
100
+ end
101
+
102
+ # Sets an after callback to be run after a group is left
103
+ #
104
+ # @param [Symbol, String] callback the method to be run
105
+ def after_leaving_group(callback)
106
+ set_callback :leave_group, :after, callback
107
+ end
108
+
109
+ # Sets an after callback to be run after an entity is blocked
110
+ #
111
+ # @param [Symbol, String] callback the method to be run
112
+ def after_blocking_entity(callback)
113
+ set_callback :block_entity, :after, callback
114
+ end
115
+
116
+ # Sets an after callback to be run after an entity is unblocked
117
+ #
118
+ # @param [Symbol, String] callback the method to be run
119
+ def after_unblocking_entity(callback)
120
+ set_callback :unblock_entity, :after, callback
121
+ end
122
+
123
+ # Defines the primary key for Tekeya to use in relations
124
+ #
125
+ # @param [Symbol] key the field to use as a primary key
126
+ def define_tekeya_primary_key(key)
127
+ self.entity_primary_key = key
128
+ end
129
+ end
130
+
131
+ # Tracks the given entity and copies it's recent feed to the tracker feed
132
+ #
133
+ # @param [Entity] entity the entity to track
134
+ # @param [Boolean] notify determines whether the tracked entity should be notified
135
+ # @return [Boolean]
136
+ def track(entity, notify=true)
137
+ run_callbacks :track_entity do
138
+ check_if_tekeya_entity(entity)
139
+ raise ::Tekeya::Errors::TekeyaRelationAlreadyExists.new("Already tracking #{entity}") if self.tracks?(entity)
140
+
141
+ ret = add_tekeya_relation(self, entity, :tracks)
142
+
143
+ if ret
144
+ ::Resque.enqueue(::Tekeya::Feed::Activity::Resque::FeedCopy, entity.profile_feed_key, self.feed_key)
145
+
146
+ activity = self.activities.tracked(entity)
147
+ entity.notifications.tracked_by self if notify
148
+ end
149
+
150
+ return ret
151
+ end
152
+ end
153
+
154
+ # Return a list of entities being tracked by this entity
155
+ #
156
+ # @param [String] type used to return a certain type of entities being tracked
157
+ # @return [Array] the entities tracked by this entity
158
+ def tracking(type = nil)
159
+ tekeya_relations_of(self, :tracks, type)
160
+ end
161
+
162
+ # Returns a list of entities tracking this entity
163
+ #
164
+ # @param [String] type used to return a certain type of entities being tracked
165
+ # @return [Array] the entities tracking this entity
166
+ def trackers(type = nil)
167
+ tekeya_relations_of(self, :tracks, type, true)
168
+ end
169
+
170
+ # Checks if this entity is tracking the given entity
171
+ #
172
+ # @param [Entity] entity the entity to check
173
+ # @return [Boolean] true if this entity is tracking the given entity, false otherwise
174
+ def tracks?(entity)
175
+ check_if_tekeya_entity(entity)
176
+ tekeya_relation_exists?(self, entity, :tracks)
177
+ end
178
+
179
+ # Untracks the given entity and deletes recent activities of the untracked entity from this entity's feed
180
+ #
181
+ # @param [Entity] entity the entity to untrack
182
+ # @return [Boolean]
183
+ def untrack(entity)
184
+ run_callbacks :untrack_entity do
185
+ check_if_tekeya_entity(entity)
186
+ raise ::Tekeya::Errors::TekeyaRelationNonExistent.new("Can't untrack an untracked entity") unless self.tracks?(entity)
187
+
188
+ ret = delete_tekeya_relation(self, entity, :tracks)
189
+
190
+ ::Resque.enqueue(::Tekeya::Feed::Activity::Resque::UntrackFeed, entity.profile_feed_key, self.feed_key) if ret
191
+
192
+ return ret
193
+ end
194
+ end
195
+
196
+ # Blocks the given entity and removes any tracking relation between both entities
197
+ #
198
+ # @param [Entity] entity the entity to block
199
+ # @return [Boolean]
200
+ def block(entity)
201
+ run_callbacks :block_entity do
202
+ check_if_tekeya_entity(entity)
203
+ raise ::Tekeya::Errors::TekeyaRelationAlreadyExists.new("Already blocking #{entity}") if self.blocks?(entity)
204
+
205
+ unless entity.is_tekeya_group?
206
+ self.untrack(entity) if self.tracks?(entity)
207
+ entity.untrack(self) if entity.tracks?(self)
208
+ end
209
+
210
+ add_tekeya_relation(self, entity, :blocks)
211
+ end
212
+ end
213
+
214
+ # Returns a list of entities blocked by this entity
215
+ #
216
+ # @param [String] type used to return a certain type of entities blocked
217
+ # @return [Array] the entities blocked by this entity
218
+ def blocked(type = nil)
219
+ tekeya_relations_of(self, :blocks, type)
220
+ end
221
+
222
+ # Checks if this entity is blocking the given entity
223
+ #
224
+ # @param [Entity] entity the entity to check
225
+ # @return [Boolean] true if this entity is blocking the given entity, false otherwise
226
+ def blocks?(entity)
227
+ check_if_tekeya_entity(entity)
228
+ tekeya_relation_exists?(self, entity, :blocks)
229
+ end
230
+
231
+ # Unblock the given entity
232
+ #
233
+ # @param [Entity] entity the entity to unblock
234
+ # @return [Boolean]
235
+ def unblock(entity)
236
+ run_callbacks :unblock_entity do
237
+ check_if_tekeya_entity(entity)
238
+ raise ::Tekeya::Errors::TekeyaRelationNonExistent.new("Can't unblock an unblocked entity") unless self.blocks?(entity)
239
+
240
+ delete_tekeya_relation(self, entity, :blocks)
241
+ end
242
+ end
243
+
244
+ # Joins the given group and tracks it
245
+ #
246
+ # @note will automatically track the group
247
+ # @param [Group] group the group to track
248
+ # @param [Boolean] track_also if set to false automatic tracking is disabled
249
+ # @param [Boolean] notify determines whether the joined group's owner should be notified
250
+ # @return [Boolean]
251
+ def join(group, track_also = true, notify=true)
252
+ run_callbacks :join_group do
253
+ check_if_tekeya_group(group)
254
+ raise ::Tekeya::Errors::TekeyaRelationAlreadyExists.new("Already a member of #{group}") if self.member_of?(group)
255
+
256
+ ret = add_tekeya_relation(self, group, :joins)
257
+ ret &= self.track(group, false) if track_also && !self.tracks?(group) && ret
258
+
259
+ if ret
260
+ activity = self.activities.joined(group)
261
+ group.owner.notifications.joined_by self, subject: group if notify
262
+ end
263
+
264
+ return ret
265
+ end
266
+ end
267
+
268
+ # Return a list of groups joined by this entity
269
+ #
270
+ # @param [String] type used to return a certain type of groups joined
271
+ # @return [Array] the groups joined by this entity
272
+ def groups(type = nil)
273
+ tekeya_relations_of(self, :joins, type)
274
+ end
275
+
276
+ # Checks if this entity is a member of the given group
277
+ #
278
+ # @param [Group] group the group to check
279
+ # @return [Boolean] true if this entity is a member of the given group, false otherwise
280
+ def member_of?(group)
281
+ check_if_tekeya_group(group)
282
+ tekeya_relation_exists?(self, group, :joins)
283
+ end
284
+
285
+ # Leaves the given group and untracks it
286
+ #
287
+ # @param [Group] group the group to untrack
288
+ # @return [Boolean]
289
+ def leave(group)
290
+ run_callbacks :leave_group do
291
+ check_if_tekeya_group(group)
292
+ raise ::Tekeya::Errors::TekeyaRelationNonExistent.new("Can't leave an unjoined group") unless self.member_of?(group)
293
+
294
+ ret = delete_tekeya_relation(self, group, :joins)
295
+ ret &= self.untrack(group) if self.tracks?(group) && ret
296
+
297
+ return ret
298
+ end
299
+ end
300
+
301
+ # Returns the entity's recent activities
302
+ #
303
+ # @return [Array] the list of recent activities by this entity
304
+ def profile_feed
305
+ acts = []
306
+ pkey = self.profile_feed_key
307
+ recent_activities_count = ::Tekeya.redis.zcard(pkey)
308
+
309
+ # Check if the cache is not empty
310
+ if recent_activities_count > 0
311
+ # Retrieve the aggregate keys from redis
312
+ acts_keys = ::Tekeya.redis.zrange(pkey, 0, -1)
313
+ # Retrieve the aggregates
314
+ acts_keys.each do |act_key|
315
+ acts << ::Tekeya::Feed::Activity::FeedItem.from_redis(act_key, self)
316
+ end
317
+ else
318
+ # Retrieve the activities from the DB
319
+ db_recent_activities = self.activities.recent
320
+ db_recent_activities.each do |activity|
321
+ acts << ::Tekeya::Feed::Activity::FeedItem.from_db(activity, self)
322
+ end
323
+ end
324
+
325
+ return acts
326
+ end
327
+
328
+ # Returns the entity's feed
329
+ #
330
+ # @return [Array] the list of activities for the entities tracked by this entity
331
+ def feed
332
+ acts = []
333
+ fkey = self.feed_key
334
+ recent_activities_count = ::Tekeya.redis.zcard(fkey)
335
+
336
+ # Check if the cache is not empty
337
+ if recent_activities_count > 0
338
+ # Retrieve the aggregate keys from redis
339
+ acts_keys = ::Tekeya.redis.zrange(fkey, 0, -1)
340
+ # Retrieve the aggregates
341
+ acts_keys.each do |act_key|
342
+ acts << ::Tekeya::Feed::Activity::FeedItem.from_redis(act_key, self)
343
+ end
344
+ else
345
+ # Retrieve the activities from the DB
346
+ self.tracking.each do |tracker|
347
+ db_recent_activities = tracker.activities.recent
348
+ db_recent_activities.each do |activity|
349
+ acts << ::Tekeya::Feed::Activity::FeedItem.from_db(activity, tracker)
350
+ end
351
+ end
352
+ end
353
+
354
+ return acts
355
+ end
356
+
357
+ # @private
358
+ # Returns a unique key for the entity's profile feed in redis
359
+ def profile_feed_key
360
+ "#{self.class.name}:#{self.send(self.entity_primary_key)}:profile:feed"
361
+ end
362
+
363
+ # @private
364
+ # Returns a unique key for the entity's feed in redis
365
+ def feed_key
366
+ "#{self.class.name}:#{self.send(self.entity_primary_key)}:feed"
367
+ end
368
+
369
+ # A method to identify the entity
370
+ #
371
+ # @return [Boolean] true
372
+ def is_tekeya_entity?
373
+ return true
374
+ end
375
+
376
+ # A method to identify the entity as a non group
377
+ #
378
+ # @return [Boolean] false
379
+ def is_tekeya_group?
380
+ return false
381
+ end
382
+
383
+ private
384
+
385
+ # @private
386
+ # Checks if the given argument is an entity and raises an error if its not
387
+ def check_if_tekeya_entity(entity)
388
+ raise ::Tekeya::Errors::TekeyaNonEntity.new("Supplied argument is not a Tekeya::Entity") unless entity.present? && entity.is_tekeya_entity?
389
+ end
390
+
391
+ # @private
392
+ # Checks if the given argument is a group and raises an error if its not
393
+ def check_if_tekeya_group(group)
394
+ raise ::Tekeya::Errors::TekeyaNonGroup.new("Supplied argument is not a Tekeya::Entity::Group") unless group.present? && group.is_tekeya_group?
395
+ end
396
+
397
+ # @private
398
+ # Adds a rebat relation
399
+ def add_tekeya_relation(from, to, type)
400
+ ::Tekeya.relations.add(from.send(from.class.entity_primary_key), from.class.name, to.send(to.class.entity_primary_key), to.class.name, 0, type)
401
+ end
402
+
403
+ # @private
404
+ # Deletes a rebat relation
405
+ def delete_tekeya_relation(from, to, type)
406
+ ::Tekeya.relations.delete(from.send(from.class.entity_primary_key), from.class.name, to.send(to.class.entity_primary_key), to.class.name, type)
407
+ end
408
+
409
+ # @private
410
+ # Retrieves rebat relations
411
+ def tekeya_relations_of(from, relation_type, entity_type, reverse = false)
412
+ result_entity_class = entity_type.safe_constantize if entity_type
413
+ unless reverse
414
+ ::Tekeya.relations.where(from.send(from.class.entity_primary_key), from.class.name, nil, entity_type, relation_type).entries.map do |entry|
415
+ result_entity_class ||= entry.toEntityType.safe_constantize
416
+ result_entity_class.where(:"#{result_entity_class.entity_primary_key}" => entry.toEntityId).first
417
+ end
418
+ else
419
+ ::Tekeya.relations.where(nil, entity_type, from.send(from.class.entity_primary_key), from.class.name, relation_type).entries.map do |entry|
420
+ result_entity_class ||= entry.fromEntityType.safe_constantize
421
+ result_entity_class.where(:"#{result_entity_class.entity_primary_key}" => entry.fromEntityId).first
422
+ end
423
+ end
424
+ end
425
+
426
+ # @private
427
+ # Checks if a rebat relation exists
428
+ def tekeya_relation_exists?(from, to, type)
429
+ !::Tekeya.relations.where(from.send(from.class.entity_primary_key), from.class.name, to.send(to.class.entity_primary_key), to.class.name, type).entries.empty?
430
+ end
431
+ end
432
+ end