eventifier 0.0.14 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/README.textile +32 -4
  4. data/app/assets/javascripts/eventifier/notifications.coffee +29 -5
  5. data/app/assets/javascripts/eventifier/templates/dropdown.hamlc +1 -1
  6. data/app/assets/stylesheets/eventifier/notifications.scss +1 -1
  7. data/app/controllers/eventifier/preferences_controller.rb +7 -9
  8. data/app/helpers/eventifier/notification_helper.rb +1 -1
  9. data/app/helpers/eventifier/path_helper.rb +2 -2
  10. data/app/models/eventifier/event.rb +1 -3
  11. data/app/models/eventifier/notification.rb +0 -2
  12. data/app/models/eventifier/notification_setting.rb +0 -2
  13. data/app/views/eventifier/notifications/index.jbuilder +2 -2
  14. data/db/migrate/5_system_events.rb +9 -0
  15. data/eventifier.gemspec +7 -5
  16. data/lib/eventifier.rb +2 -0
  17. data/lib/eventifier/event_builder.rb +4 -1
  18. data/lib/eventifier/notification_translator.rb +27 -5
  19. data/lib/eventifier/notifier/notification_subscriber.rb +6 -6
  20. data/lib/eventifier/notifier/notifier.rb +5 -5
  21. data/lib/eventifier/preferences.rb +14 -0
  22. data/lib/eventifier/tracker.rb +7 -0
  23. data/lib/generators/eventifier/install/install_generator.rb +1 -1
  24. data/lib/generators/eventifier/install/templates/{events.en.yaml → events.en.yml} +3 -0
  25. data/spec/controllers/eventifier/notifications_controller_spec.rb +3 -1
  26. data/spec/controllers/eventifier/preferences_controller_spec.rb +5 -23
  27. data/spec/event_tracking_spec.rb +2 -2
  28. data/spec/eventifier/notification_translator_spec.rb +118 -0
  29. data/spec/eventifier/preferences_spec.rb +47 -10
  30. data/spec/eventifier/relationship_spec.rb +8 -8
  31. data/spec/eventifier_spec.rb +14 -10
  32. data/spec/helpers/eventifier/notification_helper_spec.rb +13 -13
  33. data/spec/helpers/eventifier/path_helper_spec.rb +3 -3
  34. data/spec/integration/eventifier_spec.rb +11 -11
  35. data/spec/integration/internationalisation_spec.rb +4 -4
  36. data/spec/integration/system_events_spec.rb +40 -0
  37. data/spec/models/eventifier/event_spec.rb +1 -1
  38. data/spec/models/eventifier/ghost_spec.rb +3 -4
  39. data/spec/models/eventifier/notification_spec.rb +3 -3
  40. data/spec/notifier/notification_mapping_spec.rb +3 -3
  41. metadata +61 -41
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3ddc5253dc949cf041ed9ff68c7225b234750fd4
4
- data.tar.gz: 81a0e4e9121a62a2de35453c7a301450e28d1ce7
3
+ metadata.gz: 86d60265a4396c1ceb7dbf3a127244ccd8dd2544
4
+ data.tar.gz: 64b679101dc154bd27a9dae71e152c20ed469446
5
5
  SHA512:
6
- metadata.gz: 6bf60d92b16aed0434bcfec3b9ab981eeec35b1b1c8c9d16595025f44124f41dc7c32ff061ac454fe768761fa5dfe27c31a5547199d80edcd3c017760c0a10e9
7
- data.tar.gz: a025c89c3fb8f3f07a8d778de602d030bda0cb3d7f86f13261dbf06bc281418528bea49d4f6d7c4ecd1538bdbf6afd6e34c2275c9d9a395b3572dd216a78c5dd
6
+ metadata.gz: 8e90e8779758969401a869fdd588de7647c9be752534e3ffcbe028c7d36401f7db1816dd8742392440e6ad291192ca1260d16b5b2f10fafeb632c68270382eb4
7
+ data.tar.gz: b2c9601e599c49d40808265111d2226b13379f5ebd3d465f40aed21235d041614471cc049fb3563f176745c19c439624661a15b252dfa3c430021946f9fd3d38
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in eventifier.gemspec
4
- gemspec
4
+ gemspec
data/README.textile CHANGED
@@ -17,13 +17,14 @@ class EventTracking
17
17
 
18
18
  def initialize
19
19
  events_for Post do
20
- track_on [:create, :update, :destroy], :attributes => { :except => %w(updated_at) }
21
- notify :group => :members, :on => [:create, :update]
20
+ track_on [:create, :update, :destroy], attributes: { except: %w(updated_at) }
21
+ notify group: :members, on: :create
22
+ notify group: :commenters, on: :update, unless: ->post,author { post.age > 1.week }
22
23
  end
23
24
 
24
25
  events_for Announcement do
25
- track_on :create, :attributes => { :except => %w(updated_at) }
26
- notify :group => :members, :on => :create
26
+ track_on :create
27
+ notify group: :members, on: :create, if: ->announcement,admin { announcement.is_important? }
27
28
  end
28
29
  end
29
30
  end
@@ -90,6 +91,21 @@ h4. Customise views
90
91
  #{object.user.username} commented on your post
91
92
  </code></pre>
92
93
 
94
+ You can create a custom view for each context
95
+
96
+ Dropdown notification: app/views/eventifier/dropdown/_comment.haml
97
+ Email notification: app/views/eventifier/email/_comment.haml
98
+
99
+ Helpers made available to these views are:
100
+
101
+ *notification*: The notification object
102
+ Eventifier::Notification(event: event, user: user_being_notified, parent: parent_notification, sent: email_sent_flag)
103
+
104
+ *event*: The event object
105
+ Eventifier::Event(user: event_owner, eventable: object_for_event, verb: [:create, :update, :destroy], change_data: {"name" => ["Bill", "Bob"]}, groupable: grouped_object)
106
+
107
+ *object*: The object the event was created for
108
+
93
109
  h2. Sending of emails
94
110
 
95
111
  Firstly, you'll need to set the FROM address for the Eventifier mailer
@@ -106,6 +122,18 @@ You want to add a scheduled task to run the following task every x minutes
106
122
  rake eventifier:email:deliver
107
123
  </code></pre>
108
124
 
125
+ h4. Customise email settings descriptions
126
+
127
+ <pre><code>
128
+ # config/locales/events.en.yml
129
+ en:
130
+ events:
131
+ labels:
132
+ preferences:
133
+ default: "All notifications"
134
+ create_relationships_notify_followed: "When you get a new follower"
135
+ </code></pre>
136
+
109
137
  h2. Requirements
110
138
 
111
139
  * ActiveRecord
@@ -9,9 +9,10 @@ class window.NotificationDropdown
9
9
  settingsTemplate: JST['eventifier/templates/settings']
10
10
 
11
11
  constructor: (options) ->
12
- {@el, @limit, @pollTime} = options
12
+ {@el, @limit, @pollTime, @push} = options
13
13
  @limit = @limit || 5
14
14
  @pollTime = @pollTime || 15
15
+ @push = @push || false
15
16
 
16
17
 
17
18
  [@notifications, @renderedNotifications, @unreadCount, @lastReadAt] = [[], [], 0, new Date()]
@@ -23,6 +24,7 @@ class window.NotificationDropdown
23
24
  @el.html(@template(@)).attr('tabindex', 0)
24
25
 
25
26
  # @checkVisibility()
27
+
26
28
  @setEvents()
27
29
  @poll()
28
30
 
@@ -48,11 +50,22 @@ class window.NotificationDropdown
48
50
  @el.on 'addNotifications', @renderNotifications
49
51
  @el.on 'addNotifications', @setUnreadCount
50
52
  @el.on 'poll', @poll
51
- @el.on 'scroll', 'ol', @scrolling
53
+ @el.find('ol.notifications_dropdown_list').on 'scroll', @scrolling
52
54
  $(window).on 'click', @blurNotifications
55
+ if @push
56
+ @el.on 'click', '#notification_dropdown ol a', @pushUrl
53
57
 
54
58
  @
55
59
 
60
+ pushUrl: (e)=>
61
+ location = $(e.currentTarget).attr('href')
62
+ location = $('<a />').attr(href: location).get(0).pathname if location.match /^https?\:\/\//
63
+
64
+ Backbone?.history.navigate(location, true) || history.pushState({trigger: true}, '', location)
65
+ @hide()
66
+
67
+ false
68
+
56
69
  renderNotifications: =>
57
70
  @el.find(".none").remove() if @notifications.length > 0
58
71
  $.each @notifications, (index, notification)=>
@@ -150,6 +163,17 @@ class window.NotificationDropdown
150
163
  @el.addClass('alerting')
151
164
 
152
165
  @el.find(".notification_alert").html(displayCount)
166
+ $('title').html (index, old_html) ->
167
+ if old_html.match /^\(\d+\).*/
168
+ if displayCount > 0
169
+ old_html.replace(/^\(\d+\)/, "(#{displayCount})");
170
+ else
171
+ old_html.replace(/^(\(\d+\))\s/, "");
172
+ else
173
+ if displayCount > 0
174
+ "(#{displayCount}) #{old_html}"
175
+ else
176
+ old_html
153
177
 
154
178
  setUnreadCount: =>
155
179
  @unreadCount = $.grep(@notifications, (notification)=>
@@ -187,13 +211,13 @@ class window.NotificationDropdown
187
211
  0
188
212
 
189
213
  lastLookTime: =>
190
- Math.max(@lastReadAt.getTime()/1000, @newestNotificationTime())
214
+ Math.max(@lastReadAt.getTime()/1000, @newestNotificationTime()/1000)
191
215
 
192
216
  scrolling: =>
193
- scrollWindow = @$el.find('ol')
217
+ scrollWindow = @el.find('ol')
194
218
 
195
219
  if (scrollWindow.scrollTop() + scrollWindow.innerHeight() >= scrollWindow[0].scrollHeight - 50)
196
- @loadMore(after: @oldestNotificationTime())
220
+ @loadMore(after: @oldestNotificationTime()/1000)
197
221
 
198
222
  arrayFromObject: (collection)->
199
223
  serializedObject = {}
@@ -5,6 +5,6 @@
5
5
  %header
6
6
  %a.toggle_settings{ href: '#' } Settings
7
7
  %h4 Notifications
8
- %ol
8
+ %ol.notifications_dropdown_list
9
9
  %li.none You have no notifications
10
10
  #settings_pane
@@ -149,7 +149,7 @@
149
149
  &.unread { background-color: #d3d3d3; }
150
150
  a {
151
151
  display: block;
152
- overflow: auto;
152
+ overflow: visible;
153
153
  padding-left: 38px;
154
154
  }
155
155
  img.avatar {
@@ -1,19 +1,17 @@
1
1
  class Eventifier::PreferencesController < Eventifier::ApplicationController
2
2
  def show
3
- render :json => Eventifier::Preferences.new(current_user).to_hashes
3
+ render :json => preferences.to_hashes
4
4
  end
5
5
 
6
6
  def update
7
- settings = Eventifier::NotificationSetting.for_user current_user
8
- settings.preferences['email'] ||= {}
9
- params[:preferences] ||= {}
7
+ preferences.update params[:preferences] || {}
10
8
 
11
- Eventifier::Preferences.new(current_user).to_hashes.each do |hash|
12
- settings.preferences['email'][hash[:key]] = !params[:preferences][hash[:key]].nil?
13
- end
9
+ render :json => {'status' => 'OK'}
10
+ end
14
11
 
15
- settings.save
12
+ private
16
13
 
17
- render :json => {'status' => 'OK'}
14
+ def preferences
15
+ Eventifier::Preferences.new(current_user)
18
16
  end
19
17
  end
@@ -37,7 +37,7 @@ module Eventifier
37
37
  else
38
38
  key = "events.#{event.eventable_type.downcase}.#{event.verb}"
39
39
  end
40
- message = I18n.translate key, :default => :"events.default.#{event.verb}", "user.name" => event.user.name, :"event.type" => event.eventable_type
40
+ message = I18n.translate key, :default => :"events.default.#{event.verb}", "user.name" => event.user.try(:name), :"event.type" => event.eventable_type
41
41
 
42
42
  replace_vars(message, event).html_safe
43
43
  end
@@ -5,11 +5,11 @@ module Eventifier
5
5
  end
6
6
 
7
7
  def partial_path notification, context = nil
8
- if lookup_context.exists?(notification.event.eventable_type.underscore, [:eventifier, context].compact.join("/"), true)
8
+ if lookup_context.exists?(notification.event.eventable_type.underscore, [[:eventifier, context].compact.join("/")], true)
9
9
  [:eventifier, context, notification.event.eventable_type.underscore].compact.join("/")
10
10
  else
11
11
  [:eventifier, context, 'notification'].compact.join("/")
12
12
  end
13
13
  end
14
14
  end
15
- end
15
+ end
@@ -2,15 +2,13 @@ module Eventifier
2
2
  class Event < ActiveRecord::Base
3
3
  self.table_name = 'eventifier_events'
4
4
 
5
- attr_accessible :user, :eventable, :verb, :change_data, :groupable
6
-
7
5
  belongs_to :user, class_name: Eventifier.user_model_name
8
6
  belongs_to :eventable, polymorphic: true
9
7
  belongs_to :groupable, polymorphic: true
10
8
  has_many :notifications, class_name: 'Eventifier::Notification',
11
9
  dependent: :destroy
12
10
 
13
- validates :user, presence: true
11
+ validates :user, presence: true, unless: :system?
14
12
  validates :eventable, presence: true
15
13
  validates :verb, presence: true
16
14
  validates :groupable, presence: true
@@ -2,8 +2,6 @@ module Eventifier
2
2
  class Notification < ActiveRecord::Base
3
3
  self.table_name = 'eventifier_notifications'
4
4
 
5
- attr_accessible :event, :user, :relations, :event_id, :user_id
6
-
7
5
  belongs_to :event, :class_name => 'Eventifier::Event'
8
6
  belongs_to :user
9
7
 
@@ -5,8 +5,6 @@ class Eventifier::NotificationSetting < ActiveRecord::Base
5
5
 
6
6
  serialize :preferences, MultiJson
7
7
 
8
- attr_accessible :user
9
-
10
8
  validates :user, :presence => true
11
9
  validates :user_id, :uniqueness => true
12
10
 
@@ -1,6 +1,6 @@
1
- json.last_read_at current_user.notifications_last_read_at.to_i
1
+ json.last_read_at current_user.notifications_last_read_at.to_i*1000
2
2
  json.notifications @notifications do |notification|
3
3
  json.(notification, :id)
4
- json.created_at notification.created_at.to_i
4
+ json.created_at notification.created_at.to_i*1000
5
5
  json.html render_partial_view(notification, :dropdown)
6
6
  end
@@ -0,0 +1,9 @@
1
+ class SystemEvents < ActiveRecord::Migration
2
+ def up
3
+ add_column :eventifier_events, :system, :boolean
4
+ end
5
+
6
+ def down
7
+ remove_column :eventifier_events, :system
8
+ end
9
+ end
data/eventifier.gemspec CHANGED
@@ -1,12 +1,13 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  Gem::Specification.new do |s|
3
3
  s.name = "eventifier"
4
- s.version = '0.0.14'
4
+ s.version = '0.1.0'
5
5
  s.authors = ["Nathan Sampimon", "Peter Murray", "Pat Allan"]
6
6
  s.email = ["nathan@inspire9.com"]
7
7
  s.homepage = "http://github.com/inspire9/eventifier"
8
8
  s.summary = "Event tracking and notifying for active record models"
9
9
  s.description = "Tracks and logs events and sends notifications of events on Active Record models."
10
+ s.license = 'MIT'
10
11
 
11
12
  s.rubyforge_project = "eventifier"
12
13
 
@@ -15,17 +16,18 @@ Gem::Specification.new do |s|
15
16
  s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
16
17
  s.require_paths = ["lib"]
17
18
 
18
- s.add_runtime_dependency 'rails', '~> 3.2.0'
19
+ s.add_runtime_dependency 'rails', '~> 4.0.3'
19
20
  s.add_runtime_dependency "bson_ext"
20
21
  s.add_runtime_dependency 'haml-rails', '~> 0.4'
21
22
  s.add_runtime_dependency 'haml_coffee_assets'
22
- s.add_runtime_dependency 'coffee-rails', '~> 3.2.1'
23
+ s.add_runtime_dependency 'coffee-rails', '~> 4.0.0'
23
24
  s.add_runtime_dependency 'compass-rails'
24
25
  s.add_runtime_dependency 'multi_json', '~> 1.7.4'
25
26
  s.add_runtime_dependency 'jbuilder', '~> 1.4.2'
27
+ s.add_runtime_dependency 'rails-observers', '~> 0.1.2'
26
28
 
27
29
  s.add_development_dependency 'combustion', '~> 0.5.0'
28
- s.add_development_dependency 'fabrication', '~> 2.7.1'
30
+ s.add_development_dependency 'fabrication', '~> 2.11.0'
29
31
  s.add_development_dependency "pg"
30
- s.add_development_dependency 'rspec-rails', '~> 2.13.2'
32
+ s.add_development_dependency 'rspec-rails', '~> 3.0.0.beta2'
31
33
  end
data/lib/eventifier.rb CHANGED
@@ -9,6 +9,8 @@
9
9
  #
10
10
  # end
11
11
 
12
+ require "rails/observers/activerecord/active_record"
13
+
12
14
  require 'multi_json'
13
15
  require 'action_mailer'
14
16
 
@@ -10,7 +10,8 @@ class Eventifier::EventBuilder
10
10
 
11
11
  def store
12
12
  Eventifier::Event.create user: user, eventable: object,
13
- groupable: groupable, verb: verb, change_data: change_data
13
+ groupable: groupable, verb: verb, change_data: change_data,
14
+ system: options[:system]
14
15
  end
15
16
 
16
17
  private
@@ -18,6 +19,8 @@ class Eventifier::EventBuilder
18
19
  attr_reader :object, :user, :verb, :groupable, :options
19
20
 
20
21
  def change_data
22
+ return options[:change_data] unless options[:change_data].nil?
23
+
21
24
  changes = object.changes.stringify_keys
22
25
 
23
26
  changes.reject! { |attribute, value|
@@ -1,23 +1,45 @@
1
1
  class Eventifier::NotificationTranslator
2
- def initialize(prefix, delivery, *args)
3
- @prefix, @delivery = prefix, delivery
2
+ def initialize(prefix, options, *args)
3
+ @prefix, @options = prefix, options
4
4
  @event = ActiveSupport::Notifications::Event.new(*args).payload[:event]
5
5
  end
6
6
 
7
7
  def translate
8
+ return if skip?
8
9
  users_and_relations do |user, relations|
9
- next if user == event.user
10
+ next if user == event.user && !options[:notify_self]
11
+ next if skip?(user)
10
12
 
11
13
  Eventifier::Notification.create event: event, user: user,
12
14
  relations: relations
13
15
 
14
- Eventifier::Delivery.deliver_for user if delivery == :immediate
16
+ Eventifier::Delivery.deliver_for user if options[:email] == :immediate
15
17
  end
16
18
  end
17
19
 
18
20
  private
19
21
 
20
- attr_reader :event, :prefix, :delivery
22
+ attr_reader :event, :prefix, :options
23
+
24
+ def skip?(user = nil)
25
+ if conditional
26
+ !conditional_call *[event.eventable, user][0..conditional.arity-1]
27
+ else
28
+ false
29
+ end
30
+ end
31
+
32
+ def conditional
33
+ options[:if] || options[:unless]
34
+ end
35
+
36
+ def conditional_call(*args)
37
+ if options[:if]
38
+ conditional.call(*args)
39
+ else
40
+ !conditional.call(*args)
41
+ end
42
+ end
21
43
 
22
44
  def users_and_relations(&block)
23
45
  Eventifier::NotificationMapping.users_and_relations event, prefix, &block
@@ -1,23 +1,23 @@
1
1
  class Eventifier::NotificationSubscriber
2
- def self.subscribe_to_method(klass, method_name, delivery)
3
- new(klass, method_name, delivery).subscribe_to_method
2
+ def self.subscribe_to_method(klass, method_name, options)
3
+ new(klass, method_name, options).subscribe_to_method
4
4
  end
5
5
 
6
- def initialize(klass, method_name, delivery)
7
- @klass, @method_name, @delivery = klass, method_name, delivery
6
+ def initialize(klass, method_name, options)
7
+ @klass, @method_name, @options = klass, method_name, options
8
8
  end
9
9
 
10
10
  def subscribe_to_method
11
11
  return if notifications.notifier.listening?(name)
12
12
 
13
13
  notifications.subscribe(name) do |*args|
14
- Eventifier::NotificationTranslator.new(prefix, delivery, *args).translate
14
+ Eventifier::NotificationTranslator.new(prefix, options, *args).translate
15
15
  end
16
16
  end
17
17
 
18
18
  private
19
19
 
20
- attr_reader :klass, :method_name, :delivery
20
+ attr_reader :klass, :method_name, :options
21
21
 
22
22
  def name
23
23
  "#{prefix}.notification.eventifier"
@@ -1,21 +1,21 @@
1
1
  class Eventifier::Notifier
2
+ OPTION_KEYS = [:email, :if, :unless, :notify_self]
2
3
  # arguments will either be [:relation, {:on => :create}] or
3
4
  # [{:relation => :second_relation, :on => :create}]
4
5
  # If it's the first one, relation is the first in the array, otherwise treat
5
6
  # the whole thing like a hash
6
7
  def initialize(klasses, *arguments)
7
- relation = arguments.shift if arguments.length == 2
8
+ relation = arguments.shift if arguments.length >= 2
8
9
  arguments = arguments.first || {}
9
10
 
10
11
  methods = Array arguments.delete(:on)
11
- delivery = arguments.delete(:email) || :delayed
12
-
13
- relation ||= arguments
12
+ options = arguments.slice *OPTION_KEYS
13
+ relation ||= arguments.except *OPTION_KEYS
14
14
 
15
15
  klasses.each do |target_klass|
16
16
  methods.each do |method_name|
17
17
  Eventifier::NotificationMapping.add "#{method_name}.#{target_klass.name.tableize}", relation
18
- Eventifier::NotificationSubscriber.subscribe_to_method target_klass, method_name, delivery
18
+ Eventifier::NotificationSubscriber.subscribe_to_method target_klass, method_name, options
19
19
  end
20
20
  end
21
21
  end