muck-activity 0.1.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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Justin Ball
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,65 @@
1
+ = MuckActivity
2
+
3
+ == Installation
4
+ The muck activity engine is part of the muck framework and relies upon the muck_engine. The main engine can be found here:
5
+ http://github.com/jbasdf/muck_engine
6
+
7
+ The easiest way to get started with muck is to generate your application using a template:
8
+ $ rails <your-app> -m http://github.com/jbasdf/rails-templates/raw/master/muck.rb
9
+
10
+ Add optional functionality with the following command:
11
+ $ rake rails:template LOCATION=http://github.com/jbasdf/rails-templates/raw/master/mucktoo.rb
12
+
13
+ == Usage
14
+ This engine implements simple activity tracking.
15
+
16
+ Models that can have activity feed should call 'has_activities'
17
+
18
+ Example:
19
+ class User
20
+ has_activities
21
+ end
22
+
23
+ Adding an item to the activity feed requires 'acts_as_activity_source' which then profiles the method 'add_activity'
24
+
25
+ add_activity(feed_to, actor, item, template, check_method)
26
+
27
+ feed_to - contains an array of objects (typically users) that have use acts_as_activity_user
28
+ actor - the user performing the action
29
+ item - a reference to the object that
30
+ template - the template (partial) to use to render the activity
31
+ check_method - and optional method to call on each object in the feed_to array that determines whether or not to add the activity
32
+
33
+ Example:
34
+ add_activity(user.feed_to, user, comment, 'comment')
35
+
36
+ === Authorization
37
+ By default 'has_activities' will add a simple 'can_view?' method:
38
+
39
+ def can_view?(check_object)
40
+ self == check_object
41
+ end
42
+
43
+ This method determines whether or not the check_object has access to the current object's activity feeds. For example, if the
44
+ intent is to recover the activities for a given user where the user is '@parent' thus:
45
+
46
+ @parent.can_view?(current_user)
47
+
48
+ The can view method will determine whether or not the current_user has the right to view the activity feed.
49
+
50
+ In most instances you will need to override this method to implement proper security. For example, for a group that has an activity feed
51
+ you might add a can_view? method like this:
52
+
53
+ def can_view?(check_object)
54
+ self.member?(check_object) || (check_object.is_a?(User) && check_object.admin?)
55
+ end
56
+
57
+ == Live Updates
58
+ If you would like to add live updates to the user's activity feed you can do so by adding an entry to GlobalConfig.yml (configuration file added by muck)
59
+
60
+ enable_live_activity_updates: true
61
+ live_activity_update_interval: 5
62
+
63
+ Note that this will poll the server every 5 seconds and so will increase server load and bandwidth usage.
64
+
65
+ Copyright (c) 2009 Justin Ball, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,76 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the muck_activity plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation for the muck_activity plugin.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'MuckActivity'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
24
+
25
+ begin
26
+ require 'jeweler'
27
+ Jeweler::Tasks.new do |gemspec|
28
+ gemspec.name = "muck-activity"
29
+ gemspec.summary = "Activity engine for the muck system"
30
+ gemspec.email = "justinball@gmail.com"
31
+ gemspec.homepage = "http://github.com/jbasdf/muck_activity"
32
+ gemspec.description = "Activity engine for the muck system."
33
+ gemspec.authors = ["Justin Ball"]
34
+ gemspec.rubyforge_project = 'muck-activity'
35
+ gemspec.add_dependency "muck-engine"
36
+ gemspec.add_dependency "muck-users"
37
+ gemspec.files.include %w( lib/muck_users
38
+ tasks/*
39
+ db/migrate/*.rb
40
+ app/**/**/**/*
41
+ config/*
42
+ locales/*
43
+ rails/*
44
+ test/*
45
+ lib/**/* )
46
+ end
47
+ rescue LoadError
48
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
49
+ end
50
+
51
+ # rubyforge tasks
52
+ begin
53
+ require 'rake/contrib/sshpublisher'
54
+ namespace :rubyforge do
55
+
56
+ desc "Release gem and RDoc documentation to RubyForge"
57
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
58
+
59
+ namespace :release do
60
+ desc "Publish RDoc to RubyForge."
61
+ task :docs => [:rdoc] do
62
+ config = YAML.load(
63
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
64
+ )
65
+
66
+ host = "#{config['username']}@rubyforge.org"
67
+ remote_dir = "/var/www/gforge-projects/muck_activity/"
68
+ local_dir = 'rdoc'
69
+
70
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
71
+ end
72
+ end
73
+ end
74
+ rescue LoadError
75
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
76
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,155 @@
1
+ class Muck::ActivitiesController < ApplicationController
2
+ unloadable
3
+
4
+ include ApplicationHelper
5
+
6
+ before_filter :login_required
7
+ before_filter :find_parent, :only => [:index, :create]
8
+ before_filter :get_activity, :only => [:destroy]
9
+
10
+ def index
11
+ if @parent.can_view?(current_user)
12
+ if params[:latest_activity_id]
13
+ @activities = @parent.activities.after(params[:latest_activity_id]).paginate(:page => @page, :per_page => @per_page)
14
+ else
15
+ @activities = @parent.activities.paginate(:page => @page, :per_page => @per_page)
16
+ end
17
+ respond_to do |format|
18
+ format.js do
19
+ render :partial => "activities/cached_activities", :locals => {:activities => @activities}
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def create
26
+ @activity = @parent.activities.build(params[:activity])
27
+ @activity.source = @parent
28
+ @activity.content.gsub!(@parent.display_name, '')
29
+ @parent.save!
30
+
31
+ @activity_html = get_activity_html(@activity)
32
+ if @activity.template == 'status_update'
33
+ @status_html = get_status_html(@parent)
34
+ else
35
+ @status_html = ''
36
+ end
37
+ respond_to do |format|
38
+ format.js do
39
+ render :update do |page|
40
+ page.insert_html :top, 'activity-feed-content', @activity_html
41
+ page.replace_html 'current-status', @status_html if @status_html
42
+ page.visual_effect :highlight, "#{@activity.dom_id}".to_sym
43
+ page << 'jQuery("#status_update").val(\'\');'
44
+ page << "jQuery('#status-update-field').removeClass('status-update-lit');"
45
+ page << "jQuery('#status-update-field').addClass('status-update-dim');"
46
+ page << 'jQuery("#status-update-field").show();'
47
+ page << 'jQuery("#submit_status").show();'
48
+ page << 'jQuery("#progress-bar").hide();'
49
+ page << 'setup_submit_delete();'
50
+ end
51
+ end
52
+ format.html do
53
+ redirect_back_or_default(@parent)
54
+ end
55
+ format.json do
56
+ render :json => { :success => true,
57
+ :is_status_update => @activity.is_status_update,
58
+ :html => @activity_html,
59
+ :status_html => @status_html,
60
+ :message => t("muck.activities.created") }
61
+ end
62
+ end
63
+
64
+ rescue => ex
65
+
66
+ if @activity
67
+ errors = @activity.errors.full_messages.to_sentence
68
+ else
69
+ errors = ex
70
+ end
71
+ message = t('muck.activities.update_error', :errors => errors)
72
+ respond_to do |format|
73
+ format.html do
74
+ flash[:error] = message
75
+ redirect_back_or_default(@parent)
76
+ end
77
+ format.js do
78
+ render :update do |page|
79
+ page_alert(page, message)
80
+ end
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ def destroy
87
+ @activities_object = @activity.source
88
+ @activity.destroy
89
+ respond_to do |format|
90
+ format.html do
91
+ flash[:notice] = t("muck.activities.item_removed")
92
+ redirect_back_or_default(@activities_object)
93
+ end
94
+ format.js do
95
+ if @activity.is_status_update
96
+ @new_status = get_status_html(@activities_object)
97
+ render(:update) do |page|
98
+ page << "jQuery('#activity_#{@activity.id}').fadeOut();"
99
+ page.replace_html 'current-status', @new_status if @new_status
100
+ end
101
+ end
102
+ end
103
+ format.json do
104
+ @new_status = get_status_html(@activities_object)
105
+ render :json => { :success => true,
106
+ :is_status_update => @activity.is_status_update,
107
+ :html => @new_status,
108
+ :message => t("muck.activities.item_removed") }
109
+ end
110
+ end
111
+
112
+ rescue => ex
113
+ respond_to do |format|
114
+ format.html do
115
+ flash[:notice] = t("muck.activities.item_could_not_be_removed")
116
+ redirect_back_or_default(current_user)
117
+ end
118
+ format.js do
119
+ render(:update){ |page| page_alert(page, t("muck.activities.item_could_not_be_removed")) }
120
+ end
121
+ format.json { render :json => { :success => false, :message => t("muck.activities.item_could_not_be_removed") } }
122
+ end
123
+ end
124
+
125
+ protected
126
+
127
+ def get_status_html(activity)
128
+ @template.template_format = :html
129
+ render_to_string(:partial => 'activities/current_status', :locals => {:activities_object => activity})
130
+ end
131
+
132
+ def get_activity_html(activity)
133
+ render_to_string(:partial => "activity_templates/#{activity.template}", :locals => { :activity => activity })
134
+ end
135
+
136
+ def find_parent
137
+ @klass = params[:parent_type].to_s.capitalize.constantize
138
+ @parent = @klass.find(params[:parent_id])
139
+ end
140
+
141
+ def get_activity
142
+ @activity = Activity.find(params[:id])
143
+ unless @activity.can_edit?(current_user)
144
+ respond_to do |format|
145
+ format.html do
146
+ flash[:notice] = t("muck.activities.permission_denied")
147
+ redirect_back_or_default(current_user)
148
+ end
149
+ format.js { render(:update){|page| page_alert(page, t("muck.activities.permission_denied"))}}
150
+ end
151
+ end
152
+
153
+ end
154
+
155
+ end
@@ -0,0 +1,16 @@
1
+ module MuckActivityHelper
2
+
3
+ def activity_feed_for(activities_object)
4
+ activities = activities_object.activities.paginate(:page => @page, :per_page => @per_page)
5
+ render :partial => 'activities/activity_feed', :locals => { :activities_object => activities_object, :activities => activities }
6
+ end
7
+
8
+ def status_update(activities_object)
9
+ render :partial => 'activities/status_update', :locals => { :activities_object => activities_object }
10
+ end
11
+
12
+ def delete_activity(activity, button_type = :button, button_text = t("muck.activities.clear"))
13
+ render :partial => 'activities/delete', :locals => { :activity => activity, :button_type => button_type, :button_text => button_text }
14
+ end
15
+
16
+ end
@@ -0,0 +1,50 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: activities
4
+ #
5
+ # id :integer(4) not null, primary key
6
+ # item_id :integer(4)
7
+ # item_type :string(255)
8
+ # template :string(255)
9
+ # source_id :integer(4)
10
+ # source_type :string(255)
11
+ # content :text
12
+ # title :string(255)
13
+ # status_update :boolean(1)
14
+ # created_at :datetime
15
+ # updated_at :datetime
16
+ #
17
+
18
+ class Activity < ActiveRecord::Base
19
+
20
+ belongs_to :item, :polymorphic => true
21
+ belongs_to :source, :polymorphic => true
22
+ has_many :activity_feeds
23
+
24
+ validates_presence_of :source
25
+
26
+ named_scope :since, lambda { |time| {:conditions => ["activities.created_at > ?", time] } }
27
+ named_scope :before, lambda {|time| {:conditions => ["activities.created_at < ?", time] } }
28
+ named_scope :recent, :order => "activities.created_at DESC"
29
+ named_scope :after, lambda {|id| {:conditions => ["activities.id > ?", id] } }
30
+ named_scope :only_public, :conditions => ["activities.is_public = true"]
31
+
32
+ def validate
33
+ errors.add_to_base(I18n.t('muck.activities.template_or_item_required')) if template.blank? && item.blank?
34
+ end
35
+
36
+ # Provides a template that can be used to render a view of this activity.
37
+ # If 'template' is not specified when the object created then the item class
38
+ # name will be used to generated a template
39
+ def partial
40
+ template || item.class.name.underscore
41
+ end
42
+
43
+ # Checks to see if the specified object can edit this activity.
44
+ # Most likely check_object will be a user
45
+ def can_edit?(check_object)
46
+ return true if check_object.is_a?(User) && check_object.admin?
47
+ source == check_object
48
+ end
49
+
50
+ end
@@ -0,0 +1,14 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: activity_feeds
4
+ #
5
+ # id :integer(4) not null, primary key
6
+ # activity_id :integer(4)
7
+ # ownable_id :integer(4)
8
+ # ownable_type :string(255)
9
+ #
10
+
11
+ class ActivityFeed < ActiveRecord::Base
12
+ belongs_to :activity
13
+ belongs_to :ownable, :polymorphic => true
14
+ end
@@ -0,0 +1,26 @@
1
+ <div id="activity-feed">
2
+ <h2><%= t('muck.activities.activity_feed_title') %></h2>
3
+ <div id="activity-feed-content">
4
+ <%= render( :partial => "activities/cached_activities", :locals => {:activities => activities}) %>
5
+ </div>
6
+ </div>
7
+ <%= will_paginate activities, :previous_label => t('muck.activities.paging_newer'), :next_label => t('muck.activities.paging_older') -%>
8
+ <% content_for :javascript do -%>
9
+ jQuery(document).ready(function(){
10
+ setup_submit_delete();
11
+ <% if GlobalConfig.enable_live_activity_updates -%>
12
+ setInterval(function() {jQuery.ajax({success:function(request){update_feed(request);},url:'<%= activities_path(:parent_id => activities_object, :parent_type => activities_object.type, :format => 'js') %>&amp;latest_activity_id=' + get_latest_activity_id()})}, <%=GlobalConfig.live_activity_update_interval%> * 1000)
13
+ <% end -%>
14
+ });
15
+ function get_latest_activity_id(){
16
+ var activities = jQuery('#activity-feed-content').children('.activity-status-update')
17
+ if(activities.length > 0){
18
+ return activities[0].id.replace('activity_', '');
19
+ } else {
20
+ return '';
21
+ }
22
+ }
23
+ function update_feed(request){
24
+ jQuery('#activity-feed-content').prepend(request);
25
+ }
26
+ <% end -%>
@@ -0,0 +1,7 @@
1
+ <% if !activities.blank? -%>
2
+ <% activities.each do |activity| -%>
3
+ <% cache ("activities/#{I18n.locale}/#{activity.id}") do -%>
4
+ <%= render( :partial => "activity_templates/#{activity.partial}", :locals => {:activity => activity}) rescue '' %>
5
+ <% end -%>
6
+ <% end -%>
7
+ <% end -%>
@@ -0,0 +1,15 @@
1
+ <% if activities_object.status
2
+ status = h(activities_object.status.content)
3
+ time = activities_object.status.created_at
4
+ else
5
+ status = t('muck.activities.update_status_message')
6
+ time = false
7
+ end -%>
8
+ <span id="current-status-name"><%= activities_object.display_name %></span>
9
+ <span id="current-status-text"><%= status %></span>
10
+ <span id="status-time" class="status-time"><%= t('muck.activities.time_ago', :time_in_words => time_ago_in_words(time)) if time %></span>
11
+ <span id="status-clear">
12
+ <% if activities_object.status -%>
13
+ - <%= delete_activity(activities_object.status, :text, t("muck.activities.clear")) %>
14
+ <% end -%>
15
+ </span>
@@ -0,0 +1,11 @@
1
+ <% if button_type == :text -%>
2
+ <%= link_to_remote( button_text, :url => activity_path(activity, :format => 'js'), :method => :delete) %>
3
+ <% else -%>
4
+ <% remote_form_for(:activity, :url => activity_path(activity, :format => 'js'), :html => { :class => "delete-form activity-delete", :method => :delete } ) do |f| -%>
5
+ <% if button_type == :image -%>
6
+ <%= image_submit_tag '/images/icons/delete.png', {:title => button_text, :width => '15', :height => '15', :alt => button_text } %>
7
+ <% else -%>
8
+ <%= submit_tag button_text, { :title => button_text } %>
9
+ <% end -%>
10
+ <% end -%>
11
+ <% end -%>
@@ -0,0 +1,38 @@
1
+ <div id="status-update">
2
+ <%= output_errors(t('muck.users.problem_with_status'), {:class => 'help-box'}, @activity) %>
3
+
4
+ <div id="activity-icon"><%= icon activities_object %></div>
5
+ <div id="current-status"><%= render :partial => 'activities/current_status', :locals => {:activities_object => activities_object} %></div>
6
+ <% remote_form_for(:activity,
7
+ :url => activities_path(:parent_id => activities_object, :parent_type => activities_object.type, :format => 'js'),
8
+ :html => { :id => 'status_update_form',
9
+ :action => activities_path(:parent_id => activities_object, :parent_type => activities_object.type) } ) do |f| -%>
10
+ <div id="progress-bar" style="display:none;">
11
+ <h3><%= t('muck.activities.updating_status_message') %></h3>
12
+ <img src="/images/loading.gif" alt="progress bar">
13
+ </div>
14
+ <div id="status-update-field" class="status-update-dim">
15
+ <%= t("muck.activities.status_update_prompt") %>
16
+ <%= f.text_field :content, :id => 'status_update' %>
17
+ <span id="status-update-button">
18
+ <%= f.submit t('muck.activities.post'), :id => 'submit_status', :class=>"button" %>
19
+ </span>
20
+ </div>
21
+ <%= f.hidden_field :template, :value => 'status_update' %>
22
+ <%= f.hidden_field :is_status_update, :value => true %>
23
+ <% end %>
24
+ </div>
25
+ <% content_for :javascript do -%>
26
+ jQuery(document).ready(function() {
27
+ jQuery('#status_update').click(function(){
28
+ jQuery('#status-update-field').removeClass('status-update-dim');
29
+ jQuery('#status-update-field').addClass('status-update-lit');
30
+ });
31
+
32
+ jQuery("#status_update_form").submit(function() {
33
+ jQuery("#status-update-field").hide();
34
+ jQuery("#submit_status").hide();
35
+ jQuery("#progress-bar").show();
36
+ });
37
+ });
38
+ <% end -%>
@@ -0,0 +1,6 @@
1
+ <div class="activity-status-update delete-container" id="<%= activity.dom_id %>">
2
+ <%= icon activity.source %> <%= link_to activity.source.display_name, activity.source %> <%= activity.content %>
3
+ <span class="activity-time"><%= t("muck.activities.time_ago", :time_in_words => time_ago_in_words(activity.created_at)) %></span>
4
+ <%= delete_activity(activity, :image) %>
5
+ <div class="clear"></div>
6
+ </div>
@@ -0,0 +1,3 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ map.resources :activities, :controller => 'muck/activities'
3
+ end
@@ -0,0 +1,36 @@
1
+ class AddMuckActivities < ActiveRecord::Migration
2
+
3
+ def self.up
4
+
5
+ create_table :activities, :force => true do |t|
6
+ t.integer :item_id
7
+ t.string :item_type
8
+ t.string :template
9
+ t.integer :source_id
10
+ t.string :source_type
11
+ t.text :content
12
+ t.string :title
13
+ t.boolean :is_status_update, :default => false
14
+ t.boolean :is_public, :default => true
15
+ t.timestamps
16
+ end
17
+
18
+ add_index :activities, ["item_id", "item_type"]
19
+
20
+ create_table :activity_feeds, :force => true do |t|
21
+ t.integer :activity_id
22
+ t.integer :ownable_id
23
+ t.string :ownable_type
24
+ end
25
+
26
+ add_index :activity_feeds, ["activity_id"]
27
+ add_index :activity_feeds, ["ownable_id", "ownable_type"]
28
+
29
+ end
30
+
31
+ def self.down
32
+ drop_table :activities
33
+ drop_table :activity_feeds
34
+ end
35
+
36
+ end
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,8 @@
1
+ class ActionController::Routing::RouteSet
2
+ def load_routes_with_muck_activity!
3
+ muck_activity_routes = File.join(File.dirname(__FILE__), *%w[.. .. config muck_activity_routes.rb])
4
+ add_configuration_file(muck_activity_routes) unless configuration_files.include? muck_activity_routes
5
+ load_routes_without_muck_activity!
6
+ end
7
+ alias_method_chain :load_routes!, :muck_activity
8
+ end
@@ -0,0 +1,28 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+ require 'fileutils'
4
+
5
+ module MuckActivity
6
+ class Tasks < ::Rake::TaskLib
7
+ def initialize
8
+ define
9
+ end
10
+
11
+ private
12
+ def define
13
+
14
+ namespace :muck do
15
+ namespace :activity do
16
+ desc "Sync required files from activity."
17
+ task :sync do
18
+ path = File.join(File.dirname(__FILE__), *%w[.. ..])
19
+ system "rsync -ruv #{path}/db ."
20
+ system "rsync -ruv #{path}/public ."
21
+ end
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+ MuckActivity::Tasks.new
@@ -0,0 +1,76 @@
1
+ require 'cgi'
2
+
3
+ module MuckActivity # :nodoc:
4
+
5
+ def self.included(base) # :nodoc:
6
+ base.extend ActMethods
7
+ end
8
+
9
+ module ActMethods
10
+
11
+ # +has_activities+ gives the class it is called on an activity feed and a method called
12
+ # +add_activity+ that can add activities into a feed. Retrieve activity feed items
13
+ # via object.activities. ie @user.activities.
14
+ def has_activities
15
+ unless included_modules.include? InstanceMethods
16
+ has_many :activity_feeds, :as => :ownable
17
+ has_many :activities, :through => :activity_feeds, :order => 'created_at desc'
18
+ include InstanceMethods
19
+ end
20
+ end
21
+
22
+ # +acts_as_activity_source+ gives the class it is called on a method called
23
+ # +add_activity+ that can add activities into a feed.
24
+ def acts_as_activity_source
25
+ unless included_modules.include? InstanceMethods
26
+ include InstanceMethods
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ module InstanceMethods
33
+
34
+ # +add_activity+ adds an activity to all activites feeds that belong to the objects found in feed_to.
35
+ # * +feed_to+: an array of objects that have +has_activities+ declared on them. The generated activity
36
+ # will be pushed into the feed of each of these objects.
37
+ # * +source+: the object that peformed the activity ie a user or group
38
+ # * +item+: an object that will be used to generated the entry in an activity feed
39
+ # * +template+: name of an partial that will be used to generated the entry in the activity feed. Place
40
+ # templates in /app/views/activity_templates
41
+ # * +title+: optional title that can be used in the template
42
+ # * +content+: option content that can be used in the template. Useful for activities that might not have
43
+ # an item but instead might have a message or other text.
44
+ # * +check_method+: method that will be called on each item in the feed_to array. If the method evaluates
45
+ # to false the activity won't be added to the object's activity feed. An example usage would be
46
+ # letting users configure which items they want to have in their activity feed.
47
+ def add_activity(feed_to, source, item, template, title = '', content = '', check_method = nil)
48
+ feed_to = [feed_to] unless feed_to.is_a?(Array)
49
+ activity = Activity.create(:item => item, :source => source, :template => template, :title => title, :content => content)
50
+ feed_to.each do |ft|
51
+ if check_method
52
+ ft.activities << activity if ft.send(check_method)
53
+ else
54
+ ft.activities << activity
55
+ end
56
+ end
57
+ end
58
+
59
+ # +status+ returns the first activity item from the user's activity feed that is a status update.
60
+ # Used for displaying the last status update the user made
61
+ def status
62
+ self.activities.find(:first, :conditions => ['is_status_update = true'], :order => 'created_at DESC')
63
+ end
64
+
65
+ def can_view?(check_object)
66
+ self == check_object
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ ActiveRecord::Base.send(:include, MuckActivity)
73
+ ActionController::Base.send(:include, MuckActivity)
74
+ ActionController::Base.send :helper, MuckActivityHelper
75
+
76
+ I18n.load_path += Dir[ File.join(File.dirname(__FILE__), '..', 'locales', '*.{rb,yml}') ]
data/locales/en.yml ADDED
@@ -0,0 +1,22 @@
1
+ en:
2
+ muck:
3
+ activities:
4
+ item_removed: 'Item successfully removed from the recent activities list.'
5
+ item_could_not_be_removed: 'Item could not be removed from the recent activities list.'
6
+ item_created: 'Added activity'
7
+ permission_denied: "Sorry, you can't do that."
8
+ status_update_prompt: "What are you doing right now?"
9
+ activity_feed_title: 'Recent Activity'
10
+ updating_status_message: "Updating your status. Please wait."
11
+ status_indicator: is
12
+ invite_friends: Invite Friends
13
+ joined_status: "{{name}} joined {{application_name}}"
14
+ post: Share
15
+ clear: Clear
16
+ problem_with_status: "There was a problem changing your status"
17
+ update_error: "Oops... There was a problem. {{errors}}"
18
+ update_status_message: "update your status!"
19
+ time_ago: "{{time_in_words}} ago"
20
+ template_or_item_required: "An activity requires a template or an item to display correctly"
21
+ paging_newer: '&laquo; Newer'
22
+ paging_older: 'Older &raquo;'
Binary file
data/rails/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ ActiveSupport::Dependencies.load_once_paths << lib_path
2
+
3
+ require 'muck_activity'
4
+ require 'muck_activity/initialize_routes'
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), '/../lib/muck_activity/tasks')
data/test/factories.rb ADDED
@@ -0,0 +1,41 @@
1
+ Factory.sequence :email do |n|
2
+ "somebody#{n}@example.com"
3
+ end
4
+
5
+ Factory.sequence :login do |n|
6
+ "inquire#{n}"
7
+ end
8
+
9
+ Factory.sequence :name do |n|
10
+ "a_name#{n}"
11
+ end
12
+
13
+ Factory.sequence :abbr do |n|
14
+ "abbr#{n}"
15
+ end
16
+
17
+ Factory.sequence :description do |n|
18
+ "This is the description: #{n}"
19
+ end
20
+
21
+ Factory.define :user do |f|
22
+ f.login { Factory.next(:login) }
23
+ f.email { Factory.next(:email) }
24
+ f.password 'inquire_pass'
25
+ f.password_confirmation 'inquire_pass'
26
+ f.first_name 'test'
27
+ f.last_name 'guy'
28
+ f.terms_of_service true
29
+ f.activated_at DateTime.now
30
+ end
31
+
32
+ Factory.define :activity do |f|
33
+ f.item {|a| a.association(:user)}
34
+ f.template ''
35
+ f.source {|a| a.association(:user)}
36
+ f.content ''
37
+ f.title ''
38
+ f.is_status_update false
39
+ f.is_public true
40
+ f.created_at DateTime.now
41
+ end
@@ -0,0 +1,64 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class Muck::ActivitiesControllerTest < ActionController::TestCase
4
+
5
+ tests Muck::ActivitiesController
6
+
7
+ context "activities controller" do
8
+ setup do
9
+ activate_authlogic
10
+ @user = Factory(:user)
11
+ login_as @user
12
+ end
13
+
14
+ context 'on GET to index (js)' do
15
+ setup do
16
+ @activity = Factory(:activity)
17
+ get :index, :parent_type => @user.type, :parent_id => @user, :format => 'js', :latest_activity_id => @activity.to_param
18
+ end
19
+ should_respond_with :success
20
+ end
21
+
22
+ context 'on GET to index (js) no latest activity id' do
23
+ setup do
24
+ get :index, :parent_type => @user.type, :parent_id => @user, :format => 'js', :latest_activity_id => nil
25
+ end
26
+ should_respond_with :success
27
+ end
28
+
29
+ context 'on POST to create (js)' do
30
+ setup do
31
+ post :create, :activity => { :content => 'test activity' }, :parent_type => @user.type, :parent_id => @user, :format => 'js'
32
+ end
33
+ should_respond_with :success
34
+ should_not_set_the_flash
35
+ end
36
+
37
+ context 'on POST to create' do
38
+ setup do
39
+ post :create, :activity => { :content => 'test activity' }, :parent_type => @user.type, :parent_id => @user
40
+ end
41
+ should_redirect_to('show user page (user dashboard)') { @user }
42
+ end
43
+
44
+ context 'on DELETE to destroy' do
45
+ setup do
46
+ @activity = Factory(:activity, :source => @user)
47
+ delete :destroy, :id => @activity.id
48
+ end
49
+ should_respond_with :redirect
50
+ should_set_the_flash_to(I18n.t("muck.activities.item_removed"))
51
+ end
52
+
53
+ context 'on DELETE to destroy (js)' do
54
+ setup do
55
+ @activity = Factory(:activity, :source => @user)
56
+ delete :destroy, :id => @activity.id, :format => 'js'
57
+ end
58
+ should_respond_with :success
59
+ should_not_set_the_flash
60
+ end
61
+
62
+ end
63
+
64
+ end
@@ -0,0 +1,8 @@
1
+ require 'test_helper'
2
+
3
+ class MuckActivityTest < ActiveSupport::TestCase
4
+ # Replace this with your real tests.
5
+ test "the truth" do
6
+ assert true
7
+ end
8
+ end
@@ -0,0 +1,43 @@
1
+ ActiveSupport::TestCase.class_eval do
2
+
3
+ def self.should_require_login(*args)
4
+ args = Hash[*args]
5
+ login_url = args.delete :login_url
6
+ args.each do |action, verb|
7
+ should "Require login for '#{action}' action" do
8
+ send(verb, action)
9
+ assert_redirected_to(login_url)
10
+ end
11
+ end
12
+ end
13
+
14
+ def self.should_require_role(role, redirect_url, *actions)
15
+ actions.each do |action|
16
+ should "require role for '#{action}' action" do
17
+ get(action)
18
+ ensure_flash(/permission/i)
19
+ assert_response :redirect
20
+ end
21
+ end
22
+ end
23
+
24
+ #from: http://blog.internautdesign.com/2008/9/11/more-on-custom-shoulda-macros-scoping-of-instance-variables
25
+ def self.should_not_allow action, object, url= "/login", msg=nil
26
+ msg ||= "a #{object.class.to_s.downcase}"
27
+ should "not be able to #{action} #{msg}" do
28
+ object = eval(object, self.send(:binding), __FILE__, __LINE__)
29
+ get action, :id => object.id
30
+ assert_redirected_to url
31
+ end
32
+ end
33
+
34
+ def self.should_allow action, object, msg=nil
35
+ msg ||= "a #{object.class.to_s.downcase}"
36
+ should "be able to #{action} #{msg}" do
37
+ object = eval(object, self.send(:binding), __FILE__, __LINE__)
38
+ get action, :id => object.id
39
+ assert_response :success
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,35 @@
1
+ $:.reject! { |e| e.include? 'TextMate' }
2
+ ENV["RAILS_ENV"] = "test"
3
+ #require File.expand_path(File.dirname(__FILE__) + "/../../../../config/environment")
4
+ require 'factory_girl'
5
+ require 'ruby-debug'
6
+ require 'mocha'
7
+ require 'authlogic/test_case'
8
+ require 'redgreen' rescue LoadError
9
+ require File.expand_path(File.dirname(__FILE__) + '/factories')
10
+ require File.join(File.dirname(__FILE__), 'shoulda_macros', 'controller')
11
+ class ActiveSupport::TestCase
12
+ self.use_transactional_fixtures = true
13
+ self.use_instantiated_fixtures = false
14
+
15
+ include Authlogic::TestCase
16
+
17
+ def login_as(user)
18
+ success = UserSession.create(user)
19
+ if !success
20
+ errors = user.errors.full_messages.to_sentence
21
+ message = 'User has not been activated' if !user.active?
22
+ raise "could not login as #{user.to_param}. Please make sure the user is valid. #{message} #{errors}"
23
+ end
24
+ UserSession.find
25
+ end
26
+
27
+ def assure_logout
28
+ user_session = UserSession.find
29
+ user_session.destroy if user_session
30
+ end
31
+
32
+ def ensure_flash(val)
33
+ assert_contains flash.values, val, ", Flash: #{flash.inspect}"
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class ActivityFeedTest < ActiveSupport::TestCase
4
+
5
+ end
@@ -0,0 +1,49 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class ActivityTest < ActiveSupport::TestCase
4
+
5
+ context 'An Activity' do
6
+
7
+ should_validate_presence_of :source
8
+
9
+ should_belong_to :item
10
+ should_belong_to :source
11
+ should_have_many :activity_feeds
12
+
13
+ should_have_named_scope :since
14
+ should_have_named_scope :before
15
+ should_have_named_scope :recent
16
+ should_have_named_scope :only_public
17
+
18
+ end
19
+
20
+ should "require template or item" do
21
+ activity = Factory.build(:activity, :template => nil, :item => nil)
22
+ assert !activity.valid?
23
+ end
24
+
25
+ should "get the partial from the template" do
26
+ template = 'status_update'
27
+ activity = Factory(:activity, :template => template, :item => nil)
28
+ assert activity.partial == template, "The activity partial was not set to the specified template"
29
+ end
30
+
31
+ should "get the partial from the item" do
32
+ user = Factory(:user)
33
+ activity = Factory(:activity, :item => user, :template => nil)
34
+ assert activity.partial == user.class.name.underscore
35
+ end
36
+
37
+ should "be able to edit the activity" do
38
+ user = Factory(:user)
39
+ activity = Factory(:activity, :source => user)
40
+ assert activity.can_edit?(user)
41
+ end
42
+
43
+ should "not be able to edit the activity" do
44
+ user = Factory(:user)
45
+ activity = Factory(:activity)
46
+ assert !activity.can_edit?(user)
47
+ end
48
+
49
+ end
data/uninstall.rb ADDED
@@ -0,0 +1 @@
1
+ # Uninstall hook code here
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: muck-activity
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Justin Ball
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-11 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: muck-engine
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: muck-users
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: Activity engine for the muck system.
36
+ email: justinball@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ files:
44
+ - MIT-LICENSE
45
+ - README.rdoc
46
+ - Rakefile
47
+ - VERSION
48
+ - app/controllers/muck/activities_controller.rb
49
+ - app/helpers/muck_activity_helper.rb
50
+ - app/models/activity.rb
51
+ - app/models/activity_feed.rb
52
+ - app/views/activities/_activity_feed.html.erb
53
+ - app/views/activities/_cached_activities.html.erb
54
+ - app/views/activities/_current_status.html.erb
55
+ - app/views/activities/_delete.html.erb
56
+ - app/views/activities/_status_update.html.erb
57
+ - app/views/activity_templates/_status_update.html.erb
58
+ - config/muck_activity_routes.rb
59
+ - db/migrate/20090402033319_add_muck_activities.rb
60
+ - install.rb
61
+ - lib/muck_activity.rb
62
+ - lib/muck_activity/initialize_routes.rb
63
+ - lib/muck_activity/tasks.rb
64
+ - locales/en.yml
65
+ - public/images/loading.gif
66
+ - rails/init.rb
67
+ - tasks/muck_activity_tasks.rake
68
+ - test/factories.rb
69
+ - test/functional/activities_controller_test.rb
70
+ - test/muck_activity_test.rb
71
+ - test/shoulda_macros/controller.rb
72
+ - test/test_helper.rb
73
+ - test/unit/activity_feed_test.rb
74
+ - test/unit/activity_test.rb
75
+ - uninstall.rb
76
+ has_rdoc: true
77
+ homepage: http://github.com/jbasdf/muck_activity
78
+ post_install_message:
79
+ rdoc_options:
80
+ - --charset=UTF-8
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: "0"
88
+ version:
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: "0"
94
+ version:
95
+ requirements: []
96
+
97
+ rubyforge_project: muck-activity
98
+ rubygems_version: 1.3.1
99
+ signing_key:
100
+ specification_version: 2
101
+ summary: Activity engine for the muck system
102
+ test_files:
103
+ - test/factories.rb
104
+ - test/functional/activities_controller_test.rb
105
+ - test/muck_activity_test.rb
106
+ - test/shoulda_macros/controller.rb
107
+ - test/test_helper.rb
108
+ - test/unit/activity_feed_test.rb
109
+ - test/unit/activity_test.rb