rulesio 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # dependencies are in rulesio.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,154 @@
1
+ rulesio gem for rules.io
2
+ =========
3
+
4
+ [rules.io](http://rules.io) is a rules engine that reacts to things users do or experience in your software, and makes things happen in 3rd party SaaS APIs -- without your having to write any code. Rather than implementing the most rapidly evolving parts of your application's business logic in code, your team can use the rules.io web app to specify "when", "how", and "who", with rules like these:
5
+
6
+ * when a user gets a form validation error three times in an hour, send an email to Frank
7
+ * when a premium customer hasn't logged in for a month, flag them in your CRM
8
+ * when a user gets a 500 response, create a ticket in Zendesk
9
+ * when a user invites ten friends, add them to the "well-connected" segment in MailChimp
10
+
11
+ This gem contains Rack middleware that automatically generates two event streams, one about exceptions and the other about user activity (pageviews, errors, attempts to save invalid models), that can be used to trigger rules in rules.io. You can also send more specific events manually.
12
+
13
+ Setup
14
+ -----
15
+
16
+ In your Gemfile:
17
+
18
+ gem 'rulesio'
19
+
20
+ ###For Ruby on Rails
21
+
22
+ You should create two incoming channels (event streams) in rules.io, and configure their tokens in `config/rulesio.rb` (the available options are explained below). You may want to create additional channels to use in other environments, eg for staging.
23
+
24
+ token 'CHANNEL_TOKEN' # default channel (for user-centric events)
25
+ middleware :users # automatically generate events about user activity
26
+ middleware :exceptions do # automatically generate events for exceptions
27
+ token 'ERROR_CHANNEL_TOKEN' # separate channel for error-centric events
28
+ end
29
+
30
+ ###As general-purpose Rack middleware, with or without Rails
31
+
32
+ config.middleware.insert 0, 'RulesIO::Rack', :token => 'CHANNEL_TOKEN'
33
+ config.middleware.use 'RulesIO::Users'
34
+ config.middleware.use 'RulesIO::Exceptions', :token => 'ERROR_CHANNEL_TOKEN'
35
+
36
+ The current user
37
+ ----------------
38
+
39
+ rules.io can take advantage of knowing about the user behind each event, which is supplied by the \_actor field. This gem employs some to determine the current user, but you can also help it out (the default value is `current_user.to_param`). If, for example, you want to use current_user.id as the \_actor for every event (and 0 for the logged out user), you could do this via:
40
+
41
+ controller_data '{:_actor => current_user.try(:id) || 0}'
42
+
43
+ The string will be evaluated in the context of your controller.
44
+
45
+ Sending other events
46
+ --------------------
47
+
48
+ Every event must contain the \_actor, \_timestamp, \_domain and \_name fields. Beyond those fields, you can include any additional data you choose. See [docs](http://rules.io/docs) for more details.
49
+
50
+ To manually send an event when a user upgrades to a "premium" account:
51
+
52
+ RulesIO.send_event(
53
+ :_actor => current_user.unique_id,
54
+ :_timestamp => Time.now.to_f,
55
+ :_domain => 'account',
56
+ :_name => 'upgrade',
57
+ :user_email => current_user.email,
58
+ :plan => 'premium' )
59
+
60
+ Using girl_friday for asynchronous communication and persistence
61
+ -----------------
62
+
63
+ By default this gem sends a batch of events to the rules.io service synchronously, at the end of each request to your application. This means that each request to your app will be slowed down by the time it takes to do that communication. While this is fine for development or for low-volume sites, for those who wish to avoid this delay rulesio supports the use of the [girl_friday](https://github.com/mperham/girl_friday) gem, which you can enable in your rulesio.rb file:
64
+
65
+ queue RulesIO::GirlFridayQueue
66
+
67
+ Using the GirlFridayQueue also ensures that events are not lost should the rules.io service be temporarily unavailable.
68
+
69
+ You can also pass options to girl_friday. To avoid losing events when your app server instances restart, you can tell girl_friday to use Redis. In order to use the Redis backend, you must use the [connection_pool](https://github.com/mperham/connection_pool) gem to share a set of Redis connections with other threads and the GirlFriday queue. If you are not already using Redis in your application, add
70
+
71
+ gem 'connection_pool'
72
+ gem 'redis'
73
+
74
+ to your Gemfile, and add something like this to `config/rulesio.rb`:
75
+
76
+ require 'connection_pool'
77
+
78
+ redis_pool = ConnectionPool.new(:size => 5, :timeout => 5) { ::Redis.new }
79
+ queue RulesIO::GirlFridayQueue,
80
+ :store => GirlFriday::Store::Redis, :store_config => { :pool => redis_pool }
81
+
82
+ See the [girl_friday wiki](https://github.com/mperham/girl_friday/wiki) for more information on how to use girl_friday.
83
+
84
+ Options
85
+ -------
86
+
87
+ RulesIO::Rack accepts these options:
88
+
89
+ * `token` -- the token for a rules.io channel
90
+ * `webhook_url` -- defaults to 'http://www.rules.io/events'
91
+ * `middleware` -- takes the symbol for a middleware and a block, configuring it
92
+ * `queue` -- takes the class used for queuing (default: RulesIO::MemoryQueue), and an optional hash; see the section on girl_friday for examples
93
+ * `controller_data` -- a string evaluated in the context of the Rails controller (if any) handling the request; it should return a hash to be merged into every event (both automatically generated and manually triggered events)
94
+
95
+ The `exceptions` middleware accepts these options:
96
+
97
+ * `token` -- the token for a rules.io error channel
98
+ * `ignore_exceptions` -- an array of exception class names, defaults to ['ActiveRecord::RecordNotFound', 'AbstractController::ActionNotFound', 'ActionController::RoutingError']
99
+ * `ignore_crawlers` -- an array of strings to match against the user agent, includes a number of webcrawlers by default; matching requests do not generate events
100
+ * `ignore_if` -- this proc is passed env and an exception; if it returns true, the exception is not reported to rules.io
101
+ * `custom_data` -- this proc is passed env, and should return a hash to be merged into each automatically generated exception event
102
+
103
+ The `users` middleware accepts these options:
104
+
105
+ * `ignore_crawlers` -- an array of strings to match against the user agent, includes a number of webcrawlers by default; matching requests do not generate events
106
+ * `ignore_if` -- this proc is passed env; if it returns true, no automatic events are reported to rules.io for this request
107
+ * `ignore_if_controller` -- a string to be evaluated in the context of the Rails controller instance; if it evaluates to true, no automatic events are reported to rules.io for this request
108
+ * `custom_data` -- this proc is passed env, and should return a hash to be merged into each automatically generated event
109
+
110
+ The RulesIO::Users middleware uses the same token as RulesIO::Rack.
111
+
112
+ Here's an example of how to skip sending any user events for all requests to the SillyController:
113
+
114
+ middleware :users do
115
+ ignore_if lambda { |env| env['action_controller.instance'].is_a? SillyController }
116
+ end
117
+
118
+ To make life easier in the case where you want a condition evaluated in the context of a Rails controller, you can do the same thing like this. (Only the users middleware supports ignore_if_controller.)
119
+
120
+ middleware :users do
121
+ ignore_if_controller 'self.is_a?(EventsController)'
122
+ end
123
+
124
+ Or if you want to skip sending pageview events for requests from pingdom.com:
125
+
126
+ middleware :users do
127
+ ignore_crawlers RulesIO.default_ignored_crawlers + ['Pingdom.com_bot']
128
+ end
129
+
130
+ Use Cases
131
+ ---------
132
+
133
+ ### Example rule triggers
134
+
135
+ * whenever a `UserIsHavingAVeryBadDay` exception is raised
136
+ * the first time any particular exception occurs
137
+ * whenever a request takes more than 20 seconds to process
138
+ * whenever someone upgrades their account
139
+ * whenever someone does comment#create more than 10 times in a day
140
+ * whenever someone tagged 'active' doesn't login for a week
141
+
142
+ ### Example rule actions
143
+
144
+ * send yourself an email or a mobile push message
145
+ * send a user an email or a mobile push message
146
+ * create a ticket in your ticketing system
147
+ * add a data point to a Librato or StatsMix graph
148
+ * tag a user in rules.io, or in your CRM
149
+ * segment a user in your email campaign tool
150
+
151
+ Compatibility
152
+ -------------
153
+
154
+ This gem can be used without Rails, but when used with Rails it depends on Rails 3 (we've tested with Rails 3.1 and 3.2). If you want to use girl_friday, you must use Ruby 1.9.2 or greater, JRuby, or Rubinius.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,29 @@
1
+ module ActiveRecord
2
+ module RulesIOExtension
3
+ def save(*)
4
+ result = super
5
+ send_invalid_model_event if result == false
6
+ result
7
+ end
8
+
9
+ def save!(*)
10
+ begin
11
+ super
12
+ rescue ::ActiveRecord::RecordNotSaved
13
+ send_invalid_model_event
14
+ raise ::ActiveRecord::RecordNotSaved
15
+ end
16
+ end
17
+
18
+ private
19
+ def send_invalid_model_event
20
+ event = {
21
+ :_domain => 'invalid_model',
22
+ :_name => self.class.name,
23
+ :attributes => self.attributes,
24
+ :errors => self.errors.full_messages.to_sentence
25
+ }
26
+ RulesIO.send_event event
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,79 @@
1
+ # With inspiration from
2
+ # https://github.com/smartinez87/exception_notification
3
+ # http://sharagoz.com/posts/1-rolling-your-own-exception-handler-in-rails-3
4
+
5
+ require 'action_dispatch'
6
+
7
+ module RulesIO
8
+ class Exceptions
9
+ include RulesIO::Helpers
10
+
11
+ def self.default_ignored_exceptions
12
+ [].tap do |exceptions|
13
+ exceptions << 'ActiveRecord::RecordNotFound'
14
+ exceptions << 'AbstractController::ActionNotFound'
15
+ exceptions << 'ActionController::RoutingError'
16
+ end
17
+ end
18
+
19
+ def initialize(app, options={})
20
+ @app, @options = app, options
21
+ @options[:ignore_exceptions] ||= self.class.default_ignored_exceptions
22
+ @options[:ignore_crawlers] ||= RulesIO.default_ignored_crawlers
23
+ @options[:ignore_if] ||= lambda { |env, e| false }
24
+ @options[:token] ||= RulesIO.token
25
+ @options[:custom_data] ||= lambda { |env| {} }
26
+ end
27
+
28
+ def call(env)
29
+ begin
30
+ @app.call(env)
31
+ rescue Exception => exception
32
+ env['rulesio.exception'] = exception
33
+ send_event_now event(env, exception), @options[:token], env unless should_be_ignored(env, exception)
34
+ raise exception
35
+ end
36
+ end
37
+
38
+ private
39
+ def send_event_now(event, token, env)
40
+ prep = RulesIO.prepare_event(event, env)
41
+ RulesIO.post_payload_to_token prep, token
42
+ end
43
+
44
+ def should_be_ignored(env, exception)
45
+ ignored_exception(@options[:ignore_exceptions], exception) ||
46
+ from_crawler(@options[:ignore_crawlers], env['HTTP_USER_AGENT']) ||
47
+ conditionally_ignored(@options[:ignore_if], env, exception)
48
+ end
49
+
50
+ def ignored_exception(ignore_array, exception)
51
+ Array.wrap(ignore_array).map(&:to_s).include?(exception.class.name)
52
+ end
53
+
54
+ def conditionally_ignored(ignore_proc, env, exception)
55
+ ignore_proc.call(env, exception)
56
+ rescue Exception => ex
57
+ false
58
+ end
59
+
60
+ def event(env, exception)
61
+ backtrace = clean_backtrace(exception)
62
+ event = {
63
+ :_actor => actor_for_exception(exception),
64
+ :_from => '',
65
+ :_domain => exception.class.to_s,
66
+ :_name => fileline(exception),
67
+ :_message => exception.to_s,
68
+ :exception => exception.class.to_s,
69
+ :file => fileline(exception),
70
+ :backtrace => backtrace.join("\n")
71
+ }.with_indifferent_access
72
+ useractor = RulesIO.current_actor(env)
73
+ event[:_xactor] = useractor if useractor
74
+ event.merge!(@options[:custom_data].call(env))
75
+ event
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,34 @@
1
+ require 'singleton'
2
+ require 'girl_friday'
3
+
4
+ module RulesIO
5
+ class GirlFridayQueue < GirlFriday::WorkQueue
6
+
7
+ include Singleton
8
+
9
+ def initialize
10
+ super(:rulesio, {:size => 1}.merge(RulesIO.queue_options)) do |msg|
11
+ retries = 0
12
+ begin
13
+ RulesIO.post_payload_to_token msg[:payload], RulesIO.token
14
+ rescue Exception => e
15
+ if (retries += 1) % 6 == 5
16
+ RulesIO.logger.warn "RulesIO having trouble sending events; #{retries} attempts so far."
17
+ end
18
+ sleep [5, retries].max
19
+ retry
20
+ end
21
+ RulesIO.logger.warn "RulesIO resuming service after #{retries} retries." unless retries == 0
22
+ end
23
+ end
24
+
25
+ def self.push *args
26
+ instance.push *args
27
+ end
28
+
29
+ def self.status
30
+ instance.status
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ module RulesIO
2
+ module Helpers
3
+ def from_crawler(ignore_array, agent)
4
+ ignore_array.each do |crawler|
5
+ return true if (agent =~ /\b(#{crawler})\b/i)
6
+ end unless ignore_array.blank?
7
+ false
8
+ end
9
+
10
+ def clean_backtrace(exception)
11
+ if defined?(Rails) && Rails.respond_to?(:backtrace_cleaner)
12
+ Rails.backtrace_cleaner.send(:filter, exception.backtrace)
13
+ else
14
+ exception.backtrace
15
+ end
16
+ end
17
+
18
+ def fileline(exception)
19
+ fl = clean_backtrace(exception).first.match(/^(.*:.*):/)[1] rescue @app.to_s
20
+ fl.gsub(/ \((.*)\) /, '-\1-')
21
+ end
22
+
23
+ def actor_for_exception(exception)
24
+ "#{exception.class.to_s}:#{fileline(exception)}"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ require 'girl_friday'
2
+
3
+ module RulesIO
4
+ class MemoryQueue
5
+
6
+ def self.push(hash)
7
+ RulesIO.post_payload_to_token hash[:payload], RulesIO.token
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,97 @@
1
+ require 'rails/railtie'
2
+ require 'active_record'
3
+
4
+ module RulesIO
5
+ class RailsConfigurator
6
+ attr_accessor :token, :webhook_url, :middlewares, :queue, :controller_data, :queue_options
7
+ def initialize
8
+ @webhook_url = 'http://www.rules.io/events/'
9
+ @middlewares = {}
10
+ end
11
+
12
+ def token(token)
13
+ @token = token
14
+ end
15
+
16
+ def webhook_url(webhook_url)
17
+ @webhook_url = webhook_url
18
+ end
19
+
20
+ def middleware(middleware, &block)
21
+ @middlewares[middleware] = MiddlewareConfigurator.apply(&block)
22
+ end
23
+
24
+ def queue(queue, options)
25
+ @queue = queue
26
+ @queue_options = options
27
+ end
28
+
29
+ def controller_data(data)
30
+ @controller_data = data
31
+ end
32
+
33
+ def girl_friday_options(options)
34
+ @girl_friday_options = options
35
+ end
36
+ end
37
+
38
+ class MiddlewareConfigurator
39
+ attr_accessor :configuration
40
+
41
+ def self.apply(&block)
42
+ x = new
43
+ x.configure(&block) if block_given?
44
+ x
45
+ end
46
+
47
+ def initialize
48
+ @configuration = {}
49
+ end
50
+
51
+ def configure(&block)
52
+ instance_eval &block
53
+ end
54
+
55
+ def method_missing(mid, *args, &block)
56
+ mname = mid.id2name
57
+ if block_given?
58
+ @configuration[mname.to_sym] = *block
59
+ else
60
+ if args.size == 1
61
+ @configuration[mname.to_sym] = args.first
62
+ else
63
+ @configuration[mname.to_sym] = args
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ class Railtie < Rails::Railtie
70
+ initializer :rulesio do |app|
71
+ filename = Rails.root.join('config/rulesio.rb')
72
+ if File.exists?(filename)
73
+ RulesIO::RailsConfigurator.new.instance_eval do
74
+ eval IO.read(filename), binding, filename.to_s, 1
75
+ if defined?(::Rails.configuration) && ::Rails.configuration.respond_to?(:middleware)
76
+ ::Rails.configuration.middleware.insert_after 'ActionDispatch::Static', 'RulesIO::Rack',
77
+ :webhook_url => @webhook_url,
78
+ :token => @token,
79
+ :queue => @queue,
80
+ :queue_options => @queue_options,
81
+ :controller_data => @controller_data
82
+ ::Rails.configuration.middleware.use('RulesIO::Users', @middlewares[:users].configuration) if @middlewares.has_key?(:users)
83
+ ::Rails.configuration.middleware.use('RulesIO::Users', @middlewares[:pageviews].configuration) if @middlewares.has_key?(:pageviews)
84
+ ::Rails.configuration.middleware.use('RulesIO::Exceptions', @middlewares[:exceptions].configuration) if @middlewares.has_key?(:exceptions)
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ config.after_initialize do
91
+ ActiveSupport.on_load(:active_record) do
92
+ require 'rulesio/active_record_extension'
93
+ include ActiveRecord::RulesIOExtension
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,67 @@
1
+ # With inspiration from
2
+ # https://github.com/smartinez87/exception_notification
3
+ # http://sharagoz.com/posts/1-rolling-your-own-exception-handler-in-rails-3
4
+
5
+ require 'action_dispatch'
6
+
7
+ module RulesIO
8
+ class Users
9
+ include RulesIO::Helpers
10
+
11
+ def initialize(app, options={})
12
+ @app, @options = app, options
13
+ @options[:ignore_crawlers] ||= RulesIO.default_ignored_crawlers
14
+ @options[:ignore_if] ||= lambda { |env| false }
15
+ @options[:ignore_if_controller] ||= 'false'
16
+ @options[:custom_data] ||= lambda { |env| {} }
17
+ end
18
+
19
+ def call(env)
20
+ before = Time.now
21
+ status, headers, response = @app.call(env)
22
+ [status, headers, response]
23
+ rescue Exception => e
24
+ status = 500
25
+ raise e
26
+ ensure
27
+ after = Time.now
28
+ RulesIO.send_event event(env, status, after - before) unless should_be_ignored(env)
29
+ end
30
+
31
+ private
32
+ def rails_asset_request?(env)
33
+ defined?(Rails) && env['action_controller.instance'].nil?
34
+ end
35
+
36
+ def should_be_ignored(env)
37
+ rails_asset_request?(env) ||
38
+ from_crawler(@options[:ignore_crawlers], env['HTTP_USER_AGENT']) ||
39
+ conditionally_ignored(@options[:ignore_if], env) ||
40
+ conditionally_ignored_controller(@options[:ignore_if_controller], env)
41
+ end
42
+
43
+ def conditionally_ignored_controller(condition, env)
44
+ controller = env['action_controller.instance']
45
+ controller.instance_eval condition
46
+ end
47
+
48
+ def conditionally_ignored(ignore_proc, env)
49
+ ignore_proc.call(env)
50
+ end
51
+
52
+ def event(env, status, duration)
53
+ event = {
54
+ :_domain => (status.to_i >= 400) ? 'pageerror' : 'pageview',
55
+ :status => status,
56
+ :duration => "%.2f" % (duration * 1000)
57
+ }
58
+ if exception = env['rulesio.exception']
59
+ event[:_xactor] = actor_for_exception(exception)
60
+ event[:_message] = exception.to_s
61
+ end
62
+ event.merge!(@options[:custom_data].call(env))
63
+ event
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ module RulesIO
2
+ VERSION = '0.9.2'
3
+ end
data/lib/rulesio.rb ADDED
@@ -0,0 +1,137 @@
1
+ require 'rulesio/version'
2
+ require 'rulesio/helpers'
3
+ require 'rulesio/exceptions'
4
+ require 'rulesio/users'
5
+ require 'rulesio/girl_friday_queue'
6
+ require 'rulesio/memory_queue'
7
+ require 'net/http'
8
+ require 'uri'
9
+ require 'logger'
10
+ require 'active_support/core_ext/module/attribute_accessors'
11
+ require 'active_support/core_ext/hash/indifferent_access'
12
+
13
+ module RulesIO
14
+ mattr_accessor :filter_parameters, :buffer, :token, :webhook_url, :queue, :queue_options, :controller_data, :logger
15
+
16
+ def self.default_ignored_crawlers
17
+ %w(Baidu Gigabot Googlebot libwww-perl lwp-trivial msnbot SiteUptime Slurp WordPress ZIBB ZyBorg Yandex Jyxobot Huaweisymantecspider ApptusBot NewRelicPinger)
18
+ end
19
+
20
+ def self.send_event(event)
21
+ buffer << event
22
+ end
23
+
24
+ def self.flush(env={})
25
+ return if (events = RulesIO.buffer).empty?
26
+ RulesIO.buffer = []
27
+ RulesIO.queue.push(:payload => events.map {|event| RulesIO.prepare_event(event, env)})
28
+ # RulesIO.post_payload_to_token events.to_json, RulesIO.token
29
+ end
30
+
31
+ def self.post_payload_to_token(payload, token)
32
+ uri = URI(RulesIO.webhook_url + token)
33
+ req = Net::HTTP::Post.new(uri.path)
34
+ req.body = payload.to_json
35
+ req.content_type = 'application/json'
36
+ Net::HTTP.start(uri.host, uri.port) do |http|
37
+ http.request(req)
38
+ end
39
+ end
40
+
41
+ def self.current_user(env)
42
+ if controller = env['action_controller.instance']
43
+ controller.instance_variable_get('@current_user') || controller.instance_eval('current_user')
44
+ end
45
+ rescue
46
+ nil
47
+ end
48
+
49
+ def self.current_actor(env)
50
+ if controller = env['action_controller.instance']
51
+ begin
52
+ data = controller.instance_eval(RulesIO.controller_data)
53
+ data = data.with_indifferent_access
54
+ return data[:_actor] if data[:_actor]
55
+ rescue
56
+ end
57
+
58
+ user = controller.instance_variable_get('@current_user') || controller.instance_eval('current_user')
59
+ [:to_param, :id].each do |method|
60
+ return user.send(method) if user && user.respond_to?(method)
61
+ end
62
+ end
63
+ nil
64
+ end
65
+
66
+ private
67
+ def self.page_event_name(request, params)
68
+ if params && params['controller']
69
+ "#{params['controller']}##{params['action']}"
70
+ else
71
+ request.path.gsub('/', '-')[1..-1]
72
+ end
73
+ end
74
+
75
+ def self.prepare_event(event, env)
76
+ event = event.with_indifferent_access
77
+
78
+ if controller = env['action_controller.instance']
79
+ begin
80
+ data = controller.instance_eval(RulesIO.controller_data).with_indifferent_access
81
+ event = data.merge(event)
82
+ rescue
83
+ end
84
+ end
85
+
86
+ current_user = current_user(env)
87
+ actor = current_actor(env)
88
+
89
+ event[:_actor] = actor || 'anonymous' unless event[:_actor].present?
90
+ event[:_timestamp] ||= Time.now.to_f
91
+ event[:rails_env] = Rails.env if defined?(Rails)
92
+
93
+ unless env.empty?
94
+ env['rack.input'].rewind
95
+ request = defined?(Rails) ? ActionDispatch::Request.new(env) : ::Rack::Request.new(env)
96
+ params = request.params
97
+ action = page_event_name(request, params)
98
+
99
+ event[:_name] ||= action
100
+ event[:_from] ||= current_user.email if current_user && current_user.respond_to?(:email) && current_user.email != event[:_actor]
101
+ event[:action] = action
102
+ event[:request_url] = env['rulesio.request_url']
103
+ event[:request_method] = request.request_method
104
+ event[:user_agent] = request.user_agent
105
+ event[:referer_url] = request.referer
106
+ event[:params] = params.except(*RulesIO.filter_parameters)
107
+ event[:session] = request.session
108
+ end
109
+
110
+ event.reject! {|k, v| v.to_s.blank?}
111
+ event
112
+ end
113
+
114
+ class Rack
115
+ def initialize(app, options={})
116
+ @app = app
117
+ RulesIO.logger = defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
118
+ RulesIO.webhook_url = options[:webhook_url] || 'http://www.rules.io/events/'
119
+ RulesIO.buffer = []
120
+ RulesIO.filter_parameters = defined?(Rails) ? Rails.application.config.filter_parameters : []
121
+ RulesIO.token = options[:token]
122
+ RulesIO.queue = options[:queue] || RulesIO::MemoryQueue
123
+ RulesIO.queue_options = options[:queue_options] || {}
124
+ RulesIO.controller_data = options[:controller_data] || '{}'
125
+ end
126
+
127
+ def call(env)
128
+ RulesIO.buffer = []
129
+ env['rulesio.request_url'] = ::Rack::Request.new(env).url
130
+ @app.call(env)
131
+ ensure
132
+ RulesIO.flush(env)
133
+ end
134
+ end
135
+ end
136
+
137
+ require 'rulesio/railtie' if defined?(Rails)
data/rulesio.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'rulesio/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'rulesio'
7
+ s.version = RulesIO::VERSION
8
+ s.authors = ['David Anderson', 'Chris Weis']
9
+ s.email = ['david@alpinegizmo.com']
10
+ s.homepage = 'https://github.com/rulesio/rulesio'
11
+ s.summary = %q{Rack middleware for connecting to rules.io}
12
+ s.description = %q{Rack middleware for connecting Rack applications to rules.io, with extensions for Rails 3 applications.}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ['lib']
18
+
19
+ s.add_runtime_dependency 'activesupport'
20
+ s.add_runtime_dependency 'actionpack'
21
+ s.add_runtime_dependency 'girl_friday', '~> 0.10.0'
22
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rulesio
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 9
8
+ - 2
9
+ version: 0.9.2
10
+ platform: ruby
11
+ authors:
12
+ - David Anderson
13
+ - Chris Weis
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-08-28 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activesupport
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: actionpack
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ segments:
41
+ - 0
42
+ version: "0"
43
+ type: :runtime
44
+ version_requirements: *id002
45
+ - !ruby/object:Gem::Dependency
46
+ name: girl_friday
47
+ prerelease: false
48
+ requirement: &id003 !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ~>
51
+ - !ruby/object:Gem::Version
52
+ segments:
53
+ - 0
54
+ - 10
55
+ - 0
56
+ version: 0.10.0
57
+ type: :runtime
58
+ version_requirements: *id003
59
+ description: Rack middleware for connecting Rack applications to rules.io, with extensions for Rails 3 applications.
60
+ email:
61
+ - david@alpinegizmo.com
62
+ executables: []
63
+
64
+ extensions: []
65
+
66
+ extra_rdoc_files: []
67
+
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - README.md
72
+ - Rakefile
73
+ - lib/rulesio.rb
74
+ - lib/rulesio/active_record_extension.rb
75
+ - lib/rulesio/exceptions.rb
76
+ - lib/rulesio/girl_friday_queue.rb
77
+ - lib/rulesio/helpers.rb
78
+ - lib/rulesio/memory_queue.rb
79
+ - lib/rulesio/railtie.rb
80
+ - lib/rulesio/users.rb
81
+ - lib/rulesio/version.rb
82
+ - rulesio.gemspec
83
+ has_rdoc: true
84
+ homepage: https://github.com/rulesio/rulesio
85
+ licenses: []
86
+
87
+ post_install_message:
88
+ rdoc_options: []
89
+
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ segments:
104
+ - 0
105
+ version: "0"
106
+ requirements: []
107
+
108
+ rubyforge_project:
109
+ rubygems_version: 1.3.6
110
+ signing_key:
111
+ specification_version: 3
112
+ summary: Rack middleware for connecting to rules.io
113
+ test_files: []
114
+