├── VERSION ├── spec ├── dummy │ ├── log │ │ └── .keep │ ├── app │ │ ├── mailers │ │ │ └── .keep │ │ ├── models │ │ │ ├── .keep │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ ├── post.rb │ │ │ └── dummy_object.rb │ │ ├── assets │ │ │ ├── images │ │ │ │ └── .keep │ │ │ ├── javascripts │ │ │ │ └── application.js │ │ │ └── stylesheets │ │ │ │ └── application.css │ │ ├── controllers │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ ├── site_controller.rb │ │ │ └── application_controller.rb │ │ ├── views │ │ │ ├── notifly │ │ │ │ └── templates │ │ │ │ │ ├── mails │ │ │ │ │ ├── _hello.html.erb │ │ │ │ │ └── _destroy_smart.html.erb │ │ │ │ │ └── notifications │ │ │ │ │ ├── _foo.html │ │ │ │ │ ├── _hello.html │ │ │ │ │ ├── _save.html │ │ │ │ │ ├── _create.html │ │ │ │ │ ├── _destroy.html │ │ │ │ │ ├── _publish.html │ │ │ │ │ ├── _update.html │ │ │ │ │ ├── _be_smart.html │ │ │ │ │ ├── _change_title.html │ │ │ │ │ ├── _destroy_dummy.html │ │ │ │ │ └── _destroy_smart.html │ │ │ ├── site │ │ │ │ └── index.html.erb │ │ │ └── layouts │ │ │ │ └── application.html.erb │ │ └── helpers │ │ │ └── application_helper.rb │ ├── lib │ │ └── assets │ │ │ └── .keep │ ├── public │ │ ├── favicon.ico │ │ ├── 500.html │ │ ├── 422.html │ │ └── 404.html │ ├── config │ │ ├── initializers │ │ │ ├── notifly.rb │ │ │ ├── cookies_serializer.rb │ │ │ ├── session_store.rb │ │ │ ├── mime_types.rb │ │ │ ├── filter_parameter_logging.rb │ │ │ ├── assets.rb │ │ │ ├── backtrace_silencers.rb │ │ │ ├── wrap_parameters.rb │ │ │ ├── inflections.rb │ │ │ └── websocket_rails.rb │ │ ├── database.travis.yml │ │ ├── locales │ │ │ ├── notifly.en.yml │ │ │ └── en.yml │ │ ├── environment.rb │ │ ├── routes.rb │ │ ├── boot.rb │ │ ├── database.exemple.yml │ │ ├── events.rb │ │ ├── secrets.yml │ │ ├── application.rb │ │ └── environments │ │ │ ├── development.rb │ │ │ ├── test.rb │ │ │ └── production.rb │ ├── bin │ │ ├── rake │ │ ├── bundle │ │ └── rails │ ├── config.ru │ ├── db │ │ ├── migrate │ │ │ ├── 20141104121318_add_dummy_to_post.rb │ │ │ ├── 20141125105226_add_email_to_dummy_object.rb │ │ │ ├── 20141031165049_create_dummy_objects.rb │ │ │ └── 20141103191353_create_posts.rb │ │ ├── seeds.rb │ │ └── schema.rb │ ├── Rakefile │ ├── README.rdoc │ └── spec │ │ ├── features │ │ └── notifly │ │ │ ├── counter_spec.rb │ │ │ ├── loading_notifications_spec.rb │ │ │ └── read_notifications_spec.rb │ │ └── models │ │ ├── post_spec.rb │ │ └── dummy_object_spec.rb ├── support │ ├── email_helpers.rb │ ├── suppress_log.rb │ ├── shared_connection.rb │ ├── websocket_setup.rb │ └── wait_ajax.rb ├── rails_helper.rb ├── lib │ └── notifly │ │ └── models │ │ └── flyable_spec.rb ├── mailers │ └── notifly │ │ ├── async_spec.rb │ │ └── notification_mailer_spec.rb ├── spec_helper.rb └── models │ └── notifly │ └── notification_spec.rb ├── app ├── assets │ ├── images │ │ └── notifly │ │ │ └── .keep │ ├── javascripts │ │ ├── notifly_dropdown.js │ │ ├── notifly.js.erb │ │ └── notifly │ │ │ ├── seen_notifications.js.erb │ │ │ ├── more_notifications.js.erb │ │ │ ├── real_time.js.erb │ │ │ ├── get_notifications.js.erb │ │ │ └── read_notifications.js.erb │ └── stylesheets │ │ ├── notifly.css │ │ └── notifly │ │ └── layout.css ├── views │ └── notifly │ │ ├── templates │ │ ├── mails │ │ │ └── _default.html.erb │ │ └── notifications │ │ │ └── _default.html.erb │ │ ├── layouts │ │ ├── _empty.html.erb │ │ ├── _counter.html.erb │ │ ├── _index.html.erb │ │ ├── _actions.html.erb │ │ ├── _notification.html.erb │ │ └── _notifly.html.erb │ │ ├── notification_mailer │ │ └── notifly.html.erb │ │ └── notifications │ │ ├── read.js.erb │ │ ├── seen.js.erb │ │ ├── toggle_read.js.erb │ │ └── index.js.erb ├── controllers │ └── notifly │ │ ├── application_controller.rb │ │ └── notifications_controller.rb ├── helpers │ └── notifly │ │ └── view_helper.rb ├── mailers │ └── notifly │ │ └── notification_mailer.rb └── models │ └── notifly │ └── notification.rb ├── .rspec ├── lib ├── tasks │ └── notifly_tasks.rake ├── notifly │ ├── engine.rb │ ├── models │ │ ├── notifiable.rb │ │ ├── base.rb │ │ ├── options │ │ │ └── fly.rb │ │ └── flyable.rb │ └── railtie.rb ├── services │ ├── notification_channel.rb │ └── action_view_helper.rb ├── generators │ └── notifly │ │ ├── install │ │ ├── templates │ │ │ └── config │ │ │ │ └── initializers │ │ │ │ └── notifly.rb │ │ ├── utils.rb │ │ └── install_generator.rb │ │ └── views │ │ └── views_generator.rb └── notifly.rb ├── db └── migrate │ ├── 20141104150224_add_data_to_notification.rb │ ├── 20141125165636_add_mail_to_notifly_notifications.rb │ ├── 20141117193436_add_seen_to_notifly_notification.rb │ ├── 20141212122918_add_kind_to_notifly_notification.rb │ └── 20141103170528_create_notifly_notifications.rb ├── .gitignore ├── config ├── routes.rb └── locales │ └── en.yml ├── .travis.yml ├── .editorconfig ├── bin └── rails ├── Gemfile ├── Rakefile ├── MIT-LICENSE ├── vendor └── assets │ ├── javascripts │ ├── twitter │ │ └── bootstrap │ │ │ └── dropdown.js │ └── tinycon.js │ └── stylesheets │ └── twitter │ └── bootstrap.css ├── notifly.gemspec ├── Gemfile.lock └── README.md /VERSION: -------------------------------------------------------------------------------- 1 | 0.3.6 -------------------------------------------------------------------------------- /spec/dummy/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/dummy/app/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/dummy/app/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/dummy/lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/notifly/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/dummy/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/dummy/app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /spec/dummy/app/views/notifly/templates/mails/_hello.html.erb: -------------------------------------------------------------------------------- 1 | Hello mail -------------------------------------------------------------------------------- /app/views/notifly/templates/mails/_default.html.erb: -------------------------------------------------------------------------------- 1 | Default notification mail -------------------------------------------------------------------------------- /spec/dummy/app/views/notifly/templates/mails/_destroy_smart.html.erb: -------------------------------------------------------------------------------- 1 | Destroy! -------------------------------------------------------------------------------- /app/assets/javascripts/notifly_dropdown.js: -------------------------------------------------------------------------------- 1 | //= require 'twitter/bootstrap/dropdown' -------------------------------------------------------------------------------- /spec/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/dummy/app/views/notifly/templates/notifications/_foo.html: -------------------------------------------------------------------------------- 1 | _foo notification -------------------------------------------------------------------------------- /spec/dummy/app/views/notifly/templates/notifications/_hello.html: -------------------------------------------------------------------------------- 1 | _hello notification -------------------------------------------------------------------------------- /spec/dummy/app/views/notifly/templates/notifications/_save.html: -------------------------------------------------------------------------------- 1 | save notification -------------------------------------------------------------------------------- /spec/dummy/app/views/notifly/templates/notifications/_create.html: -------------------------------------------------------------------------------- 1 | create notification -------------------------------------------------------------------------------- /spec/dummy/app/views/notifly/templates/notifications/_destroy.html: -------------------------------------------------------------------------------- 1 | destroy notification -------------------------------------------------------------------------------- /spec/dummy/app/views/notifly/templates/notifications/_publish.html: -------------------------------------------------------------------------------- 1 | publish notification -------------------------------------------------------------------------------- /spec/dummy/app/views/notifly/templates/notifications/_update.html: -------------------------------------------------------------------------------- 1 | _update notification -------------------------------------------------------------------------------- /spec/support/email_helpers.rb: -------------------------------------------------------------------------------- 1 | def emails_sent 2 | ActionMailer::Base.deliveries 3 | end -------------------------------------------------------------------------------- /spec/dummy/app/views/notifly/templates/notifications/_be_smart.html: -------------------------------------------------------------------------------- 1 | _be_smart notification -------------------------------------------------------------------------------- /spec/dummy/app/views/notifly/templates/notifications/_change_title.html: -------------------------------------------------------------------------------- 1 | change title notification -------------------------------------------------------------------------------- /spec/dummy/app/views/notifly/templates/notifications/_destroy_dummy.html: -------------------------------------------------------------------------------- 1 | _destroy_dummy notification -------------------------------------------------------------------------------- /spec/dummy/app/views/notifly/templates/notifications/_destroy_smart.html: -------------------------------------------------------------------------------- 1 | _destroy_smart notification -------------------------------------------------------------------------------- /spec/dummy/config/initializers/notifly.rb: -------------------------------------------------------------------------------- 1 | Notifly.setup do |config| 2 | config.websocket = true 3 | end -------------------------------------------------------------------------------- /spec/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /spec/dummy/config/database.travis.yml: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: postgresql 3 | database: travis_ci_test 4 | username: postgres -------------------------------------------------------------------------------- /app/views/notifly/layouts/_empty.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= t('notifly.view.no_notifications') %> 3 |
4 | -------------------------------------------------------------------------------- /lib/tasks/notifly_tasks.rake: -------------------------------------------------------------------------------- 1 | # desc "Explaining what the task does" 2 | # task :notifly do 3 | # # Task goes here 4 | # end 5 | -------------------------------------------------------------------------------- /app/assets/stylesheets/notifly.css: -------------------------------------------------------------------------------- 1 | /* 2 | *= require notifly/layout 3 | *= require twitter/bootstrap 4 | *= require font-awesome 5 | */ -------------------------------------------------------------------------------- /spec/support/suppress_log.rb: -------------------------------------------------------------------------------- 1 | # http://helabs.com.br/blog/2013/02/06/testes-mais-rapidos-no-rspec 2 | 3 | Rails.logger.level = 4 unless ENV['WITH_LOG'] -------------------------------------------------------------------------------- /spec/dummy/config/locales/notifly.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | notifly: 3 | mail_subject: 4 | default: 'You have a new notification' 5 | hello: 'Hello!' -------------------------------------------------------------------------------- /app/views/notifly/notification_mailer/notifly.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "notifly/templates/mails/#{@template}", 2 | locals: { notification: @notification } %> -------------------------------------------------------------------------------- /spec/dummy/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /app/controllers/notifly/application_controller.rb: -------------------------------------------------------------------------------- 1 | module Notifly 2 | class ApplicationController < ::ApplicationController 3 | layout false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json -------------------------------------------------------------------------------- /spec/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /db/migrate/20141104150224_add_data_to_notification.rb: -------------------------------------------------------------------------------- 1 | class AddDataToNotification < ActiveRecord::Migration 2 | def change 3 | add_column :notifly_notifications, :data, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_test_app_session' 4 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20141104121318_add_dummy_to_post.rb: -------------------------------------------------------------------------------- 1 | class AddDummyToPost < ActiveRecord::Migration 2 | def change 3 | add_reference :posts, :dummy_object, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20141125105226_add_email_to_dummy_object.rb: -------------------------------------------------------------------------------- 1 | class AddEmailToDummyObject < ActiveRecord::Migration 2 | def change 3 | add_column :dummy_objects, :email, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /app/views/notifly/layouts/_counter.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= counter %> 4 | -------------------------------------------------------------------------------- /db/migrate/20141125165636_add_mail_to_notifly_notifications.rb: -------------------------------------------------------------------------------- 1 | class AddMailToNotiflyNotifications < ActiveRecord::Migration 2 | def change 3 | add_column :notifly_notifications, :mail, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | log/*.log 3 | pkg/ 4 | spec/dummy/db/*.sqlite3 5 | spec/dummy/db/*.sqlite3-journal 6 | spec/dummy/log/*.log 7 | spec/dummy/tmp/ 8 | spec/dummy/.sass-cache 9 | spec/dummy/config/database.yml 10 | -------------------------------------------------------------------------------- /app/views/notifly/notifications/read.js.erb: -------------------------------------------------------------------------------- 1 | <% rendered_index = render partial: 'notifly/layouts/index', 2 | locals: { notifications: @notifications } %> 3 | 4 | $('#notifly-notifications-content').html("<%= j rendered_index %>"); 5 | -------------------------------------------------------------------------------- /app/views/notifly/templates/notifications/_default.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= notification.receiver.try(:name) %>, you receive a new notification at <%= notification.created_at %> 4 | -------------------------------------------------------------------------------- /db/migrate/20141117193436_add_seen_to_notifly_notification.rb: -------------------------------------------------------------------------------- 1 | class AddSeenToNotiflyNotification < ActiveRecord::Migration 2 | def change 3 | add_column :notifly_notifications, :seen, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20141212122918_add_kind_to_notifly_notification.rb: -------------------------------------------------------------------------------- 1 | class AddKindToNotiflyNotification < ActiveRecord::Migration 2 | def change 3 | add_column :notifly_notifications, :kind, :string, default: :notification 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | mount Notifly::Engine => '/notifly', as: 'notifly' 3 | 4 | post 'create', to: 'site#create_notification', as: :create_notification 5 | root to: 'site#index' 6 | end 7 | -------------------------------------------------------------------------------- /lib/notifly/engine.rb: -------------------------------------------------------------------------------- 1 | require 'jquery-rails' 2 | 3 | module Notifly 4 | class Engine < ::Rails::Engine 5 | isolate_namespace Notifly 6 | 7 | config.generators do |g| 8 | g.test_framework :rspec 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/dummy/app/views/site/index.html.erb: -------------------------------------------------------------------------------- 1 |

Site#index

2 | <% if current_user %> 3 |

User logged in: <%= current_user.name %>

4 | <% end %> 5 |

<%= link_to 'create notification', create_notification_path, remote: true, method: :post %>

-------------------------------------------------------------------------------- /app/views/notifly/notifications/seen.js.erb: -------------------------------------------------------------------------------- 1 | $('#notifly-counter').replaceWith("<%= j(render 'notifly/layouts/counter', counter: @counter)%>"); 2 | $('#notifly-icon').replaceWith("<%= j notifly_icon(@counter > 0) %>"); 3 | Tinycon.setBubble(<%= @counter %>); 4 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20141031165049_create_dummy_objects.rb: -------------------------------------------------------------------------------- 1 | class CreateDummyObjects < ActiveRecord::Migration 2 | def change 3 | create_table :dummy_objects do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/notifly/layouts/_index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <% notifications.each do |notification| %> 4 | <%= render partial: 'notifly/layouts/notification', 5 | locals: { notification: notification } %> 6 | <% end %> -------------------------------------------------------------------------------- /spec/dummy/app/controllers/site_controller.rb: -------------------------------------------------------------------------------- 1 | class SiteController < ApplicationController 2 | def index 3 | end 4 | 5 | def create_notification 6 | Notifly::Notification.create! receiver: current_user 7 | render nothing: true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) 6 | -------------------------------------------------------------------------------- /spec/dummy/config/database.exemple.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: postgresql 3 | encoding: unicode 4 | user: postgres 5 | pool: 5 6 | 7 | development: 8 | <<: *default 9 | database: notifly_development 10 | test: 11 | <<: *default 12 | database: notifly_test -------------------------------------------------------------------------------- /app/views/notifly/notifications/toggle_read.js.erb: -------------------------------------------------------------------------------- 1 | <% rendered_notification = render partial: 'notifly/layouts/notification', 2 | locals: { notification: @notification } %> 3 | 4 | $('<%= "#notifly-notification-#{@notification.id}" %>') 5 | .replaceWith('<%= j rendered_notification %>') -------------------------------------------------------------------------------- /lib/notifly/models/notifiable.rb: -------------------------------------------------------------------------------- 1 | module Notifly 2 | module Models 3 | module Notifiable 4 | extend ActiveSupport::Concern 5 | 6 | def notifly!(args={}) 7 | Notifly::Notification.create! args.merge(receiver: self) 8 | end 9 | end 10 | end 11 | end -------------------------------------------------------------------------------- /spec/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/views/notifly/layouts/_actions.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <% link_text = notification.read ? t('notifly.view.unread') : t('notifly.view.read') %> 4 | <%= link_to link_text, notification_toggle_read_path(notification), method: :put, remote: true %> -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20141103191353_create_posts.rb: -------------------------------------------------------------------------------- 1 | class CreatePosts < ActiveRecord::Migration 2 | def change 3 | create_table :posts do |t| 4 | t.string :author 5 | t.string :title 6 | t.text :content 7 | t.boolean :published, default: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/notifly/models/base.rb: -------------------------------------------------------------------------------- 1 | require_relative 'notifiable' 2 | require_relative 'flyable' 3 | 4 | module Notifly 5 | module Models 6 | module Base 7 | extend ActiveSupport::Concern 8 | 9 | include Notifly::Models::Notifiable 10 | include Notifly::Models::Flyable 11 | end 12 | end 13 | end -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Notifly::Engine.routes.draw do 2 | put '/notifications/(:notification_id)/toggle_read', to: 'notifications#toggle_read', as: :notification_toggle_read 3 | 4 | resources :notifications, only: [:index] do 5 | collection do 6 | put :read 7 | put :seen 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | notifly: 3 | mail_subject: 4 | default: 'You have a new notification' 5 | view: 6 | header: 'Notifications' 7 | mark_as_read: 'Mark as read' 8 | loading: 'Loading notifications...' 9 | more: 'More' 10 | read: 'read' 11 | unread: 'unread' 12 | no_notifications: 'You have no notifications' -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | before_script: 4 | - cp spec/dummy/config/database.travis.yml spec/dummy/config/database.yml 5 | - psql -c 'create database travis_ci_test;' -U postgres 6 | - bundle install 7 | - bundle exec rake db:migrate 8 | 9 | rvm: 10 | - 2.1 11 | env: 12 | - WEBSOCKET=true 13 | - WEBSOCKET=false 14 | 15 | script: bundle exec rspec spec 16 | -------------------------------------------------------------------------------- /spec/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TestApp 5 | <%= stylesheet_link_tag 'application', media: 'all' %> 6 | <%= javascript_include_tag 'application' %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | <%= yield %> 12 | <%= notiflies if DummyObject.first %> 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 4 | charset = utf-8 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | 8 | [*.rb] 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [Rakefile] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [Gemfile*] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [config.ru] 21 | indent_style = space 22 | indent_size = 2 -------------------------------------------------------------------------------- /spec/dummy/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Precompile additional assets. 7 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 8 | # Rails.application.config.assets.precompile += %w( search.js ) 9 | -------------------------------------------------------------------------------- /lib/notifly/railtie.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../app/helpers/notifly/view_helper' 2 | require_relative 'models/base' 3 | 4 | module Notifly 5 | class Railtie < Rails::Railtie 6 | initializer 'Notifly.view_helpers' do 7 | ActionView::Base.send :include, ViewHelper 8 | end 9 | 10 | initializer 'Notifly.active_model_helpers' do 11 | ActiveRecord::Base.send :include, Notifly::Models::Base 12 | end 13 | end 14 | end -------------------------------------------------------------------------------- /spec/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | 6 | def current_user 7 | DummyObject.first 8 | end 9 | 10 | ActiveSupport.on_load(:action_controller) do 11 | helper_method :current_user 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /db/migrate/20141103170528_create_notifly_notifications.rb: -------------------------------------------------------------------------------- 1 | class CreateNotiflyNotifications < ActiveRecord::Migration 2 | def change 3 | create_table :notifly_notifications do |t| 4 | t.string :template 5 | t.boolean :read, default: false 6 | t.references :target, index: true, polymorphic: true 7 | t.references :sender, index: true, polymorphic: true 8 | t.references :receiver, index: true, polymorphic: true 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/support/shared_connection.rb: -------------------------------------------------------------------------------- 1 | # http://helabs.com.br/blog/2013/02/06/testes-mais-rapidos-no-rspec 2 | 3 | class ActiveRecord::Base 4 | mattr_accessor :shared_connection 5 | @@shared_connection = nil 6 | 7 | def self.connection 8 | @@shared_connection || retrieve_connection 9 | end 10 | end 11 | 12 | # Forces all threads to share the same connection. This works on 13 | # Capybara because it starts the web server in a thread. 14 | ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection -------------------------------------------------------------------------------- /app/helpers/notifly/view_helper.rb: -------------------------------------------------------------------------------- 1 | module Notifly 2 | module ViewHelper 3 | def notiflies 4 | notiflies_for current_user 5 | end 6 | 7 | def notiflies_for(receiver) 8 | render partial: 'notifly/layouts/notifly', locals: { receiver: receiver } 9 | end 10 | 11 | def notifly_icon(have_notifications=false) 12 | icon = have_notifications ? Notifly.icon : Notifly.icon_empty 13 | size = Notifly.icon_size 14 | fa_icon "#{icon} #{size}", id: 'notifly-icon' 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application. 3 | 4 | ENGINE_ROOT = File.expand_path('../..', __FILE__) 5 | ENGINE_PATH = File.expand_path('../../lib/notifly/engine', __FILE__) 6 | 7 | # Set up gems listed in the Gemfile. 8 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 9 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 10 | 11 | require 'rails/all' 12 | require 'rails/engine/commands' 13 | -------------------------------------------------------------------------------- /spec/dummy/db/seeds.rb: -------------------------------------------------------------------------------- 1 | puts 'Seeding...' 2 | DummyObject.delete_all 3 | Post.delete_all 4 | Notifly::Notification.delete_all 5 | 6 | dummy = DummyObject.create! name: 'dummy', email: 'dummy@exemple.com' 7 | smart = DummyObject.create! name: 'smart', email: 'smart@exemple.com' 8 | 9 | 12.times do |n| 10 | post = Post.create! author: "author #{n}", published: false, dummy_object: dummy 11 | 12 | Notifly::Notification.create! template: :default, read: false, seen: false, target: post, 13 | sender: smart, receiver: dummy, mail: :never 14 | sleep(0.5) 15 | end -------------------------------------------------------------------------------- /app/assets/javascripts/notifly.js.erb: -------------------------------------------------------------------------------- 1 | //= require 'jquery' 2 | //= require 'jquery_ujs' 3 | //= require 'tinycon' 4 | //= require 'notifly/get_notifications' 5 | //= require 'notifly/seen_notifications' 6 | //= require 'notifly/read_notifications' 7 | //= require 'notifly/more_notifications' 8 | <% require_asset 'notifly/real_time' if Notifly.websocket %> 9 | 10 | $(document).ready(function() { 11 | $(document).on('click', '#notifly-notifications-panel.dropdown-menu', function (e) { 12 | $('#notifly').hasClass('keep_open') && e.stopPropagation(); 13 | }); 14 | 15 | Tinycon.setBubble(0); 16 | }); -------------------------------------------------------------------------------- /spec/support/websocket_setup.rb: -------------------------------------------------------------------------------- 1 | Capybara.javascript_driver = :poltergeist 2 | 3 | Capybara.register_driver :chrome do |app| 4 | Capybara::Selenium::Driver.new(app, :browser => :chrome) 5 | end 6 | 7 | Capybara.run_server = true 8 | Capybara.server do |app, port| 9 | require 'rack/handler/thin' 10 | Rack::Handler::Thin.run(app, :Port => port) 11 | end 12 | 13 | RSpec.configure do |c| 14 | c.before(:each) do |example| 15 | Capybara.current_driver = example.metadata[:driver] || :poltergeist 16 | end 17 | 18 | c.before(:suite) { Notifly.websocket = true if ENV['WEBSOCKET'] == 'true' } 19 | end -------------------------------------------------------------------------------- /spec/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /spec/dummy/README.rdoc: -------------------------------------------------------------------------------- 1 | == README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | 26 | 27 | Please feel free to use a different markup language if you do not plan to run 28 | rake doc:app. 29 | -------------------------------------------------------------------------------- /lib/services/notification_channel.rb: -------------------------------------------------------------------------------- 1 | module Notifly 2 | class NotificationChannel 3 | def initialize(user_id) 4 | @user_id = user_id 5 | @channel = WebsocketRails.users[@user_id.to_s] 6 | @action_view = Notifly::ActionViewHelper.new 7 | end 8 | 9 | def trigger(notification) 10 | @channel.send_message 'notifly.notifications.new', 11 | { message: render(notification), id: notification.id } 12 | end 13 | 14 | def render(notification) 15 | @action_view.render partial: 'layouts/notification', 16 | locals: { notification: notification } 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /lib/generators/notifly/install/templates/config/initializers/notifly.rb: -------------------------------------------------------------------------------- 1 | Notifly.setup do |config| 2 | # Define how many notifications per page. 3 | config.per_page = 10 4 | 5 | # Define the time interval in miliseconfs between requests to check notifications. 6 | config.timeout = 10000 7 | 8 | # Define the notifly icon from font-awesome-rails 9 | config.icon = 'bell' 10 | 11 | # Define the notifly icon size 12 | config.icon_size = '2x' 13 | 14 | # Define your mailer sender 15 | config.mailer_sender = 'change-me-at-config-initializers-notifly@exemple.com' 16 | 17 | # Active websocket 18 | config.websocket = false 19 | end -------------------------------------------------------------------------------- /app/assets/javascripts/notifly/seen_notifications.js.erb: -------------------------------------------------------------------------------- 1 | <% notifly = Notifly::Engine.routes.url_helpers %> 2 | 3 | var _notiflySeenNotifications = function () { 4 | var $trigger = $('#notifly-trigger'); 5 | 6 | if($trigger.length !== 0) { 7 | $trigger.click(function () { 8 | $.ajax({ 9 | url: '<%= notifly.seen_notifications_path %>', 10 | data: { 11 | first_notification_id: notiflyFirstNotification, 12 | last_notification_id: notiflyLastNotification 13 | }, 14 | type: 'PUT' 15 | }); 16 | }); 17 | } 18 | }; 19 | 20 | $(document).ready(function() { 21 | _notiflySeenNotifications(); 22 | }); 23 | -------------------------------------------------------------------------------- /app/views/notifly/layouts/_notification.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <% read = notification.read ? '' : 'notifly-notification-not-read' %> 4 | 5 |
6 |
7 | <%= render partial: "notifly/templates/notifications/#{notification.template}", 8 | locals: { notification: notification } %> 9 |
10 | 11 | 12 | <%= render partial: 'notifly/layouts/actions', locals: { notification: notification } %> 13 | 14 |
-------------------------------------------------------------------------------- /app/assets/javascripts/notifly/more_notifications.js.erb: -------------------------------------------------------------------------------- 1 | <% notifly = Notifly::Engine.routes.url_helpers %> 2 | 3 | var _notiflyMoreNotifications = function () { 4 | var $notifly = $('#notifly-notifications-content'); 5 | 6 | if($notifly.length !== 0) { 7 | $('#notifly-more-notifications-link').click(function () { 8 | $.ajax({ 9 | url: '<%= notifly.notifications_path %>', 10 | data: { 11 | scope: 'older', 12 | reference_notification_id: notiflyLastNotification, 13 | mark_as_seen: true 14 | }, 15 | type: 'GET' 16 | }); 17 | }); 18 | } 19 | }; 20 | 21 | $(document).ready(function() { 22 | _notiflyMoreNotifications(); 23 | }); 24 | -------------------------------------------------------------------------------- /spec/dummy/app/models/post.rb: -------------------------------------------------------------------------------- 1 | class Post < ActiveRecord::Base 2 | belongs_to :dummy_object 3 | 4 | notifly default_values: { receiver: :dummy_object, target: :self } 5 | 6 | notifly before: :destroy, template: :destroy, data: :attributes 7 | 8 | notifly after: :publish!, template: :publish 9 | notifly before: :change_title, data: -> { { title_before_create: self.title } }, 10 | unless: -> { self.title == 'TitleFoo' } 11 | notifly after: :change_title, data: :attributes, template: :change_title, 12 | if: -> { self.title == 'NewTitle' } 13 | 14 | def publish! 15 | update(published: true) 16 | end 17 | 18 | def change_title(title='NewTitle') 19 | self.title = title 20 | self.save 21 | end 22 | end -------------------------------------------------------------------------------- /spec/dummy/config/events.rb: -------------------------------------------------------------------------------- 1 | WebsocketRails::EventMap.describe do 2 | # You can use this file to map incoming events to controller actions. 3 | # One event can be mapped to any number of controller actions. The 4 | # actions will be executed in the order they were subscribed. 5 | # 6 | # Uncomment and edit the next line to handle the client connected event: 7 | # subscribe :client_connected, :to => Controller, :with_method => :method_name 8 | # 9 | # Here is an example of mapping namespaced events: 10 | # namespace :product do 11 | # subscribe :new, :to => ProductController, :with_method => :new_product 12 | # end 13 | # The above will handle an event triggered on the client like `product.new`. 14 | end 15 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /spec/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require websocket_rails/main 14 | //= require notifly 15 | //= require notifly_dropdown 16 | -------------------------------------------------------------------------------- /app/mailers/notifly/notification_mailer.rb: -------------------------------------------------------------------------------- 1 | module Notifly 2 | class NotificationMailer < ActionMailer::Base 3 | default from: Notifly.mailer_sender 4 | 5 | def notifly(to: nil, notification_id: nil, template: nil) 6 | if defined? Delayed::Job or defined? Sidekiq::Worker 7 | delay.send_notification(to, notification_id, template) 8 | else 9 | send_notification(to, notification_id, template).deliver 10 | end 11 | end 12 | 13 | private 14 | def send_notification(to, notification_id, template) 15 | @notification = Notifly::Notification.find(notification_id) 16 | @template = template 17 | 18 | mail to: to, subject: t("notifly.mail_subject.#{@template}") 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV["RAILS_ENV"] ||= 'test' 3 | require 'spec_helper' 4 | require File.expand_path("../dummy/config/environment", __FILE__) 5 | require 'rspec/rails' 6 | require 'shoulda/matchers' 7 | require 'capybara/rails' 8 | require 'capybara/rspec' 9 | Capybara.javascript_driver = :poltergeist 10 | 11 | Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each { |f| require f } 12 | 13 | # Checks for pending migrations before tests are run. 14 | # ActiveRecord::Migration.maintain_test_schema! 15 | 16 | RSpec.configure do |config| 17 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 18 | config.use_transactional_fixtures = true 19 | config.infer_spec_type_from_file_location! 20 | end 21 | -------------------------------------------------------------------------------- /lib/generators/notifly/install/utils.rb: -------------------------------------------------------------------------------- 1 | # github.com/sferik/rails_admin/blob/master/lib/generators/rails_admin/utils.rb 2 | 3 | module Notifly 4 | module Generators 5 | module Utils 6 | module InstanceMethods 7 | def display(output, color = :green) 8 | say(" - #{output}", color) 9 | end 10 | 11 | def ask_for(wording, default_value = nil, override_if_present_value = nil) 12 | if override_if_present_value.present? 13 | display("Using [#{override_if_present_value}] for question '#{wording}'") && override_if_present_value 14 | else 15 | ask(" ? #{wording} Press for [#{default_value}] >", :yellow).presence || default_value 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/lib/notifly/models/flyable_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | module Notifly 4 | RSpec.describe Notifly::Models::Flyable do 5 | let!(:dummy) { DummyObject.create name: 'Dummy', email: 'dummy@test.com' } 6 | 7 | before(:each) do 8 | allow(Notifly::Notification).to receive(:create). 9 | and_raise(ActiveRecord::RecordInvalid, Notifly::Notification.new) 10 | expect(Rails.logger).to receive(:error) 11 | end 12 | 13 | after(:each) { Rails.env = 'test' } 14 | 15 | it 'should not raise an error if something wrong happened in production' do 16 | Rails.env = 'production' 17 | 18 | expect { dummy.be_smart }.to_not raise_error 19 | end 20 | 21 | it 'should raise an error if something wrong happened' do 22 | expect { dummy.be_smart }.to raise_error 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require notifly 14 | *= require_tree . 15 | *= require_self 16 | */ 17 | 18 | body { 19 | background: #f5f5f5; 20 | } -------------------------------------------------------------------------------- /app/assets/javascripts/notifly/real_time.js.erb: -------------------------------------------------------------------------------- 1 | window.dispatcher = new WebSocketRails(location.host + '/websocket'); 2 | var notiflyFirstNotification; 3 | 4 | $(function () { 5 | var $notifly = $('#notifly'); 6 | 7 | if ($notifly !== undefined) { 8 | dispatcher.bind('notifly.notifications.new', _injectNotificationFrom); 9 | } 10 | }); 11 | 12 | var _injectNotificationFrom = function (data) { 13 | if (data.message) { 14 | $('#notifly-notifications-content .notifly-empty').remove(); 15 | notiflyFirstNotification = data.id; 16 | 17 | if (!$('#notifly').hasClass('open')) { 18 | $counter = $('#notifly-counter'); 19 | $counter.html(eval($counter.html()) + 1); 20 | $counter.removeClass('hide'); 21 | } 22 | 23 | return $('#notifly').find('#notifly-notifications-content') 24 | .prepend(data.message) 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /app/assets/javascripts/notifly/get_notifications.js.erb: -------------------------------------------------------------------------------- 1 | <% notifly = Notifly::Engine.routes.url_helpers %> 2 | var notiflyLastNotification, notiflyFirstNotification; 3 | 4 | $(document).ready(function() { 5 | _notiflyGetNotifications(); 6 | }); 7 | 8 | var _notiflyGetNotifications = function () { 9 | var $notifly = $('#notifly'); 10 | 11 | if($notifly.length !== 0) { 12 | $.ajax({ 13 | url: '<%= notifly.notifications_path %>', 14 | data: { 15 | scope: 'newer', 16 | reference_notification_id: notiflyFirstNotification || null, 17 | mark_as_seen: $notifly.hasClass('open') 18 | }, 19 | type: 'GET', 20 | complete: _notiflyRepeatRequest 21 | }); 22 | } 23 | }; 24 | 25 | var _notiflyRepeatRequest = function () { 26 | <% if not Notifly.websocket %> 27 | setTimeout(_notiflyGetNotifications, <%= Notifly.timeout %>); 28 | <% end %> 29 | }; 30 | -------------------------------------------------------------------------------- /spec/mailers/notifly/async_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | module Notifly 4 | RSpec.describe 'Async Mail' do 5 | let(:dummy) { DummyObject.create name: 'Dummy', email: 'dummy@test.com' } 6 | 7 | it 'should not call #delay with app does not use delayed_job or sidekiq' do 8 | expect_any_instance_of(NotificationMailer).not_to receive(:delay) 9 | dummy.be_smart 10 | end 11 | 12 | it 'should call #delay with app use delayed_job' do 13 | stub_const 'Delayed::Job', Module.new 14 | 15 | expect_any_instance_of(NotificationMailer).to receive_message_chain('delay.send_notification') 16 | dummy.be_smart 17 | end 18 | 19 | it 'should call #delay with app use delayed_job' do 20 | stub_const 'Sidekiq::Worker', Module.new 21 | 22 | expect_any_instance_of(NotificationMailer).to receive_message_chain('delay.send_notification') 23 | dummy.be_smart 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rails', '4.1.8' 4 | gem 'jquery-rails', ['>= 3.0', '< 5'] 5 | gem 'font-awesome-rails', '4.5.0' 6 | gem 'coffee-rails', '4.1.1' 7 | 8 | group :development, :test do 9 | gem 'pg', '0.18.2' 10 | gem 'pry-rails', '0.3.4' 11 | gem 'pry-rescue', '1.4.2' 12 | gem 'awesome_print', '1.6.1' 13 | gem 'websocket-rails', '0.7.0' 14 | gem 'thin', '1.6.3' 15 | end 16 | 17 | group :test do 18 | gem 'rspec-rails', '3.1.0' 19 | gem 'capybara', '2.4.4' 20 | gem 'poltergeist', '1.5.1' 21 | gem 'phantomjs', '1.9.8.0', require: 'phantomjs/poltergeist' 22 | gem 'shoulda-matchers', '2.7.0' 23 | gem 'launchy', '2.4.3' 24 | end 25 | 26 | group :development do 27 | gem 'better_errors', '2.1.1' 28 | gem 'binding_of_caller', '0.7.2' 29 | gem 'jeweler', '2.0.1', require: false 30 | end 31 | -------------------------------------------------------------------------------- /app/views/notifly/layouts/_notifly.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/generators/notifly/install/install_generator.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../utils', __FILE__) 2 | 3 | module Notifly 4 | module Generators 5 | class InstallGenerator < Rails::Generators::Base 6 | include Generators::Utils::InstanceMethods 7 | source_root File.expand_path('../templates', __FILE__) 8 | argument :namespace, type: :string, required: false, desc: 'Notifly url namespace' 9 | 10 | desc 'Notifly installation generator' 11 | 12 | def mount_engine 13 | namespace = ask_for('Where do you want to mount Notifly?', 'notifly', namespace) 14 | route("mount Notifly::Engine => '/#{namespace}', as: 'notifly'") 15 | end 16 | 17 | def copy_config 18 | template 'config/initializers/notifly.rb' 19 | copy_file '../../../../../config/locales/en.yml', 'config/locales/notifly.en.yml' 20 | end 21 | 22 | def remember_to_install_migrations 23 | display 'Remember to install migrations: "rake notifly:install:migrations"', :red 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/notifly.rb: -------------------------------------------------------------------------------- 1 | require 'notifly/engine' 2 | require 'notifly/railtie' 3 | require 'services/notification_channel' 4 | require 'services/action_view_helper' 5 | require 'font-awesome-rails' 6 | require 'websocket-rails' 7 | 8 | module Notifly 9 | # How many notifications per page. 10 | mattr_accessor :per_page 11 | @@per_page = 10 12 | 13 | mattr_accessor :icon_size 14 | @@icon_size = '2x' 15 | 16 | mattr_accessor :icon 17 | @@icon = 'bell' 18 | 19 | mattr_accessor :icon_empty 20 | @@icon_empty = 'bell-o' 21 | 22 | mattr_accessor :mailer_sender 23 | @@mailer_sender = 'change-me-at-config-initializers-notifly@exemple.com' 24 | 25 | # Timeout used when notifly get new notifications per request 26 | mattr_accessor :timeout 27 | @@timeout = 10000 28 | 29 | # Active websocket 30 | mattr_accessor :websocket 31 | @@websocket = false 32 | 33 | # Default way to setup Notifly. Run rails generate notifly:install to create 34 | # a fresh initializer with all configuration values. 35 | def self.setup 36 | yield self 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/dummy/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 9444b7aeea2219e0b4bc563cb1129f0064e1caa65562545d7c851f32135adad914a90d111a5510f29552317c4171fde77e08e61ba4797d19f75693ef76d7f26e 15 | 16 | test: 17 | secret_key_base: 0012b892a47e20248312bce6225eb806629e22739bdea6e0cf91a57656085c05735cd02b4378495df903d6b31a0666f0f90ba5728aa41e8fe3d4b208cb24bb75 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rubygems' 4 | require 'bundler' 5 | 6 | begin 7 | Bundler.setup(:default, :development) 8 | rescue Bundler::BundlerError => e 9 | $stderr.puts e.message 10 | $stderr.puts "Run `bundle install` to install missing gems" 11 | exit e.status_code 12 | end 13 | 14 | require 'rake' 15 | 16 | require 'jeweler' 17 | Jeweler::Tasks.new do |gem| 18 | gem.name = 'notifly' 19 | gem.authors = ['Pedro Passalini', 'Rafael Carvalho'] 20 | gem.email = ['henrique.passalini@gmail.com', 'rafael@algorich.com.br'] 21 | gem.homepage = 'https://github.com/algorich/notifly' 22 | gem.summary = 'A full notification system' 23 | gem.description = 'This project intend to offer a full notification system, back and front-end.' 24 | gem.license = 'MIT' 25 | gem.files = Dir['{app,config,db,lib,vendor}/**/*', 'MIT-LICENSE', 'Rakefile', 'README.md'] 26 | end 27 | Jeweler::RubygemsDotOrgTasks.new 28 | 29 | APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__) 30 | load 'rails/tasks/engine.rake' 31 | 32 | Bundler::GemHelper.install_tasks -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 YOURNAME 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /app/assets/stylesheets/notifly/layout.css: -------------------------------------------------------------------------------- 1 | .notifly-counter-label { 2 | padding: 3px 7px 3px 7px; 3 | background: #cc0000; 4 | color: #ffffff; 5 | font-weight: bold; 6 | border-radius: 9px; 7 | -moz-border-radius: 9px; 8 | -webkit-border-radius: 9px; 9 | position: absolute; 10 | margin-top: -8px; 11 | margin-left: 12px; 12 | font-size: 11px; 13 | } 14 | 15 | #notifly-notifications-panel { 16 | padding: 0px !important; 17 | border: 0px !important; 18 | } 19 | 20 | #notifly-notifications-header { 21 | padding: 9px; 22 | font-weight: bold; 23 | font-size: 13px; 24 | border-bottom: 1px solid #dddddd; 25 | } 26 | 27 | #notifly-notifications-footer { 28 | padding: 9px; 29 | text-align: center; 30 | font-weight: bold; 31 | font-size: 12px; 32 | border-top: 1px solid #dddddd; 33 | } 34 | 35 | .notifly-notification-not-read { 36 | background-color: #e9eaed; 37 | } 38 | 39 | .notifly-notification { 40 | padding: 9px; 41 | } 42 | 43 | #notifly-notifications-panel { 44 | width: 430px; 45 | } 46 | 47 | #notifly-notifications-content { 48 | overflow-y: scroll; 49 | max-height: 300px; 50 | } -------------------------------------------------------------------------------- /lib/services/action_view_helper.rb: -------------------------------------------------------------------------------- 1 | module Notifly 2 | class ActionViewHelper 3 | attr_reader :action_view 4 | delegate *ActionView::Base.instance_methods.reject { |m| [:object_id, :__send__].include? m }, to: :action_view 5 | 6 | def initialize 7 | notifly_path = File.expand_path(File.dirname(File.dirname(__FILE__))) + '../../app/views/notifly' 8 | rails_path = File.join(Rails.root, 'app/views/notifly') 9 | 10 | ActionController::Base.prepend_view_path(notifly_path) 11 | ActionController::Base.prepend_view_path(rails_path) 12 | 13 | @action_view = ActionView::Base.new(ActionController::Base.view_paths) 14 | @action_view.extend ApplicationHelper 15 | 16 | @action_view.class_eval do 17 | include Notifly::Engine.routes.url_helpers 18 | Dir[File.join(Rails.root, 'app/helpers/**/*.rb')].each do |f| 19 | require f 20 | include f.split('/').last.split('.').first.camelize.constantize 21 | end 22 | 23 | def protect_against_forgery? 24 | false 25 | end 26 | 27 | def main_app 28 | Rails.application.routes.url_helpers 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/assets/javascripts/notifly/read_notifications.js.erb: -------------------------------------------------------------------------------- 1 | <% notifly = Notifly::Engine.routes.url_helpers %> 2 | 3 | $(document).ready(function() { 4 | _notiflyReadNotifications(); 5 | _notiflyReadNotificationWithLink(); 6 | }); 7 | 8 | var _notiflyReadNotifications = function () { 9 | var $notifly = $('#notifly-mark-as-read-link'); 10 | 11 | if($notifly.length !== 0) { 12 | $notifly.click(function () { 13 | $.ajax({ 14 | url: '<%= notifly.read_notifications_path %>', 15 | data: { 16 | first_notification_id: notiflyFirstNotification, 17 | last_notification_id: notiflyLastNotification 18 | }, 19 | type: 'PUT' 20 | }); 21 | }); 22 | } 23 | }; 24 | 25 | var _notiflyReadNotificationWithLink = function () { 26 | if ($('#notifly').length > 0) { 27 | $('#notifly').on('click', '.notifly-notification-message a', function () { 28 | var notificationId = $(this).closest('div.notifly-notification').data('id'); 29 | 30 | $.ajax({ 31 | url: '<%= notifly.notification_toggle_read_path %>', 32 | data: { 33 | notification_id: notificationId, 34 | read: true 35 | }, 36 | type: 'PUT' 37 | }); 38 | }); 39 | } 40 | }; -------------------------------------------------------------------------------- /spec/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | # Pick the frameworks you want: 4 | require "active_record/railtie" 5 | require "action_controller/railtie" 6 | require "action_mailer/railtie" 7 | require "action_view/railtie" 8 | require "sprockets/railtie" 9 | # require "rails/test_unit/railtie" 10 | 11 | Bundler.require(*Rails.groups) 12 | require "notifly" 13 | 14 | module TestApp 15 | class Application < Rails::Application 16 | # Settings in config/environments/* take precedence over those specified here. 17 | # Application configuration should go into files in config/initializers 18 | # -- all .rb files in that directory are automatically loaded. 19 | 20 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 21 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 22 | # config.time_zone = 'Central Time (US & Canada)' 23 | 24 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 25 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 26 | # config.i18n.default_locale = :de 27 | config.middleware.delete Rack::Lock 28 | end 29 | end 30 | 31 | -------------------------------------------------------------------------------- /lib/generators/notifly/views/views_generator.rb: -------------------------------------------------------------------------------- 1 | module Notifly 2 | module Generators 3 | class ViewsGenerator < Rails::Generators::Base 4 | source_root File.expand_path('../../../../../app/views/notifly', __FILE__) 5 | 6 | class_option :layout, type: :boolean, default: false, 7 | desc: 'Include/Remove layout files.' 8 | class_option :mail, type: :boolean, default: false, 9 | desc: 'Include/Remove mails templates files.' 10 | class_option :notification, type: :boolean, default: false, 11 | desc: 'Include/Remove notifications templates files.' 12 | 13 | def copy_views 14 | notifications_templates if options.notification? 15 | notifications_mails_templates if options.mail? 16 | layout_files if options.layout? 17 | end 18 | 19 | private 20 | def main_app_path 21 | 'app/views/notifly' 22 | end 23 | 24 | def notifications_templates 25 | directory 'templates/notifications', "#{main_app_path}/templates/notifications" 26 | end 27 | 28 | def notifications_mails_templates 29 | directory 'templates/mails', "#{main_app_path}/templates/mails" 30 | end 31 | 32 | def layout_files 33 | directory 'layouts', "#{main_app_path}/layouts" 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/notifly/models/options/fly.rb: -------------------------------------------------------------------------------- 1 | module Notifly 2 | module Models 3 | module Options 4 | class Fly 5 | attr_accessor :before, :after, :template, :sender, :receiver, :target, 6 | :if, :unless, :data, :mail, :kind, :then 7 | 8 | def initialize(options={}) 9 | options = options.fetch(:default_values, options) 10 | options.each { |key, value| try "#{key}=", value } 11 | end 12 | 13 | def hook 14 | if @before.nil? 15 | :after 16 | else 17 | :before 18 | end 19 | end 20 | 21 | def method_name 22 | self.send(hook) 23 | end 24 | 25 | def attributes 26 | no_attrs = [hook, :if, :unless, :mail, :then] 27 | attrs = instance_values.reject { |key| no_attrs.include? key.to_sym } 28 | attrs.merge({mail: get_mail_type}) 29 | end 30 | 31 | def merge(fly) 32 | raise TypeError, "#{fly} is not a Fly" unless fly.is_a? self.class 33 | 34 | Notifly::Models::Options::Fly.new instance_values.merge(fly.instance_values) 35 | end 36 | 37 | def get_mail_type 38 | if mail == true 39 | :always 40 | elsif mail.present? and mail[:only] 41 | :only 42 | else 43 | :never 44 | end 45 | end 46 | end 47 | end 48 | end 49 | end -------------------------------------------------------------------------------- /spec/dummy/app/models/dummy_object.rb: -------------------------------------------------------------------------------- 1 | class DummyObject < ActiveRecord::Base 2 | has_many :posts 3 | 4 | notifly default_values: { receiver: :self } 5 | notifly after: :create, template: :create, if: :is_smart? 6 | notifly after: :save, template: :save, if: :is_smart? 7 | notifly after: :update, template: :update, if: :is_smart? 8 | 9 | notifly before: :destroy, template: :destroy_smart, mail: true, if: :is_smart? 10 | 11 | notifly before: :destroy, template: :destroy_dummy, mail: { only: true, 12 | template: :default }, unless: :is_smart? 13 | notifly after: :be_smart, template: :be_smart, mail: { only: true, 14 | template: :default }, if: :is_smart? 15 | 16 | notifly after: :buzz, if: :is_smart? 17 | 18 | notifly after: :test_kind, kind: :message 19 | notifly after: :test_kind, kind: :feed 20 | 21 | notifly after: :test_then, then: -> { self.update name: 'name_after_then' } 22 | notifly after: :test_then_using_notification, kind: :blastoise, 23 | then: ->(n) { self.update name: n.kind } 24 | 25 | def be_smart 26 | self.name = 'smart' 27 | end 28 | 29 | def buzz 30 | 'buzz' 31 | end 32 | 33 | def is_smart? 34 | name == 'smart' 35 | end 36 | 37 | def test_kind 38 | # code 39 | true 40 | end 41 | 42 | def test_then 43 | #code 44 | end 45 | 46 | def test_then_using_notification 47 | #code 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/support/wait_ajax.rb: -------------------------------------------------------------------------------- 1 | # Ajax testing with ruby and capybara 2 | # 3 | # Add this to spec/support 4 | # 5 | # When a link or button starts an ajax request, instead of use Capybara 6 | # click_link, click_button and click_link_or_button methods use click_ajax_link, 7 | # click_ajax_button and click_ajax_link_or_button methods. You can still use 8 | # capybara methods and right after it, call wait_for_ajax method. 9 | # 10 | # This methods will wait until Capybara.default_wait_time for the ajax request 11 | # to finish before continue the normal tests flow. 12 | # 13 | 14 | require 'timeout' 15 | 16 | module WaitForAjax 17 | def click_ajax_link(locator, options = {}) 18 | click_link(locator, options) 19 | 20 | wait_for_ajax 21 | end 22 | 23 | def click_ajax_button(locator, options = {}) 24 | click_button(locator, options) 25 | 26 | wait_for_ajax 27 | end 28 | 29 | def click_ajax_link_or_button(locator, options = {}) 30 | click_link_or_button(locator, options) 31 | 32 | wait_for_ajax 33 | end 34 | 35 | def wait_for_ajax(&block) 36 | block.call if block 37 | 38 | Timeout.timeout(Capybara.default_wait_time) do 39 | loop do 40 | sleep 0.1 41 | break if finished_all_ajax_requests? 42 | end 43 | end 44 | end 45 | 46 | def finished_all_ajax_requests? 47 | page.evaluate_script('jQuery.active').zero? 48 | end 49 | end 50 | 51 | RSpec.configure do |config| 52 | config.include WaitForAjax 53 | end -------------------------------------------------------------------------------- /spec/mailers/notifly/notification_mailer_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | module Notifly 4 | RSpec.describe NotificationMailer, :type => :mailer do 5 | let(:mail) { NotificationMailer.notifly(to: dummy.email, template: notification.template, 6 | notification_id: notification.id) } 7 | let(:notification) { Notifly::Notification.create! receiver: dummy, 8 | mail: :always } 9 | let(:dummy) { DummyObject.create name: 'Dummy', email: 'dummy@test.com' } 10 | 11 | it 'should guarantee that the receiver is correct' do 12 | expect(mail.to).to eq([dummy.email]) 13 | end 14 | 15 | it 'should guarantee that the sender is correct' do 16 | expect(mail.from).to eq(['change-me-at-config-initializers-notifly@exemple.com']) 17 | end 18 | 19 | it 'should guarantee that the subject is correct' do 20 | expect(mail.subject).to include('You have a new notification') 21 | end 22 | 23 | it 'should guarantee that all information appears on the email body' do 24 | expect(mail.body).to include('Default notification mail') 25 | end 26 | 27 | context 'when using other templates' do 28 | let(:notification) { Notifly::Notification.create! receiver: dummy, 29 | mail: :only, template: :hello } 30 | 31 | it 'should guarantee that the subject is correct' do 32 | expect(mail.subject).to include('Hello!') 33 | end 34 | 35 | it 'should guarantee that all information appears on the email body' do 36 | expect(mail.body).to include('Hello mail') 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Adds additional error checking when serving assets at runtime. 31 | # Checks for improperly declared sprockets dependencies. 32 | # Raises helpful error messages. 33 | config.assets.raise_runtime_errors = true 34 | 35 | # Raises error for missing translations 36 | # config.action_view.raise_on_missing_translations = true 37 | end 38 | -------------------------------------------------------------------------------- /app/views/notifly/notifications/index.js.erb: -------------------------------------------------------------------------------- 1 | $('#notifly-notifications-content .notifly-loading').remove(); 2 | $('#notifly-notifications-content .notifly-empty').remove(); 3 | 4 | <% if @notifications.any? %> 5 | <% rendered_notifications = render partial: 'notifly/layouts/index', 6 | locals: { notifications: @notifications } %> 7 | <% last_notification = @notifications.last.try(:id) %> 8 | <% first_notification = @notifications.first.try(:id) %> 9 | 10 | <% if @scope_param == 'older' %> 11 | $('#notifly-notifications-content').append("<%= j rendered_notifications %>"); 12 | notiflyLastNotification = <%= last_notification %>; 13 | 14 | $('#notifly-notifications-content').scrollTop($('<%= "#notifly-notification-#{first_notification}" %>').offset().top); 15 | <% else %> 16 | $('#notifly-notifications-content').prepend("<%= j rendered_notifications %>"); 17 | notiflyFirstNotification = <%= first_notification %>; 18 | 19 | if (notiflyLastNotification == undefined) 20 | notiflyLastNotification = <%= last_notification %>; 21 | 22 | <% end %> 23 | 24 | <% else %> 25 | if (notiflyLastNotification == undefined) { 26 | $('#notifly-more-notifications-link').remove(); 27 | $('#notifly-notifications-content').append("<%= j(render 'notifly/layouts/empty')%>") 28 | } 29 | 30 | <% end %> 31 | 32 | <% if @scope_param == 'older' and @user.notifly_notifications.older(than: last_notification).blank? %> 33 | $('#notifly-more-notifications-link').remove(); 34 | <% end %> 35 | 36 | $('#notifly-counter').replaceWith("<%= j(render 'notifly/layouts/counter', counter: @counter)%>"); 37 | $('#notifly-icon').replaceWith("<%= j notifly_icon(@counter > 0) %>") 38 | Tinycon.setBubble(<%= @counter %>); 39 | -------------------------------------------------------------------------------- /spec/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_assets = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | 37 | # Raises error for missing translations 38 | # config.action_view.raise_on_missing_translations = true 39 | end 40 | -------------------------------------------------------------------------------- /app/models/notifly/notification.rb: -------------------------------------------------------------------------------- 1 | module Notifly 2 | class Notification < ActiveRecord::Base 3 | belongs_to :target, polymorphic: true 4 | belongs_to :sender, polymorphic: true 5 | belongs_to :receiver, polymorphic: true 6 | 7 | before_validation :set_defaults 8 | after_create :send_to_receiver, if: -> { Notifly.websocket } 9 | 10 | scope :all_from, -> (receiver) { where(receiver: receiver) } 11 | scope :unseen, -> { where(seen: false) } 12 | scope :not_only_mail, -> { where.not(mail: 'only') } 13 | scope :limited, -> { limit(Notifly.per_page) } 14 | scope :ordered, -> { order('notifly_notifications.created_at DESC') } 15 | scope :newer, ->(than: nil) do 16 | return ordered if than.blank? 17 | 18 | reference = find_by(id: than) 19 | ordered.where('notifly_notifications.created_at > ?', reference.created_at).where.not(id: reference) 20 | end 21 | scope :older, ->(than: nil) do 22 | reference = find_by(id: than) 23 | 24 | ordered. 25 | where('notifly_notifications.created_at < ?', reference.created_at). 26 | where.not(id: reference) 27 | end 28 | scope :between, ->(first, last) do 29 | notifications = where(id: [first, last]) 30 | where(created_at: (notifications.first.created_at..notifications.last.created_at)) 31 | end 32 | 33 | validates :receiver, :template, :mail, :kind, presence: true 34 | 35 | serialize :data, JSON 36 | 37 | private 38 | def set_defaults 39 | self.mail ||= :never 40 | self.kind ||= :notification 41 | self.template ||= :default 42 | end 43 | 44 | def send_to_receiver 45 | Notifly::NotificationChannel.new(self.receiver_id).trigger(self) if self.kind == 'notification' 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /spec/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /spec/dummy/spec/features/notifly/counter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Notifly counter', :type => :feature, js: true do 4 | let(:notification) { Notifly::Notification } 5 | let(:receiver) { DummyObject.create! name: 'User' } 6 | 7 | before(:each) do 8 | notification.create! receiver: receiver, seen: true, read: false, mail: :never 9 | notification.create! receiver: receiver, seen: false, read: false, mail: :never 10 | notification.create! receiver: receiver, seen: false, read: false, mail: :always 11 | notification.create! receiver: receiver, seen: false, read: false, mail: :always 12 | notification.create! receiver: DummyObject.create!, seen: false, read: false, 13 | mail: :never 14 | notification.create! receiver: DummyObject.create!, seen: false, read: false, 15 | mail: :never 16 | Notifly.per_page = 2 17 | end 18 | 19 | after(:each) do 20 | Notifly.per_page = 10 21 | end 22 | 23 | scenario 'seeing notifications' do 24 | wait_for_ajax { visit root_path } 25 | within("#notifly-counter") do 26 | expect(page).to have_content '3' 27 | end 28 | 29 | wait_for_ajax { find('#notifly-icon').click } 30 | 31 | within("#notifly-counter") do 32 | expect(page).to have_content '1' 33 | end 34 | 35 | click_ajax_link 'More' 36 | 37 | within("#notifly-counter", visible: false) do 38 | expect(page).to have_content '0' 39 | end 40 | end 41 | 42 | context 'when notification is mail only' do 43 | scenario 'seeing only notifications without mail only' do 44 | notification.create! receiver: receiver, seen: false, read: false, mail: :only 45 | wait_for_ajax { visit root_path } 46 | 47 | within("#notifly-counter") do 48 | expect(page).to_not have_content '4' 49 | expect(page).to have_content '3' 50 | end 51 | end 52 | end 53 | end -------------------------------------------------------------------------------- /app/controllers/notifly/notifications_controller.rb: -------------------------------------------------------------------------------- 1 | require_dependency "notifly/application_controller" 2 | 3 | module Notifly 4 | class NotificationsController < ApplicationController 5 | def index 6 | @notifications = scoped_notifications 7 | @notifications.update_all(seen: true) if params[:mark_as_seen] == 'true' 8 | 9 | @counter = count_unseen 10 | @scope_param = scope_param 11 | end 12 | 13 | def read 14 | if params[:first_notification_id].present? and params[:last_notification_id].present? 15 | @notifications = notifications_between 16 | @notifications.update_all(read: true) 17 | end 18 | end 19 | 20 | def toggle_read 21 | @notification = Notifly::Notification.find(params[:notification_id]) 22 | @notification.update(read: params[:read] || !@notification.read) 23 | end 24 | 25 | def seen 26 | if params[:first_notification_id].present? and params[:last_notification_id].present? 27 | @notifications = notifications_between 28 | @notifications.update_all(seen: true) 29 | end 30 | @counter = count_unseen 31 | end 32 | 33 | private 34 | def scoped_notifications 35 | current_user_notifications.send(scope_param, than: params[:reference_notification_id]).limited 36 | end 37 | 38 | def scope_param 39 | return params[:scope] if ['older', 'newer'].include?(params[:scope]) 40 | end 41 | 42 | def current_user_notifications 43 | @user = current_user 44 | @user.notifly_notifications(:notification).not_only_mail 45 | end 46 | 47 | def count_unseen 48 | current_user_notifications.unseen.count 49 | end 50 | 51 | def notifications_between 52 | current_user_notifications.between(params[:first_notification_id], 53 | params[:last_notification_id]).ordered 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/dummy/spec/features/notifly/loading_notifications_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Loading notifications', :type => :feature, js: true do 4 | 5 | before(:each) { @receiver = DummyObject.create! name: 'User' } 6 | 7 | context 'when have notifications' do 8 | before(:each) do 9 | Notifly::Notification.create! receiver: DummyObject.create!, template: :default, 10 | read: false, seen: false 11 | 12 | 3.times { notification_with_mail(:always) } # page 2 with the last 3 13 | @last_notification_from_page = notification_with_mail(:always) # page 1 14 | 15 | 2.times { notification_with_mail(:only) } # are not in a page 16 | 9.times { notification_with_mail(:never) } # page 1 17 | 18 | wait_for_ajax { visit root_path } 19 | end 20 | 21 | def notification_with_mail(occurrence) 22 | Notifly::Notification.create! receiver: @receiver, template: :default, 23 | read: false, mail: occurrence 24 | end 25 | 26 | scenario 'visiting page' do 27 | within("#notifly") do 28 | expect(page).to have_css('div.notifly-notification', count: 10, visible: false) 29 | end 30 | 31 | wait_for_ajax { find('#notifly-icon').click } 32 | 33 | within('#notifly') do 34 | expect(page).to have_css('div.notifly-notification', count: 10, visible: true) 35 | end 36 | end 37 | 38 | scenario 'loading next page link' do 39 | wait_for_ajax { find('#notifly-icon').click } 40 | expect(page).to have_css('div.notifly-notification', count: 10, visible: true) 41 | 42 | within('#notifly-notifications-footer') do 43 | expect(page).to have_link('More') 44 | end 45 | 46 | click_ajax_link 'More' 47 | 48 | expect(page).to have_css('div.notifly-notification', count: 13, visible: true) 49 | within('#notifly-notifications-footer') do 50 | expect(page).to_not have_link('More') 51 | end 52 | end 53 | end 54 | 55 | context 'when none notifications' do 56 | scenario 'loading next page link' do 57 | wait_for_ajax { visit root_path } 58 | wait_for_ajax { find('#notifly-icon').click } 59 | 60 | within('#notifly-notifications-footer') do 61 | expect(page).to_not have_link('More') 62 | end 63 | end 64 | end 65 | end -------------------------------------------------------------------------------- /spec/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20141212122918) do 15 | 16 | # These are extensions that must be enabled in order to support this database 17 | enable_extension "plpgsql" 18 | 19 | create_table "dummy_objects", force: true do |t| 20 | t.string "name" 21 | t.datetime "created_at" 22 | t.datetime "updated_at" 23 | t.string "email" 24 | end 25 | 26 | create_table "notifly_notifications", force: true do |t| 27 | t.string "template" 28 | t.boolean "read", default: false 29 | t.integer "target_id" 30 | t.string "target_type" 31 | t.integer "sender_id" 32 | t.string "sender_type" 33 | t.integer "receiver_id" 34 | t.string "receiver_type" 35 | t.datetime "created_at" 36 | t.datetime "updated_at" 37 | t.text "data" 38 | t.boolean "seen", default: false 39 | t.string "mail" 40 | t.string "kind", default: "notification" 41 | end 42 | 43 | add_index "notifly_notifications", ["receiver_id", "receiver_type"], name: "index_notifly_notifications_on_receiver_id_and_receiver_type", using: :btree 44 | add_index "notifly_notifications", ["sender_id", "sender_type"], name: "index_notifly_notifications_on_sender_id_and_sender_type", using: :btree 45 | add_index "notifly_notifications", ["target_id", "target_type"], name: "index_notifly_notifications_on_target_id_and_target_type", using: :btree 46 | 47 | create_table "posts", force: true do |t| 48 | t.string "author" 49 | t.string "title" 50 | t.text "content" 51 | t.boolean "published", default: false 52 | t.integer "dummy_object_id" 53 | end 54 | 55 | add_index "posts", ["dummy_object_id"], name: "index_posts_on_dummy_object_id", using: :btree 56 | 57 | end 58 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 2 | 3 | RSpec.configure do |config| 4 | if config.files_to_run.one? 5 | config.default_formatter = 'doc' 6 | end 7 | 8 | config.expect_with :rspec do |expectations| 9 | expectations.syntax = :expect 10 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 11 | end 12 | 13 | config.mock_with :rspec do |mocks| 14 | mocks.syntax = :expect 15 | mocks.verify_partial_doubles = false 16 | end 17 | 18 | config.order = :random 19 | 20 | # The settings below are suggested to provide a good initial experience 21 | # with RSpec, but feel free to customize to your heart's content. 22 | =begin 23 | # These two settings work together to allow you to limit a spec run 24 | # to individual examples or groups you care about by tagging them with 25 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 26 | # get run. 27 | config.filter_run :focus 28 | config.run_all_when_everything_filtered = true 29 | 30 | # Limits the available syntax to the non-monkey patched syntax that is recommended. 31 | # For more details, see: 32 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 33 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 34 | # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching 35 | config.disable_monkey_patching! 36 | 37 | # Many RSpec users commonly either run the entire suite or an individual 38 | # file, and it's useful to allow more verbose output when running an 39 | # individual spec file. 40 | if config.files_to_run.one? 41 | # Use the documentation formatter for detailed output, 42 | # unless a formatter has already been configured 43 | # (e.g. via a command-line flag). 44 | config.default_formatter = 'doc' 45 | end 46 | 47 | # Print the 10 slowest examples and example groups at the 48 | # end of the spec run, to help surface which specs are running 49 | # particularly slow. 50 | config.profile_examples = 10 51 | 52 | # Run specs in random order to surface order dependencies. If you find an 53 | # order dependency and want to debug it, you can fix the order by providing 54 | # the seed, which is printed after each run. 55 | # --seed 1234 56 | config.order = :random 57 | 58 | # Seed global randomization in this process using the `--seed` CLI option. 59 | # Setting this allows you to use `--seed` to deterministically reproduce 60 | # test failures related to randomization by passing the same `--seed` value 61 | # as the one that triggered the failure. 62 | Kernel.srand config.seed 63 | =end 64 | end 65 | -------------------------------------------------------------------------------- /spec/dummy/spec/features/notifly/read_notifications_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Read notification', :type => :feature, js: true do 4 | before do 5 | @receiver = DummyObject.create! name: 'User' 6 | @n_1 = Notifly::Notification.create! receiver: @receiver, read: false, mail: :never 7 | @n_2 = Notifly::Notification.create! receiver: @receiver, read: false, mail: :never 8 | @n_3 = Notifly::Notification.create! receiver: @receiver, read: false, mail: :never 9 | Notifly.per_page = 2 10 | end 11 | 12 | after(:each) do 13 | Notifly.per_page = 10 14 | end 15 | 16 | def open_notifly 17 | wait_for_ajax { visit root_path } 18 | wait_for_ajax { find('#notifly-icon').click } 19 | end 20 | 21 | context 'when read a specific notification' do 22 | scenario 'read a notification' do 23 | open_notifly 24 | 25 | notification_id = "#notifly-notification-#{@n_2.id}" 26 | expect(page).to have_selector ("#{notification_id}.notifly-notification-not-read") 27 | 28 | within(notification_id) do 29 | click_ajax_link 'read' 30 | end 31 | 32 | expect(page).to have_selector (notification_id) 33 | expect(page).to_not have_selector ("#{notification_id}.notifly-notification-not-read") 34 | end 35 | 36 | scenario 'unread a notification' do 37 | notification = Notifly::Notification.create! receiver: @receiver, read: true, 38 | mail: :never 39 | notification_id = "#notifly-notification-#{notification.id}" 40 | open_notifly 41 | expect(page).to have_selector (notification_id) 42 | expect(page).to_not have_selector ("#{notification_id}.notifly-notification-not-read") 43 | 44 | within(notification_id) do 45 | click_ajax_link 'unread' 46 | end 47 | 48 | expect(page).to have_selector ("#{notification_id}.notifly-notification-not-read") 49 | end 50 | end 51 | 52 | scenario 'mark all read' do 53 | open_notifly 54 | 55 | expect(page).to have_selector ("#notifly-notification-#{@n_3.id}.notifly-notification-not-read") 56 | expect(page).to have_selector ("#notifly-notification-#{@n_2.id}.notifly-notification-not-read") 57 | expect(page).to_not have_selector ("#notifly-notification-#{@n_1.id}") 58 | 59 | within('#notifly') do 60 | click_ajax_link 'Mark as read' 61 | click_ajax_link 'More' 62 | end 63 | 64 | expect(page).to have_selector ("#notifly-notification-#{@n_3.id}") 65 | expect(page).to have_selector ("#notifly-notification-#{@n_2.id}") 66 | expect(page).to have_selector ("#notifly-notification-#{@n_1.id}.notifly-notification-not-read") 67 | 68 | expect(page).to_not have_selector ("#notifly-notification-#{@n_3.id}.notifly-notification-not-read") 69 | expect(page).to_not have_selector ("#notifly-notification-#{@n_2.id}.notifly-notification-not-read") 70 | end 71 | end -------------------------------------------------------------------------------- /spec/dummy/config/initializers/websocket_rails.rb: -------------------------------------------------------------------------------- 1 | WebsocketRails.setup do |config| 2 | 3 | # Uncomment to override the default log level. The log level can be 4 | # any of the standard Logger log levels. By default it will mirror the 5 | # current Rails environment log level. 6 | # config.log_level = :debug 7 | 8 | # Uncomment to change the default log file path. 9 | # config.log_path = "#{Rails.root}/log/websocket_rails.log" 10 | 11 | # Set to true if you wish to log the internal websocket_rails events 12 | # such as the keepalive `websocket_rails.ping` event. 13 | # config.log_internal_events = false 14 | 15 | # Change to true to enable standalone server mode 16 | # Start the standalone server with rake websocket_rails:start_server 17 | # * Requires Redis 18 | config.standalone = false 19 | 20 | # Change to true to enable channel synchronization between 21 | # multiple server instances. 22 | # * Requires Redis. 23 | config.synchronize = false 24 | 25 | # Prevent Thin from daemonizing (default is true) 26 | # config.daemonize = false 27 | 28 | # Uncomment and edit to point to a different redis instance. 29 | # Will not be used unless standalone or synchronization mode 30 | # is enabled. 31 | # config.redis_options = {:host => 'localhost', :port => '6379'} 32 | 33 | # By default, all subscribers in to a channel will be removed 34 | # when that channel is made private. If you don't wish active 35 | # subscribers to be removed from a previously public channel 36 | # when making it private, set the following to true. 37 | # config.keep_subscribers_when_private = false 38 | 39 | # Set to true if you wish to broadcast channel subscriber_join and 40 | # subscriber_part events. All subscribers of a channel will be 41 | # notified when other clients join and part the channel. If you are 42 | # using the UserManager, the current_user object will be sent along 43 | # with the event. 44 | # config.broadcast_subscriber_events = true 45 | 46 | # Used as the key for the WebsocketRails.users Hash. This method 47 | # will be called on the `current_user` object in your controller 48 | # if one exists. If `current_user` does not exist or does not 49 | # respond to the identifier, the key will default to `connection.id` 50 | # config.user_identifier = :id 51 | 52 | # Uncomment and change this option to override the class associated 53 | # with your `current_user` object. This class will be used when 54 | # synchronization is enabled and you trigger events from background 55 | # jobs using the WebsocketRails.users UserManager. 56 | # config.user_class = User 57 | 58 | # Supporting HTTP streaming on Internet Explorer versions 8 & 9 59 | # requires CORS to be enabled for GET "/websocket" request. 60 | # List here the origin domains allowed to perform the request. 61 | # config.allowed_origins = ['http://localhost:3000'] 62 | 63 | end 64 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | # Disable Rails's static asset server (Apache or nginx will already do this). 23 | config.serve_static_assets = false 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # Generate digests for assets URLs. 33 | config.assets.digest = true 34 | 35 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 36 | 37 | # Specifies the header that your server uses for sending files. 38 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 40 | 41 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 42 | # config.force_ssl = true 43 | 44 | # Set to :debug to see everything in the log. 45 | config.log_level = :info 46 | 47 | # Prepend all log lines with the following tags. 48 | # config.log_tags = [ :subdomain, :uuid ] 49 | 50 | # Use a different logger for distributed setups. 51 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 52 | 53 | # Use a different cache store in production. 54 | # config.cache_store = :mem_cache_store 55 | 56 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 57 | # config.action_controller.asset_host = "http://assets.example.com" 58 | 59 | # Ignore bad email addresses and do not raise email delivery errors. 60 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 61 | # config.action_mailer.raise_delivery_errors = false 62 | 63 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 64 | # the I18n.default_locale when a translation cannot be found). 65 | config.i18n.fallbacks = true 66 | 67 | # Send deprecation notices to registered listeners. 68 | config.active_support.deprecation = :notify 69 | 70 | # Disable automatic flushing of the log to improve performance. 71 | # config.autoflush_log = false 72 | 73 | # Use default logging formatter so that PID and timestamp are not suppressed. 74 | config.log_formatter = ::Logger::Formatter.new 75 | 76 | # Do not dump schema after migrations. 77 | config.active_record.dump_schema_after_migration = false 78 | end 79 | -------------------------------------------------------------------------------- /spec/dummy/spec/models/post_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Post, :type => :model do 4 | it { is_expected.to belong_to(:dummy_object) } 5 | 6 | describe 'Notifly' do 7 | let(:dummy) { DummyObject.create! } 8 | let(:post) { Post.create! dummy_object: dummy, published: false } 9 | let(:notification) { Notifly::Notification.take } 10 | 11 | describe 'initialization' do 12 | it { expect(Post).to respond_to :notifly } 13 | end 14 | 15 | describe 'usage' do 16 | it 'should create a notification when is published' do 17 | expect(Notifly::Notification.count).to eql 0 18 | 19 | expect { post.publish! }.to change(Notifly::Notification, :count).from(0).to(1) 20 | expect(notification.receiver).to eql dummy 21 | expect(notification.target).to eql post 22 | expect(notification.template).to eql 'publish' 23 | end 24 | 25 | describe 'Notification fallback (data)' do 26 | it 'should use Notifly::Notification#data for missing informations' do 27 | post_attributes = post.attributes 28 | expect { post.destroy }.to change(Notifly::Notification, :count).from(0).to(1) 29 | 30 | expect(notification.receiver).to eql dummy 31 | expect(notification.template).to eql 'destroy' 32 | expect(notification.reload.data).to eql post_attributes 33 | end 34 | 35 | context 'when run notifly by order' do 36 | context 'when post title starts with nil' do 37 | it 'should send two notifications' do 38 | post_1 = Post.create dummy_object: dummy, published: true, title: 'TitleBar' 39 | 40 | expect { post_1.change_title }.to change(Notifly::Notification, :count). 41 | by(2) 42 | 43 | expect(Notifly::Notification.first.data['title_before_create']).to eql 'TitleBar' 44 | expect(Notifly::Notification.last.data['title']).to eql 'NewTitle' 45 | end 46 | end 47 | 48 | context 'when post title starts with TitleFoo' do 49 | it 'should send two notifications' do 50 | expect_any_instance_of(Post).to receive(:change_title) { nil } 51 | post = Post.create! dummy_object: dummy, title: 'TitleFoo' 52 | 53 | expect { post.change_title }.to_not change(Notifly::Notification, :count) 54 | end 55 | end 56 | end 57 | end 58 | end 59 | 60 | describe '#notifly_notifications' do 61 | it 'should show its notifications' do 62 | dummy_notification = Notifly::Notification.create! receiver: dummy, 63 | sender: post, mail: :never 64 | post_notifications = (1..3).map do 65 | Notifly::Notification.create! receiver: post, sender: dummy, mail: :never 66 | end 67 | 68 | expect(post.notifly_notifications).to include(*post_notifications) 69 | expect(post.notifly_notifications).to_not include(dummy_notification) 70 | end 71 | 72 | it 'should query its notifications' do 73 | dummy_2 = DummyObject.create 74 | notification_1 = Notifly::Notification.create! receiver: post, 75 | sender: dummy, template: 'destroy', mail: :never 76 | notification_2 = Notifly::Notification.create! receiver: post, 77 | sender: dummy, target: dummy_2, mail: :never 78 | notification_3 = Notifly::Notification.create! receiver: post, 79 | sender: dummy, target: dummy_2, mail: :never 80 | 81 | destroy_notifications = post.notifly_notifications.where(template: 'destroy') 82 | dummy_2_notifications = post.notifly_notifications.where(target: dummy_2) 83 | 84 | expect(destroy_notifications).to include(notification_1) 85 | expect(destroy_notifications).to_not include(notification_2, notification_3) 86 | expect(dummy_2_notifications).to include(notification_2, notification_3) 87 | expect(dummy_2_notifications).to_not include(notification_1) 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/models/notifly/notification_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | module Notifly 4 | RSpec.describe Notification, :type => :model do 5 | describe 'validations' do 6 | it { is_expected.to validate_presence_of(:receiver) } 7 | 8 | it 'should validates presence of template' do 9 | is_expected.to_not validate_presence_of(:template) 10 | 11 | allow_any_instance_of(Notification).to receive(:set_defaults) { nil } 12 | is_expected.to validate_presence_of(:template) 13 | end 14 | 15 | it 'should validates presence of mail' do 16 | is_expected.to_not validate_presence_of(:mail) 17 | 18 | allow_any_instance_of(Notification).to receive(:set_defaults) { nil } 19 | is_expected.to validate_presence_of(:mail) 20 | end 21 | 22 | it 'should validates presence of kind' do 23 | is_expected.to_not validate_presence_of(:kind) 24 | 25 | allow_any_instance_of(Notification).to receive(:set_defaults) { nil } 26 | is_expected.to validate_presence_of(:kind) 27 | end 28 | end 29 | 30 | describe '#data' do 31 | it 'should save and repond with a hash' do 32 | hash = { 'post' => Post.create!.attributes } 33 | 34 | notification = Notifly::Notification.create! receiver: DummyObject.create, 35 | template: :foo, data: hash, mail: :never 36 | 37 | expect(notification.reload.data).to eql hash 38 | end 39 | end 40 | 41 | describe 'scopes' do 42 | def simple_notification 43 | Notifly::Notification.create! receiver: DummyObject.create!, mail: :never 44 | end 45 | 46 | before(:each) { Notifly.per_page = 4 } 47 | 48 | describe '.newer' do 49 | it 'should return first page if do not receive params' do 50 | page_1 = [] 51 | first_notification_from_page_2 = simple_notification 52 | 3.times { page_1 << simple_notification } 53 | page_1 << first_notification_from_page_1 = simple_notification 54 | 55 | expect(Notifly::Notification.newer.limited).to match_array page_1 56 | end 57 | 58 | it 'should return newer notifications than a specific notification' do 59 | new_notification_1 = simple_notification 60 | new_notification_2 = simple_notification 61 | new_notification_3 = simple_notification 62 | new_notification_4 = simple_notification 63 | 64 | expect(Notifly::Notification.newer than: new_notification_2.id). 65 | to match_array [new_notification_3, new_notification_4] 66 | end 67 | end 68 | 69 | describe '.between' do 70 | it 'should return notifications between specific notifications' do 71 | notifications = [] 72 | notification_1 = simple_notification 73 | notification_2 = simple_notification 74 | notification_3 = simple_notification 75 | notification_4 = simple_notification 76 | notification_5 = simple_notification 77 | notification_6 = simple_notification 78 | 79 | expect(Notifly::Notification.between notification_2, notification_5). 80 | to match_array [notification_2, notification_3, notification_4, notification_5] 81 | end 82 | end 83 | 84 | describe '.older' do 85 | let(:page_1) { [ ] } 86 | let(:page_2) { [ ] } 87 | let(:page_3) { [ ] } 88 | 89 | before(:each) do 90 | page_3 << @last_notification_from_page_3 = simple_notification 91 | 3.times { page_3 << simple_notification } 92 | page_2 << @last_notification_from_page_2 = simple_notification 93 | 3.times { page_2 << simple_notification } 94 | page_1 << @last_notification_from_page_1 = simple_notification 95 | 3.times { page_1 << simple_notification } 96 | end 97 | 98 | it { expect(Notifly::Notification.older(than: @last_notification_from_page_1.id).limited). 99 | to match_array page_2 } 100 | it { expect(Notifly::Notification.older(than: @last_notification_from_page_2.id).limited). 101 | to match_array page_3 } 102 | it { expect(Notifly::Notification.older(than: @last_notification_from_page_3.id).limited). 103 | to match_array [] } 104 | end 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /lib/notifly/models/flyable.rb: -------------------------------------------------------------------------------- 1 | require_relative 'options/fly' 2 | 3 | module Notifly 4 | module Models 5 | module Flyable 6 | extend ActiveSupport::Concern 7 | 8 | module ClassMethods 9 | attr_reader :flies, :default_fly, :flyable_callbacks 10 | 11 | def notifly(options = {}) 12 | @flies ||= [] 13 | @flyable_callbacks ||= [] 14 | 15 | fly = Notifly::Models::Options::Fly.new options 16 | 17 | if options[:default_values] 18 | @default_fly = fly 19 | else 20 | @flies << fly 21 | 22 | if [:create, :save, :update, :destroy].include? fly.method_name 23 | _create_callback_for_active_record_from fly 24 | end 25 | end 26 | end 27 | 28 | def method_added(method_name) 29 | _create_callbacks_for method_name 30 | super 31 | end 32 | 33 | private 34 | def _create_callbacks_for(method_name) 35 | method_flies = _flies_for method_name 36 | 37 | method_flies.each do |fly| 38 | _create_callback_for_instance_method_from(fly) 39 | end 40 | end 41 | 42 | def _flies_for(method_name) 43 | if flies.present? 44 | flies.select { |fly| fly.method_name == method_name } 45 | else 46 | [] 47 | end 48 | end 49 | 50 | def _create_callback_for_active_record_from(fly) 51 | callback_name = "#{fly.hook}_#{fly.method_name}" 52 | flyable_callbacks << "#{callback_name}_#{fly.object_id}" 53 | 54 | send(callback_name, if: fly.if, unless: fly.unless) do |record| 55 | _create_notification_for(fly) 56 | end 57 | end 58 | 59 | def _create_callback_for_instance_method_from(fly) 60 | notifly_callback_name = _format_callback_name_for(fly) 61 | 62 | if not flyable_callbacks.include? notifly_callback_name 63 | flyable_callbacks << notifly_callback_name 64 | 65 | define_callbacks notifly_callback_name 66 | set_callback notifly_callback_name, fly.hook, if: fly.if, unless: fly.unless do |record| 67 | _create_notification_for(fly) 68 | end 69 | 70 | old_method = instance_method(fly.method_name) 71 | 72 | define_method(fly.method_name) do |*args| 73 | run_callbacks(notifly_callback_name) do 74 | old_method.bind(self).call(*args) 75 | end 76 | end 77 | end 78 | end 79 | 80 | def _format_callback_name_for(fly) 81 | ending_chars = { 82 | '!' => :_dangerous, 83 | '?' => :_question 84 | } 85 | 86 | method_name = fly.method_name.to_s.gsub(/(?[\?|\!])/, ending_chars) 87 | 88 | "notifly_#{fly.hook}_#{method_name}_#{fly.object_id}" 89 | end 90 | end 91 | 92 | def _create_notification_for(fly) 93 | new_fly = _default_fly.merge(fly) 94 | 95 | notification = Notifly::Notification.create _get_attributes_from(new_fly) 96 | _after_create_notification(notification, new_fly) 97 | 98 | rescue => e 99 | logger.error "Something goes wrong with Notifly, will ignore: #{e}" 100 | raise e if not Rails.env.production? 101 | 102 | end 103 | 104 | def notifly_notifications(kind=nil) 105 | notifications = Notifly::Notification.all_from(self) 106 | kind.present? ? notifications.where(kind: kind) : notifications 107 | end 108 | 109 | private 110 | def _default_fly 111 | self.class.default_fly || Notifly::Models::Options::Fly.new 112 | end 113 | 114 | def _get_attributes_from(fly) 115 | evaluated_attributes = {} 116 | 117 | fly.attributes.each do |key, value| 118 | evaluated_attributes[key] = _eval_for(key, value) 119 | end 120 | 121 | evaluated_attributes 122 | end 123 | 124 | def _eval_for(key, value) 125 | if [:template, :mail, :kind].include? key.to_sym 126 | value 127 | elsif value == :self 128 | self 129 | else 130 | if value.is_a? Proc 131 | instance_exec &value 132 | else 133 | send(value) 134 | end 135 | end 136 | end 137 | 138 | def _after_create_notification(notification, fly) 139 | if fly.then.present? 140 | block = fly.then; 141 | block.parameters.present? ? instance_exec(notification, &block) : instance_exec(&block) 142 | end 143 | 144 | if fly.mail.present? 145 | template = fly.mail.try(:fetch, :template) || notification.template 146 | Notifly::NotificationMailer.notifly to: instance_eval(fly.receiver.to_s).email, template: template, 147 | notification_id: notification.id 148 | end 149 | end 150 | end 151 | end 152 | end -------------------------------------------------------------------------------- /spec/dummy/spec/models/dummy_object_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe DummyObject, :type => :model do 4 | it { is_expected.to have_many(:posts) } 5 | 6 | describe 'Notifly' do 7 | it 'should return the method return' do 8 | smart = DummyObject.create name: 'smart' 9 | dummy = DummyObject.create name: nil 10 | 11 | expect(smart.buzz).to eql('buzz') 12 | expect(dummy.buzz).to eql('buzz') 13 | 14 | expect(smart.update(name: 'smart')).to be_truthy 15 | expect(dummy.update(name: 'dummy')).to be_truthy 16 | end 17 | 18 | context 'when using ActiveRecord methods' do 19 | let(:notifications) { Notifly::Notification.not_only_mail } 20 | 21 | describe 'class methods' do 22 | describe '.create' do 23 | it { expect { DummyObject.create name: 'dummy' }. 24 | to_not change(Notifly::Notification.where(template: :create), :count) } 25 | it { expect { DummyObject.create name: 'smart' }. 26 | to change(Notifly::Notification.where(template: :create), :count).from(0).to(1) } 27 | end 28 | end 29 | 30 | describe 'instance methods' do 31 | let!(:smart) { DummyObject.create name: 'smart', email: 'smart@mail.com' } 32 | let!(:dummy) { DummyObject.create name: nil, email: 'dummy@mail.com' } 33 | 34 | before(:each) do 35 | notifications.delete_all 36 | end 37 | 38 | describe '#save' do 39 | it { expect { smart.save! }.to change(notifications. 40 | where(template: :save), :count).from(0).to(1) } 41 | it { expect { dummy.save! }.to_not change(notifications. 42 | where(template: :save), :count) } 43 | end 44 | 45 | describe '#update' do 46 | it { expect { dummy.update!(name: 'smart') }.to change(notifications. 47 | where(template: :update), :count).from(0).to(1) } 48 | it { expect { smart.update!(name: 'dummy') }.to_not change(notifications. 49 | where(template: :update), :count) } 50 | end 51 | 52 | describe '#destroy' do 53 | it { expect { smart.destroy! }.to change(notifications, :count). 54 | from(0).to(1) } 55 | it { expect { dummy.destroy! }.to_not change(notifications, :count) } 56 | end 57 | end 58 | end 59 | 60 | context 'when notifly send email' do 61 | let!(:smart) { DummyObject.create name: 'smart', email: 'smart@mail.com' } 62 | let!(:dummy) { DummyObject.create name: nil, email: 'dummy@mail.com' } 63 | let(:notifications) { Notifly::Notification } 64 | 65 | before(:each) do 66 | notifications.delete_all 67 | emails_sent.clear 68 | end 69 | 70 | it 'should create a visible notification with email' do 71 | expect { smart.destroy! }.to change(emails_sent, :size).from(0).to(1) 72 | 73 | expect(notifications.count).to eql(1) 74 | expect(smart.notifly_notifications.not_only_mail).to include notifications.take 75 | end 76 | 77 | it 'should create an invisible notification with email' do 78 | expect { dummy.be_smart }.to change(emails_sent, :size).from(0).to(1) 79 | 80 | expect(notifications.count).to eql(1) 81 | expect(dummy.notifly_notifications.not_only_mail).to_not include notifications.take 82 | end 83 | end 84 | 85 | context 'when using kind' do 86 | let!(:dummy) { DummyObject.create name: nil, email: 'dummy@mail.com' } 87 | let(:notifications) { Notifly::Notification } 88 | 89 | before(:each) do 90 | notifications.delete_all 91 | emails_sent.clear 92 | end 93 | 94 | describe '#notifly_notifications' do 95 | it 'should show notifications by kind' do 96 | expect { dummy.test_kind }.to change { notifications.count }.by(2) 97 | 98 | expect(dummy.notifly_notifications.count).to eql 2 99 | 100 | expect(dummy.notifly_notifications(:message).count).to eql 1 101 | expect(dummy.notifly_notifications(:message)). 102 | to match_array notifications.where(receiver: dummy, kind: :message) 103 | 104 | expect(dummy.notifly_notifications(:feed).count).to eql 1 105 | expect(dummy.notifly_notifications(:feed)). 106 | to match_array notifications.where(receiver: dummy, kind: :feed) 107 | end 108 | end 109 | end 110 | 111 | context 'when using "then"' do 112 | let!(:dummy) { DummyObject.create name: nil, email: 'dummy@mail.com' } 113 | let(:notifications) { Notifly::Notification } 114 | 115 | before(:each) { notifications.delete_all } 116 | 117 | it 'should run the code' do 118 | expect(dummy.name).to eql nil 119 | 120 | expect { dummy.test_then }.to change { notifications.count }.by(1) 121 | expect(dummy.reload.name).to eql 'name_after_then' 122 | end 123 | 124 | it 'should run the code using the created notification' do 125 | expect(dummy.name).to eql nil 126 | 127 | expect { dummy.test_then_using_notification }.to change { notifications.count }.by(1) 128 | expect(dummy.reload.name).to eql 'blastoise' 129 | end 130 | end 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/twitter/bootstrap/dropdown.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | /*! 8 | * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=908146e6b02e08c0f522) 9 | * Config saved to config.json and https://gist.github.com/908146e6b02e08c0f522 10 | */ 11 | if (typeof jQuery === 'undefined') { 12 | throw new Error('Bootstrap\'s JavaScript requires jQuery') 13 | } 14 | +function ($) { 15 | var version = $.fn.jquery.split(' ')[0].split('.') 16 | if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) { 17 | throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher') 18 | } 19 | }(jQuery); 20 | 21 | /* ======================================================================== 22 | * Bootstrap: dropdown.js v3.3.1 23 | * http://getbootstrap.com/javascript/#dropdowns 24 | * ======================================================================== 25 | * Copyright 2011-2014 Twitter, Inc. 26 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 27 | * ======================================================================== */ 28 | 29 | 30 | +function ($) { 31 | 'use strict'; 32 | 33 | // DROPDOWN CLASS DEFINITION 34 | // ========================= 35 | 36 | var backdrop = '.dropdown-backdrop' 37 | var toggle = '[data-toggle="dropdown"]' 38 | var Dropdown = function (element) { 39 | $(element).on('click.bs.dropdown', this.toggle) 40 | } 41 | 42 | Dropdown.VERSION = '3.3.1' 43 | 44 | Dropdown.prototype.toggle = function (e) { 45 | var $this = $(this) 46 | 47 | if ($this.is('.disabled, :disabled')) return 48 | 49 | var $parent = getParent($this) 50 | var isActive = $parent.hasClass('open') 51 | 52 | clearMenus() 53 | 54 | if (!isActive) { 55 | if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { 56 | // if mobile we use a backdrop because click events don't delegate 57 | $('