chatterbox 0.6.2 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,20 @@
1
- HEAD
1
+ # Changelog
2
+
3
+ ### 0.8.1
4
+ - Make sure Arrays format nicely for ExceptionNotification, so backtraces are easy to read
5
+
6
+ ### 0.8.0
7
+ - Simplify API - prefer :summary and :body at top level, instead of inside a nested :message hash
8
+ - Simplify output for exception notification; using pretty print so hashes render well, even if they are nested
9
+ - Deprecate `Chatterbox#handle_notice` -- use `#notify` instead; will be removed for 1.0
10
+ - Any user provided data sent to ExceptionNotification will be rendered first in the body, as we assume its the application specific data and most useful; details like the environment vars and Ruby/Rails details will follow beneath
11
+
12
+ ### 0.7.0
13
+ - Add ability to configure `summary_prefix` for emails, for common case of "[my-app] " prefix line in the subject line for emails
14
+
15
+ ### 0.6.2
16
+ - Change to ExceptionNotification: render any other data in the hash that is not
17
+ enumerated in 'ordered sections'
2
18
 
3
19
  ### 0.6.1
4
20
  - Handle case where there is no Rails Root defined
@@ -1,25 +1,81 @@
1
1
  Chatterbox
2
2
  ==========================================
3
3
 
4
- Simple Notifications. Publishing and subscribing to notifications is decoupled by default, so bring your own message queue, web service, database, or whatever to act as an intermediary. Of course, you can wire Chatterbox to work directly if you like.
4
+ Chatterbox is a library to send notifications and messages over whatever service you like, whether it be email, Twitter, Campfire, IRC, or some combination therein. The goal of Chatterbox is to be able to send a message from your application via whatever service the user prefers simple by tweaking the configuration hash that gets sent to Chatterbox.
5
+
6
+ Publishing and subscribing to notifications can be decoupled easily, so bring your own message queue, web service, database, or whatever to act as an intermediary. Or keep it simple and wire Chatterbox directly - its your choice.
5
7
 
6
8
  ## Installing and Running
7
9
 
8
10
  For plain old gem install:
9
11
 
10
- gem install chatterbox "source" => "http://gemcutter.org"
12
+ gem install chatterbox
11
13
 
12
14
  To install within a Rails app, add the following to your environment.rb file:
13
15
 
14
- config.gem "chatterbox", :source => "http://gemcutter.org"
16
+ config.gem "chatterbox"
15
17
 
16
18
  Then run:
17
19
 
18
20
  rake gems:install
19
21
 
22
+ ## Services
23
+
24
+ Services are used to send notifications in Chatterbox. Chatterbox comes with an email service for use out of the box, which uses ActionMailer and works pretty much how you would expect.
25
+
26
+ ## Email Service Configuration
27
+
28
+ Register the email service to handle messages that get sent to Chatterbox:
29
+
30
+ Chatterbox::Publishers.register do |notice|
31
+ Chatterbox::Services::Email.deliver(notice)
32
+ end
33
+
34
+ Then, wherever you want to send email, do this:
35
+
36
+ options = {
37
+ :summary => "your subject line here", :body => "Email body here",
38
+ :config => { :to => "joe@example.com", :from => "donotreply@example.com" },
39
+ }
40
+ Chatterbox.notify(options)
41
+
42
+ You can configure defaults for the email service:
43
+
44
+ Chatterbox::Services::Email.configure({
45
+ :to => "joe@example.com",
46
+ :from => "jane@example.com",
47
+ :summary_prefix => "[my-prefix] "
48
+ })
49
+
50
+ Then when you deliver messages, the provided options will be merged with the defaults:
51
+
52
+ Chatterbox.notify(:message => { :summary => "my subject" })
53
+
54
+ Sends:
55
+
56
+ To: joe@example.com
57
+ From: jane@example.com
58
+ Subject: [my-prefix] my subject
59
+
60
+ While the following overrides the default to and from addresses...
61
+
62
+ options = {
63
+ :summary => "my subject",
64
+ :config => { :to => "distro@example.com", :from => "reply@example.com" },
65
+ }
66
+ Chatterbox.notify(options)
67
+
68
+ Sends:
69
+
70
+ To: distro@example.com
71
+ From: reply@example.com
72
+ Subject: [my-prefix] my subject
73
+
20
74
  ## Exception Notification
21
75
 
22
- One of the most handy use cases Chatterbox was developed for was Rails exception notification. To setup Chatterbox for exception notification, install it as a gem with the instructions above, then configure it inside an initializer:
76
+ One of the most handy use cases Chatterbox was developed for is exception notification. Chatterbox can be configured for Rails exception catching from controllers, and can be used in a plain Ruby app as well.
77
+
78
+ To setup Chatterbox for Rails exception notification, install it as a gem with the instructions above, then configure it inside an initializer:
23
79
 
24
80
  Chatterbox::Services::Email.configure :to => "errors@example.com", :from => "donotreply@example.com"
25
81
 
@@ -35,33 +91,23 @@ then wire the `RailsCatcher` in your `ApplicationController`:
35
91
 
36
92
  And you are done! Exceptions thrown in controllers will automatically be processed and sent by Chatterbox, and then raised to be handled (or not handled) as normally.
37
93
 
38
- ## Example: Sending Emails
39
- ---------------------------------------
40
-
41
- Register the email service to handle messages that get sent to Chatterbox:
42
-
43
- Chatterbox::Publishers.register do |notice|
44
- Chatterbox::Services::Email.deliver(notice)
45
- end
46
-
47
- Then, wherever you want to send email, do this:
48
-
49
- message = {
50
- :config => { :to => "joe@example.com", :from => "donotreply@example.com" },
51
- :message => { :summary => "your subject line here", :body => "Email body here" }
52
- }
53
- Chatterbox.handle_notice(options)
54
94
 
55
95
  Bugs & Patches
56
96
  --------------
97
+ Please submit to [Github Issues](http://github.com/rsanheim/chatterbox/TODO).
98
+
99
+ All patches must have spec coverage and a passing build, or they will be pushed back.
100
+
101
+ You can easily verify your build by pushing the project to [RunCodeRun](http://runcoderun.com). View the [master build](http://runcoderun.com/rsanheim/chatterbox) to confirm that HEAD is stable and passing.
57
102
 
58
103
  Links
59
104
  -------------
60
105
 
61
106
  Contributors
62
107
  ------------
63
- * Rob Sanheim
108
+ * Rob Sanheim (creator)
109
+ * Chad Humphries (API ideas)
64
110
 
65
111
  Copyrights
66
112
  ------------
67
- * Copyright © 2008-2009 [Relevance, Inc.](http://www.thinkrelevance.com/), under the MIT license
113
+ Copyright © 2008-2009 Rob Sanheim under the MIT license
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rake'
2
+ require 'cucumber/rake/task'
2
3
 
3
4
  begin
4
5
  require 'jeweler'
@@ -31,7 +32,15 @@ Micronaut::RakeTask.new(:rcov) do |examples|
31
32
  examples.rcov = true
32
33
  end
33
34
 
34
- task :default => :examples
35
+ Cucumber::Rake::Task.new :features do |t|
36
+ t.cucumber_opts = %w{--format progress}
37
+ end
38
+
39
+ if RUBY_VERSION == '1.9.1'
40
+ task :default => [:examples, :features]
41
+ else
42
+ task :default => [:rcov, :features]
43
+ end
35
44
 
36
45
  begin
37
46
  %w{sdoc sdoc-helpers rdiscount}.each { |name| gem name }
@@ -1,5 +1,5 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 6
4
- :patch: 2
3
+ :minor: 8
4
+ :patch: 1
5
5
  :build:
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{chatterbox}
8
- s.version = "0.6.2"
8
+ s.version = "0.8.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Rob Sanheim"]
12
- s.date = %q{2009-11-17}
12
+ s.date = %q{2009-11-26}
13
13
  s.description = %q{Send notifications and messages. However you want.}
14
14
  s.email = %q{rsanheim@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -18,7 +18,6 @@ Gem::Specification.new do |s|
18
18
  ]
19
19
  s.files = [
20
20
  ".gitignore",
21
- ".treasure_map.rb",
22
21
  "CHANGELOG.markdown",
23
22
  "LICENSE",
24
23
  "README.markdown",
@@ -32,10 +31,16 @@ Gem::Specification.new do |s|
32
31
  "examples/lib/chatterbox/exception_notification_example.rb",
33
32
  "examples/lib/chatterbox/rails_catcher_controller_example.rb",
34
33
  "examples/lib/chatterbox/rails_catcher_example.rb",
34
+ "examples/lib/chatterbox/services/campfire_example.rb",
35
35
  "examples/lib/chatterbox/services/email/mailer_example.rb",
36
36
  "examples/lib/chatterbox/services/email_example.rb",
37
37
  "examples/lib/chatterbox/services_example.rb",
38
38
  "examples/lib/chatterbox_example.rb",
39
+ "features/api.feature",
40
+ "features/email_service.feature",
41
+ "features/services.feature",
42
+ "features/step_definitions/email_service_steps.rb",
43
+ "features/support/env.rb",
39
44
  "init.rb",
40
45
  "lib/chatterbox.rb",
41
46
  "lib/chatterbox/exception_notification.rb",
@@ -44,10 +49,12 @@ Gem::Specification.new do |s|
44
49
  "lib/chatterbox/exception_notification/rails_extracter.rb",
45
50
  "lib/chatterbox/rails_catcher.rb",
46
51
  "lib/chatterbox/services.rb",
52
+ "lib/chatterbox/services/campfire.rb",
47
53
  "lib/chatterbox/services/email.rb",
48
54
  "lib/chatterbox/services/email/mailer.rb",
49
55
  "lib/chatterbox/services/email/views/chatterbox/services/email/mailer/message.erb",
50
56
  "rails/init.rb",
57
+ "tmp/.gitignore",
51
58
  "todo.markdown",
52
59
  "views/chatterbox/mailer/exception_notification.erb"
53
60
  ]
@@ -64,6 +71,7 @@ Gem::Specification.new do |s|
64
71
  "examples/lib/chatterbox/exception_notification_example.rb",
65
72
  "examples/lib/chatterbox/rails_catcher_controller_example.rb",
66
73
  "examples/lib/chatterbox/rails_catcher_example.rb",
74
+ "examples/lib/chatterbox/services/campfire_example.rb",
67
75
  "examples/lib/chatterbox/services/email/mailer_example.rb",
68
76
  "examples/lib/chatterbox/services/email_example.rb",
69
77
  "examples/lib/chatterbox/services_example.rb",
@@ -24,7 +24,7 @@ end
24
24
 
25
25
  def valid_options(overrides = {})
26
26
  options = {
27
- :message => { :summary => "here is a message" },
27
+ :summary => "here is a message",
28
28
  :config => {
29
29
  :to => "joe@example.com",
30
30
  :from => "someone@here.com"
@@ -4,11 +4,9 @@ require 'chatterbox/exception_notification'
4
4
  describe Chatterbox::ExceptionNotification::Extracter do
5
5
 
6
6
  describe "notice" do
7
- it "merges ruby info" do
8
- hsh = {}
9
- data = Chatterbox::ExceptionNotification::Extracter.new(hsh).notice
10
- data[:ruby_info][:ruby_version].should == RUBY_VERSION
11
- data[:ruby_info][:ruby_platform].should == RUBY_PLATFORM
7
+ it "merges default summary if none provided" do
8
+ data = Chatterbox::ExceptionNotification::Extracter.new({}).notice
9
+ data[:summary].should == "N/A"
12
10
  end
13
11
 
14
12
  it "merges environment hash" do
@@ -17,6 +15,13 @@ describe Chatterbox::ExceptionNotification::Extracter do
17
15
  data[:environment].should == ENV.to_hash
18
16
  end
19
17
 
18
+ it "merges ruby info" do
19
+ hsh = {}
20
+ data = Chatterbox::ExceptionNotification::Extracter.new(hsh).notice
21
+ data[:ruby_info][:ruby_version].should == RUBY_VERSION
22
+ data[:ruby_info][:ruby_platform].should == RUBY_PLATFORM
23
+ end
24
+
20
25
  it "should extract exception info from an exception in a hash" do
21
26
  exception = RuntimeError.new("Your zing bats got mixed up with the snosh frazzles.")
22
27
  data = Chatterbox::ExceptionNotification::Extracter.new(:exception => exception, :other_info => "yo dawg").notice
@@ -6,6 +6,12 @@ describe Chatterbox::ExceptionNotification::Presenter do
6
6
  describe "body" do
7
7
  it "should render sections in order" do
8
8
  options = {
9
+ :request => {
10
+ :parameters => {
11
+ :remote_ip => "10.0.0.1",
12
+ :array => [1,2,3]
13
+ }
14
+ },
9
15
  :environment => { "PATH" => "/usr/bin" },
10
16
  :error_message => "ActionView::MissingTemplate: Missing template projects/show.erb in view path app/views",
11
17
  :ruby_info => {
@@ -17,33 +23,68 @@ describe Chatterbox::ExceptionNotification::Presenter do
17
23
  expected =<<EOL
18
24
  Error Message
19
25
  ----------
20
- "ActionView::MissingTemplate: Missing template projects/show.erb in view path app/views"
26
+ ActionView::MissingTemplate: Missing template projects/show.erb in view path app/views
27
+
28
+ Request
29
+ ----------
30
+ parameters: {"remote_ip"=>"10.0.0.1", "array"=>[1, 2, 3]}
21
31
 
22
32
  Environment
23
33
  ----------
24
- PATH: /usr/bin
34
+ PATH: "/usr/bin"
25
35
 
26
36
  Ruby Info
27
37
  ----------
28
- ruby_platform: darwin
29
- ruby_version: 1.8.6
38
+ ruby_platform: "darwin"
39
+ ruby_version: "1.8.6"
30
40
  EOL
31
- presenter.body.strip.should == expected.strip
41
+ presenter.render_body.strip.should == expected.strip
32
42
  end
33
43
 
34
- it "renders un ordered sections" do
35
- options = {:exception => Exception.new, :other => "values and things", :hey => {"dragons" => "here"}}
44
+ it "renders 'extra' sections before explicitly named, ordered sections" do
45
+ options = {
46
+ :environment => { "PATH" => "/usr/bin" },
47
+ :special_details => "alert! system compromised in sector 8!"
48
+ }
36
49
  presenter = Chatterbox::ExceptionNotification::Presenter.new(options)
37
50
  expected =<<EOL
38
- Other
51
+ Special Details
39
52
  ----------
40
- values and things
53
+ alert! system compromised in sector 8!
41
54
 
42
- Hey
55
+ Environment
43
56
  ----------
44
- dragons: here
57
+ PATH: "/usr/bin"
45
58
  EOL
46
- presenter.body.should include(expected)
59
+ presenter.render_body.strip.should == expected.strip
60
+ end
61
+
62
+ it "renders unordered sections" do
63
+ options = {:error_message => "Runtime error has occured", :details => "values and things"}
64
+ presenter = Chatterbox::ExceptionNotification::Presenter.new(options)
65
+ expected =<<EOL
66
+ Details
67
+ ----------
68
+ values and things
69
+ EOL
70
+ presenter.render_body.should include(expected)
71
+ end
72
+
73
+ it "renders backtrace cleanly" do
74
+ backtrace = ["/codes/file.rb:52:in `some_method'",
75
+ "/codes/file.rb:10:in `different_method'",
76
+ "/gems/some_gem/gem_name.rb:104:in `main'"]
77
+ options = { :backtrace => backtrace }
78
+ presenter = Chatterbox::ExceptionNotification::Presenter.new(options)
79
+ expected =<<EOL
80
+ Backtrace
81
+ ----------
82
+ /codes/file.rb:52:in `some_method'
83
+ /codes/file.rb:10:in `different_method'
84
+ /gems/some_gem/gem_name.rb:104:in `main'
85
+
86
+ EOL
87
+ presenter.render_body.should == expected
47
88
  end
48
89
  end
49
90
 
@@ -64,7 +105,7 @@ EOL
64
105
  end
65
106
  end
66
107
 
67
- describe "inspect_value" do
108
+ describe "render_hash" do
68
109
  it "outputs hashes in key sorted order" do
69
110
  hash = {
70
111
  "my-key" => "something",
@@ -74,31 +115,12 @@ EOL
74
115
  }
75
116
  presenter = Chatterbox::ExceptionNotification::Presenter.new
76
117
  expected =<<-EOL
77
- abcdefg: foo
78
- my-key: something
79
- nanite: something
80
- zephyr: something
118
+ abcdefg: "foo"
119
+ my-key: "something"
120
+ nanite: "something"
121
+ zephyr: "something"
81
122
  EOL
82
- presenter.inspect_value(hash).should == expected.strip
83
- end
84
- end
85
-
86
- describe "prettyify_output" do
87
- it "should strip leading --- from to_yaml and retrun pretty output for hashes" do
88
- hash = { "my-key" => "some string value", "my-other-key" => "something" }
89
- presenter = Chatterbox::ExceptionNotification::Presenter.new
90
- output = presenter.inspect_value(hash)
91
- # NOTE: Handling different hash order below, between 1.8.x and 1.9.1
92
- actual_lines = output.split("\n")
93
- expected_lines = ["my-key: some string value", "my-other-key: something"]
94
- expected_lines.each { |line| actual_lines.should include(line) }
95
- end
96
-
97
- it "should strip leading --- from strings" do
98
- presenter = Chatterbox::ExceptionNotification::Presenter.new
99
- output = presenter.inspect_value("just a simple string")
100
- output.should == "just a simple string"
123
+ presenter.render_hash(hash).should == expected
101
124
  end
102
125
  end
103
-
104
126
  end
@@ -76,7 +76,7 @@ describe WidgetsController do
76
76
  end
77
77
 
78
78
  it "ignores anything configured on the ignore list" do
79
- Chatterbox.expects(:handle_notice).never
79
+ Chatterbox.expects(:notify).never
80
80
  begin
81
81
  @controller.rescue_action_in_public(RuntimeError.new)
82
82
  rescue RuntimeError; end
@@ -99,7 +99,7 @@ describe WidgetsController do
99
99
  end
100
100
 
101
101
  it "ignores anything configured on the ignore list" do
102
- Chatterbox.expects(:handle_notice).never
102
+ Chatterbox.expects(:notify).never
103
103
  begin
104
104
  @controller.rescue_action_in_public(RuntimeError.new)
105
105
  rescue RuntimeError; end
@@ -6,17 +6,24 @@ describe Chatterbox::Services::Email::Mailer do
6
6
 
7
7
  describe "wiring the email" do
8
8
  it "should set subject to the summary" do
9
- email = Chatterbox::Services::Email::Mailer.create_message(valid_options.merge(:message => { :summary => "check this out"}))
9
+ email = Chatterbox::Services::Email::Mailer.create_message(valid_options(:summary => "check this out"))
10
10
  email.subject.should == "check this out"
11
11
  end
12
12
 
13
+ it "should use :summary_prefix if provided" do
14
+ options = valid_options.dup
15
+ options[:config] = valid_options[:config].merge(:summary_prefix => "[my-app] [notifications] ")
16
+ email = Chatterbox::Services::Email::Mailer.create_message(options)
17
+ email.subject.should == "[my-app] [notifications] here is a message"
18
+ end
19
+
13
20
  it "should not require a body (for emails that are subject only)" do
14
- email = Chatterbox::Services::Email::Mailer.create_message(valid_options.merge(:message => { :body => nil}))
21
+ email = Chatterbox::Services::Email::Mailer.create_message(valid_options.merge(:body => nil))
15
22
  email.body.should be_blank # not nil for some reason -- ActionMailer provides an empty string somewhere
16
23
  end
17
24
 
18
25
  it "should set body to the body" do
19
- email = Chatterbox::Services::Email::Mailer.create_message(valid_options.merge(:message => { :body => "here is my body"}))
26
+ email = Chatterbox::Services::Email::Mailer.create_message(valid_options(:body => "here is my body"))
20
27
  email.body.should == "here is my body"
21
28
  end
22
29
 
@@ -27,7 +34,6 @@ describe Chatterbox::Services::Email::Mailer do
27
34
  end
28
35
 
29
36
  describe "content type" do
30
-
31
37
  it "can be set" do
32
38
  Chatterbox::Services::Email::Mailer.create_message(valid_options.merge(:config => { :content_type => "text/html"})).content_type.should == "text/html"
33
39
  end
@@ -20,14 +20,14 @@ describe Chatterbox::Services::Email do
20
20
  end
21
21
 
22
22
  it "should preserve HashWithIndifferentAccess with explicit options" do
23
- options = { :message => { :summary => "foo" }, :config => {:to => "a", :from => "a"} }.with_indifferent_access
23
+ options = { :summary => "foo", :config => { :to => "a", :from => "a" } }.with_indifferent_access
24
24
  service = Chatterbox::Services::Email.new(options)
25
25
  service.options.should be_instance_of(HashWithIndifferentAccess)
26
26
  service.options[:config].should be_instance_of(HashWithIndifferentAccess)
27
27
  end
28
28
 
29
29
  it "should preserve HashWithIndifferentAccess with default configuration" do
30
- options = { :message => { :summary => "foo" } }.with_indifferent_access
30
+ options = { :summary => "foo" }.with_indifferent_access
31
31
  Chatterbox::Services::Email.configure :to => "default-to@example.com", :from => "default-from@example.com"
32
32
  service = Chatterbox::Services::Email.new(options)
33
33
  service.options.should be_instance_of(HashWithIndifferentAccess)
@@ -36,51 +36,55 @@ describe Chatterbox::Services::Email do
36
36
  end
37
37
 
38
38
  describe "validations" do
39
- it "should require :message" do
40
- lambda {
41
- Chatterbox::Services::Email.deliver(:config => { :from => "foo", :to => "foo"})
42
- }.should raise_error(ArgumentError, /Must configure with a :message/)
43
- end
44
-
45
- it "should require :message => :summary" do
46
- lambda {
47
- Chatterbox::Services::Email.deliver(:message => {}, :config => { :from => "foo", :to => "foo"})
48
- }.should raise_error(ArgumentError, /Must provide :summary in the :message/)
39
+ describe ":summary requirement" do
40
+ it "allows top level :summary" do
41
+ Chatterbox::Services::Email.deliver(:summary => "summary!", :config => { :from => "foo", :to => "foo"})
42
+ end
43
+
44
+ it "allows nested :message for backwards compatibility" do
45
+ Chatterbox::Services::Email.deliver(:message => {:summary => "summary!"}, :config => { :from => "foo", :to => "foo"})
46
+ end
47
+
48
+ it "requires top level :summary" do
49
+ lambda {
50
+ Chatterbox::Services::Email.deliver(:config => { :from => "foo", :to => "foo"})
51
+ }.should raise_error(ArgumentError, /Must provide a :summary for your message/)
52
+ end
49
53
  end
50
54
 
51
- it "should require :to" do
55
+ it "requires :to address" do
52
56
  lambda {
53
- Chatterbox::Services::Email.deliver(:message => {:summary => ""}, :config => { :from => "anyone" })
57
+ Chatterbox::Services::Email.deliver(:summary => "", :config => { :from => "anyone" })
54
58
  }.should raise_error(ArgumentError, /Must provide :to in the :config/)
55
59
  end
56
60
 
57
- it "should require from address" do
61
+ it "requires :from address" do
58
62
  lambda {
59
- Chatterbox::Services::Email.deliver(:message => {:summary => ""}, :config => { :to => "anyone"})
63
+ Chatterbox::Services::Email.deliver(:summary => "", :config => { :to => "anyone"})
60
64
  }.should raise_error(ArgumentError, /Must provide :from in the :config/)
61
65
  end
62
66
  end
63
67
 
64
68
  describe "default_configuration=" do
65
- it "should default to empty hash" do
69
+ it "defaults to empty hash" do
66
70
  Chatterbox::Services::Email.default_configuration.should == {}
67
71
  end
68
72
 
69
- it "should set a hash of default configuration into :config hash" do
73
+ it "sets default configuration into :config" do
70
74
  Chatterbox::Services::Email.configure :to => "to@example.com", :from => "from@example.com"
71
75
  Chatterbox::Services::Email.default_configuration.should == { :to => "to@example.com", :from => "from@example.com"}
72
76
  end
73
77
 
74
- it "should use default configuration" do
78
+ it "uses default configuration if no per-message configuration provided" do
75
79
  Chatterbox::Services::Email.configure :to => "to@example.com", :from => "from@example.com"
76
- mail = Chatterbox::Services::Email.deliver(:message => {:summary => "summary"})
80
+ mail = Chatterbox::Services::Email.deliver(:summary => "summary")
77
81
  mail.to.should == ["to@example.com"]
78
82
  mail.from.should == ["from@example.com"]
79
83
  end
80
84
 
81
- it "should allow message specific configuration" do
85
+ it "allows per message configuration (if provided) to override default configuration" do
82
86
  Chatterbox::Services::Email.configure :to => "default-to@example.com", :from => "default-from@example.com"
83
- mail = Chatterbox::Services::Email.deliver(:message => {:summary => "summary"},
87
+ mail = Chatterbox::Services::Email.deliver(:summary => "summary",
84
88
  :config => { :to => "joe@example.com", :from => "harry@example.com"} )
85
89
  mail.to.should == ["joe@example.com"]
86
90
  mail.from.should == ["harry@example.com"]
@@ -4,19 +4,36 @@ describe Chatterbox do
4
4
 
5
5
  before { Chatterbox::Publishers.clear! }
6
6
 
7
- describe "handle_notice" do
7
+ describe "notify" do
8
8
  it "should return notification" do
9
- Chatterbox.handle_notice("message").should == "message"
9
+ Chatterbox.notify("message").should == "message"
10
10
  end
11
11
 
12
- it "should publish the notice" do
12
+ it "should publish the notification" do
13
13
  Chatterbox.expects(:publish_notice).with({})
14
- Chatterbox.handle_notice({})
14
+ Chatterbox.notify({})
15
15
  end
16
16
 
17
- it "should alias to notify" do
18
- Chatterbox.expects(:publish_notice).with("message")
19
- Chatterbox.notify("message")
17
+ describe "handle_notice alias" do
18
+ it "is an publishes notification" do
19
+ Chatterbox.expects(:publish_notice).with({})
20
+ ActiveSupport::Deprecation.silence do
21
+ Chatterbox.handle_notice({})
22
+ end
23
+ end
24
+
25
+ it "is deprecated" do
26
+ Chatterbox.expects(:deprecate).with("Chatterbox#handle_notice is deprecated and will be removed from Chatterbox 1.0. Call Chatterbox#notify instead.", anything)
27
+ Chatterbox.handle_notice("message")
28
+ end
29
+ end
30
+ end
31
+
32
+ describe "deprecation" do
33
+ it "uses ActiveSupport's Deprecation#warn" do
34
+ stack = caller
35
+ ActiveSupport::Deprecation.expects(:warn).with("deprecation warning here", stack)
36
+ Chatterbox.deprecate("deprecation warning here", stack)
20
37
  end
21
38
  end
22
39
 
@@ -60,4 +77,11 @@ describe Chatterbox do
60
77
  end
61
78
  end
62
79
 
80
+ describe "register" do
81
+ it "registers publisher" do
82
+ pub = Chatterbox.register { "publisher" }
83
+ Chatterbox::Publishers.publishers.should == [pub]
84
+ end
85
+ end
86
+
63
87
  end
@@ -0,0 +1,20 @@
1
+ Feature: Chatterbox API
2
+
3
+ As a Chatterbox user
4
+ I want to send messages using a simple API
5
+ So that I can deliver many different types of notifications using different services
6
+
7
+ Scenario: API ideas
8
+ Given a file named "api_testing.rb" with:
9
+ """
10
+ Chatterbox.notify(:summary => "subject", :body => "body",
11
+ :via => { :service => :email, :to => "jdoe@example.com", :from => "foo.com" },
12
+ :via => { :service => :twitter, :to => "rsanheim" },
13
+ :via => { :destinations => ["my-inbox", "joes-house" }
14
+
15
+ Chatterbox.notify(:summary => "subject", :body => "body") do |via|
16
+ via.email :to => "jdoe@example.com", :from => "foo.com"
17
+ via.twitter :to => "twitter"
18
+ end
19
+ """
20
+
@@ -0,0 +1,46 @@
1
+ Feature: Sending email
2
+
3
+ As a Chatterbox user
4
+ I want to send emails using the same options as any other service
5
+ So that I can deliver notifications over email
6
+
7
+ Scenario: Simple email sending
8
+ Given a file named "simple_email_sending.rb" with:
9
+ """
10
+ require "chatterbox"
11
+ require "chatterbox/services/email"
12
+ ActionMailer::Base.delivery_method = :test
13
+
14
+ Chatterbox::Publishers.register do |notice|
15
+ Chatterbox::Services::Email.deliver(notice)
16
+ end
17
+ Chatterbox.notify :summary => "subject", :body => "body",
18
+ :config => { :to => "joe@example.com", :from => "sender@example.com" }
19
+ puts ActionMailer::Base.deliveries.last.encoded
20
+ """
21
+ When I run "simple_email_sending.rb"
22
+ Then the exit code should be 0
23
+ And the stdout should match "To: joe@example.com"
24
+
25
+ Scenario: Sending with default configuration
26
+ Given a file named "default_configuration_email_send.rb" with:
27
+ """
28
+ require "chatterbox"
29
+ require "chatterbox/services/email"
30
+ ActionMailer::Base.delivery_method = :test
31
+
32
+ Chatterbox::Publishers.register do |notice|
33
+ Chatterbox::Services::Email.deliver(notice)
34
+ end
35
+ Chatterbox::Services::Email.configure({
36
+ :to => "to@example.com", :from => "from@example.com", :summary_prefix => "[CUKE] "
37
+ })
38
+ Chatterbox.notify :message => { :summary => "subject goes here!", :body => "body" }
39
+ puts ActionMailer::Base.deliveries.last.encoded
40
+ """
41
+ When I run "default_configuration_email_send.rb"
42
+ Then the exit code should be 0
43
+ And the stdout should match "To: to@example.com"
44
+ And the stdout should match "From: from@example.com"
45
+ And the stdout should match "Subject: [CUKE] subject goes here!"
46
+
@@ -0,0 +1,30 @@
1
+ Feature: Configurable Services
2
+
3
+ As a Chatterbox user
4
+ I want to be able to configure only the services I need
5
+ So that I can avoid unnecessary dependecies
6
+
7
+ Scenario: No services loaded
8
+ Given a file named "no_services_loaded.rb" with:
9
+ """
10
+ require "chatterbox"
11
+ require "spec"
12
+ require "spec/expectations"
13
+ Chatterbox::Services.constants.should == []
14
+ """
15
+ When I run "no_services_loaded.rb"
16
+ Then the exit code should be 0
17
+
18
+ Scenario: Email service only
19
+ Given a file named "email_service_only.rb" with:
20
+ """
21
+ require "chatterbox"
22
+ require "chatterbox/services/email"
23
+ require "spec"
24
+ require "spec/expectations"
25
+ Chatterbox::Services.constants.size.should == 1
26
+ Chatterbox::Services.constants.first.to_sym.should == :Email
27
+ """
28
+ When I run "email_service_only.rb"
29
+ Then the exit code should be 0
30
+
@@ -0,0 +1,31 @@
1
+ Given %r{^a file named "([^"]+)" with:$} do |file_name, code|
2
+ create_file(file_name, code)
3
+ end
4
+
5
+ When /^I run "([^\"]*)"$/ do |command|
6
+ ruby command
7
+ end
8
+
9
+ Then /^the exit code should be (\d+)$/ do |exit_code|
10
+ if last_exit_code != exit_code.to_i
11
+ raise "Did not exit with #{exit_code}, but with #{last_exit_code}. Standard error:\n#{last_stderr}"
12
+ end
13
+ end
14
+
15
+ Then /^the (.*) should match (.*)$/ do |stream, string_or_regex|
16
+ written = case(stream)
17
+ when 'stdout' then last_stdout
18
+ when 'stderr' then last_stderr
19
+ else raise "Unknown stream: #{stream}"
20
+ end
21
+ written.should smart_match(string_or_regex)
22
+ end
23
+
24
+ Then /^the (.*) should not match (.*)$/ do |stream, string_or_regex|
25
+ written = case(stream)
26
+ when 'stdout' then last_stdout
27
+ when 'stderr' then last_stderr
28
+ else raise "Unknown stream: #{stream}"
29
+ end
30
+ written.should_not smart_match(string_or_regex)
31
+ end
@@ -0,0 +1,71 @@
1
+ require 'tempfile'
2
+ require 'pathname'
3
+ require 'spec'
4
+ require 'spec/expectations'
5
+
6
+ class ChatterboxWorld
7
+
8
+ def root
9
+ @root ||= Pathname(__FILE__).join(*%w[.. .. ..]).expand_path
10
+ end
11
+
12
+ def working_dir
13
+ @working_dir ||= root.join(*%w[tmp cuke_generated]).expand_path
14
+ @working_dir.mkpath
15
+ @working_dir
16
+ end
17
+
18
+ def chatterbox_lib
19
+ root.join("lib")
20
+ end
21
+
22
+ def create_file(file_name, contents)
23
+ file_path = File.join(working_dir, file_name)
24
+ File.open(file_path, "w") { |f| f << contents }
25
+ end
26
+
27
+ def stderr_file
28
+ return @stderr_file if @stderr_file
29
+ @stderr_file = Tempfile.new('chatterbox_stderr')
30
+ @stderr_file.close
31
+ @stderr_file
32
+ end
33
+
34
+ def last_stderr
35
+ @stderr
36
+ end
37
+
38
+ def last_stdout
39
+ @stdout
40
+ end
41
+
42
+ def last_exit_code
43
+ @exit_code
44
+ end
45
+
46
+ def ruby(args)
47
+ Dir.chdir(working_dir) do
48
+ cmd = %[ruby -I#{chatterbox_lib} -rrubygems #{args} 2> #{stderr_file.path}]
49
+ @stdout = `#{cmd}`
50
+ end
51
+ @stderr = IO.read(stderr_file.path)
52
+ @exit_code = $?.to_i
53
+ end
54
+ end
55
+
56
+ World do
57
+ ChatterboxWorld.new
58
+ end
59
+
60
+ Spec::Matchers.define :smart_match do |expected|
61
+ match do |actual|
62
+ case expected
63
+ when /^\/.*\/?$/
64
+ actual =~ eval(expected)
65
+ when /^".*"$/
66
+ actual.index(eval(expected))
67
+ else
68
+ false
69
+ end
70
+ end
71
+ end
@@ -1,21 +1,31 @@
1
1
  require 'active_support'
2
2
 
3
3
  module Chatterbox
4
- def handle_notice(message)
4
+ def notify(message)
5
5
  publish_notice(message)
6
6
  message
7
7
  end
8
8
 
9
- alias_method :notify, :handle_notice
10
-
11
9
  def publish_notice(message)
12
10
  Publishers.publishers.each { |p| p.call(message.with_indifferent_access) }
13
11
  end
14
12
 
13
+ def handle_notice(message)
14
+ warning = "Chatterbox#handle_notice is deprecated and will be removed from Chatterbox 1.0. Call Chatterbox#notify instead."
15
+ deprecate(warning, caller)
16
+ notify(message)
17
+ end
18
+
19
+ def deprecate(message, callstack)
20
+ ActiveSupport::Deprecation.warn(message, callstack)
21
+ end
22
+
23
+ # Retrieve (lazily loaded) logger; defaults to a nil Logger
15
24
  def logger
16
25
  @logger ||= Logger.new(nil)
17
26
  end
18
27
 
28
+ # Set default logger for Chatterbox to use; mostly for development and debugging purposes
19
29
  def logger=(logger)
20
30
  @logger = logger
21
31
  end
@@ -15,8 +15,8 @@ module Chatterbox::ExceptionNotification
15
15
  end
16
16
 
17
17
  def extract_default_info(hash)
18
- { :summary => "N/A",
19
- :environment => ENV.to_hash
18
+ hsh = { :summary => "N/A",
19
+ :environment => ENV.to_hash
20
20
  }.merge(hash)
21
21
  end
22
22
 
@@ -1,4 +1,5 @@
1
1
  require 'yaml'
2
+ require 'pp'
2
3
 
3
4
  module Chatterbox::ExceptionNotification
4
5
  class Presenter
@@ -18,66 +19,65 @@ module Chatterbox::ExceptionNotification
18
19
  end
19
20
 
20
21
  def to_message
21
- { :message => { :summary => summary, :body => body },
22
+ { :summary => summary,
23
+ :body => render_body,
22
24
  :config => @config }
23
25
  end
24
26
 
25
- def section_order
27
+ def error_details
26
28
  [:error_message, :request, :backtrace, :environment, :ruby_info, :rails_info]
27
29
  end
28
30
 
29
- def body
30
- body = ""
31
- section_order.each do |section|
32
- output = render_section(section)
33
- body << output if output
34
- end
35
- options.keys.each do |other_sections|
36
- output = render_section(other_sections)
37
- body << output if output
38
- end
31
+ def render_body
32
+ processed_keys = []
33
+ extra_sections = options.keys - error_details
34
+ body = render_sections(extra_sections, processed_keys)
35
+ body << render_sections(error_details, processed_keys)
39
36
  body
40
37
  end
41
38
 
42
- def render_section(key)
39
+ def render_sections(keys, already_processed)
40
+ keys.inject(String.new) do |str, key|
41
+ output = render_section(key, already_processed)
42
+ str << output if output
43
+ str
44
+ end
45
+ end
46
+
47
+ def render_section(key, processed_keys = [])
48
+ processed_keys << key
43
49
  return nil unless options.key?(key)
44
- output = key.to_s.titleize
45
- output << "\n"
50
+ output = "#{key.to_s.titleize}\n"
46
51
  output << "----------\n"
47
- output << "#{inspect_value(options.delete(key))}\n\n"
52
+ output << render_obj(options[key])
53
+ output << "\n"
48
54
  output
49
55
  end
50
56
 
51
- # Taken from exception_notification - thanks Jamis.
52
- def inspect_value(value)
53
- object_to_yaml(value).strip
54
- end
55
-
56
- def object_to_yaml(object)
57
- result = ""
58
- result << render_obj(object)
59
- result
60
- end
61
-
62
57
  def render_obj(object)
63
- if object.is_a?(Hash)
64
- render_hash(object)
65
- else
66
- render_non_hash(object)
58
+ case object
59
+ when Hash then render_hash(object)
60
+ when Array then render_array(object)
61
+ else render_object(object)
67
62
  end
68
63
  end
69
-
70
- def render_non_hash(object)
71
- object.to_yaml.sub(/^---\s*\n?/, "")
64
+
65
+ def render_array(object)
66
+ render_object(object.join("\n"))
67
+ end
68
+
69
+ def render_object(object)
70
+ "#{object}\n"
72
71
  end
73
72
 
74
- # renders hashes with keys in sorted order
73
+ # renders hashes with keys in alpha-sorted order
75
74
  def render_hash(hsh)
76
75
  str = ""
77
76
  indiff_hsh = hsh.with_indifferent_access
78
77
  indiff_hsh.keys.sort.each do |key|
78
+ str << "#{key}: "
79
79
  value = indiff_hsh[key]
80
- str << "#{key}: #{render_obj(value)}"
80
+ PP::pp(value, str)
81
81
  end
82
82
  str
83
83
  end
@@ -19,6 +19,5 @@ module Chatterbox
19
19
  Chatterbox.logger.debug { "Chatterbox handing exception '#{exception}' off to normal rescue handling" }
20
20
  rescue_action_in_public_without_chatterbox(exception)
21
21
  end
22
-
23
22
  end
24
23
  end
@@ -0,0 +1,8 @@
1
+ require "chatterbox"
2
+ require "chatterbox/services"
3
+ require "tinder"
4
+
5
+ module Chatterbox::Services
6
+ class Campfire
7
+ end
8
+ end
@@ -21,6 +21,7 @@ module Chatterbox::Services
21
21
  def initialize(options = {})
22
22
  @options = options
23
23
  merge_configs
24
+ merge_message
24
25
  validate_options
25
26
  end
26
27
 
@@ -30,37 +31,36 @@ module Chatterbox::Services
30
31
 
31
32
  private
32
33
 
34
+ def merge_message
35
+ if @options[:message]
36
+ @options.merge!(@options[:message])
37
+ end
38
+ end
39
+
33
40
  def merge_configs
34
41
  @options[:config] ||= {}
35
42
  @options[:config] = self.class.default_configuration.merge(options[:config])
36
43
  end
37
44
 
38
45
  def validate_options
39
- require_message
40
- require_message_keys(:summary)
46
+ require_summary
41
47
 
42
48
  require_config
43
49
  require_config_keys(:to, :from)
44
50
  end
45
51
 
46
- def require_message
47
- raise(ArgumentError, "Must configure with a :message - you provided #{options.inspect}") unless options.key?(:message)
48
- end
49
-
50
52
  def require_config
51
53
  raise(ArgumentError, "Must configure with a :config or set default_configuration") unless options.key?(:config)
52
54
  end
53
55
 
54
56
  def require_config_keys(*keys)
55
57
  Array(keys).each do |key|
56
- raise(ArgumentError, "Must provide #{key.inspect} in the :config\nYou provided #{options.inspect}") unless options[:config].key?(key)
58
+ raise(ArgumentError, "Must provide #{key.inspect} in the :config - you provided:\n#{options.inspect}") unless options[:config].key?(key)
57
59
  end
58
60
  end
59
61
 
60
- def require_message_keys(*keys)
61
- Array(keys).each do |key|
62
- raise(ArgumentError, "Must provide #{key.inspect} in the :message") unless options[:message].key?(key)
63
- end
62
+ def require_summary
63
+ raise(ArgumentError, "Must provide a :summary for your message - you provided:\n#{options.inspect}") unless options.key?(:summary)
64
64
  end
65
65
  end
66
66
  end
@@ -19,10 +19,10 @@ module Chatterbox::Services
19
19
  bcc data[:config][:bcc] if data[:config][:bcc]
20
20
  cc data[:config][:cc] if data[:config][:cc]
21
21
 
22
- subject data[:message][:summary]
23
- body data[:message][:body] if data[:message][:body]
22
+ subject [data[:config][:summary_prefix], data[:summary]].join
23
+ body data[:body] if data[:body]
24
24
  end
25
-
25
+
26
26
  end
27
27
  end
28
28
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chatterbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Sanheim
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-17 00:00:00 -05:00
12
+ date: 2009-11-26 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -63,7 +63,6 @@ extra_rdoc_files:
63
63
  - README.markdown
64
64
  files:
65
65
  - .gitignore
66
- - .treasure_map.rb
67
66
  - CHANGELOG.markdown
68
67
  - LICENSE
69
68
  - README.markdown
@@ -77,10 +76,16 @@ files:
77
76
  - examples/lib/chatterbox/exception_notification_example.rb
78
77
  - examples/lib/chatterbox/rails_catcher_controller_example.rb
79
78
  - examples/lib/chatterbox/rails_catcher_example.rb
79
+ - examples/lib/chatterbox/services/campfire_example.rb
80
80
  - examples/lib/chatterbox/services/email/mailer_example.rb
81
81
  - examples/lib/chatterbox/services/email_example.rb
82
82
  - examples/lib/chatterbox/services_example.rb
83
83
  - examples/lib/chatterbox_example.rb
84
+ - features/api.feature
85
+ - features/email_service.feature
86
+ - features/services.feature
87
+ - features/step_definitions/email_service_steps.rb
88
+ - features/support/env.rb
84
89
  - init.rb
85
90
  - lib/chatterbox.rb
86
91
  - lib/chatterbox/exception_notification.rb
@@ -89,10 +94,12 @@ files:
89
94
  - lib/chatterbox/exception_notification/rails_extracter.rb
90
95
  - lib/chatterbox/rails_catcher.rb
91
96
  - lib/chatterbox/services.rb
97
+ - lib/chatterbox/services/campfire.rb
92
98
  - lib/chatterbox/services/email.rb
93
99
  - lib/chatterbox/services/email/mailer.rb
94
100
  - lib/chatterbox/services/email/views/chatterbox/services/email/mailer/message.erb
95
101
  - rails/init.rb
102
+ - tmp/.gitignore
96
103
  - todo.markdown
97
104
  - views/chatterbox/mailer/exception_notification.erb
98
105
  has_rdoc: true
@@ -131,6 +138,7 @@ test_files:
131
138
  - examples/lib/chatterbox/exception_notification_example.rb
132
139
  - examples/lib/chatterbox/rails_catcher_controller_example.rb
133
140
  - examples/lib/chatterbox/rails_catcher_example.rb
141
+ - examples/lib/chatterbox/services/campfire_example.rb
134
142
  - examples/lib/chatterbox/services/email/mailer_example.rb
135
143
  - examples/lib/chatterbox/services/email_example.rb
136
144
  - examples/lib/chatterbox/services_example.rb
@@ -1,22 +0,0 @@
1
- map_for(:chatterbox) do |map|
2
-
3
- map.keep_a_watchful_eye_for 'lib', 'examples', 'rails'
4
-
5
- # map.add_mapping %r%examples/(.*)_example\.rb% do |match|
6
- # ["examples/#{match[1]}_example.rb"]
7
- # end
8
- #
9
- # map.add_mapping %r%examples/example_helper\.rb% do |match|
10
- # Dir["examples/**/*_example.rb"]
11
- # end
12
- #
13
- # map.add_mapping %r%lib/(.*)\.rb% do |match|
14
- # Dir["examples/#{match[1]}_example.rb"]
15
- # end
16
-
17
- map.add_mapping %r%rails/(.*)\.rb% do |match|
18
- ["examples/#{match[1]}_example.rb"]
19
- end
20
-
21
-
22
- end