towncrier 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +15 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +281 -0
  4. data/Rakefile +32 -0
  5. data/app/assets/javascripts/towncrier.js +12 -0
  6. data/lib/generators/templates/create_towncries_table.rb +15 -0
  7. data/lib/generators/templates/towncrier.yml +15 -0
  8. data/lib/generators/templates/towncry.rb +10 -0
  9. data/lib/generators/towncrier_generator.rb +19 -0
  10. data/lib/towncrier/active_record_extensions.rb +19 -0
  11. data/lib/towncrier/base.rb +45 -0
  12. data/lib/towncrier/config.rb +44 -0
  13. data/lib/towncrier/cry.rb +65 -0
  14. data/lib/towncrier/eagerloader.rb +17 -0
  15. data/lib/towncrier/engine.rb +21 -0
  16. data/lib/towncrier/observer.rb +42 -0
  17. data/lib/towncrier/targets.rb +31 -0
  18. data/lib/towncrier/version.rb +10 -0
  19. data/lib/towncrier/workers/resque.rb +13 -0
  20. data/lib/towncrier/workers/sidekiq.rb +15 -0
  21. data/lib/towncrier.rb +5 -0
  22. data/test/dummy/README.rdoc +28 -0
  23. data/test/dummy/Rakefile +6 -0
  24. data/test/dummy/app/assets/javascripts/application.js +13 -0
  25. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  26. data/test/dummy/app/controllers/application_controller.rb +5 -0
  27. data/test/dummy/app/helpers/application_helper.rb +2 -0
  28. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  29. data/test/dummy/bin/bundle +3 -0
  30. data/test/dummy/bin/rails +4 -0
  31. data/test/dummy/bin/rake +4 -0
  32. data/test/dummy/config/application.rb +23 -0
  33. data/test/dummy/config/boot.rb +5 -0
  34. data/test/dummy/config/database.yml +25 -0
  35. data/test/dummy/config/environment.rb +5 -0
  36. data/test/dummy/config/environments/development.rb +29 -0
  37. data/test/dummy/config/environments/production.rb +80 -0
  38. data/test/dummy/config/environments/test.rb +36 -0
  39. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  40. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  41. data/test/dummy/config/initializers/inflections.rb +16 -0
  42. data/test/dummy/config/initializers/mime_types.rb +5 -0
  43. data/test/dummy/config/initializers/secret_token.rb +12 -0
  44. data/test/dummy/config/initializers/session_store.rb +3 -0
  45. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  46. data/test/dummy/config/locales/en.yml +23 -0
  47. data/test/dummy/config/routes.rb +56 -0
  48. data/test/dummy/config.ru +4 -0
  49. data/test/dummy/db/development.sqlite3 +0 -0
  50. data/test/dummy/db/test.sqlite3 +0 -0
  51. data/test/dummy/log/development.log +186 -0
  52. data/test/dummy/public/404.html +58 -0
  53. data/test/dummy/public/422.html +58 -0
  54. data/test/dummy/public/500.html +57 -0
  55. data/test/dummy/public/favicon.ico +0 -0
  56. data/test/test_helper.rb +15 -0
  57. data/test/towncrier_test.rb +7 -0
  58. metadata +178 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YTlhZGUxNzU0NDBiYjkxYTMzYjA5ZDAwNjdlMmFiMDU5YmFlMTYwYw==
5
+ data.tar.gz: !binary |-
6
+ NmUzY2E2MDJlOTFmOTRkOGU1ZTVlN2JjYjJkMGNiYWQ3MzZjMTJiMg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZGUwNDU0MmQ0MWI5Y2Q4NTE1OWFjOTViMTZmMDU5MWI1YzU3NGNmZDlhNjVi
10
+ NzEyZmM1ZDU5MWU2MDkzN2MzY2ZlYTdhNDQ2M2M2ZjBhMjkwNDc2NDQxNzlk
11
+ YmJhNDJmMGM5YzY4YzQ4MWRjMDA3M2VhMDdhOTk0MzAwMjg2ZjI=
12
+ data.tar.gz: !binary |-
13
+ NzQ2NzljOWViMTk1YTUzZmJkOWRlOTk0YWU4MWI2OTRlNjJjNmRhZjU2YzQw
14
+ NDBkMTk0OGNhOWNmNTU2ZTA5MmQ2ZjlkMmUzZWZlMzQwNjU1NGE0MWUwY2M1
15
+ ZjFiNGM3ZTEzMWYyMTM5Y2JlNzRhZGVmYjlmMGU0ZjU5NTFiNTY=
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,281 @@
1
+ [![Code Climate](https://codeclimate.com/github/davidlesches/towncrier.png)](https://codeclimate.com/github/davidlesches/towncrier)
2
+
3
+ # Towncrier
4
+
5
+ Consider Facebook. When a friend posts an update a growl notifications pops up on your screen. When someone send you a message your "messages" icon becomes highlighted. A friend uploads a new profile photo and that triggers your notifications counter being incremented.
6
+
7
+ Towncrier provides a handy DSL for replicating that kind of UX experience.
8
+
9
+ Towncrier is a Ruby on Rails gem that allows you to speedily create Javascript notifications for your app. It watches your models, and as records are created or updated it pushes a Javascript data payload to the users you specify. In your assets/javascripts files you can intercept these payloads and react to them, using the data within them to tweak the page in any way you desire.
10
+
11
+ ## Cheat Sheet
12
+
13
+ If you've used Towncrier before, here is a refresher. If you haven't, please read the more detailed instructions below.
14
+
15
+ Step 1: In the app/criers directory, add a new crier, using the same name as the model you are observing.
16
+
17
+ ```ruby
18
+ class AnswerCrier < Towncrier::Base
19
+
20
+ on :create do
21
+ target answer.question.author
22
+ payload answer
23
+ end
24
+
25
+ end
26
+ ```
27
+
28
+ Step 2: In the assets/javascripts directory, add a listener for the cry and use the payload as you wish.
29
+
30
+ ```javascript
31
+ towncrier.hearAnswer = function(action, payload) {
32
+ console.log(payload);
33
+ }
34
+ ```
35
+
36
+ ## Opinion
37
+
38
+ Like many gems, Towncrier is opinionated software. Towncrier believes that Javascript notifications are UX-sugar only and are not a central part of any app. As a result, Towncrier intentionally has a DSL that forces you to define the notifications outside of your ActiveRecord models, so that your ActiveRecord models do not become cluttered with notification code.
39
+
40
+ Similarly, when deployed to production, Towncrier will swallow any errors or glitches caused by these notifications so that a syntax error within a notification doesn't trip up your application or trigger a database transaction rollback.
41
+
42
+ In short, the idea is to provide a great DSL for these Javascript notifications while keeping them firmly out of the way of the important code.
43
+
44
+ ## App Setup
45
+
46
+ Towncrier relies on Ryan Bates' excellent [Private Pub gem](https://github.com/ryanb/private_pub) to handle the Javascript pub/sub system under the hood. Towncrier also requires a background process queue. You can use [Sidekiq](https://github.com/mperham/sidekiq) or [Resque](https://github.com/resque/resque), though Sidekiq is recommended.
47
+
48
+ **Step 1:** Install Private Pub and Sidekiq (or Resque). Both Private Pub and Sidekiq (or Resque) are somewhat involved to set up. Please see their homepages respectively and ensure they are set up and operating correctly before proceeding with installing Towncrier. (By default, Sidekiq/Resque is used in production mode only. See [Configuration](#configuration) for more.)
49
+
50
+ **Step 2:** Add Towncrier to your gemfile.
51
+
52
+ ```
53
+ gem 'towncrier'
54
+ ```
55
+
56
+ **Step 3:** Run the generator.
57
+
58
+ ```
59
+ rails generate towncrier
60
+ rake db:migrate
61
+ ```
62
+
63
+ **Step 4:** Add the JavaScript file to your application.js file manifest.
64
+
65
+ ```
66
+ //= require towncrier
67
+ ```
68
+
69
+ Remember to start up the Private Pub and Sidekiq (or Resque) processes as explained in their respective documentation.
70
+
71
+ ## Setting Up the Targets
72
+
73
+ You need to define which model in your app represents the users ("targets") who will be receiving the notifications. In 95% of apps, this will be a User model.
74
+
75
+ ```ruby
76
+ class User < ActiveRecord::Base
77
+ acts_as_towncrier_targets
78
+ end
79
+ ```
80
+
81
+ Next, add a string column named "towncrier_token" to that model.
82
+
83
+ ```
84
+ rails generate migration add_towncrier_token_to_users towncrier_token
85
+ rake db:migrate
86
+ ```
87
+
88
+ From this point on, each user's towncrier_token will be populated on create. If you already have users in your app, simply re-save them to populate their tokens.
89
+
90
+ ```
91
+ User.find_each(&:save)
92
+ ```
93
+
94
+ Finally, in your application **layout**, add a Javascript listener *before the closing /body tag*. This listens for the notifications coming in for the target.
95
+
96
+ ```
97
+ <%= subscribe_to(current_user.towncrier_channel) if current_user %>
98
+ ```
99
+
100
+ ## Usage
101
+
102
+ Now it's time to go notification crazy :)
103
+
104
+ For the purposes of this demo, we'll use a fictional StackOverflow app, where users post questions and answers.
105
+
106
+ Let's say that every time a question is answered a notification should be pushed to the author of the original question. Create a new file called 'answer_crier.rb' in the 'app/criers' directory.
107
+
108
+ ```ruby
109
+ class AnswerCrier < Towncrier::Base
110
+
111
+ on :create do
112
+ target answer.question.author
113
+ payload answer # => auto-converted to answer.to_json
114
+ end
115
+
116
+ end
117
+ ```
118
+
119
+ Notice the pattern. Naming conventions are everything. Because we are creating notifications when users submit answers, we create a crier class named AnswerCrier. This class inherits from Towncrier::Base, and because it is named AnswerCrier, Towncrier will watch for Answer resources being created or updated and will send out the appropriate notifications.
120
+
121
+ Notifications can be sent on creates, updates, or both, and you can send multiple notifications each time an answer is submitted.
122
+
123
+ ```ruby
124
+ class AnswerCrier < Towncrier::Base
125
+
126
+ on :create do
127
+ target answer.question.author
128
+ payload :foo => :bar
129
+ end
130
+
131
+ on :update do
132
+ target answer.question.author
133
+ payload :abc => :xyz
134
+ end
135
+
136
+ on :create, :update do
137
+ target answer.author.followers
138
+ payload :foo => :bar
139
+ end
140
+
141
+ end
142
+ ```
143
+
144
+ For every notification you must define two things, the target and the payload. The target (see section [Setting Up the Targets above](#setting-up-the-targets)) can be one user, or multiple.
145
+
146
+ ```ruby
147
+ class AnswerCrier < Towncrier::Base
148
+
149
+ # one target
150
+ on :create do
151
+ target answer.question.author
152
+ payload :foo => :bar
153
+ end
154
+
155
+ # lots and lots of targets
156
+ on :create do
157
+ target (answer.question.author + answer.followers + answer.author.followers)
158
+ payload :foo => :bar
159
+ end
160
+
161
+ end
162
+ ```
163
+
164
+ The payload can be anything that `.to_json` can be called on.
165
+
166
+ ```ruby
167
+ class AnswerCrier < Towncrier::Base
168
+
169
+ # a hash payload
170
+ on :create do
171
+ target answer.question.author
172
+ payload :foo => :bar
173
+ end
174
+
175
+ # a resource payload
176
+ on :create do
177
+ target answer.question.author
178
+ payload answer # => will be converted to answer.to_json
179
+ end
180
+
181
+ # a complex hash payload
182
+ on :create do
183
+ target answer.question.author
184
+ payload({
185
+ :answer => answer,
186
+ :answer_count => answer.author.answers.count,
187
+ :complex_stuff => {
188
+ :foo => :bar,
189
+ :bar => :foo
190
+ }
191
+ })
192
+ end
193
+
194
+ end
195
+ ```
196
+
197
+ Notice that in all the examples above, we were able to call 'answer' within the target (eg answer.author) and within the payload. Towncrier does some magic behind the scenes to enable this. Because this crier is the AnswerCrier, Towncrier sets up an 'answer' method that returns the newly created/updated answer resource, allowing you to call 'answer' within the target and payload declarations.
198
+
199
+ Now all this is for sending out the notifications. On the Javascript side, you listen for these notifications by following the same naming convention.
200
+
201
+ ```javascript
202
+ towncrier.hearAnswer = function(action, payload) {
203
+ // action will be a string, either 'create' or 'update'
204
+ // payload will be the payload in JSON format
205
+ // for example:
206
+ console.log("A new answer was just " + action + "d.")
207
+ console.log(payload);
208
+ }
209
+ ```
210
+
211
+ ## Advanced Usage
212
+
213
+ #### Custom Naming
214
+
215
+ For each notification, you can use the `as` option to give the notification a more specific name. This is imperative when you have multiple notifications for a single resource.
216
+
217
+ ```ruby
218
+ class AnswerCrier < Towncrier::Base
219
+
220
+ on :create, :update, as: :answer_for_question_author do
221
+ target answer.question.author
222
+ payload :foo => :bar
223
+ end
224
+
225
+ on :create, :update, as: :answer_for_followers do
226
+ target answer.author.followers
227
+ payload :foo => :bar
228
+ end
229
+
230
+ end
231
+ ```
232
+
233
+ ```javascript
234
+ towncrier.hearAnswerForQuestionAuthor = function(action, payload) {
235
+ // do something
236
+ }
237
+
238
+ towncrier.hearAnswerForFollowers = function(action, payload) {
239
+ // do something
240
+ }
241
+ ```
242
+
243
+ #### Persistence
244
+
245
+ By default, every time a notification is sent a copy of it is stored in the Towncry ActiveRecord table. This allows you to reference those notifications later. An obvious use case for this is to populate a Past Notification Feed. If you do not want to save copies to the database, set the `record` option to false.
246
+
247
+ ```ruby
248
+ class AnswerCrier < Towncrier::Base
249
+
250
+ on :create, :update, record: false do
251
+ target answer.question.author
252
+ payload :foo => :bar
253
+ end
254
+
255
+ end
256
+ ```
257
+
258
+ ## Configuration
259
+
260
+ A configuration file located at 'config/towncrier.yml' can be edited to tweak the settings.
261
+
262
+ - **enabled:** whether or not Towncrier runs at all
263
+ - **raise_errors:** whether to throw or swallow errors that occur when Towncrier is queueing a notification to the background process
264
+ - **background_worker:** the type of background process to use. Valid values are `:sidekiq` and `:resque`. This can also be set to `false` to run everything in the main process. In development mode, `false` is the default, as not using a background process allows you to rely on Rails autoloading to pick up changes you are making in your codebase as you make them. In production mode however, leaving this setting as `false` is a catastrophically terrible idea.
265
+
266
+ ## TODO
267
+
268
+ Add test suite.
269
+
270
+ ## License and Copyright
271
+
272
+ Copyright (C) 2014 David Lesches
273
+ [@davidlesches](http://twitter.com/davidlesches)
274
+
275
+ 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.
276
+
277
+ 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.
278
+
279
+
280
+
281
+
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Towncrier'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ require 'rake/testtask'
23
+
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << 'lib'
26
+ t.libs << 'test'
27
+ t.pattern = 'test/**/*_test.rb'
28
+ t.verbose = false
29
+ end
30
+
31
+
32
+ task default: :test
@@ -0,0 +1,12 @@
1
+ (function() {
2
+ window.towncrier = {};
3
+
4
+ towncrier.hear = function(name, action, payload) {
5
+ var json;
6
+ json = $.parseJSON(payload);
7
+ if (("hear" + name) in towncrier) {
8
+ return towncrier["hear" + name](action, json);
9
+ }
10
+ };
11
+
12
+ }).call(this);
@@ -0,0 +1,15 @@
1
+ class CreateTowncriesTable < ActiveRecord::Migration
2
+ def change
3
+ create_table :towncries do |t|
4
+ t.string :name
5
+ t.integer :target_id
6
+ t.string :target_type
7
+ t.integer :crier_id
8
+ t.string :crier_type
9
+ t.string :action
10
+ t.string :payload
11
+
12
+ t.timestamps
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ development:
2
+ enabled: true
3
+ raise_errors: true
4
+ background_worker: false
5
+
6
+ test:
7
+ enabled: false
8
+ raise_errors: false
9
+ background_worker: false
10
+
11
+ production:
12
+ enabled: true
13
+ raise_errors: false
14
+ background_worker: :sidekiq
15
+
@@ -0,0 +1,10 @@
1
+ class Towncry < ActiveRecord::Base
2
+
3
+ # Associations
4
+ belongs_to :target, polymorphic: true
5
+ belongs_to :crier, polymorphic: true
6
+
7
+ # Serializers
8
+ serialize :payload, JSON
9
+
10
+ end
@@ -0,0 +1,19 @@
1
+ class TowncrierGenerator < Rails::Generators::Base
2
+ include Rails::Generators::Migration
3
+
4
+ def self.next_migration_number(dirname)
5
+ next_migration_number = current_migration_number(dirname) + 1
6
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
7
+ end
8
+
9
+ desc "This generator sets up the Towncrier gem"
10
+ source_root File.expand_path("../templates", __FILE__)
11
+
12
+ def copy_files
13
+ copy_file "towncrier.yml", "config/towncrier.yml"
14
+ copy_file "towncry.rb", "app/models/towncry.rb"
15
+ empty_directory "app/criers"
16
+ migration_template "create_towncries_table.rb", "db/migrate/create_towncries_table.rb"
17
+ end
18
+
19
+ end
@@ -0,0 +1,19 @@
1
+ class ActiveRecord::Base
2
+
3
+ after_create :broadcast_create_towncry
4
+ after_update :broadcase_update_towncry
5
+
6
+ def broadcast_create_towncry
7
+ broadcast_towncry(:create)
8
+ end
9
+
10
+ def broadcase_update_towncry
11
+ broadcast_towncry(:update)
12
+ end
13
+
14
+ def broadcast_towncry action
15
+ info = { :class => self.class.name, :id => self.id, :action => action }
16
+ ActiveSupport::Notifications.instrument('towncry', info)
17
+ end
18
+
19
+ end
@@ -0,0 +1,45 @@
1
+ module Towncrier
2
+ class Base
3
+
4
+ CRIERS = []
5
+ RESERVED_NAMES = %w( Towncry TownCry )
6
+
7
+ def self.inherited klass
8
+ model_name = klass.name.gsub('Crier', '')
9
+ if RESERVED_NAMES.include?(model_name)
10
+ warn "#{klass} is a reserved name and cannot be used to define a Crier."
11
+ else
12
+ CRIERS << model_name
13
+ model_name.constantize.class_eval 'has_many :towncries, as: :crier, dependent: :destroy'
14
+ end
15
+ end
16
+
17
+ def self.emitters
18
+ @emitters
19
+ end
20
+
21
+ def self.on *args, &block
22
+ options = args.extract_options!
23
+ @emitters ||= []
24
+ @emitters << { on: args, options: options, block: block }
25
+ end
26
+
27
+ def self.create_cries(id, action)
28
+ emitters.each do |emitter|
29
+ next unless emitter[:on].include?(action.to_sym)
30
+ object = model_class.constantize.find(id)
31
+ cry = Towncrier::Cry.new(action: action, name: model_class, options: emitter[:options], _object: object)
32
+ cry.define_singleton_method(model_class.underscore) do
33
+ object
34
+ end
35
+ cry.instance_eval(&emitter[:block])
36
+ cry.cry
37
+ end
38
+ end
39
+
40
+ def self.model_class
41
+ self.name.gsub('Crier', '')
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,44 @@
1
+ module Towncrier
2
+ class Config < ::Rails::Engine
3
+
4
+ class_attribute :enabled, :raise_errors, :background_worker
5
+ self.enabled = true
6
+ self.raise_errors = false
7
+ self.background_worker = :sidekiq
8
+
9
+ def self.load_config
10
+ YAML.load_file(config_file_path)[Rails.env].each do |key, value|
11
+ if valid_config_values[ key.to_sym ] && valid_config_values[ key.to_sym ].include?(value)
12
+ self.send("#{key}=", value)
13
+ else
14
+ warn "Towncrier WARNING: The key/value specified in towncrier.yml for '#{key}' is an invalid option and is being ignored."
15
+ end
16
+ end
17
+ end
18
+
19
+ def self.config_file_path
20
+ File.join(Rails.root, '/config/towncrier.yml')
21
+ end
22
+
23
+ def self.config_file_exists?
24
+ File.exist?(config_file_path) &&
25
+ YAML.load_file(config_file_path) &&
26
+ !YAML.load_file(config_file_path)[Rails.env].nil?
27
+ end
28
+
29
+ def self.valid_config_values
30
+ {
31
+ :enabled => [ true, false ],
32
+ :raise_errors => [ true, false ],
33
+ :background_worker => [ false, :sidekiq, :resque ]
34
+ }
35
+ end
36
+
37
+ if config_file_exists?
38
+ load_config
39
+ else
40
+ warn 'Towncrier WARNING: towncrier.yml config file could not be found, or is missing values. Please see wiki to generate config file.'
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,65 @@
1
+ module Towncrier
2
+ class Cry
3
+
4
+ attr_accessor :action, :name, :options, :_object
5
+
6
+ def initialize args
7
+ args.each do |key, value|
8
+ send("#{key}=", value)
9
+ end
10
+ end
11
+
12
+ def target target
13
+ @target = Array(target).flatten.compact.uniq
14
+ end
15
+
16
+ def payload payload
17
+ @payload = payload
18
+ end
19
+
20
+ def record?
21
+ !options[:record].nil? ? options[:record] : true
22
+ end
23
+
24
+ def official_name
25
+ (options[:as] || name).to_s.classify
26
+ end
27
+
28
+ def validate
29
+ if @target.nil?
30
+ raise NotImplementedError, "You forgot to define 'target' in your #{name} crier."
31
+ end
32
+
33
+ if @payload.nil?
34
+ raise NotImplementedError, "You forgot to define 'payload' in the #{name} crier."
35
+ end
36
+ end
37
+
38
+ def cry
39
+ validate
40
+ @target.each do |t|
41
+ push_notification(t)
42
+ save_notification(t)
43
+ end
44
+ end
45
+
46
+ def push_notification target
47
+ PrivatePub.publish_to(target.towncrier_channel, "towncrier.hear('#{official_name}', '#{action}', '#{@payload.to_json}')")
48
+ end
49
+
50
+ def save_notification target
51
+ Towncry.create!(
52
+ :name => official_name,
53
+ :target => target,
54
+ :crier => _object,
55
+ :action => action,
56
+ :payload => @payload
57
+ ) if record?
58
+ end
59
+
60
+ def crier_class
61
+ "#{name}_crier".classify.constantize
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,17 @@
1
+ module Towncrier
2
+ class Eagerloader
3
+
4
+ def self.load_criers
5
+ if Towncrier::Base.descendants.empty? && criers_directory_exists?
6
+ Dir[Rails.root.join("app/criers/*.rb")].each do |file|
7
+ require_dependency file
8
+ end
9
+ end
10
+ end
11
+
12
+ def self.criers_directory_exists?
13
+ File.directory?(Rails.root.join("app/criers"))
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module Towncrier
2
+ class Engine < Rails::Engine
3
+
4
+ initializer "Include Towncrier gem only after Rails boot" do |app|
5
+ require 'towncrier/config'
6
+ require 'towncrier/base'
7
+ require 'towncrier/observer'
8
+ require 'towncrier/targets'
9
+ require 'towncrier/cry'
10
+ require 'towncrier/eagerloader'
11
+ require 'towncrier/active_record_extensions'
12
+ require 'towncrier/workers/resque'
13
+ require 'towncrier/workers/sidekiq'
14
+
15
+ ActionDispatch::Callbacks.to_prepare do
16
+ Towncrier::Eagerloader.load_criers
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,42 @@
1
+ module Towncrier
2
+ class Observer
3
+
4
+ attr_reader :payload
5
+
6
+ ActiveSupport::Notifications.subscribe('towncry') do |_, _, _, _, payload|
7
+ new(payload)
8
+ end
9
+
10
+ def initialize payload
11
+ @payload = payload
12
+ setup_cries if create_cries?
13
+ end
14
+
15
+ def create_cries?
16
+ Towncrier::Config.enabled && Towncrier::Base::CRIERS.include?(payload[:class])
17
+ end
18
+
19
+ def setup_cries
20
+ send("setup_#{async_type}_cries")
21
+ rescue
22
+ raise if Towncrier::Config.raise_errors
23
+ end
24
+
25
+ def async_type
26
+ Towncrier::Config.background_worker
27
+ end
28
+
29
+ def setup_sidekiq_cries
30
+ Towncrier::Workers::Sidekiq.perform_async(payload[:class], payload[:id], payload[:action])
31
+ end
32
+
33
+ def setup_resque_cries
34
+ Resque.enqueue(Towncrier::Workers::Resque, payload[:class], payload[:id], payload[:action])
35
+ end
36
+
37
+ def setup_false_cries
38
+ "#{payload[:class]}_crier".classify.constantize.create_cries(payload[:id], payload[:action])
39
+ end
40
+
41
+ end
42
+ end