public_activity 1.1.0 → 1.4.0

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -20
  3. data/README.md +50 -62
  4. data/lib/public_activity/activity.rb +2 -0
  5. data/lib/public_activity/common.rb +9 -7
  6. data/lib/public_activity/config.rb +15 -0
  7. data/lib/public_activity/models/activist.rb +2 -0
  8. data/lib/public_activity/models/adapter.rb +1 -0
  9. data/lib/public_activity/models/trackable.rb +2 -0
  10. data/lib/public_activity/orm/active_record/activist.rb +9 -6
  11. data/lib/public_activity/orm/active_record/activity.rb +4 -3
  12. data/lib/public_activity/orm/active_record/adapter.rb +4 -0
  13. data/lib/public_activity/orm/active_record/trackable.rb +4 -0
  14. data/lib/public_activity/orm/mongo_mapper/activist.rb +46 -0
  15. data/lib/public_activity/orm/mongo_mapper/activity.rb +33 -0
  16. data/lib/public_activity/orm/mongo_mapper/adapter.rb +12 -0
  17. data/lib/public_activity/orm/mongo_mapper/trackable.rb +11 -0
  18. data/lib/public_activity/orm/mongo_mapper.rb +4 -0
  19. data/lib/public_activity/orm/mongoid/activity.rb +1 -0
  20. data/lib/public_activity/renderable.rb +12 -8
  21. data/lib/public_activity/roles/deactivatable.rb +3 -0
  22. data/lib/public_activity/roles/tracked.rb +6 -6
  23. data/lib/public_activity/utility/view_helpers.rb +11 -3
  24. data/lib/public_activity/version.rb +1 -1
  25. data/test/migrations/001_create_activities.rb +23 -0
  26. data/test/migrations/002_create_articles.rb +11 -0
  27. data/test/migrations/003_create_users.rb +8 -0
  28. data/test/migrations/004_add_nonstandard_to_activities.rb +7 -0
  29. data/test/mongo_mapper.yml +4 -0
  30. data/test/mongoid.yml +6 -0
  31. data/test/test_activist.rb +56 -0
  32. data/test/test_activity.rb +67 -0
  33. data/test/test_common.rb +168 -0
  34. data/test/test_controller_integration.rb +41 -0
  35. data/test/test_generators.rb +41 -0
  36. data/test/test_helper.rb +124 -0
  37. data/test/test_tracking.rb +378 -0
  38. data/test/test_view_helpers.rb +36 -0
  39. data/test/views/layouts/_activity.erb +1 -0
  40. data/test/views/public_activity/_test.erb +8 -0
  41. metadata +139 -22
  42. data/UPGRADING +0 -8
@@ -3,10 +3,18 @@ module PublicActivity
3
3
  # Module extending ActionView::Base and adding `render_activity` helper.
4
4
  module ViewHelpers
5
5
  # View helper for rendering an activity, calls {PublicActivity::Activity#render} internally.
6
- def render_activity activity, options = {}
7
- activity.render self, options
6
+ def render_activity activities, options = {}
7
+ if activities.is_a? PublicActivity::Activity
8
+ activities.render self, options
9
+ elsif activities.respond_to?(:map)
10
+ # depend on ORMs to fetch as needed
11
+ # maybe we can support Postgres streaming with this?
12
+ activities.map {|activity| activity.render self, options.dup }.join.html_safe
13
+ end
8
14
  end
9
- # Helper for setting content_for in activity partial, needed to
15
+ alias_method :render_activities, :render_activity
16
+
17
+ # Helper for setting content_for in activity partial, needed to
10
18
  # flush remains in between partial renders.
11
19
  def single_content_for(name, content = nil, &block)
12
20
  @view_flow.set(name, ActiveSupport::SafeBuffer.new)
@@ -1,4 +1,4 @@
1
1
  module PublicActivity
2
2
  # A constant with gem's version
3
- VERSION = '1.1.0'
3
+ VERSION = '1.4.0'
4
4
  end
@@ -0,0 +1,23 @@
1
+ # Migration responsible for creating a table with activities
2
+ class CreateActivities < ActiveRecord::Migration
3
+ # Create table
4
+ def self.up
5
+ create_table :activities do |t|
6
+ t.belongs_to :trackable, :polymorphic => true
7
+ t.belongs_to :owner, :polymorphic => true
8
+ t.string :key
9
+ t.text :parameters
10
+ t.belongs_to :recipient, :polymorphic => true
11
+
12
+ t.timestamps
13
+ end
14
+
15
+ add_index :activities, [:trackable_id, :trackable_type]
16
+ add_index :activities, [:owner_id, :owner_type]
17
+ add_index :activities, [:recipient_id, :recipient_type]
18
+ end
19
+ # Drop table
20
+ def self.down
21
+ drop_table :activities
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ class CreateArticles < ActiveRecord::Migration
2
+ def self.up
3
+ puts "creating"
4
+ create_table :articles do |t|
5
+ t.string :name
6
+ t.boolean :published
7
+ t.belongs_to :user
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :users do |t|
4
+ t.string :name
5
+ t.timestamps
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ class AddNonstandardToActivities < ActiveRecord::Migration
2
+ def change
3
+ change_table :activities do |t|
4
+ t.string :nonstandard
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ test:
2
+ host: 127.0.0.1
3
+ port: 27017
4
+ database: public_activity_test
data/test/mongoid.yml ADDED
@@ -0,0 +1,6 @@
1
+ test:
2
+ sessions:
3
+ default:
4
+ hosts:
5
+ - 127.0.0.1:27017
6
+ database: public_activity_test
@@ -0,0 +1,56 @@
1
+ require 'test_helper'
2
+
3
+ describe PublicActivity::Activist do
4
+ it 'adds owner association' do
5
+ klass = article
6
+ klass.must_respond_to :activist
7
+ klass.activist
8
+ klass.new.must_respond_to :activities
9
+ case ENV["PA_ORM"]
10
+ when "active_record"
11
+ klass.reflect_on_association(:activities_as_owner).options[:as].must_equal :owner
12
+ when "mongoid"
13
+ klass.reflect_on_association(:activities_as_owner).options[:inverse_of].must_equal :owner
14
+ when "mongo_mapper"
15
+ klass.associations[:activities_as_owner].options[:as].must_equal :owner
16
+ end
17
+
18
+ if ENV["PA_ORM"] == "mongo_mapper"
19
+ klass.associations[:activities_as_owner].options[:class_name].must_equal "::PublicActivity::Activity"
20
+ else
21
+ klass.reflect_on_association(:activities_as_owner).options[:class_name].must_equal "::PublicActivity::Activity"
22
+ end
23
+ end
24
+
25
+ it 'returns activities from association' do
26
+ case PublicActivity::Config.orm
27
+ when :active_record
28
+ class ActivistUser < ActiveRecord::Base
29
+ include PublicActivity::Model
30
+ self.table_name = 'users'
31
+ activist
32
+ end
33
+ when :mongoid
34
+ class ActivistUser
35
+ include Mongoid::Document
36
+ include PublicActivity::Model
37
+ activist
38
+
39
+ field :name, type: String
40
+ end
41
+ when :mongo_mapper
42
+ class ActivistUser
43
+ include MongoMapper::Document
44
+ include PublicActivity::Model
45
+ activist
46
+
47
+ key :name, String
48
+ end
49
+ end
50
+ owner = ActivistUser.create(:name => "Peter Pan")
51
+ a = article(owner: owner).new
52
+ a.save
53
+
54
+ owner.activities_as_owner.length.must_equal 1
55
+ end
56
+ end
@@ -0,0 +1,67 @@
1
+ require 'test_helper'
2
+
3
+ describe 'PublicActivity::Activity Rendering' do
4
+ describe '#text' do
5
+ subject { PublicActivity::Activity.new(:key => 'activity.test', :parameters => {:one => 1}) }
6
+
7
+ specify '#text uses translations' do
8
+ subject.save
9
+ I18n.config.backend.store_translations(:en,
10
+ {:activity => {:test => '%{one} %{two}'}}
11
+ )
12
+ subject.text(:two => 2).must_equal('1 2')
13
+ subject.parameters.must_equal({:one => 1})
14
+ end
15
+ end
16
+
17
+ describe '#render' do
18
+ subject do
19
+ s = PublicActivity::Activity.new(:key => 'activity.test', :parameters => {:one => 1})
20
+ s.save && s
21
+ end
22
+
23
+ let(:template_output) { "<strong>1, 2</strong>\n<em>activity.test, #{subject.id}</em>\n" }
24
+ before { @controller.view_paths << File.expand_path('../views', __FILE__) }
25
+
26
+ it 'uses view partials when available' do
27
+ PublicActivity.set_controller(Struct.new(:current_user).new('fake'))
28
+ subject.render(self, :two => 2)
29
+ rendered.must_equal template_output + "fake\n"
30
+ end
31
+
32
+ it 'uses requested partial'
33
+
34
+ it 'uses view partials without controller' do
35
+ PublicActivity.set_controller(nil)
36
+ subject.render(self, :two => 2)
37
+ rendered.must_equal template_output + "\n"
38
+ end
39
+
40
+ it 'provides local variables' do
41
+ PublicActivity.set_controller(nil)
42
+ subject.render(self, locals: {two: 2})
43
+ rendered.chomp.must_equal "2"
44
+ end
45
+
46
+ it 'uses translations only when requested' do
47
+ I18n.config.backend.store_translations(:en,
48
+ {:activity => {:test => '%{one} %{two}'}}
49
+ )
50
+ @controller.view_paths.paths.clear
51
+ subject.render(self, two: 2, display: :i18n)
52
+ rendered.must_equal '1 2'
53
+ end
54
+
55
+ it "uses specified layout" do
56
+ PublicActivity.set_controller(nil)
57
+ subject.render(self, :layout => "activity")
58
+ rendered.must_include "Here be the layouts"
59
+
60
+ subject.render(self, :layout => "layouts/activity")
61
+ rendered.must_include "Here be the layouts"
62
+
63
+ subject.render(self, :layout => :activity)
64
+ rendered.must_include "Here be the layouts"
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,168 @@
1
+ require 'test_helper'
2
+
3
+ describe PublicActivity::Common do
4
+ before do
5
+ @owner = User.create(:name => "Peter Pan")
6
+ @recipient = User.create(:name => "Bruce Wayne")
7
+ @options = {:params => {:author_name => "Peter",
8
+ :summary => "Default summary goes here..."},
9
+ :owner => @owner, :recipient => @recipient}
10
+ end
11
+ subject { article(@options).new }
12
+
13
+ it 'prioritizes parameters passed to #create_activity' do
14
+ subject.save
15
+ subject.create_activity(:test, params: {author_name: 'Pan'}).parameters[:author_name].must_equal 'Pan'
16
+ subject.create_activity(:test, parameters: {author_name: 'Pan'}).parameters[:author_name].must_equal 'Pan'
17
+ subject.create_activity(:test, params: {author_name: nil}).parameters[:author_name].must_be_nil
18
+ subject.create_activity(:test, parameters: {author_name: nil}).parameters[:author_name].must_be_nil
19
+ end
20
+
21
+ it 'prioritizes owner passed to #create_activity' do
22
+ subject.save
23
+ subject.create_activity(:test, owner: @recipient).owner.must_equal @recipient
24
+ subject.create_activity(:test, owner: nil).owner.must_be_nil
25
+ end
26
+
27
+ it 'prioritizes recipient passed to #create_activity' do
28
+ subject.save
29
+ subject.create_activity(:test, recipient: @owner).recipient.must_equal @owner
30
+ subject.create_activity(:test, recipient: nil).recipient.must_be_nil
31
+ end
32
+
33
+ it 'uses global fields' do
34
+ subject.save
35
+ activity = subject.activities.last
36
+ activity.parameters.must_equal @options[:params]
37
+ activity.owner.must_equal @owner
38
+ end
39
+
40
+ it 'allows custom fields' do
41
+ subject.save
42
+ subject.create_activity :with_custom_fields, nonstandard: "Custom allowed"
43
+ subject.activities.last.nonstandard.must_equal "Custom allowed"
44
+ end
45
+
46
+ it '#create_activity returns a new activity object' do
47
+ subject.save
48
+ subject.create_activity("some.key").wont_be_nil
49
+ end
50
+
51
+ it 'allows passing owner through #create_activity' do
52
+ article = article().new
53
+ article.save
54
+ activity = article.create_activity("some.key", :owner => @owner)
55
+ activity.owner.must_equal @owner
56
+ end
57
+
58
+ it 'allows resolving custom fields' do
59
+ subject.name = "Resolving is great"
60
+ subject.published = true
61
+ subject.save
62
+ subject.create_activity :with_custom_fields, nonstandard: :name
63
+ subject.activities.last.nonstandard.must_equal "Resolving is great"
64
+ subject.create_activity :with_custom_fields_2, nonstandard: proc {|_, model| model.published.to_s}
65
+ subject.activities.last.nonstandard.must_equal "true"
66
+ end
67
+
68
+ it 'inherits instance parameters' do
69
+ subject.activity :params => {:author_name => "Michael"}
70
+ subject.save
71
+ activity = subject.activities.last
72
+
73
+ activity.parameters[:author_name].must_equal "Michael"
74
+ end
75
+
76
+ it 'accepts instance recipient' do
77
+ subject.activity :recipient => @recipient
78
+ subject.save
79
+ subject.activities.last.recipient.must_equal @recipient
80
+ end
81
+
82
+ it 'accepts instance owner' do
83
+ subject.activity :owner => @owner
84
+ subject.save
85
+ subject.activities.last.owner.must_equal @owner
86
+ end
87
+
88
+ it 'accepts owner as a symbol' do
89
+ klass = article(:owner => :user)
90
+ @article = klass.new(:user => @owner)
91
+ @article.save
92
+ activity = @article.activities.last
93
+
94
+ activity.owner.must_equal @owner
95
+ end
96
+
97
+ describe '#extract_key' do
98
+ describe 'for class#activity_key method' do
99
+ before do
100
+ @article = article(:owner => :user).new(:user => @owner)
101
+ end
102
+
103
+ it 'assigns key to value of activity_key if set' do
104
+ def @article.activity_key; "my_custom_key" end
105
+
106
+ @article.extract_key(:create, {}).must_equal "my_custom_key"
107
+ end
108
+
109
+ it 'assigns key based on class name as fallback' do
110
+ def @article.activity_key; nil end
111
+
112
+ @article.extract_key(:create).must_equal "article.create"
113
+ end
114
+
115
+ it 'assigns key value from options hash' do
116
+ @article.extract_key(:create, :key => :my_custom_key).must_equal "my_custom_key"
117
+ end
118
+ end
119
+
120
+ describe 'for camel cased classes' do
121
+ before do
122
+ class CamelCase < article(:owner => :user)
123
+ def self.name; 'CamelCase' end
124
+ end
125
+ @camel_case = CamelCase.new
126
+ end
127
+
128
+ it 'assigns generates key from class name' do
129
+ @camel_case.extract_key(:create, {}).must_equal "camel_case.create"
130
+ end
131
+ end
132
+
133
+ describe 'for namespaced classes' do
134
+ before do
135
+ module ::MyNamespace;
136
+ class CamelCase < article(:owner => :user)
137
+ def self.name; 'MyNamespace::CamelCase' end
138
+ end
139
+ end
140
+ @namespaced_camel_case = MyNamespace::CamelCase.new
141
+ end
142
+
143
+ it 'assigns key value from options hash' do
144
+ @namespaced_camel_case.extract_key(:create, {}).must_equal "my_namespace_camel_case.create"
145
+ end
146
+ end
147
+ end
148
+
149
+ # no key implicated or given
150
+ specify { ->{subject.prepare_settings}.must_raise PublicActivity::NoKeyProvided }
151
+
152
+ describe 'resolving values' do
153
+ it 'allows procs with models and controllers' do
154
+ context = mock('context')
155
+ context.expects(:accessor).times(2).returns(5)
156
+ controller = mock('controller')
157
+ controller.expects(:current_user).returns(:cu)
158
+ PublicActivity.set_controller(controller)
159
+ p = proc {|controller, model|
160
+ assert_equal :cu, controller.current_user
161
+ assert_equal 5, model.accessor
162
+ }
163
+ PublicActivity.resolve_value(context, p)
164
+ PublicActivity.resolve_value(context, :accessor)
165
+ end
166
+ end
167
+
168
+ end
@@ -0,0 +1,41 @@
1
+ require 'test_helper'
2
+
3
+ class StoringController < ActionView::TestCase::TestController
4
+ include PublicActivity::StoreController
5
+ include ActionController::Testing::ClassMethods
6
+ end
7
+
8
+ describe PublicActivity::StoreController do
9
+ it 'stores controller' do
10
+ controller = StoringController.new
11
+ PublicActivity.set_controller(controller)
12
+ controller.must_be_same_as PublicActivity.instance_eval { class_variable_get(:@@controllers)[Thread.current.object_id] }
13
+ controller.must_be_same_as PublicActivity.get_controller
14
+ end
15
+
16
+ it 'stores controller with a filter in controller' do
17
+ controller = StoringController.new
18
+ controller._process_action_callbacks.select {|c| c.kind == :before}.map(&:filter).must_include :store_controller_for_public_activity
19
+ controller.instance_eval { store_controller_for_public_activity }
20
+ controller.must_be_same_as PublicActivity.class_eval { class_variable_get(:@@controllers)[Thread.current.object_id] }
21
+ end
22
+
23
+ it 'stores controller in a threadsafe way' do
24
+ reset_controllers
25
+ PublicActivity.set_controller(1)
26
+ PublicActivity.get_controller.must_equal 1
27
+
28
+ a = Thread.new {
29
+ PublicActivity.set_controller(2)
30
+ PublicActivity.get_controller.must_equal 2
31
+ }
32
+
33
+ PublicActivity.get_controller.must_equal 1
34
+ # cant really test finalizers though
35
+ end
36
+
37
+ private
38
+ def reset_controllers
39
+ PublicActivity.class_eval { class_variable_set(:@@controllers, {}) }
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ if ENV["PA_ORM"] == "active_record"
2
+
3
+ require 'test_helper'
4
+ require 'rails/generators/test_case'
5
+ require 'generators/public_activity/activity/activity_generator'
6
+ require 'generators/public_activity/migration/migration_generator'
7
+ require 'generators/public_activity/migration_upgrade/migration_upgrade_generator'
8
+
9
+ class TestActivityGenerator < Rails::Generators::TestCase
10
+ tests PublicActivity::Generators::ActivityGenerator
11
+ destination File.expand_path("../tmp", File.dirname(__FILE__))
12
+ setup :prepare_destination
13
+
14
+ def test_generating_activity_model
15
+ run_generator
16
+ assert_file "app/models/activity.rb"
17
+ end
18
+ end
19
+
20
+ class TestMigrationGenerator < Rails::Generators::TestCase
21
+ tests PublicActivity::Generators::MigrationGenerator
22
+ destination File.expand_path("../tmp", File.dirname(__FILE__))
23
+ setup :prepare_destination
24
+
25
+ def test_generating_activity_model
26
+ run_generator
27
+ assert_migration "db/migrate/create_activities.rb"
28
+ end
29
+ end
30
+
31
+ class TestMigrationUpgradeGenerator < Rails::Generators::TestCase
32
+ tests PublicActivity::Generators::MigrationUpgradeGenerator
33
+ destination File.expand_path("../tmp", File.dirname(__FILE__))
34
+ setup :prepare_destination
35
+
36
+ def test_generating_activity_model
37
+ run_generator
38
+ assert_migration "db/migrate/upgrade_activities.rb"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,124 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+ Bundler.setup(:default, :test)
4
+
5
+ unless ENV['NOCOV']
6
+ require 'simplecov'
7
+ SimpleCov.start do
8
+ add_filter "/test/"
9
+ end
10
+ end
11
+ $:.unshift File.expand_path('../../lib/', __FILE__)
12
+ require 'active_support/testing/setup_and_teardown'
13
+ require 'public_activity'
14
+ require 'minitest/autorun'
15
+ require 'minitest/pride' if ENV['WITH_PRIDE'] or ENV['PRIDE']
16
+
17
+ PublicActivity::Config.orm = (ENV['PA_ORM'] || :active_record)
18
+
19
+ case PublicActivity::Config.orm
20
+ when :active_record
21
+ require 'active_record'
22
+ require 'active_record/connection_adapters/sqlite3_adapter'
23
+ require 'stringio' # silence the output
24
+ $stdout = StringIO.new # from migrator
25
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
26
+ ActiveRecord::Migrator.migrate(File.expand_path('../migrations', __FILE__))
27
+ $stdout = STDOUT
28
+
29
+ def article(options = {})
30
+ klass = Class.new(ActiveRecord::Base) do
31
+ self.table_name = 'articles'
32
+ include PublicActivity::Model
33
+ tracked options
34
+ belongs_to :user
35
+
36
+ def self.name
37
+ "Article"
38
+ end
39
+
40
+ if ::ActiveRecord::VERSION::MAJOR < 4
41
+ attr_accessible :name, :published, :user
42
+ end
43
+ end
44
+ klass
45
+ end
46
+ class User < ActiveRecord::Base; end
47
+
48
+ if ::ActiveRecord::VERSION::MAJOR < 4
49
+ PublicActivity::Activity.class_eval do
50
+ attr_accessible :nonstandard
51
+ end
52
+ end
53
+ when :mongoid
54
+ require 'mongoid'
55
+
56
+ Mongoid.load!(File.expand_path("test/mongoid.yml"), :test)
57
+
58
+ class User
59
+ include Mongoid::Document
60
+ include Mongoid::Timestamps
61
+
62
+ has_many :articles
63
+
64
+ field :name, type: String
65
+ end
66
+
67
+ class Article
68
+ include Mongoid::Document
69
+ include Mongoid::Timestamps
70
+ include PublicActivity::Model
71
+
72
+ belongs_to :user
73
+
74
+ field :name, type: String
75
+ field :published, type: Boolean
76
+ end
77
+
78
+ def article(options = {})
79
+ Article.class_eval do
80
+ set_public_activity_class_defaults
81
+ tracked options
82
+ end
83
+ Article
84
+ end
85
+
86
+ when :mongo_mapper
87
+ require 'mongo_mapper'
88
+
89
+ config = YAML.load(File.read("test/mongo_mapper.yml"))
90
+ MongoMapper.setup(config, :test)
91
+
92
+ class User
93
+ include MongoMapper::Document
94
+
95
+ has_many :articles
96
+
97
+ key :name, String
98
+ timestamps!
99
+ end
100
+
101
+ class Article
102
+ include MongoMapper::Document
103
+ include PublicActivity::Model
104
+
105
+ belongs_to :user
106
+
107
+ key :name, String
108
+ key :published, Boolean
109
+ end
110
+
111
+ def article(options = {})
112
+ Article.class_eval do
113
+ set_public_activity_class_defaults
114
+ tracked options
115
+ end
116
+ Article
117
+ end
118
+ end
119
+
120
+ class ViewSpec < MiniTest::Spec
121
+ include ActiveSupport::Testing::SetupAndTeardown
122
+ include ActionView::TestCase::Behavior
123
+ end
124
+ MiniTest::Spec.register_spec_type(/Rendering$/, ViewSpec)