unread 0.1.2 → 0.2.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/.travis.yml CHANGED
@@ -3,7 +3,6 @@ rvm:
3
3
  - 1.8.7
4
4
  - 1.9.3
5
5
  gemfile:
6
- - ci/Gemfile.rails-2.3.x
7
6
  - ci/Gemfile.rails-3.0.x
8
7
  - ci/Gemfile.rails-3.1.x
9
8
  - ci/Gemfile.rails-3.2.x
@@ -11,4 +10,4 @@ env:
11
10
  - DB=sqlite
12
11
  - DB=mysql
13
12
  before_script:
14
- - "mysql -e 'create database myapp_test;' >/dev/null"
13
+ - "mysql -e 'create database myapp_test;' >/dev/null"
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in unread.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -4,7 +4,7 @@ Unread
4
4
  Ruby gem to manage read/unread status of ActiveRecord objects - and it's fast.
5
5
 
6
6
  [![Build Status](https://secure.travis-ci.org/ledermann/unread.png)](http://travis-ci.org/ledermann/unread)
7
-
7
+ [![Code Climate](https://codeclimate.com/github/ledermann/unread.png)](https://codeclimate.com/github/ledermann/unread)
8
8
 
9
9
  ## Features
10
10
 
@@ -19,7 +19,7 @@ Ruby gem to manage read/unread status of ActiveRecord objects - and it's fast.
19
19
  ## Requirements
20
20
 
21
21
  * Ruby 1.8.7 or 1.9.3
22
- * Rails >= 2.3.6 (including 3.0, 3.1, 3.2)
22
+ * Rails 3 (including 3.0, 3.1, 3.2). The use with Rails 2.3 there is a branch named "rails2"
23
23
  * Tested with SQLite and MySQL
24
24
  * Needs a timestamp field in your models (like created_at or updated_at) with a database index on it
25
25
 
@@ -28,38 +28,24 @@ Ruby gem to manage read/unread status of ActiveRecord objects - and it's fast.
28
28
 
29
29
  Step 1: Add this to your Gemfile:
30
30
 
31
- gem 'unread'
31
+ ```ruby
32
+ gem 'unread'
33
+ ```
32
34
 
33
35
  and run
34
36
 
35
- bundle
37
+ ```shell
38
+ bundle
39
+ ```
36
40
 
37
41
 
38
- Step 2: Add this migration:
42
+ Step 2: Generate and run the migration:
39
43
 
40
- ```ruby
41
- class CreateReadMarks < ActiveRecord::Migration
42
- def self.up
43
- create_table :read_marks, :force => true do |t|
44
- t.integer :readable_id
45
- t.integer :user_id, :null => false
46
- t.string :readable_type, :null => false, :limit => 20
47
- t.datetime :timestamp
48
- end
49
-
50
- add_index :read_marks, [:user_id, :readable_type, :readable_id]
51
- end
52
-
53
- def self.down
54
- drop_table :read_marks
55
- end
56
- end
44
+ ```shell
45
+ rails g unread:migration
46
+ rake db:migrate
57
47
  ```
58
48
 
59
- and run the migration:
60
-
61
- rake db:migrate
62
-
63
49
 
64
50
  ## Usage
65
51
 
@@ -122,7 +108,7 @@ current_user = User.find(42)
122
108
  Message.unread_by(current_user)
123
109
  ```
124
110
 
125
- Generated query:
111
+ Generated query:
126
112
 
127
113
  ```sql
128
114
  SELECT messages.*
@@ -148,4 +134,4 @@ There are two other gems/plugins doing a similar job:
148
134
  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.
149
135
 
150
136
 
151
- Copyright (c) 2010-2013 [Georg Ledermann](http://www.georg-ledermann.de), released under the MIT license
137
+ Copyright (c) 2010-2013 [Georg Ledermann](http://www.georg-ledermann.de), released under the MIT license
data/Rakefile CHANGED
@@ -12,4 +12,4 @@ Rake::TestTask.new(:test) do |t|
12
12
  t.libs << 'test'
13
13
  t.pattern = 'test/**/*_test.rb'
14
14
  t.verbose = true
15
- end
15
+ end
data/changelog.md CHANGED
@@ -1,3 +1,9 @@
1
+ 0.2.0 - WIP
2
+
3
+ * Support for Rails 2 dropped
4
+ * Refactoring
5
+ * Added migration generator
6
+
1
7
  0.1.2 - 2013/01/27
2
8
 
3
9
  * Scopes: Improved parameter check
@@ -39,8 +45,8 @@
39
45
 
40
46
  0.0.2 - 2011/06/23
41
47
 
42
- * Fixed scoping for ActiveRecord 2.x
48
+ * Fixed scoping for ActiveRecord 2.x
43
49
 
44
50
  0.0.1 - 2011/06/23
45
51
 
46
- * Released as Gem
52
+ * Released as Gem
@@ -3,5 +3,5 @@ source :rubygems
3
3
  gem 'activerecord', '~> 3.0.10'
4
4
  gem 'sqlite3'
5
5
  gem 'mysql2', '~> 0.2.11'
6
- gem 'mocha', "~> 0.12.8"
7
- gem 'rake'
6
+ gem 'timecop'
7
+ gem 'rake'
@@ -3,5 +3,5 @@ source :rubygems
3
3
  gem 'activerecord', '~> 3.1.1'
4
4
  gem 'sqlite3'
5
5
  gem 'mysql2', '>= 0.3.6'
6
- gem 'mocha', "~> 0.12.8"
7
- gem 'rake'
6
+ gem 'timecop'
7
+ gem 'rake'
@@ -3,5 +3,5 @@ source :rubygems
3
3
  gem 'activerecord', '~> 3.2.1'
4
4
  gem 'sqlite3'
5
5
  gem 'mysql2', '>= 0.3.6'
6
- gem 'mocha', "~> 0.12.8"
7
- gem 'rake'
6
+ gem 'timecop'
7
+ gem 'rake'
@@ -1,26 +1,17 @@
1
1
  class ReadMark < ActiveRecord::Base
2
2
  belongs_to :readable, :polymorphic => true
3
3
  attr_accessible :readable_id, :user_id, :readable_type, :timestamp
4
-
4
+
5
5
  validates_presence_of :user_id, :readable_type
6
-
7
- scope_method = ActiveRecord::VERSION::MAJOR < 3 ? :named_scope : :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] }}
6
+
7
+ scope :global, where(:readable_id => nil)
8
+ scope :single, where('readable_id IS NOT NULL')
9
+ scope :older_than, lambda { |timestamp| where([ 'timestamp < ?', timestamp]) }
14
10
 
15
11
  # Returns the class defined by ActsAsReadable::acts_as_reader
16
12
  def self.reader_class
17
- user_association = reflect_on_all_associations(:belongs_to).find { |assoc| assoc.name == :user }
18
- user_association.try(:klass)
13
+ reflect_on_all_associations(:belongs_to).find { |assoc| assoc.name == :user }.try(:klass)
19
14
  end
20
-
21
- if respond_to?(:class_attribute)
22
- class_attribute :readable_classes
23
- else
24
- class_inheritable_accessor :readable_classes
25
- end
26
- end
15
+
16
+ class_attribute :readable_classes
17
+ end
@@ -0,0 +1,23 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module Unread
5
+ class MigrationGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ desc "Generates migration for read_markers"
9
+ source_root File.expand_path('../templates', __FILE__)
10
+
11
+ def create_migration_file
12
+ migration_template 'migration.rb', 'db/migrate/unread_migration'
13
+ end
14
+
15
+ def self.next_migration_number(dirname)
16
+ if ActiveRecord::Base.timestamped_migrations
17
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
18
+ else
19
+ "%.3d" % (current_migration_number(dirname) + 1)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ class UnreadMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :read_marks, :force => true do |t|
4
+ t.integer :readable_id
5
+ t.integer :user_id, :null => false
6
+ t.string :readable_type, :null => false, :limit => 20
7
+ t.datetime :timestamp
8
+ end
9
+
10
+ add_index :read_marks, [:user_id, :readable_type, :readable_id]
11
+ end
12
+
13
+ def self.down
14
+ drop_table :read_marks
15
+ end
16
+ end
@@ -2,95 +2,90 @@ module Unread
2
2
  def self.included(base)
3
3
  base.extend ActsAsReadable
4
4
  end
5
-
5
+
6
6
  module ActsAsReadable
7
7
  def acts_as_reader
8
8
  ReadMark.belongs_to :user, :class_name => self.to_s
9
-
9
+
10
10
  has_many :read_marks, :dependent => :delete_all, :foreign_key => 'user_id', :inverse_of => :user
11
-
11
+
12
12
  after_create do |user|
13
+ # We assume that a new user should not be tackled by tons of old messages
14
+ # created BEFORE he signed up.
15
+ # Instead, the new user starts with zero unread messages
13
16
  (ReadMark.readable_classes || []).each do |klass|
14
17
  klass.mark_as_read! :all, :for => user
15
18
  end
16
19
  end
17
-
20
+
18
21
  include ReaderInstanceMethods
19
22
  end
20
-
23
+
21
24
  def acts_as_readable(options={})
22
- if respond_to?(:class_attribute)
23
- class_attribute :readable_options
24
- else
25
- class_inheritable_accessor :readable_options
26
- end
25
+ class_attribute :readable_options
27
26
 
28
- options.reverse_merge!({ :on => :updated_at })
27
+ options.reverse_merge!(:on => :updated_at)
29
28
  self.readable_options = options
30
-
29
+
31
30
  has_many :read_marks, :as => :readable, :dependent => :delete_all
32
-
31
+
33
32
  ReadMark.readable_classes ||= []
34
- ReadMark.readable_classes << self unless ReadMark.readable_classes.map(&:name).include?(self.name)
35
-
36
- scope_method = ActiveRecord::VERSION::MAJOR < 3 ? :named_scope : :scope
37
-
38
- send scope_method, :unread_by, lambda { |user|
33
+ ReadMark.readable_classes << self unless ReadMark.readable_classes.include?(self)
34
+
35
+ scope :join_read_marks, lambda { |user|
39
36
  assert_reader(user)
40
37
 
41
- result = { :joins => "LEFT JOIN read_marks ON read_marks.readable_type = '#{self.base_class.name}'
42
- AND read_marks.readable_id = #{self.table_name}.id
43
- AND read_marks.user_id = #{user.id}
44
- AND read_marks.timestamp >= #{self.table_name}.#{readable_options[:on]}",
45
- :conditions => 'read_marks.id IS NULL' }
38
+ joins "LEFT JOIN read_marks ON read_marks.readable_type = '#{self.base_class.name}'
39
+ AND read_marks.readable_id = #{self.table_name}.id
40
+ AND read_marks.user_id = #{user.id}
41
+ AND read_marks.timestamp >= #{self.table_name}.#{readable_options[:on]}"
42
+ }
43
+
44
+ scope :unread_by, lambda { |user|
45
+ result = join_read_marks(user).
46
+ where('read_marks.id IS NULL')
47
+
46
48
  if global_time_stamp = user.read_mark_global(self).try(:timestamp)
47
- result[:conditions] += " AND #{self.table_name}.#{readable_options[:on]} > '#{global_time_stamp.to_s(:db)}'"
49
+ result = result.where("#{self.table_name}.#{readable_options[:on]} > '#{global_time_stamp.to_s(:db)}'")
48
50
  end
51
+
49
52
  result
50
53
  }
51
-
52
- send scope_method, :with_read_marks_for, lambda { |user|
53
- assert_reader(user)
54
-
55
- { :select => "#{self.table_name}.*, read_marks.id AS read_mark_id",
56
- :joins => "LEFT JOIN read_marks ON read_marks.readable_type = '#{self.base_class.name}'
57
- AND read_marks.readable_id = #{self.table_name}.id
58
- AND read_marks.user_id = #{user.id}
59
- AND read_marks.timestamp >= #{self.table_name}.#{readable_options[:on]}" }
54
+
55
+ scope :with_read_marks_for, lambda { |user|
56
+ join_read_marks(user).select("#{self.table_name}.*, read_marks.id AS read_mark_id")
60
57
  }
58
+
61
59
  extend ReadableClassMethods
62
60
  include ReadableInstanceMethods
63
61
  end
64
62
  end
65
-
63
+
66
64
  module ReadableClassMethods
67
65
  def mark_as_read!(target, options)
68
- raise ArgumentError unless target == :all || target.is_a?(Array)
69
-
70
66
  user = options[:for]
71
67
  assert_reader(user)
72
-
68
+
73
69
  if target == :all
74
- reset_read_marks!(user)
75
- elsif target.is_a?(Array)
76
- ReadMark.transaction do
77
- target.each do |obj|
78
- raise ArgumentError unless obj.is_a?(self)
79
-
80
- rm = ReadMark.user(user).readable_type(self.base_class.name).find_by_readable_id(obj.id) ||
81
- user.read_marks.build(:readable_id => obj.id, :readable_type => self.base_class.name)
82
- rm.timestamp = obj.send(readable_options[:on])
83
- rm.save!
84
- end
70
+ reset_read_marks_for_user(user)
71
+ elsif target.is_a?(Array)
72
+ mark_array_as_read(target, user)
73
+ else
74
+ raise ArgumentError
75
+ end
76
+ end
77
+
78
+ def mark_array_as_read(array, user)
79
+ ReadMark.transaction do
80
+ array.each do |obj|
81
+ raise ArgumentError unless obj.is_a?(self)
82
+
83
+ rm = obj.read_marks.where(:user_id => user.id).first || obj.read_marks.build(:user_id => user.id)
84
+ rm.timestamp = obj.send(readable_options[:on])
85
+ rm.save!
85
86
  end
86
87
  end
87
88
  end
88
-
89
- def set_read_mark(user, timestamp)
90
- rm = user.read_mark_global(self) || user.read_marks.build(:readable_type => self.base_class.name)
91
- rm.timestamp = timestamp
92
- rm.save!
93
- end
94
89
 
95
90
  # A scope with all items accessable for the given user
96
91
  # It's used in cleanup_read_marks! to support a filtered cleanup
@@ -107,64 +102,69 @@ module Unread
107
102
 
108
103
  def cleanup_read_marks!
109
104
  assert_reader_class
110
-
105
+
111
106
  ReadMark.reader_class.find_each do |user|
112
107
  ReadMark.transaction do
113
- # Get the timestamp of the oldest unread item the user has access to
114
- oldest_timestamp = read_scope(user).unread_by(user).minimum(readable_options[:on])
115
-
116
- if oldest_timestamp
117
- # Delete markers OLDER than this timestamp and move the global timestamp for this user
118
- user.read_marks.readable_type(self.base_class.name).single.older_than(oldest_timestamp).delete_all
119
- set_read_mark(user, oldest_timestamp - 1.second)
108
+ if oldest_timestamp = read_scope(user).unread_by(user).minimum(readable_options[:on])
109
+ # There are unread items, so update the global read_mark for this user to the oldest
110
+ # unread item and delete older read_marks
111
+ update_read_marks_for_user(user, oldest_timestamp)
120
112
  else
121
- # There is no unread item, so mark all as read (which deletes all markers)
122
- mark_as_read!(:all, :for => user)
113
+ # There is no unread item, so deletes all markers and move global timestamp
114
+ reset_read_marks_for_user(user)
123
115
  end
124
116
  end
125
117
  end
126
118
  end
127
-
128
- def reset_read_marks!(user = :all)
129
- assert_reader_class
130
119
 
120
+ def update_read_marks_for_user(user, timestamp)
121
+ # Delete markers OLDER than the given timestamp
122
+ user.read_marks.where(:readable_type => self.base_class.name).single.older_than(timestamp).delete_all
123
+
124
+ # Change the global timestamp for this user
125
+ rm = user.read_mark_global(self) || user.read_marks.build(:readable_type => self.base_class.name)
126
+ rm.timestamp = timestamp - 1.second
127
+ rm.save!
128
+ end
129
+
130
+ def reset_read_marks_for_all
131
131
  ReadMark.transaction do
132
- if user == :all
133
- ReadMark.delete_all :readable_type => self.base_class.name
134
-
135
- ReadMark.connection.execute("
136
- INSERT INTO read_marks (user_id, readable_type, timestamp)
137
- SELECT id, '#{self.base_class.name}', '#{Time.now.to_s(:db)}'
138
- FROM #{ReadMark.reader_class.table_name}
139
- ")
140
- else
141
- ReadMark.delete_all :readable_type => self.base_class.name, :user_id => user.id
142
- ReadMark.create! :readable_type => self.base_class.name, :user_id => user.id, :timestamp => Time.now
143
- end
132
+ ReadMark.delete_all :readable_type => self.base_class.name
133
+ ReadMark.connection.execute <<-EOT
134
+ INSERT INTO read_marks (user_id, readable_type, timestamp)
135
+ SELECT id, '#{self.base_class.name}', '#{Time.now.to_s(:db)}'
136
+ FROM #{ReadMark.reader_class.table_name}
137
+ EOT
144
138
  end
145
- true
146
139
  end
147
-
140
+
141
+ def reset_read_marks_for_user(user)
142
+ assert_reader(user)
143
+
144
+ ReadMark.transaction do
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
+
148
150
  def assert_reader(user)
149
151
  assert_reader_class
150
-
152
+
151
153
  raise ArgumentError, "Class #{user.class.name} is not registered by acts_as_reader!" unless user.is_a?(ReadMark.reader_class)
152
154
  raise ArgumentError, "The given user has no id!" unless user.id
153
155
  end
154
-
156
+
155
157
  def assert_reader_class
156
- unless ReadMark.reader_class
157
- raise RuntimeError, 'There is no class using acts_as_reader!'
158
- end
158
+ raise RuntimeError, 'There is no class using acts_as_reader!' unless ReadMark.reader_class
159
159
  end
160
160
  end
161
-
162
- module ReadableInstanceMethods
161
+
162
+ module ReadableInstanceMethods
163
163
  def unread?(user)
164
164
  if self.respond_to?(:read_mark_id)
165
165
  # For use with scope "with_read_marks_for"
166
166
  return false if self.read_mark_id
167
-
167
+
168
168
  if global_timestamp = user.read_mark_global(self.class).try(:timestamp)
169
169
  self.send(readable_options[:on]) > global_timestamp
170
170
  else
@@ -174,11 +174,11 @@ module Unread
174
174
  self.class.unread_by(user).exists?(self)
175
175
  end
176
176
  end
177
-
177
+
178
178
  def mark_as_read!(options)
179
179
  user = options[:for]
180
180
  self.class.assert_reader(user)
181
-
181
+
182
182
  ReadMark.transaction do
183
183
  if unread?(user)
184
184
  rm = read_mark(user) || read_marks.build(:user_id => user.id)
@@ -189,15 +189,15 @@ module Unread
189
189
  end
190
190
 
191
191
  def read_mark(user)
192
- read_marks.user(user).first
192
+ read_marks.where(:user_id => user.id).first
193
193
  end
194
194
  end
195
-
195
+
196
196
  module ReaderInstanceMethods
197
197
  def read_mark_global(klass)
198
198
  instance_var_name = "@read_mark_global_#{klass.name.gsub('::','_')}"
199
199
  instance_variable_get(instance_var_name) || begin # memoize
200
- obj = self.read_marks.readable_type(klass.base_class.name).global.first
200
+ obj = self.read_marks.where(:readable_type => klass.base_class.name).global.first
201
201
  instance_variable_set(instance_var_name, obj)
202
202
  end
203
203
  end
@@ -1,3 +1,3 @@
1
1
  module Unread
2
- VERSION = '0.1.2'
2
+ VERSION = '0.2.0'
3
3
  end
data/test/database.yml CHANGED
@@ -4,5 +4,5 @@ sqlite:
4
4
  mysql:
5
5
  adapter: mysql2
6
6
  database: myapp_test
7
- username:
7
+ username:
8
8
  encoding: utf8
data/test/schema.rb CHANGED
@@ -2,14 +2,14 @@ ActiveRecord::Schema.define(:version => 0) do
2
2
  create_table :readers, :force => true do |t|
3
3
  t.string :name
4
4
  end
5
-
5
+
6
6
  create_table :emails, :force => true do |t|
7
7
  t.string :subject
8
8
  t.text :content
9
9
  t.datetime :created_at
10
10
  t.datetime :updated_at
11
11
  end
12
-
12
+
13
13
  create_table :read_marks, :force => true do |t|
14
14
  t.integer :readable_id
15
15
  t.integer :user_id, :null => false
@@ -17,4 +17,4 @@ ActiveRecord::Schema.define(:version => 0) do
17
17
  t.datetime :timestamp
18
18
  end
19
19
  add_index :read_marks, [:user_id, :readable_type, :readable_id]
20
- end
20
+ end
data/test/test_helper.rb CHANGED
@@ -2,7 +2,7 @@ require 'test/unit'
2
2
  require 'active_support'
3
3
  require 'active_support/test_case'
4
4
  require 'active_record'
5
- require 'unread'
5
+ require 'timecop'
6
6
 
7
7
  configs = YAML.load_file(File.dirname(__FILE__) + '/database.yml')
8
8
  ActiveRecord::Base.configurations = configs
@@ -12,6 +12,8 @@ ActiveRecord::Base.establish_connection(db_name)
12
12
  ActiveRecord::Migration.verbose = false
13
13
  load(File.dirname(__FILE__) + "/schema.rb")
14
14
 
15
+ require 'unread'
16
+
15
17
  class Reader < ActiveRecord::Base
16
18
  acts_as_reader
17
19
  end
@@ -20,4 +22,4 @@ class Email < ActiveRecord::Base
20
22
  acts_as_readable :on => :updated_at
21
23
  end
22
24
 
23
- puts "Testing with ActiveRecord #{ActiveRecord::VERSION::STRING}"
25
+ puts "Testing with ActiveRecord #{ActiveRecord::VERSION::STRING}"
data/test/unread_test.rb CHANGED
@@ -9,21 +9,21 @@ class UnreadTest < ActiveSupport::TestCase
9
9
  wait
10
10
  @email2 = Email.create!
11
11
  end
12
-
12
+
13
13
  def teardown
14
14
  Reader.delete_all
15
15
  Email.delete_all
16
16
  ReadMark.delete_all
17
17
  end
18
-
18
+
19
19
  def test_schema_has_loaded_correctly
20
20
  assert_equal [@email1, @email2], Email.all
21
21
  end
22
-
22
+
23
23
  def test_readable_classes
24
24
  assert_equal [ Email ], ReadMark.readable_classes
25
25
  end
26
-
26
+
27
27
  def test_reader_class
28
28
  assert_equal Reader, ReadMark.reader_class
29
29
  end
@@ -31,37 +31,37 @@ class UnreadTest < ActiveSupport::TestCase
31
31
  def test_scope
32
32
  assert_equal [@email1, @email2], Email.unread_by(@reader)
33
33
  assert_equal [@email1, @email2], Email.unread_by(@other_reader)
34
-
34
+
35
35
  assert_equal 2, Email.unread_by(@reader).count
36
36
  assert_equal 2, Email.unread_by(@other_reader).count
37
37
  end
38
-
38
+
39
39
  def test_with_read_marks_for
40
40
  @email1.mark_as_read! :for => @reader
41
-
41
+
42
42
  emails = Email.with_read_marks_for(@reader).all
43
43
 
44
44
  assert emails[0].read_mark_id.present?
45
45
  assert emails[1].read_mark_id.nil?
46
-
46
+
47
47
  assert_equal false, emails[0].unread?(@reader)
48
48
  assert_equal true, emails[1].unread?(@reader)
49
49
  end
50
-
50
+
51
51
  def test_scope_param_check
52
52
  [ 42, nil, 'foo', :foo, {} ].each do |not_a_reader|
53
53
  assert_raise(ArgumentError) { Email.unread_by(not_a_reader)}
54
54
  assert_raise(ArgumentError) { Email.with_read_marks_for(not_a_reader)}
55
55
  end
56
-
56
+
57
57
  unsaved_reader = Reader.new
58
58
  assert_raise(ArgumentError) { Email.unread_by(unsaved_reader)}
59
59
  assert_raise(ArgumentError) { Email.with_read_marks_for(unsaved_reader)}
60
60
  end
61
-
61
+
62
62
  def test_scope_after_reset
63
63
  @email1.mark_as_read! :for => @reader
64
-
64
+
65
65
  assert_equal [@email2], Email.unread_by(@reader)
66
66
  assert_equal 1, Email.unread_by(@reader).count
67
67
  end
@@ -69,12 +69,12 @@ class UnreadTest < ActiveSupport::TestCase
69
69
  def test_unread_after_create
70
70
  assert_equal true, @email1.unread?(@reader)
71
71
  assert_equal true, @email1.unread?(@other_reader)
72
-
72
+
73
73
  assert_raise(ArgumentError) {
74
74
  @email1.unread?(42)
75
75
  }
76
76
  end
77
-
77
+
78
78
  def test_unread_after_update
79
79
  @email1.mark_as_read! :for => @reader
80
80
  wait
@@ -82,86 +82,84 @@ class UnreadTest < ActiveSupport::TestCase
82
82
 
83
83
  assert_equal true, @email1.unread?(@reader)
84
84
  end
85
-
85
+
86
86
  def test_mark_as_read
87
87
  @email1.mark_as_read! :for => @reader
88
-
88
+
89
89
  assert_equal false, @email1.unread?(@reader)
90
90
  assert_equal [@email2], Email.unread_by(@reader)
91
-
91
+
92
92
  assert_equal true, @email1.unread?(@other_reader)
93
93
  assert_equal [@email1, @email2], Email.unread_by(@other_reader)
94
-
94
+
95
95
  assert_equal 1, @reader.read_marks.single.count
96
96
  assert_equal @email1, @reader.read_marks.single.first.readable
97
97
  end
98
-
98
+
99
99
  def test_mark_as_read_multiple
100
100
  assert_equal true, @email1.unread?(@reader)
101
101
  assert_equal true, @email2.unread?(@reader)
102
-
102
+
103
103
  Email.mark_as_read! [ @email1, @email2 ], :for => @reader
104
-
104
+
105
105
  assert_equal false, @email1.unread?(@reader)
106
106
  assert_equal false, @email2.unread?(@reader)
107
107
  end
108
-
108
+
109
109
  def test_mark_as_read_with_marked_all
110
110
  wait
111
-
111
+
112
112
  Email.mark_as_read! :all, :for => @reader
113
113
  @email1.mark_as_read! :for => @reader
114
-
114
+
115
115
  assert_equal [], @reader.read_marks.single
116
116
  end
117
-
117
+
118
118
  def test_mark_as_read_twice
119
119
  @email1.mark_as_read! :for => @reader
120
120
  @email1.mark_as_read! :for => @reader
121
-
121
+
122
122
  assert_equal 1, @reader.read_marks.single.count
123
123
  end
124
-
124
+
125
125
  def test_mark_all_as_read
126
126
  Email.mark_as_read! :all, :for => @reader
127
127
  assert_equal Time.now.to_s, @reader.read_mark_global(Email).try(:timestamp).to_s
128
-
128
+
129
129
  assert_equal [], @reader.read_marks.single
130
130
  assert_equal 0, ReadMark.single.count
131
131
  assert_equal 2, ReadMark.global.count
132
132
  end
133
-
133
+
134
134
  def test_cleanup_read_marks
135
135
  assert_equal 0, @reader.read_marks.single.count
136
-
136
+
137
137
  @email1.mark_as_read! :for => @reader
138
-
138
+
139
139
  assert_equal [@email2], Email.unread_by(@reader)
140
140
  assert_equal 1, @reader.read_marks.single.count
141
-
142
- Email.cleanup_read_marks!
143
-
141
+
142
+ Email.cleanup_read_marks!
143
+
144
144
  @reader.reload
145
145
  assert_equal 0, @reader.read_marks.single.count
146
146
  end
147
-
147
+
148
148
  def test_cleanup_read_marks_not_delete_from_other_readables
149
149
  other_read_mark = @reader.read_marks.create! :readable_type => 'Foo', :readable_id => 42, :timestamp => 5.years.ago
150
150
  Email.cleanup_read_marks!
151
151
  assert_equal true, ReadMark.exists?(other_read_mark.id)
152
152
  end
153
-
153
+
154
154
  def test_reset_read_marks_for_all
155
- Email.reset_read_marks!
156
-
155
+ Email.reset_read_marks_for_all
156
+
157
157
  assert_equal 0, ReadMark.single.count
158
158
  assert_equal 2, ReadMark.global.count
159
159
  end
160
-
160
+
161
161
  private
162
162
  def wait
163
- # Skip one second
164
- now = Time.now + 1.second
165
- Time.stubs(:now).returns(now)
163
+ Timecop.travel(1.minute)
166
164
  end
167
- end
165
+ end
data/unread.gemspec CHANGED
@@ -17,11 +17,11 @@ Gem::Specification.new do |s|
17
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
-
21
- s.add_dependency 'activerecord', '>= 2.3.6'
22
-
20
+
21
+ s.add_dependency 'activerecord', '>= 3'
22
+
23
23
  s.add_development_dependency 'rake'
24
- s.add_development_dependency 'mocha', "~> 0.12.8"
24
+ s.add_development_dependency 'timecop'
25
25
  s.add_development_dependency 'sqlite3'
26
26
  s.add_development_dependency 'mysql2'
27
- end
27
+ end
metadata CHANGED
@@ -2,14 +2,14 @@
2
2
  name: unread
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.2
5
+ version: 0.2.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Georg Ledermann
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-27 00:00:00.000000000 Z
12
+ date: 2013-02-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  prerelease: false
@@ -17,7 +17,7 @@ dependencies:
17
17
  requirements:
18
18
  - - ! '>='
19
19
  - !ruby/object:Gem::Version
20
- version: 2.3.6
20
+ version: '3'
21
21
  none: false
22
22
  type: :runtime
23
23
  name: activerecord
@@ -25,7 +25,7 @@ dependencies:
25
25
  requirements:
26
26
  - - ! '>='
27
27
  - !ruby/object:Gem::Version
28
- version: 2.3.6
28
+ version: '3'
29
29
  none: false
30
30
  - !ruby/object:Gem::Dependency
31
31
  prerelease: false
@@ -47,17 +47,17 @@ dependencies:
47
47
  prerelease: false
48
48
  version_requirements: !ruby/object:Gem::Requirement
49
49
  requirements:
50
- - - ~>
50
+ - - ! '>='
51
51
  - !ruby/object:Gem::Version
52
- version: 0.12.8
52
+ version: '0'
53
53
  none: false
54
54
  type: :development
55
- name: mocha
55
+ name: timecop
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - ~>
58
+ - - ! '>='
59
59
  - !ruby/object:Gem::Version
60
- version: 0.12.8
60
+ version: '0'
61
61
  none: false
62
62
  - !ruby/object:Gem::Dependency
63
63
  prerelease: false
@@ -106,11 +106,12 @@ files:
106
106
  - README.md
107
107
  - Rakefile
108
108
  - changelog.md
109
- - ci/Gemfile.rails-2.3.x
110
109
  - ci/Gemfile.rails-3.0.x
111
110
  - ci/Gemfile.rails-3.1.x
112
111
  - ci/Gemfile.rails-3.2.x
113
112
  - lib/app/models/read_mark.rb
113
+ - lib/generators/unread/migration/migration_generator.rb
114
+ - lib/generators/unread/migration/templates/migration.rb
114
115
  - lib/unread.rb
115
116
  - lib/unread/acts_as_readable.rb
116
117
  - lib/unread/version.rb
@@ -1,7 +0,0 @@
1
- source :rubygems
2
-
3
- gem 'activerecord', '~> 2.3.14'
4
- gem 'sqlite3'
5
- gem 'mysql2', '~> 0.2.11'
6
- gem 'mocha', "~> 0.12.8"
7
- gem 'rake'