rumor 0.1.0

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,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rumor.gemspec
4
+ gemspec
@@ -0,0 +1,5 @@
1
+ guard 'minitest' do
2
+ watch(%r|^spec/(.*)_spec\.rb|)
3
+ watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
4
+ watch(%r|^spec/spec_helper\.rb|) { "spec" }
5
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Mattias Putman
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,61 @@
1
+ # Rumor
2
+
3
+ All event processing is done asynchronously. Different adapters can be used to handle the processing (currently only Resque, default).
4
+
5
+ ## Usage
6
+
7
+ ### Let those controllers rumor
8
+
9
+ **First**
10
+
11
+ Add rumor capabilities to your controller by adding `include Rumor::Source`.
12
+
13
+ **Then**
14
+
15
+ Add rumor instructions in your controller methods.
16
+
17
+ ```ruby
18
+ # Rumor about an event.
19
+ rumor(:upgrade).on('john').mention(plan: plan.id).tag(:important).spread
20
+ # Rumor only to certain channels.
21
+ # Rumor whatever you want.
22
+ rumor(:profit).tag(:finally).spread only: [:mixpanel]
23
+ ```
24
+
25
+ **Where does the rumor come from?**
26
+
27
+ The `rumor` method is a regular method defined in your controller (defined by including `Source`). You can use it to add default values to your rumors.
28
+
29
+ ```ruby
30
+ class AccountController
31
+ include Rumor::Source
32
+
33
+ def rumor event
34
+ super(event).on(current_user.id).tag(:accounts)
35
+ end
36
+ end
37
+ ```
38
+
39
+ ### Adding Rumor Channels
40
+
41
+ ```ruby
42
+ class MixpanelChannel < Rumor::Channel
43
+
44
+ # This is just a regular class.
45
+ def initialize tracker
46
+ @tracker = tracker
47
+ end
48
+
49
+ # Matches only upgrade events with the important tag.
50
+ on(:upgrade) do |rumor|
51
+ rumor.subject # => 'john'
52
+ rumor.tag # => [:important]
53
+ plan = Plan.find rumor.mentions[:plan]
54
+ @tracker.track 'Upgraded Account', to: plan.name
55
+ end
56
+ end
57
+
58
+ # Register the channel.
59
+ tracker = Mixpanel::Tracker.new Environment::MIXPANEL_TOKEN,
60
+ Rumor.register :mixpanel, MixpanelChannel.new tracker
61
+ ```
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,30 @@
1
+ require "rumor/version"
2
+ require 'rumor/channel'
3
+ require 'rumor/rumor'
4
+ require 'rumor/source'
5
+
6
+ module Rumor
7
+ class << self
8
+ attr_accessor :channels
9
+ attr_accessor :async_handler
10
+ end
11
+
12
+ @channels = {}
13
+
14
+ # Internal: Register a new channel.
15
+ def self.register name, channel
16
+ self.channels[name] = channel
17
+ end
18
+
19
+ # Internal: Spread a rumor to required channels.
20
+ def self.spread rumor
21
+ self.channels.each do |name, channel|
22
+ channel.send rumor if rumor.to?(name)
23
+ end
24
+ end
25
+
26
+ # Internal: Spread a rumor asynchronously.
27
+ def self.spread_async rumor
28
+ self.async_handler.spread_async rumor
29
+ end
30
+ end
@@ -0,0 +1,38 @@
1
+ require 'resque'
2
+
3
+ module Rumor
4
+ module Async
5
+
6
+ class Resque
7
+
8
+ def self.spread_async rumor
9
+ ::Resque.enqueue Job, rumor.to_h
10
+ end
11
+
12
+ class Job
13
+ @queue = :rumor
14
+
15
+ def self.perform rumor_hash
16
+ hash = hash_to_symbols rumor_hash
17
+ hash[:mentions] = hash_to_symbols hash[:mentions]
18
+ hash[:tags].map! &:to_sym
19
+ hash[:time] = Time.new hash[:time]
20
+ # Deserialize the rumor.
21
+ rumor = Rumor.from_h hash
22
+ # Spread again.
23
+ ::Rumor.spread rumor
24
+ end
25
+
26
+ def self.hash_to_symbols hash
27
+ symbol_hash = {}
28
+ hash.each do |k,v|
29
+ symbol_hash[k.to_sym] = v
30
+ end
31
+ symbol_hash
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ Rumor.async_handler = Rumor::Async::Resque
@@ -0,0 +1,27 @@
1
+ module Rumor
2
+
3
+ # Public: A Channel is a module that can be included in any class.
4
+ #
5
+ # It provides an 'on' method for matching rumors by event.
6
+ class Channel
7
+ class << self
8
+ attr_accessor :handlers
9
+ end
10
+
11
+ def self.inherited klass
12
+ klass.handlers = {}
13
+ end
14
+
15
+ # Internal: Send a Rumor to this channel.
16
+ def send rumor
17
+ handle = self.class.handlers[rumor.event.to_sym]
18
+ raise "No handler for event #{rumor.event}" unless handle
19
+ self.instance_exec rumor, &handle
20
+ end
21
+
22
+ # Public: Catch all events with specified name.
23
+ def self.on event, &handle
24
+ self.handlers[event] = handle
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,119 @@
1
+ module Rumor
2
+
3
+ # Public: A Rumor represents some knowledge about
4
+ # something that can be spread.
5
+ #
6
+ # Examples
7
+ #
8
+ # Rumor.new(:upgraded).on('user1').mention(plan: :tasty).tag(:business).spread
9
+ #
10
+ class Rumor
11
+
12
+ # Public: A rumor has an event name.
13
+ # this is required for every rumor.
14
+ attr_accessor :event
15
+
16
+ # Public: Wo the rumor is about
17
+ # This is mostly the user that executed the event.
18
+ attr_accessor :subject
19
+
20
+ # Public: The mentions of the rumor.
21
+ # All the information a rumor mentions.
22
+ attr_accessor :mentions
23
+
24
+ # Public: Every rumor has some tags.
25
+ # A Rumor can be categorized in multiple optional tags.
26
+ attr_accessor :tags
27
+
28
+ # Public: Create Rumor from hash.
29
+ def self.from_h hash
30
+ self.new(hash[:event], hash[:time]).
31
+ mention(hash[:mentions]).
32
+ on(hash[:subject]).
33
+ tag(*hash[:tags])
34
+ end
35
+
36
+ # Public: Creates a new rumor.
37
+ #
38
+ # event - event of the rumor.
39
+ def initialize event, time = nil
40
+ @event = event
41
+ @tags = []
42
+ @mentions = {}
43
+ @time = time
44
+ end
45
+
46
+ # Public: Tell who/what the rumor is concerning.
47
+ def on subject
48
+ @subject = subject
49
+ self
50
+ end
51
+
52
+ # Public: Mention some things in the rumor.
53
+ # Merges with already mentioned information.
54
+ def mention mentions = {}
55
+ @mentions.merge!(mentions) do |key, old_val, new_val|
56
+ if old.kind_of?(Array)
57
+ old_val + new_val
58
+ elsif old.kind_of?(Hash)
59
+ old_val.merge new_val
60
+ else
61
+ new_val
62
+ end
63
+ end
64
+ self
65
+ end
66
+
67
+ # Public: Add some tags to the rumor.
68
+ def tag *tags
69
+ @tags += tags
70
+ self
71
+ end
72
+
73
+ # Public: Copy a rumor while altering information.
74
+ def copy &alter
75
+ Rumor.new(hash).tap &alter
76
+ end
77
+
78
+ # Spread the rumor to all applicable channels.
79
+ #
80
+ # conditions - some conditions on the spreading (Hash).
81
+ # :only - The channels to spread to (and none other).
82
+ # :except - The channels not to spread to.
83
+ #
84
+ # Returns nothing.
85
+ def spread conditions = {}
86
+ @time = Time.now.utc
87
+ @only = conditions[:only]
88
+ @except = conditions[:except]
89
+ ::Rumor.spread_async self
90
+ end
91
+
92
+ # Public: The time the rumor was spread.
93
+ def time
94
+ @time
95
+ end
96
+
97
+ # Public: Whether to send this rumor to the given channel.
98
+ #
99
+ # channel - Name of the channel.
100
+ def to? channel
101
+ (!@only && !@except) ||
102
+ (@only && @only.include?(channel)) ||
103
+ (@except && !@except.include?(channel))
104
+ end
105
+
106
+ # Public: Rumor in hash form.
107
+ #
108
+ # Returns the rumor converted to a Hash.
109
+ def to_h
110
+ {
111
+ event: event,
112
+ subject: subject,
113
+ mentions: mentions,
114
+ tags: tags,
115
+ time: time
116
+ }
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,31 @@
1
+ module Rumor
2
+
3
+ # Public: Include this module everywhere you want to be
4
+ # able to rumor. This gives you the benefit that the rumor
5
+ # method can easily be overrided.
6
+ #
7
+ # Exampe:
8
+ #
9
+ # class Controller
10
+ # include Rumor::Rumoring
11
+ #
12
+ # def rumor subject
13
+ # super.on(current_user)
14
+ # end
15
+ #
16
+ # def upgrade
17
+ # # some code
18
+ # rumor(:upgraded).mention(plan: :basic).spread only: :kissmetrics
19
+ # end
20
+ # end
21
+ #
22
+ module Source
23
+
24
+ # Public: Acts as a specialized factory for rumors.
25
+ #
26
+ # Returns a new Rumor.
27
+ def rumor event
28
+ Rumor.new(event)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module Rumor
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rumor/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "rumor"
8
+ gem.version = Rumor::VERSION
9
+ gem.authors = ["Mattias Putman"]
10
+ gem.email = ["mattias.putman@gmail.com"]
11
+ gem.description = %q{Rumor}
12
+ gem.summary = %q{Rumor}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency 'resque'
21
+ gem.add_development_dependency 'rake'
22
+ gem.add_development_dependency 'guard'
23
+ gem.add_development_dependency 'guard-minitest'
24
+ gem.add_development_dependency 'mocha'
25
+ gem.add_development_dependency 'rb-fsevent', '~> 0.9'
26
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ class TestChannel < MiniTest::Unit::TestCase
4
+
5
+ class ExampleChannel < Rumor::Channel
6
+ def helper
7
+ "helper"
8
+ end
9
+ end
10
+
11
+ def setup
12
+ @channel = ExampleChannel.new
13
+ end
14
+
15
+ def test_define_handler
16
+ assert_equal ExampleChannel.handlers, {}
17
+ handle = proc {}
18
+ ExampleChannel.on(:upgrade, &handle)
19
+ assert_equal ExampleChannel.handlers[:upgrade], handle
20
+ end
21
+
22
+ def test_send_rumor
23
+ upgrade = proc {}
24
+ install = proc { |rumor| @rumor = rumor }
25
+ ExampleChannel.on(:upgrade, &upgrade)
26
+ ExampleChannel.on(:install, &install)
27
+
28
+ @channel.send Rumor::Rumor.new(:install).
29
+ tag(:business).
30
+ on(:cool_user).
31
+ mention(plan: :enterprise)
32
+
33
+ rumor = @channel.instance_variable_get :@rumor
34
+ assert rumor.event == :install && rumor.tags == [:business] &&
35
+ rumor.mentions == { plan: :enterprise } && rumor.subject == :cool_user
36
+ end
37
+
38
+ def test_use_methods
39
+ ExampleChannel.on(:method_test) do |rumor|
40
+ helper
41
+ end
42
+ @channel.expects(:helper).once
43
+ @channel.send Rumor::Rumor.new(:method_test)
44
+ end
45
+
46
+ def teardown
47
+ ExampleChannel.handlers = {}
48
+ end
49
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ class TestRumor < MiniTest::Unit::TestCase
4
+ include Rumor::Source
5
+
6
+ class ExampleChannel < Rumor::Channel
7
+ end
8
+
9
+ class IntegrationChannel < Rumor::Channel
10
+ def initialize delegate
11
+ @delegate = delegate
12
+ end
13
+
14
+ on(:upgrade) do |rumor|
15
+ @delegate.upgrade
16
+ end
17
+
18
+ on(:install) do |rumor|
19
+ @delegate.install
20
+ end
21
+ end
22
+
23
+ def setup
24
+ @channel = ExampleChannel.new
25
+ @rumor = Rumor::Rumor.new(:upgrade).mention price: 8
26
+ end
27
+
28
+ def test_channels_initialized
29
+ assert_equal Rumor.channels, {}
30
+ end
31
+
32
+ def test_add_channel
33
+ Rumor.register :example, @channel
34
+ assert_equal Rumor.channels[:example], @channel
35
+ end
36
+
37
+ def test_spread_both
38
+ left, right = ExampleChannel.new, ExampleChannel.new
39
+ Rumor.register :left, left
40
+ Rumor.register :right, right
41
+ left.expects(:send).with(@rumor).once
42
+ right.expects(:send).with(@rumor).once
43
+ Rumor.spread @rumor
44
+ end
45
+
46
+ def test_spread_filter
47
+ left, right = ExampleChannel.new, ExampleChannel.new
48
+ @rumor.stubs(:to?).with(:left).returns true
49
+ @rumor.stubs(:to?).with(:right).returns false
50
+ Rumor.register :left, left
51
+ Rumor.register :right, right
52
+ left.expects(:send).with(@rumor).once
53
+ right.expects(:send).with(@rumor).never
54
+ Rumor.spread @rumor
55
+ end
56
+
57
+ def test_spread_async
58
+ Rumor::Async::Resque.expects(:spread_async).with(@rumor).once
59
+ Rumor.spread_async @rumor
60
+ end
61
+
62
+ def test_integration
63
+ channel1 = mock
64
+ Rumor.register :channel1, IntegrationChannel.new(channel1)
65
+ channel1.expects(:upgrade)
66
+ rumor(:upgrade).mention(plan: :enterprise).spread
67
+ end
68
+
69
+ def teardown
70
+ Rumor.channels = {}
71
+ end
72
+ end
@@ -0,0 +1,7 @@
1
+ require 'minitest/autorun'
2
+ require 'mocha/setup'
3
+
4
+ require 'rumor'
5
+ require 'rumor/async/resque'
6
+
7
+ Resque.inline = true
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rumor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mattias Putman
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: resque
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: guard
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: guard-minitest
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: mocha
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rb-fsevent
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '0.9'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '0.9'
110
+ description: Rumor
111
+ email:
112
+ - mattias.putman@gmail.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - .gitignore
118
+ - Gemfile
119
+ - Guardfile
120
+ - LICENSE.txt
121
+ - README.md
122
+ - Rakefile
123
+ - lib/rumor.rb
124
+ - lib/rumor/async/resque.rb
125
+ - lib/rumor/channel.rb
126
+ - lib/rumor/rumor.rb
127
+ - lib/rumor/source.rb
128
+ - lib/rumor/version.rb
129
+ - rumor.gemspec
130
+ - spec/rumor/channel_spec.rb
131
+ - spec/rumor_spec.rb
132
+ - spec/spec_helper.rb
133
+ homepage: ''
134
+ licenses: []
135
+ post_install_message:
136
+ rdoc_options: []
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ none: false
141
+ requirements:
142
+ - - ! '>='
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ none: false
147
+ requirements:
148
+ - - ! '>='
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubyforge_project:
153
+ rubygems_version: 1.8.19
154
+ signing_key:
155
+ specification_version: 3
156
+ summary: Rumor
157
+ test_files:
158
+ - spec/rumor/channel_spec.rb
159
+ - spec/rumor_spec.rb
160
+ - spec/spec_helper.rb
161
+ has_rdoc: