harbinger 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c8d9107e1f0f58e16654d251c1faa1c8959f86cc
4
- data.tar.gz: e35f452a7355e1a065e962ad3a27f9aebf08d034
3
+ metadata.gz: 9286ebe3e606bd4307248feb1794d0c6a4e84a44
4
+ data.tar.gz: ebe9958929959fbc584187e2afe50cae6eb0a5de
5
5
  SHA512:
6
- metadata.gz: 68d76e1139c5a602a2a15386f4d9cafa87a899ba95cf1b28432f0215b95d1e77b24119db3a118542b12b2da350d87d9f1c1260ce4534f7c26df5a99741ca85e1
7
- data.tar.gz: ce7e2a07029cda8eb3270a42717614ca41a441ecec51fdaee7a1be7e05da32530a40e4c56807c9ef0d5ef31336fea59249d30c21c89356a88e71280cc0453216
6
+ metadata.gz: 8c61a8b758e5fd0e1ec0f1aa11da0048195166cc8c55e3dfedbe53fe574a1917b5a0d083bcf68a389a5105c4d9f6fd276fa022917562110748bed57fc59f41cd
7
+ data.tar.gz: 7982c7f1af6d2d912d54ed749030e6b171378bb34c106973c6967b94c9930c1b87dcd77317d2c8f1b1b5546ab41f6425e896e3c63a5d8cc0c42d5c7a612acc1f
data/README.md CHANGED
@@ -8,3 +8,51 @@
8
8
  [![APACHE 2 License](http://img.shields.io/badge/APACHE2-license-blue.svg)](./LICENSE)
9
9
 
10
10
  A Rails engine for arbitrary message creation and delivery.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ gem 'blorg'
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install blorg
25
+
26
+ ## Usage
27
+
28
+ ```ruby
29
+ class PagesController
30
+ def show
31
+ @page = Page.find(params[:id])
32
+ rescue ActiveRecord::RecordNotFound => exception
33
+ Harbinger.call(
34
+ channels: [:database, :logger],
35
+ reporters: [exception, current_user, request]
36
+ )
37
+ end
38
+ end
39
+ ```
40
+
41
+ Given the above Rails-like code
42
+ When a user attempts to find a page that does not exist
43
+ Then an exception is raised
44
+ And the Harbinger.call code will:
45
+
46
+ * Build a message based on the three reporters:
47
+ * The raised exception
48
+ * The current user
49
+ * The request
50
+ * Deliver that message to the two channels:
51
+ * Database
52
+ * Logger
53
+
54
+ For further details I recommend delving into the [end to end exception handling spec](./spec/features/end_to_end_exception_handling_spec.rb)
55
+
56
+ ### Extending Contexts and Channels
57
+
58
+ Harbinger is built to allow for easy creation of new Contexts and Channels.
@@ -1,5 +1,5 @@
1
1
  module Harbinger
2
- class MessagesController < ActionController::Base
2
+ class MessagesController < ApplicationController
3
3
 
4
4
  def index
5
5
  messages
@@ -12,7 +12,7 @@ module Harbinger
12
12
  protected
13
13
 
14
14
  def messages
15
- @messages ||= DatabaseChannelMessage.search(q: params[:q])
15
+ @messages ||= DatabaseChannelMessage.page(params[:page]).search(q: params[:q])
16
16
  end
17
17
 
18
18
  def message
@@ -6,17 +6,18 @@ module Harbinger
6
6
  self.table_name = 'harbinger_messages'
7
7
  has_many :elements, class_name: 'Harbinger::DatabaseChannelMessageElement', foreign_key: :message_id
8
8
 
9
- def contexts=(values)
9
+ def reporters=(values)
10
10
  super(Array.wrap(values).join(','))
11
11
  end
12
12
 
13
- def contexts
13
+ def reporters
14
14
  super.split(',')
15
15
  end
16
16
 
17
17
  def self.store_message(message, storage = new)
18
- storage.contexts = message.contexts
18
+ storage.reporters = message.reporters
19
19
  storage.state = 'new'
20
+ storage.message_object_id = message.object_id
20
21
  message.attributes.each do |key, value|
21
22
  storage.elements.build(key: key, value: value)
22
23
  end
@@ -33,10 +34,13 @@ module Harbinger
33
34
  scope :search_text, lambda { |text|
34
35
  if text
35
36
  where(
36
- arel_table[:contexts].matches("#{text}%").
37
+ arel_table[:reporters].matches("#{text}%").
37
38
  or(
38
- arel_table[:id].
39
- in(Arel::SqlLiteral.new(DatabaseChannelMessageElement.search_text(text).select(:message_id).to_sql))
39
+ arel_table[:message_object_id].eq(text).
40
+ or(
41
+ arel_table[:id].
42
+ in(Arel::SqlLiteral.new(DatabaseChannelMessageElement.search_text(text).select(:message_id).to_sql))
43
+ )
40
44
  )
41
45
  )
42
46
  else
@@ -17,11 +17,14 @@
17
17
  </fieldset>
18
18
  <% end %>
19
19
 
20
+ <%= paginate messages %>
21
+
20
22
  <table>
21
23
  <caption>Messages</caption>
22
24
  <thead>
23
25
  <tr>
24
26
  <th>Created At</th>
27
+ <th>Message Object ID</th>
25
28
  <th>Contexts</th>
26
29
  <th>State</th>
27
30
  <th>Actions</th>
@@ -31,9 +34,10 @@
31
34
  <% messages.each do |message| %>
32
35
  <tr class="message">
33
36
  <td class="detail message-created-at-detail"><a href="<%= harbinger.message_path(message.to_param)%>"><time><%= message.created_at %></time></a></td>
37
+ <td class="detail message-object-id-detail"><%= message.message_object_id %></td>
34
38
  <td>
35
- <ul><% message.contexts.each do |context| %>
36
- <li class="detail message-contexts-detail"><%= context %></li>
39
+ <ul><% message.reporters.each do |context| %>
40
+ <li class="detail message-reporters-detail"><%= context %></li>
37
41
  <% end %></ul>
38
42
  </td>
39
43
  <td class="detail message-state-detail"><%= message.state %></td>
@@ -2,14 +2,16 @@
2
2
  <header class="message-header">
3
3
  <h2>Message</h2>
4
4
  <dl>
5
- <dt class="term message-contexts-term">Contexts</dt>
6
- <% message.contexts.each do |context| %>
7
- <dd class="detail message-contexts-detail"><%= context %></dd>
5
+ <dt class="term message-created-at-term">Created at</dt>
6
+ <dd class="detail message-created-at-detail"><time><%= message.created_at %></time></dd>
7
+ <dt class="term message-object-id-term">Message Object ID</dt>
8
+ <dd class="detail message-object-id-detail"><%= message.message_object_id %></dd>
9
+ <dt class="term message-reporters-term">Contexts</dt>
10
+ <% message.reporters.each do |context| %>
11
+ <dd class="detail message-reporters-detail"><%= context %></dd>
8
12
  <% end %>
9
13
  <dt class="term message-state-term">State</dt>
10
14
  <dd class="detail message-state-detail"><%= message.state %></dd>
11
- <dt class="term message-created-at-term">Created at</dt>
12
- <dd class="detail message-created-at-detail"><time><%= message.created_at %></time></dd>
13
15
  </dl>
14
16
  </header>
15
17
  <section class="message-body">
@@ -1,5 +1,6 @@
1
1
  Harbinger::Engine.routes.draw do
2
2
  scope module: 'harbinger' do
3
3
  resources :messages, only: [:index, :show]
4
+ root 'messages#index'
4
5
  end
5
6
  end
@@ -1,11 +1,13 @@
1
1
  class CreateHarbingerDatabaseChannelMessage < ActiveRecord::Migration
2
2
  def change
3
3
  create_table :harbinger_messages do |t|
4
- t.string :contexts
4
+ t.string :reporters
5
5
  t.string :state, limit: 32
6
+ t.integer :message_object_id
6
7
  t.timestamps
7
8
  end
8
9
  add_index :harbinger_messages, :state
9
- add_index :harbinger_messages, :contexts
10
+ add_index :harbinger_messages, :reporters
11
+ add_index :harbinger_messages, :message_object_id
10
12
  end
11
13
  end
@@ -9,6 +9,5 @@ class CreateHarbingerDatabaseChannelMessageElements < ActiveRecord::Migration
9
9
  add_index :harbinger_message_elements, :message_id
10
10
  add_index :harbinger_message_elements, :key
11
11
  add_index :harbinger_message_elements, [:message_id, :key]
12
- add_index :harbinger_message_elements, [:message_id, :value]
13
12
  end
14
13
  end
@@ -24,13 +24,15 @@ Gem::Specification.new do |spec|
24
24
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
25
25
  spec.require_paths = ["lib"]
26
26
 
27
+ spec.add_dependency "kaminari", "~> 0.16"
28
+ spec.add_dependency "rails", "~> 4.0"
29
+
27
30
  spec.add_development_dependency "bundler", "~> 1.5"
28
31
  spec.add_development_dependency "rake", '~> 10.3'
29
32
  spec.add_development_dependency "rspec-given", '~> 3.5'
30
33
  spec.add_development_dependency 'rspec-rails', '~> 3.0'
31
34
  spec.add_development_dependency 'rspec-html-matchers', '~>0.6'
32
35
  spec.add_development_dependency "engine_cart", '~> 0.3'
33
- spec.add_development_dependency "rails", "~> 4.0"
34
36
  spec.add_development_dependency 'sqlite3', '~> 1.3'
35
37
  spec.add_development_dependency 'database_cleaner', '~> 1.3'
36
38
  spec.add_development_dependency 'capybara', '~> 2.4'
@@ -6,21 +6,24 @@ require "harbinger/exceptions"
6
6
  require "harbinger/configuration"
7
7
 
8
8
  module Harbinger
9
- module_function
10
9
  class << self
11
10
  attr_writer :configuration
12
11
 
12
+ # @see Configuration
13
13
  def configuration
14
14
  @configuration ||= Configuration.new
15
15
  end
16
16
  end
17
17
 
18
18
  module_function
19
+
20
+ # @see Configuration
21
+ # @see .configuration
19
22
  def configure
20
23
  yield(configuration)
21
24
  end
22
25
 
23
- # Responsible for building a :message from the various :contexts and then
26
+ # Responsible for building a :message from the various :reporters and then
24
27
  # delivering the :message to the appropriate :channels.
25
28
  #
26
29
  # @see .build_message
@@ -29,7 +32,7 @@ module Harbinger
29
32
  # @param [Hash] options
30
33
  # @option options [Message] :message The message you want to amend.
31
34
  # If none is provided, then one is created.
32
- # @option options [Object, Array<Object>] :contexts One or more Objects that
35
+ # @option options [Object, Array<Object>] :reporters One or more Objects that
33
36
  # Harbinger will visit and extract message elements from.
34
37
  # @option options [Symbol, Array<Symbol>] :channels One or more channels that
35
38
  # Harbinger will deliver the :message to
@@ -38,20 +41,20 @@ module Harbinger
38
41
  deliver_message(message, options)
39
42
  end
40
43
 
41
- # Responsible for building a :message from the various :contexts.
44
+ # Responsible for building a :message from the various :reporters.
42
45
  #
43
46
  # @see .call
44
47
  #
45
48
  # @param [Hash] options
46
49
  # @option options [Message] :message The message you want to amend.
47
50
  # If none is provided, then one is created.
48
- # @option options [Object, Array<Object>] :contexts One or more Objects that
51
+ # @option options [Object, Array<Object>] :reporters One or more Objects that
49
52
  # Harbinger will visit and extract message elements from.
50
53
  def build_message(options = {})
51
- contexts = Array(options.fetch(:contexts)).flatten.compact
54
+ reporters = Array(options.fetch(:reporters)).flatten.compact
52
55
  message = options.fetch(:message) { default_message }
53
56
 
54
- contexts.each { |context| reporter_for(context).accept(message) }
57
+ reporters.each { |context| reporter_for(context).accept(message) }
55
58
  message
56
59
  end
57
60
 
@@ -89,14 +92,17 @@ module Harbinger
89
92
  end
90
93
  private_class_method :channel_for
91
94
 
95
+ # @api protected
92
96
  def default_channels
93
97
  configuration.default_channels
94
98
  end
95
99
 
100
+ # @api protected
96
101
  def logger
97
102
  configuration.logger
98
103
  end
99
104
 
105
+ # @api protected
100
106
  def database_storage
101
107
  configuration.database_storage
102
108
  end
@@ -1,3 +1,5 @@
1
+ require "kaminari"
2
+
1
3
  module Harbinger
2
4
  class Engine < ::Rails::Engine
3
5
  engine_name 'harbinger'
@@ -12,7 +12,7 @@ module Harbinger
12
12
  @attributes[composite_key] << value
13
13
  end
14
14
 
15
- def contexts
15
+ def reporters
16
16
  attributes.keys.collect { |key| key.split('.')[0] }.uniq
17
17
  end
18
18
 
@@ -21,7 +21,7 @@ module Harbinger
21
21
  if context.is_a?(Exception)
22
22
  "ExceptionReporter"
23
23
  else
24
- context.class.to_s.gsub(/(?:^|_)([a-z])/) { $1.upcase } + "Reporter"
24
+ context.class.to_s.split('::').last.gsub(/(?:^|_)([a-z])/) { $1.upcase } + "Reporter"
25
25
  end
26
26
  end
27
27
  private_class_method :reporter_name_for_instance
@@ -1,3 +1,3 @@
1
1
  module Harbinger
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -4,19 +4,15 @@ require 'harbinger'
4
4
  module Harbinger
5
5
  describe 'handling a message', type: :feature do
6
6
  let(:message) do
7
- begin
8
- {}.fetch(:missing_key)
9
- rescue KeyError => exception
10
- Harbinger.build_message(contexts: [exception])
11
- end
12
7
  end
13
8
 
14
9
  it 'sends the exception message to the database channel' do
15
10
  expect(Harbinger.logger).to receive(:add).at_least(:once).and_call_original
16
-
17
- expect { Harbinger.deliver_message(message, channels: [:database, :logger]) }.
18
- to change { DatabaseChannelMessage.count }.
19
- by(1)
11
+ begin
12
+ {}.fetch(:missing_key)
13
+ rescue KeyError => exception
14
+ Harbinger.call(channels: [:database, :logger], reporters: [exception])
15
+ end
20
16
 
21
17
  message = DatabaseChannelMessage.last
22
18
 
@@ -31,7 +27,7 @@ module Harbinger
31
27
  page.find(:xpath, "//a[@href='#{harbinger.message_path(message.to_param)}']").click
32
28
 
33
29
  expect(page.html).to have_tag('.message') do
34
- with_tag('.message-contexts-detail', text: 'exception')
30
+ with_tag('.message-reporters-detail', text: 'exception')
35
31
  with_tag('.message-state-detail', text: 'new')
36
32
  end
37
33
  end
@@ -9,7 +9,7 @@ module Harbinger
9
9
  When { message.append('other_container', 'other_key', 'other_value')}
10
10
 
11
11
  Then { expect(message.attributes).to eq({ 'container.key' => ['value'], 'other_container.other_key' => ['other_value']}) }
12
- And { expect(message.contexts).to eq(['container', 'other_container']) }
12
+ And { expect(message.reporters).to eq(['container', 'other_container']) }
13
13
 
14
14
  end
15
15
  end
@@ -2,6 +2,8 @@ require 'spec_fast_helper'
2
2
  require 'harbinger/reporters'
3
3
 
4
4
  module Harbinger
5
+ class Request
6
+ end
5
7
  describe Reporters do
6
8
  context '.find_for' do
7
9
  Given(:reporter) { double('Reporter') }
@@ -41,6 +43,12 @@ module Harbinger
41
43
  When(:result) { described_class.find_for(context) }
42
44
  Then { expect(result).to be_an_instance_of(Reporters::NullReporter) }
43
45
  end
46
+
47
+ context 'module conversion' do
48
+ Given(:context) { ::Harbinger::Request.new }
49
+ When(:result) { described_class.find_for(context) }
50
+ Then { expect(result).to be_an_instance_of(Reporters::RequestReporter) }
51
+ end
44
52
  end
45
53
  end
46
54
  end
@@ -14,7 +14,7 @@ describe Harbinger do
14
14
  Given(:channel_name) { 'channel_double' }
15
15
  Given(:channel) { double('Channel', deliver: true) }
16
16
  Given(:message) { Harbinger::Message.new }
17
- When { Harbinger.call(contexts: [user, request], message: message, channels: channel_name) }
17
+ When { Harbinger.call(reporters: [user, request], message: message, channels: channel_name) }
18
18
  Then do expect(message.attributes).to eq(
19
19
  'user.username' => [user.username],
20
20
  'request.path' => [request.path],
@@ -27,7 +27,7 @@ describe Harbinger do
27
27
 
28
28
  context '.build_message' do
29
29
  Given(:message) { Harbinger::Message.new }
30
- When { Harbinger.build_message(contexts: [user, request], message: message) }
30
+ When { Harbinger.build_message(reporters: [user, request], message: message) }
31
31
  Then do expect(message.attributes).to eq(
32
32
  'user.username' => [user.username],
33
33
  'request.path' => [request.path],
@@ -57,10 +57,10 @@ module Harbinger
57
57
  by(3)
58
58
  end
59
59
 
60
- it 'assigns :contexts' do
60
+ it 'assigns :reporters' do
61
61
  storage = described_class.new
62
62
  expect { described_class.store_message(message, storage) }.
63
- to change { storage.attributes.values_at('contexts', 'state') }.
63
+ to change { storage.attributes.values_at('reporters', 'state') }.
64
64
  from([nil, nil]).to(['exception,user', 'new'])
65
65
  end
66
66
  end
@@ -8,13 +8,15 @@ describe 'harbinger/messages/index.html.erb', type: :view do
8
8
  let(:message) do
9
9
  double(
10
10
  'Message',
11
- contexts: ['Hello','World'],
11
+ reporters: ['Hello','World'],
12
12
  state: 'new',
13
- created_at: created_at
13
+ created_at: created_at,
14
+ message_object_id: '123456'
14
15
  )
15
16
  end
16
17
  let(:harbinger) { double("Engine", message_path: true, messages_path: '/messages/') }
17
18
  it 'renders the object and fieldsets' do
19
+ expect(view).to receive(:paginate).with([message])
18
20
  expect(harbinger).to receive(:message_path).with(message.to_param).and_return("/messages/#{message.to_param}")
19
21
  render template: "harbinger/messages/index.html.erb", locals: { messages: [message], harbinger: harbinger }
20
22
  expect(rendered).to have_tag('.search-form') do
@@ -23,8 +25,9 @@ describe 'harbinger/messages/index.html.erb', type: :view do
23
25
 
24
26
  expect(rendered).to have_tag('.message') do
25
27
  with_tag(".detail.message-created-at-detail a time", text: created_at.to_s)
26
- with_tag('.detail.message-contexts-detail', text: 'Hello')
27
- with_tag('.detail.message-contexts-detail', text: 'World')
28
+ with_tag('.detail.message-object-id-detail', text: message.message_object_id)
29
+ with_tag('.detail.message-reporters-detail', text: 'Hello')
30
+ with_tag('.detail.message-reporters-detail', text: 'World')
28
31
  with_tag('.detail.message-state-detail', text: 'new')
29
32
  end
30
33
  end
@@ -10,9 +10,10 @@ describe 'harbinger/messages/show.html.erb', type: :view do
10
10
  let(:message) do
11
11
  double(
12
12
  'Message',
13
- contexts: ['Hello','World'],
13
+ reporters: ['Hello','World'],
14
14
  state: 'new',
15
15
  created_at: created_at,
16
+ message_object_id: '123456',
16
17
  elements: [netid_element, request_referrer_element]
17
18
  )
18
19
  end
@@ -20,9 +21,11 @@ describe 'harbinger/messages/show.html.erb', type: :view do
20
21
  render template: 'harbinger/messages/show',
21
22
  locals: { message: message }
22
23
  expect(rendered).to have_tag('article.message') do
23
- with_tag('.term.message-contexts-term')
24
- with_tag('.detail.message-contexts-detail', text: 'Hello')
25
- with_tag('.detail.message-contexts-detail', text: 'World')
24
+ with_tag('.term.message-reporters-term')
25
+ with_tag('.detail.message-reporters-detail', text: 'Hello')
26
+ with_tag('.detail.message-reporters-detail', text: 'World')
27
+ with_tag('.term.message-object-id-term')
28
+ with_tag('.detail.message-object-id-detail', text: '123456')
26
29
  with_tag('.term.message-state-term')
27
30
  with_tag('.detail.message-state-detail', text: 'new')
28
31
  with_tag('.term.message-created-at-term')
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: harbinger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Friesen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-18 00:00:00.000000000 Z
11
+ date: 2014-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: kaminari
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.16'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -94,20 +122,6 @@ dependencies:
94
122
  - - "~>"
95
123
  - !ruby/object:Gem::Version
96
124
  version: '0.3'
97
- - !ruby/object:Gem::Dependency
98
- name: rails
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '4.0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '4.0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: sqlite3
113
127
  requirement: !ruby/object:Gem::Requirement