parity_timeline 0.3.2

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.
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