moderation 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in moderation.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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,6 @@
1
+ class Moderation
2
+ class Storage
3
+ autoload 'InMemory', 'moderation/storage/in_memory.rb'
4
+ autoload 'Redis', 'moderation/storage/redis.rb'
5
+ end
6
+ end
@@ -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
+
@@ -0,0 +1,3 @@
1
+ class Moderation
2
+ VERSION = "0.0.1"
3
+ end
@@ -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