notify_user 0.1.4 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -0
  3. data/Rakefile +7 -1
  4. data/app/controllers/notify_user/base_notifications_controller.rb +36 -35
  5. data/app/mailers/notify_user/notification_mailer.rb +1 -1
  6. data/app/models/notify_user/apn_connection.rb +35 -17
  7. data/app/models/notify_user/apns.rb +124 -14
  8. data/app/models/notify_user/base_notification.rb +172 -35
  9. data/app/models/notify_user/gcm.rb +58 -0
  10. data/app/models/notify_user/push.rb +37 -0
  11. data/app/models/notify_user/unsubscribe.rb +26 -9
  12. data/app/models/notify_user/urban_airship.rb +1 -1
  13. data/app/serializers/notify_user/notification_serializer.rb +1 -4
  14. data/config/routes.rb +2 -2
  15. data/lib/generators/notify_user/aggr_interval_update/USAGE +5 -0
  16. data/lib/generators/notify_user/aggr_interval_update/aggr_interval_update_generator.rb +36 -0
  17. data/lib/generators/notify_user/aggr_interval_update/templates/add_sent_time_to_notifications.rb +10 -0
  18. data/lib/generators/notify_user/aggr_interval_update/templates/update_unsubscribe.rb +6 -0
  19. data/lib/generators/notify_user/install/install_generator.rb +2 -5
  20. data/lib/generators/notify_user/install/templates/create_notify_user_notifications.rb +8 -0
  21. data/lib/generators/notify_user/install/templates/create_notify_user_unsubscribes.rb +5 -0
  22. data/lib/generators/notify_user/notification/notification_generator.rb +1 -0
  23. data/lib/notify_user/channels/action_mailer/action_mailer_channel.rb +1 -1
  24. data/lib/notify_user/channels/apns/apns_channel.rb +23 -23
  25. data/lib/notify_user/engine.rb +0 -1
  26. data/lib/notify_user/version.rb +1 -1
  27. data/spec/controllers/notify_user/notifications_controller_spec.rb +130 -108
  28. data/spec/dummy/rails-4.1.0/Gemfile +1 -1
  29. data/spec/dummy/rails-4.1.0/app/notifications/new_post_notification.rb +2 -6
  30. data/spec/dummy/{rails-4.0.4/app/views/notify_user/new_post_notification/mobile_sdk/notification.html.erb → rails-4.1.0/app/views/notify_user/new_post_notification/mobile_sdk/aggregate_notifications.html.erb} +0 -0
  31. data/spec/dummy/rails-4.1.0/app/views/notify_user/new_post_notification/mobile_sdk/notification.html.erb +1 -0
  32. data/spec/dummy/rails-4.1.0/config/environments/production.rb +1 -6
  33. data/spec/dummy/rails-4.1.0/config/initializers/assets.rb +8 -0
  34. data/spec/dummy/rails-4.1.0/config/initializers/notify_user.rb +10 -0
  35. data/spec/dummy/rails-4.1.0/config/secrets.yml +2 -2
  36. data/spec/dummy/{rails-3.2.17/db/migrate/20141102231350_create_users.rb → rails-4.1.0/db/migrate/20150907004705_create_users.rb} +0 -0
  37. data/spec/dummy/{rails-4.0.4/db/migrate/20141102231413669669843000_create_notify_user_notifications.rb → rails-4.1.0/db/migrate/20150907004707_create_notify_user_notifications.rb} +8 -0
  38. data/spec/dummy/rails-4.1.0/db/migrate/{20141102231434013013078000_create_notify_user_unsubscribes.rb → 20150907004708_create_notify_user_unsubscribes.rb} +5 -0
  39. data/spec/dummy/{rails-3.2.17/db/migrate/20141102231353651651843000_create_notify_user_user_hashes.rb → rails-4.1.0/db/migrate/20150907004709_create_notify_user_user_hashes.rb} +0 -0
  40. data/spec/dummy/rails-4.1.0/db/schema.rb +12 -1
  41. data/spec/dummy/rails-4.1.0/log/test.log +55083 -8099
  42. data/spec/dummy/rails-4.1.0/test/test_helper.rb +0 -3
  43. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/2iFf6DF21eHswaXamThRpysQhZa4HHhfXSwW0I26I_Q.cache +3 -0
  44. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/56gcPUZsFk_z-NC4BqxyzJ2fHeG-wa2DVNUK5iFQico.cache +0 -0
  45. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/8DepzOh5SIwaxC825zft8xeBhAi84AFjIkQLBafpfcI.cache +1 -0
  46. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/DFBSCnNmEyoM8roYdJ4CwzgAxnyRtTG3S2qD1ryqXxw.cache +1 -0
  47. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/DcdyyraxoBKSYh1m6Spmfn5Qtn0V2X2t8aO3dmAi0Po.cache +3 -0
  48. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/IO46kchRzb_wUQ8H3D25_DHv7lZK4CxthkOj6ZKp8mQ.cache +1 -0
  49. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/JTg9Khb4iQ4MkGt2OoYdfz6rYD9ccxMNxCOnVK92fJA.cache +1 -0
  50. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/O8akbUwAbmIKY-AhITimq-JDQ6IgsO3Hvq2v5etufFw.cache +2 -0
  51. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/RJrFPSmoXCFaapGXw1bP4Lg0NLmr9qG2RMuN56UrR7U.cache +0 -0
  52. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/TWJMA4bg5d25falxiw_TLK0GtSXWpfl0vFaFLRpkYKI.cache +1 -0
  53. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/XjnNasrgRd0l5xx4UR4IeE-Tz1EtjiPKwmizcf59P9U.cache +0 -0
  54. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/Y829B5qbE0t7idbBscHZCJxlb-D9S6G_8P6HwwMe7J4.cache +2 -0
  55. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/aXGU_8_bWOQw87gl5m_8fgEUtethSIj95P57d5YuW5k.cache +0 -0
  56. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/cjXxcrFHyMpHUOqA38Yaft85pmxR7gZ4__TFcgJQyKA.cache +1 -0
  57. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/cpoXvFR-s_2VM6kFm4ixeZKjW6en5HiD8B-ttj-iUSI.cache +0 -0
  58. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/hAsOx0KPt1yL2Nh2sDOmOIWrAfoEZo1RxZQkNop9FOU.cache +0 -0
  59. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/j52KuMJNJLJcKPVLf8rk8USLswZAmy6Hi1OW35JjVTo.cache +1 -0
  60. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/mwsAf4xggfO0hGKNns79uKcXG7B5rk8a4Zav3vXyr6M.cache +2 -0
  61. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/tPF6H9NRpGO3whu2y5_e_R_1EACKdCwu5437D83qJT0.cache +3 -0
  62. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/x5V5ICXz753PW9A8l6gpNAh_utfwyXyixuUxtMgJ2Pg.cache +1 -0
  63. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/y8AwtETeMIB25XCoxQDwn3DmMpUFLblIFK4yb18zN4c.cache +1 -0
  64. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/yWa5z3IIFR71QlDOGDwO5IpC9vsVUGFNfficVy0q3zo.cache +1 -0
  65. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/v3.0/zDCAil6tD1zhO-gqs7OG9_zEEdMVEZo_aUkZjLHP53M.cache +0 -0
  66. data/spec/factories/notify_user_notifications.rb +4 -7
  67. data/spec/factories/users.rb +7 -0
  68. data/spec/lib/notify_user/channels/apns_channel_spec.rb +65 -0
  69. data/spec/mailers/notify_user/notification_mailer_spec.rb +24 -24
  70. data/spec/models/notify_user/apn_connection_spec.rb +29 -0
  71. data/spec/models/notify_user/apns_spec.rb +96 -0
  72. data/spec/models/notify_user/gcm_spec.rb +70 -0
  73. data/spec/models/notify_user/notification_spec.rb +456 -64
  74. data/spec/models/notify_user/unsubscribe_spec.rb +59 -16
  75. data/spec/models/notify_user/user_hash_spec.rb +11 -14
  76. data/spec/serializers/notify_user/notification_serializer_spec.rb +24 -6
  77. data/spec/setup_spec.rb +3 -4
  78. data/spec/spec_helper.rb +20 -12
  79. data/spec/support/test_apn_connection.rb +8 -0
  80. data/spec/support/test_gcm_connection.rb +5 -0
  81. metadata +131 -332
  82. data/app/models/notify_user/houston.rb +0 -101
  83. data/spec/dummy/rails-3.2.17/Gemfile +0 -38
  84. data/spec/dummy/rails-3.2.17/README.rdoc +0 -261
  85. data/spec/dummy/rails-3.2.17/Rakefile +0 -7
  86. data/spec/dummy/rails-3.2.17/app/assets/images/rails.png +0 -0
  87. data/spec/dummy/rails-3.2.17/app/assets/javascripts/application.js +0 -15
  88. data/spec/dummy/rails-3.2.17/app/assets/stylesheets/application.css +0 -13
  89. data/spec/dummy/rails-3.2.17/app/controllers/application_controller.rb +0 -3
  90. data/spec/dummy/rails-3.2.17/app/controllers/notify_user/notifications_controller.rb +0 -9
  91. data/spec/dummy/rails-3.2.17/app/helpers/application_helper.rb +0 -2
  92. data/spec/dummy/rails-3.2.17/app/models/user.rb +0 -3
  93. data/spec/dummy/rails-3.2.17/app/notifications/new_post_notification.rb +0 -15
  94. data/spec/dummy/rails-3.2.17/app/views/layouts/application.html.erb +0 -14
  95. data/spec/dummy/rails-3.2.17/app/views/notify_user/layouts/action_mailer.html.erb +0 -39
  96. data/spec/dummy/rails-3.2.17/app/views/notify_user/new_post_notification/action_mailer/notification.html.erb +0 -1
  97. data/spec/dummy/rails-3.2.17/config.ru +0 -4
  98. data/spec/dummy/rails-3.2.17/config/application.rb +0 -62
  99. data/spec/dummy/rails-3.2.17/config/boot.rb +0 -6
  100. data/spec/dummy/rails-3.2.17/config/database.yml +0 -24
  101. data/spec/dummy/rails-3.2.17/config/environment.rb +0 -5
  102. data/spec/dummy/rails-3.2.17/config/environments/development.rb +0 -37
  103. data/spec/dummy/rails-3.2.17/config/environments/production.rb +0 -67
  104. data/spec/dummy/rails-3.2.17/config/environments/test.rb +0 -37
  105. data/spec/dummy/rails-3.2.17/config/initializers/backtrace_silencers.rb +0 -7
  106. data/spec/dummy/rails-3.2.17/config/initializers/inflections.rb +0 -15
  107. data/spec/dummy/rails-3.2.17/config/initializers/mime_types.rb +0 -5
  108. data/spec/dummy/rails-3.2.17/config/initializers/notify_user.rb +0 -14
  109. data/spec/dummy/rails-3.2.17/config/initializers/secret_token.rb +0 -7
  110. data/spec/dummy/rails-3.2.17/config/initializers/session_store.rb +0 -8
  111. data/spec/dummy/rails-3.2.17/config/initializers/wrap_parameters.rb +0 -14
  112. data/spec/dummy/rails-3.2.17/config/keys/development_push.pem +0 -0
  113. data/spec/dummy/rails-3.2.17/config/locales/en.yml +0 -5
  114. data/spec/dummy/rails-3.2.17/config/routes.rb +0 -58
  115. data/spec/dummy/rails-3.2.17/db/migrate/20141102231353649649586000_create_notify_user_notifications.rb +0 -13
  116. data/spec/dummy/rails-3.2.17/db/migrate/20141102231353650650817000_create_notify_user_unsubscribes.rb +0 -10
  117. data/spec/dummy/rails-3.2.17/db/schema.rb +0 -50
  118. data/spec/dummy/rails-3.2.17/db/seeds.rb +0 -7
  119. data/spec/dummy/rails-3.2.17/doc/README_FOR_APP +0 -2
  120. data/spec/dummy/rails-3.2.17/log/test.log +0 -9574
  121. data/spec/dummy/rails-3.2.17/public/404.html +0 -26
  122. data/spec/dummy/rails-3.2.17/public/422.html +0 -26
  123. data/spec/dummy/rails-3.2.17/public/500.html +0 -25
  124. data/spec/dummy/rails-3.2.17/public/favicon.ico +0 -0
  125. data/spec/dummy/rails-3.2.17/public/index.html +0 -241
  126. data/spec/dummy/rails-3.2.17/public/robots.txt +0 -5
  127. data/spec/dummy/rails-3.2.17/script/rails +0 -6
  128. data/spec/dummy/rails-3.2.17/test/fixtures/users.yml +0 -7
  129. data/spec/dummy/rails-3.2.17/test/performance/browsing_test.rb +0 -12
  130. data/spec/dummy/rails-3.2.17/test/test_helper.rb +0 -13
  131. data/spec/dummy/rails-3.2.17/test/unit/user_test.rb +0 -7
  132. data/spec/dummy/rails-4.0.4/Gemfile +0 -45
  133. data/spec/dummy/rails-4.0.4/README.rdoc +0 -28
  134. data/spec/dummy/rails-4.0.4/Rakefile +0 -6
  135. data/spec/dummy/rails-4.0.4/app/assets/javascripts/application.js +0 -16
  136. data/spec/dummy/rails-4.0.4/app/assets/stylesheets/application.css +0 -13
  137. data/spec/dummy/rails-4.0.4/app/controllers/application_controller.rb +0 -5
  138. data/spec/dummy/rails-4.0.4/app/controllers/notify_user/notifications_controller.rb +0 -9
  139. data/spec/dummy/rails-4.0.4/app/helpers/application_helper.rb +0 -2
  140. data/spec/dummy/rails-4.0.4/app/models/user.rb +0 -2
  141. data/spec/dummy/rails-4.0.4/app/notifications/new_post_notification.rb +0 -15
  142. data/spec/dummy/rails-4.0.4/app/views/layouts/application.html.erb +0 -14
  143. data/spec/dummy/rails-4.0.4/app/views/notify_user/layouts/action_mailer.html.erb +0 -39
  144. data/spec/dummy/rails-4.0.4/app/views/notify_user/new_post_notification/action_mailer/notification.html.erb +0 -1
  145. data/spec/dummy/rails-4.0.4/bin/bundle +0 -3
  146. data/spec/dummy/rails-4.0.4/bin/rails +0 -4
  147. data/spec/dummy/rails-4.0.4/bin/rake +0 -4
  148. data/spec/dummy/rails-4.0.4/config.ru +0 -4
  149. data/spec/dummy/rails-4.0.4/config/application.rb +0 -23
  150. data/spec/dummy/rails-4.0.4/config/boot.rb +0 -4
  151. data/spec/dummy/rails-4.0.4/config/database.yml +0 -24
  152. data/spec/dummy/rails-4.0.4/config/environment.rb +0 -5
  153. data/spec/dummy/rails-4.0.4/config/environments/development.rb +0 -29
  154. data/spec/dummy/rails-4.0.4/config/environments/production.rb +0 -80
  155. data/spec/dummy/rails-4.0.4/config/environments/test.rb +0 -36
  156. data/spec/dummy/rails-4.0.4/config/initializers/backtrace_silencers.rb +0 -7
  157. data/spec/dummy/rails-4.0.4/config/initializers/filter_parameter_logging.rb +0 -4
  158. data/spec/dummy/rails-4.0.4/config/initializers/inflections.rb +0 -16
  159. data/spec/dummy/rails-4.0.4/config/initializers/mime_types.rb +0 -5
  160. data/spec/dummy/rails-4.0.4/config/initializers/notify_user.rb +0 -14
  161. data/spec/dummy/rails-4.0.4/config/initializers/secret_token.rb +0 -12
  162. data/spec/dummy/rails-4.0.4/config/initializers/session_store.rb +0 -3
  163. data/spec/dummy/rails-4.0.4/config/initializers/wrap_parameters.rb +0 -14
  164. data/spec/dummy/rails-4.0.4/config/keys/development_push.pem +0 -136
  165. data/spec/dummy/rails-4.0.4/config/locales/en.yml +0 -23
  166. data/spec/dummy/rails-4.0.4/config/routes.rb +0 -56
  167. data/spec/dummy/rails-4.0.4/db/migrate/20141102231412_create_users.rb +0 -9
  168. data/spec/dummy/rails-4.0.4/db/migrate/20141102231413670670945000_create_notify_user_unsubscribes.rb +0 -10
  169. data/spec/dummy/rails-4.0.4/db/migrate/20141102231413671671735000_create_notify_user_user_hashes.rb +0 -12
  170. data/spec/dummy/rails-4.0.4/db/schema.rb +0 -53
  171. data/spec/dummy/rails-4.0.4/db/seeds.rb +0 -7
  172. data/spec/dummy/rails-4.0.4/log/test.log +0 -45689
  173. data/spec/dummy/rails-4.0.4/public/404.html +0 -58
  174. data/spec/dummy/rails-4.0.4/public/422.html +0 -58
  175. data/spec/dummy/rails-4.0.4/public/500.html +0 -57
  176. data/spec/dummy/rails-4.0.4/public/favicon.ico +0 -0
  177. data/spec/dummy/rails-4.0.4/public/robots.txt +0 -5
  178. data/spec/dummy/rails-4.0.4/test/fixtures/users.yml +0 -7
  179. data/spec/dummy/rails-4.0.4/test/models/user_test.rb +0 -7
  180. data/spec/dummy/rails-4.0.4/test/test_helper.rb +0 -15
  181. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/0e8918f38b9bf3fc19fca4efda1600a1 +0 -0
  182. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  183. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/1bded108b40ef13c7e07be129e2cd83c +0 -0
  184. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/1ebdd9dfe7e88e90bee37951e2da1ea2 +0 -0
  185. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/23fe609acece1521082ad6b8249f96b1 +0 -0
  186. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  187. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  188. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/40fd179449501b801f80c852d1635a16 +0 -0
  189. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/4499e16a29d89bdf30304de41c456b43 +0 -0
  190. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/49088e1598df49c63ff7d874c97e958f +0 -0
  191. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/7398a3a743326d3576cbb05a000df04b +0 -0
  192. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/8eedfb1d9aee665c9f5a77df67b64a81 +0 -0
  193. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/91d718eeae4552c702b8eafd9e8bbe76 +0 -0
  194. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/9e2c26ef339b5827a5c296acb284ab99 +0 -0
  195. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/a4f2f445b4f8578493957e4f7b0c76e3 +0 -0
  196. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/af361e697648848dbf0282eba1d15c2f +0 -0
  197. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/af95fe3d35c9fe417700e8c3625329fe +0 -0
  198. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/b89f385c2540cb58f04dbfed561d3902 +0 -0
  199. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/bc1864fd5bfa875243b05071735cbb82 +0 -0
  200. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/bede0f0813e15081ea4ee32b640c92c4 +0 -0
  201. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/c1ba4e65491e8bb9fcdc38b22cb64aa9 +0 -0
  202. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/cb741827a22668ecf975f78242076b6b +0 -0
  203. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  204. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/d3516b65bb025bc333e0f91647a7b119 +0 -0
  205. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  206. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/df7c1d81da90fb0287da1e283291dc81 +0 -0
  207. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/f78ea05f6019d54c3572513ebae03556 +0 -0
  208. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/f7c2f64864ec1995aee8dcead45d7f24 +0 -0
  209. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  210. data/spec/dummy/rails-4.0.4/tmp/cache/assets/test/sprockets/fbbb5b21a4144f5e3efee9eda4fc38e5 +0 -0
  211. data/spec/dummy/rails-4.1.0/db/migrate/20141102231432_create_users.rb +0 -9
  212. data/spec/dummy/rails-4.1.0/db/migrate/20141102231434012012157000_create_notify_user_notifications.rb +0 -13
  213. data/spec/dummy/rails-4.1.0/db/migrate/20141102231434013013847000_create_notify_user_user_hashes.rb +0 -12
  214. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  215. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/1bded108b40ef13c7e07be129e2cd83c +0 -0
  216. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/1ebdd9dfe7e88e90bee37951e2da1ea2 +0 -0
  217. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/23fe609acece1521082ad6b8249f96b1 +0 -0
  218. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  219. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  220. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/3fa5cc1bc06bfb81887b971eb8d36258 +0 -0
  221. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/796287194e2490ae173cabd0f8e0fce7 +0 -0
  222. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/8d2563dcd2d59f9e020fc2623eda3999 +0 -0
  223. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/8ebc0d0c963e18c7c16e6e7f55fd379f +0 -0
  224. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/91d718eeae4552c702b8eafd9e8bbe76 +0 -0
  225. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/9e2c26ef339b5827a5c296acb284ab99 +0 -0
  226. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/a098882f88a8b3ca91d2efaada23dba3 +0 -0
  227. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/af361e697648848dbf0282eba1d15c2f +0 -0
  228. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/b89f385c2540cb58f04dbfed561d3902 +0 -0
  229. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/c1ba4e65491e8bb9fcdc38b22cb64aa9 +0 -0
  230. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/cb741827a22668ecf975f78242076b6b +0 -0
  231. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  232. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  233. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/df7c1d81da90fb0287da1e283291dc81 +0 -0
  234. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/f031b3342a7246183ffa1bb7819f293e +0 -0
  235. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/f78ea05f6019d54c3572513ebae03556 +0 -0
  236. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  237. data/spec/dummy/rails-4.1.0/tmp/cache/assets/test/sprockets/fbbb5b21a4144f5e3efee9eda4fc38e5 +0 -0
  238. data/spec/models/notify_user/houston_spec.rb +0 -33
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 12da1c1e7ee14dfcbba8a54801e75d654615e599
4
- data.tar.gz: 34e6d23884ab55ca3509f72d2790b77a4c1ab4f8
3
+ metadata.gz: 77798252ab441cbfca131b125e40292b701210dd
4
+ data.tar.gz: 5cc16d6d5c354588db35b6a0797ce61da23a669d
5
5
  SHA512:
6
- metadata.gz: 4b89ad049ba111308ee77de5a79614ac315f7783ac4e92d97c613640b295027aa649cb145a6becddbe56eedb5cf790da08e2a5c3699bd46be95ec087bdf9b647
7
- data.tar.gz: 2538cf1ecd80bd7a71c7b23a9ccfbf7abf8257abad192ba7d569a91ef5345cf2c763d75a1982e14fc5a1822afb626db4df92bafe02aef7e88ad129089f9c9da7
6
+ metadata.gz: fdb8a7efaa8c5e7af6bc9b8a84ae0189ce564c4492d25d8cb00c991a1964df787e00a392e77cea452c358e34380bc785d4638177304dd74df052a5d0ddc90d7c
7
+ data.tar.gz: 13e3307a5e50a15aade5b029bdf9483b3d20df4bd1b704b6057c2ba60dd30dc36433f14542520c5b0203cb485f5afe5f01a55304e19b79d601cf2f5160a1d710
data/README.md CHANGED
@@ -141,6 +141,22 @@ Unsubscribe link helper - add this to your views/notify_user/layouts/action_mail
141
141
  </p>
142
142
  <% end %>
143
143
  ```
144
+ ##Unsubscribing/Subscribing to a specific group_id or resource
145
+ Unsubscribing from a specific `group_id` eg. specific resource.
146
+ `put /notify_user/notifications/unsubscribe_from_object.json`
147
+ expects
148
+ ```
149
+ subscription: {type: "NotificationType", group_id: 1, unsubscribe: true|false}
150
+ ```
151
+ returns
152
+ `response_code 201`
153
+
154
+ ##Upgrade v0.1.4 to v0.2
155
+ Run aggregate_interval generator which generates the migrations to add a sent_time field to notifications
156
+ ```
157
+ rails generate notify_user:aggr_interval
158
+ rake db:migrate
159
+ ```
144
160
 
145
161
  ##Upgrading to JSON params data type
146
162
  Run json_update generator which generates the migrations to change the params datatype to json as well as convert the current data to json
data/Rakefile CHANGED
@@ -1,4 +1,10 @@
1
- require "bundler"
1
+ begin
2
+ require 'bundler/setup'
3
+ require 'appraisal'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
2
8
  require 'rake'
3
9
  Bundler.setup
4
10
  Bundler::GemHelper.install_tasks
@@ -3,14 +3,14 @@ class NotifyUser::BaseNotificationsController < ApplicationController
3
3
  before_filter :authenticate!, :except => [:unauth_unsubscribe]
4
4
 
5
5
  def index
6
- collection
6
+ collection
7
7
  respond_to_method
8
8
  end
9
9
 
10
10
  def collection
11
11
  @notifications = NotifyUser::BaseNotification.for_target(@user)
12
12
  .order("created_at DESC")
13
- .limit(30)
13
+ .where('parent_id IS NULL')
14
14
  collection_pagination
15
15
  end
16
16
 
@@ -32,17 +32,33 @@ class NotifyUser::BaseNotificationsController < ApplicationController
32
32
  end
33
33
 
34
34
  def mark_all
35
- @notifications = NotifyUser::BaseNotification.for_target(@user).where('state IN (?)', ["pending","sent"])
35
+ @notifications = NotifyUser::BaseNotification.for_target(@user).where('state != ?', 'read')
36
36
  @notifications.update_all(state: :read)
37
- redirect_to notify_user_notifications_path
37
+ render json: @notifications
38
38
  end
39
39
 
40
40
  def notifications_count
41
- @notifications = NotifyUser::BaseNotification.for_target(@user).where('state IN (?)', ["sent", "pending"])
42
- render json: {:count => @notifications.count}
41
+ @notifications = NotifyUser::BaseNotification.for_target(@user)
42
+ .where('parent_id IS NULL')
43
+ .where('state IN (?)', ["sent_as_aggregation_parent", "sent", "pending"])
44
+
45
+ render json: { :count => @notifications.count }
43
46
  end
44
47
 
45
- #get
48
+ def unsubscribe_from_object
49
+ case params[:subscription][:unsubscribe]
50
+ when true
51
+ NotifyUser::Unsubscribe.unsubscribe(@user, params[:subscription][:type], params[:subscription][:group_id])
52
+ when false
53
+ NotifyUser::Unsubscribe.subscribe(@user, params[:subscription][:type], params[:subscription][:group_id])
54
+ else
55
+ raise "unsubscribe field required"
56
+ end
57
+
58
+ render json: {status: "OK"}, status: 201
59
+ end
60
+
61
+ #get
46
62
  def read
47
63
  @notification = NotifyUser::BaseNotification.for_target(@user).where('id = ?', params[:id]).first
48
64
  unless @notification.read?
@@ -58,8 +74,8 @@ class NotifyUser::BaseNotificationsController < ApplicationController
58
74
 
59
75
  def unsubscribe
60
76
  if params[:type]
61
- unsubscribe_from(params[:type])
62
- redirect_to notify_user_notifications_unsubscribe_path
77
+ NotifyUser::Unsubscribe.unsubscribe(@user, params[:type])
78
+ redirect_to notify_user_notifications_unsubscribe_path
63
79
  end
64
80
  @types = build_notification_types
65
81
  @unsubscribale_types = NotifyUser.unsubscribable_notifications
@@ -82,28 +98,25 @@ class NotifyUser::BaseNotificationsController < ApplicationController
82
98
  NotifyUser::Unsubscribe.unsubscribe(@user,type[:type])
83
99
  else
84
100
  if unsubscribe.empty?
85
- #if unsubscribe doesn't exist create it
101
+ #if unsubscribe doesn't exist create it
86
102
  unsubscribe = NotifyUser::Unsubscribe.create(target: @user, type: type[:type])
87
103
  end
88
104
  end
89
105
  end
90
106
  flash[:message] = "Successfully updated your notifcation settings"
91
- end
92
- redirect_to notify_user_notifications_unsubscribe_path
107
+ end
108
+ redirect_to notify_user_notifications_unsubscribe_path
93
109
  end
94
110
 
95
111
  def update_subscriptions(types)
96
112
  types.each do |type|
97
113
  unsubscribe = NotifyUser::Unsubscribe.has_unsubscribed_from(@user, type[:type])
98
114
  if type[:status] == '0'
99
- if unsubscribe.empty?
100
- #if unsubscribe doesn't exist create it
101
- unsubscribe = NotifyUser::Unsubscribe.create(target: @user, type: type[:type])
102
- end
115
+ NotifyUser::Unsubscribe.unsubscribe(@user, type[:type])
103
116
  else
104
- subscribe_to(type[:type])
117
+ NotifyUser::Unsubscribe.subscribe(@user, type[:type])
105
118
  end
106
- end
119
+ end
107
120
  end
108
121
 
109
122
  def unauth_unsubscribe
@@ -122,7 +135,7 @@ class NotifyUser::BaseNotificationsController < ApplicationController
122
135
  end
123
136
 
124
137
  def subscribe
125
- subscribe_to(params[:type]) if params[:type]
138
+ NotifyUser::Unsubscribe.subscribe(@user, params[:type]) if params[:type]
126
139
  redirect_to notify_user_notifications_unsubscribe_path
127
140
  end
128
141
 
@@ -141,6 +154,7 @@ class NotifyUser::BaseNotificationsController < ApplicationController
141
154
  end
142
155
 
143
156
  private
157
+
144
158
  def build_notification_types()
145
159
  #dirty way to build a json hash with pagination
146
160
  types = {:subscriptions => []}
@@ -151,28 +165,15 @@ class NotifyUser::BaseNotificationsController < ApplicationController
151
165
  NotifyUser::BaseNotification.channels.each do |type, options|
152
166
  channel = (type.to_s + "_channel").camelize.constantize
153
167
  types[:subscriptions] << {type: type, description: channel.default_options[:description],
154
- status: NotifyUser::Unsubscribe.has_unsubscribed_from(@user, type).empty?}
155
- end
168
+ status: NotifyUser::Unsubscribe.has_unsubscribed_from?(@user, type)}
169
+ end
156
170
 
157
171
  #iterates over type
158
172
  notification_types.each do |type|
159
173
  types[:subscriptions] << {type: type, description: type.constantize.description,
160
- status: NotifyUser::Unsubscribe.has_unsubscribed_from(@user, type).empty?}
174
+ status: NotifyUser::Unsubscribe.has_unsubscribed_from?(@user, type)}
161
175
  end
162
176
  return types
163
177
  end
164
178
 
165
- def unsubscribe_from(type)
166
- unsubscribe = NotifyUser::Unsubscribe.new(target: @user, type: type)
167
- if unsubscribe.save
168
- flash[:message] = "successfully unsubscribed from #{type} notifications"
169
- else
170
- flash[:message] = "Please try again"
171
- end
172
- end
173
-
174
- def subscribe_to(type)
175
- NotifyUser::Unsubscribe.unsubscribe(@user,type)
176
- flash[:message] = "successfully subscribed to #{type} notifications"
177
- end
178
179
  end
@@ -3,7 +3,7 @@ module NotifyUser
3
3
  helper NotifyUser::ApplicationHelper
4
4
 
5
5
  layout "notify_user/layouts/action_mailer"
6
-
6
+
7
7
  def notification_email(notification, options)
8
8
  @notification = notification
9
9
 
@@ -1,43 +1,61 @@
1
1
  module NotifyUser
2
2
  class APNConnection
3
3
 
4
- attr_accessor :connection
5
-
6
4
  def initialize
7
- setup
5
+ connection
6
+ end
7
+
8
+ def connection
9
+ @connection ||= setup_connection
10
+ end
11
+
12
+ def write(data)
13
+ raise "Connection is closed" unless @connection.open?
14
+ @connection.write(data)
8
15
  end
9
16
 
10
- def setup
17
+ def reset
18
+ @connection.close if @connection
19
+ @connection = nil
20
+ connection
21
+ end
22
+
23
+ private
24
+
25
+ def apn_environment
26
+ return nil unless ENV['APN_ENVIRONMENT']
27
+
28
+ ENV['APN_ENVIRONMENT'].downcase.to_sym
29
+ end
30
+
31
+ def setup_connection
32
+ return if Rails.env.test?
33
+
11
34
  @uri, @certificate = if Rails.env.development? || apn_environment == :development
12
35
  Rails.logger.info "Using development gateway. Rails env: #{Rails.env}, APN_ENVIRONMENT: #{apn_environment}"
13
36
  [
14
37
  ::Houston::APPLE_DEVELOPMENT_GATEWAY_URI,
15
- File.read("#{Rails.root}/config/keys/development_push.pem")
38
+ File.read(development_certificate)
16
39
  ]
17
40
  else
18
41
  Rails.logger.info "Using production gateway. Rails env: #{Rails.env}, APN_ENVIRONMENT: #{apn_environment}"
19
42
  [
20
43
  ::Houston::APPLE_PRODUCTION_GATEWAY_URI,
21
- File.read("#{Rails.root}/config/keys/production_push.pem")
44
+ File.read(production_certificate)
22
45
  ]
23
46
  end
24
47
 
25
48
  @connection = ::Houston::Connection.new(@uri, @certificate, nil)
26
- @connection.open
27
49
  end
28
50
 
29
- def write(data)
30
- raise "Connection is closed" unless @connection.open?
31
- @connection.write(data)
51
+ def development_certificate
52
+ file_path = ENV['APN_DEVELOPMENT_PATH'] || 'config/keys/development_push.pem'
53
+ "#{Rails.root}/#{file_path}"
32
54
  end
33
55
 
34
- private
35
-
36
- def apn_environment
37
- return nil unless ENV['APN_ENVIRONMENT']
38
-
39
- ENV['APN_ENVIRONMENT'].downcase.to_sym
56
+ def production_certificate
57
+ file_path = ENV['APN_PRODUCTION_PATH'] || "config/keys/production_push.pem"
58
+ "#{Rails.root}/#{file_path}"
40
59
  end
41
-
42
60
  end
43
61
  end
@@ -1,28 +1,138 @@
1
+ require_relative 'apn_connection'
2
+ require 'houston'
3
+
1
4
  module NotifyUser
2
- class Apns
3
- SYMBOL_NAMES_SIZE = 10
4
- PAYLOAD_LIMIT = 255
5
+ class Apns < Push
6
+ NO_ERROR = -42
7
+ INVALID_TOKEN_ERROR = 8
8
+ CONNECTION = APNConnection.new
9
+
10
+ attr_accessor :push_options
5
11
 
6
- def initialize(notification, options)
7
- @notification = notification
8
- @options = options
12
+ def initialize(notifications, devices, options)
13
+ super(notifications, devices, options)
14
+
15
+ @push_options = setup_options
16
+ @devices = devices
9
17
  end
10
18
 
11
- # Sends push notification:
12
19
  def push
13
- raise "Base APNS class should not be used."
20
+ send_notifications
14
21
  end
15
22
 
16
23
  private
17
24
 
18
- # Calculates the bytes already used:
19
- def used_space
20
- used_space = SYMBOL_NAMES_SIZE + @notification.id.size + @notification.created_at.to_time.to_i.size +
21
- @notification.type.size
25
+ attr_accessor :devices
26
+
27
+ def connection
28
+ CONNECTION.connection
29
+ end
30
+
31
+ def reset_connection
32
+ CONNECTION.reset
33
+ end
34
+
35
+ def setup_options
36
+ space_allowance = PAYLOAD_LIMIT - used_space
37
+
38
+ mobile_message = ''
39
+ if @notification.parent_id
40
+ parent = @notification.class.find(@notification.parent_id)
41
+ mobile_message = parent.mobile_message(space_allowance)
42
+ else
43
+ mobile_message = @notification.mobile_message(space_allowance)
44
+ end
45
+ mobile_message.gsub!('\n', "\n")
46
+
47
+ push_options = {
48
+ alert: mobile_message,
49
+ badge: @notification.count_for_target,
50
+ category: @notification.params[:category] || @notification.type,
51
+ custom_data: @notification.params,
52
+ sound: @options[:sound] || 'default'
53
+ }
54
+
55
+ if @options[:silent]
56
+ push_options.merge!({
57
+ alert: '',
58
+ sound: '',
59
+ content_available: true
60
+ }).delete(:badge)
61
+ end
62
+
63
+ push_options
64
+ end
65
+
66
+ def valid?(payload)
67
+ payload.to_json.bytesize <= PAYLOAD_LIMIT
68
+ end
69
+
70
+ def send_notifications
71
+ connection.open if connection.closed?
72
+
73
+ Rails.logger.info "PAYLOAD"
74
+ Rails.logger.info "----"
75
+ Rails.logger.info "#{@push_options}"
76
+
77
+ unless valid?(@push_options)
78
+ Rails.logger.info "Error: Payload exceeds size limit."
79
+ end
80
+
81
+ devices.each_with_index do |device, index|
82
+ notification = ::Houston::Notification.new(@push_options.dup.merge({ token: device.token, id: index }))
83
+ connection.write(notification.message)
84
+ end
85
+
86
+ error_index = io_errors
87
+
88
+ if error_index == NO_ERROR
89
+ return true
90
+ else
91
+ # Resend all notifications after the once that produced the error:
92
+ send_notifications
93
+ end
94
+ rescue OpenSSL::SSL::SSLError, Errno::EPIPE, Errno::ETIMEDOUT => e
95
+ Rails.logger.error "[##{connection.object_id}] Exception occurred: #{e.inspect}."
96
+ reset_connection
97
+ Rails.logger.debug "[##{connection.object_id}] Socket reestablished."
98
+ retry
99
+ end
100
+
101
+ def io_errors
102
+ error_index = NO_ERROR
103
+ ssl = connection.ssl
104
+
105
+ Rails.logger.info "READING ERRORS"
106
+ Rails.logger.info "----"
107
+ read_socket, write_socket = IO.select([ssl], [], [ssl], 1)
108
+ Rails.logger.info "#{ssl}"
109
+
110
+ if (read_socket && read_socket[0])
111
+ error = connection.read(6)
112
+
113
+ Rails.logger.info "#{error}"
114
+
115
+ if error
116
+ command, status, error_index = error.unpack("ccN")
117
+
118
+ Rails.logger.info "Error: #{status} with id: #{error_index}."
119
+
120
+ # Remove all the devices prior to the error (we assume they were successful), and close the current connection:
121
+ if error_index != NO_ERROR
122
+ device = devices.at(error_index)
22
123
 
23
- used_space += @notification.params[:action_id].size if @notification.params[:action_id]
124
+ # If we encounter the Invalid Token error from APNS, just remove the device:
125
+ if status == INVALID_TOKEN_ERROR
126
+ Rails.logger.info "Invalid token encountered, removing device. Token: #{device.token}."
127
+ device.destroy
128
+ end
24
129
 
25
- used_space
130
+ devices.slice!(0..error_index)
131
+ reset_connection
132
+ end
133
+ end
134
+ end
135
+ return error_index
26
136
  end
27
137
  end
28
138
  end
@@ -11,7 +11,7 @@ module NotifyUser
11
11
  after_commit :deliver, on: :create
12
12
 
13
13
  if ActiveRecord::VERSION::MAJOR < 4
14
- attr_accessible :params, :target, :type, :state
14
+ attr_accessible :params, :target, :type, :state, :group_id, :parent_id
15
15
  end
16
16
 
17
17
  # Override point in case of collisions, plus keeps the table name tidy.
@@ -29,6 +29,7 @@ module NotifyUser
29
29
  belongs_to :target, polymorphic: true
30
30
 
31
31
  validates_presence_of :target_id, :target_type, :target, :type, :state
32
+ validate :presence_of_group_id
32
33
 
33
34
  aasm column: :state do
34
35
 
@@ -41,12 +42,25 @@ module NotifyUser
41
42
  # Email/SMS/APNS has been sent.
42
43
  state :sent
43
44
 
45
+ # Identifies which notification within the aggregation window that was actually delayed
46
+ state :sent_as_aggregation_parent
47
+ state :pending_as_aggregation_parent
48
+
44
49
  # The user has seen this notification.
45
50
  state :read
46
51
 
47
52
  # Record that we have sent message(s) to the user about this notification.
48
53
  event :mark_as_sent do
49
- transitions from: [:pending, :pending_no_aggregation], to: :sent
54
+ transitions from: [:pending_as_aggregation_parent], to: :sent_as_aggregation_parent, :if => :pending_as_aggregation_parent?
55
+ transitions from: [:pending, :pending_no_aggregation], to: :sent, :unless => :pending_as_aggregation_parent?
56
+ after do
57
+ self.sent_time = Time.now
58
+ self.save
59
+ end
60
+ end
61
+
62
+ event :mark_as_pending_as_aggregation_parent do
63
+ transitions from: [:pending], to: :pending_as_aggregation_parent
50
64
  end
51
65
 
52
66
  event :dont_aggregate do
@@ -57,7 +71,7 @@ module NotifyUser
57
71
  # A notification can go straight from pending to read if it's seen in a view before
58
72
  # sent in an email.
59
73
  event :mark_as_read do
60
- transitions from: [:pending, :sent], to: :read
74
+ transitions from: [:pending, :sent, :pending_as_aggregation_parent, :sent_as_aggregation_parent], to: :read
61
75
  end
62
76
  end
63
77
 
@@ -71,23 +85,35 @@ module NotifyUser
71
85
 
72
86
  # returns the global unread notification count for a user
73
87
  def count_for_target
74
- NotifyUser::BaseNotification.for_target(target).where('state IN (?)', ["sent", "pending"]).count
88
+ NotifyUser::BaseNotification.for_target(target)
89
+ .where('parent_id IS NULL')
90
+ .where('state IN (?)', ["sent_as_aggregation_parent", "sent", "pending"])
91
+ .count
92
+ end
93
+
94
+ def self.aggregate_message(notifications)
95
+ string = ActionView::Base.new(
96
+ ActionController::Base.view_paths).render(
97
+ :template => self.class.views[:mobile_sdk][:aggregate_path].call(self), :formats => [:html],
98
+ :locals => { :notifications => notifications})
99
+
100
+ return ::CGI.unescapeHTML("#{string}")
75
101
  end
76
102
 
77
103
  def message
78
104
  string = ActionView::Base.new(
79
- Rails.configuration.paths["app/views"]).render(
105
+ ActionController::Base.view_paths).render(
80
106
  :template => self.class.views[:mobile_sdk][:template_path].call(self), :formats => [:html],
81
- :locals => { :params => self.params})
107
+ :locals => { :params => self.params, :notification => self})
82
108
 
83
109
  return ::CGI.unescapeHTML("#{string}")
84
110
  end
85
111
 
86
112
  def mobile_message(length=115)
87
113
  string = truncate(ActionView::Base.new(
88
- Rails.configuration.paths["app/views"]).render(
114
+ ActionController::Base.view_paths).render(
89
115
  :template => self.class.views[:mobile_sdk][:template_path].call(self), :formats => [:html],
90
- :locals => { :params => self.params}), :length => length)
116
+ :locals => { :params => self.params, :notification => self}), :length => length)
91
117
 
92
118
  return ::CGI.unescapeHTML("#{string}")
93
119
  end
@@ -103,15 +129,33 @@ module NotifyUser
103
129
  self
104
130
  end
105
131
 
132
+ def grouped_by_id(group_id)
133
+ self.group_id = group_id
134
+ self
135
+ end
136
+
106
137
  def notify!
107
138
  # Bang version of 'notify' ignores aggregation
108
139
  dont_aggregate!
109
140
  end
110
141
 
111
142
  # Send any Emails/SMS/APNS
112
- def notify
143
+ def notify(deliver=true)
144
+ #All notifications except the notification at interval 0 should have there parent_id set
145
+ if self.aggregate_grouping
146
+ parents = current_parents.where(parent_id: nil).where('created_at >= ?', 24.hours.ago).order('created_at DESC')
147
+
148
+ if parents.any?
149
+ self.parent_id = parents.first.id
150
+ end
151
+ end
152
+
113
153
  # Sends with aggregation if enabled
114
154
  save
155
+
156
+ ## if deliver == false don't perform deliver log but still perform aggregation logic
157
+ ## notification then gets marked as sent
158
+ mark_as_sent! unless deliver
115
159
  end
116
160
 
117
161
  def generate_unsubscribe_hash
@@ -119,6 +163,25 @@ module NotifyUser
119
163
  return NotifyUser::UserHash.where(target_id: self.target.id).where(target_type: self.target.class.base_class).where(type: self.type).where(active: true).first || NotifyUser::UserHash.create(target: self.target, type: self.type, active: true)
120
164
  end
121
165
 
166
+ def aggregation_interval
167
+ pending_and_sent_aggregation_parents.count
168
+ end
169
+
170
+ def delay_time(options)
171
+ a_interval = options[:aggregate_per][aggregation_interval]
172
+
173
+ # uses the last interval by default once we deplete the intervals
174
+ a_interval = options[:aggregate_per].last if a_interval.nil?
175
+
176
+ # last sent notification
177
+ last_sent_parent = sent_aggregation_parents.first
178
+ # Uses the time of the last notification sent otherwise will send it now.
179
+ delay_time = last_sent_parent ? last_sent_parent.sent_time : created_at
180
+
181
+ # If this is the first notification the aggregate interval will return 0. Thus sending the notification now!
182
+ return delay_time + a_interval.minutes
183
+ end
184
+
122
185
  ## Notification description
123
186
  class_attribute :description
124
187
  self.description = ""
@@ -128,12 +191,23 @@ module NotifyUser
128
191
  self.channels = {
129
192
  }
130
193
 
194
+ ## Aggregation
195
+
196
+ class_attribute :aggregate_per
197
+ self.aggregate_per = 1.minute
198
+
199
+ ## True will implement a grouping/aggregation algorithm so that even though 10 notifications are delivered eg. Push Notifications
200
+ ## Only 1 notification will be displayed to the user within the notification.json payload
201
+ class_attribute :aggregate_grouping
202
+ self.aggregate_grouping = false
203
+
131
204
  # Not sure about this. The JSON and web feeds don't fit into channels, because nothing is broadcast through
132
205
  # them. Not sure if they really need another concept though, they could just be formats on the controller.
133
206
  class_attribute :views
134
207
  self.views = {
135
208
  mobile_sdk: {
136
- template_path: Proc.new {|n| "notify_user/#{n.class.name.underscore}/mobile_sdk/notification" }
209
+ template_path: Proc.new {|n| "notify_user/#{n.class.name.underscore}/mobile_sdk/notification" },
210
+ aggregate_path: Proc.new {|n| "notify_user/#{n.class.name.underscore}/mobile_sdk/aggregate_notifications" }
137
211
  }
138
212
  }
139
213
 
@@ -144,11 +218,6 @@ module NotifyUser
144
218
  self.channels = channels_clone
145
219
  end
146
220
 
147
- ## Aggregation
148
-
149
- class_attribute :aggregate_per
150
- self.aggregate_per = 1.minute
151
-
152
221
  ## Sending
153
222
 
154
223
  def self.for_target(target)
@@ -156,31 +225,91 @@ module NotifyUser
156
225
  .where(target_type: target.class.base_class)
157
226
  end
158
227
 
228
+ # Returns all parent notifications with a given group_id
229
+ def current_parents
230
+ self.class
231
+ .for_target(self.target)
232
+ .where(group_id: group_id)
233
+ end
234
+
235
+ def aggregation_parents
236
+ current_parents
237
+ .where('id != ?', id)
238
+ end
239
+
240
+ def sent_aggregation_parents
241
+ aggregation_parents
242
+ .where(state: :sent_as_aggregation_parent)
243
+ .order('created_at DESC')
244
+ end
245
+
246
+ def pending_and_sent_aggregation_parents
247
+ aggregation_parents
248
+ .where(state: [:sent_as_aggregation_parent, :pending_as_aggregation_parent])
249
+ .order('created_at DESC')
250
+ end
251
+
252
+ # Used for aggregation when grouping isn't enabled
253
+ def self.pending_aggregations_marked_as_parent(notification)
254
+ where(type: notification.type)
255
+ .for_target(notification.target)
256
+ .where(state: :pending_as_aggregation_parent)
257
+ end
258
+
259
+ # Used for aggregation when grouping based on group_id for target
260
+ def self.pending_aggregations_grouped_marked_as_parent(notification)
261
+ where(type: notification.type)
262
+ .for_target(notification.target)
263
+ .where(state: :pending_as_aggregation_parent)
264
+ .where(group_id: notification.group_id)
265
+ end
266
+
267
+ # Used to find all pending notifications with aggregation enabled for target
268
+ def self.pending_aggregation_by_group_with(notification)
269
+ for_target(notification.target)
270
+ .where(state: [:pending, :pending_as_aggregation_parent])
271
+ .where(group_id: notification.group_id)
272
+ end
273
+
274
+ # Used to find all pending notifications for target
159
275
  def self.pending_aggregation_with(notification)
160
276
  where(type: notification.type)
161
277
  .for_target(notification.target)
162
- .where(state: :pending)
278
+ .where(state: [:pending, :pending_as_aggregation_parent])
163
279
  end
164
280
 
165
281
  def aggregation_pending?
166
282
  # A notification of the same type, that would have an aggregation job associated with it,
167
283
  # already exists.
168
- return (self.class.pending_aggregation_with(self).where('id != ?', id).count > 0)
284
+
285
+ # When group aggregation is enabled we provide a different scope
286
+ if self.aggregate_grouping
287
+ return (self.class.pending_aggregations_grouped_marked_as_parent(self).where('id != ?', id).count > 0)
288
+ else
289
+ return (self.class.pending_aggregations_marked_as_parent(self).where('id != ?', id).count > 0)
290
+ end
169
291
  end
170
292
 
171
293
  # Aggregates appropriately
172
294
  def deliver
173
295
  if pending? and not user_has_unsubscribed?
174
- self.mark_as_sent!
175
-
176
296
  # if aggregation is false bypass aggregation completely
177
297
  self.channels.each do |channel_name, options|
178
298
  if(options[:aggregate_per] == false)
299
+ self.mark_as_sent!
179
300
  self.class.delay.deliver_notification_channel(self.id, channel_name)
180
301
  else
181
- # only notifies channels if no pending aggreagte notifications
182
- if not aggregation_pending?
183
- self.class.delay_for(options[:aggregate_per] || self.aggregate_per).notify_aggregated_channel(self.id, channel_name)
302
+ # only notifies channels if no pending aggregate notifications
303
+ unless aggregation_pending?
304
+ self.mark_as_pending_as_aggregation_parent!
305
+
306
+ # adds fallback support for integer or array of integers
307
+ if options[:aggregate_per].kind_of?(Array)
308
+ self.class.delay_until(delay_time(options)).notify_aggregated_channel(self.id, channel_name)
309
+ else
310
+ a_interval = options[:aggregate_per] ? options[:aggregate_per].minutes : self.aggregate_per
311
+ self.class.delay_for(a_interval).notify_aggregated_channel(self.id, channel_name)
312
+ end
184
313
  end
185
314
  end
186
315
  end
@@ -221,7 +350,7 @@ module NotifyUser
221
350
  channel_options = channels[channel_name.to_sym]
222
351
  channel = (channel_name.to_s + "_channel").camelize.constantize
223
352
 
224
- unless self.unsubscribed_from_channel?(notification.target, channel_name)
353
+ unless notification.user_has_unsubscribed?(channel_name)
225
354
  channel.deliver(notification, channel_options)
226
355
  end
227
356
  end
@@ -232,7 +361,7 @@ module NotifyUser
232
361
  channel = (channel_name.to_s + "_channel").camelize.constantize
233
362
 
234
363
  #check if user unsubsribed from channel type
235
- unless self.unsubscribed_from_channel?(notifications.first.target, channel_name)
364
+ unless notifications.first.user_has_unsubscribed?(channel_name)
236
365
  channel.deliver_aggregated(notifications, channel_options)
237
366
  end
238
367
  end
@@ -242,12 +371,18 @@ module NotifyUser
242
371
  notification = self.find(notification_id) # Raise an exception if not found.
243
372
 
244
373
  # Find any pending notifications with the same type and target, which can all be sent in one message.
245
- notifications = self.pending_aggregation_with(notification)
374
+ if self.aggregate_grouping
375
+ notifications = self.pending_aggregation_by_group_with(notification)
376
+ else
377
+ notifications = self.pending_aggregation_with(notification)
378
+ end
246
379
 
247
380
  notifications.map(&:mark_as_sent)
248
381
  notifications.map(&:save)
249
382
 
250
- return if notifications.empty?
383
+ # If the notification has been marked as read before it's sent we don't want to send it.
384
+ return if notification.read? || notifications.empty?
385
+
251
386
  if notifications.length == 1
252
387
  # Despite waiting for more to aggregate, we only got one in the end.
253
388
  self.deliver_notification_channel(notifications.first.id, channel_name)
@@ -257,24 +392,26 @@ module NotifyUser
257
392
  end
258
393
  end
259
394
 
395
+ def user_has_unsubscribed?(channel_name=nil)
396
+ #return true if user has unsubscribed
397
+ return Unsubscribe.has_unsubscribed_from?(self.target, self.type, self.group_id, channel_name)
398
+ end
399
+
260
400
  private
261
401
 
262
- def unsubscribed_validation
263
- errors.add(:target, (" has unsubscribed from this type")) if user_has_unsubscribed?
402
+ def presence_of_group_id
403
+ if self.aggregate_grouping && group_id.blank?
404
+ errors.add(:group_id, "required when aggregate_grouping is set to true")
405
+ end
264
406
  end
265
407
 
266
- def user_has_unsubscribed?
267
- #return true if user has unsubscribed
268
- return true unless NotifyUser::Unsubscribe.has_unsubscribed_from(self.target, self.type).empty?
269
-
270
- return false
408
+ def unsubscribed_validation
409
+ errors.add(:target, (" has unsubscribed from this type")) if user_has_unsubscribed?
271
410
  end
272
411
 
273
412
  def self.unsubscribed_from_channel?(user, type)
274
413
  #return true if user has unsubscribed
275
414
  return !NotifyUser::Unsubscribe.has_unsubscribed_from(user, type).empty?
276
415
  end
277
-
278
-
279
416
  end
280
417
  end