adva_activity 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.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/adva_activity.gemspec +19 -0
- data/app/assets/stylesheets/adva_activity/admin/activities.css +81 -0
- data/app/helpers/activities_helper.rb +73 -0
- data/app/models/activity.rb +87 -0
- data/app/models/activity_notifier.rb +11 -0
- data/app/observers/activities/activity_observer.rb +25 -0
- data/app/observers/activities/article_observer.rb +20 -0
- data/app/observers/activities/comment_observer.rb +29 -0
- data/app/observers/activities/logger.rb +94 -0
- data/app/observers/activities/section_observer.rb +9 -0
- data/app/views/activity_notifier/new_content_notification.html.erb +5 -0
- data/app/views/admin/activities/_activities.html.erb +10 -0
- data/app/views/admin/activities/_comment.html.erb +35 -0
- data/app/views/admin/activities/_content.html.erb +15 -0
- data/app/views/admin/activities/_topic.html.erb +16 -0
- data/config/locales/en.yml +22 -0
- data/db/migrate/20080401000000_create_activities_table.rb +23 -0
- data/lib/adva_activity.rb +33 -0
- data/lib/adva_activity/version.rb +3 -0
- data/test/test_helper.rb +3 -0
- data/test/unit/activities_notifier_test.rb +75 -0
- data/test/unit/activity_observer_test.rb +65 -0
- data/test/unit/activity_test.rb +84 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 965ecb6fbf299a3e716179dbe3ac1b72dbcc24d4
|
4
|
+
data.tar.gz: 3f6dbbb3828849f4895c76e4913a3302dd2e4a3a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fc05fa3e5a2c9b94f65903a66b90517161037fc4a086c57bdce6e453fdff5455babcbc24ba34a52b5202adb6ae49ce277b6479ccd7296124c09bfea36b56a357
|
7
|
+
data.tar.gz: 10e9ad8ae619b7af5eee9101452278acbf8682f1251e4cca442445edec5ee1c965198bea60ae917b01bc1f16003415f343e98b0f2ca20cb61af47bee67976579
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Micah Geisel
|
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,29 @@
|
|
1
|
+
# AdvaActivity
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'adva_activity'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install adva_activity
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/adva_activity/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Micah Geisel"]
|
6
|
+
gem.email = ["micah@botandrose.com"]
|
7
|
+
gem.description = %q{Adva Activity}
|
8
|
+
gem.summary = %q{Engine for Adva CMS activity feed}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "adva_activity"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = AdvaActivity::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency "rails-observers"
|
19
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
.activities h3 {
|
2
|
+
font-size: 12pt;
|
3
|
+
}
|
4
|
+
.activities h4,
|
5
|
+
.activities p,
|
6
|
+
.activities blockquote {
|
7
|
+
margin: 0px 0px 3px 0px;
|
8
|
+
font-weight: normal;
|
9
|
+
}
|
10
|
+
.activities ul {
|
11
|
+
margin-top: 10px;
|
12
|
+
list-style-type: none;
|
13
|
+
}
|
14
|
+
.activities blockquote {
|
15
|
+
font-style: italic;
|
16
|
+
}
|
17
|
+
.activities .meta {
|
18
|
+
clear: right;
|
19
|
+
float: right;
|
20
|
+
margin: 0;
|
21
|
+
padding-bottom: 0;
|
22
|
+
text-align: right;
|
23
|
+
}
|
24
|
+
.activities .meta,
|
25
|
+
.activities .meta a {
|
26
|
+
font-size: 12px;
|
27
|
+
color: #888;
|
28
|
+
line-height: 20px;
|
29
|
+
}
|
30
|
+
.activities li.empty {
|
31
|
+
margin-top: 10px;
|
32
|
+
}
|
33
|
+
|
34
|
+
.activities .activity {
|
35
|
+
padding: 10px 10px 10px 35px;
|
36
|
+
}
|
37
|
+
.activities .activity.highlight {
|
38
|
+
background-color: #f6f6f6;
|
39
|
+
}
|
40
|
+
|
41
|
+
.activity.comment_created {
|
42
|
+
background: url(/assets/adva_cms/icons/comment_add.png) no-repeat 10px 11px;
|
43
|
+
}
|
44
|
+
.activity.comment_edited {
|
45
|
+
background: url(/assets/adva_cms/icons/comment_edit.png) no-repeat 10px 11px;
|
46
|
+
}
|
47
|
+
.activity.comment_deleted {
|
48
|
+
background: url(/assets/adva_cms/icons/comment_delete.png) no-repeat 10px 11px;
|
49
|
+
}
|
50
|
+
.activity.comment_approved {
|
51
|
+
background: url(/assets/adva_cms/icons/comment.png) no-repeat 10px 11px;
|
52
|
+
}
|
53
|
+
.activity.comment_unapproved {
|
54
|
+
background: url(/assets/adva_cms/icons/comment.png) no-repeat 10px 11px;
|
55
|
+
}
|
56
|
+
|
57
|
+
.activity.article_created {
|
58
|
+
background: url(/assets/adva_cms/icons/page_add.png) no-repeat 10px 11px;
|
59
|
+
}
|
60
|
+
.activity.article_revised {
|
61
|
+
background: url(/assets/adva_cms/icons/page_edit.png) no-repeat 10px 11px;
|
62
|
+
}
|
63
|
+
.activity.article_published {
|
64
|
+
background: url(/assets/adva_cms/icons/page_go.png) no-repeat 10px 11px;
|
65
|
+
}
|
66
|
+
.activity.article_unpublished {
|
67
|
+
background: url(/assets/adva_cms/icons/page_red.png) no-repeat 10px 11px;
|
68
|
+
}
|
69
|
+
.activity.article_deleted {
|
70
|
+
background: url(/assets/adva_cms/icons/page_delete.png) no-repeat 10px 11px;
|
71
|
+
}
|
72
|
+
|
73
|
+
.activity.wikipage_created {
|
74
|
+
background: url(/assets/adva_cms/icons/page_white_add.png) no-repeat 10px 11px;
|
75
|
+
}
|
76
|
+
.activity.wikipage_revised {
|
77
|
+
background: url(/assets/adva_cms/icons/page_white_edit.png) no-repeat 10px 11px;
|
78
|
+
}
|
79
|
+
.activity.wikipage_deleted {
|
80
|
+
background: url(/assets/adva_cms/icons/page_white_delete.png) no-repeat 10px 11px;
|
81
|
+
}
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module ActivitiesHelper
|
2
|
+
def render_activities(activities, recent = false)
|
3
|
+
if activities.present?
|
4
|
+
html = activities.collect do |activity|
|
5
|
+
render :partial => "admin/activities/#{activity.object_type.downcase}",
|
6
|
+
:locals => { :activity => activity, :recent => recent }
|
7
|
+
end.join
|
8
|
+
else
|
9
|
+
html = %(<li class="empty shade">#{I18n.t(:'adva.activity.none')}.</li>)
|
10
|
+
end
|
11
|
+
raw %(<ul class="activities">#{html}</ul>)
|
12
|
+
end
|
13
|
+
|
14
|
+
def activity_css_classes(activity)
|
15
|
+
type = activity.object_attributes['type'] || activity.object_type
|
16
|
+
"#{type}_#{activity.all_actions.last}".downcase
|
17
|
+
# activity.all_actions.collect {|action| "#{type}-#{action}".downcase }.uniq * ' '
|
18
|
+
end
|
19
|
+
|
20
|
+
def activity_datetime(activity, short = false)
|
21
|
+
if activity.from and short
|
22
|
+
from = activity.from.to_s(:time_only)
|
23
|
+
to = activity.to.to_s(:time_only)
|
24
|
+
"#{from} - #{to}"
|
25
|
+
elsif activity.from and activity.from.to_date != activity.to.to_date
|
26
|
+
from = activity.from.to_ordinalized_s(:plain)
|
27
|
+
to = activity.to.to_ordinalized_s(:plain)
|
28
|
+
"#{from} - #{to}"
|
29
|
+
elsif activity.from
|
30
|
+
from = activity.from.to_ordinalized_s(:plain)
|
31
|
+
to = activity.to.to_ordinalized_s(:time_only)
|
32
|
+
"#{from} - #{to}"
|
33
|
+
else
|
34
|
+
activity.created_at.send *(short ? [:to_s, :time_only] : [:to_ordinalized_s, :plain])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# def activity_datetime(activity, short = false)
|
39
|
+
# from, to = if activity.from && short
|
40
|
+
# [l(activity.from, :format => :time), l(activity.to, :format => :time)]
|
41
|
+
# elsif activity.from && activity.from.to_date != activity.to.to_date
|
42
|
+
# [l(activity.from, :format => :short), l(activity.to, :format => :short)]
|
43
|
+
# elsif activity.from
|
44
|
+
# [l(activity.from, :format => :short), l(activity.to, :format => :time)]
|
45
|
+
# end
|
46
|
+
|
47
|
+
# t(:'adva.activity.from_to', :from => from, :to => to)
|
48
|
+
# end
|
49
|
+
|
50
|
+
def activity_object_edit_url(activity)
|
51
|
+
type = activity.object_attributes['type'] || activity.object_type
|
52
|
+
send "edit_admin_#{type}_path".downcase, activity.site_id, activity.section_id, activity.object_id
|
53
|
+
end
|
54
|
+
|
55
|
+
# FIXME not used anywhere?
|
56
|
+
# def activity_commentable_edit_url(activity)
|
57
|
+
# type = activity.object_attributes['commentable_type']
|
58
|
+
# send "edit_admin_#{type}_path".downcase, activity.site_id, activity.section_id, activity.commentable_id
|
59
|
+
# end
|
60
|
+
|
61
|
+
# FIXME not used anywhere?
|
62
|
+
# def link_to_activity_commentable(activity)
|
63
|
+
# link_to truncate(activity.commentable_title, 100), activity_commentable_url(activity)
|
64
|
+
# end
|
65
|
+
|
66
|
+
def link_to_activity_user(activity)
|
67
|
+
if activity.author.registered?
|
68
|
+
link_to activity.author_name, admin_site_user_path(activity.site, activity.author)
|
69
|
+
else
|
70
|
+
activity.author_link(:include_email => true)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class Activity < ActiveRecord::Base
|
2
|
+
belongs_to :site
|
3
|
+
belongs_to :section
|
4
|
+
belongs_to :object, :polymorphic => true
|
5
|
+
|
6
|
+
def method_missing_with_object_attributes(name, *args)
|
7
|
+
attrs = self[:object_attributes]
|
8
|
+
return attrs[name.to_s] if attrs && attrs.has_key?(name.to_s)
|
9
|
+
method_missing_without_object_attributes name, *args
|
10
|
+
end
|
11
|
+
alias_method_chain :method_missing, :object_attributes
|
12
|
+
|
13
|
+
belongs_to_author
|
14
|
+
|
15
|
+
serialize :actions
|
16
|
+
serialize :object_attributes
|
17
|
+
|
18
|
+
validates_presence_of :site, :section, :object
|
19
|
+
|
20
|
+
attr_accessor :siblings
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def find_coinciding_grouped_by_dates(*dates)
|
24
|
+
groups = (1..dates.size).collect{[]}
|
25
|
+
activities = find_coinciding #, :include => :user
|
26
|
+
|
27
|
+
# collect activities for the given dates
|
28
|
+
activities.each do |activity|
|
29
|
+
activity_date = activity.created_at.to_date
|
30
|
+
dates.each_with_index {|date, i| groups[i] << activity and break if activity_date == date }
|
31
|
+
end
|
32
|
+
|
33
|
+
# remove all found activities from the original resultset
|
34
|
+
groups.each{|group| group.each{ |activity| activities.delete(activity) }}
|
35
|
+
|
36
|
+
# push remaining resultset as a group itself (i.e. 'the rest of them')
|
37
|
+
groups << activities
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_coinciding(options = {})
|
41
|
+
activities = order(created_at: :desc).limit(50)
|
42
|
+
activities = activities.group_by{|r| "#{r.object_type}#{r.object_id}"}.values
|
43
|
+
activities = group_coinciding(activities)
|
44
|
+
activities.sort{|a, b| b.created_at <=> a.created_at }
|
45
|
+
end
|
46
|
+
|
47
|
+
def group_coinciding(activities, delta = nil)
|
48
|
+
activities.inject [] do |chunks, group|
|
49
|
+
chunks << group.shift
|
50
|
+
group.each do |activity|
|
51
|
+
last = chunks.last.siblings.last || chunks.last
|
52
|
+
if last.coincides_with?(activity, delta)
|
53
|
+
chunks.last.siblings << activity
|
54
|
+
else
|
55
|
+
chunks << activity
|
56
|
+
end
|
57
|
+
end
|
58
|
+
chunks
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
after_initialize do
|
64
|
+
@siblings = []
|
65
|
+
end
|
66
|
+
|
67
|
+
def coincides_with?(other, delta = nil)
|
68
|
+
delta ||= 1.hour
|
69
|
+
created_at - other.created_at <= delta.to_i
|
70
|
+
end
|
71
|
+
|
72
|
+
# FIXME should be translated!
|
73
|
+
def all_actions
|
74
|
+
actions = Array(siblings.reverse.map(&:actions).compact.flatten) + self.actions
|
75
|
+
previous = nil
|
76
|
+
actions.reject! { |action| (action == previous).tap { previous = action } }
|
77
|
+
actions
|
78
|
+
end
|
79
|
+
|
80
|
+
def from
|
81
|
+
siblings.last.created_at if siblings.present?
|
82
|
+
end
|
83
|
+
|
84
|
+
def to
|
85
|
+
created_at
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class ActivityNotifier < ActionMailer::Base
|
2
|
+
helper :content
|
3
|
+
|
4
|
+
def new_content_notification(activity, user)
|
5
|
+
recipients user.email
|
6
|
+
subject "[#{activity.site.name} / #{activity.section.title}] " +
|
7
|
+
I18n.t( :'adva.activity.notifier.new', :activity => activity.object.class )
|
8
|
+
from "#{activity.site.name} <#{activity.site.email}>"
|
9
|
+
body :activity => activity
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Activities
|
2
|
+
class ActivityObserver < ActiveRecord::Observer
|
3
|
+
observe :activity
|
4
|
+
|
5
|
+
def after_create(activity)
|
6
|
+
self.class.send(:notify_subscribers, activity)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
class << self
|
11
|
+
def notify_subscribers(activity)
|
12
|
+
find_subscribers(activity).each do |subscriber|
|
13
|
+
ActivityNotifier.deliver_new_content_notification(activity, subscriber) if activity.site.email_notification
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_subscribers(activity)
|
18
|
+
[].tap do |subscribers|
|
19
|
+
subscribers << User.by_role_and_context(:admin, activity.site)
|
20
|
+
subscribers << User.by_role_and_context(:superuser, activity.site)
|
21
|
+
end.flatten
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Activities
|
2
|
+
class ArticleObserver < Activities::Logger
|
3
|
+
observe :article
|
4
|
+
|
5
|
+
logs_activity :attributes => [ :title, :type ] do |log|
|
6
|
+
log.revised :if => [:save_version?, {:not => :new_record?}]
|
7
|
+
log.published :if => [:published_at_changed?, :published?]
|
8
|
+
log.unpublished :if => [:published_at_changed?, {:not => :published?}]
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize_activity(record)
|
12
|
+
super.tap do |activity|
|
13
|
+
activity.site = record.site
|
14
|
+
activity.section = record.section
|
15
|
+
activity.author = record.author
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
if defined?(Comment)
|
2
|
+
module Activities
|
3
|
+
class CommentObserver < Activities::Logger
|
4
|
+
|
5
|
+
observe :comment
|
6
|
+
|
7
|
+
logs_activity do |log|
|
8
|
+
log.edited :if => [:body_changed?, {:not => :new_record?}]
|
9
|
+
log.approved :if => [:approved_changed?, :approved?]
|
10
|
+
log.unapproved :if => [:approved_changed?, :unapproved?]
|
11
|
+
end
|
12
|
+
|
13
|
+
def collect_activity_attributes(record)
|
14
|
+
attrs = record.send(:clone_attributes)
|
15
|
+
attrs = attrs.slice 'commentable_id', 'body', 'author_name', 'author_email', 'author_url'
|
16
|
+
type = record.commentable.has_attribute?('type') ? record.commentable['type'] : record.commentable_type
|
17
|
+
attrs.update('commentable_type' => type, 'commentable_title' => record.commentable.title)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize_activity(record)
|
21
|
+
super.tap do |activity|
|
22
|
+
activity.site = record.commentable.site
|
23
|
+
activity.section = record.commentable.section
|
24
|
+
activity.author = record.author
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Activities
|
2
|
+
class Logger < ActiveRecord::Observer
|
3
|
+
class_attribute :activity_attributes, :activity_conditions
|
4
|
+
self.activity_attributes = []
|
5
|
+
self.activity_conditions = [[:created, :new_record?], [:deleted, :frozen?]]
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def logs_activity(options = {})
|
9
|
+
self.activity_attributes += options[:attributes] if options[:attributes]
|
10
|
+
yield Configurator.new(self) if block_given?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Configurator
|
15
|
+
def initialize(klass)
|
16
|
+
@klass = klass
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(name, options)
|
20
|
+
options.assert_valid_keys :if
|
21
|
+
@klass.activity_conditions << [name, options[:if]]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def before_save(record)
|
26
|
+
prepare_activity_logging(record)
|
27
|
+
end
|
28
|
+
|
29
|
+
def after_save(record)
|
30
|
+
log_activity(record)
|
31
|
+
end
|
32
|
+
|
33
|
+
def after_destroy(record)
|
34
|
+
prepare_activity_logging(record)
|
35
|
+
log_activity(record)
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def prepare_activity_logging(record)
|
41
|
+
record.instance_variable_set :@activity, initialize_activity(record)
|
42
|
+
end
|
43
|
+
|
44
|
+
def log_activity(record)
|
45
|
+
activity = record.instance_variable_get :@activity
|
46
|
+
if activity && !activity.actions.empty?
|
47
|
+
activity.object = record
|
48
|
+
activity.author = record.author if record.respond_to? :author
|
49
|
+
activity.save!
|
50
|
+
end
|
51
|
+
record.instance_variable_set :@activity, nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize_activity(record)
|
55
|
+
Activity.new :actions => collect_actions(record),
|
56
|
+
:object_attributes => collect_activity_attributes(record)
|
57
|
+
end
|
58
|
+
|
59
|
+
def collect_actions(record)
|
60
|
+
activity_conditions.collect do |name, conditions|
|
61
|
+
name.to_s if conditions_satisfied?(record, conditions)
|
62
|
+
end.compact
|
63
|
+
end
|
64
|
+
|
65
|
+
def conditions_satisfied?(record, conditions)
|
66
|
+
conditions = conditions.is_a?(Array) ? conditions : [conditions]
|
67
|
+
conditions.collect do |condition|
|
68
|
+
condition_satisfied?(record, condition)
|
69
|
+
end.inject(true){|a, b| a && b }
|
70
|
+
end
|
71
|
+
|
72
|
+
def condition_satisfied?(record, condition)
|
73
|
+
case condition
|
74
|
+
when Symbol then !!record.send(condition)
|
75
|
+
when Hash then
|
76
|
+
condition.collect do |key, condition|
|
77
|
+
case key
|
78
|
+
when :not then !record.send(condition)
|
79
|
+
else raise 'not implemented' # TODO: should this be NotImplementedError?
|
80
|
+
end
|
81
|
+
end.inject(false){|a, b| a || b }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def collect_activity_attributes(record)
|
86
|
+
Hash[*activity_attributes.map do |attribute|
|
87
|
+
[attribute.to_s, case attribute
|
88
|
+
when Symbol then record.send attribute
|
89
|
+
when Proc then record.attribute.call(self)
|
90
|
+
end]
|
91
|
+
end.flatten]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<div class="activities">
|
2
|
+
<h3><%= t(:'adva.activity.titles.today', :date => l(Date.current, :format => :short)) %></h3>
|
3
|
+
<%= render_activities activities[0], true %>
|
4
|
+
|
5
|
+
<h3><%= t(:'adva.activity.titles.yesterday', :date => l(Date.current.yesterday, :format => :short)) %></h3>
|
6
|
+
<%= render_activities activities[1], true %>
|
7
|
+
|
8
|
+
<h3><%= t(:'adva.activity.titles.past', :date => l(Date.current.yesterday, :format => :short)) %></h3>
|
9
|
+
<%= render_activities activities[2] %>
|
10
|
+
</div>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<li class="activity <%= activity_css_classes(activity) %> <%= cycle 'highlight', '', :name => "activities" %>">
|
2
|
+
<p class="meta">
|
3
|
+
<%= activity_datetime(activity, recent) %>
|
4
|
+
<%= link_to_activity_user(activity) %>
|
5
|
+
<%= link_to_content(activity.object.commentable) if activity.object %>
|
6
|
+
</p>
|
7
|
+
|
8
|
+
<p>
|
9
|
+
<%= t(:'adva.activity.comment', :actions => activity.all_actions.collect { |a| t(:"adva.activity.action_names.#{a.downcase}" }.to_sentence) %>
|
10
|
+
</p>
|
11
|
+
|
12
|
+
<p>
|
13
|
+
<%= truncate strip_tags(activity.body), 100 %>
|
14
|
+
</p>
|
15
|
+
|
16
|
+
<% if activity.object -%>
|
17
|
+
<ul>
|
18
|
+
<% unless activity.object.commentable_type == 'Topic' %>
|
19
|
+
<% unless activity.object.approved? -%>
|
20
|
+
<li>
|
21
|
+
<%= link_to t(:'adva.common.approve'), admin_comment_path(activity.object, "comment[approved]" => 1, :return => true), :method => :put %>
|
22
|
+
</li>
|
23
|
+
<% else -%>
|
24
|
+
<li>
|
25
|
+
<%= link_to t(:'adva.common.unapprove'), admin_comment_path(activity.object, "comment[approved]" => 0, :return => true), :method => :put %>
|
26
|
+
</li>
|
27
|
+
<% end -%>
|
28
|
+
<% end -%>
|
29
|
+
<li>
|
30
|
+
<%= link_to_edit(activity.object, :url => edit_admin_comment_path(activity.object, :return => true)) %>
|
31
|
+
<%= link_to_delete(activity.object, :url => admin_comment_path(activity.object, :return => true)) %>
|
32
|
+
</li>
|
33
|
+
</ul>
|
34
|
+
<% end -%>
|
35
|
+
</li>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<li class="activity <%= activity_css_classes(activity) %> <%= cycle 'highlight', '', :name => "activities" %>">
|
2
|
+
<p class="meta">
|
3
|
+
<%= link_to_activity_user(activity) %><br />
|
4
|
+
</p>
|
5
|
+
<p class="meta">
|
6
|
+
<%= activity_datetime(activity, recent) %>
|
7
|
+
</p>
|
8
|
+
|
9
|
+
<p>
|
10
|
+
<%= t(:'adva.activity.actions', :type => t(:"adva.activity.types.#{activity.object_attributes['type'].tableize.singularize}"),
|
11
|
+
:actions => activity.all_actions.collect { |a| t(:"adva.activity.action_names.#{a.downcase}") }.to_sentence,
|
12
|
+
:activity => (activity.section ? link_to(activity.section.title, [:admin, activity.section.site, activity.section, :contents]) : "Deleted Section")).html_safe %>
|
13
|
+
</p>
|
14
|
+
<%= link_to_if activity.section, activity.title, [:edit, :admin, activity.section.try(:site), activity.section, activity.object] %>
|
15
|
+
</li>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<li class="activity <%= activity_css_classes(activity) %> <%= cycle 'highlight', '', :name => "activities" %>">
|
2
|
+
<p class="meta">
|
3
|
+
<%= activity_datetime(activity, recent) %>
|
4
|
+
<%= link_to_activity_user(activity) %>
|
5
|
+
<%= link_to_content(activity.object) if activity.object %>
|
6
|
+
</p>
|
7
|
+
<p><%= t(:'adva.activity.topic', :actions => activity.all_actions.collect { |a| t(:"adva.activity.action_names.#{a.downcase}" }.to_sentence) %></p>
|
8
|
+
<p><%= truncate strip_tags(activity.title), 100 %></p>
|
9
|
+
|
10
|
+
<% if activity.object -%>
|
11
|
+
<ul>
|
12
|
+
<li><%= link_to_edit(activity.object) %></li>
|
13
|
+
<li><%= link_to_delete(activity.object) %></li>
|
14
|
+
</ul>
|
15
|
+
<% end -%>
|
16
|
+
</li>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
en:
|
2
|
+
adva:
|
3
|
+
activity:
|
4
|
+
from_to: "%{from} - %{to}"
|
5
|
+
types:
|
6
|
+
article: Article
|
7
|
+
action_names:
|
8
|
+
created: created
|
9
|
+
published: published
|
10
|
+
revised: revised
|
11
|
+
titles:
|
12
|
+
today: Today %{date}
|
13
|
+
yesterday: Yesterday %{date}
|
14
|
+
past: Before %{date}
|
15
|
+
none: Nothing happened
|
16
|
+
notifier:
|
17
|
+
new: New %{activity} posted
|
18
|
+
view:
|
19
|
+
new: "%{name} <%{email}> just posted a new %{activity} on %{site} in section %{section}."
|
20
|
+
comment: Comment %{actions}
|
21
|
+
topic: Topic %{actions}
|
22
|
+
actions: "%{type} %{actions} in %{activity}."
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class CreateActivitiesTable < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :activities, :force => true do |t|
|
4
|
+
t.references :site
|
5
|
+
t.references :section
|
6
|
+
|
7
|
+
t.references :author, :polymorphic => true
|
8
|
+
t.string :author_name, :limit => 40
|
9
|
+
t.string :author_email, :limit => 40
|
10
|
+
t.string :author_homepage
|
11
|
+
|
12
|
+
t.string :actions
|
13
|
+
t.integer :object_id
|
14
|
+
t.string :object_type, :limit => 15
|
15
|
+
t.text :object_attributes
|
16
|
+
t.datetime :created_at, :null => false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.down
|
21
|
+
drop_table :activities
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# require "adva_activity/version"
|
2
|
+
require "rails"
|
3
|
+
require "rails-observers"
|
4
|
+
|
5
|
+
module AdvaActivity
|
6
|
+
module SiteExtensions
|
7
|
+
def self.included base
|
8
|
+
base.has_many :activities, :dependent => :destroy
|
9
|
+
end
|
10
|
+
|
11
|
+
def grouped_activities
|
12
|
+
activities.find_coinciding_grouped_by_dates(Time.zone.now.to_date, 1.day.ago.to_date)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module HasManyActivities
|
17
|
+
def self.included base
|
18
|
+
base.has_many :activities, :as => :object
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Engine < Rails::Engine
|
23
|
+
initializer "add assets to precompilation list" do |app|
|
24
|
+
app.config.assets.precompile += %w(adva_activity/admin/activities.css)
|
25
|
+
end
|
26
|
+
|
27
|
+
config.to_prepare do
|
28
|
+
Site.send :include, SiteExtensions
|
29
|
+
Content.send :include, HasManyActivities
|
30
|
+
Comment.send :include, HasManyActivities if defined?(Comment)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
|
2
|
+
|
3
|
+
class ActivitiesNotifierTest < ActiveSupport::TestCase
|
4
|
+
def setup
|
5
|
+
super
|
6
|
+
ActionMailer::Base.delivery_method = :test
|
7
|
+
ActionMailer::Base.perform_deliveries = true
|
8
|
+
ActionMailer::Base.deliveries = []
|
9
|
+
|
10
|
+
# set up a mock mail
|
11
|
+
@mail = TMail::Mail.new
|
12
|
+
@mail.set_content_type('text', 'plain', { 'charset' => 'utf-8' })
|
13
|
+
@mail.mime_version = '1.0'
|
14
|
+
|
15
|
+
# need to stub out Time.now and set mail date in order to avoid automatic setting
|
16
|
+
# see http://github.com/rails/rails/commit/f73d34c131c1e371c76c5a146aac2c2bffbf96e5
|
17
|
+
stub(Time).now { Time.local(2009, 10, 8, 12, 0, 0) }
|
18
|
+
@mail.date = Time.now
|
19
|
+
|
20
|
+
@site = Site.first
|
21
|
+
@section = Section.first
|
22
|
+
@user = User.first
|
23
|
+
end
|
24
|
+
|
25
|
+
def teardown
|
26
|
+
super
|
27
|
+
ActionMailer::Base.deliveries.clear
|
28
|
+
end
|
29
|
+
|
30
|
+
test "sets the mail up correctly for articles" do
|
31
|
+
article = Article.first
|
32
|
+
activity = activity_for(article)
|
33
|
+
|
34
|
+
@mail.subject = "[#{@site.name} / #{@section.title}] New Article posted"
|
35
|
+
@mail.from = "#{@site.name} <#{@site.email}>"
|
36
|
+
@mail.body = "#{article.author_name} <#{article.author_email}> just posted a new Article on #{@site.name} in section #{@section.title}."
|
37
|
+
@mail.to = "#{@user.email}"
|
38
|
+
|
39
|
+
ActivityNotifier.create_new_content_notification(activity, @user).encoded.should == @mail.encoded
|
40
|
+
end
|
41
|
+
|
42
|
+
test "sets the mail up correctly for comments" do
|
43
|
+
comment = Comment.first
|
44
|
+
activity = activity_for(comment)
|
45
|
+
|
46
|
+
@mail.subject = "[#{@site.name} / #{@section.title}] New Comment posted"
|
47
|
+
@mail.from = "#{@site.name} <#{@site.email}>"
|
48
|
+
@mail.body = "#{comment.author_name} <#{comment.author_email}> just posted a new Comment on #{@site.name} in section #{@section.title}."
|
49
|
+
@mail.to = "#{@user.email}"
|
50
|
+
|
51
|
+
ActivityNotifier.create_new_content_notification(activity, @user).encoded.should == @mail.encoded
|
52
|
+
end
|
53
|
+
|
54
|
+
if Rails.plugin?(:adva_wiki)
|
55
|
+
test "sets the mail up correctly for wikipages" do
|
56
|
+
wikipage = Wikipage.first
|
57
|
+
activity = activity_for(wikipage)
|
58
|
+
|
59
|
+
@mail.subject = "[#{@site.name} / #{@section.title}] New Wikipage posted"
|
60
|
+
@mail.from = "#{@site.name} <#{@site.email}>"
|
61
|
+
@mail.body = "#{wikipage.author_name} <#{wikipage.author_email}> just posted a new Wikipage on #{@site.name} in section #{@section.title}."
|
62
|
+
@mail.to = "#{@user.email}"
|
63
|
+
|
64
|
+
ActivityNotifier.create_new_content_notification(activity, @user).encoded.should == @mail.encoded
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def activity_for(object)
|
69
|
+
Activity.new(:site => @site, :section => @section).tap do |activity|
|
70
|
+
activity.object = object
|
71
|
+
activity.author_name = object.author_name
|
72
|
+
activity.author_email = object.author_email
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
|
2
|
+
|
3
|
+
class ActivityTest < ActiveSupport::TestCase
|
4
|
+
def setup
|
5
|
+
super
|
6
|
+
@site = Site.first
|
7
|
+
@section = @site.sections.root
|
8
|
+
@user = User.first
|
9
|
+
end
|
10
|
+
|
11
|
+
# FIXME
|
12
|
+
# Can't get this to pass ... apparently RR has problems with expecting calls
|
13
|
+
# to dynamic methods?
|
14
|
+
#
|
15
|
+
# test "notify_subscribers sends emails to all subscribers" do
|
16
|
+
# users = User.all
|
17
|
+
# activity = Activity.new
|
18
|
+
#
|
19
|
+
# stub(Activities::ActivityObserver).find_subscribers(activity).returns(users)
|
20
|
+
# mock(ActivityNotifier).deliver_new_content_notification(anything, anything).times(users.size)
|
21
|
+
#
|
22
|
+
# Activities::ActivityObserver.instance.after_create(activity)
|
23
|
+
# end
|
24
|
+
|
25
|
+
test "receives #notify_subscribers when an Article gets created" do
|
26
|
+
mock(Activities::ActivityObserver).notify_subscribers(is_a(Activity))
|
27
|
+
|
28
|
+
Activity.with_observers('activities/activity_observer') do
|
29
|
+
Article.with_observers('activities/article_observer') do
|
30
|
+
Article.create! :title => 'An article', :body => 'body',
|
31
|
+
:author => @user, :site => @site, :section => @section
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
test "#find_subscribers returns only subscribed users" do
|
37
|
+
activity = Activity.new(:site => @site)
|
38
|
+
subscribers = @site.users.includes(:roles).where(['roles.name IN (?)', ['superuser', 'admin']])
|
39
|
+
assert_equal subscribers.count, Activities::ActivityObserver.send(:find_subscribers, activity).count
|
40
|
+
end
|
41
|
+
|
42
|
+
if Rails.plugin?(:adva_wiki)
|
43
|
+
test "receives #notify_subscribers when a Wikipage gets created" do
|
44
|
+
mock(Activities::ActivityObserver).notify_subscribers(is_a(Activity))
|
45
|
+
|
46
|
+
Activity.with_observers('activities/activity_observer') do
|
47
|
+
Wikipage.with_observers('activities/wikipage_observer') do
|
48
|
+
Wikipage.create! :title => 'A wikipage', :body => 'body', :author => @user,
|
49
|
+
:site => @site, :section => @section
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
test "receives #notify_subscribers when a Comment gets created" do
|
56
|
+
mock(Activities::ActivityObserver).notify_subscribers(is_a(Activity))
|
57
|
+
|
58
|
+
Activity.with_observers('activities/activity_observer') do
|
59
|
+
Comment.with_observers('activities/comment_observer') do
|
60
|
+
Comment.create! :body => 'body', :author => @user, :commentable => Article.first,
|
61
|
+
:site => @site, :section => @section
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
|
2
|
+
|
3
|
+
class ActivityTest < ActiveSupport::TestCase
|
4
|
+
setup :bunch_of_activities
|
5
|
+
|
6
|
+
test 'associations' do
|
7
|
+
@activity.should belong_to(:site)
|
8
|
+
@activity.should belong_to(:section)
|
9
|
+
@activity.should belong_to(:object, :polymorphic => true)
|
10
|
+
@activity.should belong_to(:author)
|
11
|
+
end
|
12
|
+
|
13
|
+
test 'validations' do
|
14
|
+
@activity.should validate_presence_of(:site)
|
15
|
+
@activity.should validate_presence_of(:section)
|
16
|
+
@activity.should validate_presence_of(:object)
|
17
|
+
end
|
18
|
+
|
19
|
+
# CLASS METHODS
|
20
|
+
|
21
|
+
test '#find_coinciding_grouped_by_dates finds coinciding activities grouped
|
22
|
+
by given dates' do
|
23
|
+
stub(Activity).find.returns @activities
|
24
|
+
today, yesterday = Time.zone.now.to_date, 1.day.ago.to_date
|
25
|
+
result = Activity.find_coinciding_grouped_by_dates today, yesterday
|
26
|
+
result.should == [[@activity], @yesterdays, @older]
|
27
|
+
end
|
28
|
+
|
29
|
+
test '#find_coinciding finds activities and groups them to chunks coninciding
|
30
|
+
within a given time delta, hiding grouped activities in @activity.siblings' do
|
31
|
+
stub(Activity).find.returns [@activity] + @others
|
32
|
+
result = Activity.find_coinciding
|
33
|
+
result.should == [@activity]
|
34
|
+
result.first.siblings.should == @others
|
35
|
+
end
|
36
|
+
|
37
|
+
# INSTANCE METHODS
|
38
|
+
|
39
|
+
test "#coincides_with?(other) is true when the compared created_at values
|
40
|
+
differ by less/equal to the given delta value" do
|
41
|
+
@activity.coincides_with?(@others.first).should be_true
|
42
|
+
end
|
43
|
+
|
44
|
+
test "#coincides_with?(other) is false when the compared created_at values
|
45
|
+
differ by more than the given delta value" do
|
46
|
+
@activity.coincides_with?(@others.last).should be_false
|
47
|
+
end
|
48
|
+
|
49
|
+
test "#from returns the last sibling's created_at value" do
|
50
|
+
@activity.siblings = @others
|
51
|
+
@activity.from.should == @others.last.created_at
|
52
|
+
end
|
53
|
+
|
54
|
+
test "#to return the activity's created_at value" do
|
55
|
+
@activity.siblings = @others
|
56
|
+
@activity.to.should == @activity.created_at
|
57
|
+
end
|
58
|
+
|
59
|
+
test "#all_actions returns all actions from all siblings in a chronological order" do
|
60
|
+
@activity.siblings = @others
|
61
|
+
@activity.all_actions.should == ['created', 'edited', 'revised']
|
62
|
+
end
|
63
|
+
|
64
|
+
test "when a missing method is called it looks for a corresponding key in object_attributes" do
|
65
|
+
@activity.object_attributes = { 'foo' => 'bar' }
|
66
|
+
@activity.foo.should == 'bar'
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def bunch_of_activities
|
71
|
+
attributes = {:object_type => 'Article', :object_id => 1}
|
72
|
+
@activity = Activity.new attributes.update(:actions => ['edited', 'revised'], :created_at => Time.zone.now)
|
73
|
+
@others = [10, 70, 80].collect do |minutes_ago|
|
74
|
+
actions = minutes_ago == 80 ? ['created'] : ['edited']
|
75
|
+
Activity.new attributes.update(:actions => actions, :created_at => minutes_ago.minutes.ago)
|
76
|
+
end
|
77
|
+
|
78
|
+
@yesterdays = [Activity.new(attributes.update(:created_at => 1.day.ago))]
|
79
|
+
@older = [Activity.new(attributes.update(:created_at => 2.days.ago))]
|
80
|
+
|
81
|
+
@activities = [@activity] + @others + @yesterdays + @older
|
82
|
+
@activities.sort!{|left, right| right.created_at <=> left.created_at }
|
83
|
+
end
|
84
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: adva_activity
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Micah Geisel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails-observers
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Adva Activity
|
28
|
+
email:
|
29
|
+
- micah@botandrose.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- Gemfile
|
35
|
+
- LICENSE
|
36
|
+
- README.md
|
37
|
+
- Rakefile
|
38
|
+
- adva_activity.gemspec
|
39
|
+
- app/assets/stylesheets/adva_activity/admin/activities.css
|
40
|
+
- app/helpers/activities_helper.rb
|
41
|
+
- app/models/activity.rb
|
42
|
+
- app/models/activity_notifier.rb
|
43
|
+
- app/observers/activities/activity_observer.rb
|
44
|
+
- app/observers/activities/article_observer.rb
|
45
|
+
- app/observers/activities/comment_observer.rb
|
46
|
+
- app/observers/activities/logger.rb
|
47
|
+
- app/observers/activities/section_observer.rb
|
48
|
+
- app/views/activity_notifier/new_content_notification.html.erb
|
49
|
+
- app/views/admin/activities/_activities.html.erb
|
50
|
+
- app/views/admin/activities/_comment.html.erb
|
51
|
+
- app/views/admin/activities/_content.html.erb
|
52
|
+
- app/views/admin/activities/_topic.html.erb
|
53
|
+
- config/locales/en.yml
|
54
|
+
- db/migrate/20080401000000_create_activities_table.rb
|
55
|
+
- lib/adva_activity.rb
|
56
|
+
- lib/adva_activity/version.rb
|
57
|
+
- test/test_helper.rb
|
58
|
+
- test/unit/activities_notifier_test.rb
|
59
|
+
- test/unit/activity_observer_test.rb
|
60
|
+
- test/unit/activity_test.rb
|
61
|
+
homepage: ''
|
62
|
+
licenses: []
|
63
|
+
metadata: {}
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 2.4.6
|
81
|
+
signing_key:
|
82
|
+
specification_version: 4
|
83
|
+
summary: Engine for Adva CMS activity feed
|
84
|
+
test_files:
|
85
|
+
- test/test_helper.rb
|
86
|
+
- test/unit/activities_notifier_test.rb
|
87
|
+
- test/unit/activity_observer_test.rb
|
88
|
+
- test/unit/activity_test.rb
|