tekeya 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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