webhookr 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. data/.gitignore +14 -0
  2. data/Gemfile +18 -0
  3. data/Guardfile +31 -0
  4. data/MIT-LICENSE +22 -0
  5. data/README.md +159 -0
  6. data/Rakefile +46 -0
  7. data/app/controllers/webhookr/events_controller.rb +34 -0
  8. data/config/routes.rb +4 -0
  9. data/lib/generators/webhookr/add_route_generator.rb +12 -0
  10. data/lib/generators/webhookr/init_generator.rb +30 -0
  11. data/lib/tasks/webhookr_tasks.rake +16 -0
  12. data/lib/webhookr/adapter_response.rb +3 -0
  13. data/lib/webhookr/engine.rb +16 -0
  14. data/lib/webhookr/invalid_payload_error.rb +3 -0
  15. data/lib/webhookr/invalid_security_token_error.rb +3 -0
  16. data/lib/webhookr/ostruct_utils.rb +28 -0
  17. data/lib/webhookr/service.rb +51 -0
  18. data/lib/webhookr/services/adapter/base.rb +20 -0
  19. data/lib/webhookr/services/adapter.rb +7 -0
  20. data/lib/webhookr/services.rb +7 -0
  21. data/lib/webhookr/version.rb +3 -0
  22. data/lib/webhookr.rb +24 -0
  23. data/script/rails +8 -0
  24. data/test/dummy/README.rdoc +261 -0
  25. data/test/dummy/Rakefile +7 -0
  26. data/test/dummy/app/assets/javascripts/application.js +15 -0
  27. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  28. data/test/dummy/app/controllers/application_controller.rb +3 -0
  29. data/test/dummy/app/helpers/application_helper.rb +2 -0
  30. data/test/dummy/app/mailers/.gitkeep +0 -0
  31. data/test/dummy/app/models/.gitkeep +0 -0
  32. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  33. data/test/dummy/config/application.rb +65 -0
  34. data/test/dummy/config/boot.rb +10 -0
  35. data/test/dummy/config/environment.rb +5 -0
  36. data/test/dummy/config/environments/development.rb +31 -0
  37. data/test/dummy/config/environments/production.rb +64 -0
  38. data/test/dummy/config/environments/test.rb +35 -0
  39. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  40. data/test/dummy/config/initializers/inflections.rb +15 -0
  41. data/test/dummy/config/initializers/mime_types.rb +5 -0
  42. data/test/dummy/config/initializers/secret_token.rb +7 -0
  43. data/test/dummy/config/initializers/session_store.rb +8 -0
  44. data/test/dummy/config/initializers/webhookr.rb +2 -0
  45. data/test/dummy/config/initializers/wrap_parameters.rb +10 -0
  46. data/test/dummy/config/locales/en.yml +5 -0
  47. data/test/dummy/config/routes.rb +3 -0
  48. data/test/dummy/config.ru +4 -0
  49. data/test/dummy/lib/assets/.gitkeep +0 -0
  50. data/test/dummy/log/.gitkeep +0 -0
  51. data/test/dummy/public/404.html +26 -0
  52. data/test/dummy/public/422.html +26 -0
  53. data/test/dummy/public/500.html +25 -0
  54. data/test/dummy/public/favicon.ico +0 -0
  55. data/test/dummy/script/rails +6 -0
  56. data/test/functional/webhookr/events_controller_test.rb +76 -0
  57. data/test/functional/webhookr/events_routes_test.rb +33 -0
  58. data/test/functional/webhookr/service_test.rb +91 -0
  59. data/test/integration/webhookr/add_route_generator_test.rb +15 -0
  60. data/test/integration/webhookr/init_generator_test.rb +27 -0
  61. data/test/stubs/service_under_test_stubs.rb +73 -0
  62. data/test/test_helper.rb +18 -0
  63. data/test/unit/webhookr/Services/ServiceUnderTest/adapter_test.rb +39 -0
  64. data/test/unit/webhookr/adapter_response_test.rb +20 -0
  65. data/test/unit/webhookr/ostruct_utils_test.rb +32 -0
  66. data/test/webhookr_test.rb +11 -0
  67. data/webhookr.gemspec +22 -0
  68. metadata +171 -0
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ README.html
2
+ *.orig
3
+ .*.swp
4
+ .*.swo
5
+ *.tmp
6
+ *.patch
7
+ *.kpf
8
+ *~
9
+ .DS_Store
10
+ Thumbs.db
11
+ Gemfile.lock
12
+ Gemfile.local
13
+ Guardfile.local
14
+ *.log
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "rake", "~> 10.0"
6
+ gem "minitest"
7
+ gem "minitest-reporters"
8
+ gem "em-websocket"
9
+ gem "guard"
10
+ gem "guard-minitest"
11
+ gem "guard-markdown"
12
+ gem "guard-livereload"
13
+ gem "simplecov", :require => false
14
+
15
+ if File.exists?('Gemfile.local')
16
+ instance_eval File.read('Gemfile.local')
17
+ end
18
+
data/Guardfile ADDED
@@ -0,0 +1,31 @@
1
+
2
+ guard 'minitest', :test_folders => 'test', :test_file_patterns => '*_test.rb' do
3
+ watch(%r|^test/(.+)_test\.rb|)
4
+ watch(%r|^test/stubs/(.+)\.rb$|) { "test" }
5
+
6
+ # Rails
7
+ watch(%r{^app/models/(.+)\.rb$}) { |m|
8
+ "test/unit/#{m[1]}_test.rb"
9
+ }
10
+
11
+ watch(%r{^app/controllers/(.+)\.rb$}) { |m|
12
+ "test/functional/#{m[1]}_test.rb"
13
+ }
14
+
15
+ watch('config/routes.rb') {
16
+ ["test/functional", "test/integration"]
17
+ }
18
+ end
19
+
20
+ guard 'livereload' do
21
+ watch('README.md')
22
+ end
23
+
24
+ guard 'markdown', :convert_on_start => true do
25
+ watch ('README.md') { "./README.md|./README.html" }
26
+ end
27
+
28
+ if File.exists?('Guardfile.local')
29
+ instance_eval File.read('Guardfile.local')
30
+ end
31
+
data/MIT-LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 2167961 Ontario Inc., Zoocasa <code@zoocasa.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # webhookr: Rails Webhooks Made Easy
2
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/zoocasa/webhookr)
3
+
4
+ ## <a name="introduction"></a>Introduction
5
+
6
+ ### What is webhookr?
7
+
8
+ webhookr is a Rails Engine that easily and securely adds third-party
9
+ webhooks to your Rails app. It supports third-party sites with modular
10
+ plugins.
11
+
12
+ ### What are 'webhooks'?
13
+
14
+ From 30,000 feet, webhooks allow disparate cloud services to coordinate and
15
+ asynchronously notify each other of events of interest. For example, a shopping app might
16
+ want to process a [customer credit card transaction](https://stripe.com/docs/webhooks),
17
+ and be notified of success or failure.
18
+
19
+ From 10,000 feet, webhooks allow external third parties the ability to
20
+ execute code within your application, using a well defined message exchange
21
+ protocol.
22
+
23
+ From 1,000 feet and below, webhooks are application routes and controller
24
+ endpoints, third-party API discovery, security decisions, and code that
25
+ executes within your application.
26
+
27
+
28
+ ### Why use webhookr?
29
+
30
+ Webhooks are http callbacks from another service that are useful for
31
+ pushing information to your Rails application. The question is: How do I
32
+ easily and securely add them to my Rails application?
33
+
34
+ webhookr provides a single, secure route for all your webhooks, and uses
35
+ the observer pattern to integrate the webhook with your business logic.
36
+ Using webhookr you can be up and running with a third-party webhook in less
37
+ than an hour.
38
+
39
+ ### How does webhookr work?
40
+
41
+ Webhookr provides a standard approach for third-party webhooks via plugins. The
42
+ author of a plugin implements code to standardize any webhook into an action and
43
+ data packet. Your task is to write glue code that is called whenever an action is
44
+ received, and to use the resulting data packet for your application logic. Here is
45
+ a subset of the MailChimp handler that might be written to deal with the 'unsubscribe'
46
+ action sent from MailChimp:
47
+
48
+ ```ruby
49
+ class MailChimpHooks
50
+ def on_unsubscribe(payload)
51
+ User.unsubscribe_newletter(payload.data.email)
52
+ end
53
+ end
54
+ ```
55
+
56
+ ## <a name="usage"></a>Usage & Setup
57
+
58
+ webhookr works with Rails 3.1 onwards. It generally requires a plugin to be
59
+ useful, such as the [MailChimp plugin](https://github.com/zoocasa/webhookr-mailchimp).
60
+
61
+ ## Setup
62
+
63
+ Add webhookr to your Gemfile with:
64
+
65
+ ```ruby
66
+ gem 'webhookr'
67
+ ```
68
+
69
+ Run the bundle command to install it and then run the generator to
70
+ add the engine route to your config/routes.rb:
71
+
72
+ ```console
73
+ rails g webhookr:add_route
74
+ ```
75
+
76
+ or, add the routing information manually to config/routes.rb
77
+
78
+ ```ruby
79
+ mount Webhookr::Engine => "/webhookr", :as => "webhookr"
80
+ ```
81
+
82
+ The initialization file for webhookr is optional. To see what
83
+ options are available, you can run the generator and review the
84
+ sample config file:
85
+
86
+ ```console
87
+ rails g webhookr:init *initializer_name*
88
+ ```
89
+
90
+ Once you have added third-party plugins, you can use the provided
91
+ rake task to output your applications urls for each service:
92
+
93
+ ```console
94
+ rake webhookr:services
95
+ ```
96
+
97
+ ## <a name="security"></a>Webhookr Security
98
+
99
+ ### General security issues with webhooks
100
+
101
+ A webhook is by design, a http post to your application that results in code execution.
102
+ Having a well defined approach to adding such functionality can help ensure you don't open security issues
103
+ within your application.
104
+
105
+ Many webhook providers do not provide built-in security for their
106
+ webhooks - they usually send the post over http, and they often have no authentication
107
+ or verification mechanism. If your webhook third-party service responds to hooks, it is
108
+ possible for an evil person to guess your webhook URL, and attempt to wreak havoc with your system.
109
+
110
+ ### How using webhookr can increase security
111
+ webhookr provides a unique url for each service, via a security token that is used in the webhook path, to help make it hard to guess your webhook url.
112
+ For example, if you were using MailChimp, your webhook url might look like: '/webhookr/mailchimp/cdd2a24cfac821' and if you were using Mandrill, your
113
+ webhook might look like: '/webhookr/mandrill/de69557a4d95e7'. This will help prevent someone guessing your
114
+ webhook service url. If you are using https, the URL will be encrypted, keeping your security token a secret.
115
+
116
+ You can also enable http basic auth, if the third party service supports it. Note that enabling http basic auth
117
+ affects all your webhooks, so be sure it is supported by all your third-party services.
118
+
119
+ If you are sending sensitive data via webhooks, it is recommended you use HTTPS.
120
+
121
+ ### <a name="supported_services"></a>Supported Services
122
+
123
+ * MailChimp - webhookr-mail_chimp
124
+ * Mandrill - webhookr-mandrill
125
+ * Github - coming soon
126
+ * Stripe - coming soon
127
+
128
+ ## <a name="works_with"></a>Works with:
129
+
130
+ webhookr works with Rails 3.1 and 3.2, and has been tested on the following Ruby
131
+ implementations:
132
+
133
+ * JRuby 1.7.1
134
+ * MRI 1.8.7
135
+ * MRI 1.9.2
136
+ * MRI 1.9.3
137
+ * Rubinius 1.2.4
138
+ * Ruby EE 1.8.7
139
+
140
+ Pending:
141
+
142
+ * MRI 2.0
143
+
144
+ ### TODO
145
+ * Implement get/post strategies and responses so the controller can return variable text to the service.
146
+ This allows support for advanced Webhook responses for services that require it.
147
+ * Enhance testing of Rake tasks
148
+ * Clean up the stubs with FactoryGirl
149
+ * Test with MRI 2.0
150
+
151
+ ### License
152
+
153
+ webhookr is released under the [MIT license](http://www.opensource.org/licenses/MIT).
154
+
155
+ ## Author
156
+
157
+ * [Gerry Power](https://github.com/gerrypower)
158
+
159
+ ## <a name="Version History"></a>Version History
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+
2
+ # -*- ruby -*-
3
+
4
+ require 'rubygems'
5
+ require 'rubygems/package_task'
6
+ require 'rake/testtask'
7
+ require 'rdoc/task'
8
+ require 'bundler/gem_tasks'
9
+
10
+ $:.push File.expand_path(File.dirname(__FILE__), 'lib')
11
+
12
+ version = Webhookr::VERSION
13
+
14
+ desc 'Test Webhookr'
15
+ Rake::TestTask.new(:test) do |t|
16
+ t.test_files = FileList['test/**/*_test.rb']
17
+ t.verbose = !!ENV['VERBOSE_TESTS']
18
+ t.warning = !!ENV['WARNINGS']
19
+ end
20
+
21
+ desc 'Build docs'
22
+ Rake::RDocTask.new do |t|
23
+ t.main = 'README.md'
24
+ t.title = "Webhookr #{version}"
25
+ t.rdoc_dir = 'doc'
26
+ t.rdoc_files.include('README.md', 'MIT-LICENSE', 'lib/**/*.rb')
27
+ end
28
+
29
+ namespace :webhookr do
30
+ namespace:test do
31
+ desc 'Install gems in all Rubies'
32
+ task :install do
33
+ sh %{rbenv each -v bundle install}
34
+ end
35
+
36
+ desc 'Test with all Rubies'
37
+ task :test_versions do
38
+ sh %{rbenv each -v bundle exec rake test}
39
+ end
40
+
41
+ desc 'Install and test all'
42
+ task :all => [:install, :test_versions]
43
+ end
44
+ end
45
+
46
+ task :default => :test
@@ -0,0 +1,34 @@
1
+ module Webhookr
2
+ class EventsController < ActionController::Base
3
+ http_basic_authenticate_with(
4
+ :name => Webhookr.config.basic_auth.username,
5
+ :password => Webhookr.config.basic_auth.password
6
+ ) if Webhookr.config.basic_auth.username && Webhookr.config.basic_auth.password
7
+
8
+ before_filter :create_service
9
+
10
+ def show
11
+ render :nothing => true
12
+ end
13
+
14
+ def create
15
+ @service.process!
16
+ render :nothing => true
17
+ end
18
+
19
+ private
20
+
21
+ def create_service
22
+ begin
23
+ @service = Webhookr::Service.new(
24
+ params[:service_id], :payload => request.body.read, :security_token => params[:security_token]
25
+ )
26
+ rescue NameError => e
27
+ raise ActionController::RoutingError.new("No service '#{params[:service_id]}' is available.")
28
+ rescue Webhookr::InvalidSecurityTokenError => e
29
+ raise ActionController::InvalidAuthenticityToken.new("Invalid or missing security token for service '#{params[:service_id]}'.")
30
+ end
31
+ end
32
+
33
+ end
34
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,4 @@
1
+ Webhookr::Engine.routes.draw do
2
+ get "/events/:service_id(/:security_token)" => "events#show", :as => "events"
3
+ post "/events/:service_id(/:security_token)" => "events#create", :as => "events"
4
+ end
@@ -0,0 +1,12 @@
1
+ module Webhookr
2
+ module Generators
3
+ class AddRouteGenerator < Rails::Generators::Base
4
+
5
+ desc 'This generator adds \'mount Webhookr::Engine => "/webhookr", :as => "webhookr"\' to your routes'
6
+ def add_route
7
+ route 'mount Webhookr::Engine => "/webhookr", :as => "webhookr"'
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ module Webhookr
2
+ module Generators
3
+ class InitGenerator < Rails::Generators::NamedBase
4
+
5
+ desc "This generator creates an initializer file 'config/initializers/NAME.rb'"
6
+ def init
7
+ initializer("#{file_name}.rb") do
8
+ file_contents
9
+ end
10
+ end
11
+
12
+ def file_contents
13
+ <<-eos
14
+ # Webhookr Initializer
15
+
16
+ ## Turn on http basic authentication for all plugins
17
+ # Webhookr.config.basic_auth.username = "admin"
18
+ # Webhookr.config.basic_auth.password = "password"
19
+
20
+ ## Plugin Initializers go here ##
21
+ eos
22
+ end
23
+
24
+ def generate_security_token
25
+ rand(10000000000000000).floor.to_s(36)
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ namespace :webhookr do
2
+ desc "List the configured services and paths"
3
+ task :services => :environment do
4
+
5
+ puts "No webhookr services configured - add and configure webhookr plugins." and next if Webhookr.adapters.empty?
6
+
7
+ include Webhookr::Engine.routes.url_helpers
8
+
9
+ Webhookr.adapters.each do |key, adapter|
10
+ puts "\n\n#{key}:"
11
+ %w{ GET POST}.each do |x|
12
+ puts " #{x}\t#{events_path(key, :security_token => Webhookr.config[key].try(:security_token))}\n"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Webhookr
2
+ class AdapterResponse < Struct.new(:service_name, :event_type, :payload); end
3
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_support/core_ext/kernel/singleton_class'
2
+
3
+ module Webhookr
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Webhookr
6
+
7
+ # Enable basic auth for all services when config.basic_auth.username
8
+ # and config.basic_auth.password are set.
9
+ self.config.webhookr = ActiveSupport::OrderedOptions.new
10
+ self.config.webhookr.basic_auth = ActiveSupport::OrderedOptions.new
11
+
12
+ initializer "webhookr.config" do |app|
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,3 @@
1
+ module Webhookr
2
+ class InvalidPayloadError < RuntimeError; end
3
+ end
@@ -0,0 +1,3 @@
1
+ module Webhookr
2
+ class InvalidSecurityTokenError < RuntimeError; end
3
+ end
@@ -0,0 +1,28 @@
1
+ module Webhookr
2
+ # Adapted from http://www.rebeccamiller-webster.com/2012/06/recursively-convert-a-ruby-hash-to-openstruct/
3
+ module OstructUtils
4
+
5
+ def self.to_ostruct(obj)
6
+ case
7
+ when obj.kind_of?(Hash)
8
+ return hash_to_ostruct(obj)
9
+ when obj.kind_of?(Array)
10
+ return array_to_ostruct(obj)
11
+ else
12
+ return obj
13
+ end
14
+ end
15
+
16
+ def self.hash_to_ostruct(hash)
17
+ hash.each do |key, val|
18
+ hash[key] = to_ostruct(val)
19
+ end
20
+ OpenStruct.new(hash)
21
+ end
22
+
23
+ def self.array_to_ostruct(array)
24
+ array.map { |r| to_ostruct(r) }
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,51 @@
1
+ module Webhookr
2
+ class Service
3
+ attr_reader :service_name
4
+
5
+ def initialize(service_name, options = {})
6
+ @service_name = (service_name || "").downcase
7
+ @raw_payload = options[:payload]
8
+ available?
9
+ validate_security_token(options[:security_token]) if configured_security_token
10
+ end
11
+
12
+ def process!
13
+ Array.wrap(service_adapter.send(:process, @raw_payload)).each do |payload|
14
+ callback(callback_class, payload)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def callback(object, payload)
21
+ method = method_for(payload)
22
+ object.send(method, payload) if object.respond_to?(method)
23
+ end
24
+
25
+ def method_for(payload)
26
+ "on_" + payload.event_type
27
+ end
28
+
29
+ def callback_class
30
+ callback = Webhookr.config[service_name].try(:callback)
31
+ raise "No callback is configured for the service '#{service_name}'." if callback.nil?
32
+ @call_back_class || callback.new
33
+ end
34
+
35
+ def configured_security_token
36
+ Webhookr.config[service_name].try(:security_token)
37
+ end
38
+
39
+ def validate_security_token(token)
40
+ raise Webhookr::InvalidSecurityTokenError if token.nil? || token != configured_security_token
41
+ end
42
+
43
+ def service_adapter
44
+ raise NameError.new(%{Bad service name "#{service_name}"}) unless Webhookr.adapters[service_name]
45
+ @service_adapter ||= Webhookr.adapters[service_name]
46
+ end
47
+
48
+ alias_method :available?, :service_adapter
49
+
50
+ end
51
+ end
@@ -0,0 +1,20 @@
1
+
2
+ module Webhookr::Services::Adapter::Base
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ self.initialize! if self.respond_to?(:initialize!)
7
+ Webhookr.adapters[self::SERVICE_NAME] = self
8
+ end
9
+
10
+ module ClassMethods
11
+ def config
12
+ if Webhookr.config[self::SERVICE_NAME]
13
+ Webhookr.config[self::SERVICE_NAME]
14
+ else
15
+ Webhookr.config[self::SERVICE_NAME] = ActiveSupport::OrderedOptions.new
16
+ end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,7 @@
1
+
2
+ module Webhookr::Services::Adapter
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Base
6
+ end
7
+
@@ -0,0 +1,7 @@
1
+
2
+ module Webhookr::Services
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Adapter
6
+ end
7
+
@@ -0,0 +1,3 @@
1
+ module Webhookr
2
+ VERSION = "0.0.2"
3
+ end
data/lib/webhookr.rb ADDED
@@ -0,0 +1,24 @@
1
+ require "webhookr/engine"
2
+
3
+ module Webhookr
4
+ extend ActiveSupport::Autoload
5
+
6
+ autoload :InvalidPayloadError
7
+ autoload :AdapterResponse
8
+ autoload :Service
9
+ autoload :VERSION
10
+
11
+ class << self
12
+ def adapters
13
+ @adapters ||= HashWithIndifferentAccess.new
14
+ end
15
+
16
+ def config
17
+ @config ||= defined?(Rails) ? Rails.application.config.webhookr :
18
+ ActiveSupport::OrderedOptions.new
19
+ end
20
+ end
21
+ end
22
+
23
+ require "webhookr/services"
24
+
data/script/rails ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
5
+ ENGINE_PATH = File.expand_path('../../lib/webhookr/engine', __FILE__)
6
+
7
+ require 'rails/all'
8
+ require 'rails/engine/commands'