newman 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +23 -0
- data/Gemfile +3 -0
- data/README.md +126 -7
- data/examples/config/dreamhost.rb.example +5 -1
- data/examples/config/gmail.rb.example +4 -0
- data/examples/live_test.rb +4 -7
- data/examples/simple_mailing_list.rb +2 -2
- data/lib/newman.rb +97 -1
- data/lib/newman/application.rb +238 -1
- data/lib/newman/controller.rb +93 -8
- data/lib/newman/email_logger.rb +54 -0
- data/lib/newman/filters.rb +123 -0
- data/lib/newman/mailer.rb +98 -32
- data/lib/newman/mailing_list.rb +83 -0
- data/lib/newman/recorder.rb +132 -0
- data/lib/newman/request_logger.rb +41 -0
- data/lib/newman/response_logger.rb +41 -0
- data/lib/newman/server.rb +197 -23
- data/lib/newman/settings.rb +72 -4
- data/lib/newman/store.rb +68 -4
- data/lib/newman/test_mailer.rb +79 -6
- data/lib/newman/version.rb +5 -2
- data/test/helper.rb +18 -0
- data/{examples → test/integration}/acid_tests.rb +30 -20
- data/test/integration/skip_response_test.rb +22 -0
- data/test/integration/subject_filter_test.rb +25 -0
- data/test/settings.rb +4 -0
- data/test/suite.rb +6 -0
- metadata +74 -9
- data/lib/newman/commands.rb +0 -72
- data/lib/newman/store/recorder.rb +0 -63
data/CHANGELOG.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
### 0.2.0 (2012-02-08)
|
2
|
+
|
3
|
+
- Internals mostly rewritten, changes too numerous to outline meaningfully. We'll keep better track of these changes in the future.
|
4
|
+
|
5
|
+
- Basic logging support added.
|
6
|
+
|
7
|
+
- Lots of documentation added.
|
8
|
+
|
9
|
+
- Server no longer crashes upon application errors unless
|
10
|
+
service.raise_exceptions is set to true.
|
11
|
+
|
12
|
+
- Added Newman::Controller#skip_response which allows disabling delivery of a response
|
13
|
+
email upon demand.
|
14
|
+
|
15
|
+
- Make Newman::MailingList fail gracefully by checking subscriber status before
|
16
|
+
attempting subscribe / unsubscribe.
|
17
|
+
|
18
|
+
- Add a generic callback method to Newman::Application which allows for arbitrary
|
19
|
+
callbacks to be run based on filters against the Mail::Message object.
|
20
|
+
|
21
|
+
### 0.1.1 (2012-02-03)
|
22
|
+
|
23
|
+
- First official release.
|
data/Gemfile
ADDED
data/README.md
CHANGED
@@ -1,10 +1,129 @@
|
|
1
|
-
|
1
|
+
![Newman](http://i.imgur.com/92bZB.jpg)
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
Newman is a microframework which aims to do for email-based
|
4
|
+
applications what Rack and Sinatra have done for web programming. **While our
|
5
|
+
goals may be ambitious, this project is
|
6
|
+
currently in a very early experimental stage, and is in no way safe for use in
|
7
|
+
production.**
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
That said, Newman is already capable of doing a number of interesting things. In
|
10
|
+
particular:
|
11
|
+
|
12
|
+
* A simple polling server provides a basic interface for
|
13
|
+
reading email from a single inbox and then building up a response email.
|
14
|
+
|
15
|
+
* Filters are provided for handling emails based on their TO and SUBJECT fields.
|
16
|
+
|
17
|
+
* Filters can also be defined for handling messages based on arbitrary
|
18
|
+
conditions evaluated against a `Mail::Messsage` object.
|
19
|
+
|
20
|
+
* A rudimentary PStore backed storage mechanism is provided for persistence.
|
21
|
+
|
22
|
+
* Basic support for maintaining persistent lists of email addresses is
|
23
|
+
provided.
|
24
|
+
|
25
|
+
* Basic support for email templates are provided via Tilt.
|
26
|
+
|
27
|
+
* A barebones configuration system allows configuring email settings, service
|
28
|
+
settings, and application specific settings.
|
29
|
+
|
30
|
+
We are still working on figuring out what belongs in Newman and what doesn't,
|
31
|
+
and so all of these features are subject to change or disappear entirely. But if
|
32
|
+
you have a need for this sort of tool, it's worth noting that this software
|
33
|
+
isn't entirely vaporware, and that we could use your help!
|
34
|
+
|
35
|
+
### Scary Warning:
|
36
|
+
|
37
|
+
DON'T HOOK UP NEWMAN TO ANY EMAIL INBOX THAT YOU CAN'T COMPLETELY WIPE OUT EVERY TIME IT RUNS. NEWMAN **WILL** DELETE YOUR EMAILS!!!
|
38
|
+
|
39
|
+
### For a demonstration of how Newman is used:
|
40
|
+
|
41
|
+
Check out [Jester](http://github.com/mendicant-university/jester) as well as some of the
|
10
42
|
simple examples in this repository.
|
43
|
+
|
44
|
+
### For a walkthrough of Newman's codebase:
|
45
|
+
|
46
|
+
Check out [Newman's Rocco-based API documentation](http://mendicant-university.github.com/newman/lib/newman.html).
|
47
|
+
|
48
|
+
### For general discussion, questions, and ideas about Newman:
|
49
|
+
|
50
|
+
Find seacreature or ericgj in the #newman channel on Freenode or send an email to newman@librelist.org
|
51
|
+
|
52
|
+
### Contributing to Newman:
|
53
|
+
|
54
|
+
We do not yet have a clear roadmap or contributor guidelines, so be sure to talk
|
55
|
+
to us before working on bug fixes or patches. But assuming you do want to send
|
56
|
+
us some code, here is what you need to know:
|
57
|
+
|
58
|
+
* You get to keep the copyright to your code, but you must agree to license it
|
59
|
+
under the MIT license.
|
60
|
+
|
61
|
+
* Your code should come with tests. Integration tests are fine, but unit tests
|
62
|
+
would be nice where appropriate. Right now Newman is under tested and we don't
|
63
|
+
want to make that problem worse. You can of course submit a pull request for
|
64
|
+
feedback BEFORE writing tests.
|
65
|
+
|
66
|
+
* Your code should be fully documented, and properly formatted for use with
|
67
|
+
Rocco. Please try to emulate the style and conventions we've been using where
|
68
|
+
possible. Do the best you can with this, and we'll help tighten up wording and
|
69
|
+
clean up formatting as needed. You can of course submit a pull request for
|
70
|
+
feedback BEFORE writing documentation.
|
71
|
+
|
72
|
+
* Newman is taking a use-case oriented approach to design. Be prepared to
|
73
|
+
justify any proposed change with real or realistic scenarios, rather than
|
74
|
+
simply addressing theoretical concerns.
|
75
|
+
|
76
|
+
### Versioning Policy:
|
77
|
+
|
78
|
+
We will try to follow the guidelines below when cutting new releases,
|
79
|
+
to the extent that it makes sense to do so.
|
80
|
+
|
81
|
+
1) Clearly mark each object that Newman provides as being part of either
|
82
|
+
the 'external API' or the 'internal API'. The external API is for
|
83
|
+
application developers, the internal API is for Newman itself as well as
|
84
|
+
extension developers
|
85
|
+
|
86
|
+
2) Before 1.0, allow backwards incompatible internal API changes during
|
87
|
+
every release, and allow backwards incompatible external API changes
|
88
|
+
during minor version bumps, but do not add or change external behavior
|
89
|
+
in tiny version bumps.
|
90
|
+
|
91
|
+
3) After 1.0, do not allow external or internal API changes during tiny
|
92
|
+
version bumps (these will be bug fixes only). Allow changes to the
|
93
|
+
internal API during minor version bumps, but maintain backwards
|
94
|
+
compatibility with the 1.0 release (i.e. things introduced after 1.0 can
|
95
|
+
be changed / removed, but things which shipped with 1.0 should stay
|
96
|
+
supported, even internally). Allow external API changes or
|
97
|
+
backwards-incompatible changes to the internals only on major version
|
98
|
+
bumps (i.e. 2.0, 3.0, etc). Use semantic versioning and declare the
|
99
|
+
external API to be the 'public' API.
|
100
|
+
|
101
|
+
We plan to get to 1.0 quickly to reach a stabilizing point for application
|
102
|
+
developers and a slower moving target for extension developers. This means
|
103
|
+
that our 1.0 release will be more of a minimum-viable product and not
|
104
|
+
necessarily a full-stack framework.
|
105
|
+
|
106
|
+
### Authorship:
|
107
|
+
|
108
|
+
Newman is being developed by [Gregory Brown](http://community.mendicantuniversity.org/people/sandal)
|
109
|
+
and [Eric Gjertsen](http://community.mendicantuniversity.org/people/ericgj), along with
|
110
|
+
help from several other folks.
|
111
|
+
|
112
|
+
It is based on an assignment from [Mendicant
|
113
|
+
University](http://mendicantuniversity.org)'s January 2011 core
|
114
|
+
skills course, and was also used as a sample application for the [Practicing Ruby](http://practicingruby.com)
|
115
|
+
journal. The original inspiration for this project came from some code
|
116
|
+
written by [Brent Vatne](http://community.mendicantuniversity.org/people/brentvatne),
|
117
|
+
as well as from the general ideas behind Rack and Sinatra.
|
118
|
+
|
119
|
+
[View the full list of contributors](https://github.com/mendicant-university/newman/contributors) to see who else has helped out.
|
120
|
+
|
121
|
+
### License:
|
122
|
+
|
123
|
+
Copyright (c) 2012 Gregory Brown, Eric Gjertsen, et al.
|
124
|
+
|
125
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
126
|
+
|
127
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
128
|
+
|
129
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -1,9 +1,13 @@
|
|
1
|
+
# WARNING: DON'T HOOK UP NEWMAN TO ANY EMAIL INBOX THAT
|
2
|
+
# YOU CAN'T COMPLETELY WIPE OUT EVERY TIME IT RUNS.
|
3
|
+
# NEWMAN **WILL** DELETE YOUR EMAILS!!!
|
4
|
+
|
1
5
|
imap.address = "mail.<<DOMAIN>>"
|
2
6
|
imap.user = "<<USERNAME>>@<<DOMAIN>>"
|
3
7
|
imap.password = "<<PASSWORD>>"
|
4
8
|
|
5
9
|
smtp.address = "mail.<<DOMAIN>>"
|
6
|
-
smtp.user = "<<
|
10
|
+
smtp.user = "<<USERNAME>>@<<DOMAIN>>"
|
7
11
|
smtp.password = "<<PASSWORD>>"
|
8
12
|
|
9
13
|
service.domain = "<<DOMAIN>>"
|
data/examples/live_test.rb
CHANGED
@@ -2,13 +2,10 @@ require_relative "ping_pong"
|
|
2
2
|
|
3
3
|
|
4
4
|
settings = Newman::Settings.from_file("config/environment.rb")
|
5
|
+
mailer = Newman::Mailer.new(settings)
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
server = Newman::Server
|
10
|
-
server.settings = settings
|
11
|
-
server.mailer = mailer
|
7
|
+
server = Newman::Server.new(settings, mailer)
|
8
|
+
server.apps << Newman::Examples::PingPong
|
12
9
|
|
13
10
|
mailer.deliver_message(:to => settings.application.ping_email,
|
14
11
|
:from => settings.service.default_sender)
|
@@ -20,7 +17,7 @@ settings.application.live_test_delay.downto(1) do |i|
|
|
20
17
|
end
|
21
18
|
puts
|
22
19
|
|
23
|
-
|
20
|
+
server.tick
|
24
21
|
|
25
22
|
remaining_attempts = 3
|
26
23
|
loop do
|
@@ -12,7 +12,7 @@ module Newman
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
|
15
|
+
subject(:match, "subscribe") do
|
16
16
|
if list.subscriber?(sender)
|
17
17
|
respond :subject => "ERROR: Already subscribed",
|
18
18
|
:body => template("subscribe-error")
|
@@ -24,7 +24,7 @@ module Newman
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
subject(:match, "unsubscribe") do
|
28
28
|
if list.subscriber?(sender)
|
29
29
|
list.unsubscribe(sender)
|
30
30
|
|
data/lib/newman.rb
CHANGED
@@ -1,3 +1,95 @@
|
|
1
|
+
# [Newman](https://github.com/mendicant-university/newman) is a
|
2
|
+
# microframework which aims to do for email-based applications what Rack and
|
3
|
+
# Sinatra have done for web programming. **While our goals may be ambitious,
|
4
|
+
# this project is currently in a very early experimental stage,
|
5
|
+
# and is in no way safe for use in production.**
|
6
|
+
#
|
7
|
+
# That said, we still welcome contributors to help us work on this project
|
8
|
+
# as we collectively figure out what exactly Newman should be. Even in its
|
9
|
+
# very early stages, Newman is already doing some useful things and provides
|
10
|
+
# a wide range of application development features via its external interface
|
11
|
+
# as well as extension points via its internal interface.
|
12
|
+
#
|
13
|
+
# The documentation you're currently reading is meant to help explain Newman's
|
14
|
+
# implementation to contributors and alpha testers. Before you dig deeper into
|
15
|
+
# the source, make sure to read
|
16
|
+
# [Newman's README](https://github.com/mendicant-university/newman)
|
17
|
+
# as well as the [Jester](https://github.com/mendicant-university/jester) demo
|
18
|
+
# application.
|
19
|
+
#
|
20
|
+
# Assuming you have done those things and are now familiar with the basic ideas
|
21
|
+
# behind Newman, the following outline may help you in your explorations of
|
22
|
+
# its source code.
|
23
|
+
#
|
24
|
+
# ### External interface (for application developers)
|
25
|
+
#
|
26
|
+
# * [Newman::Server](http://mendicant-university.github.com/newman/lib/newman/server.html)
|
27
|
+
# takes incoming mesages from a mailer object and passes them to applications as a
|
28
|
+
# request, and then delivers a response email that built up by its
|
29
|
+
# applications.
|
30
|
+
#
|
31
|
+
# * [Newman::Application](http://mendicant-university.github.com/newman/lib/newman/application.html)
|
32
|
+
# provides the main entry point for Newman application developers, and exists to tie together
|
33
|
+
# various Newman objects in a convenient way.
|
34
|
+
#
|
35
|
+
# * [Newman::Filters](http://mendicant-university.github.com/newman/lib/newman/filters.html)
|
36
|
+
# provides high level filters for matching incoming requests.
|
37
|
+
#
|
38
|
+
# * [Newman::Controller](http://mendicant-university.github.com/newman/lib/newman/controller.html)
|
39
|
+
# provides a context for application callbacks to run in, and provides most of
|
40
|
+
# the core functionality for preparing a response email.
|
41
|
+
#
|
42
|
+
# * [Newman::MailingList](http://mendicant-university.github.com/newman/lib/newman/mailing_list.html)
|
43
|
+
# implements a simple mechanism for storing persistent lists of email addresses keyed
|
44
|
+
# by a mailing list name.
|
45
|
+
#
|
46
|
+
# * [Newman::Store](http://mendicant-university.github.com/newman/lib/newman/store.html) provides
|
47
|
+
# a minimal persistence layer for storing non-relational data.
|
48
|
+
#
|
49
|
+
# * [Newman::Recorder](http://mendicant-university.github.com/newman/lib/newman/recorder.html)
|
50
|
+
# provides a mechanism for storing records with autoincrementing identifiers
|
51
|
+
# within a `Newman::Store` and supports some rudimentary CRUD functionality.
|
52
|
+
#
|
53
|
+
# * Implicitly, the settings files used by Newman as well as the structure of
|
54
|
+
# its low level data storage format are also considered part of external API,
|
55
|
+
# even though these are actually implementation details. This is simply
|
56
|
+
# because we want to make sure to clearly reflect backwards incompatible
|
57
|
+
# changes to these features via our versioning policy, as this sort of
|
58
|
+
# change could potentially cause update problems for application
|
59
|
+
# developers.
|
60
|
+
#
|
61
|
+
# ### Internal interface (for extension developers)
|
62
|
+
#
|
63
|
+
# * [Newman::EmailLogger](http://mendicant-university.github.com/newman/lib/newman/email_logger.html)
|
64
|
+
# provides rudimentary logging support for email objects, and primarily exists to
|
65
|
+
# support the `Newman::RequestLogger` and `Newman::ResponseLogger` objects.
|
66
|
+
#
|
67
|
+
# * [Newman::RequestLogger](http://mendicant-university.github.com/newman/lib/newman/request_logger.html)
|
68
|
+
# provides a mechanism for logging information about incoming emails.
|
69
|
+
#
|
70
|
+
# * [Newman::ResponseLogger](http://mendicant-university.github.com/newman/lib/newman/response_logger.html)
|
71
|
+
# provides a mechanism for logging information about outgoing emails.
|
72
|
+
#
|
73
|
+
# * [Newman::Mailer](http://mendicant-university.github.com/newman/lib/newman/mailer.html) provides a thin
|
74
|
+
# wrapper on top of the [mail gem](http://github.com/mikel/mail) which is
|
75
|
+
# designed to have a minimal API so that it can easily be swapped out with
|
76
|
+
# another mailer object.
|
77
|
+
#
|
78
|
+
# * [Newman::TestMailer](http://mendicant-university.github.com/newman/lib/newman/test_mailer.html)
|
79
|
+
# is a drop-in replacement for `Newman::Mailer` meant for use in automated testing.
|
80
|
+
#
|
81
|
+
# * [Newman::Settings](http://mendicant-university.github.com/newman/lib/newman/settings.html) provides
|
82
|
+
# the base functionality that is used by Newman's configuration files. Note
|
83
|
+
# that while this object is part of the internals, the settings actually used
|
84
|
+
# by Newman should be considered part of the external API.
|
85
|
+
#
|
86
|
+
# ### Getting help, or helping out:
|
87
|
+
#
|
88
|
+
# Please catch up with seacreature or ericgj in the #newman channel on Freenode,
|
89
|
+
# or send an email to newman@librelist.org. We'd love to hear any questions,
|
90
|
+
# ideas, or suggestions you'd like to share with us.
|
91
|
+
|
92
|
+
require "logger"
|
1
93
|
require "pstore"
|
2
94
|
require "ostruct"
|
3
95
|
require "fileutils"
|
@@ -5,13 +97,17 @@ require "fileutils"
|
|
5
97
|
require "mail"
|
6
98
|
require "tilt"
|
7
99
|
|
100
|
+
require_relative "newman/email_logger"
|
101
|
+
require_relative "newman/request_logger"
|
102
|
+
require_relative "newman/response_logger"
|
8
103
|
require_relative "newman/server"
|
9
|
-
require_relative "newman/
|
104
|
+
require_relative "newman/filters"
|
10
105
|
require_relative "newman/application"
|
11
106
|
require_relative "newman/controller"
|
12
107
|
require_relative "newman/mailing_list"
|
13
108
|
require_relative "newman/settings"
|
14
109
|
require_relative "newman/store"
|
110
|
+
require_relative "newman/recorder"
|
15
111
|
require_relative "newman/mailer"
|
16
112
|
require_relative "newman/test_mailer"
|
17
113
|
require_relative "newman/version"
|
data/lib/newman/application.rb
CHANGED
@@ -1,6 +1,33 @@
|
|
1
|
+
# `Newman::Application` provides the main entry point for Newman application
|
2
|
+
# developers, and exists to tie together various Newman objects in a convenient
|
3
|
+
# way.
|
4
|
+
#
|
5
|
+
# For an fairly complete example of a `Newman::Application` object in use, be
|
6
|
+
# sure to check out [Jester](https://github.com/mendicant-university/jester).
|
7
|
+
#
|
8
|
+
# `Newman::Application` is part of Newman's **external interface**.
|
9
|
+
|
1
10
|
module Newman
|
2
11
|
class Application
|
3
|
-
|
12
|
+
|
13
|
+
include Filters
|
14
|
+
# ---
|
15
|
+
|
16
|
+
# A `Newman::Application` object is a blank slate upon creation, with fields
|
17
|
+
# set to hold `callbacks`, `matchers`, and `extensions`. A block may
|
18
|
+
# optionally be provided, which then gets executed within the context of the
|
19
|
+
# newly created `Newman::Application` instance. This is the common way of
|
20
|
+
# building applications, and is demonstrated by the example below:
|
21
|
+
#
|
22
|
+
# ping_pong = Newman::Application.new do
|
23
|
+
# subject("ping") do
|
24
|
+
# respond(:subject => "pong")
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# Any method that can be called on a `Newman::Application` instance can be
|
29
|
+
# called within the provided block, including those methods mixed in by
|
30
|
+
# `Newman::Filters`.
|
4
31
|
|
5
32
|
def initialize(&block)
|
6
33
|
self.callbacks = []
|
@@ -9,6 +36,18 @@ module Newman
|
|
9
36
|
|
10
37
|
instance_eval(&block) if block_given?
|
11
38
|
end
|
39
|
+
|
40
|
+
# ---
|
41
|
+
|
42
|
+
# `Newman::Application#call` accepts a hash of parameters which gets used to
|
43
|
+
# create a new `Newman::Controller` object. The controller is then extended
|
44
|
+
# by all of the modules stored in the `extensions` field on the application
|
45
|
+
# object, and is finally passed along to
|
46
|
+
# `Newman::Application#trigger_callbacks`, which does the
|
47
|
+
# magical work of figuring out which callbacks to run, if any.
|
48
|
+
#
|
49
|
+
# This method is meant to be run by a `Newman::Server` object, and isn't
|
50
|
+
# especially useful on its own.
|
12
51
|
|
13
52
|
def call(params)
|
14
53
|
controller = Controller.new(params)
|
@@ -16,8 +55,206 @@ module Newman
|
|
16
55
|
trigger_callbacks(controller)
|
17
56
|
end
|
18
57
|
|
58
|
+
# ---
|
59
|
+
|
60
|
+
# `Newman::Application#default` is used to define a default callback
|
61
|
+
# which will run when no other callbacks match the incoming request.
|
62
|
+
# For example, you can define a callback such as the one below:
|
63
|
+
#
|
64
|
+
# default do
|
65
|
+
# respond(:subject => "REQUEST NOT UNDERSTOOD")
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# Unless you are building an application that will never fail to
|
69
|
+
# match at least one of its filters, you MUST set up a default callback
|
70
|
+
# if you want to avoid a possible application error. We know this is
|
71
|
+
# not exactly the most desireable behavior, and will try to fix this
|
72
|
+
# in a future version of Newman.
|
73
|
+
|
74
|
+
def default(&callback)
|
75
|
+
self.default_callback = callback
|
76
|
+
end
|
77
|
+
|
78
|
+
# ---
|
79
|
+
|
80
|
+
# `Newman:::Application#use` is used to register the extension
|
81
|
+
# modules that get mixed in to the controller
|
82
|
+
# objects created by `Newman::Application#call`. This allows
|
83
|
+
# an application object to provide extensions for use within its
|
84
|
+
# callbacks, as in the example shown below.
|
85
|
+
#
|
86
|
+
# module ListLoader
|
87
|
+
# def load_list(name)
|
88
|
+
# store = Newman::Store.new(settings.application.list_db)
|
89
|
+
# Newman::MailingList.new(name, store)
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# list_app = Newman::Application.new do
|
94
|
+
# use ListLoader
|
95
|
+
#
|
96
|
+
# match :list_id, "[^.]+"
|
97
|
+
#
|
98
|
+
# to(:tag, "{list_id}.subscribe") do
|
99
|
+
# list = load_list(params[:list_id])
|
100
|
+
#
|
101
|
+
# if list.subscriber?(sender)
|
102
|
+
# # send a failure message
|
103
|
+
# else
|
104
|
+
# # susbcribe the user and send a welcome message
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# This method is mainly meant to be used with pre-packaged extensions or
|
110
|
+
# more complicated forms of callback helpers. This example was just shown
|
111
|
+
# for the sake of its simplicity, but for similar use cases it would
|
112
|
+
# actually be better to use `Newman::Application#helpers`
|
113
|
+
|
114
|
+
def use(extension)
|
115
|
+
extensions << extension
|
116
|
+
end
|
117
|
+
|
118
|
+
# ---
|
119
|
+
|
120
|
+
# `Newman::Application#helpers` is used to build simple controller
|
121
|
+
# extensions, and is mostly just syntactic sugar. For example,
|
122
|
+
# rather than using an explicit
|
123
|
+
# module, the example shown in the `Newman::Application#use` documentation
|
124
|
+
# can be rewritten as follows:
|
125
|
+
#
|
126
|
+
# list_app = Newman::Application.new do
|
127
|
+
# helpers do
|
128
|
+
# def load_list(name)
|
129
|
+
# store = Newman::Store.new(settings.application.list_db)
|
130
|
+
# Newman::MailingList.new(name, store)
|
131
|
+
# end
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# match :list_id, "[^.]+"
|
135
|
+
#
|
136
|
+
# to(:tag, "{list_id}.subscribe") do
|
137
|
+
# list = load_list(params[:list_id])
|
138
|
+
#
|
139
|
+
# if list.subscriber?(sender)
|
140
|
+
# # send a failure message
|
141
|
+
# else
|
142
|
+
# # susbcribe the user and send a welcome message
|
143
|
+
# end
|
144
|
+
# end
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
# It's important to note that for any controller extensions that might be
|
148
|
+
# reusable, or for more complicated logic, `Newman::Application#use` is
|
149
|
+
# probably a better tool to use.
|
150
|
+
|
151
|
+
def helpers(&block)
|
152
|
+
use Module.new(&block)
|
153
|
+
end
|
154
|
+
|
155
|
+
# ---
|
156
|
+
|
157
|
+
# `Newman::Application#match` is used to define patterns which are used for
|
158
|
+
# extracting callback parameters. An example is shown below:
|
159
|
+
#
|
160
|
+
# jester = Newman::Application.new do
|
161
|
+
# match :genre, '\S+'
|
162
|
+
# match :title, '.*'
|
163
|
+
#
|
164
|
+
# subject(:match, "a {genre} story '{title}'") do
|
165
|
+
# story_library.add_story(:genre => params[:genre],
|
166
|
+
# :title => params[:title],
|
167
|
+
# :body => request.body.to_s)
|
168
|
+
#
|
169
|
+
# respond :subject => "Jester saved '#{params[:title]}'"
|
170
|
+
# end
|
171
|
+
# end
|
172
|
+
#
|
173
|
+
# Because Newman's built in filters are designed to escape regular
|
174
|
+
# expression syntax by default, `Newman::Application#match` provides the
|
175
|
+
# only high-level mechanism for dynamic filter matches. Low level matching
|
176
|
+
# is possible via `Newman::Application#callback`, but would
|
177
|
+
# be an exercise in tedium for most application developers.
|
178
|
+
|
179
|
+
def match(name, pattern)
|
180
|
+
matchers[name] = pattern
|
181
|
+
end
|
182
|
+
|
183
|
+
# ---
|
184
|
+
|
185
|
+
# `Newman::Application#callback` is a low level feature for defining custom
|
186
|
+
# callbacks. For ideas on how to roll your own filters with it, see the
|
187
|
+
# implementation of the `Newman::Filters` module.
|
188
|
+
|
189
|
+
def callback(action, filter)
|
190
|
+
callbacks << { :filter => filter,
|
191
|
+
:action => action }
|
192
|
+
end
|
193
|
+
|
194
|
+
# ---
|
195
|
+
|
196
|
+
# `Newman::Application#compile_regex` is used for converting pattern strings
|
197
|
+
# into a regular expression string suitable for use in filters. This method is a
|
198
|
+
# low-level feature, and is not meant for use by application developers. See
|
199
|
+
# the `Newman::Filters` module for how to use it to build your own callback
|
200
|
+
# filters.
|
201
|
+
|
202
|
+
def compile_regex(pattern)
|
203
|
+
Regexp.escape(pattern)
|
204
|
+
.gsub(/\\{(.*?)\\}/) { |m| "(?<#{$1}>#{matchers[$1]})" }
|
205
|
+
end
|
206
|
+
|
207
|
+
# ---
|
208
|
+
|
209
|
+
# **NOTE: Methods below this point in the file are implementation
|
210
|
+
# details, and should not be depended upon.**
|
211
|
+
|
19
212
|
private
|
20
213
|
|
214
|
+
# ---
|
215
|
+
|
216
|
+
# `Newman::Application#trigger_callbacks` runs a two step process:
|
217
|
+
#
|
218
|
+
# 1) It grabs the `filter` Proc for each callback and executes it, passing in
|
219
|
+
# the provided `controller`. Any `filter` proc that returns a logically true
|
220
|
+
# value is selected to be run.
|
221
|
+
#
|
222
|
+
# 2) If the selection of `matched_callbacks` is empty, it executes the default
|
223
|
+
# callback in the context of a controller object. Otherwise, it runs each
|
224
|
+
# callback in sequence, in the context of a controller object.
|
225
|
+
#
|
226
|
+
# This method may have some weird side effects because it relies on
|
227
|
+
# awkward state mutations that could be either done in a better way or
|
228
|
+
# replaced with a mostly stateless approach. We will look at fixing
|
229
|
+
# this in a future Newman release.
|
230
|
+
|
231
|
+
def trigger_callbacks(controller)
|
232
|
+
matched_callbacks = callbacks.select do |e|
|
233
|
+
filter = e[:filter]
|
234
|
+
e[:match_data] = filter.call(controller)
|
235
|
+
end
|
236
|
+
|
237
|
+
if matched_callbacks.empty?
|
238
|
+
controller.instance_exec(&default_callback)
|
239
|
+
else
|
240
|
+
matched_callbacks.each do |e|
|
241
|
+
action = e[:action]
|
242
|
+
controller.params = e[:match_data] || {}
|
243
|
+
controller.instance_exec(&action)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# ---
|
249
|
+
|
250
|
+
# These accessors have been made private to reflect the fact that
|
251
|
+
# `Newman::Application` is meant to be customized via its high level methods
|
252
|
+
# such as `use`, `helpers`, `match`, and `callback`. Any extensions of
|
253
|
+
# `Newman::Application` should rely on those methods and not directly
|
254
|
+
# reference these fields at all. If there is some functionality missing
|
255
|
+
# that needs to be added to the public API for you to build your
|
256
|
+
# extension, just let us know.
|
257
|
+
|
21
258
|
attr_accessor :callbacks, :default_callback, :matchers, :extensions
|
22
259
|
end
|
23
260
|
end
|