userlist 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9534a94215f86a6e4da9add6729d0dfdc04f2d37
4
- data.tar.gz: 8d6a8b22aaeca9dc9ab99b4d4f567929c797da47
2
+ SHA256:
3
+ metadata.gz: e36d328f443fa566c4e77f6d76bf7c3eb279677447d3451a56813868eef365c6
4
+ data.tar.gz: 42a788537faafae0cc66a8dcfd037c2ef7b2ca3ab78918922738e40e0b7c67b0
5
5
  SHA512:
6
- metadata.gz: 60533319f67189405db69dc774ea8135408386d4305fdd82d1c0cc9c864a28f4620ab16a62947f571cfdcd52379a3fa3ee9d36c4d5033ce39cb3c0aa5d46fbbd
7
- data.tar.gz: 6b9870124cc409d969fbf0a5c374de2956184d7f47a5b8b1c9b79107af0753de8c3dff663c5b697941262f0fc2507c96a7335d77fc24a2a4a1397d453af0ca97
6
+ metadata.gz: 8ac7634b4f9a6059ee0c4c1c774be2d1b6d4ca2857eeceb4b4c6ef05ff7f070034780991cc196dc6f5ffe8f49f1f844a00f7ac614638f850d3e2e3a78ee644c8
7
+ data.tar.gz: 5a3154eeb7aa9368802961166c6fa89ab98c485d6c182d9a80d57d51edd0a09170da59237794d482fc5b1f470a19ce90f59ad5e297b864f6f633b028ea28fc3d
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.5
2
+ TargetRubyVersion: 2.2
3
3
  Exclude:
4
4
  - 'bin/*'
5
5
  - 'Guardfile'
@@ -1,39 +1,79 @@
1
1
  module Userlist
2
2
  class Config
3
3
  DEFAULT_CONFIGURATION = {
4
- push_key: nil,
5
- push_endpoint: 'https://push.userlist.io/'
4
+ push_key: nil,
5
+ push_endpoint: 'https://push.userlist.io/',
6
+ push_strategy: :threaded,
7
+ log_level: :warn
6
8
  }.freeze
7
9
 
8
10
  def initialize(config_from_initialize = {})
9
- @config = DEFAULT_CONFIGURATION
11
+ @config = default_config
10
12
  .merge(config_from_initialize)
11
13
  .merge(config_from_environment)
12
14
  end
13
15
 
14
- private
16
+ def self.inherit(parent, config_from_arguments)
17
+ config = allocate
18
+ config.instance_variable_set(:@parent, parent)
19
+ config.instance_variable_set(:@config, config_from_arguments.to_hash)
20
+ config
21
+ end
22
+
23
+ def merge(other_config)
24
+ self.class.inherit(self, other_config)
25
+ end
26
+
27
+ def to_hash
28
+ config
29
+ end
30
+
31
+ def to_h
32
+ parent ? parent.to_h.merge(config) : to_hash
33
+ end
34
+
35
+ def ==(other)
36
+ config == other.config && parent == other.parent
37
+ end
38
+
39
+ protected
15
40
 
16
- attr_reader :config
41
+ attr_reader :config, :parent
42
+
43
+ def default_config
44
+ DEFAULT_CONFIGURATION
45
+ end
17
46
 
18
47
  def config_from_environment
19
- DEFAULT_CONFIGURATION.keys.each_with_object({}) do |key, config|
48
+ default_config.keys.each_with_object({}) do |key, config|
20
49
  value = ENV["USERLIST_#{key.to_s.upcase}"]
21
50
  config[key] = value if value
22
51
  end
23
52
  end
24
53
 
25
- def respond_to_missing?(name)
54
+ def key?(key)
55
+ config.key?(key) || parent && parent.key?(key)
56
+ end
57
+
58
+ def [](key)
59
+ config[key] || parent && parent[key]
60
+ end
61
+
62
+ def []=(key, value)
63
+ config[key] = value
64
+ end
65
+
66
+ def respond_to_missing?(name, include_private = false)
26
67
  name = name.to_s.sub(/=$/, '')
27
- config.key?(name.to_sym)
68
+ key?(name.to_sym) || super
28
69
  end
29
70
 
30
71
  def method_missing(name, *args, &block)
31
- name = name.to_s
32
-
33
72
  if respond_to_missing?(name)
73
+ name = name.to_s
34
74
  method = name.match?(/=$/) ? :[]= : :[]
35
75
  name = name.sub(/=$/, '').to_sym
36
- config.public_send(method, name, *args, &block)
76
+ send(method, name, *args, &block)
37
77
  else
38
78
  super
39
79
  end
@@ -0,0 +1,11 @@
1
+ module Userlist
2
+ module Logging
3
+ def self.included(mod)
4
+ mod.send(:extend, self)
5
+ end
6
+
7
+ def logger
8
+ Userlist.logger
9
+ end
10
+ end
11
+ end
@@ -4,10 +4,12 @@ require 'net/http'
4
4
  require 'openssl'
5
5
 
6
6
  module Userlist
7
- module Push
7
+ class Push
8
8
  class Client
9
- def initialize(config = Userlist.config)
10
- @config = config
9
+ include Userlist::Logging
10
+
11
+ def initialize(config = {})
12
+ @config = Userlist.config.merge(config)
11
13
  end
12
14
 
13
15
  def post(endpoint, payload = {})
@@ -31,13 +33,15 @@ module Userlist
31
33
  end
32
34
  end
33
35
 
34
- def request(endpoint, payload = {})
35
- request = Net::HTTP::Post.new(endpoint)
36
+ def request(path, payload = {})
37
+ request = Net::HTTP::Post.new(path)
36
38
  request['Accept'] = 'application/json'
37
39
  request['Authorization'] = "Push #{token}"
38
40
  request['Content-Type'] = 'application/json; charset=UTF-8'
39
41
  request.body = JSON.dump(payload)
40
42
 
43
+ logger.debug "Sending #{request.method} to #{URI.join(endpoint, request.path)} with body #{request.body}"
44
+
41
45
  http.request(request)
42
46
  end
43
47
 
@@ -0,0 +1,23 @@
1
+ module Userlist
2
+ class Push
3
+ module Strategies
4
+ class Direct
5
+ def initialize(config = {})
6
+ @config = Userlist.config.merge(config)
7
+ end
8
+
9
+ def call(*args)
10
+ client.public_send(*args)
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :config
16
+
17
+ def client
18
+ @client ||= Userlist::Push::Client.new(config)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ module Userlist
2
+ class Push
3
+ module Strategies
4
+ class Null
5
+ def initialize(*); end
6
+
7
+ def call(*); end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,51 @@
1
+ module Userlist
2
+ class Push
3
+ module Strategies
4
+ class Threaded
5
+ class Worker
6
+ include Userlist::Logging
7
+
8
+ MAX_WORKER_TIMEOUT = 30
9
+
10
+ def initialize(queue, config = {})
11
+ @queue = queue
12
+ @config = Userlist.config.merge(config)
13
+ @thread = Thread.new { run }
14
+ @thread.abort_on_exception = true
15
+ end
16
+
17
+ def run
18
+ logger.info 'Starting worker thread...'
19
+
20
+ loop do
21
+ begin
22
+ method, url, payload = *queue.pop
23
+ break if method == :stop
24
+
25
+ client.public_send(method, url, payload)
26
+ rescue StandardError => exception
27
+ logger.error "Failed to deliver payload: [#{exception.class.name}] #{exception.message}"
28
+ end
29
+ end
30
+
31
+ logger.info "Worker thread exited with #{queue.size} tasks still in the queue..."
32
+ end
33
+
34
+ def stop
35
+ logger.info 'Stopping worker thread...'
36
+ queue.push([:stop])
37
+ thread.join(MAX_WORKER_TIMEOUT)
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :queue, :config, :thread
43
+
44
+ def client
45
+ @client ||= Userlist::Push::Client.new(config)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,28 @@
1
+ require 'userlist/push/strategies/threaded/worker'
2
+
3
+ module Userlist
4
+ class Push
5
+ module Strategies
6
+ class Threaded
7
+ def initialize(config = {})
8
+ @queue = Queue.new
9
+ @worker = Worker.new(queue, config)
10
+
11
+ at_exit { stop_worker }
12
+ end
13
+
14
+ def call(*args)
15
+ queue.push(args)
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :queue, :worker
21
+
22
+ def stop_worker
23
+ worker && worker.stop
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ require 'userlist/push/strategies/null'
2
+ require 'userlist/push/strategies/direct'
3
+ require 'userlist/push/strategies/threaded'
4
+
5
+ module Userlist
6
+ class Push
7
+ module Strategies
8
+ def self.strategy_for(strategy, config = {})
9
+ strategy = Userlist::Push::Strategies.const_get(strategy.to_s.capitalize) if strategy.is_a?(Symbol) || strategy.is_a?(String)
10
+
11
+ strategy = strategy.new(config) if strategy.respond_to?(:new)
12
+
13
+ strategy
14
+ end
15
+ end
16
+ end
17
+ end
data/lib/userlist/push.rb CHANGED
@@ -1,6 +1,67 @@
1
1
  require 'userlist/push/client'
2
+ require 'userlist/push/strategies'
2
3
 
3
4
  module Userlist
4
- module Push
5
+ class Push
6
+ class << self
7
+ [:event, :track, :user, :identify, :company].each do |method|
8
+ define_method(method) { |*args| default_push_instance.send(method, *args) }
9
+ end
10
+
11
+ private
12
+
13
+ def default_push_instance
14
+ @default_push_instance ||= new
15
+ end
16
+ end
17
+
18
+ def initialize(config = {})
19
+ @config = Userlist.config.merge(config)
20
+ @mutex = Mutex.new
21
+ end
22
+
23
+ def event(payload = {})
24
+ with_mutex do
25
+ raise ArgumentError, 'Missing required payload hash' unless payload
26
+ raise ArgumentError, 'Missing required parameter :name' unless payload[:name]
27
+ raise ArgumentError, 'Missing required parameter :user' unless payload[:user]
28
+
29
+ payload[:occured_at] ||= Time.now
30
+
31
+ strategy.call(:post, '/events', payload)
32
+ end
33
+ end
34
+ alias track event
35
+
36
+ def user(payload = {})
37
+ with_mutex do
38
+ raise ArgumentError, 'Missing required payload hash' unless payload
39
+ raise ArgumentError, 'Missing required parameter :identifier' unless payload[:identifier]
40
+
41
+ strategy.call(:post, '/users', payload)
42
+ end
43
+ end
44
+ alias identify user
45
+
46
+ def company(payload = {})
47
+ with_mutex do
48
+ raise ArgumentError, 'Missing required payload hash' unless payload
49
+ raise ArgumentError, 'Missing required parameter :identifier' unless payload[:identifier]
50
+
51
+ strategy.call(:post, '/companies', payload)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ attr_reader :config
58
+
59
+ def strategy
60
+ @strategy ||= Userlist::Push::Strategies.strategy_for(config.push_strategy, config)
61
+ end
62
+
63
+ def with_mutex(&block)
64
+ @mutex.synchronize(&block)
65
+ end
5
66
  end
6
67
  end
@@ -1,3 +1,3 @@
1
1
  module Userlist
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
data/lib/userlist.rb CHANGED
@@ -1,5 +1,8 @@
1
+ require 'logger'
2
+
1
3
  require 'userlist/version'
2
4
  require 'userlist/config'
5
+ require 'userlist/logging'
3
6
  require 'userlist/push'
4
7
 
5
8
  module Userlist
@@ -7,5 +10,20 @@ module Userlist
7
10
  def config
8
11
  @config ||= Userlist::Config.new
9
12
  end
13
+
14
+ def logger
15
+ @logger ||= begin
16
+ logger = Logger.new(STDOUT)
17
+ logger.progname = 'userlist'
18
+ logger.level = config.log_level
19
+ logger
20
+ end
21
+ end
22
+
23
+ def configure
24
+ yield config
25
+ end
26
+
27
+ attr_writer :logger
10
28
  end
11
29
  end
data/userlist.gemspec CHANGED
@@ -1,5 +1,4 @@
1
-
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'userlist/version'
5
4
 
@@ -20,6 +19,8 @@ Gem::Specification.new do |spec|
20
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
20
  spec.require_paths = ['lib']
22
21
 
22
+ spec.required_ruby_version = '>= 2.1'
23
+
23
24
  spec.add_development_dependency 'bundler', '~> 1.15'
24
25
  spec.add_development_dependency 'rake', '~> 10.0'
25
26
  spec.add_development_dependency 'rspec', '~> 3.0'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: userlist
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benedikt Deicke
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-01-18 00:00:00.000000000 Z
11
+ date: 2018-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -87,8 +87,14 @@ files:
87
87
  - bin/setup
88
88
  - lib/userlist.rb
89
89
  - lib/userlist/config.rb
90
+ - lib/userlist/logging.rb
90
91
  - lib/userlist/push.rb
91
92
  - lib/userlist/push/client.rb
93
+ - lib/userlist/push/strategies.rb
94
+ - lib/userlist/push/strategies/direct.rb
95
+ - lib/userlist/push/strategies/null.rb
96
+ - lib/userlist/push/strategies/threaded.rb
97
+ - lib/userlist/push/strategies/threaded/worker.rb
92
98
  - lib/userlist/version.rb
93
99
  - userlist.gemspec
94
100
  homepage: http://github.com/userlistio/userlist-ruby
@@ -103,7 +109,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
103
109
  requirements:
104
110
  - - ">="
105
111
  - !ruby/object:Gem::Version
106
- version: '0'
112
+ version: '2.1'
107
113
  required_rubygems_version: !ruby/object:Gem::Requirement
108
114
  requirements:
109
115
  - - ">="
@@ -111,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
117
  version: '0'
112
118
  requirements: []
113
119
  rubyforge_project:
114
- rubygems_version: 2.6.11
120
+ rubygems_version: 2.7.3
115
121
  signing_key:
116
122
  specification_version: 4
117
123
  summary: Ruby wrapper for the Userlist.io API