activity_hub 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.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +260 -0
  5. data/Rakefile +20 -0
  6. data/lib/generators/public_activity.rb +16 -0
  7. data/lib/generators/public_activity/migration/migration_generator.rb +19 -0
  8. data/lib/generators/public_activity/migration_upgrade/migration_upgrade_generator.rb +19 -0
  9. data/lib/generators/public_activity/migration_upgrade/templates/upgrade.rb +11 -0
  10. data/lib/public_activity.rb +70 -0
  11. data/lib/public_activity/actions/creation.rb +19 -0
  12. data/lib/public_activity/actions/destruction.rb +19 -0
  13. data/lib/public_activity/actions/update.rb +20 -0
  14. data/lib/public_activity/activity.rb +8 -0
  15. data/lib/public_activity/common.rb +363 -0
  16. data/lib/public_activity/config.rb +102 -0
  17. data/lib/public_activity/models/activist.rb +11 -0
  18. data/lib/public_activity/models/activity.rb +6 -0
  19. data/lib/public_activity/models/adapter.rb +7 -0
  20. data/lib/public_activity/models/trackable.rb +11 -0
  21. data/lib/public_activity/orm/active_record.rb +7 -0
  22. data/lib/public_activity/orm/active_record/activist.rb +35 -0
  23. data/lib/public_activity/orm/active_record/activity.rb +66 -0
  24. data/lib/public_activity/orm/active_record/adapter.rb +23 -0
  25. data/lib/public_activity/orm/active_record/trackable.rb +17 -0
  26. data/lib/public_activity/orm/mongo_mapper.rb +6 -0
  27. data/lib/public_activity/orm/mongo_mapper/activist.rb +36 -0
  28. data/lib/public_activity/orm/mongo_mapper/activity.rb +35 -0
  29. data/lib/public_activity/orm/mongo_mapper/adapter.rb +19 -0
  30. data/lib/public_activity/orm/mongo_mapper/trackable.rb +13 -0
  31. data/lib/public_activity/orm/mongoid.rb +6 -0
  32. data/lib/public_activity/orm/mongoid/activist.rb +36 -0
  33. data/lib/public_activity/orm/mongoid/activity.rb +34 -0
  34. data/lib/public_activity/orm/mongoid/adapter.rb +19 -0
  35. data/lib/public_activity/orm/mongoid/trackable.rb +13 -0
  36. data/lib/public_activity/renderable.rb +166 -0
  37. data/lib/public_activity/roles/deactivatable.rb +44 -0
  38. data/lib/public_activity/roles/tracked.rb +196 -0
  39. data/lib/public_activity/testing.rb +37 -0
  40. data/lib/public_activity/utility/store_controller.rb +32 -0
  41. data/lib/public_activity/utility/view_helpers.rb +30 -0
  42. data/lib/public_activity/version.rb +6 -0
  43. data/test/migrations/001_create_activities.rb +25 -0
  44. data/test/migrations/002_create_articles.rb +15 -0
  45. data/test/migrations/003_create_users.rb +12 -0
  46. data/test/migrations/004_add_nonstandard_to_activities.rb +11 -0
  47. data/test/migrations_base.rb +7 -0
  48. data/test/mongo_mapper.yml +4 -0
  49. data/test/mongoid.yml +6 -0
  50. data/test/test_activist.rb +58 -0
  51. data/test/test_activity.rb +89 -0
  52. data/test/test_common.rb +194 -0
  53. data/test/test_controller_integration.rb +42 -0
  54. data/test/test_generators.rb +31 -0
  55. data/test/test_helper.rb +144 -0
  56. data/test/test_testing.rb +37 -0
  57. data/test/test_tracking.rb +385 -0
  58. data/test/test_view_helpers.rb +38 -0
  59. data/test/views/custom/_layout.erb +1 -0
  60. data/test/views/custom/_test.erb +1 -0
  61. data/test/views/layouts/_activity.erb +1 -0
  62. data/test/views/public_activity/_test.erb +8 -0
  63. metadata +295 -0
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+ # Provides helper methods for selecting activities from a user.
5
+ module Activist
6
+ # Delegates to configured ORM.
7
+ def self.included(base)
8
+ base.extend PublicActivity::inherit_orm("Activist")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+ class Activity < inherit_orm("Activity")
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+ # Loads database-specific routines for use by PublicActivity.
5
+ class Adapter < inherit_orm("Adapter")
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+ # Provides association for activities bound to this object by *trackable*.
5
+ module Trackable
6
+ # Delegates to ORM.
7
+ def self.included(base)
8
+ base.extend PublicActivity::inherit_orm("Trackable")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+ require_relative 'active_record/activity.rb'
5
+ require_relative 'active_record/adapter.rb'
6
+ require_relative 'active_record/activist.rb'
7
+ require_relative 'active_record/trackable.rb'
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+ module ORM
5
+ module ActiveRecord
6
+ # Module extending classes that serve as owners
7
+ module Activist
8
+ # Adds ActiveRecord associations to model to simplify fetching
9
+ # so you can list activities performed by the owner.
10
+ # It is completely optional. Any model can be an owner to an activity
11
+ # even without being an explicit activist.
12
+ #
13
+ # == Usage:
14
+ # In model:
15
+ #
16
+ # class User < ActiveRecord::Base
17
+ # include PublicActivity::Model
18
+ # activist
19
+ # end
20
+ #
21
+ # In controller:
22
+ # User.first.activities
23
+ #
24
+ def activist
25
+ has_many :activities_as_owner,
26
+ :class_name => "::PublicActivity::Activity",
27
+ :as => :owner
28
+ has_many :activities_as_recipient,
29
+ :class_name => "::PublicActivity::Activity",
30
+ :as => :recipient
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+
5
+ if not defined? ::PG::ConnectionBad
6
+ module ::PG
7
+ class ConnectionBad < Exception; end
8
+ end
9
+ end
10
+ if not defined? Mysql2::Error::ConnectionError
11
+ module Mysql2
12
+ module Error
13
+ class ConnectionError < Exception; end
14
+ end
15
+ end
16
+ end
17
+
18
+ module ORM
19
+ module ActiveRecord
20
+ # The ActiveRecord model containing
21
+ # details about recorded activity.
22
+ class Activity < ::ActiveRecord::Base
23
+ include Renderable
24
+ self.table_name = PublicActivity.config.table_name
25
+ self.abstract_class = true
26
+
27
+ # Define polymorphic association to the parent
28
+ belongs_to :trackable, :polymorphic => true
29
+
30
+ case ::ActiveRecord::VERSION::MAJOR
31
+ when 3..4
32
+ # Define ownership to a resource responsible for this activity
33
+ belongs_to :owner, :polymorphic => true
34
+ # Define ownership to a resource targeted by this activity
35
+ belongs_to :recipient, :polymorphic => true
36
+ when 5..6
37
+ with_options(:required => false) do
38
+ # Define ownership to a resource responsible for this activity
39
+ belongs_to :owner, :polymorphic => true
40
+ # Define ownership to a resource targeted by this activity
41
+ belongs_to :recipient, :polymorphic => true
42
+ end
43
+ end
44
+
45
+ # Serialize parameters Hash
46
+ begin
47
+ if table_exists?
48
+ serialize :parameters, Hash unless [:json, :jsonb, :hstore].include?(columns_hash['parameters'].type)
49
+ else
50
+ warn("[WARN] table #{name} doesn't exist. Skipping PublicActivity::Activity#parameters's serialization")
51
+ end
52
+ rescue ::ActiveRecord::NoDatabaseError => e
53
+ warn("[WARN] database doesn't exist. Skipping PublicActivity::Activity#parameters's serialization")
54
+ rescue ::PG::ConnectionBad => e
55
+ warn("[WARN] couldn't connect to database. Skipping PublicActivity::Activity#parameters's serialization")
56
+ rescue Mysql2::Error::ConnectionError
57
+ warn("[WARN] couldn't connect to database. Skipping PublicActivity::Activity#parameters's serialization")
58
+ end
59
+
60
+ if ::ActiveRecord::VERSION::MAJOR < 4 || defined?(ProtectedAttributes)
61
+ attr_accessible :key, :owner, :parameters, :recipient, :trackable
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+ module ORM
5
+ # Support for ActiveRecord for PublicActivity. Used by default and supported
6
+ # officialy.
7
+ module ActiveRecord
8
+ # Provides ActiveRecord specific, database-related routines for use by
9
+ # PublicActivity.
10
+ class Adapter
11
+ # Creates the activity on `trackable` with `options`
12
+ def self.create_activity(trackable, options)
13
+ trackable.activities.create options
14
+ end
15
+
16
+ # Creates activity on `trackable` with `options`; throws error on validation failure
17
+ def self.create_activity!(trackable, options)
18
+ trackable.activities.create! options
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+ module ORM
5
+ module ActiveRecord
6
+ # Implements {PublicActivity::Trackable} for ActiveRecord
7
+ # @see PublicActivity::Trackable
8
+ module Trackable
9
+ # Creates an association for activities where self is the *trackable*
10
+ # object.
11
+ def self.extended(base)
12
+ base.has_many :activities, :class_name => "::PublicActivity::Activity", :as => :trackable
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "mongo_mapper/activity.rb"
4
+ require_relative "mongo_mapper/adapter.rb"
5
+ require_relative "mongo_mapper/activist.rb"
6
+ require_relative "mongo_mapper/trackable.rb"
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+ module ORM
5
+ module MongoMapper
6
+ # Module extending classes that serve as owners
7
+ module Activist
8
+ # Adds MongoMapper associations to model to simplify fetching
9
+ # so you can list activities performed by the owner.
10
+ # It is completely optional. Any model can be an owner to an activity
11
+ # even without being an explicit activist.
12
+ #
13
+ # == Usage:
14
+ # In model:
15
+ #
16
+ # class User
17
+ # include MongoMapper::Document
18
+ # include PublicActivity::Model
19
+ # activist
20
+ # end
21
+ #
22
+ # In controller:
23
+ # User.first.activities
24
+ #
25
+ def activist
26
+ many :activities_as_owner,
27
+ :class_name => "::PublicActivity::Activity",
28
+ :as => :owner
29
+ many :activities_as_recipient,
30
+ :class_name => "::PublicActivity::Activity",
31
+ :as => :recipient
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongo_mapper'
4
+ require 'active_support/core_ext'
5
+
6
+ module PublicActivity
7
+ module ORM
8
+ module MongoMapper
9
+ # The MongoMapper document containing
10
+ # details about recorded activity.
11
+ class Activity
12
+ include ::MongoMapper::Document
13
+ include Renderable
14
+
15
+ class SymbolHash < Hash
16
+ def self.from_mongo(value)
17
+ value.symbolize_keys unless value.nil?
18
+ end
19
+ end
20
+
21
+ # Define polymorphic association to the parent
22
+ belongs_to :trackable, polymorphic: true
23
+ # Define ownership to a resource responsible for this activity
24
+ belongs_to :owner, polymorphic: true
25
+ # Define ownership to a resource targeted by this activity
26
+ belongs_to :recipient, polymorphic: true
27
+
28
+ key :key, String
29
+ key :parameters, SymbolHash
30
+
31
+ timestamps!
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+ module ORM
5
+ module MongoMapper
6
+ class Adapter
7
+ # Creates the activity on `trackable` with `options`
8
+ def self.create_activity(trackable, options)
9
+ trackable.activities.create options
10
+ end
11
+
12
+ # Creates activity on `trackable` with `options`; throws error on validation failure
13
+ def self.create_activity!(trackable, options)
14
+ trackable.activities.create! options
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+ module ORM
5
+ module MongoMapper
6
+ module Trackable
7
+ def self.extended(base)
8
+ base.many :activities, :class_name => "::PublicActivity::Activity", order: :created_at.asc, :as => :trackable
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "mongoid/activity.rb"
4
+ require_relative "mongoid/adapter.rb"
5
+ require_relative "mongoid/activist.rb"
6
+ require_relative "mongoid/trackable.rb"
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+ module ORM
5
+ module Mongoid
6
+ # Module extending classes that serve as owners
7
+ module Activist
8
+ # Adds ActiveRecord associations to model to simplify fetching
9
+ # so you can list activities performed by the owner.
10
+ # It is completely optional. Any model can be an owner to an activity
11
+ # even without being an explicit activist.
12
+ #
13
+ # == Usage:
14
+ # In model:
15
+ #
16
+ # class User < ActiveRecord::Base
17
+ # include PublicActivity::Model
18
+ # activist
19
+ # end
20
+ #
21
+ # In controller:
22
+ # User.first.activities
23
+ #
24
+ def activist
25
+ has_many :activities_as_owner,
26
+ :class_name => "::PublicActivity::Activity",
27
+ :inverse_of => :owner
28
+
29
+ has_many :activities_as_recipient,
30
+ :class_name => "::PublicActivity::Activity",
31
+ :inverse_of => :recipient
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongoid'
4
+
5
+ module PublicActivity
6
+ module ORM
7
+ module Mongoid
8
+ # The ActiveRecord model containing
9
+ # details about recorded activity.
10
+ class Activity
11
+ include ::Mongoid::Document
12
+ include ::Mongoid::Timestamps
13
+ include ::Mongoid::Attributes::Dynamic if ::Mongoid::VERSION.split('.')[0].to_i >= 4
14
+ include Renderable
15
+
16
+ if ::Mongoid::VERSION.split('.')[0].to_i >= 7
17
+ opts = { polymorphic: true, optional: false }
18
+ else
19
+ opts = { polymorphic: true }
20
+ end
21
+
22
+ # Define polymorphic association to the parent
23
+ belongs_to :trackable, opts
24
+ # Define ownership to a resource responsible for this activity
25
+ belongs_to :owner, opts
26
+ # Define ownership to a resource targeted by this activity
27
+ belongs_to :recipient, opts
28
+
29
+ field :key, type: String
30
+ field :parameters, type: Hash
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+ module ORM
5
+ module Mongoid
6
+ class Adapter
7
+ # Creates the activity on `trackable` with `options`
8
+ def self.create_activity(trackable, options)
9
+ trackable.activities.create options
10
+ end
11
+
12
+ # Creates activity on `trackable` with `options`; throws error on validation failure
13
+ def self.create_activity!(trackable, options)
14
+ trackable.activities.create! options
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+ module ORM
5
+ module Mongoid
6
+ module Trackable
7
+ def self.extended(base)
8
+ base.has_many :activities, :class_name => "::PublicActivity::Activity", :as => :trackable
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PublicActivity
4
+ # Provides logic for rendering activities. Handles both i18n strings
5
+ # support and smart partials rendering (different templates per activity key).
6
+ module Renderable
7
+ # Virtual attribute returning text description of the activity
8
+ # using the activity's key to translate using i18n.
9
+ def text(params = {})
10
+ # TODO: some helper for key transformation for two supported formats
11
+ k = key.split('.')
12
+ k.unshift('activity') if k.first != 'activity'
13
+ k = k.join('.')
14
+
15
+ I18n.t(k, parameters.merge(params) || {})
16
+ end
17
+
18
+ # Renders activity from views.
19
+ #
20
+ # @param [ActionView::Base] context
21
+ # @return [nil] nil
22
+ #
23
+ # Renders activity to the given ActionView context with included
24
+ # AV::Helpers::RenderingHelper (most commonly just ActionView::Base)
25
+ #
26
+ # The *preferred* *way* of rendering activities is
27
+ # to provide a template specifying how the rendering should be happening.
28
+ # However, one may choose using _I18n_ based approach when developing
29
+ # an application that supports plenty of languages.
30
+ #
31
+ # If partial view exists that matches the *key* attribute
32
+ # renders that partial with local variables set to contain both
33
+ # Activity and activity_parameters (hash with indifferent access)
34
+ #
35
+ # Otherwise, it outputs the I18n translation to the context
36
+ # @example Render a list of all activities from a view (erb)
37
+ # <ul>
38
+ # <% for activity in PublicActivity::Activity.all %>
39
+ # <li><%= render_activity(activity) %></li>
40
+ # <% end %>
41
+ # </ul>
42
+ #
43
+ # = Layouts
44
+ # You can supply a layout that will be used for activity partials
45
+ # with :layout param.
46
+ # Keep in mind that layouts for partials are also partials.
47
+ # @example Supply a layout
48
+ # # in views:
49
+ # # All examples look for a layout in app/views/layouts/_activity.erb
50
+ # render_activity @activity, :layout => "activity"
51
+ # render_activity @activity, :layout => "layouts/activity"
52
+ # render_activity @activity, :layout => :activity
53
+ #
54
+ # # app/views/layouts/_activity.erb
55
+ # <p><%= a.created_at %></p>
56
+ # <%= yield %>
57
+ #
58
+ # == Custom Layout Location
59
+ # You can customize the layout directory by supplying :layout_root
60
+ # or by using an absolute path.
61
+ #
62
+ # @example Declare custom layout location
63
+ #
64
+ # # Both examples look for a layout in "app/views/custom/_layout.erb"
65
+ #
66
+ # render_activity @activity, :layout_root => "custom"
67
+ # render_activity @activity, :layout => "/custom/layout"
68
+ #
69
+ # = Creating a template
70
+ # To use templates for formatting how the activity should render,
71
+ # create a template based on activity key, for example:
72
+ #
73
+ # Given a key _activity.article.create_, create directory tree
74
+ # _app/views/public_activity/article/_ and create the _create_ partial there
75
+ #
76
+ # Note that if a key consists of more than three parts splitted by commas, your
77
+ # directory structure will have to be deeper, for example:
78
+ # activity.article.comments.destroy => app/views/public_activity/articles/comments/_destroy.html.erb
79
+ #
80
+ # == Custom Directory
81
+ # You can override the default `public_directory` template root with the :root parameter
82
+ #
83
+ # @example Custom template root
84
+ # # look for templates inside of /app/views/custom instead of /app/views/public_directory
85
+ # render_activity @activity, :root => "custom"
86
+ #
87
+ # == Variables in templates
88
+ # From within a template there are two variables at your disposal:
89
+ # * activity (aliased as *a* for a shortcut)
90
+ # * params (aliased as *p*) [converted into a HashWithIndifferentAccess]
91
+ #
92
+ # @example Template for key: _activity.article.create_ (erb)
93
+ # <p>
94
+ # Article <strong><%= p[:name] %></strong>
95
+ # was written by <em><%= p["author"] %></em>
96
+ # <%= distance_of_time_in_words_to_now(a.created_at) %>
97
+ # </p>
98
+ def render(context, params = {})
99
+ partial_root = params.delete(:root) || 'public_activity'
100
+ partial_path = nil
101
+ layout_root = params.delete(:layout_root) || 'layouts'
102
+
103
+ if params.has_key? :display
104
+ if params[:display].to_sym == :"i18n"
105
+ text = self.text(params)
106
+ return context.render :text => text, :plain => text
107
+ else
108
+ partial_path = File.join(partial_root, params[:display].to_s)
109
+ end
110
+ end
111
+
112
+ context.render(
113
+ params.merge({
114
+ :partial => prepare_partial(partial_root, partial_path),
115
+ :layout => prepare_layout(layout_root, params.delete(:layout)),
116
+ :locals => prepare_locals(params)
117
+ })
118
+ )
119
+ end
120
+
121
+ def prepare_partial(root, path)
122
+ path || self.template_path(self.key, root)
123
+ end
124
+
125
+ def prepare_locals(params)
126
+ locals = params.delete(:locals) || Hash.new
127
+
128
+ controller = PublicActivity.get_controller
129
+ prepared_params = prepare_parameters(params)
130
+ locals.merge(
131
+ {
132
+ :a => self,
133
+ :activity => self,
134
+ :controller => controller,
135
+ :current_user => controller.respond_to?(:current_user) ? controller.current_user : nil,
136
+ :p => prepared_params,
137
+ :params => prepared_params
138
+ }
139
+ )
140
+ end
141
+
142
+ def prepare_layout(root, layout)
143
+ if layout
144
+ path = layout.to_s
145
+ unless path.starts_with?(root) || path.starts_with?("/")
146
+ return File.join(root, path)
147
+ end
148
+ end
149
+ layout
150
+ end
151
+
152
+ def prepare_parameters(params)
153
+ @prepared_params ||= self.parameters.with_indifferent_access.merge(params)
154
+ end
155
+
156
+ protected
157
+
158
+ # Builds the path to template based on activity key
159
+ def template_path(key, partial_root)
160
+ path = key.split(".")
161
+ path.delete_at(0) if path[0] == "activity"
162
+ path.unshift partial_root
163
+ path.join("/")
164
+ end
165
+ end
166
+ end