tekeya 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|