message_train 0.1.7 → 0.2.0

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