message_train 0.1.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.
Files changed (214) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.rspec +1 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +7 -0
  7. data/Gemfile +51 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.rdoc +83 -0
  10. data/Rakefile +71 -0
  11. data/VERSION +1 -0
  12. data/app/assets/javascripts/message_train.js +138 -0
  13. data/app/assets/stylesheets/message_train.scss +107 -0
  14. data/app/controllers/message_train/application_controller.rb +64 -0
  15. data/app/controllers/message_train/boxes_controller.rb +37 -0
  16. data/app/controllers/message_train/conversations_controller.rb +38 -0
  17. data/app/controllers/message_train/messages_controller.rb +74 -0
  18. data/app/controllers/message_train/participants_controller.rb +36 -0
  19. data/app/helpers/message_train/application_helper.rb +4 -0
  20. data/app/helpers/message_train/boxes_helper.rb +30 -0
  21. data/app/helpers/message_train/conversations_helper.rb +73 -0
  22. data/app/helpers/message_train/messages_helper.rb +58 -0
  23. data/app/models/message_train/attachment.rb +17 -0
  24. data/app/models/message_train/box.rb +268 -0
  25. data/app/models/message_train/conversation.rb +101 -0
  26. data/app/models/message_train/ignore.rb +18 -0
  27. data/app/models/message_train/message.rb +146 -0
  28. data/app/models/message_train/receipt.rb +70 -0
  29. data/app/views/application/404.html.haml +3 -0
  30. data/app/views/message_train/application/results.json.jbuilder +8 -0
  31. data/app/views/message_train/boxes/_dropdown_list.html.haml +5 -0
  32. data/app/views/message_train/boxes/_list_item.html.haml +5 -0
  33. data/app/views/message_train/boxes/_widget.html.haml +6 -0
  34. data/app/views/message_train/boxes/show.html.haml +47 -0
  35. data/app/views/message_train/conversations/_conversation.html.haml +9 -0
  36. data/app/views/message_train/conversations/_deleted_toggle.html.haml +1 -0
  37. data/app/views/message_train/conversations/_ignored_toggle.html.haml +4 -0
  38. data/app/views/message_train/conversations/_read_toggle.html.haml +4 -0
  39. data/app/views/message_train/conversations/_toggle.html.haml +1 -0
  40. data/app/views/message_train/conversations/_trashed_toggle.html.haml +4 -0
  41. data/app/views/message_train/conversations/show.html.haml +11 -0
  42. data/app/views/message_train/conversations/show.json.jbuilder +2 -0
  43. data/app/views/message_train/messages/_deleted_toggle.html.haml +1 -0
  44. data/app/views/message_train/messages/_form.html.haml +8 -0
  45. data/app/views/message_train/messages/_message.html.haml +35 -0
  46. data/app/views/message_train/messages/_read_toggle.html.haml +4 -0
  47. data/app/views/message_train/messages/_toggle.html.haml +1 -0
  48. data/app/views/message_train/messages/_trashed_toggle.html.haml +4 -0
  49. data/app/views/message_train/messages/edit.html.haml +2 -0
  50. data/app/views/message_train/messages/new.html.haml +2 -0
  51. data/app/views/message_train/messages/show.json.jbuilder +2 -0
  52. data/app/views/message_train/participants/_field.html.haml +4 -0
  53. data/app/views/message_train/participants/_participant.json.jbuilder +5 -0
  54. data/app/views/message_train/participants/index.json.jbuilder +3 -0
  55. data/app/views/message_train/participants/show.json.jbuilder +1 -0
  56. data/bin/rails +12 -0
  57. data/config/initializers/bootstrap_pager_config.rb +10 -0
  58. data/config/initializers/date_time.rb +5 -0
  59. data/config/locales/en.yml +50 -0
  60. data/config/routes.rb +11 -0
  61. data/db/migrate/20150721145319_create_message_train_conversations.rb +9 -0
  62. data/db/migrate/20150721160322_create_message_train_messages.rb +14 -0
  63. data/db/migrate/20150721161144_create_message_train_attachments.rb +10 -0
  64. data/db/migrate/20150721161940_create_message_train_receipts.rb +16 -0
  65. data/db/migrate/20150721163838_create_message_train_ignores.rb +11 -0
  66. data/lib/generators/message_train/install/install_generator.rb +30 -0
  67. data/lib/generators/message_train/install/templates/initializer.rb +10 -0
  68. data/lib/generators/message_train/utils.rb +16 -0
  69. data/lib/message_train/configuration.rb +35 -0
  70. data/lib/message_train/engine.rb +19 -0
  71. data/lib/message_train/localization.rb +31 -0
  72. data/lib/message_train/mixin.rb +83 -0
  73. data/lib/message_train/version.rb +7 -0
  74. data/lib/message_train.rb +20 -0
  75. data/lib/tasks/message_train_tasks.rake +0 -0
  76. data/message_train.gemspec +325 -0
  77. data/night_train.gemspec +274 -0
  78. data/spec/controllers/message_train/boxes_controller_spec.rb +82 -0
  79. data/spec/controllers/message_train/conversations_controller_spec.rb +86 -0
  80. data/spec/controllers/message_train/messages_controller_spec.rb +210 -0
  81. data/spec/controllers/message_train/participants_controller_spec.rb +35 -0
  82. data/spec/dummy/.rspec +2 -0
  83. data/spec/dummy/Rakefile +6 -0
  84. data/spec/dummy/app/assets/files/message_train/attachments/1917-Boys_Race_Above-Wiki.jpg +0 -0
  85. data/spec/dummy/app/assets/files/message_train/attachments/ACD_Silverbarn's_Mayumi.jpg +0 -0
  86. data/spec/dummy/app/assets/files/message_train/attachments/ACD_obedience.jpg +0 -0
  87. data/spec/dummy/app/assets/files/message_train/attachments/A_Man_and_His_Dog.jpg +0 -0
  88. data/spec/dummy/app/assets/files/message_train/attachments/Alaskan_Malamute_agility_a-frame.jpg +0 -0
  89. data/spec/dummy/app/assets/files/message_train/attachments/American_water_spaniel_02.jpg +0 -0
  90. data/spec/dummy/app/assets/files/message_train/attachments/Aport_konkurs_rybnik.jpg +0 -0
  91. data/spec/dummy/app/assets/files/message_train/attachments/Askerhytta.jpg +0 -0
  92. data/spec/dummy/app/assets/files/message_train/attachments/Bambisj.jpg +0 -0
  93. data/spec/dummy/app/assets/files/message_train/attachments/Cattle_dog_skating.jpg +0 -0
  94. data/spec/dummy/app/assets/files/message_train/attachments/Dog's_Olympics_training_ground_-_geograph.org.uk_-_660324.jpg +0 -0
  95. data/spec/dummy/app/assets/files/message_train/attachments/DogShowHierarchy.png +0 -0
  96. data/spec/dummy/app/assets/files/message_train/attachments/Dog_weight_pull.jpg +0 -0
  97. data/spec/dummy/app/assets/files/message_train/attachments/Draghuntpostcard.jpg +0 -0
  98. data/spec/dummy/app/assets/files/message_train/attachments/Esham_51.JPG +0 -0
  99. data/spec/dummy/app/assets/files/message_train/attachments/FastCourseExample.jpg +0 -0
  100. data/spec/dummy/app/assets/files/message_train/attachments/Fotos_joel_141.JPG +0 -0
  101. data/spec/dummy/app/assets/files/message_train/attachments/Galgo_Spanish_male_brindle.jpg +0 -0
  102. data/spec/dummy/app/assets/files/message_train/attachments/Golden-retriever-carlos-bei-der-dummyarbeit.jpg +0 -0
  103. data/spec/dummy/app/assets/files/message_train/attachments/Habavk.jpg +0 -0
  104. data/spec/dummy/app/assets/files/message_train/attachments/Haie_rci.svg +1714 -0
  105. data/spec/dummy/app/assets/files/message_train/attachments/Hanging_18.jpg +0 -0
  106. data/spec/dummy/app/assets/files/message_train/attachments/Hundewa.jpg +0 -0
  107. data/spec/dummy/app/assets/files/message_train/attachments/K9ProSports_AttackOnHandler.jpg +0 -0
  108. data/spec/dummy/app/assets/files/message_train/attachments/K9ProSports_heel.jpg +0 -0
  109. data/spec/dummy/app/assets/files/message_train/attachments/K9Pro_Civil_agitation.JPG +0 -0
  110. data/spec/dummy/app/assets/files/message_train/attachments/TrainingSchutzhundRetrieveOverWall.jpg +0 -0
  111. data/spec/dummy/app/assets/files/message_train/attachments/example.pdf +0 -0
  112. data/spec/dummy/app/assets/files/message_train/attachments/letterlegal5.doc +0 -0
  113. data/spec/dummy/app/assets/files/message_train/attachments/pdf-sample.pdf +0 -0
  114. data/spec/dummy/app/assets/files/message_train/attachments/sample.pdf +0 -0
  115. data/spec/dummy/app/assets/files/message_train/attachments/tips.doc +0 -0
  116. data/spec/dummy/app/assets/files/message_train/attachments/wd-spectools-word-sample-04.doc +0 -0
  117. data/spec/dummy/app/assets/images/logo.svg +140 -0
  118. data/spec/dummy/app/assets/javascripts/application.js +17 -0
  119. data/spec/dummy/app/assets/stylesheets/application.css.scss +35 -0
  120. data/spec/dummy/app/controllers/application_controller.rb +14 -0
  121. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  122. data/spec/dummy/app/mailers/.keep +0 -0
  123. data/spec/dummy/app/models/.keep +0 -0
  124. data/spec/dummy/app/models/group.rb +19 -0
  125. data/spec/dummy/app/models/role.rb +10 -0
  126. data/spec/dummy/app/models/user.rb +23 -0
  127. data/spec/dummy/app/views/layouts/_top_navigation.html.haml +5 -0
  128. data/spec/dummy/app/views/layouts/application.html.haml +29 -0
  129. data/spec/dummy/bin/bundle +3 -0
  130. data/spec/dummy/bin/rails +4 -0
  131. data/spec/dummy/bin/rake +4 -0
  132. data/spec/dummy/bin/setup +29 -0
  133. data/spec/dummy/config/application.rb +40 -0
  134. data/spec/dummy/config/boot.rb +5 -0
  135. data/spec/dummy/config/database.yml +25 -0
  136. data/spec/dummy/config/environment.rb +5 -0
  137. data/spec/dummy/config/environments/development.rb +41 -0
  138. data/spec/dummy/config/environments/production.rb +79 -0
  139. data/spec/dummy/config/environments/test.rb +42 -0
  140. data/spec/dummy/config/initializers/assets.rb +11 -0
  141. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  142. data/spec/dummy/config/initializers/bootstrap_leather.rb +7 -0
  143. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  144. data/spec/dummy/config/initializers/devise.rb +265 -0
  145. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  146. data/spec/dummy/config/initializers/inflections.rb +16 -0
  147. data/spec/dummy/config/initializers/message_train.rb +10 -0
  148. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  149. data/spec/dummy/config/initializers/paperclip.rb +2 -0
  150. data/spec/dummy/config/initializers/rolify.rb +7 -0
  151. data/spec/dummy/config/initializers/session_store.rb +3 -0
  152. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  153. data/spec/dummy/config/locales/devise.en.yml +60 -0
  154. data/spec/dummy/config/locales/en.yml +23 -0
  155. data/spec/dummy/config/routes.rb +5 -0
  156. data/spec/dummy/config/secrets.yml +22 -0
  157. data/spec/dummy/config/unused/temporary_i18n_debugging.rb +12 -0
  158. data/spec/dummy/config.ru +4 -0
  159. data/spec/dummy/db/migrate/20150721140013_devise_create_users.rb +42 -0
  160. data/spec/dummy/db/migrate/20150721141009_rolify_create_roles.rb +19 -0
  161. data/spec/dummy/db/migrate/20150721141128_create_groups.rb +11 -0
  162. data/spec/dummy/db/migrate/20150721150307_add_display_name_column_to_users.rb +6 -0
  163. data/spec/dummy/db/migrate/20150724142846_create_message_train_conversations.night_train.rb +10 -0
  164. data/spec/dummy/db/migrate/20150724142847_create_message_train_messages.night_train.rb +15 -0
  165. data/spec/dummy/db/migrate/20150724142848_create_message_train_attachments.night_train.rb +11 -0
  166. data/spec/dummy/db/migrate/20150724142849_create_message_train_receipts.night_train.rb +17 -0
  167. data/spec/dummy/db/migrate/20150724142850_create_message_train_ignores.night_train.rb +12 -0
  168. data/spec/dummy/db/schema.rb +121 -0
  169. data/spec/dummy/db/seeds/conversations.seeds.rb +86 -0
  170. data/spec/dummy/db/seeds/development/conversations.seeds.rb +9 -0
  171. data/spec/dummy/db/seeds/groups.seeds.rb +9 -0
  172. data/spec/dummy/db/seeds/test/conversations.seeds.rb +9 -0
  173. data/spec/dummy/db/seeds/users.seeds.rb +15 -0
  174. data/spec/dummy/db/seeds.rb +0 -0
  175. data/spec/dummy/db/test.sqlite3 +0 -0
  176. data/spec/dummy/lib/assets/.keep +0 -0
  177. data/spec/dummy/log/.keep +0 -0
  178. data/spec/dummy/public/404.html +67 -0
  179. data/spec/dummy/public/422.html +67 -0
  180. data/spec/dummy/public/500.html +66 -0
  181. data/spec/dummy/public/capybara.html +193 -0
  182. data/spec/dummy/public/favicon.ico +0 -0
  183. data/spec/factories/attachment.rb +6 -0
  184. data/spec/factories/group.rb +20 -0
  185. data/spec/factories/message.rb +68 -0
  186. data/spec/factories/user.rb +8 -0
  187. data/spec/features/boxes_spec.rb +50 -0
  188. data/spec/features/conversations_spec.rb +36 -0
  189. data/spec/features/messages_spec.rb +53 -0
  190. data/spec/helpers/message_train/boxes_helper_spec.rb +44 -0
  191. data/spec/message_train_spec.rb +7 -0
  192. data/spec/models/group_spec.rb +23 -0
  193. data/spec/models/message_train/attachment_spec.rb +12 -0
  194. data/spec/models/message_train/box_spec.rb +195 -0
  195. data/spec/models/message_train/conversation_spec.rb +98 -0
  196. data/spec/models/message_train/ignore_spec.rb +27 -0
  197. data/spec/models/message_train/message_spec.rb +83 -0
  198. data/spec/models/message_train/receipt_spec.rb +73 -0
  199. data/spec/models/user_spec.rb +68 -0
  200. data/spec/rails_helper.rb +83 -0
  201. data/spec/spec_helper.rb +92 -0
  202. data/spec/support/controller_behaviors.rb +88 -0
  203. data/spec/support/controller_macros.rb +13 -0
  204. data/spec/support/conversations.rb +10 -0
  205. data/spec/support/factory_girl.rb +3 -0
  206. data/spec/support/feature_behaviors.rb +62 -0
  207. data/spec/support/groups.rb +3 -0
  208. data/spec/support/loaded_site.rb +5 -0
  209. data/spec/support/shared_connection.rb +13 -0
  210. data/spec/support/users.rb +6 -0
  211. data/spec/support/utilities.rb +8 -0
  212. data/vendor/assets/javascripts/bootstrap-tags.js +623 -0
  213. data/vendor/assets/stylesheets/bootstrap-tags.css +78 -0
  214. metadata +608 -0
@@ -0,0 +1,74 @@
1
+ module MessageTrain
2
+ class MessagesController < MessageTrain::ApplicationController
3
+ before_filter :load_message, only: [:show, :edit, :update]
4
+
5
+ # GET /box/in/messages/1
6
+ def show
7
+ respond_to do |format|
8
+ format.json { render :show }
9
+ end
10
+ end
11
+
12
+ # GET /box/in/messages/new
13
+ def new
14
+ @message = @box.new_message(conversation_id: params[:conversation_id])
15
+ end
16
+
17
+ # GET /box/in/messages/1/edit
18
+ def edit
19
+ unless @message.draft
20
+ raise ActiveRecord::RecordNotFound
21
+ end
22
+ end
23
+
24
+ # POST /box/in/messages
25
+ def create
26
+ @message = @box.send_message(message_params)
27
+ if @box.errors.all.empty?
28
+ if @message.draft
29
+ redirect_to message_train.box_path(:drafts), alert: :message_saved_as_draft.l
30
+ else
31
+ redirect_to message_train.box_path(:sent), notice: :message_sent.l
32
+ end
33
+ else
34
+ flash[:error] = @box.message
35
+ render :new
36
+ end
37
+ end
38
+
39
+ # PATCH/PUT /box/in/messages/1
40
+ def update
41
+ unless @message.draft
42
+ raise ActiveRecord::RecordNotFound
43
+ end
44
+ @box.update_message(@message, message_params)
45
+ if @box.errors.all.empty?
46
+ if @message.draft
47
+ redirect_to message_train.box_conversation_url(@box, @message.conversation), alert: :message_saved_as_draft.l
48
+ else
49
+ redirect_to message_train.box_path(:sent), notice: :message_sent.l
50
+ end
51
+ else
52
+ flash[:error] = @box.message
53
+ render :edit
54
+ end
55
+ end
56
+
57
+ private
58
+ def load_message
59
+ @message = @box.find_message(params[:id])
60
+ end
61
+
62
+ # Never trust parameters from the scary internet, only allow the white list through.
63
+ def message_params
64
+ params.require(:message).permit(
65
+ :conversation_id,
66
+ :subject,
67
+ :body,
68
+ :draft,
69
+ attachments: [:attachment],
70
+ recipients_to_save: MessageTrain.configuration.recipient_tables
71
+ )
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,36 @@
1
+ module MessageTrain
2
+ class ParticipantsController < MessageTrain::ApplicationController
3
+ before_filter :load_participants
4
+ before_filter :load_participant, only: :show
5
+
6
+ # GET /box/in/participants
7
+ def index
8
+ respond_to do |format|
9
+ format.json { render :index }
10
+ end
11
+ end
12
+
13
+ # GET /box/in/participants/1
14
+ def show
15
+ respond_to do |format|
16
+ format.json { render :show }
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def load_participants
23
+ current_participant = send(MessageTrain.configuration.current_user_method)
24
+ method = MessageTrain.configuration.address_book_method
25
+ if current_participant.respond_to? method
26
+ @participants = current_participant.send(method)
27
+ else
28
+ @participants = current_participant.class.all
29
+ end
30
+ end
31
+
32
+ def load_participant
33
+ @participant = @participants.find(params[:id])
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,4 @@
1
+ module MessageTrain
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,30 @@
1
+ module MessageTrain
2
+ module BoxesHelper
3
+ def box_nav_item(box)
4
+ text = box.title
5
+ link = message_train.box_path(box.division)
6
+ if box.unread_count > 0
7
+ text << ' '
8
+ text << badge(box.unread_count.to_s, 'info pull-right')
9
+ end
10
+ nav_item text, link
11
+ end
12
+ def box_list_item(box, html_options = {})
13
+ render partial: 'message_train/boxes/list_item', locals: { box: box, html_options: html_options }
14
+ end
15
+ def boxes_widget
16
+ boxes = send(MessageTrain.configuration.current_user_method).all_boxes
17
+ render partial: 'message_train/boxes/widget', locals: { boxes: boxes }
18
+ end
19
+ def boxes_dropdown_list
20
+ boxes = send(MessageTrain.configuration.current_user_method).all_boxes
21
+ render partial: 'message_train/boxes/dropdown_list', locals: { boxes: boxes }
22
+ end
23
+ def box_participant_name(participant)
24
+ participant.send(MessageTrain.configuration.name_columns[participant.class.table_name.to_sym])
25
+ end
26
+ def box_participant_slug(participant)
27
+ participant.send(MessageTrain.configuration.slug_columns[participant.class.table_name.to_sym])
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,73 @@
1
+ module MessageTrain
2
+ module ConversationsHelper
3
+ def conversation_senders(conversation)
4
+ conversation.messages.collect { |x| box_participant_name(x.sender) }.uniq.to_sentence
5
+ end
6
+
7
+ def conversation_class(box, conversation)
8
+ css_classes = []
9
+
10
+ if conversation.includes_unread_for?(@box.parent)
11
+ css_classes << 'unread'
12
+ else
13
+ css_classes << 'read'
14
+ end
15
+
16
+ if conversation.includes_drafts_by?(@box.parent)
17
+ css_classes << 'draft'
18
+ end
19
+
20
+ unless conversation.includes_undeleted_for?(box.parent)
21
+ css_classes << 'hide'
22
+ end
23
+
24
+ if box.division == :trash
25
+ unless conversation.includes_trashed_for?(box.parent)
26
+ css_classes << 'hide'
27
+ end
28
+ else
29
+ unless conversation.includes_untrashed_for?(box.parent)
30
+ css_classes << 'hide'
31
+ end
32
+ if box.division == :ignored
33
+ unless conversation.is_ignored?(box.parent)
34
+ css_classes << 'hide'
35
+ end
36
+ else
37
+ if conversation.is_ignored?(box.parent)
38
+ css_classes << 'hide'
39
+ end
40
+ end
41
+ end
42
+ css_classes.uniq.join(' ')
43
+ end
44
+
45
+ def conversation_trashed_toggle(conversation)
46
+ render partial: 'message_train/conversations/trashed_toggle', locals: { conversation: conversation }
47
+ end
48
+
49
+ def conversation_read_toggle(conversation)
50
+ render partial: 'message_train/conversations/read_toggle', locals: { conversation: conversation }
51
+ end
52
+
53
+ def conversation_ignored_toggle(conversation)
54
+ render partial: 'message_train/conversations/ignored_toggle', locals: { conversation: conversation }
55
+ end
56
+
57
+ def conversation_deleted_toggle(conversation)
58
+ render partial: 'message_train/conversations/deleted_toggle', locals: { conversation: conversation }
59
+ end
60
+
61
+ def conversation_toggle(conversation, icon, mark_to_set, method, title, options = {})
62
+ options[:remote] = true
63
+ options[:method] = method
64
+ options[:title] = title
65
+ render partial: 'message_train/conversations/toggle', locals: {
66
+ conversation: conversation,
67
+ icon: icon,
68
+ mark_to_set: mark_to_set,
69
+ options: options
70
+ }
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,58 @@
1
+ module MessageTrain
2
+ module MessagesHelper
3
+ def message_class(box, message)
4
+ css_classes = []
5
+
6
+ if message.is_unread_for?(@box.parent)
7
+ css_classes << 'unread panel-info'
8
+ else
9
+ css_classes << 'read'
10
+ end
11
+
12
+ if message.draft
13
+ css_classes << 'draft'
14
+ end
15
+
16
+ if box.division == :trash
17
+ unless message.is_trashed_for?(box.parent)
18
+ css_classes << 'hide'
19
+ end
20
+ else
21
+ unless message.is_untrashed_for?(box.parent)
22
+ css_classes << 'hide'
23
+ end
24
+ end
25
+ css_classes.join(' ')
26
+ end
27
+
28
+ def message_trashed_toggle(message)
29
+ render partial: 'message_train/messages/trashed_toggle', locals: { message: message }
30
+ end
31
+
32
+ def message_read_toggle(message)
33
+ render partial: 'message_train/messages/read_toggle', locals: { message: message }
34
+ end
35
+
36
+ def message_deleted_toggle(message)
37
+ render partial: 'message_train/messages/deleted_toggle', locals: { message: message }
38
+ end
39
+
40
+ def message_toggle(message, icon, mark_to_set, title, options = {})
41
+ options[:remote] = true
42
+ options[:id] = "mark_#{mark_to_set}_#{message.id}"
43
+ options[:class] = "mark-link"
44
+ options[:method] = :put
45
+ options[:title] = title
46
+ render partial: 'message_train/messages/toggle', locals: {
47
+ message: message,
48
+ icon: icon,
49
+ mark_to_set: mark_to_set,
50
+ options: options
51
+ }
52
+ end
53
+
54
+ def message_recipients(message)
55
+ message.recipients.collect { |x| box_participant_name(x) }.to_sentence
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,17 @@
1
+ module MessageTrain
2
+ class Attachment < ActiveRecord::Base
3
+ belongs_to :message
4
+ has_attached_file :attachment
5
+ validates_attachment_presence :attachment
6
+ validates_attachment_content_type :attachment, content_type: [
7
+ "application/pdf",
8
+ "application/vnd.ms-excel",
9
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
10
+ "application/msword",
11
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
12
+ "application/rtf",
13
+ "text/plain",
14
+ /^image\/.*$/
15
+ ]
16
+ end
17
+ end
@@ -0,0 +1,268 @@
1
+ module MessageTrain
2
+ class Box
3
+ include ActiveModel::Model
4
+ attr_accessor :parent, :division, :errors, :results
5
+ alias_method :id, :division
6
+
7
+ def initialize(parent, division)
8
+ @parent = parent
9
+ @division = division
10
+ @results = Results.new(self)
11
+ @errors = Errors.new(self)
12
+ end
13
+
14
+ def marks
15
+ { conversations: nil }
16
+ end
17
+
18
+ def to_param
19
+ division.to_s
20
+ end
21
+
22
+ def unread_count
23
+ conversations(unread: true).count
24
+ end
25
+
26
+ def conversations(options = {})
27
+ found = parent.conversations(division).with_undeleted_for(parent)
28
+ if options[:read] == false || options[:unread]
29
+ found = found.with_unread_for(parent)
30
+ end
31
+ if division == :trash
32
+ found = found.with_trashed_for(parent)
33
+ else
34
+ found = found.with_untrashed_for(parent)
35
+ if division == :drafts
36
+ found = found.with_drafts_by(parent)
37
+ else
38
+ found = found.with_ready_for(parent)
39
+ end
40
+ if division == :ignored
41
+ found = found.ignored(parent)
42
+ else
43
+ found = found.unignored(parent)
44
+ end
45
+ end
46
+ found
47
+ end
48
+
49
+ def find_conversation(id)
50
+ parent.all_conversations.find(id)
51
+ end
52
+
53
+ def find_message(id)
54
+ parent.all_messages.find(id)
55
+ end
56
+
57
+ def new_message(args = {})
58
+ if args[:conversation_id].nil?
59
+ message = MessageTrain::Message.new(args)
60
+ else
61
+ conversation = find_conversation(args[:conversation_id])
62
+ message = conversation.messages.build(args)
63
+ message.subject = "Re: #{conversation.subject}"
64
+ recipient_arrays = {}
65
+ conversation.default_recipients_for(parent).each do |recipient|
66
+ table_name = recipient.class.table_name
67
+ recipient_arrays[table_name] ||= []
68
+ recipient_arrays[table_name] << recipient.send(MessageTrain.configuration.slug_columns[table_name.to_sym])
69
+ end
70
+ recipient_arrays.each do |key, array|
71
+ message.recipients_to_save[key] = array.join(', ')
72
+ end
73
+ end
74
+ message
75
+ end
76
+
77
+ def send_message(attributes)
78
+ message_to_send = MessageTrain::Message.new attributes
79
+ message_to_send.sender = parent
80
+ unless message_to_send.save
81
+ errors.add(message_to_send, message_to_send.errors.full_messages.to_sentence)
82
+ end
83
+ message_to_send
84
+ end
85
+
86
+ def update_message(message_to_update, attributes)
87
+ attributes.delete(:sender)
88
+ if message_to_update.sender == parent
89
+ message_to_update.update(attributes)
90
+ unless message_to_update.errors.empty?
91
+ errors.add(message_to_update, message_to_update.errors.full_messages.to_sentence)
92
+ end
93
+ else
94
+ errors.add(message_to_update, :access_to_message_id_denied.l(id: message_to_update.id))
95
+ end
96
+ message_to_update.reload
97
+ end
98
+
99
+ def ignore(object)
100
+ case object.class.name
101
+ when 'Hash'
102
+ ignore object.values
103
+ when 'Array'
104
+ object.collect { |item| ignore(item) }.uniq == [true]
105
+ when 'String', 'Fixnum'
106
+ id = object.to_i
107
+ object = parent.all_conversations.find_by_id(id)
108
+ if object.nil?
109
+ errors.add(self, :class_id_not_found_in_box.l(class: 'Conversation', id: id.to_s))
110
+ else
111
+ ignore(object)
112
+ end
113
+ when 'MessageTrain::Conversation'
114
+ if authorize(object)
115
+ if object.set_ignored(parent)
116
+ results.add(object, :update_successful.l)
117
+ else
118
+ errors.add(object, object.errors.full_messages.to_sentence)
119
+ end
120
+ else
121
+ false
122
+ end
123
+ else
124
+ errors.add(self, :cannot_ignore_type.l(type: object.class.name))
125
+ end
126
+ end
127
+
128
+ def unignore(object)
129
+ case object.class.name
130
+ when 'Hash'
131
+ unignore object.values
132
+ when 'Array'
133
+ object.collect { |item| unignore(item) }.uniq == [true]
134
+ when 'String', 'Fixnum'
135
+ id = object.to_i
136
+ object = parent.all_conversations.find_by_id(id)
137
+ if object.nil?
138
+ errors.add(self, :class_id_not_found_in_box.l(class: 'Conversation', id: id.to_s))
139
+ else
140
+ unignore(object)
141
+ end
142
+ when 'MessageTrain::Conversation'
143
+ if authorize(object)
144
+ if object.set_unignored(parent)
145
+ results.add(object, :update_successful.l)
146
+ else
147
+ errors.add(object, object.errors.full_messages.to_sentence)
148
+ end
149
+ else
150
+ false
151
+ end
152
+ else
153
+ errors.add(self, :cannot_ignore_type.l(type: object.class.name))
154
+ end
155
+ end
156
+
157
+ def title
158
+ "box_title_#{division.to_s}".to_sym.l
159
+ end
160
+
161
+ def message
162
+ if !errors.all.empty?
163
+ errors.all.collect { |x| x[:message] }.uniq.to_sentence
164
+ elsif results.all.empty?
165
+ :nothing_to_do.l
166
+ else
167
+ results.all.collect { |x| x[:message] }.uniq.to_sentence
168
+ end
169
+ end
170
+
171
+ def mark(mark_to_set, objects)
172
+ objects.each do |key, object|
173
+ if object.present?
174
+ case object.class.name
175
+ when 'Hash'
176
+ mark(mark_to_set, { key => object.values } )
177
+ when 'Array'
178
+ object.collect { |item| mark(mark_to_set, key => item) }.uniq == [true]
179
+ when 'String', 'Fixnum'
180
+ id = object.to_i
181
+ object = parent.send("all_#{key}".to_sym).find_by_id(id)
182
+ if object.nil?
183
+ errors.add(self, :class_id_not_found_in_box.l(class: key.to_s.classify, id: id.to_s))
184
+ else
185
+ mark(mark_to_set, key => object)
186
+ end
187
+ when 'MessageTrain::Conversation', 'MessageTrain::Message'
188
+ if authorize(object)
189
+ if object.mark(mark_to_set, parent)
190
+ results.add(object, :update_successful.l)
191
+ else
192
+ errors.add(object, object.errors.full_messages.to_sentence)
193
+ end
194
+ else
195
+ false
196
+ end
197
+ else
198
+ errors.add(self, :cannot_mark_type.l(type: object.class.name))
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ def authorize(object)
205
+ case object.class.name
206
+ when 'MessageTrain::Conversation'
207
+ if object.includes_receipts_for? parent
208
+ true
209
+ else
210
+ errors.add(object, :access_to_conversation_id_denied.l(id: object.id))
211
+ end
212
+ when 'MessageTrain::Message'
213
+ if object.receipts.for(parent).any?
214
+ true
215
+ else
216
+ errors.add(object, :access_to_message_id_denied.l(id: object.id))
217
+ end
218
+ else
219
+ errors.add(object, :dont_know_how_to_mark_object.l(object: object.class.name))
220
+ end
221
+ end
222
+
223
+ class Results
224
+ attr_accessor :items, :box
225
+
226
+ def initialize(box)
227
+ @items = []
228
+ @box = box
229
+ end
230
+
231
+ def add(object, message)
232
+ item = {}
233
+ if object.is_a? MessageTrain::Box
234
+ item[:css_id] = 'box'
235
+ item[:path] = MessageTrain::Engine.routes.path_for({
236
+ controller: 'message_train/boxes',
237
+ action: :show,
238
+ division: object.division
239
+ })
240
+ elsif object.new_record?
241
+ item[:css_id] = "#{object.class.table_name.singularize}"
242
+ item[:path] = nil
243
+ else
244
+ item[:css_id] = "#{object.class.table_name.singularize}_#{object.id.to_s}"
245
+ item[:path] = MessageTrain::Engine.routes.path_for({
246
+ controller: object.class.table_name.gsub('message_train_', 'message_train/'),
247
+ action: :show,
248
+ box_division: box.division,
249
+ id: object.id
250
+ })
251
+ end
252
+ item[:message] = message
253
+ items << item
254
+ true
255
+ end
256
+
257
+ def all
258
+ items
259
+ end
260
+ end
261
+ class Errors < Results
262
+ def add(object, message)
263
+ super object, message
264
+ false
265
+ end
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,101 @@
1
+ module MessageTrain
2
+ class Conversation < ActiveRecord::Base
3
+ has_many :messages
4
+ has_many :ignores
5
+ has_many :receipts, through: :messages
6
+
7
+ # Scopes
8
+ default_scope { order('updated_at DESC') }
9
+ scope :ignored, ->(participant) { where('id IN (?)', ignored_ids_for(participant))}
10
+ scope :unignored, ->(participant) { where('NOT(id IN (?))', ignored_ids_for(participant))}
11
+ scope :filter_by_receipt_method_ids, ->(receipt_method, participant) {
12
+ all.collect { |x| x.receipts.send(receipt_method, participant).conversation_ids }.flatten.uniq
13
+ }
14
+ scope :filter_by_receipt_method, ->(receipt_method, participant) {
15
+ where('id IN (?)', filter_by_receipt_method_ids(receipt_method, participant))
16
+ }
17
+ scope :with_drafts_by, ->(participant) {
18
+ ids_with_drafts = all.collect { |x| x.messages.drafts.by(participant).conversation_ids }.flatten.uniq
19
+ where('id IN (?)', ids_with_drafts)
20
+ }
21
+ scope :with_ready_for, ->(participant) {
22
+ ids_with_ready = all.collect { |x| x.messages.ready.with_receipts_for(participant).conversation_ids }.flatten.uniq
23
+ where('id IN (?)', ids_with_ready)
24
+ }
25
+
26
+ def default_recipients_for(sender)
27
+ recipients = messages.with_receipts_for(sender)
28
+ .collect { |x| x.receipts.collect { |y| y.recipient } }
29
+ .flatten
30
+ .uniq
31
+ recipients.delete(sender)
32
+ recipients
33
+ end
34
+
35
+ def set_ignored(participant)
36
+ ignores.first_or_create!(participant: participant)
37
+ end
38
+
39
+ def set_unignored(participant)
40
+ ignores.where(participant: participant).destroy_all
41
+ end
42
+
43
+ def is_ignored?(participant)
44
+ if ignores.empty?
45
+ false
46
+ else
47
+ !ignores.find_all_by_participant(participant).empty?
48
+ end
49
+ end
50
+
51
+ def mark(mark, participant)
52
+ messages.mark(mark, participant)
53
+ end
54
+
55
+ def includes_drafts_by?(participant)
56
+ !messages.drafts_by(participant).empty?
57
+ end
58
+
59
+ def method_missing(method_sym, *arguments, &block)
60
+ # the first argument is a Symbol, so you need to_s it if you want to pattern match
61
+ if method_sym.to_s =~ /^includes_(.*_(by|to|for))\?$/
62
+ !receipts.send($1.to_sym, arguments.first).empty?
63
+ else
64
+ super
65
+ end
66
+ end
67
+
68
+ def self.method_missing(method_sym, *arguments, &block)
69
+ # the first argument is a Symbol, so you need to_s it if you want to pattern match
70
+ if method_sym.to_s =~ /^with_((.*)_(by|to|for))$/
71
+ self.filter_by_receipt_method($1.to_sym, arguments.first)
72
+ else
73
+ super
74
+ end
75
+ end
76
+
77
+ # It's important to know Object defines respond_to to take two parameters: the method to check, and whether to include private methods
78
+ # http://www.ruby-doc.org/core/classes/Object.html#M000333
79
+ def respond_to?(method_sym, include_private = false)
80
+ if method_sym.to_s =~ /^includes_((.*)_(by|to|for))\?$/
81
+ true
82
+ else
83
+ super
84
+ end
85
+ end
86
+
87
+ def self.respond_to?(method_sym, include_private = false)
88
+ if method_sym.to_s =~ /^(.*)_(by|to|for)$/
89
+ true
90
+ else
91
+ super
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def self.ignored_ids_for(participant)
98
+ MessageTrain::Ignore.find_all_by_participant(participant).conversation_ids
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,18 @@
1
+ module MessageTrain
2
+ class Ignore < ActiveRecord::Base
3
+ belongs_to :participant, polymorphic: true
4
+ belongs_to :conversation
5
+
6
+ validates_presence_of :conversation, :participant
7
+
8
+ scope :find_all_by_participant, ->(participant) { where('participant_type = ? AND participant_id = ?', participant.class.name, participant.id) }
9
+
10
+ def self.conversation_ids
11
+ all.collect { |y| y.conversation_id }
12
+ end
13
+
14
+ def self.conversations
15
+ MessageTrain::Conversation.where('id IN (?)', conversation_ids )
16
+ end
17
+ end
18
+ end