├── .bnsignore
├── .gitignore
├── .rspec
├── .travis.yml
├── Gemfile
├── Gemfile.lock
├── History.txt
├── MIT-LICENSE
├── README.md
├── Rakefile
├── lib
├── redis-timeline.rb
├── tasks
│ └── timeline_tasks.rake
└── timeline
│ ├── activity.rb
│ ├── actor.rb
│ ├── config.rb
│ ├── helpers.rb
│ ├── track.rb
│ └── version.rb
├── redis_timeline.gemspec
└── spec
├── activity_spec.rb
├── actor_spec.rb
├── redis-test.conf
├── spec_helper.rb
├── stdout
├── timeline_spec.rb
└── track_spec.rb
/.bnsignore:
--------------------------------------------------------------------------------
1 | # The list of files that should be ignored by Mr Bones.
2 | # Lines that start with '#' are comments.
3 | #
4 | # A .gitignore file can be used instead by setting it as the ignore
5 | # file in your Rakefile:
6 | #
7 | # Bones {
8 | # ignore_file '.gitignore'
9 | # }
10 | #
11 | # For a project with a C extension, the following would be a good set of
12 | # exclude patterns (uncomment them if you want to use them):
13 | # *.[oa]
14 | # *~
15 | announcement.txt
16 | coverage
17 | doc
18 | pkg
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle/
2 | log/*.log
3 | pkg/
4 | test/dummy/db/*.sqlite3
5 | test/dummy/log/*.log
6 | test/dummy/tmp/
7 | test/dummy/.sass-cache
8 | spec/*.rdb
9 | *.gem
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --colour
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 2.0.0
4 | - 1.9.3
5 |
6 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | # Declare your gem's dependencies in timeline.gemspec.
4 | # Bundler will treat runtime dependencies like base dependencies, and
5 | # development dependencies will be added by default to the :development group.
6 | gemspec
7 |
8 | # Declare any dependencies that are still in development here instead of in
9 | # your gemspec. These might include edge Rails or gems from your path or
10 | # Git. Remember to move these dependencies to your gemspec before releasing
11 | # your gem to rubygems.org.
12 |
13 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | redis-timeline (0.2.1)
5 | activemodel
6 | activesupport
7 | hashie
8 | multi_json
9 | rake
10 | redis
11 | redis-namespace
12 |
13 | GEM
14 | remote: http://rubygems.org/
15 | specs:
16 | activemodel (4.1.6)
17 | activesupport (= 4.1.6)
18 | builder (~> 3.1)
19 | activesupport (4.1.6)
20 | i18n (~> 0.6, >= 0.6.9)
21 | json (~> 1.7, >= 1.7.7)
22 | minitest (~> 5.1)
23 | thread_safe (~> 0.1)
24 | tzinfo (~> 1.1)
25 | builder (3.2.2)
26 | diff-lcs (1.2.5)
27 | hashie (3.3.1)
28 | i18n (0.6.11)
29 | json (1.8.1)
30 | minitest (5.4.1)
31 | multi_json (1.10.1)
32 | rake (10.3.2)
33 | redis (3.1.0)
34 | redis-namespace (1.5.1)
35 | redis (~> 3.0, >= 3.0.4)
36 | rspec (3.1.0)
37 | rspec-core (~> 3.1.0)
38 | rspec-expectations (~> 3.1.0)
39 | rspec-mocks (~> 3.1.0)
40 | rspec-core (3.1.4)
41 | rspec-support (~> 3.1.0)
42 | rspec-expectations (3.1.1)
43 | diff-lcs (>= 1.2.0, < 2.0)
44 | rspec-support (~> 3.1.0)
45 | rspec-mocks (3.1.1)
46 | rspec-support (~> 3.1.0)
47 | rspec-support (3.1.0)
48 | sqlite3 (1.3.9)
49 | thread_safe (0.3.4)
50 | tzinfo (1.2.2)
51 | thread_safe (~> 0.1)
52 |
53 | PLATFORMS
54 | ruby
55 |
56 | DEPENDENCIES
57 | redis-timeline!
58 | rspec
59 | sqlite3
60 |
--------------------------------------------------------------------------------
/History.txt:
--------------------------------------------------------------------------------
1 | == 0.1.4 / 2012-02-28
2 |
3 | * fix bug in extra fields
4 | * add activity to user's posts timeline
5 |
6 | == 0.1.3 / 2012-02-28
7 |
8 | * Accept :extra_fields as an option for track, to cache extra fields on the timeline post.
9 |
10 | == 0.1.2 / 2012-02-25
11 |
12 | * Add ActiveModel::Naming to Timeline::Activity so that it can be passed to the render method in Rails.
13 |
14 |
15 | == 0.1.1 / 2012-02-24
16 |
17 | * Add json-ified object to lists.
18 |
19 |
20 | == 0.1.0 / 2012-02-17
21 |
22 | * First commit
23 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
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.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | redis-timeline
2 | ===========
3 |
4 | [](https://travis-ci.org/felixclack/redis-timeline)
5 |
6 | Redis backed timelines in your app.
7 |
8 |
9 |
11 |
12 |
13 | Features
14 | --------
15 |
16 | * store your timeline in Redis.
17 |
18 | Examples
19 | --------
20 |
21 | The simple way...
22 |
23 | class Post < ActiveRecord::Base
24 | include Timeline::Track
25 |
26 | track :new_post
27 | end
28 |
29 | By default, track fires in the `after_create` callback of your model and uses `self` as the object and `creator` as the actor.
30 |
31 | You can specify these options explicity...
32 |
33 | class Comment < ActiveRecord::Base
34 | include Timeline::Track
35 | belongs_to :author, class_name: "User"
36 | belongs_to :post
37 |
38 | track :new_comment,
39 | actor: :author,
40 | followers: :post_participants,
41 | object: [:body],
42 | on: :update,
43 | target: :post
44 |
45 | delegate :participants, to: :post, prefix: true
46 | end
47 |
48 | Parameters
49 | ----------
50 |
51 | `track` accepts the following parameters...
52 |
53 | the first param is the verb name.
54 |
55 | The rest all fit neatly in an options hash.
56 |
57 | * `on:` [ActiveModel callback]
58 | You use it to specify whether you want the timeline activity created after a create, update or destroy.
59 | Default: :create
60 |
61 | * `actor:` [the method that specifies the object that took this action]
62 | In the above example, comment.author is this object.
63 | Default: :creator, so make sure this exists if you don't specify a method here
64 |
65 | * `object:` defaults to self, which is good most of the time.
66 | You can override it if you need to
67 |
68 | * `target:` [related to the `:object` method above. In the example this is the post related to the comment]
69 | default: nil
70 |
71 | * `followers:` [who should see this story in their timeline. This references a method on the actor]
72 | Defaults to the method `followers` defined by Timeline::Actor.
73 |
74 | * `extra_fields:` [accepts an array of method names that you would like to cache the value of in your timeline]
75 | Defaults to nil.
76 |
77 | * `if:` symbol or proc/lambda lets you put conditions on when to track.
78 |
79 | Display a timeline
80 | ------------------
81 |
82 | To retrieve a timeline for a user...
83 |
84 | class User < ActiveRecord::Base
85 | include Timeline::Actor
86 | end
87 |
88 | The timeline objects are just hashes that are extended by [Hashie](http://github.com/intridea/hashie) to provide method access to the keys.
89 |
90 | user = User.find(1)
91 | user.timeline # => []
92 |
93 | Requirements
94 | ------------
95 |
96 | * redis
97 | * active_support
98 | * hashie
99 |
100 | Install
101 | -------
102 |
103 | Install redis.
104 |
105 | Add to your Gemfile:
106 |
107 | gem 'redis-timeline'
108 |
109 | Or install it by hand:
110 |
111 | gem install redis-timeline
112 |
113 | Setup your redis instance. For a Rails app, something like this...
114 |
115 | # in config/initializers/redis.rb
116 |
117 | Timeline.redis = "localhost:6379/timeline"
118 |
119 | Author
120 | ------
121 |
122 | Original author: Felix Clack
123 |
124 | License
125 | -------
126 |
127 | (The MIT License)
128 |
129 | Copyright (c) 2012 Felix Clack
130 |
131 | Permission is hereby granted, free of charge, to any person obtaining
132 | a copy of this software and associated documentation files (the
133 | 'Software'), to deal in the Software without restriction, including
134 | without limitation the rights to use, copy, modify, merge, publish,
135 | distribute, sublicense, and/or sell copies of the Software, and to
136 | permit persons to whom the Software is furnished to do so, subject to
137 | the following conditions:
138 |
139 | The above copyright notice and this permission notice shall be
140 | included in all copies or substantial portions of the Software.
141 |
142 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
143 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
144 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
145 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
146 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
147 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
148 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
149 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
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
39 |
--------------------------------------------------------------------------------
/lib/redis-timeline.rb:
--------------------------------------------------------------------------------
1 | require 'active_support/concern'
2 | require 'multi_json'
3 | require 'hashie'
4 | require 'timeline/config'
5 | require 'timeline/helpers'
6 | require 'timeline/track'
7 | require 'timeline/actor'
8 | require 'timeline/activity'
9 |
10 | module Timeline
11 | extend Config
12 | extend Helpers
13 | end
14 |
15 |
--------------------------------------------------------------------------------
/lib/tasks/timeline_tasks.rake:
--------------------------------------------------------------------------------
1 | # desc "Explaining what the task does"
2 | # task :timeline do
3 | # # Task goes here
4 | # end
5 |
--------------------------------------------------------------------------------
/lib/timeline/activity.rb:
--------------------------------------------------------------------------------
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
--------------------------------------------------------------------------------
/lib/timeline/actor.rb:
--------------------------------------------------------------------------------
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 | end
30 | end
31 | end
32 | end
33 | end
34 | end
--------------------------------------------------------------------------------
/lib/timeline/config.rb:
--------------------------------------------------------------------------------
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
43 |
--------------------------------------------------------------------------------
/lib/timeline/helpers.rb:
--------------------------------------------------------------------------------
1 | module Timeline
2 | module Helpers
3 | class DecodeException < StandardError; end
4 |
5 | def encode(object)
6 | ::MultiJson.encode(object)
7 | end
8 |
9 | def decode(object)
10 | return unless object
11 |
12 | begin
13 | ::MultiJson.decode(object)
14 | rescue ::MultiJson::DecodeError => e
15 | raise DecodeException, e
16 | end
17 | end
18 |
19 | def get_list(options={})
20 | Timeline.redis.lrange options[:list_name], options[:start], options[:end]
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/timeline/track.rb:
--------------------------------------------------------------------------------
1 | module Timeline::Track
2 | extend ActiveSupport::Concern
3 |
4 | module ClassMethods
5 | def track(name, options={})
6 | @name = name
7 | @callback = options.delete :on
8 | @callback ||= :create
9 | @actor = options.delete :actor
10 | @actor ||= :creator
11 | @object = options.delete :object
12 | @target = options.delete :target
13 | @followers = options.delete :followers
14 | @followers ||= :followers
15 | @mentionable = options.delete :mentionable
16 |
17 | method_name = "track_#{@name}_after_#{@callback}".to_sym
18 | define_activity_method method_name, actor: @actor, object: @object, target: @target, followers: @followers, verb: name, mentionable: @mentionable
19 |
20 | send "after_#{@callback}".to_sym, method_name, if: options.delete(:if)
21 | end
22 |
23 | private
24 | def define_activity_method(method_name, options={})
25 | define_method method_name do
26 | @actor = send(options[:actor])
27 | @fields_for = {}
28 | @object = set_object(options[:object])
29 | @target = !options[:target].nil? ? send(options[:target].to_sym) : nil
30 | @extra_fields ||= nil
31 | @followers = @actor.send(options[:followers].to_sym)
32 | @mentionable = options[:mentionable]
33 | add_activity activity(verb: options[:verb])
34 | end
35 | end
36 | end
37 |
38 | protected
39 | def activity(options={})
40 | {
41 | verb: options[:verb],
42 | actor: options_for(@actor),
43 | object: options_for(@object),
44 | target: options_for(@target),
45 | created_at: Time.now
46 | }
47 | end
48 |
49 | def add_activity(activity_item)
50 | redis_add "global:activity", activity_item
51 | add_activity_to_user(activity_item[:actor][:id], activity_item)
52 | add_activity_by_user(activity_item[:actor][:id], activity_item)
53 | add_mentions(activity_item)
54 | add_activity_to_followers(activity_item) if @followers.any?
55 | end
56 |
57 | def add_activity_by_user(user_id, activity_item)
58 | redis_add "user:id:#{user_id}:posts", activity_item
59 | end
60 |
61 | def add_activity_to_user(user_id, activity_item)
62 | redis_add "user:id:#{user_id}:activity", activity_item
63 | end
64 |
65 | def add_activity_to_followers(activity_item)
66 | @followers.each { |follower| add_activity_to_user(follower.id, activity_item) }
67 | end
68 |
69 | def add_mentions(activity_item)
70 | return unless @mentionable and @object.send(@mentionable)
71 | @object.send(@mentionable).scan(/@\w+/).each do |mention|
72 | if user = @actor.class.find_by_username(mention[1..-1])
73 | add_mention_to_user(user.id, activity_item)
74 | end
75 | end
76 | end
77 |
78 | def add_mention_to_user(user_id, activity_item)
79 | redis_add "user:id:#{user_id}:mentions", activity_item
80 | end
81 |
82 | def extra_fields_for(object)
83 | return {} unless @fields_for.has_key?(object.class.to_s.downcase.to_sym)
84 | @fields_for[object.class.to_s.downcase.to_sym].inject({}) do |sum, method|
85 | sum[method.to_sym] = @object.send(method.to_sym)
86 | sum
87 | end
88 | end
89 |
90 | def options_for(target)
91 | if !target.nil?
92 | {
93 | id: target.id,
94 | class: target.class.to_s,
95 | display_name: target.to_s
96 | }.merge(extra_fields_for(target))
97 | else
98 | nil
99 | end
100 | end
101 |
102 | def redis_add(list, activity_item)
103 | Timeline.redis.lpush list, Timeline.encode(activity_item)
104 | end
105 |
106 | def set_object(object)
107 | case
108 | when object.is_a?(Symbol)
109 | send(object)
110 | when object.is_a?(Array)
111 | @fields_for[self.class.to_s.downcase.to_sym] = object
112 | self
113 | else
114 | self
115 | end
116 | end
117 |
118 | end
--------------------------------------------------------------------------------
/lib/timeline/version.rb:
--------------------------------------------------------------------------------
1 | module Timeline
2 | VERSION = "0.2.1"
3 | end
4 |
--------------------------------------------------------------------------------
/redis_timeline.gemspec:
--------------------------------------------------------------------------------
1 | $:.push File.expand_path("../lib", __FILE__)
2 |
3 | # Maintain your gem's version:
4 | require "timeline/version"
5 |
6 | # Describe your gem and declare its dependencies:
7 | Gem::Specification.new do |s|
8 | s.name = "redis-timeline"
9 | s.version = Timeline::VERSION
10 | s.authors = ["Felix Clack"]
11 | s.email = ["felixclack@gmail.com"]
12 | s.homepage = "http://felixclack.github.com/redis-timeline"
13 | s.summary = "Redis backed timeline for your activity feeds."
14 | s.description = ""
15 |
16 | s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"]
17 | s.test_files = Dir["spec/**/*"]
18 |
19 | s.add_dependency "activesupport"
20 | s.add_dependency "activemodel"
21 | s.add_dependency "multi_json"
22 | s.add_dependency "rake"
23 | s.add_dependency "redis"
24 | s.add_dependency "hashie"
25 | s.add_dependency "redis-namespace"
26 |
27 | s.add_development_dependency "sqlite3"
28 | s.add_development_dependency "rspec"
29 | end
30 |
--------------------------------------------------------------------------------
/spec/activity_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), %w[spec_helper])
2 |
3 | describe Timeline::Activity do
4 | describe "initialized with json" do
5 | let(:json) { { id: "1", verb: "new_post" } }
6 |
7 | it "returns a Hashie-fied object" do
8 | expect(Timeline::Activity.new(json).id).to eq("1")
9 | expect(Timeline::Activity.new(json).verb).to eq("new_post")
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/spec/actor_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), %w[spec_helper])
2 |
3 | class User
4 | include Timeline::Actor
5 |
6 | attr_accessor :id
7 | end
8 |
9 | describe Timeline::Actor do
10 | describe "when included" do
11 | before { @user = User.new }
12 |
13 | it "defines a timeline association" do
14 | expect(@user).to respond_to :timeline
15 | end
16 |
17 | describe ".timeline" do
18 | subject { @user.timeline }
19 |
20 | it { should == [] }
21 | end
22 | end
23 | end
--------------------------------------------------------------------------------
/spec/redis-test.conf:
--------------------------------------------------------------------------------
1 | # Redis configuration file example
2 |
3 | # Note on units: when memory size is needed, it is possible to specify
4 | # it in the usual form of 1k 5GB 4M and so forth:
5 | #
6 | # 1k => 1000 bytes
7 | # 1kb => 1024 bytes
8 | # 1m => 1000000 bytes
9 | # 1mb => 1024*1024 bytes
10 | # 1g => 1000000000 bytes
11 | # 1gb => 1024*1024*1024 bytes
12 | #
13 | # units are case insensitive so 1GB 1Gb 1gB are all the same.
14 |
15 | # By default Redis does not run as a daemon. Use 'yes' if you need it.
16 | # Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
17 | daemonize yes
18 |
19 | # When running daemonized, Redis writes a pid file in /var/run/redis.pid by
20 | # default. You can specify a custom pid file location here.
21 | pidfile ./spec/redis-test.pid
22 |
23 | # Accept connections on the specified port, default is 6379.
24 | # If port 0 is specified Redis will not listen on a TCP socket.
25 | port 9736
26 |
27 | # If you want you can bind a single interface, if the bind option is not
28 | # specified all the interfaces will listen for incoming connections.
29 | #
30 | # bind 127.0.0.1
31 |
32 | # Specify the path for the unix socket that will be used to listen for
33 | # incoming connections. There is no default, so Redis will not listen
34 | # on a unix socket when not specified.
35 | #
36 | # unixsocket /tmp/redis.sock
37 | # unixsocketperm 755
38 |
39 | # Close the connection after a client is idle for N seconds (0 to disable)
40 | timeout 3000
41 |
42 | # TCP keepalive.
43 | #
44 | # If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
45 | # of communication. This is useful for two reasons:
46 | #
47 | # 1) Detect dead peers.
48 | # 2) Take the connection alive from the point of view of network
49 | # equipment in the middle.
50 | #
51 | # On Linux, the specified value (in seconds) is the period used to send ACKs.
52 | # Note that to close the connection the double of the time is needed.
53 | # On other kernels the period depends on the kernel configuration.
54 | #
55 | # A reasonable value for this option is 60 seconds.
56 | tcp-keepalive 0
57 |
58 | # Specify the server verbosity level.
59 | # This can be one of:
60 | # debug (a lot of information, useful for development/testing)
61 | # verbose (many rarely useful info, but not a mess like the debug level)
62 | # notice (moderately verbose, what you want in production probably)
63 | # warning (only very important / critical messages are logged)
64 | loglevel debug
65 |
66 | # Specify the log file name. Also 'stdout' can be used to force
67 | # Redis to log on the standard output. Note that if you use standard
68 | # output for logging but daemonize, logs will be sent to /dev/null
69 | logfile stdout
70 |
71 | # To enable logging to the system logger, just set 'syslog-enabled' to yes,
72 | # and optionally update the other syslog parameters to suit your needs.
73 | # syslog-enabled no
74 |
75 | # Specify the syslog identity.
76 | # syslog-ident redis
77 |
78 | # Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
79 | # syslog-facility local0
80 |
81 | # Set the number of databases. The default database is DB 0, you can select
82 | # a different one on a per-connection basis using SELECT where
83 | # dbid is a number between 0 and 'databases'-1
84 | databases 16
85 |
86 | ################################ SNAPSHOTTING #################################
87 | #
88 | # Save the DB on disk:
89 | #
90 | # save
91 | #
92 | # Will save the DB if both the given number of seconds and the given
93 | # number of write operations against the DB occurred.
94 | #
95 | # In the example below the behaviour will be to save:
96 | # after 900 sec (15 min) if at least 1 key changed
97 | # after 300 sec (5 min) if at least 10 keys changed
98 | # after 60 sec if at least 10000 keys changed
99 | #
100 | # Note: you can disable saving at all commenting all the "save" lines.
101 | #
102 | # It is also possible to remove all the previously configured save
103 | # points by adding a save directive with a single empty string argument
104 | # like in the following example:
105 | #
106 | # save ""
107 |
108 | save 900 1
109 | save 300 10
110 | save 60 10000
111 |
112 | # By default Redis will stop accepting writes if RDB snapshots are enabled
113 | # (at least one save point) and the latest background save failed.
114 | # This will make the user aware (in an hard way) that data is not persisting
115 | # on disk properly, otherwise chances are that no one will notice and some
116 | # distater will happen.
117 | #
118 | # If the background saving process will start working again Redis will
119 | # automatically allow writes again.
120 | #
121 | # However if you have setup your proper monitoring of the Redis server
122 | # and persistence, you may want to disable this feature so that Redis will
123 | # continue to work as usually even if there are problems with disk,
124 | # permissions, and so forth.
125 | stop-writes-on-bgsave-error yes
126 |
127 | # Compress string objects using LZF when dump .rdb databases?
128 | # For default that's set to 'yes' as it's almost always a win.
129 | # If you want to save some CPU in the saving child set it to 'no' but
130 | # the dataset will likely be bigger if you have compressible values or keys.
131 | rdbcompression yes
132 |
133 | # Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
134 | # This makes the format more resistant to corruption but there is a performance
135 | # hit to pay (around 10%) when saving and loading RDB files, so you can disable it
136 | # for maximum performances.
137 | #
138 | # RDB files created with checksum disabled have a checksum of zero that will
139 | # tell the loading code to skip the check.
140 | rdbchecksum yes
141 |
142 | # The filename where to dump the DB
143 | dbfilename dump.rdb
144 |
145 | # The working directory.
146 | #
147 | # The DB will be written inside this directory, with the filename specified
148 | # above using the 'dbfilename' configuration directive.
149 | #
150 | # The Append Only File will also be created inside this directory.
151 | #
152 | # Note that you must specify a directory here, not a file name.
153 | dir ./spec/
154 |
155 | ################################# REPLICATION #################################
156 |
157 | # Master-Slave replication. Use slaveof to make a Redis instance a copy of
158 | # another Redis server. Note that the configuration is local to the slave
159 | # so for example it is possible to configure the slave to save the DB with a
160 | # different interval, or to listen to another port, and so on.
161 | #
162 | # slaveof
163 |
164 | # If the master is password protected (using the "requirepass" configuration
165 | # directive below) it is possible to tell the slave to authenticate before
166 | # starting the replication synchronization process, otherwise the master will
167 | # refuse the slave request.
168 | #
169 | # masterauth
170 |
171 | # When a slave loses its connection with the master, or when the replication
172 | # is still in progress, the slave can act in two different ways:
173 | #
174 | # 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
175 | # still reply to client requests, possibly with out of date data, or the
176 | # data set may just be empty if this is the first synchronization.
177 | #
178 | # 2) if slave-serve-stale-data is set to 'no' the slave will reply with
179 | # an error "SYNC with master in progress" to all the kind of commands
180 | # but to INFO and SLAVEOF.
181 | #
182 | slave-serve-stale-data yes
183 |
184 | # You can configure a slave instance to accept writes or not. Writing against
185 | # a slave instance may be useful to store some ephemeral data (because data
186 | # written on a slave will be easily deleted after resync with the master) but
187 | # may also cause problems if clients are writing to it because of a
188 | # misconfiguration.
189 | #
190 | # Since Redis 2.6 by default slaves are read-only.
191 | #
192 | # Note: read only slaves are not designed to be exposed to untrusted clients
193 | # on the internet. It's just a protection layer against misuse of the instance.
194 | # Still a read only slave exports by default all the administrative commands
195 | # such as CONFIG, DEBUG, and so forth. To a limited extend you can improve
196 | # security of read only slaves using 'rename-command' to shadow all the
197 | # administrative / dangerous commands.
198 | slave-read-only yes
199 |
200 | # Slaves send PINGs to server in a predefined interval. It's possible to change
201 | # this interval with the repl_ping_slave_period option. The default value is 10
202 | # seconds.
203 | #
204 | # repl-ping-slave-period 10
205 |
206 | # The following option sets a timeout for both Bulk transfer I/O timeout and
207 | # master data or ping response timeout. The default value is 60 seconds.
208 | #
209 | # It is important to make sure that this value is greater than the value
210 | # specified for repl-ping-slave-period otherwise a timeout will be detected
211 | # every time there is low traffic between the master and the slave.
212 | #
213 | # repl-timeout 60
214 |
215 | # Disable TCP_NODELAY on the slave socket after SYNC?
216 | #
217 | # If you select "yes" Redis will use a smaller number of TCP packets and
218 | # less bandwidth to send data to slaves. But this can add a delay for
219 | # the data to appear on the slave side, up to 40 milliseconds with
220 | # Linux kernels using a default configuration.
221 | #
222 | # If you select "no" the delay for data to appear on the slave side will
223 | # be reduced but more bandwidth will be used for replication.
224 | #
225 | # By default we optimize for low latency, but in very high traffic conditions
226 | # or when the master and slaves are many hops away, turning this to "yes" may
227 | # be a good idea.
228 | repl-disable-tcp-nodelay no
229 |
230 | # The slave priority is an integer number published by Redis in the INFO output.
231 | # It is used by Redis Sentinel in order to select a slave to promote into a
232 | # master if the master is no longer working correctly.
233 | #
234 | # A slave with a low priority number is considered better for promotion, so
235 | # for instance if there are three slaves with priority 10, 100, 25 Sentinel will
236 | # pick the one wtih priority 10, that is the lowest.
237 | #
238 | # However a special priority of 0 marks the slave as not able to perform the
239 | # role of master, so a slave with priority of 0 will never be selected by
240 | # Redis Sentinel for promotion.
241 | #
242 | # By default the priority is 100.
243 | slave-priority 100
244 |
245 | ################################## SECURITY ###################################
246 |
247 | # Require clients to issue AUTH before processing any other
248 | # commands. This might be useful in environments in which you do not trust
249 | # others with access to the host running redis-server.
250 | #
251 | # This should stay commented out for backward compatibility and because most
252 | # people do not need auth (e.g. they run their own servers).
253 | #
254 | # Warning: since Redis is pretty fast an outside user can try up to
255 | # 150k passwords per second against a good box. This means that you should
256 | # use a very strong password otherwise it will be very easy to break.
257 | #
258 | # requirepass foobared
259 |
260 | # Command renaming.
261 | #
262 | # It is possible to change the name of dangerous commands in a shared
263 | # environment. For instance the CONFIG command may be renamed into something
264 | # hard to guess so that it will still be available for internal-use tools
265 | # but not available for general clients.
266 | #
267 | # Example:
268 | #
269 | # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
270 | #
271 | # It is also possible to completely kill a command by renaming it into
272 | # an empty string:
273 | #
274 | # rename-command CONFIG ""
275 | #
276 | # Please note that changing the name of commands that are logged into the
277 | # AOF file or transmitted to slaves may cause problems.
278 |
279 | ################################### LIMITS ####################################
280 |
281 | # Set the max number of connected clients at the same time. By default
282 | # this limit is set to 10000 clients, however if the Redis server is not
283 | # able to configure the process file limit to allow for the specified limit
284 | # the max number of allowed clients is set to the current file limit
285 | # minus 32 (as Redis reserves a few file descriptors for internal uses).
286 | #
287 | # Once the limit is reached Redis will close all the new connections sending
288 | # an error 'max number of clients reached'.
289 | #
290 | # maxclients 10000
291 |
292 | # Don't use more memory than the specified amount of bytes.
293 | # When the memory limit is reached Redis will try to remove keys
294 | # accordingly to the eviction policy selected (see maxmemmory-policy).
295 | #
296 | # If Redis can't remove keys according to the policy, or if the policy is
297 | # set to 'noeviction', Redis will start to reply with errors to commands
298 | # that would use more memory, like SET, LPUSH, and so on, and will continue
299 | # to reply to read-only commands like GET.
300 | #
301 | # This option is usually useful when using Redis as an LRU cache, or to set
302 | # an hard memory limit for an instance (using the 'noeviction' policy).
303 | #
304 | # WARNING: If you have slaves attached to an instance with maxmemory on,
305 | # the size of the output buffers needed to feed the slaves are subtracted
306 | # from the used memory count, so that network problems / resyncs will
307 | # not trigger a loop where keys are evicted, and in turn the output
308 | # buffer of slaves is full with DELs of keys evicted triggering the deletion
309 | # of more keys, and so forth until the database is completely emptied.
310 | #
311 | # In short... if you have slaves attached it is suggested that you set a lower
312 | # limit for maxmemory so that there is some free RAM on the system for slave
313 | # output buffers (but this is not needed if the policy is 'noeviction').
314 | #
315 | # maxmemory
316 |
317 | # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
318 | # is reached. You can select among five behaviors:
319 | #
320 | # volatile-lru -> remove the key with an expire set using an LRU algorithm
321 | # allkeys-lru -> remove any key accordingly to the LRU algorithm
322 | # volatile-random -> remove a random key with an expire set
323 | # allkeys-random -> remove a random key, any key
324 | # volatile-ttl -> remove the key with the nearest expire time (minor TTL)
325 | # noeviction -> don't expire at all, just return an error on write operations
326 | #
327 | # Note: with any of the above policies, Redis will return an error on write
328 | # operations, when there are not suitable keys for eviction.
329 | #
330 | # At the date of writing this commands are: set setnx setex append
331 | # incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
332 | # sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
333 | # zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
334 | # getset mset msetnx exec sort
335 | #
336 | # The default is:
337 | #
338 | # maxmemory-policy volatile-lru
339 |
340 | # LRU and minimal TTL algorithms are not precise algorithms but approximated
341 | # algorithms (in order to save memory), so you can select as well the sample
342 | # size to check. For instance for default Redis will check three keys and
343 | # pick the one that was used less recently, you can change the sample size
344 | # using the following configuration directive.
345 | #
346 | # maxmemory-samples 3
347 |
348 | ############################## APPEND ONLY MODE ###############################
349 |
350 | # By default Redis asynchronously dumps the dataset on disk. This mode is
351 | # good enough in many applications, but an issue with the Redis process or
352 | # a power outage may result into a few minutes of writes lost (depending on
353 | # the configured save points).
354 | #
355 | # The Append Only File is an alternative persistence mode that provides
356 | # much better durability. For instance using the default data fsync policy
357 | # (see later in the config file) Redis can lose just one second of writes in a
358 | # dramatic event like a server power outage, or a single write if something
359 | # wrong with the Redis process itself happens, but the operating system is
360 | # still running correctly.
361 | #
362 | # AOF and RDB persistence can be enabled at the same time without problems.
363 | # If the AOF is enabled on startup Redis will load the AOF, that is the file
364 | # with the better durability guarantees.
365 | #
366 | # Please check http://redis.io/topics/persistence for more information.
367 |
368 | appendonly no
369 |
370 | # The name of the append only file (default: "appendonly.aof")
371 | # appendfilename appendonly.aof
372 |
373 | # The fsync() call tells the Operating System to actually write data on disk
374 | # instead to wait for more data in the output buffer. Some OS will really flush
375 | # data on disk, some other OS will just try to do it ASAP.
376 | #
377 | # Redis supports three different modes:
378 | #
379 | # no: don't fsync, just let the OS flush the data when it wants. Faster.
380 | # always: fsync after every write to the append only log . Slow, Safest.
381 | # everysec: fsync only one time every second. Compromise.
382 | #
383 | # The default is "everysec", as that's usually the right compromise between
384 | # speed and data safety. It's up to you to understand if you can relax this to
385 | # "no" that will let the operating system flush the output buffer when
386 | # it wants, for better performances (but if you can live with the idea of
387 | # some data loss consider the default persistence mode that's snapshotting),
388 | # or on the contrary, use "always" that's very slow but a bit safer than
389 | # everysec.
390 | #
391 | # More details please check the following article:
392 | # http://antirez.com/post/redis-persistence-demystified.html
393 | #
394 | # If unsure, use "everysec".
395 |
396 | # appendfsync always
397 | appendfsync everysec
398 | # appendfsync no
399 |
400 | # When the AOF fsync policy is set to always or everysec, and a background
401 | # saving process (a background save or AOF log background rewriting) is
402 | # performing a lot of I/O against the disk, in some Linux configurations
403 | # Redis may block too long on the fsync() call. Note that there is no fix for
404 | # this currently, as even performing fsync in a different thread will block
405 | # our synchronous write(2) call.
406 | #
407 | # In order to mitigate this problem it's possible to use the following option
408 | # that will prevent fsync() from being called in the main process while a
409 | # BGSAVE or BGREWRITEAOF is in progress.
410 | #
411 | # This means that while another child is saving, the durability of Redis is
412 | # the same as "appendfsync none". In practical terms, this means that it is
413 | # possible to lose up to 30 seconds of log in the worst scenario (with the
414 | # default Linux settings).
415 | #
416 | # If you have latency problems turn this to "yes". Otherwise leave it as
417 | # "no" that is the safest pick from the point of view of durability.
418 | no-appendfsync-on-rewrite no
419 |
420 | # Automatic rewrite of the append only file.
421 | # Redis is able to automatically rewrite the log file implicitly calling
422 | # BGREWRITEAOF when the AOF log size grows by the specified percentage.
423 | #
424 | # This is how it works: Redis remembers the size of the AOF file after the
425 | # latest rewrite (if no rewrite has happened since the restart, the size of
426 | # the AOF at startup is used).
427 | #
428 | # This base size is compared to the current size. If the current size is
429 | # bigger than the specified percentage, the rewrite is triggered. Also
430 | # you need to specify a minimal size for the AOF file to be rewritten, this
431 | # is useful to avoid rewriting the AOF file even if the percentage increase
432 | # is reached but it is still pretty small.
433 | #
434 | # Specify a percentage of zero in order to disable the automatic AOF
435 | # rewrite feature.
436 |
437 | auto-aof-rewrite-percentage 100
438 | auto-aof-rewrite-min-size 64mb
439 |
440 | ################################ LUA SCRIPTING ###############################
441 |
442 | # Max execution time of a Lua script in milliseconds.
443 | #
444 | # If the maximum execution time is reached Redis will log that a script is
445 | # still in execution after the maximum allowed time and will start to
446 | # reply to queries with an error.
447 | #
448 | # When a long running script exceed the maximum execution time only the
449 | # SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be
450 | # used to stop a script that did not yet called write commands. The second
451 | # is the only way to shut down the server in the case a write commands was
452 | # already issue by the script but the user don't want to wait for the natural
453 | # termination of the script.
454 | #
455 | # Set it to 0 or a negative value for unlimited execution without warnings.
456 | lua-time-limit 5000
457 |
458 | ################################## SLOW LOG ###################################
459 |
460 | # The Redis Slow Log is a system to log queries that exceeded a specified
461 | # execution time. The execution time does not include the I/O operations
462 | # like talking with the client, sending the reply and so forth,
463 | # but just the time needed to actually execute the command (this is the only
464 | # stage of command execution where the thread is blocked and can not serve
465 | # other requests in the meantime).
466 | #
467 | # You can configure the slow log with two parameters: one tells Redis
468 | # what is the execution time, in microseconds, to exceed in order for the
469 | # command to get logged, and the other parameter is the length of the
470 | # slow log. When a new command is logged the oldest one is removed from the
471 | # queue of logged commands.
472 |
473 | # The following time is expressed in microseconds, so 1000000 is equivalent
474 | # to one second. Note that a negative number disables the slow log, while
475 | # a value of zero forces the logging of every command.
476 | slowlog-log-slower-than 10000
477 |
478 | # There is no limit to this length. Just be aware that it will consume memory.
479 | # You can reclaim memory used by the slow log with SLOWLOG RESET.
480 | slowlog-max-len 128
481 |
482 | ############################### ADVANCED CONFIG ###############################
483 |
484 | # Hashes are encoded using a memory efficient data structure when they have a
485 | # small number of entries, and the biggest entry does not exceed a given
486 | # threshold. These thresholds can be configured using the following directives.
487 | hash-max-ziplist-entries 512
488 | hash-max-ziplist-value 64
489 |
490 | # Similarly to hashes, small lists are also encoded in a special way in order
491 | # to save a lot of space. The special representation is only used when
492 | # you are under the following limits:
493 | list-max-ziplist-entries 512
494 | list-max-ziplist-value 64
495 |
496 | # Sets have a special encoding in just one case: when a set is composed
497 | # of just strings that happens to be integers in radix 10 in the range
498 | # of 64 bit signed integers.
499 | # The following configuration setting sets the limit in the size of the
500 | # set in order to use this special memory saving encoding.
501 | set-max-intset-entries 512
502 |
503 | # Similarly to hashes and lists, sorted sets are also specially encoded in
504 | # order to save a lot of space. This encoding is only used when the length and
505 | # elements of a sorted set are below the following limits:
506 | zset-max-ziplist-entries 128
507 | zset-max-ziplist-value 64
508 |
509 | # Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
510 | # order to help rehashing the main Redis hash table (the one mapping top-level
511 | # keys to values). The hash table implementation Redis uses (see dict.c)
512 | # performs a lazy rehashing: the more operation you run into an hash table
513 | # that is rehashing, the more rehashing "steps" are performed, so if the
514 | # server is idle the rehashing is never complete and some more memory is used
515 | # by the hash table.
516 | #
517 | # The default is to use this millisecond 10 times every second in order to
518 | # active rehashing the main dictionaries, freeing memory when possible.
519 | #
520 | # If unsure:
521 | # use "activerehashing no" if you have hard latency requirements and it is
522 | # not a good thing in your environment that Redis can reply form time to time
523 | # to queries with 2 milliseconds delay.
524 | #
525 | # use "activerehashing yes" if you don't have such hard requirements but
526 | # want to free memory asap when possible.
527 | activerehashing yes
528 |
529 | # The client output buffer limits can be used to force disconnection of clients
530 | # that are not reading data from the server fast enough for some reason (a
531 | # common reason is that a Pub/Sub client can't consume messages as fast as the
532 | # publisher can produce them).
533 | #
534 | # The limit can be set differently for the three different classes of clients:
535 | #
536 | # normal -> normal clients
537 | # slave -> slave clients and MONITOR clients
538 | # pubsub -> clients subcribed to at least one pubsub channel or pattern
539 | #
540 | # The syntax of every client-output-buffer-limit directive is the following:
541 | #
542 | # client-output-buffer-limit
543 | #
544 | # A client is immediately disconnected once the hard limit is reached, or if
545 | # the soft limit is reached and remains reached for the specified number of
546 | # seconds (continuously).
547 | # So for instance if the hard limit is 32 megabytes and the soft limit is
548 | # 16 megabytes / 10 seconds, the client will get disconnected immediately
549 | # if the size of the output buffers reach 32 megabytes, but will also get
550 | # disconnected if the client reaches 16 megabytes and continuously overcomes
551 | # the limit for 10 seconds.
552 | #
553 | # By default normal clients are not limited because they don't receive data
554 | # without asking (in a push way), but just after a request, so only
555 | # asynchronous clients may create a scenario where data is requested faster
556 | # than it can read.
557 | #
558 | # Instead there is a default limit for pubsub and slave clients, since
559 | # subscribers and slaves receive data in a push fashion.
560 | #
561 | # Both the hard or the soft limit can be disabled by setting them to zero.
562 | client-output-buffer-limit normal 0 0 0
563 | client-output-buffer-limit slave 256mb 64mb 60
564 | client-output-buffer-limit pubsub 32mb 8mb 60
565 |
566 | # Redis calls an internal function to perform many background tasks, like
567 | # closing connections of clients in timeot, purging expired keys that are
568 | # never requested, and so forth.
569 | #
570 | # Not all tasks are perforemd with the same frequency, but Redis checks for
571 | # tasks to perform accordingly to the specified "hz" value.
572 | #
573 | # By default "hz" is set to 10. Raising the value will use more CPU when
574 | # Redis is idle, but at the same time will make Redis more responsive when
575 | # there are many keys expiring at the same time, and timeouts may be
576 | # handled with more precision.
577 | #
578 | # The range is between 1 and 500, however a value over 100 is usually not
579 | # a good idea. Most users should use the default of 10 and raise this up to
580 | # 100 only in environments where very low latency is required.
581 | hz 10
582 |
583 | # When a child rewrites the AOF file, if the following option is enabled
584 | # the file will be fsync-ed every 32 MB of data generated. This is useful
585 | # in order to commit the file to the disk more incrementally and avoid
586 | # big latency spikes.
587 | aof-rewrite-incremental-fsync yes
588 |
589 | ################################## INCLUDES ###################################
590 |
591 | # Include one or more other config files here. This is useful if you
592 | # have a standard template that goes to all Redis server but also need
593 | # to customize a few per-server settings. Include files can include
594 | # other files, so use this wisely.
595 | #
596 | # include /path/to/local.conf
597 | # include /path/to/other.conf# Redis configuration file example
598 |
599 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib redis-timeline]))
2 | dir = File.dirname(File.expand_path(__FILE__))
3 |
4 | #
5 | # make sure we can run redis
6 | #
7 |
8 | if !system("which redis-server")
9 | puts '', "** can't find `redis-server` in your path"
10 | puts "** try running `sudo rake install`"
11 | abort ''
12 | end
13 |
14 | #
15 | # start our own redis when the tests start,
16 | # kill it when they end
17 | #
18 |
19 | at_exit do
20 | next if $!
21 |
22 | exit_code = RSpec::Core::Runner.autorun
23 |
24 | pid = `ps -A -o pid,command | grep [r]edis-test`.split(" ")[0]
25 | puts "Killing test redis server..."
26 | `rm -f #{dir}/dump.rdb`
27 | Process.kill("KILL", pid.to_i)
28 | exit exit_code
29 | end
30 |
31 | puts "Starting redis for testing at localhost:9736..."
32 | `redis-server #{dir}/redis-test.conf`
33 | Timeline.redis = 'localhost:9736'
34 |
--------------------------------------------------------------------------------
/spec/stdout:
--------------------------------------------------------------------------------
1 | [97466] 18 Sep 14:58:15.166 - DB 0: 5 keys (0 volatile) in 8 slots HT.
2 | [97466] 18 Sep 14:58:15.167 - 0 clients connected (0 slaves), 1006848 bytes in use
3 | [97466] 18 Sep 14:58:20.205 - DB 0: 5 keys (0 volatile) in 8 slots HT.
4 | [97466] 18 Sep 14:58:20.205 - 0 clients connected (0 slaves), 1006848 bytes in use
5 | [97466] 18 Sep 14:58:25.231 - DB 0: 5 keys (0 volatile) in 8 slots HT.
6 | [97466] 18 Sep 14:58:25.231 - 0 clients connected (0 slaves), 1006848 bytes in use
7 |
--------------------------------------------------------------------------------
/spec/timeline_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), %w[spec_helper])
2 |
3 | describe Timeline do
4 | it("can set a redis instance") { expect(Timeline).to respond_to(:redis=) }
5 | it("has a namespace, timeline") { expect(Timeline.redis.namespace).to eq(:timeline) }
6 |
7 | it "sets the namespace through a url-like string" do
8 | Timeline.redis = 'localhost:9736/namespace'
9 | expect(Timeline.redis.namespace).to eq('namespace')
10 | end
11 | end
12 |
13 |
--------------------------------------------------------------------------------
/spec/track_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), %w[spec_helper])
2 |
3 | require 'active_model'
4 |
5 | class Post
6 | extend ActiveModel::Callbacks
7 |
8 | define_model_callbacks :create
9 | attr_accessor :id, :to_param, :creator_id, :name
10 |
11 | include Timeline::Track
12 | track :new_post
13 |
14 | def initialize(options={})
15 | @creator_id = options.delete :creator_id
16 | @name = options.delete :name
17 | end
18 |
19 | def save
20 | run_callbacks :create
21 | true
22 | end
23 |
24 | def creator
25 | User.find(creator_id)
26 | end
27 |
28 | def to_s
29 | name
30 | end
31 | end
32 |
33 | class Comment
34 | extend ActiveModel::Callbacks
35 |
36 | define_model_callbacks :create
37 | attr_accessor :id, :creator_id, :body
38 |
39 | include Timeline::Track
40 |
41 | track :new_comment, object: [:post_name, :post_id, :body], mentionable: :body
42 |
43 | def initialize(options={})
44 | @creator_id = options.delete :creator_id
45 | @body = options.delete :body
46 | end
47 |
48 | def save
49 | run_callbacks :create
50 | true
51 | end
52 |
53 | def post_id
54 | 1
55 | end
56 |
57 | def post_name
58 | "My Post"
59 | end
60 |
61 | def creator
62 | User.find(creator_id)
63 | end
64 |
65 | def to_s
66 | "Comment"
67 | end
68 | end
69 |
70 | class User
71 | include Timeline::Actor
72 | attr_accessor :id, :to_param, :username
73 |
74 | def initialize(options={})
75 | @id = options.delete :id
76 | @username = options.delete :username
77 | end
78 |
79 | class << self
80 | def find user_id
81 | User.new(id: user_id)
82 | end
83 |
84 | def find_by_username username
85 | User.new(username: username)
86 | end
87 | end
88 | end
89 |
90 | describe Timeline::Track do
91 | let(:creator) { User.new(id: 1, username: "first_user") }
92 | let(:post) { Post.new(creator_id: creator.id, name: "New post") }
93 | let(:comment) { Comment.new(creator_id: creator.id, id: 1) }
94 |
95 | describe "included in an ActiveModel-compliant class" do
96 | it "tracks on create by default" do
97 | expect(post).to receive(:track_new_post_after_create)
98 | post.save
99 | end
100 |
101 | it "uses the creator as the actor by default" do
102 | expect(post).to receive(:creator).and_return(double("User", id: 1, to_param: "1", followers: []))
103 | post.save
104 | end
105 |
106 | it "adds the activity to the global timeline set" do
107 | post.save
108 | expect(creator.timeline(:global).last).to be_kind_of(Timeline::Activity)
109 | end
110 |
111 | it "adds the activity to the actor's timeline" do
112 | post.save
113 | expect(creator.timeline.last).to be_kind_of(Timeline::Activity)
114 | end
115 |
116 | it "cc's the actor's followers by default" do
117 | follower = User.new(id: 2)
118 | expect_any_instance_of(User).to receive(:followers).and_return([follower])
119 | post.save
120 | expect(follower.timeline.last.verb).to eq("new_post")
121 | expect(follower.timeline.last.actor.id).to eq(1)
122 | end
123 | end
124 |
125 | describe "with extra_fields" do
126 | it "stores the extra fields in the timeline" do
127 | comment.save
128 | expect(creator.timeline.first.object).to respond_to :post_id
129 | end
130 | end
131 |
132 | describe "tracking mentions" do
133 | it "adds to a user's mentions timeline" do
134 | allow(User).to receive(:find_by_username).and_return(creator)
135 | Comment.new(creator_id: creator.id, body: "@first_user should see this").save
136 | expect(creator.timeline(:mentions).first.object.body).to eq("@first_user should see this")
137 | end
138 | end
139 | end
140 |
--------------------------------------------------------------------------------