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.
- data/.document +0 -0
- data/.gitignore +23 -0
- data/.rspec +3 -0
- data/.yardopts +1 -0
- data/Gemfile +39 -0
- data/Guardfile +40 -0
- data/LICENSE +22 -0
- data/README.md +37 -0
- data/Rakefile +13 -0
- data/TODO.todo +8 -0
- data/app/active_record/tekeya/activity.rb +7 -0
- data/app/active_record/tekeya/attachment.rb +5 -0
- data/app/active_record/tekeya/notification.rb +5 -0
- data/app/mongoid/tekeya/activity.rb +9 -0
- data/app/mongoid/tekeya/attachment.rb +6 -0
- data/app/mongoid/tekeya/notification.rb +10 -0
- data/db/migrate/00_create_activities.rb +11 -0
- data/db/migrate/01_create_attachments.rb +13 -0
- data/db/migrate/02_create_notifications.rb +14 -0
- data/lib/tasks/resque_tasks.rake +3 -0
- data/lib/tekeya.rb +81 -0
- data/lib/tekeya/configuration.rb +48 -0
- data/lib/tekeya/entity.rb +432 -0
- data/lib/tekeya/entity/group.rb +22 -0
- data/lib/tekeya/errors/tekeya_error.rb +11 -0
- data/lib/tekeya/errors/tekeya_fatal.rb +11 -0
- data/lib/tekeya/errors/tekeya_non_entity.rb +6 -0
- data/lib/tekeya/errors/tekeya_non_group.rb +6 -0
- data/lib/tekeya/errors/tekeya_relation_already_exists.rb +6 -0
- data/lib/tekeya/errors/tekeya_relation_non_existent.rb +6 -0
- data/lib/tekeya/feed/activity.rb +101 -0
- data/lib/tekeya/feed/activity/feed_item.rb +58 -0
- data/lib/tekeya/feed/activity/resque.rb +56 -0
- data/lib/tekeya/feed/activity/resque/activity_fanout.rb +61 -0
- data/lib/tekeya/feed/activity/resque/delete_activity.rb +43 -0
- data/lib/tekeya/feed/activity/resque/feed_copy.rb +34 -0
- data/lib/tekeya/feed/activity/resque/untrack_feed.rb +34 -0
- data/lib/tekeya/feed/attachable.rb +15 -0
- data/lib/tekeya/feed/attachment.rb +19 -0
- data/lib/tekeya/feed/notification.rb +93 -0
- data/lib/tekeya/railtie.rb +18 -0
- data/lib/tekeya/version.rb +3 -0
- data/spec/fabricators/attachment_fabricator.rb +3 -0
- data/spec/fabricators/group_fabricator.rb +4 -0
- data/spec/fabricators/status_fabricator.rb +3 -0
- data/spec/fabricators/user_fabricator.rb +3 -0
- data/spec/orm/active_record.rb +14 -0
- data/spec/orm/mongoid.rb +6 -0
- data/spec/rails_app/Rakefile +7 -0
- data/spec/rails_app/app/active_record/group.rb +3 -0
- data/spec/rails_app/app/active_record/status.rb +3 -0
- data/spec/rails_app/app/active_record/user.rb +3 -0
- data/spec/rails_app/app/controllers/application_controller.rb +3 -0
- data/spec/rails_app/app/helpers/application_helper.rb +2 -0
- data/spec/rails_app/app/mongoid/group.rb +6 -0
- data/spec/rails_app/app/mongoid/status.rb +6 -0
- data/spec/rails_app/app/mongoid/user.rb +7 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
- data/spec/rails_app/config.ru +4 -0
- data/spec/rails_app/config/application.rb +35 -0
- data/spec/rails_app/config/boot.rb +8 -0
- data/spec/rails_app/config/database.yml +21 -0
- data/spec/rails_app/config/environment.rb +5 -0
- data/spec/rails_app/config/environments/development.rb +18 -0
- data/spec/rails_app/config/environments/production.rb +33 -0
- data/spec/rails_app/config/environments/test.rb +33 -0
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_app/config/initializers/configure_mongoid.rb +6 -0
- data/spec/rails_app/config/initializers/inflections.rb +15 -0
- data/spec/rails_app/config/initializers/resque.rb +1 -0
- data/spec/rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/rails_app/config/locales/en.yml +5 -0
- data/spec/rails_app/config/routes.rb +58 -0
- data/spec/rails_app/db/migrate/100_create_users.rb +9 -0
- data/spec/rails_app/db/migrate/101_create_groups.rb +11 -0
- data/spec/rails_app/db/migrate/102_create_statuses.rb +9 -0
- data/spec/rails_app/db/seeds.rb +7 -0
- data/spec/rails_app/public/404.html +26 -0
- data/spec/rails_app/public/422.html +26 -0
- data/spec/rails_app/public/500.html +25 -0
- data/spec/rails_app/public/favicon.ico +0 -0
- data/spec/rails_app/public/index.html +241 -0
- data/spec/rails_app/public/robots.txt +5 -0
- data/spec/rails_app/script/rails +6 -0
- data/spec/spec_helper.rb +78 -0
- data/spec/tekeya/activity_spec.rb +170 -0
- data/spec/tekeya/entity_spec.rb +158 -0
- data/spec/tekeya/notification_spec.rb +49 -0
- data/spec/tekeya_helper.rb +7 -0
- data/spec/tekeya_spec.rb +51 -0
- data/tekeya.gemspec +22 -0
- metadata +231 -0
data/.document
ADDED
|
File without changes
|
data/.gitignore
ADDED
|
@@ -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
data/.yardopts
ADDED
|
@@ -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
|
data/Guardfile
ADDED
|
@@ -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.
|
data/README.md
ADDED
|
@@ -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
|
data/Rakefile
ADDED
|
@@ -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
|
data/TODO.todo
ADDED
|
@@ -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,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
|
data/lib/tekeya.rb
ADDED
|
@@ -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
|