unread 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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'