opt_out 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +14 -0
- data/Gemfile +10 -0
- data/LICENSE +22 -0
- data/README.md +71 -0
- data/Rakefile +11 -0
- data/lib/opt_out.rb +58 -0
- data/lib/opt_out/adapters.rb +8 -0
- data/lib/opt_out/adapters/abstract_adapter.rb +52 -0
- data/lib/opt_out/adapters/active_record_adapter.rb +56 -0
- data/lib/opt_out/adapters/memory_adapter.rb +40 -0
- data/lib/opt_out/adapters/redis_adapter.rb +64 -0
- data/lib/opt_out/version.rb +3 -0
- data/opt_out.gemspec +21 -0
- data/test/active_record_adapter_test.rb +20 -0
- data/test/adapter_tests.rb +79 -0
- data/test/memory_adapter_test.rb +6 -0
- data/test/redis_adapter_test.rb +17 -0
- metadata +115 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Jerry Cheung
|
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,71 @@
|
|
1
|
+
# OptOut
|
2
|
+
|
3
|
+
OptOut is a rubygem for tracking unsubscriptions to newsletters.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
OptOut.unsubscribe('newsletters', '5') # unsubscribe user id '5' from 'newsletters'
|
9
|
+
OptOut.subscribed?('newsletters', '5')
|
10
|
+
=> false
|
11
|
+
|
12
|
+
OptOut.subscribe('newsletters', '5') # re-subscribe a user to 'newsletters'
|
13
|
+
OptOut.subscribed?('newsletters', '5')
|
14
|
+
=> true
|
15
|
+
|
16
|
+
OptOut.unsubscribed?('newsletters', '5') # another way to query
|
17
|
+
=> false
|
18
|
+
|
19
|
+
OptOut.subscribed('newsletters', '8') # users are subscribed by default unless explicitly unsubscribed
|
20
|
+
=> true
|
21
|
+
|
22
|
+
['1', '2', '3'].each {|user_id| OptOut.unsubscribe('newsletters', user_id)}
|
23
|
+
OptOut.unsubscribers # returns a list of unsubscribed user ids
|
24
|
+
=> ['1', '2', '3']
|
25
|
+
```
|
26
|
+
|
27
|
+
## Configuration
|
28
|
+
|
29
|
+
The persistence backend can be configured to be one of:
|
30
|
+
|
31
|
+
* [MemoryAdapter](lib/opt_out/adapters/memory_adapter.rb)
|
32
|
+
* [RedisAdapter](lib/opt_out/adapters/redis_adapter.rb)
|
33
|
+
* [ActiveRecordAdapter](lib/opt_out/adapters/active_record_adapter.rb)
|
34
|
+
|
35
|
+
For example, to configure OptOut to store unsubscriptions in Redis:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
OptOut.configure do |c|
|
39
|
+
c.adapter = OptOut::Adapters::RedisAdapter
|
40
|
+
c.options = {
|
41
|
+
:url => 'redis://localhost:6379'
|
42
|
+
}
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
See individual adapter classes for setup and configuration options. To write a
|
47
|
+
custom adapter, take a look at [AbstractAdapter](lib/opt_out/adapters/abstract_adapter.rb)
|
48
|
+
|
49
|
+
|
50
|
+
## Development
|
51
|
+
|
52
|
+
To run tests, you will need a running redis instance. Add a `.env` file to the
|
53
|
+
project root to configure where redis lives:
|
54
|
+
|
55
|
+
```
|
56
|
+
REDIS_URL=redis://localhost:6379
|
57
|
+
```
|
58
|
+
|
59
|
+
To run tests:
|
60
|
+
|
61
|
+
```sh
|
62
|
+
$ rake
|
63
|
+
```
|
64
|
+
|
65
|
+
## Contributing
|
66
|
+
|
67
|
+
1. [Fork it](https://help.github.com/articles/fork-a-repo)
|
68
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
69
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
70
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
71
|
+
5. Create new [Pull Request](https://help.github.com/articles/using-pull-requests)
|
data/Rakefile
ADDED
data/lib/opt_out.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# Track user unsubscriptions by list.
|
2
|
+
#
|
3
|
+
# OptOut.unsubscribe('newsletters', '5') # unsubscribe user id '5' from 'newsletters'
|
4
|
+
# OptOut.subscribed?('newsletters', '5')
|
5
|
+
# => false
|
6
|
+
#
|
7
|
+
# OptOut.subscribe('newsletters', '5') # re-subscribe a user to 'newsletters'
|
8
|
+
# OptOut.subscribed?('newsletters', '5')
|
9
|
+
# => true
|
10
|
+
#
|
11
|
+
# OptOut.unsubscribed?('newsletters', '5') # another way to query
|
12
|
+
# => false
|
13
|
+
#
|
14
|
+
# OptOut.subscribed('newsletters', '8') # users are subscribed by default unless explicitly unsubscribed
|
15
|
+
# => true
|
16
|
+
#
|
17
|
+
# ['1', '2', '3'].each {|user_id| OptOut.unsubscribe('newsletters', user_id)}
|
18
|
+
# OptOut.unsubscribers # returns a list of unsubscribed user ids
|
19
|
+
# => ['1', '2', '3']
|
20
|
+
require 'forwardable'
|
21
|
+
require 'opt_out/adapters'
|
22
|
+
|
23
|
+
module OptOut
|
24
|
+
# Options:
|
25
|
+
# :adapter - subclass of OptOut::Adapters::AbstractAdapter
|
26
|
+
# :options - instantiation options to pass to `adapter`
|
27
|
+
class Configuration < Struct.new(:adapter, :options)
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
extend Forwardable
|
32
|
+
delegate [:subscribe, :subscribed?, :unsubscribe, :unsubscribed?, :unsubscribers, :reset] => :adapter
|
33
|
+
|
34
|
+
# Private: returns a memoized instance of adapter to use
|
35
|
+
def adapter
|
36
|
+
@adapter ||= config.adapter.new(config.options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: Configure OptOut. Returns Configuration.
|
40
|
+
#
|
41
|
+
# Example:
|
42
|
+
#
|
43
|
+
# OptOut.configure do |c|
|
44
|
+
# c.adapter = OptOut::Adapters::RedisAdapter
|
45
|
+
# c.options = {:host => 'localhost', :port => '6379', :password => ''}
|
46
|
+
# end
|
47
|
+
def configure(&blk)
|
48
|
+
blk.call(config)
|
49
|
+
@adapter = nil # invalidate adapter on reconfiguration
|
50
|
+
config
|
51
|
+
end
|
52
|
+
|
53
|
+
# Public: Returns Configuration
|
54
|
+
def config
|
55
|
+
@config ||= Configuration.new
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module OptOut
|
2
|
+
module Adapters
|
3
|
+
autoload :AbstractAdapter, 'opt_out/adapters/abstract_adapter'
|
4
|
+
autoload :MemoryAdapter, 'opt_out/adapters/memory_adapter'
|
5
|
+
autoload :RedisAdapter, 'opt_out/adapters/redis_adapter'
|
6
|
+
autoload :ActiveRecordAdapter, 'opt_out/adapters/active_record_adapter'
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module OptOut
|
2
|
+
module Adapters
|
3
|
+
# An adapter is responsible for tracking (un/re)subscriptions, and
|
4
|
+
# unsubscribers.
|
5
|
+
class AbstractAdapter
|
6
|
+
def initialize(options = nil)
|
7
|
+
@options = options || {}
|
8
|
+
end
|
9
|
+
|
10
|
+
# Public: `user_id` is subscribed? to `list_id` iff it's unsubscribed.
|
11
|
+
#
|
12
|
+
# Returns boolean.
|
13
|
+
def subscribed?(list_id, user_id)
|
14
|
+
!unsubscribed?(list_id, user_id)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Public: Resubscribe `user_id` to `list_id`. Note that adapters should
|
18
|
+
# only keep track of unsubscriptions. Even if subscribe has never been
|
19
|
+
# called before, a user is unsubscribed only if `#unsubscribe` is
|
20
|
+
# called.
|
21
|
+
#
|
22
|
+
# Returns nothing.
|
23
|
+
def subscribe(list_id, user_id)
|
24
|
+
raise NotImplementedError.new
|
25
|
+
end
|
26
|
+
|
27
|
+
# Public: unsubscribe `user_id` from `list_id`
|
28
|
+
#
|
29
|
+
# Returns nothing.
|
30
|
+
def unsubscribe(list_id, user_id)
|
31
|
+
raise NotImplementedError.new
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public: is `user_id` unsubscribed from `list_id`?
|
35
|
+
#
|
36
|
+
# Returns boolean.
|
37
|
+
def unsubscribed?(list_id, user_id)
|
38
|
+
raise NotImplementedError.new
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public: returns an array of unsubscribers for `list_id`
|
42
|
+
def unsubscribers(list_id)
|
43
|
+
raise NotImplementedError.new
|
44
|
+
end
|
45
|
+
|
46
|
+
# Private: reset internal data store for testing
|
47
|
+
def reset
|
48
|
+
raise NotImplementedError.new
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module OptOut
|
4
|
+
module Adapters
|
5
|
+
# Adapter that stores persists data through ActiveRecord.
|
6
|
+
# It requires the following table:
|
7
|
+
#
|
8
|
+
# :list_id string
|
9
|
+
# :user_id string
|
10
|
+
# composite index on (list_id, user_id)
|
11
|
+
#
|
12
|
+
# Options
|
13
|
+
# :table_name - name of storage table. Defaults to 'opt_outs'
|
14
|
+
class ActiveRecordAdapter < AbstractAdapter
|
15
|
+
|
16
|
+
def subscribe(list_id, user_id)
|
17
|
+
return if [list_id, user_id].any? {|s| s.nil? || s == ''}
|
18
|
+
store.where(:list_id => list_id.to_s, :user_id => user_id.to_s).delete_all
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# TODO: would prefer opt_outs table to not have a primary key `id`, but
|
23
|
+
# that's not working right now
|
24
|
+
def unsubscribe(list_id, user_id)
|
25
|
+
store.create(:list_id => list_id.to_s, :user_id => user_id.to_s)
|
26
|
+
rescue ActiveRecord::RecordNotUnique
|
27
|
+
# already unsubscribed
|
28
|
+
ensure
|
29
|
+
return nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def unsubscribed?(list_id, user_id)
|
33
|
+
store.exists?(:list_id => list_id.to_s, :user_id => user_id.to_s)
|
34
|
+
end
|
35
|
+
|
36
|
+
def unsubscribers(list_id)
|
37
|
+
store.where(:list_id => list_id.to_s).map(&:user_id).to_a
|
38
|
+
end
|
39
|
+
|
40
|
+
def reset
|
41
|
+
store.delete_all
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def store
|
47
|
+
return @store if @store
|
48
|
+
|
49
|
+
table_name = @options[:table_name]
|
50
|
+
@store = Class.new(ActiveRecord::Base) do
|
51
|
+
self.table_name = table_name
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module OptOut
|
4
|
+
module Adapters
|
5
|
+
# Adapter that stores persists data in memory in a hash.
|
6
|
+
#
|
7
|
+
# Options
|
8
|
+
# :store - optional Hash instance to store unsubscriptions
|
9
|
+
class MemoryAdapter < AbstractAdapter
|
10
|
+
# Subscribe `user_id` to `list_id`. Returns nothing.
|
11
|
+
def subscribe(list_id, user_id)
|
12
|
+
store[list_id].delete(user_id) and return
|
13
|
+
end
|
14
|
+
|
15
|
+
def unsubscribe(list_id, user_id)
|
16
|
+
store[list_id] ||= Set.new
|
17
|
+
store[list_id] << user_id
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def unsubscribed?(list_id, user_id)
|
22
|
+
(store[list_id] || []).include?(user_id)
|
23
|
+
end
|
24
|
+
|
25
|
+
def unsubscribers(list_id)
|
26
|
+
store[list_id].to_a
|
27
|
+
end
|
28
|
+
|
29
|
+
def reset
|
30
|
+
store.clear
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def store
|
36
|
+
@store ||= @options[:store] || Hash.new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module OptOut
|
5
|
+
module Adapters
|
6
|
+
# Adapter that persists data in a Redis set.
|
7
|
+
#
|
8
|
+
# Options
|
9
|
+
# :redis - redis connection object OR
|
10
|
+
# :url - redis connection url OR
|
11
|
+
# :host
|
12
|
+
# :port
|
13
|
+
# :password
|
14
|
+
# :key_format - format string for redis key. list_id is interpolated into this option.
|
15
|
+
# Default is "opt_out:%s"
|
16
|
+
class RedisAdapter < AbstractAdapter
|
17
|
+
def subscribe(list_id, user_id)
|
18
|
+
redis.srem(key(list_id), user_id) and return
|
19
|
+
end
|
20
|
+
|
21
|
+
def unsubscribe(list_id, user_id)
|
22
|
+
redis.sadd(key(list_id), user_id) and return
|
23
|
+
end
|
24
|
+
|
25
|
+
def unsubscribed?(list_id, user_id)
|
26
|
+
redis.sismember(key(list_id), user_id)
|
27
|
+
end
|
28
|
+
|
29
|
+
def unsubscribers(list_id)
|
30
|
+
redis.smembers(key(list_id))
|
31
|
+
end
|
32
|
+
|
33
|
+
def reset
|
34
|
+
redis.flushdb
|
35
|
+
end
|
36
|
+
|
37
|
+
def key_format
|
38
|
+
@key_format || @options[:key_format] || "opt_out:%s"
|
39
|
+
end
|
40
|
+
attr_writer :key_format
|
41
|
+
|
42
|
+
# Private: returns redis client for this adapter
|
43
|
+
def redis
|
44
|
+
return @redis if @redis
|
45
|
+
|
46
|
+
@redis = if @options[:redis]
|
47
|
+
@options[:redis]
|
48
|
+
elsif @options[:url]
|
49
|
+
uri = URI.parse(@options[:url])
|
50
|
+
Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
|
51
|
+
else
|
52
|
+
Redis.new(:host => @options[:host], :port => @options[:port], :password => @options[:password])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Returns key to use for redis set add from `:key_format` option
|
59
|
+
def key(list_id)
|
60
|
+
key_format % list_id
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/opt_out.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path("../lib/opt_out/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "opt_out"
|
6
|
+
gem.version = OptOut::VERSION
|
7
|
+
gem.license = "MIT"
|
8
|
+
gem.authors = ["Jerry Cheung"]
|
9
|
+
gem.email = ["jch@whatcodecraves.com"]
|
10
|
+
gem.description = %q{Track newsletter unsubscriptions}
|
11
|
+
gem.summary = %q{Utilities for managing user unsubscribes from lists}
|
12
|
+
gem.homepage = "https://github.com/jch/opt_out"
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split $/
|
15
|
+
gem.test_files = gem.files.grep(%r{^test})
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
|
18
|
+
gem.add_development_dependency "redis"
|
19
|
+
gem.add_development_dependency "activerecord"
|
20
|
+
gem.add_development_dependency "sqlite3"
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'adapter_tests'
|
2
|
+
require 'sqlite3'
|
3
|
+
|
4
|
+
class OptOut::ActiveRecordAdapterTest < Test::Unit::TestCase
|
5
|
+
include AdapterTests
|
6
|
+
test_adapter OptOut::Adapters::ActiveRecordAdapter, :table_name => 'opt_outs' do
|
7
|
+
ActiveRecord::Base.establish_connection({
|
8
|
+
:adapter => 'sqlite3',
|
9
|
+
:database => './test.db'
|
10
|
+
})
|
11
|
+
conn = ActiveRecord::Base.connection
|
12
|
+
|
13
|
+
unless conn.table_exists?(:opt_outs)
|
14
|
+
conn.create_table(:opt_outs) do |t|
|
15
|
+
t.string "list_id"
|
16
|
+
t.string "user_id"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'opt_out'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'dotenv'
|
4
|
+
|
5
|
+
Dotenv.load
|
6
|
+
|
7
|
+
# All adapters must pass these tests. To setup a new adapter test:
|
8
|
+
#
|
9
|
+
# class MyAdapterTest < Test::Unit::TestCase
|
10
|
+
# include AdapterTests
|
11
|
+
# test_adapter(MyAdapter, {:some => 'options'}) do
|
12
|
+
# # optional test setup block
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
module AdapterTests
|
16
|
+
def self.included(base)
|
17
|
+
base.extend Macros
|
18
|
+
end
|
19
|
+
|
20
|
+
module Macros
|
21
|
+
attr_accessor :original_config, :test_config
|
22
|
+
|
23
|
+
def test_adapter(adapter, options = {}, &blk)
|
24
|
+
self.original_config = OptOut.config.dup
|
25
|
+
self.test_config = {
|
26
|
+
:adapter => adapter,
|
27
|
+
:options => options,
|
28
|
+
:setup => blk
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def setup
|
34
|
+
if custom_setup = self.class.test_config[:setup]
|
35
|
+
custom_setup.call
|
36
|
+
end
|
37
|
+
OptOut.configure do |c|
|
38
|
+
c.adapter = self.class.test_config[:adapter]
|
39
|
+
c.options = self.class.test_config[:options]
|
40
|
+
end
|
41
|
+
OptOut.reset
|
42
|
+
end
|
43
|
+
|
44
|
+
def teardown
|
45
|
+
OptOut.config.adapter = self.class.original_config[:adapter]
|
46
|
+
OptOut.config.options = self.class.original_config[:options]
|
47
|
+
OptOut.reset
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_auto_subscribed
|
51
|
+
assert OptOut.subscribed?('newsletters', '5')
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_resubscribe
|
55
|
+
OptOut.unsubscribe('newsletters', '5')
|
56
|
+
OptOut.subscribe('newsletters', '5')
|
57
|
+
assert OptOut.subscribed?('newsletters', '5')
|
58
|
+
assert !OptOut.unsubscribed?('newsletters', '5')
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_unsubscribe
|
62
|
+
OptOut.unsubscribe('newsletters', '5')
|
63
|
+
assert !OptOut.subscribed?('newsletters', '5')
|
64
|
+
assert OptOut.unsubscribed?('newsletters', '5')
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_multi_unsubscribe
|
68
|
+
OptOut.unsubscribe('newsletters', '5')
|
69
|
+
OptOut.unsubscribe('newsletters', '5')
|
70
|
+
assert !OptOut.subscribed?('newsletters', '5')
|
71
|
+
assert OptOut.unsubscribed?('newsletters', '5')
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_unsubscribers
|
75
|
+
OptOut.unsubscribe('newsletters', '5')
|
76
|
+
OptOut.unsubscribe('newsletters', '6')
|
77
|
+
assert_equal ['5', '6'], OptOut.unsubscribers('newsletters').sort
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'adapter_tests'
|
2
|
+
|
3
|
+
class OptOut::RedisAdapterTest < Test::Unit::TestCase
|
4
|
+
include AdapterTests
|
5
|
+
test_adapter OptOut::Adapters::RedisAdapter, :url => ENV['REDIS_URL']
|
6
|
+
|
7
|
+
def test_default_key_format
|
8
|
+
OptOut.unsubscribe('releases', '9')
|
9
|
+
assert_equal ['9'], OptOut.adapter.redis.smembers("opt_out:releases")
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_custom_key_format
|
13
|
+
OptOut.adapter.key_format = "notifications:%s:subscribe"
|
14
|
+
OptOut.unsubscribe('releases', '9')
|
15
|
+
assert_equal ['9'], OptOut.adapter.redis.smembers("notifications:releases:subscribe")
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: opt_out
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jerry Cheung
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: redis
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activerecord
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: sqlite3
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Track newsletter unsubscriptions
|
63
|
+
email:
|
64
|
+
- jch@whatcodecraves.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- lib/opt_out.rb
|
75
|
+
- lib/opt_out/adapters.rb
|
76
|
+
- lib/opt_out/adapters/abstract_adapter.rb
|
77
|
+
- lib/opt_out/adapters/active_record_adapter.rb
|
78
|
+
- lib/opt_out/adapters/memory_adapter.rb
|
79
|
+
- lib/opt_out/adapters/redis_adapter.rb
|
80
|
+
- lib/opt_out/version.rb
|
81
|
+
- opt_out.gemspec
|
82
|
+
- test/active_record_adapter_test.rb
|
83
|
+
- test/adapter_tests.rb
|
84
|
+
- test/memory_adapter_test.rb
|
85
|
+
- test/redis_adapter_test.rb
|
86
|
+
homepage: https://github.com/jch/opt_out
|
87
|
+
licenses:
|
88
|
+
- MIT
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 1.8.23
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: Utilities for managing user unsubscribes from lists
|
111
|
+
test_files:
|
112
|
+
- test/active_record_adapter_test.rb
|
113
|
+
- test/adapter_tests.rb
|
114
|
+
- test/memory_adapter_test.rb
|
115
|
+
- test/redis_adapter_test.rb
|