harbinger 0.1.0 → 0.2.0

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