notify_user 0.1.4 → 0.3.1

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