unread 0.0.1

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 ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in unread.gemspec
4
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010,2011 Georg Ledermann
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,133 @@
1
+ Unread
2
+ ======
3
+
4
+ Gem to manage read/unread status of ActiveRecord objects - and it's fast.
5
+
6
+
7
+ ## Features
8
+
9
+ * Manages unread records for anything you want your users to read (like messages, documents, comments etc.)
10
+ * Supports "mark as read" to mark a **single** record as read
11
+ * Supports "mark all as read" to mark **all** records as read in a single step
12
+ * Gives you a named_scope to get the unread records for a given user
13
+ * Needs only one additional database table
14
+ * Most important: Great performance
15
+
16
+
17
+ ## Requirements
18
+
19
+ * ActiveRecord (tested with SQLite and MySQL)
20
+ * Needs a timestamp field in your models (e.g. created_at) with a database index on it
21
+
22
+
23
+ ## Installation
24
+
25
+ Step 1: Add this to your Gemfile:
26
+
27
+ gem 'unread'
28
+
29
+ and run
30
+
31
+ bundle
32
+
33
+
34
+ Step 2: Add this migration:
35
+
36
+ class CreateReadMarks < ActiveRecord::Migration
37
+ def self.up
38
+ create_table :read_marks, :force => true do |t|
39
+ t.integer :readable_id
40
+ t.integer :user_id, :null => false
41
+ t.string :readable_type, :null => false, :limit => 20
42
+ t.datetime :timestamp
43
+ end
44
+ add_index :read_marks, [:user_id, :readable_type, :readable_id]
45
+ end
46
+
47
+ def self.down
48
+ drop_table :read_marks
49
+ end
50
+ end
51
+
52
+ and run the migration:
53
+
54
+ rake db:migrate
55
+
56
+
57
+ ## Usage
58
+
59
+ class User < ActiveRecord::Base
60
+ acts_as_reader
61
+ end
62
+
63
+ class Message < ActiveRecord::Base
64
+ acts_as_readable :on => :created_at
65
+ end
66
+
67
+ message1 = Message.create!
68
+ message2 = Message.create!
69
+
70
+ Message.unread_by(current_user)
71
+ # => [ message1, message2 ]
72
+
73
+ message1.mark_as_read! :for => current_user
74
+ Message.unread_by(current_user)
75
+ # => [ message2 ]
76
+
77
+ Message.mark_as_read! :all, :for => current_user
78
+ Message.unread_by(current_user)
79
+ # => [ ]
80
+
81
+ # Optional: Cleaning up unneeded markers
82
+ # Do this in a cron job once a day.
83
+ Message.cleanup_read_marks!
84
+
85
+
86
+ ## How does it work?
87
+
88
+ The main idea of this gem is to manage a list of read items for every user **after** a certain timestamp.
89
+
90
+ The gem defines a named_scope doing a LEFT JOIN to this list, so your app can get the unread items in a performant manner. Of course, other scopes can be combined.
91
+
92
+ It will be ensured that the list of read items will not grow up too much:
93
+
94
+ * If a user uses "mark all as read", his list is deleted and the timestamp is set to the current time.
95
+ * If a user never uses "mark all as read", the list will grow and grow with each item he reads. But there is help: Your app can use a cleanup method which removes unnecessary list items.
96
+
97
+ Overall, this gem can be used for large tables, too. If you are in doubt, look at the generated SQL queries, here is an example:
98
+
99
+ # Assuming we have a user who has marked all messages as read on 2010-10-20 08:50
100
+ current_user = User.find(42)
101
+
102
+ # Get the unread messages for this user
103
+ Message.unread_by(current_user)
104
+
105
+ # =>
106
+ # SELECT messages.*
107
+ # FROM messages
108
+ # LEFT JOIN read_marks ON read_marks.readable_type = 'Message'
109
+ # AND read_marks.readable_id = messages.id
110
+ # AND read_marks.user_id = 42
111
+ # AND read_marks.timestamp >= messages.created_at
112
+ # WHERE read_marks.id IS NULL
113
+ # AND messages.created_at > '2010-10-20 08:50:00'
114
+
115
+ Hint: You should add a database index on messages.created_at.
116
+
117
+
118
+ ## Similar tools
119
+
120
+ There a two other gems/plugins doing a similar job:
121
+
122
+ * http://github.com/jhnvz/mark_as_read
123
+ * http://github.com/mbleigh/acts-as-readable
124
+
125
+ Unfortunately, both of them have a lack of performance, because they calculate the unread records doing a _find(:all)_, which should be avoided for a large amount of records. This gem is based on a timestamp algorithm and therefore it's very fast.
126
+
127
+
128
+ ## TODO
129
+
130
+ * Add more documentation
131
+
132
+
133
+ Copyright (c) 2010,2011 Georg Ledermann, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+
6
+ desc 'Default: run unit tests.'
7
+ task :default => :test
8
+
9
+ desc 'Test the unread plugin.'
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << 'lib'
12
+ t.libs << 'test'
13
+ t.pattern = 'test/**/*_test.rb'
14
+ t.verbose = true
15
+ end
data/changelog.md ADDED
@@ -0,0 +1,3 @@
1
+ 0.0.1 - 2011/06/23
2
+
3
+ * Released as Gem
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'unread'
@@ -0,0 +1,20 @@
1
+ class ReadMark < ActiveRecord::Base
2
+ belongs_to :user
3
+ belongs_to :readable, :polymorphic => true
4
+
5
+ validates_presence_of :user_id, :readable_type
6
+
7
+ scope_method = respond_to?(:scope) ? :scope : :named_scope
8
+
9
+ send scope_method, :global, :conditions => { :readable_id => nil }
10
+ send scope_method, :single, :conditions => 'readable_id IS NOT NULL'
11
+ send scope_method, :readable_type, lambda { |readable_type | { :conditions => { :readable_type => readable_type }}}
12
+ send scope_method, :user, lambda { |user| { :conditions => { :user_id => user.id }}}
13
+ send scope_method, :older_than, lambda { |timestamp| { :conditions => [ 'timestamp < ?', timestamp] }}
14
+
15
+ if respond_to?(:class_attribute)
16
+ class_attribute :reader_class, :readable_classes
17
+ else
18
+ class_inheritable_accessor :reader_class, :readable_classes
19
+ end
20
+ end
@@ -0,0 +1,183 @@
1
+ module Unread
2
+ def self.included(base)
3
+ base.extend ActsAsReadable
4
+ end
5
+
6
+ module ActsAsReadable
7
+ def acts_as_reader
8
+ ReadMark.reader_class = self
9
+
10
+ has_many :read_marks, :dependent => :delete_all
11
+
12
+ after_create do |user|
13
+ (ReadMark.readable_classes || []).each do |klass|
14
+ klass.mark_as_read! :all, :for => user
15
+ end
16
+ end
17
+ end
18
+
19
+ def acts_as_readable(options={})
20
+ options.reverse_merge!({ :on => :updated_at })
21
+ if respond_to?(:class_attribute)
22
+ class_attribute :readable_options
23
+ else
24
+ class_inheritable_accessor :readable_options
25
+ end
26
+ self.readable_options = options
27
+
28
+ self.has_many :read_marks, :as => :readable, :dependent => :delete_all
29
+
30
+ classes = ReadMark.readable_classes || []
31
+ classes << self
32
+ ReadMark.readable_classes = classes
33
+
34
+ scope_method = respond_to?(:scope) ? :scope : :named_scope
35
+
36
+ send scope_method, :unread_by, lambda { |user|
37
+ check_reader
38
+ raise ArgumentError unless user.is_a?(ReadMark.reader_class)
39
+
40
+ result = { :joins => "LEFT JOIN read_marks ON read_marks.readable_type = '#{self.base_class.name}'
41
+ AND read_marks.readable_id = #{self.table_name}.id
42
+ AND read_marks.user_id = #{user.id}
43
+ AND read_marks.timestamp >= #{self.table_name}.#{readable_options[:on]}",
44
+ :conditions => 'read_marks.id IS NULL' }
45
+ if last = read_timestamp(user)
46
+ result[:conditions] += " AND #{self.table_name}.#{readable_options[:on]} > '#{last.to_s(:db)}'"
47
+ end
48
+ result
49
+ }
50
+
51
+ extend ClassMethods
52
+ include InstanceMethods
53
+ end
54
+ end
55
+
56
+ module ClassMethods
57
+ def mark_as_read!(target, options)
58
+ check_reader
59
+ raise ArgumentError unless target == :all || target.is_a?(Array)
60
+
61
+ user = options[:for]
62
+ raise ArgumentError unless user.is_a?(ReadMark.reader_class)
63
+
64
+ if target == :all
65
+ reset_read_marks!(user)
66
+ elsif target.is_a?(Array)
67
+ ReadMark.transaction do
68
+ last = read_timestamp(user)
69
+
70
+ target.each do |obj|
71
+ raise ArgumentError unless obj.is_a?(self)
72
+
73
+ rm = ReadMark.user(user).readable_type(self.base_class.name).find_by_readable_id(obj.id) ||
74
+ user.read_marks.build(:readable_id => obj.id, :readable_type => self.base_class.name)
75
+ rm.timestamp = obj.send(readable_options[:on])
76
+ rm.save!
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def read_mark(user)
83
+ check_reader
84
+ raise ArgumentError unless user.is_a?(ReadMark.reader_class)
85
+
86
+ user.read_marks.readable_type(self.base_class.name).global.first
87
+ end
88
+
89
+ def read_timestamp(user)
90
+ read_mark(user).try(:timestamp)
91
+ end
92
+
93
+ def set_read_mark(user, timestamp)
94
+ rm = read_mark(user) || user.read_marks.build(:readable_type => self.base_class.name)
95
+ rm.timestamp = timestamp
96
+ rm.save!
97
+ end
98
+
99
+ # A scope with all items accessable for the given user
100
+ # It's used in cleanup_read_marks! to support a filtered cleanup
101
+ # Should be overriden if a user doesn't have access to all items
102
+ # Default: User has access to all items and should read them all
103
+ #
104
+ # Example:
105
+ # def Message.read_scope(user)
106
+ # user.visible_messages
107
+ # end
108
+ def read_scope(user)
109
+ self
110
+ end
111
+
112
+ def cleanup_read_marks!
113
+ check_reader
114
+
115
+ ReadMark.reader_class.find_each do |user|
116
+ ReadMark.transaction do
117
+ # Get the timestamp of the oldest unread item the user has access to
118
+ oldest_timestamp = read_scope(user).unread_by(user).minimum(readable_options[:on])
119
+
120
+ if oldest_timestamp
121
+ # Delete markers OLDER than this timestamp and move the global timestamp for this user
122
+ user.read_marks.readable_type(self.base_class.name).single.older_than(oldest_timestamp).delete_all
123
+ set_read_mark(user, oldest_timestamp - 1.second)
124
+ else
125
+ # There is no unread item, so mark all as read (which deletes all markers)
126
+ mark_as_read!(:all, :for => user)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ def reset_read_marks!(user = :all)
133
+ check_reader
134
+
135
+ ReadMark.transaction do
136
+ if user == :all
137
+ ReadMark.delete_all :readable_type => self.base_class.name
138
+
139
+ ReadMark.connection.execute("
140
+ INSERT INTO read_marks (user_id, readable_type, timestamp)
141
+ SELECT id, '#{self.base_class.name}', '#{Time.now.to_s(:db)}'
142
+ FROM #{ReadMark.reader_class.table_name}
143
+ ")
144
+ else
145
+ ReadMark.delete_all :readable_type => self.base_class.name, :user_id => user.id
146
+ ReadMark.create! :readable_type => self.base_class.name, :user_id => user.id, :timestamp => Time.now
147
+ end
148
+ end
149
+ true
150
+ end
151
+
152
+ def check_reader
153
+ raise RuntimeError, 'Plugin "unread": No reader defined!' unless ReadMark.reader_class
154
+ end
155
+ end
156
+
157
+ module InstanceMethods
158
+ def unread?(user)
159
+ self.class.unread_by(user).exists?(self)
160
+ end
161
+
162
+ def mark_as_read!(options)
163
+ self.class.check_reader
164
+
165
+ user = options[:for]
166
+ raise ArgumentError unless user.is_a?(ReadMark.reader_class)
167
+
168
+ ReadMark.transaction do
169
+ if unread?(user)
170
+ rm = read_mark(user) || read_marks.build(:user => user)
171
+ rm.timestamp = self.send(readable_options[:on])
172
+ rm.save!
173
+ end
174
+ end
175
+ end
176
+
177
+ def read_mark(user)
178
+ read_marks.user(user).first
179
+ end
180
+ end
181
+ end
182
+
183
+ ActiveRecord::Base.send :include, Unread
@@ -0,0 +1,3 @@
1
+ module Unread
2
+ VERSION = "0.0.1"
3
+ end
data/lib/unread.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'unread/version'
2
+ require 'app/models/read_mark'
3
+ require 'unread/acts_as_readable'
data/test/database.yml ADDED
@@ -0,0 +1,3 @@
1
+ sqlite3mem:
2
+ adapter: sqlite3
3
+ database: ":memory:"
data/test/schema.rb ADDED
@@ -0,0 +1,20 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :users, :force => true do |t|
3
+ t.string :name
4
+ end
5
+
6
+ create_table :emails, :force => true do |t|
7
+ t.string :subject
8
+ t.text :content
9
+ t.datetime :created_at
10
+ t.datetime :updated_at
11
+ end
12
+
13
+ create_table :read_marks, :force => true do |t|
14
+ t.integer :readable_id
15
+ t.integer :user_id, :null => false
16
+ t.string :readable_type, :null => false
17
+ t.datetime :timestamp
18
+ end
19
+ add_index :read_marks, [:user_id, :readable_type, :readable_id]
20
+ end
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+
3
+ gem 'activerecord'
4
+ gem 'mocha'
5
+
6
+ require 'test/unit'
7
+ require 'active_record'
8
+ require 'active_support'
9
+ require 'active_support/test_case'
10
+
11
+ require File.dirname(__FILE__) + '/../init.rb'
12
+
13
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
14
+ ActiveRecord::Base.establish_connection(config['sqlite3mem'])
15
+ ActiveRecord::Migration.verbose = false
16
+ load(File.dirname(__FILE__) + "/schema.rb")
17
+
18
+ class User < ActiveRecord::Base
19
+ acts_as_reader
20
+ end
21
+
22
+ class Email < ActiveRecord::Base
23
+ acts_as_readable :on => :updated_at
24
+ end
@@ -0,0 +1,132 @@
1
+ require 'test_helper'
2
+
3
+ class UnreadTest < ActiveSupport::TestCase
4
+ def setup
5
+ @user = User.create! :name => 'David'
6
+ @other_user = User.create :name => 'Matz'
7
+ wait
8
+ @email1 = Email.create!
9
+ wait
10
+ @email2 = Email.create!
11
+ end
12
+
13
+ def teardown
14
+ User.delete_all
15
+ Email.delete_all
16
+ ReadMark.delete_all
17
+ end
18
+
19
+ def test_schema_has_loaded_correctly
20
+ assert_equal [@email1, @email2], Email.all
21
+ end
22
+
23
+ def test_scope
24
+ assert_equal [@email1, @email2], Email.unread_by(@user)
25
+ assert_equal [@email1, @email2], Email.unread_by(@other_user)
26
+
27
+ assert_equal 2, Email.unread_by(@user).count
28
+ assert_equal 2, Email.unread_by(@other_user).count
29
+ end
30
+
31
+ def test_scope_after_reset
32
+ @email1.mark_as_read! :for => @user
33
+
34
+ assert_equal [@email2], Email.unread_by(@user)
35
+ assert_equal 1, Email.unread_by(@user).count
36
+ end
37
+
38
+ def test_unread_by
39
+ assert_equal true, @email1.unread?(@user)
40
+ assert_equal true, @email1.unread?(@other_user)
41
+ end
42
+
43
+ def test_unread_after_update
44
+ @email1.mark_as_read! :for => @user
45
+ wait
46
+ @email1.update_attributes! :subject => 'changed'
47
+
48
+ assert_equal true, @email1.unread?(@user)
49
+ end
50
+
51
+ def test_mark_as_read
52
+ @email1.mark_as_read! :for => @user
53
+
54
+ assert_equal false, @email1.unread?(@user)
55
+ assert_equal [@email2], Email.unread_by(@user)
56
+
57
+ assert_equal true, @email1.unread?(@other_user)
58
+ assert_equal [@email1, @email2], Email.unread_by(@other_user)
59
+
60
+ assert_equal 1, @user.read_marks.single.count
61
+ assert_equal @email1, @user.read_marks.single.first.readable
62
+ end
63
+
64
+ def test_mark_as_read_multiple
65
+ assert_equal true, @email1.unread?(@user)
66
+ assert_equal true, @email2.unread?(@user)
67
+
68
+ Email.mark_as_read! [ @email1, @email2 ], :for => @user
69
+
70
+ assert_equal false, @email1.unread?(@user)
71
+ assert_equal false, @email2.unread?(@user)
72
+ end
73
+
74
+ def test_mark_as_read_with_marked_all
75
+ wait
76
+
77
+ Email.mark_as_read! :all, :for => @user
78
+ @email1.mark_as_read! :for => @user
79
+
80
+ assert_equal [], @user.read_marks.single
81
+ end
82
+
83
+ def test_mark_as_read_twice
84
+ @email1.mark_as_read! :for => @user
85
+ @email1.mark_as_read! :for => @user
86
+
87
+ assert_equal 1, @user.read_marks.single.count
88
+ end
89
+
90
+ def test_mark_all_as_read
91
+ Email.mark_as_read! :all, :for => @user
92
+ assert_equal Time.now.to_s, Email.read_mark(@user).timestamp.to_s
93
+
94
+ assert_equal [], @user.read_marks.single
95
+ assert_equal 0, ReadMark.single.count
96
+ assert_equal 2, ReadMark.global.count
97
+ end
98
+
99
+ def test_cleanup_read_marks
100
+ assert_equal 0, @user.read_marks.single.count
101
+
102
+ @email1.mark_as_read! :for => @user
103
+
104
+ assert_equal [@email2], Email.unread_by(@user)
105
+ assert_equal 1, @user.read_marks.single.count
106
+
107
+ Email.cleanup_read_marks!
108
+
109
+ @user.reload
110
+ assert_equal 0, @user.read_marks.single.count
111
+ end
112
+
113
+ def test_cleanup_read_marks_not_delete_from_other_readables
114
+ other_read_mark = @user.read_marks.create! :readable_type => 'Foo', :readable_id => 42, :timestamp => 5.years.ago
115
+ Email.cleanup_read_marks!
116
+ assert_equal true, ReadMark.exists?(other_read_mark.id)
117
+ end
118
+
119
+ def test_reset_read_marks_for_all
120
+ Email.reset_read_marks!
121
+
122
+ assert_equal 0, ReadMark.single.count
123
+ assert_equal 2, ReadMark.global.count
124
+ end
125
+
126
+ private
127
+ def wait
128
+ # Skip one second
129
+ now = Time.now + 1.second
130
+ Time.stubs(:now).returns(now)
131
+ end
132
+ end
data/uninstall.rb ADDED
@@ -0,0 +1 @@
1
+ # Uninstall hook code here
data/unread.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "unread/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "unread"
7
+ s.version = Unread::VERSION
8
+ s.authors = ["Georg Ledermann"]
9
+ s.email = ["mail@georg-ledermann.de"]
10
+ s.homepage = ""
11
+ s.summary = %q{Manages read/unread status of ActiveRecord objects}
12
+ s.description = %q{This gem creates a scope for unread objects and adds methods to mark objects as read }
13
+
14
+ s.rubyforge_project = "unread"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency 'activerecord', '>= 2.3'
22
+ s.add_dependency 'activesupport', '>= 2.3'
23
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unread
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Georg Ledermann
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-06-23 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activerecord
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 5
29
+ segments:
30
+ - 2
31
+ - 3
32
+ version: "2.3"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: activesupport
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 5
44
+ segments:
45
+ - 2
46
+ - 3
47
+ version: "2.3"
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ description: "This gem creates a scope for unread objects and adds methods to mark objects as read "
51
+ email:
52
+ - mail@georg-ledermann.de
53
+ executables: []
54
+
55
+ extensions: []
56
+
57
+ extra_rdoc_files: []
58
+
59
+ files:
60
+ - .gitignore
61
+ - Gemfile
62
+ - MIT-LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - changelog.md
66
+ - init.rb
67
+ - lib/app/models/read_mark.rb
68
+ - lib/unread.rb
69
+ - lib/unread/acts_as_readable.rb
70
+ - lib/unread/version.rb
71
+ - test/database.yml
72
+ - test/schema.rb
73
+ - test/test_helper.rb
74
+ - test/unread_test.rb
75
+ - uninstall.rb
76
+ - unread.gemspec
77
+ homepage: ""
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options: []
82
+
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ requirements: []
104
+
105
+ rubyforge_project: unread
106
+ rubygems_version: 1.8.5
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: Manages read/unread status of ActiveRecord objects
110
+ test_files:
111
+ - test/database.yml
112
+ - test/schema.rb
113
+ - test/test_helper.rb
114
+ - test/unread_test.rb