public_activity 0.5.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. data/Gemfile +19 -2
  2. data/README.md +103 -19
  3. data/UPGRADING +4 -13
  4. data/lib/generators/public_activity/migration/templates/migration.rb +4 -0
  5. data/lib/public_activity.rb +15 -11
  6. data/lib/public_activity/{creation.rb → actions/creation.rb} +0 -0
  7. data/lib/public_activity/{destruction.rb → actions/destruction.rb} +0 -0
  8. data/lib/public_activity/{update.rb → actions/update.rb} +0 -0
  9. data/lib/public_activity/common.rb +195 -18
  10. data/lib/public_activity/config.rb +49 -3
  11. data/lib/public_activity/orm/active_record.rb +5 -0
  12. data/lib/public_activity/orm/active_record/activist.rb +42 -0
  13. data/lib/public_activity/orm/active_record/activity.rb +23 -0
  14. data/lib/public_activity/orm/active_record/adapter.rb +14 -0
  15. data/lib/public_activity/orm/active_record/trackable.rb +11 -0
  16. data/lib/public_activity/orm/mongoid.rb +4 -0
  17. data/lib/public_activity/orm/mongoid/activist.rb +42 -0
  18. data/lib/public_activity/orm/mongoid/activity.rb +25 -0
  19. data/lib/public_activity/orm/mongoid/adapter.rb +14 -0
  20. data/lib/public_activity/orm/mongoid/trackable.rb +11 -0
  21. data/lib/public_activity/{activity.rb → renderable.rb} +40 -56
  22. data/lib/public_activity/roles/deactivatable.rb +39 -0
  23. data/lib/public_activity/roles/tracked.rb +183 -0
  24. data/lib/public_activity/{store_controller.rb → utility/store_controller.rb} +0 -0
  25. data/lib/public_activity/{view_helpers.rb → utility/view_helpers.rb} +0 -0
  26. data/lib/public_activity/version.rb +1 -1
  27. metadata +40 -154
  28. data/lib/public_activity/activist.rb +0 -37
  29. data/lib/public_activity/tracked.rb +0 -337
@@ -1,12 +1,58 @@
1
+ require 'singleton'
2
+
1
3
  module PublicActivity
2
4
  # Class used to initialize configuration object.
3
5
  class Config
4
- include Singleton
6
+ include ::Singleton
5
7
  attr_accessor :enabled
6
8
 
9
+ @@orm = :active_record
10
+
7
11
  def initialize
8
12
  # Indicates whether PublicActivity is enabled globally
9
- @enabled = true
13
+ @enabled = true
14
+ load_orm
15
+ end
16
+
17
+ def self.set &block
18
+ b = Block.new
19
+ b.instance_eval &block
20
+ orm = b.instance_variable_get(:@orm)
21
+ @@orm = orm unless orm.nil?
22
+ enabled = b.instance_variable_get(:@en)
23
+ instance
24
+ instance.instance_variable_set(:@enabled, enabled) unless enabled.nil?
25
+ end
26
+
27
+ def self.orm(orm = nil)
28
+ @@orm = (orm ? orm.to_sym : false) || @@orm
29
+ end
30
+
31
+ def self.orm=(orm = nil)
32
+ orm(orm)
33
+ end
34
+
35
+ def orm(orm=nil)
36
+ self.class.orm(orm)
37
+ end
38
+
39
+ def load_orm
40
+ require "public_activity/orm/#{@@orm.to_s}"
41
+ m = "PublicActivity::ORM::#{@@orm.to_s.classify}".constantize
42
+ ::PublicActivity.const_set(:Activity, m.const_get(:Activity))
43
+ ::PublicActivity.const_set(:Adapter, m.const_get(:Adapter))
44
+ ::PublicActivity.const_set(:Activist, m.const_get(:Activist))
45
+ ::PublicActivity.const_set(:Trackable, m.const_get(:Trackable))
46
+ end
47
+
48
+ class Block
49
+ def orm(orm = nil)
50
+ @orm = (orm ? orm.to_sym : false) || @orm
51
+ end
52
+
53
+ def enabled(en = nil)
54
+ @en = (en.nil? ? @en : en)
55
+ end
10
56
  end
11
57
  end
12
- end
58
+ end
@@ -0,0 +1,5 @@
1
+ require 'active_record'
2
+ require_relative 'active_record/activity.rb'
3
+ require_relative 'active_record/adapter.rb'
4
+ require_relative 'active_record/activist.rb'
5
+ require_relative 'active_record/trackable.rb'
@@ -0,0 +1,42 @@
1
+ module PublicActivity
2
+ module ORM
3
+ module ActiveRecord
4
+ # Module extending classes that serve as owners
5
+ module Activist
6
+ extend ActiveSupport::Concern
7
+
8
+ # Association of activities as their owner.
9
+ # @!method activities
10
+ # @return [Array<Activity>] Activities which self is the owner of.
11
+
12
+ # Association of activities as their recipient.
13
+ # @!method private_activities
14
+ # @return [Array<Activity>] Activities which self is the recipient of.
15
+
16
+ # Module extending classes that serve as owners
17
+ module ClassMethods
18
+ # Adds ActiveRecord associations to model to simplify fetching
19
+ # so you can list activities performed by the owner.
20
+ # It is completely optional. Any model can be an owner to an activity
21
+ # even without being an explicit activist.
22
+ #
23
+ # == Usage:
24
+ # In model:
25
+ #
26
+ # class User < ActiveRecord::Base
27
+ # include PublicActivity::Model
28
+ # activist
29
+ # end
30
+ #
31
+ # In controller:
32
+ # User.first.activities
33
+ #
34
+ def activist
35
+ has_many :activities_as_owner, :class_name => "PublicActivity::Activity", :as => :owner
36
+ has_many :activities_as_recipient, :class_name => "PublicActivity::Activity", :as => :recipient
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ module PublicActivity
2
+ module ORM
3
+ module ActiveRecord
4
+ # The ActiveRecord model containing
5
+ # details about recorded activity.
6
+ class Activity < ::ActiveRecord::Base
7
+ include Renderable
8
+
9
+ # Define polymorphic association to the parent
10
+ belongs_to :trackable, :polymorphic => true
11
+ # Define ownership to a resource responsible for this activity
12
+ belongs_to :owner, :polymorphic => true
13
+ # Define ownership to a resource targeted by this activity
14
+ belongs_to :recipient, :polymorphic => true
15
+ # Serialize parameters Hash
16
+ serialize :parameters, Hash
17
+
18
+ # should recipient and owner be accessible?
19
+ attr_accessible :key, :owner, :parameters, :recipient, :trackable
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,14 @@
1
+ module PublicActivity
2
+ module ORM
3
+ module ActiveRecord
4
+ module Adapter
5
+ class << self
6
+ # Creates the activity on `trackable` with `options`
7
+ def create_activity(trackable, options)
8
+ trackable.activities.create options
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module PublicActivity
2
+ module ORM
3
+ module ActiveRecord
4
+ module Trackable
5
+ def self.included(base)
6
+ base.has_many :activities, :class_name => "::PublicActivity::Activity", :as => :trackable
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ require_relative "mongoid/activity.rb"
2
+ require_relative "mongoid/adapter.rb"
3
+ require_relative "mongoid/activist.rb"
4
+ require_relative "mongoid/trackable.rb"
@@ -0,0 +1,42 @@
1
+ module PublicActivity
2
+ module ORM
3
+ module Mongoid
4
+ # Module extending classes that serve as owners
5
+ module Activist
6
+ extend ActiveSupport::Concern
7
+
8
+ # Association of activities as their owner.
9
+ # @!method activities
10
+ # @return [Array<Activity>] Activities which self is the owner of.
11
+
12
+ # Association of activities as their recipient.
13
+ # @!method private_activities
14
+ # @return [Array<Activity>] Activities which self is the recipient of.
15
+
16
+ # Module extending classes that serve as owners
17
+ module ClassMethods
18
+ # Adds ActiveRecord associations to model to simplify fetching
19
+ # so you can list activities performed by the owner.
20
+ # It is completely optional. Any model can be an owner to an activity
21
+ # even without being an explicit activist.
22
+ #
23
+ # == Usage:
24
+ # In model:
25
+ #
26
+ # class User < ActiveRecord::Base
27
+ # include PublicActivity::Model
28
+ # activist
29
+ # end
30
+ #
31
+ # In controller:
32
+ # User.first.activities
33
+ #
34
+ def activist
35
+ has_many :activities_as_owner, :class_name => "PublicActivity::Activity", :inverse_of => :owner
36
+ has_many :activities_as_recipient, :class_name => "PublicActivity::Activity", :inverse_of => :recipient
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,25 @@
1
+ require 'mongoid'
2
+
3
+ module PublicActivity
4
+ module ORM
5
+ module Mongoid
6
+ # The ActiveRecord model containing
7
+ # details about recorded activity.
8
+ class Activity
9
+ include ::Mongoid::Document
10
+ include ::Mongoid::Timestamps
11
+ include Renderable
12
+
13
+ # Define polymorphic association to the parent
14
+ belongs_to :trackable, polymorphic: true
15
+ # Define ownership to a resource responsible for this activity
16
+ belongs_to :owner, polymorphic: true
17
+ # Define ownership to a resource targeted by this activity
18
+ belongs_to :recipient, polymorphic: true
19
+
20
+ field :key, type: String
21
+ field :parameters, type: Hash
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ module PublicActivity
2
+ module ORM
3
+ module Mongoid
4
+ module Adapter
5
+ class << self
6
+ # Creates the activity on `trackable` with `options`
7
+ def create_activity(trackable, options)
8
+ trackable.activities.create options
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module PublicActivity
2
+ module ORM
3
+ module Mongoid
4
+ module Trackable
5
+ def self.included(base)
6
+ base.has_many :activities, :class_name => "::PublicActivity::Activity", :as => :trackable
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,46 +1,7 @@
1
1
  module PublicActivity
2
- # The ActiveRecord model containing
3
- # details about recorded activity.
4
- class Activity < ActiveRecord::Base
5
- # Define polymorphic association to the parent
6
- belongs_to :trackable, :polymorphic => true
7
- # Define ownership to a resource responsible for this activity
8
- belongs_to :owner, :polymorphic => true
9
- # Define ownership to a resource targeted by this activity
10
- belongs_to :recipient, :polymorphic => true
11
- # Serialize parameters Hash
12
- serialize :parameters, Hash
13
-
14
- class_attribute :template
15
-
16
- # should recipient and owner be accessible?
17
- attr_accessible :key, :owner, :parameters, :recipient, :trackable
2
+ module Renderable
18
3
  # Virtual attribute returning text description of the activity
19
- # using basic ERB templating
20
- #
21
- # == Example:
22
- #
23
- # Let's say you want to show article's title inside Activity message.
24
- #
25
- # #config/pba.yml
26
- # activity:
27
- # article:
28
- # create: "New <%= trackable.name %> article has been created"
29
- # update: 'Someone modified the article'
30
- # destroy: 'Someone deleted the article!'
31
- #
32
- # And in controller:
33
- #
34
- # def create
35
- # @article = Article.new
36
- # @article.title = "Rails 3.0.5 released!"
37
- # @article.activity_params = {:title => @article.title}
38
- # @article.save
39
- # end
40
- #
41
- # Now when you list articles, you should see:
42
- # @article.activities.last.text #=> "Someone has created an article 'Rails 3.0.5 released!'"
43
- # @see #render Advanced rendering
4
+ # using the activity's key to translate using i18n.
44
5
  def text(params = {})
45
6
  # TODO: some helper for key transformation for two supported formats
46
7
  k = key.split('.')
@@ -75,6 +36,21 @@ module PublicActivity
75
36
  # <% end %>
76
37
  # </ul>
77
38
  #
39
+ # = Layouts
40
+ # You can supply a layout that will be used for activity partials
41
+ # with :layout param.
42
+ # Keep in mind that layouts for partials are also partials.
43
+ # @example Supply a layout
44
+ # # in views:
45
+ # # All examples look for a layout in app/views/layouts/_activity.erb
46
+ # render_activity @activity, :layout => "activity"
47
+ # render_activity @activity, :layout => "layouts/activity"
48
+ # render_activity @activity, :layout => :activity
49
+ #
50
+ # # app/views/layouts/_activity.erb
51
+ # <p><%= a.created_at %></p>
52
+ # <%= yield %>
53
+ #
78
54
  # = Creating a template
79
55
  # To use templates for formatting how the activity should render,
80
56
  # create a template based on activity key, for example:
@@ -84,7 +60,7 @@ module PublicActivity
84
60
  #
85
61
  # Note that if a key consists of more than three parts splitted by commas, your
86
62
  # directory structure will have to be deeper, for example:
87
- # activity.article.comments.destroy => /app/views/public_activity/articles/comments/_destroy.html.erb
63
+ # activity.article.comments.destroy => app/views/public_activity/articles/comments/_destroy.html.erb
88
64
  #
89
65
  # == Variables in templates
90
66
  # From within a template there are two variables at your disposal:
@@ -98,21 +74,29 @@ module PublicActivity
98
74
  # <%= distance_of_time_in_words_to_now(a.created_at) %>
99
75
  # </p>
100
76
  def render(context, params = {})
101
- begin
102
- params_indifferent = self.parameters.with_indifferent_access
103
- params_indifferent.merge!(params)
104
- controller = PublicActivity.get_controller
105
- context.render :partial => self.template_path(self.key),
106
- :layout => params_indifferent.delete(:layout),
107
- :locals =>
108
- {:a => self, :activity => self,
109
- :controller => controller,
110
- :current_user => controller.respond_to?(:current_user) ?
111
- controller.current_user : nil ,
112
- :p => params_indifferent, :params => params_indifferent}
113
- rescue ActionView::MissingTemplate
114
- context.render :text => self.text(params)
77
+ partial_path = nil
78
+ if params.has_key? :display
79
+ # if i18n has been requested, let it render and bail
80
+ return context.render :text => self.text(params) if params[:display].to_sym == :"i18n"
81
+ partial_path = 'public_activity/'+params[:display].to_s
82
+ end
83
+ # if we're going to render a partial, let it throw when none can be found
84
+ params_indifferent = self.parameters.with_indifferent_access
85
+ params_indifferent.merge!(params)
86
+ controller = PublicActivity.get_controller
87
+ layout = params_indifferent.delete(:layout)
88
+ if layout
89
+ layout = layout.to_s
90
+ layout = layout[0,8] == "layouts/" ? layout : "layouts/#{layout}"
115
91
  end
92
+ context.render :partial => (partial_path || self.template_path(self.key)),
93
+ :layout => layout,
94
+ :locals =>
95
+ {:a => self, :activity => self,
96
+ :controller => controller,
97
+ :current_user => controller.respond_to?(:current_user) ?
98
+ controller.current_user : nil ,
99
+ :p => params_indifferent, :params => params_indifferent}
116
100
  end
117
101
 
118
102
  protected
@@ -0,0 +1,39 @@
1
+ module PublicActivity
2
+ module Deactivatable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :public_activity_enabled_for_model
7
+ set_public_activity_class_defaults
8
+ end
9
+
10
+ # Returns true if PublicActivity is enabled
11
+ # globally and for this class.
12
+ # @return [Boolean]
13
+ # @api private
14
+ # @since 0.5.0
15
+ # overrides the method from Common
16
+ def public_activity_enabled?
17
+ PublicActivity.enabled? && self.class.public_activity_enabled_for_model
18
+ end
19
+
20
+ module ClassMethods
21
+ # Switches public_activity off for this class
22
+ def public_activity_off
23
+ self.public_activity_enabled_for_model = false
24
+ end
25
+
26
+ # Switches public_activity on for this class
27
+ def public_activity_on
28
+ self.public_activity_enabled_for_model = true
29
+ end
30
+
31
+ # @since 1.0.0
32
+ # @api private
33
+ def set_public_activity_class_defaults
34
+ super
35
+ self.public_activity_enabled_for_model = true
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,183 @@
1
+ module PublicActivity
2
+ # Main module extending classes we want to keep track of.
3
+ module Tracked
4
+ extend ActiveSupport::Concern
5
+ # A shortcut method for setting custom key, owner and parameters of {Activity}
6
+ # in one line. Accepts a hash with 3 keys:
7
+ # :key, :owner, :params. You can specify all of them or just the ones you want to overwrite.
8
+ #
9
+ # == Options
10
+ #
11
+ # [:key]
12
+ # See {#activity_key}
13
+ # [:owner]
14
+ # See {#activity_owner}
15
+ # [:params]
16
+ # See {#activity_params}
17
+ # [:recipient]
18
+ # Set the recipient for this activity. Useful for private notifications, which should only be visible to a certain user. See {#activity_recipient}.
19
+ # @example
20
+ #
21
+ # @article = Article.new
22
+ # @article.title = "New article"
23
+ # @article.activity :key => "my.custom.article.key", :owner => @article.author, :params => {:title => @article.title}
24
+ # @article.save
25
+ # @article.activities.last.key #=> "my.custom.article.key"
26
+ # @article.activities.last.parameters #=> {:title => "New article"}
27
+ #
28
+ # @param options [Hash] instance options to set on the tracked model
29
+ # @return [nil]
30
+ def activity(options = {})
31
+ rest = options.clone
32
+ self.activity_key = rest.delete(:key) if rest[:key]
33
+ self.activity_owner = rest.delete(:owner) if rest[:owner]
34
+ self.activity_params = rest.delete(:params) if rest[:params]
35
+ self.activity_recipient = rest.delete(:recipient) if rest[:recipient]
36
+ self.activity_custom_fields = rest if rest.count > 0
37
+ nil
38
+ end
39
+
40
+ # Module with basic +tracked+ method that enables tracking models.
41
+ module ClassMethods
42
+ # Adds required callbacks for creating and updating
43
+ # tracked models and adds +activities+ relation for listing
44
+ # associated activities.
45
+ #
46
+ # == Parameters:
47
+ # [:owner]
48
+ # Specify the owner of the {Activity} (person responsible for the action).
49
+ # It can be a Proc, Symbol or an ActiveRecord object:
50
+ # == Examples:
51
+ #
52
+ # tracked :owner => :author
53
+ # tracked :owner => {|o| o.author}
54
+ #
55
+ # Keep in mind that owner relation is polymorphic, so you can't just
56
+ # provide id number of the owner object.
57
+ # [:recipient]
58
+ # Specify the recipient of the {Activity}
59
+ # It can be a Proc, Symbol, or an ActiveRecord object
60
+ # == Examples:
61
+ #
62
+ # tracked :recipient => :author
63
+ # tracked :recipient => {|o| o.author}
64
+ #
65
+ # Keep in mind that recipient relation is polymorphic, so you can't just
66
+ # provide id number of the owner object.
67
+ # [:params]
68
+ # Accepts a Hash with custom parameters you want to pass to i18n.translate
69
+ # method. It is later used in {Activity#text} method.
70
+ # == Example:
71
+ # class Article < ActiveRecord::Base
72
+ # include PublicActivity::Model
73
+ # tracked :params => {
74
+ # :title => :title,
75
+ # :author_name => "Michael",
76
+ # :category_name => proc {|controller, model_instance| model_instance.category.name},
77
+ # :summary => proc {|controller, model_instance| truncate(model.text, :length => 30)}
78
+ # }
79
+ # end
80
+ #
81
+ # Values in the :params hash can either be an *exact* *value*, a *Proc/Lambda* executed before saving the activity or a *Symbol*
82
+ # which is a an attribute or a method name executed on the tracked model's instance.
83
+ #
84
+ # Everything specified here has a lower priority than parameters
85
+ # specified directly in {#activity} method.
86
+ # So treat it as a place where you provide 'default' values or where you
87
+ # specify what data should be gathered for every activity.
88
+ # For more dynamic settings refer to {Activity} model documentation.
89
+ # [:skip_defaults]
90
+ # Disables recording of activities on create/update/destroy leaving that to programmer's choice. Check {PublicActivity::Common#create_activity}
91
+ # for a guide on how to manually record activities.
92
+ # [:only]
93
+ # Accepts a symbol or an array of symbols, of which any combination of the three is accepted:
94
+ # * _:create_
95
+ # * _:update_
96
+ # * _:destroy_
97
+ # Selecting one or more of these will make PublicActivity create activities
98
+ # automatically for the tracked model on selected actions.
99
+ #
100
+ # Resulting activities will have have keys assigned to, respectively:
101
+ # * _article.create_
102
+ # * _article.update_
103
+ # * _article.destroy_
104
+ # Since only three options are valid,
105
+ # see _:except_ option for a shorter version
106
+ # [:except]
107
+ # Accepts a symbol or an array of symbols with values like in _:only_, above.
108
+ # Values provided will be subtracted from all default actions:
109
+ # (create, update, destroy).
110
+ #
111
+ # So, passing _create_ would track and automatically create
112
+ # activities on _update_ and _destroy_ actions,
113
+ # but not on the _create_ action.
114
+ # [:on]
115
+ # Accepts a Hash with key being the *action* on which to execute *value* (proc)
116
+ # Currently supported only for CRUD actions which are enabled in _:only_
117
+ # or _:except_ options on this method.
118
+ #
119
+ # Key-value pairs in this option define callbacks that can decide
120
+ # whether to create an activity or not. Procs have two attributes for
121
+ # use: _model_ and _controller_. If the proc returns true, the activity
122
+ # will be created, if not, then activity will not be saved.
123
+ #
124
+ # == Example:
125
+ # # app/models/article.rb
126
+ # tracked :on => {:update => proc {|model, controller| model.published? }}
127
+ #
128
+ # In the example above, given a model Article with boolean column _published_.
129
+ # The activities with key _article.update_ will only be created
130
+ # if the published status is set to true on that article.
131
+ # @param options [Hash] options
132
+ # @return [nil] options
133
+ def tracked(opts = {})
134
+ options = opts.clone
135
+
136
+ all_options = [:create, :update, :destroy]
137
+
138
+ if !options.has_key?(:skip_defaults) && !options[:only] && !options[:except]
139
+ include Creation
140
+ include Destruction
141
+ include Update
142
+ end
143
+ options.delete(:skip_defaults)
144
+
145
+ if options[:except]
146
+ options[:only] = all_options - Array(options.delete(:except))
147
+ end
148
+
149
+ if options[:only]
150
+ Array(options[:only]).each do |opt|
151
+ if opt.eql?(:create)
152
+ include Creation
153
+ elsif opt.eql?(:destroy)
154
+ include Destruction
155
+ elsif opt.eql?(:update)
156
+ include Update
157
+ end
158
+ end
159
+ options.delete(:only)
160
+ end
161
+
162
+ if options[:owner]
163
+ self.activity_owner_global = options.delete(:owner)
164
+ end
165
+ if options[:recipient]
166
+ self.activity_recipient_global = options.delete(:recipient)
167
+ end
168
+ if options[:params]
169
+ self.activity_params_global = options.delete(:params)
170
+ end
171
+ if options.has_key?(:on) and options[:on].is_a? Hash
172
+ self.activity_hooks = options.delete(:on).select {|_, v| v.is_a? Proc}.symbolize_keys
173
+ end
174
+
175
+ options.each do |k, v|
176
+ self.activity_custom_fields_global[k] = v
177
+ end
178
+
179
+ nil
180
+ end
181
+ end
182
+ end
183
+ end