moderation 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +214 -0
- data/Rakefile +1 -0
- data/lib/moderation.rb +45 -0
- data/lib/moderation/storage.rb +6 -0
- data/lib/moderation/storage/in_memory.rb +31 -0
- data/lib/moderation/storage/redis.rb +61 -0
- data/lib/moderation/version.rb +3 -0
- data/moderation.gemspec +27 -0
- data/spec/moderation_spec.rb +94 -0
- data/spec/storage/in_memory_spec.rb +43 -0
- data/spec/storage/redis_spec.rb +55 -0
- metadata +113 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Lee Jones
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
# Moderation
|
2
|
+
|
3
|
+
Certain types of data are good to keep around, but only in moderation.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'moderation'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install moderation
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Moderation stores the most recent data based on a limit you set (25 objects by default). Moderation can store the data in Redis or in-memory.
|
22
|
+
|
23
|
+
### Possible use cases
|
24
|
+
|
25
|
+
* admin dashboards and event streams
|
26
|
+
* user activity (signups, purchases, favorites)
|
27
|
+
* user search history
|
28
|
+
* processing failures
|
29
|
+
* visitor ip addresses
|
30
|
+
* sent emails
|
31
|
+
* incoming emails
|
32
|
+
* referring pages/domains
|
33
|
+
|
34
|
+
### Initialization Options
|
35
|
+
|
36
|
+
Moderation initializes with a Hash of options:
|
37
|
+
|
38
|
+
Moderation.new(options = {})
|
39
|
+
|
40
|
+
**options**
|
41
|
+
|
42
|
+
* `:limit` - integer that determines how many objects Moderation will keep before deleting old objects (default: 25)
|
43
|
+
* `:storage` - an instance of a storage object (default: in-memory)
|
44
|
+
* `:constructor` - optional, class used to initialize new objects when fetching data from storage
|
45
|
+
* `:construct_with` - optional, symbol for the method to call on the `:constructor`
|
46
|
+
|
47
|
+
Example:
|
48
|
+
|
49
|
+
website_visitors = Moderation.new(
|
50
|
+
:limit => 50,
|
51
|
+
:storage => redis,
|
52
|
+
:constructor => Visitor,
|
53
|
+
:construct_with => :new_from_json
|
54
|
+
)
|
55
|
+
|
56
|
+
### Interface
|
57
|
+
|
58
|
+
**insert(item)**
|
59
|
+
|
60
|
+
* `item` - object, stored as JSON
|
61
|
+
* Hash
|
62
|
+
* Array
|
63
|
+
* Other objects
|
64
|
+
* returns `nil`
|
65
|
+
|
66
|
+
Example:
|
67
|
+
|
68
|
+
new_visitor = Visitor.new('223.123.243.11', 'http://example.com/')
|
69
|
+
website_visitors.insert(new_visitor)
|
70
|
+
|
71
|
+
**all(options = {})**
|
72
|
+
|
73
|
+
* `options` - optional, a Hash of parameters
|
74
|
+
* `:limit` - specifies the max number of objects that are fetched from storage (default: 25)
|
75
|
+
* returns an Array of objects
|
76
|
+
|
77
|
+
Examples (after a couple more people visited the website):
|
78
|
+
|
79
|
+
website_visitors.all
|
80
|
+
=> [#<Visitor ip_address="223.123.243.11", visited_url="http://example.com">, #<Visitor ip_address="123.443.243.11", visited_url="http://example.com/about">, #<Visitor ip_address="292.122.155.11", visited_url="http://example.com/contact">]
|
81
|
+
|
82
|
+
website_visitors.all(:limit => 1)
|
83
|
+
=> [#<Visitor ip_address="223.123.243.11", visited_url="http://example.com">]
|
84
|
+
|
85
|
+
#### Storing Hash or Array objects
|
86
|
+
|
87
|
+
Example:
|
88
|
+
|
89
|
+
temperatures = Moderation.new
|
90
|
+
|
91
|
+
# add some data
|
92
|
+
(1..28).to_a.each do |n|
|
93
|
+
high = rand(100)
|
94
|
+
temperature = {
|
95
|
+
:date => "2013-02-#{n}",
|
96
|
+
:high => "#{high}F",
|
97
|
+
:low => "#{high - rand(20)}F"
|
98
|
+
}
|
99
|
+
temperatures.insert(temperature)
|
100
|
+
end
|
101
|
+
|
102
|
+
# keeps only the 25 most recent temperatures:
|
103
|
+
temperatures.all.count
|
104
|
+
=> 25
|
105
|
+
|
106
|
+
# the most recently added temperature is first:
|
107
|
+
temperatures.all.first
|
108
|
+
=> {:date=>"2013-02-28", :high=>"89F", :low=>"72F"}
|
109
|
+
|
110
|
+
#### Storing other types of objects
|
111
|
+
|
112
|
+
Other types of objects need to meet 2 requirements:
|
113
|
+
|
114
|
+
* respond to `to_json(*a)` with a string that can be later used to reconstruct the object
|
115
|
+
* initialize with a hash of attributes (see “Custom constructors” if you need a different way to initialize your objects)
|
116
|
+
|
117
|
+
For this example we’ll create a simple object to represent a user's recent search history:
|
118
|
+
|
119
|
+
require 'ostruct'
|
120
|
+
require 'json'
|
121
|
+
|
122
|
+
class UserSearch < OpenStruct
|
123
|
+
def to_json(*a)
|
124
|
+
{:term => term, :user => user, :result_count => result_count}.to_json(*a)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
Now we can setup moderation and store new searches using the `:constructor` option:
|
129
|
+
|
130
|
+
user_search_history = Moderation.new(
|
131
|
+
:limit => 50,
|
132
|
+
:constructor => UserSearch
|
133
|
+
)
|
134
|
+
|
135
|
+
Our search controller might look something like:
|
136
|
+
|
137
|
+
search = UserSearch.new(
|
138
|
+
:user => current_user.id,
|
139
|
+
:term => params[:q],
|
140
|
+
:result_count => results.count
|
141
|
+
)
|
142
|
+
user_search_history.insert(search)
|
143
|
+
|
144
|
+
We configured `user_search_history` with `:limit => 50` so only the 50 most recent searches are kept in storage.
|
145
|
+
|
146
|
+
#### Custom constructors
|
147
|
+
|
148
|
+
If your object does not initialize from a hash of attributes you can pass in the `:contruct_with` option and parse the JSON yourself. For example, if we had a Note class:
|
149
|
+
|
150
|
+
require 'json'
|
151
|
+
|
152
|
+
class Note
|
153
|
+
attr_reader :title, :content
|
154
|
+
|
155
|
+
def initialize(title, content)
|
156
|
+
@title = title
|
157
|
+
@content = content
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_json(*a)
|
161
|
+
{:title => title, :content => content}.to_json(*a)
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.new_from_json(json)
|
165
|
+
data = JSON.parse(json)
|
166
|
+
new(data['title'], data['content'])
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
Then we could configure moderation to use the `new_from_json` constructor method:
|
171
|
+
|
172
|
+
notes = Moderation.new(
|
173
|
+
:limit => 3,
|
174
|
+
:constructor => Note,
|
175
|
+
:construct_with => :new_from_json
|
176
|
+
)
|
177
|
+
|
178
|
+
### Storage
|
179
|
+
|
180
|
+
#### Redis storage
|
181
|
+
|
182
|
+
Moderation can use a Redis storage backend. You'll want to pass in a `:collection` which is a string that gets used as the storage key in Redis. We're using a Redis list to store the data.
|
183
|
+
|
184
|
+
require 'redis'
|
185
|
+
redis = Redis.new
|
186
|
+
redis_storage = Moderation::Storage::Redis.new(
|
187
|
+
:collection => 'recent_visitors',
|
188
|
+
:server => redis
|
189
|
+
)
|
190
|
+
|
191
|
+
Then setup moderation with the `:storage` option:
|
192
|
+
|
193
|
+
recent_visitors = Moderation.new(
|
194
|
+
:limit => 50,
|
195
|
+
:constructor => Visitor,
|
196
|
+
:construct_with => :new_from_json,
|
197
|
+
:storage => redis_storage
|
198
|
+
)
|
199
|
+
|
200
|
+
#### In-memory storage
|
201
|
+
|
202
|
+
Moderation stores data in-memory by default.
|
203
|
+
|
204
|
+
## Contributing
|
205
|
+
|
206
|
+
1. Fork it
|
207
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
208
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
209
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
210
|
+
5. Create new Pull Request
|
211
|
+
|
212
|
+
## TODO
|
213
|
+
|
214
|
+
- [] document creating new storage backends
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/moderation.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'moderation/version'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
class Moderation
|
5
|
+
attr_reader :constructor, :construct_with, :limit, :storage
|
6
|
+
autoload 'Storage', 'moderation/storage.rb'
|
7
|
+
DEFAULT_LIMIT = 25
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@constructor = options[:constructor]
|
11
|
+
@construct_with = options[:construct_with]
|
12
|
+
@limit = options.fetch(:limit, DEFAULT_LIMIT)
|
13
|
+
@storage = options.fetch(:storage, Storage::InMemory.new)
|
14
|
+
@storage.limit = @limit
|
15
|
+
end
|
16
|
+
|
17
|
+
def insert(item)
|
18
|
+
storage.insert(MultiJson.dump(item))
|
19
|
+
end
|
20
|
+
|
21
|
+
def all(options = {})
|
22
|
+
fetch_limit = options.fetch(:limit, limit)
|
23
|
+
storage.all(limit: fetch_limit).map do |stored_item|
|
24
|
+
if using_custom_constructor?
|
25
|
+
constructor.send(construct_with, stored_item)
|
26
|
+
elsif using_constructor?
|
27
|
+
data = MultiJson.load(stored_item, :symbolize_keys => true)
|
28
|
+
constructor.new(data)
|
29
|
+
else
|
30
|
+
MultiJson.load(stored_item, :symbolize_keys => true)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def using_constructor?
|
38
|
+
! constructor.nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
def using_custom_constructor?
|
42
|
+
using_constructor? && ! construct_with.nil?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Moderation
|
2
|
+
class Storage
|
3
|
+
class InMemory
|
4
|
+
attr_accessor :limit
|
5
|
+
|
6
|
+
def initialize(limit = Moderation::DEFAULT_LIMIT)
|
7
|
+
@limit = limit
|
8
|
+
end
|
9
|
+
|
10
|
+
def insert(item)
|
11
|
+
data.unshift(item)
|
12
|
+
if data.count > @limit
|
13
|
+
data.pop(data.count - @limit)
|
14
|
+
end
|
15
|
+
data
|
16
|
+
end
|
17
|
+
|
18
|
+
def all(options = {})
|
19
|
+
fetch_limit = options.fetch(:limit, limit)
|
20
|
+
data.first(fetch_limit)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def data
|
26
|
+
@data ||= []
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'redis/namespace'
|
2
|
+
|
3
|
+
class Moderation
|
4
|
+
class Storage
|
5
|
+
class Redis
|
6
|
+
attr_reader :collection, :server
|
7
|
+
attr_accessor :limit
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@limit = options.fetch(:limit, Moderation::DEFAULT_LIMIT)
|
11
|
+
@collection = options.fetch(:collection)
|
12
|
+
@server = options.fetch(:server, nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
def insert(item)
|
16
|
+
if redis.respond_to?(:multi)
|
17
|
+
redis.multi { insert_item_and_trim_collection(item) }
|
18
|
+
else
|
19
|
+
insert_item_and_trim_collection(item)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def all(options = {})
|
24
|
+
fetch_limit = options.fetch(:limit, limit).to_i - 1
|
25
|
+
redis.lrange(collection, 0, fetch_limit)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def redis
|
31
|
+
@redis ||= begin
|
32
|
+
# graciously borrowed from https://github.com/defunkt/resque
|
33
|
+
case server
|
34
|
+
when String
|
35
|
+
if server['redis://']
|
36
|
+
redis_connection = ::Redis.connect(:url => server, :thread_safe => true)
|
37
|
+
else
|
38
|
+
url, namespace = server.split('/', 2)
|
39
|
+
host, port, db = server.split(':')
|
40
|
+
redis_connection = ::Redis.new(:host => host, :port => port,
|
41
|
+
:thread_safe => true, :db => db)
|
42
|
+
end
|
43
|
+
namespace ||= :moderation
|
44
|
+
|
45
|
+
::Redis::Namespace.new(namespace, :redis => redis_connection)
|
46
|
+
when ::Redis::Namespace
|
47
|
+
server
|
48
|
+
else
|
49
|
+
::Redis::Namespace.new(:moderation, :redis => server)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def insert_item_and_trim_collection(item)
|
55
|
+
redis.lpush(collection, item)
|
56
|
+
redis.ltrim(collection, 0, (limit - 1))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
data/moderation.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'moderation/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "moderation"
|
8
|
+
gem.version = Moderation::VERSION
|
9
|
+
gem.authors = ["Lee Jones"]
|
10
|
+
gem.email = ["scribblethink@gmail.com"]
|
11
|
+
gem.description = %q{Moderation stores only the most recent data based on a limit you set.}
|
12
|
+
gem.summary = %q{Certain types of data are good to keep around, but only in moderation. Moderation makes it easy to keep only the most recent amount of data you care about. Persistent storage is backed by Redis.}
|
13
|
+
gem.homepage = "http://github.com/leejones/moderation"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
|
21
|
+
gem.add_dependency('multi_json', '~> 1.0')
|
22
|
+
gem.add_dependency('redis-namespace', '~> 1.0')
|
23
|
+
|
24
|
+
gem.add_development_dependency('rspec', '~> 2.0')
|
25
|
+
gem.add_development_dependency('pry')
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'moderation'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
describe Moderation do
|
8
|
+
it 'keeps a limited amount of data' do
|
9
|
+
recent_visitors = Moderation.new(:limit => 3, :constructor => Visitor)
|
10
|
+
5.times do |n|
|
11
|
+
visitor = Visitor.new(:ip_address => "222.333.44#{n}")
|
12
|
+
recent_visitors.insert(visitor)
|
13
|
+
end
|
14
|
+
recent_visitors.all.count.should eql(3)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'keeps the most recently stored objects' do
|
18
|
+
recent_visitors = Moderation.new(:limit => 3, :constructor => Visitor)
|
19
|
+
5.times do |n|
|
20
|
+
visitor = Visitor.new(:ip_address => "222.333.44#{n}")
|
21
|
+
recent_visitors.insert(visitor)
|
22
|
+
end
|
23
|
+
recent_visitors.all.map(&:ip_address).should eql(["222.333.444", "222.333.443", "222.333.442"])
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'retrieves objects you stored' do
|
27
|
+
notes = Moderation.new(:limit => 3, :constructor => Note)
|
28
|
+
stored_note = Note.new(:title => 'A Title', :content => 'Some content')
|
29
|
+
notes.insert(stored_note)
|
30
|
+
retrieved_note = notes.all.first
|
31
|
+
retrieved_note.title.should eql('A Title')
|
32
|
+
retrieved_note.content.should eql('Some content')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'limits the number of results returned' do
|
36
|
+
notes = Moderation.new(:limit => 3, :constructor => Note)
|
37
|
+
(0..5).to_a.each do |n|
|
38
|
+
note = Note.new(:title => "Title #{n}", :content => "Content #{n}")
|
39
|
+
notes.insert(note)
|
40
|
+
end
|
41
|
+
notes.all(limit: 2).count.should eql(2)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'uses a custom contructor' do
|
45
|
+
books = Moderation.new(:constructor => Book, :construct_with => :new_from_json)
|
46
|
+
new_book = Book.new('Title Goes Here', 'Author Name')
|
47
|
+
books.insert(new_book)
|
48
|
+
retrieved_book = books.all.first
|
49
|
+
[retrieved_book.title, retrieved_book.author].should eql(['Title Goes Here', 'Author Name'])
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'stores a limited amount of data in redis' do
|
53
|
+
redis_storage = Moderation::Storage::Redis.new(:collection => 'rows')
|
54
|
+
rows = Moderation.new(:limit => 33, :storage => redis_storage)
|
55
|
+
50.times { |n| rows.insert [n] }
|
56
|
+
rows.all.count.should eql(33)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
class Visitor < OpenStruct
|
62
|
+
def to_json(*a)
|
63
|
+
{ :ip_address => ip_address }.to_json
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Note < OpenStruct
|
68
|
+
def to_json(*a)
|
69
|
+
{
|
70
|
+
:title => title,
|
71
|
+
:content => content
|
72
|
+
}.to_json
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Book
|
77
|
+
attr_reader :title, :author
|
78
|
+
|
79
|
+
def initialize(title, author)
|
80
|
+
@title = title
|
81
|
+
@author = author
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_json(*a)
|
85
|
+
{ :title => title, :author => author }.to_json(a)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.new_from_json(json)
|
89
|
+
data = JSON.parse(json, :symbolize_names => true)
|
90
|
+
new(data[:title], data[:author])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.expand_path('../../../lib/moderation/storage/in_memory.rb', __FILE__)
|
2
|
+
|
3
|
+
describe Moderation::Storage::InMemory do
|
4
|
+
it 'initializes with a default limit of 25' do
|
5
|
+
storage = Moderation::Storage::InMemory.new
|
6
|
+
storage.limit.should eql(25)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'accepts a custom limit of 200' do
|
10
|
+
storage = Moderation::Storage::InMemory.new(200)
|
11
|
+
storage.limit.should eql(200)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'accepts a new limit' do
|
15
|
+
storage = Moderation::Storage::InMemory.new(200)
|
16
|
+
storage.limit = 100
|
17
|
+
storage.limit.should eql(100)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'inserts data' do
|
21
|
+
storage = Moderation::Storage::InMemory.new
|
22
|
+
storage.insert('treadstone')
|
23
|
+
storage.all.should eql(['treadstone'])
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'returns all data' do
|
27
|
+
numbers = Moderation::Storage::InMemory.new
|
28
|
+
Array(0..10).each { |n| numbers.insert(n) }
|
29
|
+
numbers.all.should eql([10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'returns a subset of data' do
|
33
|
+
numbers = Moderation::Storage::InMemory.new
|
34
|
+
Array(0..10).each { |n| numbers.insert(n) }
|
35
|
+
numbers.all(:limit => 5).should eql([10, 9, 8, 7, 6])
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns an empty Array' do
|
39
|
+
numbers = Moderation::Storage::InMemory.new
|
40
|
+
numbers.all.should eql([])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.expand_path('../../../lib/moderation/storage/redis.rb', __FILE__)
|
2
|
+
|
3
|
+
describe Moderation::Storage::Redis do
|
4
|
+
before(:each) do
|
5
|
+
redis = Redis.new
|
6
|
+
@redis = Redis::Namespace.new('moderation_test', redis)
|
7
|
+
@redis.del 'test_data'
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'initializes with a default limit of 25' do
|
11
|
+
storage = Moderation::Storage::Redis.new(:collection => 'test_data', :server => @redis)
|
12
|
+
storage.limit.should eql(25)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'accepts a custom limit' do
|
16
|
+
storage = Moderation::Storage::Redis.new(:limit => 200, :collection => 'test_data', :server => @redis)
|
17
|
+
storage.limit.should eql(200)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'accespts a new limit after initialization' do
|
21
|
+
storage = Moderation::Storage::Redis.new(:limit => 200, :collection => 'test_data', :server => @redis)
|
22
|
+
storage.limit = 135
|
23
|
+
storage.limit.should eql(135)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'inserts data' do
|
27
|
+
storage = Moderation::Storage::Redis.new(:collection => 'test_data', :server => @redis)
|
28
|
+
storage.insert('a little bit of data')
|
29
|
+
storage.all.should eql(['a little bit of data'])
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'returns all data' do
|
33
|
+
numbers = Moderation::Storage::Redis.new(:collection => 'test_data', :server => @redis)
|
34
|
+
Array(0..10).each { |n| numbers.insert(n) }
|
35
|
+
numbers.all.should eql(["10", "9", "8", "7", "6", "5", "4", "3", "2", "1", "0"])
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns a subset of data' do
|
39
|
+
numbers = Moderation::Storage::Redis.new(:collection => 'test_data', :server => @redis)
|
40
|
+
Array(0..10).each { |n| numbers.insert(n) }
|
41
|
+
numbers.all(:limit => 5).should eql(["10", "9", "8", "7", "6"])
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'returns an empty Array' do
|
45
|
+
numbers = Moderation::Storage::Redis.new(:collection => 'test_data', :server => @redis)
|
46
|
+
numbers.all.should eql([])
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'removes data outside of limit' do
|
50
|
+
numbers = Moderation::Storage::Redis.new(:collection => 'test_data', :server => @redis, :limit => 5)
|
51
|
+
Array(0..10).each { |n| numbers.insert(n) }
|
52
|
+
@redis.lrange('test_data', 0, -1).count.should eql(5)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: moderation
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Lee Jones
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2013-02-11 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: multi_json
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "1.0"
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: redis-namespace
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "1.0"
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rspec
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "2.0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: pry
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
type: :development
|
58
|
+
version_requirements: *id004
|
59
|
+
description: Moderation stores only the most recent data based on a limit you set.
|
60
|
+
email:
|
61
|
+
- scribblethink@gmail.com
|
62
|
+
executables: []
|
63
|
+
|
64
|
+
extensions: []
|
65
|
+
|
66
|
+
extra_rdoc_files: []
|
67
|
+
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE.txt
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- lib/moderation.rb
|
75
|
+
- lib/moderation/storage.rb
|
76
|
+
- lib/moderation/storage/in_memory.rb
|
77
|
+
- lib/moderation/storage/redis.rb
|
78
|
+
- lib/moderation/version.rb
|
79
|
+
- moderation.gemspec
|
80
|
+
- spec/moderation_spec.rb
|
81
|
+
- spec/storage/in_memory_spec.rb
|
82
|
+
- spec/storage/redis_spec.rb
|
83
|
+
homepage: http://github.com/leejones/moderation
|
84
|
+
licenses: []
|
85
|
+
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: "0"
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: "0"
|
103
|
+
requirements: []
|
104
|
+
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 1.8.24
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: Certain types of data are good to keep around, but only in moderation. Moderation makes it easy to keep only the most recent amount of data you care about. Persistent storage is backed by Redis.
|
110
|
+
test_files:
|
111
|
+
- spec/moderation_spec.rb
|
112
|
+
- spec/storage/in_memory_spec.rb
|
113
|
+
- spec/storage/redis_spec.rb
|