message_train 0.1.7 → 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.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +8 -0
  3. data/.travis.yml +7 -2
  4. data/Gemfile +3 -0
  5. data/README.rdoc +37 -1
  6. data/Rakefile +5 -6
  7. data/VERSION +1 -1
  8. data/app/assets/javascripts/message_train.js +33 -0
  9. data/app/assets/stylesheets/message_train.scss +26 -0
  10. data/app/controllers/concerns/message_train_support.rb +127 -0
  11. data/app/controllers/message_train/application_controller.rb +1 -60
  12. data/app/controllers/message_train/boxes_controller.rb +0 -4
  13. data/app/controllers/message_train/messages_controller.rb +12 -6
  14. data/app/controllers/message_train/participants_controller.rb +1 -1
  15. data/app/controllers/message_train/unsubscribes_controller.rb +59 -0
  16. data/app/helpers/message_train/application_helper.rb +26 -0
  17. data/app/helpers/message_train/attachments_helper.rb +19 -0
  18. data/app/helpers/message_train/boxes_helper.rb +16 -11
  19. data/app/helpers/message_train/collectives_helper.rb +48 -0
  20. data/app/helpers/message_train/conversations_helper.rb +24 -16
  21. data/app/helpers/message_train/messages_helper.rb +14 -12
  22. data/app/mailers/message_train/application_mailer.rb +8 -0
  23. data/app/mailers/message_train/previews/receipt_mailer_preview.rb +10 -0
  24. data/app/mailers/message_train/receipt_mailer.rb +17 -0
  25. data/app/models/message_train/attachment.rb +28 -10
  26. data/app/models/message_train/box.rb +114 -83
  27. data/app/models/message_train/conversation.rb +48 -39
  28. data/app/models/message_train/ignore.rb +2 -6
  29. data/app/models/message_train/message.rb +40 -24
  30. data/app/models/message_train/receipt.rb +20 -10
  31. data/app/models/message_train/unsubscribe.rb +7 -0
  32. data/app/views/layouts/mailer.html.haml +6 -0
  33. data/app/views/message_train/application/_attachment_fields.html.haml +7 -0
  34. data/app/views/message_train/application/_attachment_link.html.haml +4 -0
  35. data/app/views/message_train/application/_widget.html.haml +6 -0
  36. data/app/views/message_train/boxes/_dropdown_list.html.haml +1 -2
  37. data/app/views/message_train/boxes/_list_item.html.haml +2 -2
  38. data/app/views/message_train/boxes/_widget.html.haml +4 -1
  39. data/app/views/message_train/boxes/show.html.haml +12 -4
  40. data/app/views/message_train/collectives/_dropdown_list.html.haml +6 -0
  41. data/app/views/message_train/collectives/_list_item.html.haml +5 -0
  42. data/app/views/message_train/collectives/_widget.html.haml +7 -0
  43. data/app/views/message_train/conversations/_conversation.html.haml +22 -7
  44. data/app/views/message_train/conversations/_deleted_toggle.html.haml +1 -1
  45. data/app/views/message_train/conversations/_ignored_toggle.html.haml +3 -3
  46. data/app/views/message_train/conversations/_read_toggle.html.haml +3 -3
  47. data/app/views/message_train/conversations/_toggle.html.haml +4 -1
  48. data/app/views/message_train/conversations/_trashed_toggle.html.haml +3 -3
  49. data/app/views/message_train/conversations/show.html.haml +4 -3
  50. data/app/views/message_train/messages/_deleted_toggle.html.haml +1 -1
  51. data/app/views/message_train/messages/_form.html.haml +22 -7
  52. data/app/views/message_train/messages/_message.html.haml +14 -4
  53. data/app/views/message_train/messages/_read_toggle.html.haml +1 -1
  54. data/app/views/message_train/messages/_trashed_toggle.html.haml +1 -1
  55. data/app/views/message_train/messages/edit.html.haml +1 -1
  56. data/app/views/message_train/messages/new.html.haml +4 -1
  57. data/app/views/message_train/participants/_field.html.haml +1 -1
  58. data/app/views/message_train/participants/_prefilled_field.html.haml +4 -0
  59. data/app/views/message_train/receipt_mailer/notification_email.html.haml +13 -0
  60. data/app/views/message_train/unsubscribes/index.html.haml +10 -0
  61. data/config/environment.rb +1 -0
  62. data/config/locales/en.yml +49 -7
  63. data/config/routes.rb +10 -2
  64. data/db/migrate/20150901183458_add_received_through_to_message_train_receipts.rb +6 -0
  65. data/db/migrate/20151004184347_add_unique_index_to_receipts.rb +5 -0
  66. data/db/migrate/20151124000820_create_message_train_unsubscribes.rb +14 -0
  67. data/lib/generators/message_train/install/install_generator.rb +8 -2
  68. data/lib/generators/message_train/install/templates/initializer.rb +5 -1
  69. data/lib/message_train/configuration.rb +11 -1
  70. data/lib/message_train/engine.rb +1 -0
  71. data/lib/message_train/mixin.rb +206 -21
  72. data/message_train.gemspec +66 -13
  73. data/spec/controllers/message_train/boxes_controller_spec.rb +10 -3
  74. data/spec/controllers/message_train/concerns_spec.rb +40 -0
  75. data/spec/controllers/message_train/conversations_controller_spec.rb +3 -3
  76. data/spec/controllers/message_train/messages_controller_spec.rb +60 -27
  77. data/spec/controllers/message_train/participants_controller_spec.rb +41 -6
  78. data/spec/controllers/message_train/unsubscribes_controller_spec.rb +56 -0
  79. data/spec/dummy/app/assets/files/message_train/attachments/{1917-Boys_Race_Above-Wiki.jpg → image-sample.jpg} +0 -0
  80. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  81. data/spec/dummy/app/models/group.rb +16 -1
  82. data/spec/dummy/app/models/role.rb +22 -0
  83. data/spec/dummy/app/models/user.rb +1 -1
  84. data/spec/dummy/app/views/layouts/_top_navigation.html.haml +4 -2
  85. data/spec/dummy/app/views/layouts/application.html.haml +2 -3
  86. data/spec/dummy/app/views/pages/index.html.haml +4 -0
  87. data/spec/dummy/config/application.rb +6 -0
  88. data/spec/dummy/config/environments/development.rb +1 -0
  89. data/spec/dummy/config/environments/test.rb +1 -0
  90. data/spec/dummy/config/initializers/high_voltage.rb +3 -0
  91. data/spec/dummy/config/initializers/message_train.rb +6 -1
  92. data/spec/dummy/config/initializers/paperclip.rb +2 -2
  93. data/spec/dummy/config/routes.rb +2 -2
  94. data/spec/dummy/config/settings.yml +9 -0
  95. data/spec/dummy/db/migrate/{20150724142846_create_message_train_conversations.night_train.rb → 20150901183629_create_message_train_conversations.message_train.rb} +0 -0
  96. data/spec/dummy/db/migrate/{20150724142847_create_message_train_messages.night_train.rb → 20150901183630_create_message_train_messages.message_train.rb} +0 -0
  97. data/spec/dummy/db/migrate/{20150724142848_create_message_train_attachments.night_train.rb → 20150901183631_create_message_train_attachments.message_train.rb} +0 -0
  98. data/spec/dummy/db/migrate/{20150724142849_create_message_train_receipts.night_train.rb → 20150901183632_create_message_train_receipts.message_train.rb} +0 -0
  99. data/spec/dummy/db/migrate/{20150724142850_create_message_train_ignores.night_train.rb → 20150901183633_create_message_train_ignores.message_train.rb} +0 -0
  100. data/spec/dummy/db/migrate/20150901183634_add_received_through_to_message_train_receipts.message_train.rb +7 -0
  101. data/spec/dummy/db/migrate/20151004184519_add_unique_index_to_receipts.message_train.rb +6 -0
  102. data/spec/dummy/db/migrate/20151124001417_create_message_train_unsubscribes.message_train.rb +15 -0
  103. data/spec/dummy/db/schema.rb +24 -7
  104. data/spec/dummy/db/seeds/conversations.seeds.rb +92 -3
  105. data/spec/dummy/db/seeds/groups.seeds.rb +27 -0
  106. data/spec/dummy/db/seeds/test/attachments.seeds.rb +4 -0
  107. data/spec/dummy/db/seeds/unsubscribes.seeds.rb +12 -0
  108. data/spec/dummy/db/seeds/users.seeds.rb +27 -0
  109. data/spec/dummy/db/test.sqlite3 +0 -0
  110. data/spec/factories/group.rb +4 -4
  111. data/spec/factories/message.rb +10 -3
  112. data/spec/features/boxes_spec.rb +160 -33
  113. data/spec/features/conversations_spec.rb +11 -4
  114. data/spec/features/messages_spec.rb +20 -6
  115. data/spec/features/unsubscribes_spec.rb +38 -0
  116. data/spec/helpers/message_train/application_helper_spec.rb +60 -0
  117. data/spec/helpers/message_train/attachment_helper_spec.rb +35 -0
  118. data/spec/helpers/message_train/boxes_helper_spec.rb +11 -5
  119. data/spec/helpers/message_train/collectives_helper_spec.rb +76 -0
  120. data/spec/helpers/message_train/conversations_helper_spec.rb +295 -0
  121. data/spec/helpers/message_train/messages_helper_spec.rb +217 -0
  122. data/spec/models/group_spec.rb +112 -2
  123. data/spec/models/message_train/attachment_spec.rb +44 -1
  124. data/spec/models/message_train/box_spec.rb +306 -51
  125. data/spec/models/message_train/conversation_spec.rb +84 -6
  126. data/spec/models/message_train/ignore_spec.rb +0 -4
  127. data/spec/models/message_train/message_spec.rb +49 -12
  128. data/spec/models/message_train/receipt_spec.rb +44 -8
  129. data/spec/models/message_train/unsubscribe_spec.rb +16 -0
  130. data/spec/models/role_spec.rb +125 -0
  131. data/spec/models/user_spec.rb +155 -26
  132. data/spec/rails_helper.rb +8 -1
  133. data/spec/support/attachments.rb +4 -0
  134. data/spec/support/controller_behaviors.rb +28 -0
  135. data/spec/support/conversations.rb +13 -0
  136. data/spec/support/groups.rb +3 -0
  137. data/spec/support/loaded_site.rb +3 -0
  138. data/spec/support/messages.rb +23 -0
  139. data/spec/support/roles.rb +4 -0
  140. data/spec/support/users.rb +6 -0
  141. data/spec/support/wysihtml5_helper.rb +8 -0
  142. metadata +99 -12
  143. data/spec/dummy/app/assets/files/message_train/attachments/Haie_rci.svg +0 -1714
  144. data/spec/dummy/public/capybara.html +0 -193
@@ -3,40 +3,26 @@ module MessageTrain
3
3
  has_many :messages
4
4
  has_many :ignores
5
5
  has_many :receipts, through: :messages
6
+ has_many :attachments, through: :messages
6
7
 
7
8
  # Scopes
8
- default_scope { order('updated_at DESC') }
9
- scope :ignored, ->(participant) { where('id IN (?)', ignored_ids_for(participant))}
10
- scope :unignored, ->(participant) {
11
- ignored_ids = ignored_ids_for(participant)
12
- if ignored_ids.empty?
13
- all
14
- else
15
- where('NOT(id IN (?))', ignored_ids)
16
- end
17
- }
18
- scope :filter_by_receipt_method_ids, ->(receipt_method, participant) {
19
- all.collect { |x| x.receipts.send(receipt_method, participant).conversation_ids }.flatten.uniq
20
- }
21
- scope :filter_by_receipt_method, ->(receipt_method, participant) {
22
- where('id IN (?)', filter_by_receipt_method_ids(receipt_method, participant))
23
- }
24
- scope :with_drafts_by, ->(participant) {
25
- ids_with_drafts = all.collect { |x| x.messages.drafts.by(participant).conversation_ids }.flatten.uniq
26
- where('id IN (?)', ids_with_drafts)
27
- }
28
- scope :with_ready_for, ->(participant) {
29
- ids_with_ready = all.collect { |x| x.messages.ready.with_receipts_for(participant).conversation_ids }.flatten.uniq
30
- where('id IN (?)', ids_with_ready)
31
- }
9
+ default_scope { order(updated_at: :desc) }
10
+ scope :ignored, ->(participant) { where(id: ignored_ids_for(participant))}
11
+ scope :unignored, ->(participant) { where.not(id: ignored_ids_for(participant)) }
12
+ scope :with_drafts_by, ->(participant) { joins(:messages).where(message_train_messages: { id: messages.drafts.with_receipts_by(participant) }) }
13
+ scope :with_ready_for, ->(participant) { joins(:messages).where(message_train_messages: { id: messages.ready.with_receipts_for(participant) }) }
14
+ scope :with_messages_for, ->(participant) { joins(:messages).where(message_train_messages: { id: messages.with_receipts_for(participant) }) }
15
+ scope :with_messages_through, ->(participant) { joins(:messages).where(message_train_messages: { id: messages.with_receipts_through(participant) }) }
32
16
 
33
17
  def default_recipients_for(sender)
34
- recipients = messages.with_receipts_for(sender)
35
- .collect { |x| x.receipts.collect { |y| y.recipient } }
36
- .flatten
37
- .uniq
38
- recipients.delete(sender)
39
- recipients
18
+ default_recipients = []
19
+ messages.with_receipts_for(sender).each do |conversation|
20
+ conversation.receipts.each do |receipt|
21
+ default_recipients << receipt.recipient
22
+ end
23
+ end
24
+ default_recipients.delete(sender)
25
+ default_recipients.flatten.uniq
40
26
  end
41
27
 
42
28
  def set_ignored(participant)
@@ -56,17 +42,30 @@ module MessageTrain
56
42
  end
57
43
 
58
44
  def mark(mark, participant)
45
+ # # This fixed a bug that only Travis seemed to pick up, but it's code smell.
46
+ # if messages.nil?
47
+ # return false
48
+ # end
59
49
  messages.mark(mark, participant)
60
50
  end
61
51
 
62
- def includes_drafts_by?(participant)
63
- !messages.drafts_by(participant).empty?
52
+ def self.messages
53
+ MessageTrain::Message.joins(:conversation).where(conversation: where(nil))
64
54
  end
65
55
 
66
56
  def method_missing(method_sym, *arguments, &block)
67
57
  # the first argument is a Symbol, so you need to_s it if you want to pattern match
68
- if method_sym.to_s =~ /^includes_(.*_(by|to|for))\?$/
69
- !receipts.send($1.to_sym, arguments.first).empty?
58
+ if method_sym.to_s =~ /^includes_((.*)_(by|to|for|through))\?$/
59
+ case $2
60
+ when 'ready', 'drafts'
61
+ if $3 == 'by'
62
+ !messages.send($2).by(arguments.first).empty?
63
+ else
64
+ !messages.send($2).receipts.send("receipts_#{$3}".to_sym, arguments.first).empty?
65
+ end
66
+ else
67
+ !receipts.send($1.to_sym, arguments.first).empty?
68
+ end
70
69
  else
71
70
  super
72
71
  end
@@ -74,8 +73,15 @@ module MessageTrain
74
73
 
75
74
  def self.method_missing(method_sym, *arguments, &block)
76
75
  # the first argument is a Symbol, so you need to_s it if you want to pattern match
77
- if method_sym.to_s =~ /^with_((.*)_(by|to|for))$/
78
- self.filter_by_receipt_method($1.to_sym, arguments.first)
76
+ if method_sym.to_s =~ /^with_((.*)_(by|to|for|through))$/
77
+ case $2
78
+ when 'ready', 'drafts'
79
+ self.messages.send($2).filter_by_receipt_method("receipts_#{$3}".to_sym, arguments.first).conversations
80
+ when 'messages'
81
+ self.messages.filter_by_receipt_method("receipts_#{$3}".to_sym, arguments.first).conversations
82
+ else
83
+ self.filter_by_receipt_method($1.to_sym, arguments.first)
84
+ end
79
85
  else
80
86
  super
81
87
  end
@@ -84,7 +90,7 @@ module MessageTrain
84
90
  # It's important to know Object defines respond_to to take two parameters: the method to check, and whether to include private methods
85
91
  # http://www.ruby-doc.org/core/classes/Object.html#M000333
86
92
  def respond_to?(method_sym, include_private = false)
87
- if method_sym.to_s =~ /^includes_((.*)_(by|to|for))\?$/
93
+ if method_sym.to_s =~ /^includes_((.*)_(by|to|for|through))\?$/
88
94
  true
89
95
  else
90
96
  super
@@ -92,7 +98,7 @@ module MessageTrain
92
98
  end
93
99
 
94
100
  def self.respond_to?(method_sym, include_private = false)
95
- if method_sym.to_s =~ /^(.*)_(by|to|for)$/
101
+ if method_sym.to_s =~ /^with_(.*)_(by|to|for|through)$/
96
102
  true
97
103
  else
98
104
  super
@@ -100,9 +106,12 @@ module MessageTrain
100
106
  end
101
107
 
102
108
  private
109
+ scope :filter_by_receipt_method, ->(receipt_method, participant) {
110
+ where(id: where(nil).messages.receipts.send(receipt_method, participant).conversation_ids)
111
+ }
103
112
 
104
113
  def self.ignored_ids_for(participant)
105
- MessageTrain::Ignore.find_all_by_participant(participant).conversation_ids
114
+ MessageTrain::Ignore.find_all_by_participant(participant).pluck(:conversation_id)
106
115
  end
107
116
  end
108
117
  end
@@ -5,14 +5,10 @@ module MessageTrain
5
5
 
6
6
  validates_presence_of :conversation, :participant
7
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
8
+ scope :find_all_by_participant, ->(participant) { where(participant: participant) }
13
9
 
14
10
  def self.conversations
15
- MessageTrain::Conversation.where('id IN (?)', conversation_ids )
11
+ MessageTrain::Conversation.joins(:ignores).where(message_train_ignores: { id: where(nil) })
16
12
  end
17
13
  end
18
14
  end
@@ -18,20 +18,14 @@ module MessageTrain
18
18
  after_save :generate_receipts_or_set_draft
19
19
  after_save :set_conversation_subject_if_alone
20
20
 
21
+ # Nested Attributes
22
+ accepts_nested_attributes_for :attachments, reject_if: :all_blank, allow_destroy: true
23
+
21
24
  # Scopes
22
- default_scope { order('updated_at DESC') }
23
- scope :filter_by_receipt_method_ids, ->(receipt_method, participant) {
24
- all.collect { |x| x.receipts.send(receipt_method, participant).message_ids }.flatten
25
- }
26
- scope :filter_by_receipt_method, ->(receipt_method, participant) {
27
- where('id IN (?)', filter_by_receipt_method_ids(receipt_method, participant))
28
- }
29
- scope :filter_out_by_receipt_method, ->(receipt_method, participant) {
30
- where('NOT(id IN (?))', filter_by_receipt_method_ids(receipt_method, participant))
31
- }
32
- scope :ready, -> { where('draft = ?', false) }
33
- scope :drafts, -> { where('draft = ?', true) }
34
- scope :by, ->(participant) { where('sender_type = ? AND sender_id = ?', participant.class.name, participant.id) }
25
+ default_scope { order(updated_at: :desc) }
26
+ scope :ready, -> { where(draft: false) }
27
+ scope :drafts, -> { where(draft: true) }
28
+ scope :by, ->(participant) { where(sender: participant) }
35
29
  scope :drafts_by, ->(participant) { drafts.by(participant) }
36
30
 
37
31
  def mark(mark_to_set, participant)
@@ -42,26 +36,34 @@ module MessageTrain
42
36
  end
43
37
 
44
38
  def self.mark(mark_to_set, participant)
45
- all.each do |message|
39
+ where(nil).each do |message|
46
40
  message.mark(mark_to_set, participant)
47
41
  end
48
42
  end
49
43
 
50
44
  def recipients
51
- receipts.recipient_receipt.collect { |x| x.recipient }
45
+ recips = []
46
+ receipts.recipient_receipt.each do |message|
47
+ recips << message.received_through
48
+ end
49
+ recips.uniq
52
50
  end
53
51
 
54
52
  def self.conversation_ids
55
- all.collect { |y| y.conversation_id }
53
+ pluck(:conversation_id)
54
+ end
55
+
56
+ def self.receipts
57
+ MessageTrain::Receipt.joins(:message).where(message_train_messages: { id: where(nil) })
56
58
  end
57
59
 
58
60
  def self.conversations
59
- MessageTrain::Conversation.where('id IN (?)', conversation_ids )
61
+ MessageTrain::Conversation.joins(:messages).where(message_train_messages: { id: where(nil) })
60
62
  end
61
63
 
62
64
  def method_missing(method_sym, *arguments, &block)
63
65
  # the first argument is a Symbol, so you need to_s it if you want to pattern match
64
- if method_sym.to_s =~ /^is_((.*)_(by|to|for))\?$/
66
+ if method_sym.to_s =~ /^is_((.*)_(by|to|for|through))\?$/
65
67
  !receipts.send($1.to_sym, arguments.first).empty?
66
68
  elsif method_sym.to_s =~ /^mark_(.*)_for$/
67
69
  receipts.for(arguments.first).first.mark($1.to_sym)
@@ -72,7 +74,7 @@ module MessageTrain
72
74
 
73
75
  def self.method_missing(method_sym, *arguments, &block)
74
76
  # the first argument is a Symbol, so you need to_s it if you want to pattern match
75
- if method_sym.to_s =~ /^with_(.*_(by|to|for))$/
77
+ if method_sym.to_s =~ /^with_(.*_(by|to|for|through))$/
76
78
  filter_by_receipt_method($1.to_sym, arguments.first)
77
79
  else
78
80
  super
@@ -82,7 +84,7 @@ module MessageTrain
82
84
  # It's important to know Object defines respond_to to take two parameters: the method to check, and whether to include private methods
83
85
  # http://www.ruby-doc.org/core/classes/Object.html#M000333
84
86
  def respond_to?(method_sym, include_private = false)
85
- if method_sym.to_s =~ /^is_.*_(by|to|for)\?$/ || method_sym.to_s =~ /^mark_.*_for\?$/
87
+ if method_sym.to_s =~ /^is_.*_(by|to|for|through)\?$/ || method_sym.to_s =~ /^mark_.*_for\?$/
86
88
  true
87
89
  else
88
90
  super
@@ -90,7 +92,7 @@ module MessageTrain
90
92
  end
91
93
 
92
94
  def self.respond_to?(method_sym, include_private = false)
93
- if method_sym.to_s =~ /^.*_(by|to|for)$/
95
+ if method_sym.to_s =~ /^.*_(by|to|for|through)$/
94
96
  true
95
97
  else
96
98
  super
@@ -98,6 +100,11 @@ module MessageTrain
98
100
  end
99
101
 
100
102
  private
103
+
104
+ scope :filter_by_receipt_method, ->(receipt_method, participant) {
105
+ where(id: where(nil).receipts.send(receipt_method, participant).message_ids)
106
+ }
107
+
101
108
  def create_conversation_if_blank
102
109
  if conversation.nil?
103
110
  self.conversation = Conversation.create(subject: subject)
@@ -105,7 +112,7 @@ module MessageTrain
105
112
  end
106
113
 
107
114
  def generate_sender_receipt
108
- receipts.first_or_create!(recipient_type: sender.class.name, recipient_id: sender.id, sender: true)
115
+ receipts.first_or_create!(recipient: sender, received_through: sender, sender: true)
109
116
  end
110
117
 
111
118
  def generate_receipts_or_set_draft
@@ -119,8 +126,17 @@ module MessageTrain
119
126
  slug_column = MessageTrain.configuration.slug_columns[table.to_sym] || :slug
120
127
  if model.exists?(slug_column => slug)
121
128
  recipient = model.find_by(slug_column => slug)
122
- unless conversation.is_ignored?(recipient)
123
- receipts.create!(recipient_type: model_name, recipient_id: recipient.id)
129
+ end_recipient_method = MessageTrain.configuration.valid_recipients_methods[table.to_sym]
130
+ if end_recipient_method.nil?
131
+ unless conversation.is_ignored?(recipient)
132
+ receipts.create!(recipient: recipient, received_through: recipient)
133
+ end
134
+ else
135
+ recipient.send(end_recipient_method).uniq.each do |end_recipient|
136
+ unless conversation.is_ignored?(end_recipient) || end_recipient == sender
137
+ receipts.create!(recipient: end_recipient, received_through: recipient)
138
+ end
139
+ end
124
140
  end
125
141
  else
126
142
  errors.add :recipients_to_save, :name_not_found.l(name: slug)
@@ -1,19 +1,23 @@
1
1
  module MessageTrain
2
2
  class Receipt < ActiveRecord::Base
3
3
  belongs_to :recipient, polymorphic: true
4
+ belongs_to :received_through, polymorphic: true
4
5
  belongs_to :message
5
6
  validates_presence_of :recipient, :message
6
7
 
7
- default_scope { order("created_at DESC") }
8
+ default_scope { order(updated_at: :desc) }
8
9
  scope :sender_receipt, -> { where('sender = ?', true) }
9
10
  scope :recipient_receipt, -> { where('sender = ?', false) }
10
11
  scope :by, ->(sender) { sender_receipt.for(sender) }
11
12
  scope :for, ->(recipient) { where('recipient_type = ? AND recipient_id = ?', recipient.class.name, recipient.id) }
12
13
  scope :to, ->(recipient) { recipient_receipt.for(recipient) }
14
+ scope :through, ->(received_through) { where('received_through_type = ? AND received_through_id = ?', received_through.class.name, received_through.id) }
13
15
  scope :trashed, ->(setting = true) { where('marked_trash = ?', setting) }
14
16
  scope :read, ->(setting = true) { where('marked_read = ?', setting) }
15
17
  scope :deleted, ->(setting = true) { where('marked_deleted = ?', setting) }
16
18
 
19
+ after_create :notify
20
+
17
21
  def mark(mark_to_set)
18
22
  if mark_to_set.to_s =~ /^un/
19
23
  setting = false
@@ -27,29 +31,27 @@ module MessageTrain
27
31
  end
28
32
 
29
33
  def self.message_ids
30
- all.collect { |y| y.message_id }
34
+ pluck(:message_id)
31
35
  end
32
36
 
33
37
  def self.messages
34
- MessageTrain::Message.where('id IN (?)', message_ids )
38
+ MessageTrain::Message.joins(:receipts).where(message_train_receipts: { id: where(nil) })
35
39
  end
36
40
 
37
41
  def self.conversation_ids
38
- all.collect { |y| y.message.conversation_id }
42
+ messages.conversation_ids
39
43
  end
40
44
 
41
45
  def self.conversations
42
- MessageTrain::Conversation.where('id IN (?)', conversation_ids )
46
+ MessageTrain::Conversation.joins(:receipts).where(message_train_receipts: { id: where(nil) })
43
47
  end
44
48
 
45
49
  def self.method_missing(method_sym, *arguments, &block)
46
50
  # the first argument is a Symbol, so you need to_s it if you want to pattern match
47
- if method_sym.to_s =~ /^receipts_(by|to|for)$/
51
+ if method_sym.to_s =~ /^receipts_(by|to|for|through)$/
48
52
  send($1.to_sym, arguments.first)
49
- elsif method_sym.to_s =~ /^(.*)_(by|to|for)$/
53
+ elsif method_sym.to_s =~ /^(.*)_(by|to|for|through)$/
50
54
  send($1.to_sym).send($2.to_sym, arguments.first)
51
- elsif method_sym.to_s =~ /^mark_(.*)$/
52
- mark($1.to_sym)
53
55
  elsif method_sym.to_s =~ /^un(.*)$/
54
56
  send($1.to_sym, false)
55
57
  else
@@ -60,11 +62,19 @@ module MessageTrain
60
62
  # It's important to know Object defines respond_to to take two parameters: the method to check, and whether to include private methods
61
63
  # http://www.ruby-doc.org/core/classes/Object.html#M000333
62
64
  def self.respond_to?(method_sym, include_private = false)
63
- if method_sym.to_s =~ /^(.*)_(by|to|for)$/ || method_sym.to_s =~ /^(un|mark_)(.*)$/
65
+ if method_sym.to_s =~ /^(.*)_(by|to|for|through)$/ || method_sym.to_s =~ /^un(.*)$/
64
66
  true
65
67
  else
66
68
  super
67
69
  end
68
70
  end
71
+
72
+ private
73
+
74
+ def notify
75
+ unless sender? or recipient.unsubscribed_from?(received_through)
76
+ ReceiptMailer.notification_email(self).deliver_later
77
+ end
78
+ end
69
79
  end
70
80
  end
@@ -0,0 +1,7 @@
1
+ module MessageTrain
2
+ class Unsubscribe < ActiveRecord::Base
3
+ belongs_to :recipient, polymorphic: true
4
+ belongs_to :from, polymorphic: true
5
+ validates_presence_of :recipient, :from
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ !!!
2
+ %html{ lang: "en" }
3
+ %head
4
+ %body
5
+ #wrapper
6
+ =yield
@@ -0,0 +1,7 @@
1
+ .nested-fields.panel.panel-default
2
+ .panel-body
3
+ = link_to_remove_association '&times;'.html_safe, f, title: :remove_attachment.l, class: 'close', data: { confirm: :are_you_sure.l }
4
+ - if f.object.attachment.exists?
5
+ .attachment
6
+ = attachment_link(f.object)
7
+ = f.file_field :attachment
@@ -0,0 +1,4 @@
1
+ - if attachment.image?
2
+ = link_to attachment_icon(attachment), '#', class: 'thumbnail', data: { toggle: 'modal', target: '#attachment_preview', src: attachment.attachment.url(:large), original: attachment.attachment.url, text: :click_for_original.l }
3
+ - else
4
+ = link_to attachment_icon(attachment), attachment.attachment.url, class: 'thumbnail'
@@ -0,0 +1,6 @@
1
+ - if @collective.nil?
2
+ - add_widget do
3
+ = boxes_widget @box_user
4
+ - else
5
+ - add_widget do
6
+ = collective_boxes_widget @collective, @box_user
@@ -1,5 +1,4 @@
1
1
  - unless boxes.empty?
2
2
  = dropdown_nav_item "#{:box_title_in.l} #{badge boxes.first.unread_count.to_s}".html_safe, '#' do
3
3
  - boxes.each do |box|
4
- = box_nav_item box
5
- = nav_item :compose.l, message_train.new_box_message_path(:in)
4
+ = box_nav_item box
@@ -1,5 +1,5 @@
1
1
  - if box.present?
2
2
  %li{ html_options }
3
3
  = link_to box.title, message_train.box_path(box.division)
4
- - if box.unread_count > 0
5
- = badge box.unread_count.to_s
4
+ - if unread_count > 0
5
+ = badge unread_count.to_s
@@ -3,4 +3,7 @@
3
3
  .text-center= link_to :compose.l, message_train.new_box_message_path(:in), class: 'btn btn-lg btn-primary btn-compose'
4
4
  %ul.list-group
5
5
  - boxes.each do |box|
6
- = box_list_item box, class: 'list-group-item'
6
+ - if @collective.nil?
7
+ = box_list_item box, class: 'list-group-item'
8
+ - else
9
+ = collective_list_item box, class: 'list-group-item'
@@ -1,5 +1,10 @@
1
- - add_title @box.title
2
- = form_tag message_train.box_path(@box.division), method: :put, remote: true, id: 'box', data: { type: :json } do
1
+ - if @collective.nil?
2
+ - add_title @box.title
3
+ - marking_path = message_train.box_path(@box.division)
4
+ - else
5
+ - add_title collective_name(@collective) + ' ' + @box.title
6
+ - marking_path = message_train.collective_box_path(@collective.path_part, @box.division)
7
+ = form_tag marking_path, method: :put, remote: true, id: 'box', data: { type: :json } do
3
8
  - unless @conversations.empty?
4
9
  #box-actions
5
10
  .btn-group.check-all
@@ -39,9 +44,12 @@
39
44
  - else
40
45
  %li
41
46
  = link_to :mark_as_name.l(name: :ignored.l), '#0', class: 'mark', id: 'mark-ignored', data: { mark: 'ignore'}
42
- = link_to :compose.l, message_train.new_box_message_path(@box.division), class: 'btn btn-primary'
47
+ - if @collective.nil?
48
+ = link_to :compose.l, message_train.new_box_message_path(@box.division), class: 'btn btn-primary compose'
49
+ - elsif @collective.allows_sending_by? @box_user
50
+ = link_to :compose_to_collective.l(collective: collective_name(@collective)), message_train.new_collective_box_message_path(@collective.path_part, @box.division), class: 'btn btn-primary compose'
43
51
  %span#spinner.hide= icon 'refresh spinning'
44
52
  = hidden_field_tag :mark_to_set
45
53
  %table#message_train_conversations.table.table-condensed
46
54
  = render @conversations
47
- = paginate @conversations
55
+ = paginate @conversations
@@ -0,0 +1,6 @@
1
+ - unless collective_boxes.empty?
2
+ - collective_boxes.each do |key, boxes|
3
+ - if show[key]
4
+ = dropdown_nav_item "#{key.to_s.humanize} #{badge total_unread_count[key].to_s}".html_safe, '#' do
5
+ - boxes.each do |box|
6
+ = collective_nav_item box, box_user
@@ -0,0 +1,5 @@
1
+ - if box.present?
2
+ %li{ html_options }
3
+ = link_to box.title, message_train.collective_box_path(box.parent.path_part, box.division)
4
+ - if unread_count > 0
5
+ = badge unread_count.to_s
@@ -0,0 +1,7 @@
1
+ - unless collective.boxes_for_participant(box_user).empty?
2
+ %h3= :collective_messages.l(collective: collective_name(collective))
3
+ - if collective.allows_sending_by?(box_user)
4
+ .text-center= link_to :compose_to_collective.l(collective: collective_name(collective)), message_train.new_collective_box_message_path(collective.path_part, :sent), class: 'btn btn-lg btn-primary btn-compose'
5
+ %ul.list-group
6
+ - collective.boxes_for_participant(box_user).each do |box|
7
+ = collective_list_item box, class: 'list-group-item'
@@ -1,9 +1,24 @@
1
1
  %tr.message_train_conversation{ class: conversation_class(@box, conversation), id: 'message_train_conversation_' + conversation.id.to_s }
2
2
  %td.col-xs-1= check_box_tag "objects[conversations][#{conversation.id.to_s}]", conversation.id
3
- %td.col-xs-2= link_to conversation_senders(conversation), message_train.box_conversation_path(@box.division, conversation)
4
- %td.col-xs-8= link_to conversation.subject, message_train.box_conversation_path(@box.division, conversation)
5
- %td.col-xs-1.conversation-actions
6
- = conversation_read_toggle conversation
7
- = conversation_trashed_toggle conversation
8
- - if @box.division == :trash
9
- = conversation_deleted_toggle conversation
3
+ - if @collective.nil?
4
+ %td.col-xs-2= link_to conversation_senders(conversation), message_train.box_conversation_path(@box.division, conversation)
5
+ %td.col-xs-8= link_to conversation.subject, message_train.box_conversation_path(@box.division, conversation)
6
+ %td
7
+ - if conversation.attachments.any?
8
+ = icon 'paperclip'
9
+ %td.col-xs-1.conversation-actions
10
+ = conversation_trashed_toggle conversation
11
+ - if @box.division == :trash
12
+ = conversation_deleted_toggle conversation
13
+ %td.col-xs-1.date-column= fuzzy_date(conversation.updated_at)
14
+ - else
15
+ %td.col-xs-2= link_to conversation_senders(conversation), message_train.collective_box_conversation_path(@collective.path_part, @box.division, conversation)
16
+ %td.col-xs-8= link_to conversation.subject, message_train.collective_box_conversation_path(@collective.path_part, @box.division, conversation)
17
+ %td
18
+ - if conversation.attachments.any?
19
+ = icon 'paperclip'
20
+ %td.col-xs-1.conversation-actions
21
+ = conversation_trashed_toggle conversation, @collective
22
+ - if @box.division == :trash
23
+ = conversation_deleted_toggle conversation, @collective
24
+ %td.col-xs-1.date-column= fuzzy_date(conversation.updated_at)
@@ -1 +1 @@
1
- = conversation_toggle conversation, 'remove', :deleted, :put, :mark_as_name.l(name: :deleted.l), data: { confirm: :delete_forever_this_cannot_be_undone.l }
1
+ = conversation_toggle conversation, 'remove', :deleted, :put, :mark_as_name.l(name: :deleted.l), data: { confirm: :delete_forever_this_cannot_be_undone.l }, collective: collective
@@ -1,4 +1,4 @@
1
- - if conversation.is_ignored?(@box.parent)
2
- = conversation_toggle conversation, 'volume-up', :unignore, :delete, :mark_as_name.l(name: :unignored.l)
1
+ - if conversation.is_ignored?(@box_user)
2
+ = conversation_toggle conversation, 'volume-up', :unignore, :delete, :mark_as_name.l(name: :unignored.l), collective: collective
3
3
  - else
4
- = conversation_toggle conversation, 'volume-off', :ignore, :delete, :mark_as_name.l(name: :ignored.l), data: { confirm: :are_you_sure.l}
4
+ = conversation_toggle conversation, 'volume-off', :ignore, :delete, :mark_as_name.l(name: :ignored.l), data: { confirm: :are_you_sure.l}, collective: collective
@@ -1,4 +1,4 @@
1
- - if conversation.includes_unread_for?(@box.parent)
2
- = conversation_toggle conversation, 'eye-open', :read, :put, :mark_as_name.l(name: :read.l)
1
+ - if conversation.includes_unread_for?(@box_user)
2
+ = conversation_toggle conversation, 'eye-open', :read, :put, :mark_as_name.l(name: :read.l), collective: collective
3
3
  - else
4
- = conversation_toggle conversation, 'eye-close', :unread, :put, :mark_as_name.l(name: :unread.l)
4
+ = conversation_toggle conversation, 'eye-close', :unread, :put, :mark_as_name.l(name: :unread.l), collective: collective
@@ -1 +1,4 @@
1
- = icon_link_to icon, '', message_train.box_path(@box.division, objects: { 'conversations' => {conversation.id.to_s => conversation.id.to_s}}, mark_to_set: mark_to_set, format: :json), options
1
+ - if options[:collective].nil?
2
+ = icon_link_to icon, '', message_train.box_path(@box.division, objects: { 'conversations' => {conversation.id.to_s => conversation.id.to_s}}, mark_to_set: mark_to_set, format: :json), options
3
+ - else
4
+ = icon_link_to icon, '', message_train.collective_box_path(options[:collective].path_part, @box.division, objects: { 'conversations' => {conversation.id.to_s => conversation.id.to_s}}, mark_to_set: mark_to_set, format: :json), options
@@ -1,4 +1,4 @@
1
- - if conversation.includes_untrashed_for?(@box.parent)
2
- = conversation_toggle conversation, 'trash', :trash, :put, :mark_as_name.l(name: :trashed.l), data: { confirm: :are_you_sure.l}
1
+ - if conversation.includes_untrashed_for?(@box_user)
2
+ = conversation_toggle conversation, 'trash', :trash, :put, :mark_as_name.l(name: :trashed.l), data: { confirm: :are_you_sure.l}, collective: collective
3
3
  - else
4
- = conversation_toggle conversation, 'inbox', :untrash, :put, :mark_as_name.l(name: :untrashed.l)
4
+ = conversation_toggle conversation, 'inbox', :untrash, :put, :mark_as_name.l(name: :untrashed.l), collective: collective
@@ -1,6 +1,5 @@
1
1
  - add_title @conversation.subject
2
- - add_subtitle :started_at_time.l(time: @conversation.created_at)
3
-
2
+ - add_subtitle :updated_at_time.l(time: @conversation.updated_at)
4
3
  #box
5
4
  #conversation-actions.pull-right
6
5
  .btn.btn-default.btn-lg= conversation_ignored_toggle(@conversation)
@@ -8,4 +7,6 @@
8
7
  - unless @messages.empty?
9
8
  #message_train_messages.messages.panel-group{aria: { multiselectable: 'true' }, role: "tablist"}
10
9
  = render @messages
11
- = paginate @messages
10
+ = paginate @messages
11
+ = modal 'attachment_preview', :attachment_preview.l do
12
+ #image_placeholder
@@ -1 +1 @@
1
- = message_toggle message, 'remove', :deleted, :mark_as_name.l(name: :deleted.l)
1
+ = message_toggle message, 'remove', :deleted, :mark_as_name.l(name: :deleted.l), data: { confirm: :delete_forever_this_cannot_be_undone.l }
@@ -1,8 +1,23 @@
1
- - resource = message.new_record? ? message_train.box_messages_path(@box) : message_train.box_message_path(@box, message)
1
+ - if @collective.nil?
2
+ - resource = message.new_record? ? message_train.box_messages_path(@box) : message_train.box_message_path(@box, message)
3
+ - else
4
+ - resource = message.new_record? ? message_train.collective_box_messages_path(@collective.path_part, @box) : message_train.collective_box_message_path(@collective.path_part, @box, message)
2
5
  = bootstrap_form_for message, url: resource do |f|
3
- - MessageTrain.configuration.recipient_tables.each do |table_sym, class_name|
4
- = render partial: 'message_train/participants/field', locals: { message: message, field_name: table_sym }
5
- = f.text_field :subject
6
- = f.text_area :body
7
- = f.check_box :draft
8
- = f.submit :send.l
6
+ .row
7
+ .col-md-8
8
+ - if @collective.nil?
9
+ = render partial: 'message_train/participants/field', locals: { message: message, field_name: :users }
10
+ - else
11
+ = render partial: 'message_train/participants/prefilled_field', locals: { message: message, recipient: @collective }
12
+ = f.text_field :subject
13
+ = f.text_area :body, class: 'wysiwyg'
14
+ .col-md-4
15
+ .links
16
+ .pull-right= link_to_add_association(icon('plus'), f, :attachments, title: :add_attachment.l, class: 'btn btn-info', id: 'add-attachment', data: { association_insertion_method: 'append', association_insertion_node: '#attachments'})
17
+ %h3
18
+ = :attachments.l
19
+ #attachments
20
+ = f.fields_for :attachments do |attachment|
21
+ = render 'attachment_fields', f: attachment
22
+ = f.submit :save_as_draft.l, name: 'message[draft]'
23
+ = f.submit :send.l, class: 'btn btn-primary', name: 'message[draft]'