parity_timeline 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 12ff062fb5f48769808d2edb009304c53afaeaa8
4
+ data.tar.gz: 781075a55bf33f1aaafdb863289e67aae7ad8c39
5
+ SHA512:
6
+ metadata.gz: fa7367b09d7c6b4281f809bbb4351fc73317d9a304d2d3d40c5e3666aa4f80fae19dd252a22e31fcf4f70a0491705237fa17de3e62a2a8afcb9005404a2e2c27
7
+ data.tar.gz: 3f49d6d5c12ef03a531b5133337dd81d7e8522c792b949f2d8a8642871c9eb08988d0f0a83b1702af57070538b2950697d3aae7a513b1dbadb3739b0145679d4
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 Felix Clack
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.md ADDED
@@ -0,0 +1,152 @@
1
+ redis-timeline
2
+ ===========
3
+
4
+ [![Build Status](https://travis-ci.org/felixclack/redis-timeline.png?branch=master)](https://travis-ci.org/felixclack/redis-timeline)
5
+
6
+ Redis backed timelines in your app.
7
+
8
+ <a href="mailto:felixclack+pairwithme@gmail.com" title="Pair program with me!">
9
+ <img src="http://pairprogramwith.me/badge.png"
10
+ alt="Pair program with me!" />
11
+ </a>
12
+
13
+ Features
14
+ --------
15
+
16
+ * store your timeline in Redis.
17
+
18
+ Examples
19
+ --------
20
+
21
+ The simple way...
22
+
23
+ class PostsController < ApplicationController
24
+ include Timeline::ControllerHelper
25
+
26
+ end
27
+
28
+ Instead doing on the callback we explicity mention when we want to track the activity
29
+
30
+ You can specify these options ...
31
+
32
+ class PostsController < ApplicationController
33
+ include Timeline::ControllerHelper
34
+ belongs_to :author, class_name: "User"
35
+ belongs_to :post
36
+
37
+ track :new_comment,
38
+ actor: :author,
39
+ followers: :post_participants,
40
+ object: [:body],
41
+ on: :update,
42
+ target: :post
43
+
44
+ delegate :participants, to: :post, prefix: true
45
+ def create
46
+ @post=Post.new(params[:post])
47
+ respond_to do |format|
48
+ if @post.save!
49
+ track_timeline_activity(:new_post,actor: current_user,followers: current_user.followers,target: @post.comment,object: @post)
50
+ format.html { redirect_to(posts_path , :notice => 'Post was successfully created.') }
51
+ else
52
+ format.html { redirect_to(posts_path , :notice => @post.errors.full_messages) }
53
+ format.json { render json: @post.errors, status: :unprocessable_entity }
54
+ end
55
+
56
+ end
57
+ end
58
+ end
59
+
60
+ Parameters
61
+ ----------
62
+
63
+ `track` accepts the following parameters...
64
+
65
+ the first param is the verb name.
66
+
67
+ The rest all fit neatly in an options hash.
68
+
69
+ * `actor:` [the method that specifies the object that took this action]
70
+ In the above example, comment.author is this object.
71
+
72
+
73
+ * `object:` defaults to self, which is good most of the time.
74
+ You can override it if you need to
75
+
76
+ * `target:` [related to the `:object` method above. In the example this is the post related to the comment]
77
+ default: nil
78
+
79
+ * `followers:` [who should see this story in their timeline. This references a method on the actor]
80
+ Defaults to the method `followers` defined by Timeline::Actor.
81
+
82
+
83
+ Display a timeline
84
+ ------------------
85
+
86
+ To retrieve a timeline for a user...
87
+
88
+ class User < ActiveRecord::Base
89
+ include Timeline::Actor
90
+ end
91
+
92
+ The timeline objects are just hashes that are extended by [Hashie](http://github.com/intridea/hashie) to provide method access to the keys.
93
+
94
+ user = User.find(1)
95
+ user.timeline # => [<Timeline::Activity verb='new_comment' ...>]
96
+
97
+ Requirements
98
+ ------------
99
+
100
+ * redis
101
+ * active_support
102
+ * hashie
103
+
104
+ Install
105
+ -------
106
+
107
+ Install redis.
108
+
109
+ Add to your Gemfile:
110
+
111
+ gem 'redis-timeline'
112
+
113
+ Or install it by hand:
114
+
115
+ gem install redis-timeline
116
+
117
+ Setup your redis instance. For a Rails app, something like this...
118
+
119
+ # in config/initializers/redis.rb
120
+
121
+ Timeline.redis = "localhost:6379/timeline"
122
+
123
+ Author
124
+ ------
125
+
126
+ Original author: Felix Clack
127
+
128
+ License
129
+ -------
130
+
131
+ (The MIT License)
132
+
133
+ Copyright (c) 2012 Felix Clack
134
+
135
+ Permission is hereby granted, free of charge, to any person obtaining
136
+ a copy of this software and associated documentation files (the
137
+ 'Software'), to deal in the Software without restriction, including
138
+ without limitation the rights to use, copy, modify, merge, publish,
139
+ distribute, sublicense, and/or sell copies of the Software, and to
140
+ permit persons to whom the Software is furnished to do so, subject to
141
+ the following conditions:
142
+
143
+ The above copyright notice and this permission notice shall be
144
+ included in all copies or substantial portions of the Software.
145
+
146
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
147
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
148
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
149
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
150
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
151
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
152
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Timeline'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ require 'rake/testtask'
29
+
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.libs << 'test'
33
+ t.pattern = 'test/**/*_test.rb'
34
+ t.verbose = false
35
+ end
36
+
37
+
38
+ task :default => :test
@@ -0,0 +1,21 @@
1
+ require 'active_support/concern'
2
+ require 'action_controller'
3
+ require 'multi_json'
4
+ require 'hashie'
5
+ require 'timeline/config'
6
+ require 'timeline/helpers'
7
+ require 'timeline/track'
8
+ require 'timeline/actor'
9
+ require 'timeline/activity'
10
+
11
+
12
+ module Timeline
13
+ extend Config
14
+ extend Helpers
15
+ end
16
+
17
+ require 'timeline/controller_helper'
18
+ require 'timeline/notification_helper'
19
+
20
+ ActionController::Base.send :include, Timeline::ControllerHelper
21
+ ActionController::Base.send :include, Timeline::NotificationHelper
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :timeline do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,11 @@
1
+ require 'active_model'
2
+
3
+ module Timeline
4
+ class Activity < Hashie::Mash
5
+ extend ActiveModel::Naming
6
+
7
+ def to_partial_path
8
+ "timelines/#{verb}"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,36 @@
1
+ module Timeline
2
+ module Actor
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ def timeline(options={})
7
+ ::Timeline.get_list(timeline_options(options)).map do |item|
8
+ ::Timeline::Activity.new ::Timeline.decode(item)
9
+ end
10
+ end
11
+
12
+ def followers
13
+ []
14
+ end
15
+
16
+ private
17
+ def timeline_options(options)
18
+ defaults = { list_name: "user:id:#{self.id}:activity", start: 0, end: 19 }
19
+ if options.is_a? Hash
20
+ defaults.merge!(options)
21
+ elsif options.is_a? Symbol
22
+ case options
23
+ when :global
24
+ defaults.merge!(list_name: "global:activity")
25
+ when :posts
26
+ defaults.merge!(list_name: "user:id:#{self.id}:posts")
27
+ when :mentions
28
+ defaults.merge!(list_name: "user:id:#{self.id}:mentions")
29
+ when :notifications
30
+ defaults.merge!(list_name: "user:id:#{self.id}:notification")
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,42 @@
1
+ require 'redis/namespace'
2
+
3
+ module Timeline
4
+ module Config
5
+
6
+ # Accepts:
7
+ # 1. A 'hostname:port' String
8
+ # 2. A 'hostname:port:db' String (to select the Redis db)
9
+ # 3. A 'hostname:port/namespace' String (to set the Redis namespace)
10
+ # 4. A Redis URL String 'redis://host:port'
11
+ # 5. An instance of `Redis`, `Redis::Client`, `Redis::DistRedis`,
12
+ # or `Redis::Namespace`.
13
+ def redis=(server)
14
+ case server
15
+ when String
16
+ if server =~ /redis\:\/\//
17
+ redis = Redis.connect(:url => server, :thread_safe => true)
18
+ else
19
+ server, namespace = server.split('/', 2)
20
+ host, port, db = server.split(':')
21
+ redis = Redis.new(:host => host, :port => port,
22
+ :thread_safe => true, :db => db)
23
+ end
24
+ namespace ||= :timeline
25
+
26
+ @redis = Redis::Namespace.new(namespace, :redis => redis)
27
+ when Redis::Namespace
28
+ @redis = server
29
+ else
30
+ @redis = Redis::Namespace.new(:timeline, :redis => server)
31
+ end
32
+ end
33
+
34
+ # Returns the current Redis connection. If none has been created, will
35
+ # create a new one.
36
+ def redis
37
+ return @redis if @redis
38
+ self.redis = Redis.respond_to?(:connect) ? Redis.connect : "localhost:6379"
39
+ self.redis
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,116 @@
1
+ module Timeline
2
+
3
+ module ControllerHelper
4
+ def self.included(base)
5
+ base.send :include, InstanceMethods
6
+ end
7
+
8
+ module InstanceMethods
9
+ def track_timeline_activity(name, options={})
10
+ @name = name
11
+ @start_value = 0
12
+ @limit_records = options.delete(:limit_records) || -1
13
+ @actor = options.delete :actor
14
+ @actor ||= :creator
15
+ @object = options.delete :object
16
+ @target = options.delete :target
17
+ @followers = options.delete :followers
18
+ @mentionable = options.delete :mentionable
19
+
20
+ @fields_for = {}
21
+ @extra_fields ||= nil
22
+ @merge_similar = options[:merge_similar] == true ? true : false
23
+ options[:verb] = name
24
+
25
+ add_activity(activity(verb: options[:verb]))
26
+ end
27
+
28
+ # track_timeline_activity(:new_coupon,actor: :user,object: :coupon_code,followers: :followers)
29
+
30
+ protected
31
+
32
+ def activity(options={})
33
+ {
34
+ verb: options[:verb],
35
+ actor: options_for(@actor),
36
+ object: options_for(@object),
37
+ target: options_for(@target),
38
+ created_at: Time.now
39
+ }
40
+ end
41
+
42
+ def add_activity(activity_item)
43
+ redis_add "global:activity", activity_item
44
+ redis_add "global:activity:#{activity_item[:verb]}", activity_item
45
+ add_activity_to_user(activity_item[:actor][:id], activity_item)
46
+ add_activity_by_user(activity_item[:actor][:id], activity_item)
47
+ add_mentions(activity_item)
48
+ add_activity_to_followers(activity_item) if @followers.any?
49
+ end
50
+
51
+ def add_activity_by_user(user_id, activity_item)
52
+ redis_add "user:id:#{user_id}:posts", activity_item
53
+ end
54
+
55
+ def add_activity_to_user(user_id, activity_item)
56
+ redis_add "user:id:#{user_id}:activity", activity_item
57
+ end
58
+
59
+ def add_activity_to_followers(activity_item)
60
+ @followers.each { |follower| add_activity_to_user(follower.id, activity_item) }
61
+ end
62
+
63
+ def add_mentions(activity_item)
64
+ return unless @mentionable and @object.send(@mentionable)
65
+ @object.send(@mentionable).scan(/@\w+/).each do |mention|
66
+ if user = @actor.class.find_by_username(mention[1..-1])
67
+ add_mention_to_user(user.id, activity_item)
68
+ end
69
+ end
70
+ end
71
+
72
+ def add_mention_to_user(user_id, activity_item)
73
+ redis_add "user:id:#{user_id}:mentions", activity_item
74
+ end
75
+
76
+ def extra_fields_for(object)
77
+ return {} unless @fields_for.has_key?(object.class.to_s.downcase.to_sym)
78
+ @fields_for[object.class.to_s.downcase.to_sym].inject({}) do |sum, method|
79
+ sum[method.to_sym] = @object.send(method.to_sym)
80
+ sum
81
+ end
82
+ end
83
+
84
+ def options_for(target)
85
+ if !target.nil?
86
+ {
87
+ id: target.id,
88
+ class: target.class.to_s,
89
+ display_name: target.to_s
90
+ }.merge(extra_fields_for(target))
91
+ else
92
+ nil
93
+ end
94
+ end
95
+
96
+ def redis_add(list, activity_item)
97
+ Timeline.redis.lpush list, Timeline.encode(activity_item)
98
+ Timeline.redis.ltrim list , @start_value , @limit_records
99
+ end
100
+
101
+ def set_object(object)
102
+ case
103
+ when object.is_a?(Symbol)
104
+ send(object)
105
+ when object.is_a?(Array)
106
+ @fields_for[self.class.to_s.downcase.to_sym] = object
107
+ self
108
+ else
109
+ self
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+
116
+ end