blamescope 0.0.3

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0f0301896ebca21df7ef26fb732142ffe3ad8f3d
4
+ data.tar.gz: ebbe482c0b14d987baa3e9341eaffdc484c417e8
5
+ SHA512:
6
+ metadata.gz: 387c2232c674bec8635d2cdacedd89f9353b52373f9aeb931c827d6f40962a33d20d949024d31a824c4741f1acd4cd3a9301ad9491eef9b102b4c7f3218ac679
7
+ data.tar.gz: 623534cec783f7fd61e99786005566e6cf0beadc52b6d1081a0677ff062a43dea2fe2c5abc0c2fb00c66ce2560b682c4f1447fce7736d31899ccf825142d1260
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ addons:
3
+ code_climate:
4
+ repo_token: 857b1adec905710209e302b26df48ef67900dabba836fcddc993334fb035a0ca
5
+ rvm:
6
+ - 1.9.3
7
+ - 2.0.0
8
+ - 2.1.1
9
+ script: bundle exec rake
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in blamescope.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 vladnik
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,70 @@
1
+ # BlameScope
2
+
3
+ Track events per user
4
+
5
+ [![Build Status](https://travis-ci.org/Demandbase/blamescope.svg?branch=master)](https://travis-ci.org/Demandbase/blamescope)
6
+ [![Code Climate](https://codeclimate.com/github/Demandbase/blamescope/badges/gpa.svg)](https://codeclimate.com/github/Demandbase/blamescope)
7
+ [![Test Coverage](https://codeclimate.com/github/Demandbase/blamescope/badges/coverage.svg)](https://codeclimate.com/github/Demandbase/blamescope)
8
+
9
+ ### Client mode
10
+
11
+ Rails setup
12
+
13
+ # config/application.rb
14
+ config.middleware.use Blamescope::Middleware, rabbit: 'amqp://user:pass@host:port/vhost'
15
+
16
+ Rack setup
17
+
18
+ # config.ru
19
+ use Blamescope::Middleware, rabbit: 'amqp://user:pass@host:port/vhost'
20
+
21
+ #### Usage
22
+
23
+ class User < ActiveRecord::Base
24
+ include Blamescope
25
+
26
+ blame_activerecord # Tracks create, update and delete action
27
+ def custom_action
28
+ # Some action
29
+ end
30
+ blame_method :custom_action
31
+ end
32
+
33
+ At the current time it adds such methods:
34
+ * blame_activerecord - track create, update, delete of ActiveRecord model
35
+ * blame_method - track specific method
36
+ * blame_methods - track multiple methods
37
+
38
+ You can also track static methods and Devise is the only authorization it supports.
39
+
40
+ ### Server mode
41
+
42
+ blamescope -d postgres://user:password@host/database -r amqp://user:pass@host:port/vhost
43
+
44
+ ## Installation
45
+
46
+ Add this line to your application's Gemfile:
47
+
48
+ ```ruby
49
+ gem 'blamescope'
50
+ ```
51
+
52
+ And then execute:
53
+
54
+ $ bundle
55
+
56
+ Or install it yourself as:
57
+
58
+ $ gem install blamescope
59
+
60
+ ## Usage
61
+
62
+ TODO: Write usage instructions here
63
+
64
+ ## Contributing
65
+
66
+ 1. Fork it ( https://github.com/[my-github-username]/blamescope/fork )
67
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
68
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
69
+ 4. Push to the branch (`git push origin my-new-feature`)
70
+ 5. Create a new Pull Request
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task default: :spec
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'rubygems'
5
+ rescue LoadError
6
+ end
7
+ require 'blamescope'
8
+
9
+ Blamescope::Server.start
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'blamescope/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "blamescope"
8
+ spec.version = Blamescope::VERSION
9
+ spec.authors = ["Volodymyr Ladnik"]
10
+ spec.email = ["vladnik@demandbase.com"]
11
+ spec.summary = %q{Track events per user}
12
+ spec.description = %q{Find whom to blame for broken stuff}
13
+ spec.homepage = "https://github.com/Demandbase/blamescope"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "bunny"
22
+ spec.add_dependency "pg"
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "codeclimate-test-reporter"
27
+ spec.add_development_dependency "pry"
28
+ end
@@ -0,0 +1,70 @@
1
+ require "bunny"
2
+ require "blamescope/version"
3
+ require "blamescope/middleware"
4
+ require "blamescope/trackable"
5
+ require "blamescope/server"
6
+
7
+ #
8
+ # Blamescope module
9
+ #
10
+ # @author Volodymyr Ladnik
11
+ #
12
+ module Blamescope
13
+ class << self
14
+ attr_accessor :exchange
15
+
16
+ def included(base)
17
+ base.extend(Trackable)
18
+ base.class.extend(Trackable)
19
+ end
20
+
21
+ #
22
+ # Configure Blamescope
23
+ # @param [Hash] config
24
+ # @option config [String] :rabbit Rabbit connection in format amqp://user:pass@host:port/vhost
25
+ #
26
+ def configure(config)
27
+ self.exchange = rabbit_connection(config[:rabbit])[:exchange]
28
+ end
29
+
30
+ #
31
+ # Get current user
32
+ #
33
+ # @return [String] user email
34
+ def user
35
+ env = Thread.current[:env]
36
+ if env && env['warden']
37
+ env['warden'].user.email
38
+ else
39
+ "#{ENV['USER']}@#{Socket.gethostname}"
40
+ end
41
+ end
42
+
43
+ #
44
+ # Send event
45
+ # @param [type] attrs event attributes
46
+ # @option attrs [String] :model current class name
47
+ # @option attrs [String] :action current method
48
+ # @option attrs [String] :attrs object attributes
49
+ #
50
+ def emit(attrs)
51
+ attrs[:email] ||= user
52
+ exchange.publish(attrs.to_json)
53
+ rescue Bunny::ConnectionClosedError
54
+ puts "Bunny is down: couldn't send #{attrs[:action]} action for #{attrs[:model]} model"
55
+ end
56
+
57
+ #
58
+ # Create RabbitMQ connection
59
+ # @param config [String] Rabbit connection in format amqp://user:pass@host:port/vhost
60
+ #
61
+ # @return [Hash] rabbit connection, channel and exchange
62
+ def rabbit_connection(config)
63
+ conn = Bunny.new(config)
64
+ conn.start
65
+ channel = conn.create_channel
66
+ exchange = channel.fanout("blamescope")
67
+ {conn: conn, channel: channel, exchange: exchange}
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,19 @@
1
+ module Blamescope
2
+ #
3
+ # Rack middleware to intercept request environment
4
+ #
5
+ # @author Volodymyr Ladnik
6
+ #
7
+ class Middleware
8
+
9
+ def initialize(app, config = nil)
10
+ @app = app
11
+ Blamescope.configure(config) if config
12
+ end
13
+
14
+ def call(env)
15
+ Thread.current[:env] = env
16
+ @app.call(env)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,80 @@
1
+ require "pg"
2
+ require "json"
3
+ require "uri"
4
+ require "optparse"
5
+
6
+ module Blamescope
7
+ class Server
8
+ TABLE_QUERY = <<-EOS
9
+ CREATE TABLE IF NOT EXISTS blamescope (
10
+ id serial NOT NULL,
11
+ email character varying(255),
12
+ action character varying(255),
13
+ model character varying(255),
14
+ attributes json,
15
+ created_at timestamp without time zone,
16
+ CONSTRAINT blamescope_pkey PRIMARY KEY (id)
17
+ );
18
+ EOS
19
+
20
+ def self.start(options = {})
21
+ # Parse options
22
+ optparse = OptionParser.new do |opts|
23
+ opts.banner = "Usage: blamescope [options]"
24
+ opts.on("-d", "--database CONF", "Database connection in format postgres://user:password@host/database") do |d|
25
+ options[:database] = d
26
+ end
27
+ opts.on("-r", "--rabbit CONF", "RabbitMQ connection in format amqp://user:pass@host:port/vhost") do |r|
28
+ options[:rabbit] = r
29
+ end
30
+ opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
31
+ options[:verbose] = v
32
+ end
33
+ end
34
+
35
+ optparse.parse!
36
+ if options[:database] && options[:rabbit]
37
+ new.listen(options)
38
+ else
39
+ puts optparse
40
+ end
41
+ end
42
+
43
+ def listen(config)
44
+ rabbit = rabbit_connection(config[:rabbit])
45
+ db = postgres_connection(config[:database])
46
+ rabbit[:queue].subscribe(block: true) do |delivery_info, properties, body|
47
+ store_event(db, body)
48
+ end
49
+ rescue Interrupt => _
50
+ rabbit[:channel].close
51
+ rabbit[:conn].close
52
+ end
53
+
54
+ def store_event(db, body, time = Time.now.utc)
55
+ event = JSON.parse(body)
56
+ db.exec_prepared('insert_event', [event['email'], event['action'], event['model'], event['attrs'].to_json, time])
57
+ end
58
+
59
+ def rabbit_connection(config)
60
+ rabbit = Blamescope.rabbit_connection(config)
61
+ queue = rabbit[:channel].queue("", exclusive: true)
62
+ queue.bind(rabbit[:exchange])
63
+ rabbit[:queue] = queue
64
+ return rabbit
65
+ end
66
+
67
+ def postgres_connection(config)
68
+ config = URI.parse(config)
69
+ params = {host: config.host, dbname: config.path[1..-1]}
70
+ params[:user] = config.user if config.user
71
+ params[:password] = config.password if config.password
72
+ params[:port] = config.port if config.port
73
+ conn = PG.connect(params)
74
+ conn.exec(TABLE_QUERY)
75
+ conn.prepare("insert_event", "INSERT INTO blamescope (email, action, model, attributes, created_at)
76
+ VALUES ($1, $2, $3, $4, $5)")
77
+ return conn
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,64 @@
1
+ #
2
+ # Trackable module
3
+ #
4
+ # @author Volodymyr Ladnik
5
+ #
6
+ module Blamescope
7
+ module Trackable
8
+
9
+ #
10
+ # Send event for model
11
+ # @param model [Object] current object
12
+ # @param method_name [String|Symbol] method invoked
13
+ #
14
+ # @return [type] [description]
15
+ def self.send_event(model, method_name)
16
+ class_name = model.class == Class ? model.name : model.class.name
17
+ attrs = {}
18
+ attrs[:id] = model.id if model.respond_to? :id
19
+ if model.respond_to? :name
20
+ attrs[:name] = model.name
21
+ elsif model.respond_to? :title
22
+ attrs[:title] = model.title
23
+ end
24
+ Blamescope.emit(model: class_name, action: method_name, attrs: attrs)
25
+ end
26
+
27
+ #
28
+ # Track method
29
+ # @param name [String|Symbol] method name
30
+ #
31
+ def blame_method method_name
32
+ alias_method "original_#{method_name}", method_name
33
+ define_method method_name do |*args, &block|
34
+ Blamescope::Trackable.send_event(self, method_name)
35
+ send("original_#{method_name}", *args, &block)
36
+ end
37
+ end
38
+
39
+ #
40
+ # Track multiple methods
41
+ # @param *names [String|Symbol] method name
42
+ #
43
+ def blame_methods *names
44
+ names.each do |name|
45
+ blame_method(name)
46
+ end
47
+ end
48
+
49
+ #
50
+ # Track ActiveRecord model
51
+ #
52
+ def blame_activerecord
53
+ after_create do
54
+ Blamescope::Trackable.send_event(self, :create)
55
+ end
56
+ after_update do
57
+ Blamescope::Trackable.send_event(self, :update)
58
+ end
59
+ after_destroy do
60
+ Blamescope::Trackable.send_event(self, :delete)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,3 @@
1
+ module Blamescope
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ module Blamescope
4
+ describe Middleware do
5
+ describe "::new" do
6
+ it "should configure application" do
7
+ expect(Blamescope).to receive(:configure).with(:config)
8
+ Middleware.new(:app, :config)
9
+ end
10
+ end
11
+
12
+ describe "#call" do
13
+ it "should set environment from request" do
14
+ app = double('app')
15
+ expect(Thread).to receive_message_chain(:current, :[]=).with(:env, :env)
16
+ expect(app).to receive(:call).with(:env)
17
+ subject = Middleware.new(app)
18
+ subject.call(:env)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ module Blamescope
4
+ describe Server do
5
+ describe "::start" do
6
+ it "should initialize server if options provided" do
7
+ options = {database: 'db_url', rabbit: 'rabbit_url'}
8
+ expect(Server).to receive_message_chain(:new, :listen).with(options)
9
+ Server.start(options)
10
+ end
11
+ it "shouldn't initialize server if any option missing" do
12
+ expect(Server).to receive(:puts).twice
13
+ expect(Server).not_to receive(:new)
14
+ Server.start(database: 'db_url')
15
+ Server.start(rabbit: 'rabbit_url')
16
+ end
17
+ end
18
+
19
+ describe "#listen" do
20
+ it "should listen for events" do
21
+ subject = Server.new
22
+ queue = double('rabbit queue')
23
+ expect(queue).to receive(:subscribe).and_yield(:info, :properties,:body)
24
+ expect(subject).to receive(:rabbit_connection).with(:rabbit).and_return(queue: queue)
25
+ expect(subject).to receive(:postgres_connection).with(:database).and_return(:db)
26
+ expect(subject).to receive(:store_event).with(:db, :body)
27
+ subject.listen(rabbit: :rabbit, database: :database)
28
+ end
29
+
30
+ it "should close channel and connection on interrupt" do
31
+ subject = Server.new
32
+ queue = double('rabbit queue')
33
+ expect(queue).to receive(:subscribe).and_raise(Interrupt)
34
+ channel = double('rabbit channel')
35
+ expect(channel).to receive(:close)
36
+ conn = double('rabbit connection')
37
+ expect(conn).to receive(:close)
38
+ expect(subject).to receive(:rabbit_connection).with(:rabbit).and_return(queue: queue, channel: channel, conn: conn)
39
+ expect(subject).to receive(:postgres_connection).with(:database).and_return(:db)
40
+ subject.listen(rabbit: :rabbit, database: :database)
41
+ end
42
+ end
43
+ describe "#store_event" do
44
+ it "should store event" do
45
+ time = Time.now.utc
46
+ db = double('database connection')
47
+ expect(db).to receive(:exec_prepared).with('insert_event',
48
+ ['user@example.com', 'create', 'User', '{"id":1}', time])
49
+ subject = Server.new
50
+ subject.store_event(db, {email: 'user@example.com', action: 'create', model: 'User', attrs: {id: 1}}.to_json, time)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ class TestClass
4
+ include Blamescope
5
+
6
+ class << self
7
+ def static_method arg
8
+ 'static response'
9
+ end
10
+ blame_method :static_method
11
+ end
12
+
13
+ def create arg1, arg2
14
+ 'create result'
15
+ end
16
+ blame_method :create
17
+
18
+ def update arg1, &block
19
+ return 'block given' if block_given?
20
+ end
21
+ blame_method :update
22
+
23
+ def name
24
+ 'some name'
25
+ end
26
+ end
27
+
28
+ module Blamescope
29
+ describe Trackable do
30
+ describe "#blame_method" do
31
+ it "should work with multiple arguments" do
32
+ instance = TestClass.new
33
+ expect(Blamescope::Trackable).to receive(:send_event).with(instance, :create)
34
+ expect(instance).to receive(:original_create).with(:arg1,:arg2).and_call_original
35
+ expect(instance.create(:arg1,:arg2)).to eq('create result')
36
+ end
37
+
38
+ it "should work with block of code" do
39
+ instance = TestClass.new
40
+ expect(Blamescope::Trackable).to receive(:send_event).with(instance, :update)
41
+ expect(instance).to receive(:original_update).with(:arg1).and_call_original
42
+ expect(instance.update(:arg1){ block }).to eq('block given')
43
+ end
44
+ end
45
+
46
+ describe "::blame_method" do
47
+ it "should work with static methods" do
48
+ expect(Blamescope::Trackable).to receive(:send_event).with(TestClass, :static_method)
49
+ expect(TestClass).to receive(:original_static_method).with(:arg1).and_call_original
50
+ expect(TestClass.static_method(:arg1)).to eq('static response')
51
+ end
52
+ end
53
+
54
+ describe "#blame_methods" do
55
+ it "should invoke #track_method for each argument" do
56
+ expect(TestClass).to receive(:blame_method).with(:first)
57
+ expect(TestClass).to receive(:blame_method).with(:second)
58
+ TestClass.blame_methods :first, :second
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Blamescope do
4
+ describe "#configure" do
5
+ it "should create connection" do
6
+ expect(Blamescope).to receive(:rabbit_connection).with(:config).and_return(exchange: :exchange)
7
+ Blamescope.configure(rabbit: :config)
8
+ expect(Blamescope.exchange).to eq(:exchange)
9
+ end
10
+ end
11
+
12
+ describe "#user" do
13
+ it "should get user from warden key" do
14
+ warden = double('warden')
15
+ allow(warden).to receive_message_chain(:user, email: 'user@example.com')
16
+ Thread.current[:env] = {'warden' => warden }
17
+ expect(Blamescope.user).to eq('user@example.com')
18
+ end
19
+
20
+ it "should use system user if warden is not available" do
21
+ Thread.current[:env] = nil
22
+ expect(Blamescope.user).to eq("#{ENV['USER']}@#{Socket.gethostname}")
23
+ end
24
+ end
25
+
26
+ describe "#emit" do
27
+ it "should publish event" do
28
+ allow(Blamescope).to receive(:user).and_return('user@example.com')
29
+ expect(Blamescope).to receive_message_chain(:exchange, :publish).with({model:'USER', email: 'user@example.com'}.to_json)
30
+ Blamescope.emit(model: 'USER')
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ require "codeclimate-test-reporter"
2
+ CodeClimate::TestReporter.start
3
+
4
+ require 'blamescope'
5
+
6
+ # Configure RSpec
7
+ RSpec.configure do |config|
8
+ config.order = 'random'
9
+ end
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blamescope
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Volodymyr Ladnik
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bunny
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: codeclimate-test-reporter
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Find whom to blame for broken stuff
112
+ email:
113
+ - vladnik@demandbase.com
114
+ executables:
115
+ - blamescope
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - .gitignore
120
+ - .rspec
121
+ - .travis.yml
122
+ - Gemfile
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - bin/blamescope
127
+ - blamescope.gemspec
128
+ - lib/blamescope.rb
129
+ - lib/blamescope/middleware.rb
130
+ - lib/blamescope/server.rb
131
+ - lib/blamescope/trackable.rb
132
+ - lib/blamescope/version.rb
133
+ - spec/blamescope/middleware_spec.rb
134
+ - spec/blamescope/server_spec.rb
135
+ - spec/blamescope/trackable_spec.rb
136
+ - spec/blamescope_spec.rb
137
+ - spec/spec_helper.rb
138
+ homepage: https://github.com/Demandbase/blamescope
139
+ licenses:
140
+ - MIT
141
+ metadata: {}
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - '>='
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - '>='
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubyforge_project:
158
+ rubygems_version: 2.2.2
159
+ signing_key:
160
+ specification_version: 4
161
+ summary: Track events per user
162
+ test_files:
163
+ - spec/blamescope/middleware_spec.rb
164
+ - spec/blamescope/server_spec.rb
165
+ - spec/blamescope/trackable_spec.rb
166
+ - spec/blamescope_spec.rb
167
+ - spec/spec_helper.rb