discussion 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.
Files changed (177) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +16 -0
  3. data/Rakefile +40 -0
  4. data/app/assets/javascripts/discussion/application.js +1 -0
  5. data/app/assets/javascripts/discussion/discussion.js +28 -0
  6. data/app/assets/stylesheets/discussion/application.css +13 -0
  7. data/app/assets/stylesheets/discussion/threads.css +4 -0
  8. data/app/assets/stylesheets/scaffold.css +56 -0
  9. data/app/controllers/discussion/application_controller.rb +4 -0
  10. data/app/controllers/discussion/comments_controller.rb +100 -0
  11. data/app/controllers/discussion/threads_controller.rb +128 -0
  12. data/app/helpers/discussion/application_helper.rb +9 -0
  13. data/app/helpers/discussion/comments_helper.rb +20 -0
  14. data/app/helpers/discussion/threads_helper.rb +52 -0
  15. data/app/models/discussion/comment.rb +54 -0
  16. data/app/models/discussion/comment_read.rb +27 -0
  17. data/app/models/discussion/concerns.rb +24 -0
  18. data/app/models/discussion/thread.rb +69 -0
  19. data/app/models/discussion/thread_read.rb +9 -0
  20. data/app/models/thread_sweeper.rb +23 -0
  21. data/app/views/discussion/comments/_form.html.erb +1 -0
  22. data/app/views/discussion/comments/_list_with_form.html.erb +10 -0
  23. data/app/views/discussion/comments/_view.html.erb +3 -0
  24. data/app/views/discussion/comments/create.js.erb +2 -0
  25. data/app/views/discussion/comments/index.js.erb +2 -0
  26. data/app/views/discussion/threads/_form.html.erb +16 -0
  27. data/app/views/discussion/threads/_index.html.erb +5 -0
  28. data/app/views/discussion/threads/_list.html.erb +40 -0
  29. data/app/views/discussion/threads/_view.html.erb +13 -0
  30. data/app/views/discussion/threads/create.js.erb +2 -0
  31. data/app/views/discussion/threads/index.html.erb +3 -0
  32. data/app/views/discussion/threads/index.js.erb +2 -0
  33. data/app/views/discussion/threads/list.html.erb +1 -0
  34. data/app/views/discussion/threads/new.html.erb +1 -0
  35. data/app/views/discussion/threads/new.js.erb +2 -0
  36. data/app/views/discussion/threads/show.html.erb +1 -0
  37. data/app/views/discussion/threads/show.js.erb +2 -0
  38. data/config/routes.rb +5 -0
  39. data/db/migrate/20130501074817_create_discussion_threads.rb +14 -0
  40. data/db/migrate/20130501075956_create_discussion_concerns.rb +13 -0
  41. data/db/migrate/20130501080150_create_discussion_comments.rb +15 -0
  42. data/db/migrate/20130501080604_create_discussion_comment_reads.rb +11 -0
  43. data/db/migrate/20130502121352_create_discussion_thread_reads.rb +13 -0
  44. data/db/migrate/20130509100700_add_topic_to_threads.rb +9 -0
  45. data/lib/discussion.rb +15 -0
  46. data/lib/discussion/engine.rb +10 -0
  47. data/lib/discussion/version.rb +3 -0
  48. data/lib/generators/discussion/USAGE +15 -0
  49. data/lib/generators/discussion/discussion_generator.rb +26 -0
  50. data/lib/tasks/discussion_tasks.rake +4 -0
  51. data/test/discussion_test.rb +7 -0
  52. data/test/dummy/README.rdoc +261 -0
  53. data/test/dummy/Rakefile +7 -0
  54. data/test/dummy/app/assets/javascripts/application.js +4 -0
  55. data/test/dummy/app/assets/javascripts/assignments.js +2 -0
  56. data/test/dummy/app/assets/stylesheets/application.css +28 -0
  57. data/test/dummy/app/assets/stylesheets/assignments.css +4 -0
  58. data/test/dummy/app/assets/stylesheets/scaffold.css +56 -0
  59. data/test/dummy/app/controllers/application_controller.rb +8 -0
  60. data/test/dummy/app/controllers/assignments_controller.rb +2 -0
  61. data/test/dummy/app/helpers/application_helper.rb +8 -0
  62. data/test/dummy/app/helpers/assignments_helper.rb +2 -0
  63. data/test/dummy/app/models/assignment.rb +5 -0
  64. data/test/dummy/app/models/user.rb +9 -0
  65. data/test/dummy/app/views/assignments/_form.html.erb +29 -0
  66. data/test/dummy/app/views/assignments/edit.html.erb +6 -0
  67. data/test/dummy/app/views/assignments/index.html.erb +27 -0
  68. data/test/dummy/app/views/assignments/new.html.erb +5 -0
  69. data/test/dummy/app/views/assignments/show.html.erb +32 -0
  70. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  71. data/test/dummy/app/views/layouts/discussion/application.html.erb +14 -0
  72. data/test/dummy/config.ru +4 -0
  73. data/test/dummy/config/application.rb +58 -0
  74. data/test/dummy/config/boot.rb +10 -0
  75. data/test/dummy/config/database.yml +25 -0
  76. data/test/dummy/config/environment.rb +5 -0
  77. data/test/dummy/config/environments/development.rb +52 -0
  78. data/test/dummy/config/environments/production.rb +67 -0
  79. data/test/dummy/config/environments/test.rb +37 -0
  80. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  81. data/test/dummy/config/initializers/inflections.rb +15 -0
  82. data/test/dummy/config/initializers/mime_types.rb +5 -0
  83. data/test/dummy/config/initializers/secret_token.rb +7 -0
  84. data/test/dummy/config/initializers/session_store.rb +8 -0
  85. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  86. data/test/dummy/config/locales/en.yml +5 -0
  87. data/test/dummy/config/routes.rb +9 -0
  88. data/test/dummy/custom_plan.rb +11 -0
  89. data/test/dummy/db/development.sqlite3 +0 -0
  90. data/test/dummy/db/migrate/20120624033524_devise_create_users.rb +53 -0
  91. data/test/dummy/db/migrate/20130501154800_create_discussion_threads.discussion.rb +15 -0
  92. data/test/dummy/db/migrate/20130501154801_create_discussion_concerns.discussion.rb +14 -0
  93. data/test/dummy/db/migrate/20130501154802_create_discussion_comments.discussion.rb +16 -0
  94. data/test/dummy/db/migrate/20130501154803_create_discussion_comment_reads.discussion.rb +12 -0
  95. data/test/dummy/db/migrate/20130502144148_create_discussion_thread_reads.discussion.rb +14 -0
  96. data/test/dummy/db/migrate/20130509101316_create_assignments.rb +12 -0
  97. data/test/dummy/db/migrate/20130509101334_add_topic_to_threads.discussion.rb +10 -0
  98. data/test/dummy/db/schema.rb +104 -0
  99. data/test/dummy/db/seed.rb +28 -0
  100. data/test/dummy/db/test.sqlite3 +0 -0
  101. data/test/dummy/log/development.log +9981 -0
  102. data/test/dummy/public/404.html +26 -0
  103. data/test/dummy/public/422.html +26 -0
  104. data/test/dummy/public/500.html +25 -0
  105. data/test/dummy/public/favicon.ico +0 -0
  106. data/test/dummy/script/rails +6 -0
  107. data/test/dummy/spec/controllers/assignments_controller_spec.rb +160 -0
  108. data/test/dummy/spec/helpers/assignments_helper_spec.rb +15 -0
  109. data/test/dummy/spec/models/assignment_spec.rb +5 -0
  110. data/test/dummy/spec/requests/assignments_spec.rb +11 -0
  111. data/test/dummy/spec/routing/assignments_routing_spec.rb +35 -0
  112. data/test/dummy/spec/views/assignments/edit.html.erb_spec.rb +22 -0
  113. data/test/dummy/spec/views/assignments/index.html.erb_spec.rb +26 -0
  114. data/test/dummy/spec/views/assignments/new.html.erb_spec.rb +22 -0
  115. data/test/dummy/spec/views/assignments/show.html.erb_spec.rb +19 -0
  116. data/test/dummy/tmp/cache/34A/041/http%3A%2F%2Flocalhost%3A3000%2Fassets%2Fjquery.js%3Fbody%3D1 +0 -0
  117. data/test/dummy/tmp/cache/458/621/http%3A%2F%2Flocalhost%3A3000%2Fassets%2Fscaffold.css%3Fbody%3D1 +0 -0
  118. data/test/dummy/tmp/cache/4FB/6C1/http%3A%2F%2Flocalhost%3A3000%2Fassets%2Fjquery_ujs.js%3Fbody%3D1 +0 -0
  119. data/test/dummy/tmp/cache/53E/311/http%3A%2F%2Flocalhost%3A3000%2Fassets%2Fapplication.js%3Fbody%3D1 +0 -0
  120. data/test/dummy/tmp/cache/556/F41/http%3A%2F%2Flocalhost%3A3000%2Fassets%2Fassignments.js%3Fbody%3D1 +0 -0
  121. data/test/dummy/tmp/cache/5AA/581/http%3A%2F%2Flocalhost%3A3000%2Fassets%2Fapplication.css%3Fbody%3D1 +0 -0
  122. data/test/dummy/tmp/cache/5C2/331/http%3A%2F%2Flocalhost%3A3000%2Fassets%2Fassignments.css%3Fbody%3D1 +0 -0
  123. data/test/dummy/tmp/cache/9A9/BB0/total_unread_thread_by_6 +1 -0
  124. data/test/dummy/tmp/cache/9CF/491/http%3A%2F%2Flocalhost%3A3000%2Fassets%2Fdiscussion%2Fdiscussion.js%3Fbody%3D1 +0 -0
  125. data/test/dummy/tmp/cache/A1F/311/http%3A%2F%2Flocalhost%3A3000%2Fassets%2Fdiscussion%2Fapplication.js%3Fbody%3D1 +0 -0
  126. data/test/dummy/tmp/cache/A44/EA0/2898ba2093d346f7463a4396e46d4bd73590fd60 +25 -0
  127. data/test/dummy/tmp/cache/A53/930/8ab0f4219a7b2c19044a090115a45e76e43b268e +6 -0
  128. data/test/dummy/tmp/cache/A91/340/3f3d2d8955322f325af6db2238355fa07007ebd9 +5 -0
  129. data/test/dummy/tmp/cache/A9B/580/326f111948a98b94ce79f7ead20a6a8f6134316a +17 -0
  130. data/test/dummy/tmp/cache/ACB/1C0/61e9711fd55eccdf62882cb8d127b22664f7023f +4 -0
  131. data/test/dummy/tmp/cache/ACB/C60/2d70f6e0b599912c0a0a476f7d1d873d16fc587a +15 -0
  132. data/test/dummy/tmp/cache/AEF/AF0/24b5ae4d0aa3c3e6f7898cc323c8434330bf6c48 +20 -0
  133. data/test/dummy/tmp/cache/AF9/0E0/96b52cbbdd9b2500e89198a5d9ac461e490d2d27 +30 -0
  134. data/test/dummy/tmp/cache/B06/3F0/da9f60ff5af114f8058d98986d9c2350fd2df114 +9599 -0
  135. data/test/dummy/tmp/cache/B1C/810/9e06e1eec0d1350714a34d137eb4e4e93df71d7b +19 -0
  136. data/test/dummy/tmp/cache/B21/980/93de869eb331c65cb1c7a282ae089ee1b9111fd1 +25 -0
  137. data/test/dummy/tmp/cache/B27/910/da39a3ee5e6b4b0d3255bfef95601890afd80709 +0 -0
  138. data/test/dummy/tmp/cache/B30/970/7c338ed2840d2bf55f9f5e4eed04f66c80840eb3 +4 -0
  139. data/test/dummy/tmp/cache/B52/020/723598e1e44b35d4f2dca9ba0d3d1fa86fb63c81 +17 -0
  140. data/test/dummy/tmp/cache/B69/300/03c05703ee2f7ecbe9331dabc80cb14364ae1cd4 +14 -0
  141. data/test/dummy/tmp/cache/B72/C30/6c7fd8edb7af04528edf3f4c189f498ef511349e +20 -0
  142. data/test/dummy/tmp/cache/B8A/9B0/8446a97c4cbd4f1c6650b6dda8f7381cacfe5f31 +57 -0
  143. data/test/dummy/tmp/cache/BDA/810/586d7c0cfc531f2d9bffa4afa518cdde00e640a5 +25 -0
  144. data/test/dummy/tmp/cache/BE1/FA0/cef8de7e145c5aba7808de00a8b78750bfeba3b4 +30 -0
  145. data/test/dummy/tmp/cache/C0F/F10/853eabd960e1d1c97f280dfde98cda31aceda31c +401 -0
  146. data/test/dummy/tmp/cache/C41/FC0/b5dff9ef4aa8e34a09fbc8bc024f3ddd59d711ba +17 -0
  147. data/test/dummy/tmp/cache/assets/C80/A60/sprockets%2F3d71727f9a9628e5ea4071de13513523 +0 -0
  148. data/test/dummy/tmp/cache/assets/C8C/B80/sprockets%2F371bf96e99717688ed7313a0c53f4212 +0 -0
  149. data/test/dummy/tmp/cache/assets/CBB/080/sprockets%2F2d799cd88730be6f8d442356020b671f +0 -0
  150. data/test/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
  151. data/test/dummy/tmp/cache/assets/CE3/D10/sprockets%2F966d9544f2171c807e5dd194302de1cc +0 -0
  152. data/test/dummy/tmp/cache/assets/CEE/900/sprockets%2Fa62a39d06b7d4de441784b9974f92e52 +0 -0
  153. data/test/dummy/tmp/cache/assets/CF0/1D0/sprockets%2F6fc757c2c8329244ca95d6909865bbc2 +0 -0
  154. data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  155. data/test/dummy/tmp/cache/assets/D34/9E0/sprockets%2F11dabe8b1273c07cacd98060623de865 +0 -0
  156. data/test/dummy/tmp/cache/assets/D40/4E0/sprockets%2F441fdbbb1185dcb64934067f3a64f85e +0 -0
  157. data/test/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
  158. data/test/dummy/tmp/cache/assets/D59/650/sprockets%2F0e15e6c08b5d2c6fc092a5a804f1a4a2 +0 -0
  159. data/test/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
  160. data/test/dummy/tmp/cache/assets/D69/160/sprockets%2Fd50935d69817529b1cb6e6cadc111aec +0 -0
  161. data/test/dummy/tmp/cache/assets/D6C/290/sprockets%2Fedf363ea932d74107ecad71304e3c69d +0 -0
  162. data/test/dummy/tmp/cache/assets/D6D/870/sprockets%2F0b22785b6eb9daa3c9480e70771b6bed +0 -0
  163. data/test/dummy/tmp/cache/assets/D81/A20/sprockets%2F7fe1c5ae586747d5bba86892a2df7f42 +0 -0
  164. data/test/dummy/tmp/cache/assets/D92/C00/sprockets%2Fdd4af168d08e874f3d9c8e2d1f7d9898 +0 -0
  165. data/test/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
  166. data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  167. data/test/dummy/zeus.json +23 -0
  168. data/test/functional/discussion/threads_controller_test.rb +51 -0
  169. data/test/integration/navigation_test.rb +10 -0
  170. data/test/test_helper.rb +15 -0
  171. data/test/unit/discussion/comment_read_test.rb +9 -0
  172. data/test/unit/discussion/comment_test.rb +9 -0
  173. data/test/unit/discussion/concerns_test.rb +9 -0
  174. data/test/unit/discussion/thread_read_test.rb +9 -0
  175. data/test/unit/discussion/thread_test.rb +9 -0
  176. data/test/unit/helpers/discussion/threads_helper_test.rb +6 -0
  177. metadata +491 -0
@@ -0,0 +1,52 @@
1
+ module Discussion
2
+ module ThreadsHelper
3
+ def link_to_threads(options={}, &block)
4
+ options = {remote: Discussion.ajaxify}.merge(options)
5
+ link_to options[:link_text] || 'Threads', path_to_threads(options), options, &block
6
+ end
7
+
8
+ def path_to_threads(options={})
9
+ sent_item = options[:sent_item].nil? ? nil : (options[:sent_item] ? 'true' : 'false')
10
+ @threadable.present? ? main_app.polymorphic_url([@threadable, :threads], sent_item: sent_item) : threads_path(sent_item: sent_item)
11
+ end
12
+
13
+ def link_to_thread(thread, options={}, &block)
14
+ path = @threadable.present? ? main_app.polymorphic_url([@threadable, thread]) : thread_path(thread)
15
+ options = {remote: Discussion.ajaxify}.merge(options)
16
+ link_to options[:link_text] || 'Threads', path, options, &block
17
+ end
18
+
19
+ def link_to_destroy_thread(thread, options={}, &block)
20
+ options = {method: :delete, data: {confirm: 'Are you sure?'}, link_text: 'Destroy'}
21
+ link_to_thread(thread, options, &block)
22
+ end
23
+
24
+ def link_to_new_thread(options={}, &block)
25
+ path = @threadable.present? ? main_app.polymorphic_url([:new, @threadable, :thread]) : new_thread_path
26
+ options = {remote: Discussion.ajaxify}.merge(options)
27
+ link_to options[:link_text] || 'New Thread', path, options, &block
28
+ end
29
+
30
+ def form_path
31
+ @threadable.present? ? main_app.polymorphic_url([@threadable, :threads]) : threads_path
32
+ end
33
+
34
+ def load_threads_for(topic, options={})
35
+ remote = options[:remote] || false
36
+ threads_container_id = "threads-#{Time.now.to_i}"
37
+ if remote
38
+ <<-EOF.html_safe
39
+ <div id='#{threads_container_id}' class="comments_container"></div>
40
+ <script type='text/javascript'>
41
+ Disussion.loadComments('#{polymorphic_url([topic, :threads])}', '#{threads_container_id}');
42
+ </script>
43
+ EOF
44
+ else
45
+ content_tag :div, id: comments_container_id do
46
+ render :partial => "discussion/comments/list_with_form", locals: {commentable: commentable, contriner_id: comments_container_id}
47
+ end
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,54 @@
1
+ module Discussion
2
+ class Comment < ActiveRecord::Base
3
+ attr_accessible :body
4
+ validates :author_id, :body, presence: true
5
+
6
+ belongs_to :author, class_name: Discussion.user_class
7
+ belongs_to :commentable, polymorphic: true
8
+ has_many :comment_reads, class_name: 'Discussion::CommentRead', dependent: :destroy
9
+
10
+ after_create :update_last_posted_at, :create_comment_read_for_concerns, :update_total_comments_post
11
+
12
+ scope :order_by_recent, order('discussion_comments.created_at DESC')
13
+
14
+ #TODO: improve the performance for the ready by
15
+ def self.load_read_by(comments, user)
16
+ #comment_ids = comments.collect(:id)
17
+ #comment_ids
18
+ #Discussion::CommentRead.where()
19
+ end
20
+
21
+ def read_by!(user)
22
+ my_comment_reads = self.comment_reads.where(user_id: user.id, comment_id: self.id)
23
+ comment_read = my_comment_reads.first || my_comment_reads.new
24
+ comment_read.read_at ||= Time.zone.now
25
+ comment_read.save!
26
+ end
27
+
28
+ def read_by?(user)
29
+ @read_already ||= self.comment_reads.where('user_id = ? AND read_at IS NOT NULL', user.id).count > 0
30
+ end
31
+
32
+ private
33
+ def update_last_posted_at
34
+ if self.commentable.has_attribute?(:last_posted_at)
35
+ self.commentable.update_column :last_posted_at, Time.zone.now
36
+ end
37
+ end
38
+
39
+ def create_comment_read_for_concerns
40
+ if self.commentable.kind_of?(Discussion::Thread)
41
+ self.commentable.concern_users.each do |user|
42
+ scope = self.comment_reads.by(user.id)
43
+ scope.first || scope.create!(comment_id: self.id)
44
+ end
45
+ end
46
+ end
47
+
48
+ def update_total_comments_post
49
+ if self.commentable.has_attribute?(:total_comments_post)
50
+ self.commentable.update_column :total_comments_post, self.commentable.comments.count
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,27 @@
1
+ module Discussion
2
+ class CommentRead < ActiveRecord::Base
3
+ attr_accessible :comment_id, :user_id
4
+ belongs_to :user, class_name: Discussion.user_class
5
+ belongs_to :comment, class_name: 'Discussion::Comment'
6
+
7
+ validates :user_id, :comment_id, presence: true
8
+
9
+ scope :by, ->(user) { where(user_id: user) }
10
+ after_save :sync_thread_read
11
+
12
+ private
13
+ def sync_thread_read
14
+ if (self.read_at_changed? or self.id_changed?) and self.comment.commentable.kind_of?(Discussion::Thread)
15
+ scope = self.comment.commentable.thread_reads.by(self.user_id)
16
+ thread_read = scope.first || scope.new
17
+
18
+ total_unread = self.comment.commentable.comments.joins(:comment_reads).
19
+ where('discussion_comment_reads.read_at IS NULL AND user_id=?', user.id).count(distinct: true)
20
+
21
+ thread_read.read = total_unread == 0
22
+
23
+ thread_read.save
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ module Discussion
2
+ class Concerns < ActiveRecord::Base
3
+ attr_accessible :thread_id, :user_id
4
+ belongs_to :thread, class_name: 'Discussion::Thread'
5
+ belongs_to :user, class_name: Discussion.user_class
6
+
7
+ after_create :create_thread_read, :create_comment_read_for_comments
8
+
9
+ private
10
+ def create_thread_read
11
+ scope = self.thread.thread_reads.by(self.user_id)
12
+ thread_read = scope.first || scope.new
13
+ thread_read.read = false
14
+ thread_read.save!
15
+ end
16
+
17
+ def create_comment_read_for_comments
18
+ self.thread.comments.each do |msg|
19
+ scope = msg.comment_reads.by(self.user_id)
20
+ scope.first || scope.create!
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,69 @@
1
+ module Discussion
2
+ class Thread < ActiveRecord::Base
3
+ attr_accessible :subject, :comments_attributes, :concern_user_ids
4
+ validates :subject, presence: true
5
+
6
+ belongs_to :topic, polymorphic: true
7
+
8
+ belongs_to :initiator, class_name: Discussion.user_class
9
+ has_many :comments, as: :commentable, dependent: :destroy
10
+
11
+ has_many :concerns, class_name: 'Discussion::Concerns', dependent: :destroy
12
+ has_many :concern_users, through: :concerns, source: Discussion.user_class.underscore.to_sym
13
+
14
+ has_many :thread_reads, class_name: 'Discussion::ThreadRead', dependent: :destroy
15
+
16
+ accepts_nested_attributes_for :comments
17
+ before_create :add_initiator_to_concerns
18
+
19
+ # use the count with distinct: true as following
20
+ # Discussion::Thread.read_by(User.first).count(distinct: true)
21
+ scope :by_user_and_status, ->(user, read) {
22
+ joins(:thread_reads).where('discussion_thread_reads.user_id = ? AND discussion_thread_reads.read = ?', user.id, read)
23
+ }
24
+
25
+ scope :by_initiator, ->(user) {
26
+ where(initiator_id: user.id)
27
+ }
28
+
29
+ scope :read_by, ->(user) {
30
+ by_user_and_status(user, true)
31
+ }
32
+
33
+ scope :unread_by, ->(user) {
34
+ by_user_and_status(user, false)
35
+ }
36
+
37
+ scope :order_by_recent, order('discussion_threads.last_posted_at DESC')
38
+
39
+ scope :concerns_with, ->(user) {
40
+ joins(:concerns).where('discussion_concerns.user_id=?', user.id)
41
+ }
42
+ scope :sent_item_for, ->(user) { where(initiator_id: user.id) }
43
+
44
+ def read_by?(user)
45
+ self.thread_reads.by(user).where(read: false).count == 0
46
+ end
47
+
48
+ def self.total_unread_by(user)
49
+ Rails.cache.fetch("total_unread_thread_by_#{user.id}", expires_in: 10.minutes) do
50
+ Thread.unread_by(user).count
51
+ end
52
+ end
53
+
54
+ def number_of_read_comments_by(user)
55
+ @number_of_unread_comments ||= {}
56
+ @number_of_unread_comments[user.id] ||= self.comments.
57
+ joins(:comment_reads).where('discussion_comment_reads.user_id = ? AND discussion_comment_reads.read_at IS NOT NULL', user.id).count
58
+ end
59
+
60
+ def number_of_unread_comments_by(user)
61
+ self.total_comments_post - number_of_read_comments_by(user)
62
+ end
63
+
64
+ private
65
+ def add_initiator_to_concerns
66
+ self.concern_users << self.initiator
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,9 @@
1
+ module Discussion
2
+ class ThreadRead < ActiveRecord::Base
3
+ attr_accessible :read, :thread_id, :user_id
4
+ belongs_to :user, class_name: Discussion.user_class
5
+ belongs_to :thread, class_name: 'Discussion::Thread'
6
+
7
+ scope :by, ->(user) { where(user_id: user) }
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ class ThreadSweeper < ActionController::Caching::Sweeper
2
+ observe Discussion::ThreadRead
3
+
4
+ def after_save(thread_read)
5
+ expire_cache_for(thread_read) if thread_read.read_changed?
6
+ end
7
+
8
+ def after_create(thread_read)
9
+ expire_cache_for(thread_read)
10
+ end
11
+
12
+ def after_destroy(thread_read)
13
+ expire_cache_for(thread_read)
14
+ end
15
+
16
+ private
17
+ def expire_cache_for(thread_read)
18
+ Rails.logger.debug "deleting cache for :: #{thread_read.user_id}"
19
+ Rails.logger.debug "================================================================="
20
+ Rails.cache.delete("total_unread_thread_by_#{thread_read.user_id}")
21
+ end
22
+
23
+ end
@@ -0,0 +1 @@
1
+ <%= f.input :body %>
@@ -0,0 +1,10 @@
1
+ <%= render :partial => "discussion/comments/view", collection: commentable.comments.includes(:author), as: 'comment' %>
2
+
3
+ <%= simple_form_for([commentable, commentable.comments.build], remote: Discussion.ajaxify) do |f| %>
4
+ <%= f.error_notification %>
5
+ <%= render :partial => "discussion/comments/form", locals: {f: f} %>
6
+
7
+ <div class="form-actions">
8
+ <%= f.submit %>
9
+ </div>
10
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <div class="comment <%= comment.read_by?(current_user) ? 'read' : 'unread' %>">
2
+ <%= comment.author.try(:email) %> | <%= comment.body %>
3
+ </div>
@@ -0,0 +1,2 @@
1
+ var container = $('#<%= params[:contrinerId] || Discussion.ajax_wrapper_id %>');
2
+ container.html("<%= escape_javascript(render partial: 'discussion/comments/list_with_form', locals: {commentable: @commentable, contriner_id: params[:contrinerId]}) %>");
@@ -0,0 +1,2 @@
1
+ var container = $('#<%= params[:contrinerId] || Discussion.ajax_wrapper_id %>');
2
+ container.html("<%= escape_javascript(render :partial => "discussion/comments/list_with_form", locals: {commentable: @commentable, contriner_id: params[:contrinerId]}) %>");
@@ -0,0 +1,16 @@
1
+ <h1>New thread</h1>
2
+
3
+ <%= simple_form_for(@thread, url: form_path, remote: Discussion.ajaxify) do |f| %>
4
+ <%= f.error_notification %>
5
+
6
+ <%= field_set_tag do %>
7
+ <%= f.input :subject %>
8
+ <%= f.simple_fields_for :comments, [@thread.comments.build] do |mf| %>
9
+ <%= render :partial => "discussion/comments/form", locals: {f: mf} %>
10
+ <% end %>
11
+ <% end %>
12
+
13
+ <div class="form-actions">
14
+ <%= link_to_threads link_text: "Cancel" %> | <%= f.submit %>
15
+ </div>
16
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <h1>Listing threads</h1>
2
+ Ttotal unread thread for <%= current_user.email %> is ::
3
+ <span><%= Discussion::Thread.total_unread_by(current_user) %></span>
4
+
5
+ <%= render :partial => "discussion/threads/list" %>
@@ -0,0 +1,40 @@
1
+ <%= search_form_for @search, url: path_to_threads, remote: Discussion.ajaxify do |f| %>
2
+ <%= hidden_field_tag :sent_item, params[:sent_item] %>
3
+ <div class="field">
4
+ <%= f.text_field :subject_cont, placeholder: 'Subject' %>
5
+ </div>
6
+ <div class="actions"><%= f.submit "Search" %></div>
7
+ <% end %>
8
+
9
+ <%= link_to_threads link_text: 'All comments' %> |
10
+ <%= link_to_threads link_text: 'Sent Item', sent_item: true %>
11
+
12
+ <table class="table ">
13
+ <tr>
14
+ <th>Subject</th>
15
+ <th>Initiator</th>
16
+ <th><%= sort_link(@search, :created_at, "Initiated on", {}, {remote: Discussion.ajaxify}) %></th>
17
+ <th><%= sort_link(@search, :last_posted_at, "Last post at", {}, {remote: Discussion.ajaxify}) %></th>
18
+ <th><%= sort_link(@search, :total_comments_post, "Total comments post", {}, {remote: Discussion.ajaxify}) %></th>
19
+ <th></th>
20
+ </tr>
21
+
22
+ <% @threads.each do |thread| %>
23
+ <tr class="thread <%= thread.read_by?(current_user) ? 'read' : 'unread' %>">
24
+ <td>
25
+ <!--<span class="counter">(<%#= thread.number_of_unread_comments_by(current_user) %>)</span>-->
26
+ <%= link_to_thread thread, link_text: thread.subject.truncate(80, :omission => "... (continued)") %>
27
+ </td>
28
+ <td><%= thread.initiator.try(:email) %></td>
29
+ <td><%= thread.created_at %></td>
30
+ <td><%= thread.last_posted_at %></td>
31
+ <td><%= thread.total_comments_post %></td>
32
+ <td><%= link_to_destroy_thread(thread) %></td>
33
+ </tr>
34
+ <% end %>
35
+ </table>
36
+
37
+ <%= paginate @threads, remote: Discussion.ajaxify %>
38
+ <br/>
39
+
40
+ <%= link_to_new_thread %>
@@ -0,0 +1,13 @@
1
+ <p id="notice"><%= notice %></p>
2
+
3
+ <p>
4
+ <b>Subject:</b>
5
+ <%= @thread.subject %>
6
+ </p>
7
+
8
+ <p>
9
+ <b>Initiator:</b>
10
+ <%= @thread.initiator.try(:email) %>
11
+ </p>
12
+
13
+ <%= load_comments_for(@thread, lazy: true) %>
@@ -0,0 +1,2 @@
1
+ var container = $('#<%= params['contrinerId'] || Discussion.ajax_wrapper_id %>')
2
+ container.html("<%= escape_javascript(render partial: 'discussion/threads/view') %>");
@@ -0,0 +1,3 @@
1
+ <div id="<%= Discussion.ajax_wrapper_id %>">
2
+ <%= render :partial => "discussion/threads/index" %>
3
+ </div>
@@ -0,0 +1,2 @@
1
+ var container = $('#<%= params['contrinerId'] || Discussion.ajax_wrapper_id %>')
2
+ container.html("<%= escape_javascript(render partial: 'discussion/threads/list') %>");
@@ -0,0 +1 @@
1
+ <%= render :partial => "discussion/threads/list" %>
@@ -0,0 +1 @@
1
+ <%= render 'form' %>
@@ -0,0 +1,2 @@
1
+ var container = $('#<%= params['contrinerId'] || Discussion.ajax_wrapper_id %>')
2
+ container.html("<%= escape_javascript(render partial: 'discussion/threads/form') %>");
@@ -0,0 +1 @@
1
+ <%= render :partial => "discussion/threads/view" %>
@@ -0,0 +1,2 @@
1
+ var container = $('#<%= params['contrinerId'] || Discussion.ajax_wrapper_id %>')
2
+ container.html("<%= escape_javascript(render partial: 'discussion/threads/view') %>");
@@ -0,0 +1,5 @@
1
+ Discussion::Engine.routes.draw do
2
+ resources :threads do
3
+ resources :comments, except: [:show]
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ class CreateDiscussionThreads < ActiveRecord::Migration
2
+ def change
3
+ create_table :discussion_threads do |t|
4
+ t.string :subject
5
+ t.integer :initiator_id
6
+ t.datetime :last_posted_at
7
+ t.integer :total_comments_post, default: 0
8
+
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :discussion_threads, :initiator_id
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ class CreateDiscussionConcerns < ActiveRecord::Migration
2
+ def change
3
+ create_table :discussion_concerns do |t|
4
+ t.integer :user_id
5
+ t.integer :thread_id
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :discussion_concerns, :user_id
11
+ add_index :discussion_concerns, :thread_id
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ class CreateDiscussionComments < ActiveRecord::Migration
2
+ def change
3
+ create_table :discussion_comments do |t|
4
+ t.integer :author_id
5
+ t.integer :commentable_id
6
+ t.string :commentable_type, limit: 32
7
+ t.text :body
8
+
9
+ t.timestamps
10
+ end
11
+ add_index :discussion_comments, :author_id
12
+ add_index :discussion_comments, :commentable_id
13
+ add_index :discussion_comments, :commentable_type
14
+ end
15
+ end