userlist 0.4.1 → 0.5.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
2
  SHA256:
3
- metadata.gz: d654ea7675fae65eaef3bd08958b6e565ad8cee82ddf8c00d25dc647bb04b047
4
- data.tar.gz: 29ac435d2b7df8df2563d952c7c0f40557519e1fcd8fe035dbdd6bf61b420692
3
+ metadata.gz: a76254952c47cc9014e31019550491b96b1a8f46814cb6e44750ddf4c4d966f1
4
+ data.tar.gz: 17eabd156ef0d1eda09a7480c9eb3f5822098f045123d2a2d9f33351630493d6
5
5
  SHA512:
6
- metadata.gz: 6f725c5b9060d012eecf0bc99475687fb38df01c9111eb066a6cbdd4f3393b0aff1b0cd93d9c948c0b788f7ecdbc93f62efe525573c99ba95e8a5ee7aa3205ff
7
- data.tar.gz: ee7fdc2e800edad20bdda9753b447b6da599ea14588c98247750af0ab63f3eaf74c2c3d1233c21f8e09b8e8de3e796bb9cf0068344f633ba99059944ad3a0140
6
+ metadata.gz: 7d3311432655c8b7758d59733845558f526208874f31fee9034dbbb8a75bca653dc39b8b18007928b7fae7d2cf3efae449126c2c355bd6f3b3e5d0e82f2a24a7
7
+ data.tar.gz: 8531012b1ec1824c2ff60b537c641b17b534e60ea4665e476b66055f1f623ed51787c4a336402fb1adb63b1645e238008bb94ec64a2c7caf87cb21956d35111c
@@ -0,0 +1,21 @@
1
+ name: Tests
2
+ on: [push]
3
+ jobs:
4
+ build:
5
+ strategy:
6
+ matrix:
7
+ ruby:
8
+ - 2.4
9
+ - 2.5
10
+ - 2.6
11
+ - 2.7
12
+ - 3.0
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v2
16
+ - uses: ruby/setup-ruby@v1
17
+ with:
18
+ ruby-version: ${{ matrix.ruby }}
19
+ bundler-cache: true
20
+ - name: RSpec
21
+ run: bundle exec rake
@@ -1,5 +1,6 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.3
2
+ TargetRubyVersion: 2.4
3
+ NewCops: enable
3
4
  Exclude:
4
5
  - 'bin/*'
5
6
  - 'Guardfile'
data/Gemfile CHANGED
@@ -7,3 +7,5 @@ gemspec
7
7
  gem 'guard-rspec', '~> 4.7'
8
8
  gem 'guard-rubocop', '~> 1.3'
9
9
  gem 'rubocop', '~> 0.68'
10
+
11
+ gem 'sidekiq'
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # Userlist [![Build Status](https://travis-ci.com/userlistio/userlist-ruby.svg?branch=master)](https://travis-ci.com/userlistio/userlist-ruby)
1
+ # Userlist for Ruby [![Build Status](https://github.com/userlist/userlist-ruby/workflows/Tests/badge.svg)](https://github.com/userlist/userlist-ruby)
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/userlist`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ This gem helps with integrating [Userlist](https://userlist.com) into Ruby applications.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ > To integrate Userlist into a Ruby on Rails application, please use [userlist-rails](https://github.com/userlist/userlist-rails)
6
6
 
7
7
  ## Installation
8
8
 
@@ -20,9 +20,180 @@ Or install it yourself as:
20
20
 
21
21
  $ gem install userlist
22
22
 
23
+ ## Configuration
24
+
25
+ The only required configuration is the Push API key. You can get your Push API key via the [Push API settings](https://app.userlist.com/settings/push) in your Userlist account.
26
+
27
+ Configuration values can either be set via the `Userlist.configure` method or as environment variables. The environment variables take precedence over configuration values from the initializer.
28
+
29
+ Configuration via environment variables:
30
+
31
+ ```shell
32
+ USERLIST_PUSH_KEY=VvB7pjDrv0V2hoaOCeZ5rIiUEPbEhSUN
33
+ USERLIST_PUSH_ID=6vPkJl44cm82y4aLBIzaOhuEHJd0Bm7b
34
+ ```
35
+
36
+ Configuration via an initializer:
37
+
38
+ ```ruby
39
+ Userlist.configure do |config|
40
+ config.push_key = 'VvB7pjDrv0V2hoaOCeZ5rIiUEPbEhSUN'
41
+ config.push_id = '6vPkJl44cm82y4aLBIzaOhuEHJd0Bm7b'
42
+ end
43
+ ```
44
+
45
+ The possible configuration values are listed in the table below.
46
+
47
+ | Name | Default value | Description |
48
+ | ----------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
49
+ | `push_key` | `nil` | The push key for your account. See [Push API settings](https://app.userlist.com/settings/push). |
50
+ | `push_id` | `nil` | The push id for your account. See [Push API settings](https://app.userlist.com/settings/push). |
51
+ | `push_endpoint` | `https://push.userlist.com/` | The HTTP endpoint that the library will send data to. |
52
+ | `push_strategy` | `:threaded` | The strategy to use to send data to the HTTP endpoint. Possible values are `:threaded`, `:sidekiq`, `:direct`, and `:null`. |
53
+ | `push_strategy_options` | `{}` | Any additional options for the push strategy. |
54
+ | `log_level` | `:warn` | The log level for Userlist related log messages. Possible values are `:debug`, `:error`, `:fatal`, `:info`, and `:warn` |
55
+ | `token_lifetime` | `3600` | The lifetime of generated in-app messages tokens in seconds |
56
+
57
+ ### Disabling in development and test environments
58
+
59
+ As sending test and development data into data into Userlist isn't very desirable, you can disable transmissions by setting the push strategy to `:null`.
60
+
61
+ ```ruby
62
+ Userlist.configure do |config|
63
+ config.push_strategy = :null
64
+ end
65
+ ```
66
+
23
67
  ## Usage
24
68
 
25
- TODO: Write usage instructions here
69
+ This library is a wrapper for Userlist's Push API. For details about the accepted payloads, please check [its documentation](https://userlist.com/docs/getting-started/integration-guide/).
70
+
71
+ ### Tracking Users
72
+
73
+ To manually send user data into Userlist, use the `Userlist::Push.users.push` method.
74
+
75
+ ```ruby
76
+ Userlist::Push.users.push(
77
+ identifier: 'user-1',
78
+ email: 'foo@example.com',
79
+ properties: {
80
+ first_name: 'Foo',
81
+ last_name: 'Example'
82
+ }
83
+ )
84
+ ```
85
+
86
+ It's also possible to delete a user from Userlist, using the `Userlist::Push.users.delete` method.
87
+
88
+ ```ruby
89
+ Userlist::Push.users.delete('user-1')
90
+ ```
91
+
92
+ ### Tracking Companies
93
+
94
+ To manually send company data into Userlist, use the `Userlist::Push.companies.push` method.
95
+
96
+ ```ruby
97
+ Userlist::Push.companies.push(
98
+ identifier: 'company-1',
99
+ email: 'Example, Inc.',
100
+ properties: {
101
+ industry: 'Software Testing'
102
+ }
103
+ )
104
+ ```
105
+
106
+ It's also possible to delete a user from Userlist, using the `Userlist::Push.companies.delete` method.
107
+
108
+ ```ruby
109
+ Userlist::Push.companies.delete('user-1')
110
+ ```
111
+
112
+ ### Tracking Relationships
113
+
114
+ Tracking relationships can either be done using nested properties in user or company payloads or via the `Userlist::Push.relationships.push` method.
115
+
116
+ ```ruby
117
+ Userlist::Push.relationships.push(
118
+ user: 'user-1',
119
+ company: 'company-1',
120
+ properties: {
121
+ role: 'owner'
122
+ }
123
+ )
124
+ ```
125
+
126
+ This is equivalent to specifying the relationship on the user model.
127
+
128
+ ```ruby
129
+ Userlist::Push.users.push(
130
+ identifier: 'user-1',
131
+ relationships: [
132
+ {
133
+ company: 'company-1',
134
+ properties: {
135
+ role: 'owner'
136
+ }
137
+ }
138
+ ]
139
+ )
140
+ ```
141
+
142
+ It's also equivalent specifying the relationship on the company model.
143
+
144
+ ```ruby
145
+ Userlist::Push.companies.push(
146
+ identifier: 'company-1',
147
+ relationships: [
148
+ {
149
+ user: 'user-1',
150
+ properties: {
151
+ role: 'owner'
152
+ }
153
+ }
154
+ ]
155
+ )
156
+ ```
157
+
158
+ ### Tracking Events
159
+
160
+ To track custom events use the `Userlist::Push.events.push` method.
161
+
162
+ ```ruby
163
+ Userlist::Push.events.push(
164
+ name: 'project_created',
165
+ user: 'user-1',
166
+ properties: {
167
+ project_name: 'Example project'
168
+ }
169
+ )
170
+ ```
171
+
172
+ Instead of just sending a user or company identifier, it's also possible to expand the properties into objects. This will update the user / company record as well as trigger the event in one operation.
173
+
174
+ ```ruby
175
+ Userlist::Push.events.push(
176
+ name: 'project_created',
177
+ user: {
178
+ identifier: 'user-1',
179
+ properties: {
180
+ projects: 5
181
+ }
182
+ },
183
+ properties: {
184
+ project_name: 'Example project'
185
+ }
186
+ )
187
+ ```
188
+
189
+ ### Tokens for in-app messages
190
+
191
+ In order to use in-app messages, you must create a JWT token for the currently signed in user on the server side. To do this, please configure both the `push_key` and the `push_id` configuration variables. Afterwards, you can use the `Userlist::Token.generate` method to get a signed token for the given user identifier.
192
+
193
+ ```ruby
194
+ Userlist::Token.generate('user-1')
195
+ # => "eyJraWQiOiI2dlBrSmw0NGNtODJ5NGFMQkl6YU9odU...kPGe8KX8JZBTQ"
196
+ ```
26
197
 
27
198
  ## Development
28
199
 
@@ -32,7 +203,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
203
 
33
204
  ## Contributing
34
205
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/userlist. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
206
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/userlist/userlist-ruby>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
207
 
37
208
  ## License
38
209
 
@@ -40,4 +211,12 @@ The gem is available as open source under the terms of the [MIT License](http://
40
211
 
41
212
  ## Code of Conduct
42
213
 
43
- Everyone interacting in the Userlist project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/userlist/blob/master/CODE_OF_CONDUCT.md).
214
+ Everyone interacting in the Userlist project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/userlist/userlist-ruby/blob/master/CODE_OF_CONDUCT.md).
215
+
216
+ ## What is Userlist?
217
+
218
+ [![Userlist](https://userlist.com/images/external/userlist-logo-github.svg)](https://userlist.com/)
219
+
220
+ [Userlist](https://userlist.com/) allows you to onboard and engage your SaaS users with targeted behavior-based campaigns using email or in-app messages.
221
+
222
+ Userlist was started in 2017 as an alternative to bulky enterprise messaging tools. We believe that running SaaS products should be more enjoyable. Learn more [about us](https://userlist.com/about-us/).
@@ -8,16 +8,16 @@ require 'userlist/token'
8
8
 
9
9
  module Userlist
10
10
  class Error < StandardError; end
11
+
11
12
  class ArgumentError < Error; end
13
+
12
14
  class ConfigurationError < Error
13
15
  attr_reader :key
14
16
 
15
17
  def initialize(key)
16
18
  @key = key.to_sym
17
- end
18
19
 
19
- def message
20
- <<~MESSAGE
20
+ super <<~MESSAGE
21
21
  Missing required configuration value for `#{key}`
22
22
 
23
23
  Please set a value for `#{key}` using an environment variable:
@@ -40,7 +40,7 @@ module Userlist
40
40
 
41
41
  def logger
42
42
  @logger ||= begin
43
- logger = Logger.new(STDOUT)
43
+ logger = Logger.new($stdout)
44
44
  logger.progname = 'userlist'
45
45
  logger.level = Logger.const_get(config.log_level.to_s.upcase)
46
46
  logger
@@ -3,8 +3,9 @@ module Userlist
3
3
  DEFAULT_CONFIGURATION = {
4
4
  push_key: nil,
5
5
  push_id: nil,
6
- push_endpoint: 'https://push.userlist.io/',
6
+ push_endpoint: 'https://push.userlist.com/',
7
7
  push_strategy: :threaded,
8
+ push_strategy_options: {},
8
9
  log_level: :warn,
9
10
  token_lifetime: 3600
10
11
  }.freeze
@@ -2,6 +2,7 @@ require 'userlist/push/client'
2
2
  require 'userlist/push/strategies'
3
3
 
4
4
  require 'userlist/push/resource'
5
+ require 'userlist/push/resource_collection'
5
6
  require 'userlist/push/relation'
6
7
 
7
8
  require 'userlist/push/operations/create'
@@ -9,12 +10,15 @@ require 'userlist/push/operations/delete'
9
10
 
10
11
  require 'userlist/push/user'
11
12
  require 'userlist/push/company'
13
+ require 'userlist/push/relationship'
12
14
  require 'userlist/push/event'
13
15
 
16
+ require 'userlist/push/serializer'
17
+
14
18
  module Userlist
15
19
  class Push
16
20
  class << self
17
- [:event, :track, :user, :identify, :company, :users, :events, :companies].each do |method|
21
+ [:event, :track, :user, :identify, :company, :users, :events, :companies, :relationships].each do |method|
18
22
  define_method(method) { |*args| default_push_instance.send(method, *args) }
19
23
  end
20
24
 
@@ -44,6 +48,10 @@ module Userlist
44
48
  @companies ||= Relation.new(self, Company, [Operations::Create, Operations::Delete])
45
49
  end
46
50
 
51
+ def relationships
52
+ @relationships ||= Relation.new(self, Relationship, [Operations::Create, Operations::Delete])
53
+ end
54
+
47
55
  def event(payload = {})
48
56
  events.create(payload)
49
57
  end
@@ -53,7 +53,7 @@ module Userlist
53
53
  request['Accept'] = 'application/json'
54
54
  request['Authorization'] = "Push #{token}"
55
55
  request['Content-Type'] = 'application/json; charset=UTF-8'
56
- request.body = JSON.dump(payload) if payload
56
+ request.body = JSON.generate(payload) if payload
57
57
 
58
58
  logger.debug "Sending #{request.method} to #{URI.join(endpoint, request.path)} with body #{request.body}"
59
59
 
@@ -1,13 +1,20 @@
1
1
  module Userlist
2
2
  class Push
3
3
  class Company < Resource
4
+ include Operations::Create
5
+ include Operations::Delete
6
+
4
7
  def self.endpoint
5
8
  '/companies'
6
9
  end
7
10
 
8
- def initialize(attributes = {})
9
- raise Userlist::ArgumentError, 'Missing required attributes hash' unless attributes
10
- raise Userlist::ArgumentError, 'Missing required parameter :identifier' unless attributes[:identifier]
11
+ has_many :relationships, type: 'Userlist::Push::Relationship'
12
+ has_many :users, type: 'Userlist::Push::User'
13
+ has_one :user, type: 'Userlist::Push::User'
14
+
15
+ def initialize(payload = {}, config = Userlist.config)
16
+ raise Userlist::ArgumentError, 'Missing required payload hash' unless payload
17
+ raise Userlist::ArgumentError, 'Missing required parameter :identifier' unless payload[:identifier]
11
18
 
12
19
  super
13
20
  end
@@ -1,15 +1,26 @@
1
1
  module Userlist
2
2
  class Push
3
3
  class Event < Resource
4
- def initialize(attributes = {})
5
- raise Userlist::ArgumentError, 'Missing required attributes hash' unless attributes
6
- raise Userlist::ArgumentError, 'Missing required parameter :name' unless attributes[:name]
7
- raise Userlist::ArgumentError, 'Missing required parameter :user' unless attributes[:user]
4
+ include Operations::Create
8
5
 
9
- attributes[:occured_at] ||= Time.now
6
+ has_one :user, type: 'Userlist::Push::User'
7
+ has_one :company, type: 'Userlist::Push::Company'
8
+
9
+ def initialize(payload = {}, config = Userlist.config)
10
+ raise Userlist::ArgumentError, 'Missing required payload' unless payload
11
+ raise Userlist::ArgumentError, 'Missing required parameter :name' unless payload[:name]
12
+ raise Userlist::ArgumentError, 'Missing required parameter :user or :company' unless payload[:user] || payload[:company]
10
13
 
11
14
  super
12
15
  end
16
+
17
+ def occured_at
18
+ payload[:occured_at] || Time.now
19
+ end
20
+
21
+ def push?
22
+ (user.nil? || user.push?) && (company.nil? || company.push?)
23
+ end
13
24
  end
14
25
  end
15
26
  end
@@ -3,17 +3,22 @@ module Userlist
3
3
  module Operations
4
4
  module Create
5
5
  module ClassMethods
6
- def create(payload = {})
7
- resource = from_payload(payload)
8
- strategy.call(:post, endpoint, resource.attributes)
6
+ def create(payload = {}, config = self.config)
7
+ return false unless resource = from_payload(payload, config)
8
+
9
+ strategy.call(:post, endpoint, resource) if resource.create?
9
10
  end
10
11
 
11
12
  alias push create
12
13
  end
13
14
 
14
- def included(base)
15
+ def self.included(base)
15
16
  base.extend(ClassMethods)
16
17
  end
18
+
19
+ def create?
20
+ push?
21
+ end
17
22
  end
18
23
  end
19
24
  end
@@ -3,14 +3,20 @@ module Userlist
3
3
  module Operations
4
4
  module Delete
5
5
  module ClassMethods
6
- def delete(identifier)
7
- strategy.call(:delete, "#{endpoint}/#{identifier}")
6
+ def delete(payload = {}, config = self.config)
7
+ return false unless resource = from_payload(payload, config)
8
+
9
+ strategy.call(:delete, resource.url) if resource.delete?
8
10
  end
9
11
  end
10
12
 
11
- def included(base)
13
+ def self.included(base)
12
14
  base.extend(ClassMethods)
13
15
  end
16
+
17
+ def delete?
18
+ true
19
+ end
14
20
  end
15
21
  end
16
22
  end
@@ -10,10 +10,8 @@ module Userlist
10
10
 
11
11
  attr_reader :scope, :type
12
12
 
13
- private
14
-
15
- def from_payload(payload)
16
- type.new(payload)
13
+ def from_payload(payload, config = self.config)
14
+ type.from_payload(payload, config)
17
15
  end
18
16
 
19
17
  def endpoint
@@ -23,6 +21,10 @@ module Userlist
23
21
  def strategy
24
22
  scope.strategy
25
23
  end
24
+
25
+ def config
26
+ scope.config
27
+ end
26
28
  end
27
29
  end
28
30
  end
@@ -0,0 +1,29 @@
1
+ module Userlist
2
+ class Push
3
+ class Relationship < Resource
4
+ include Operations::Create
5
+ include Operations::Delete
6
+
7
+ has_one :user, type: 'Userlist::Push::User'
8
+ has_one :company, type: 'Userlist::Push::Company'
9
+
10
+ def initialize(payload = {}, config = Userlist.config)
11
+ raise Userlist::ArgumentError, 'Missing required payload' unless payload
12
+ raise Userlist::ArgumentError, 'Missing required parameter :user or :company' unless payload[:user] || payload[:company]
13
+
14
+ super
15
+ end
16
+
17
+ def url
18
+ raise Userlist::Error, "Cannot generate url for #{self.class.name} without a user" unless user
19
+ raise Userlist::Error, "Cannot generate url for #{self.class.name} without a company" unless company
20
+
21
+ "#{self.class.endpoint}/#{user.identifier}/#{company.identifier}"
22
+ end
23
+
24
+ def push?
25
+ user&.push? && company&.push?
26
+ end
27
+ end
28
+ end
29
+ end
@@ -9,18 +9,97 @@ module Userlist
9
9
  def endpoint
10
10
  "/#{resource_name.downcase}s"
11
11
  end
12
+
13
+ def from_payload(payload, config = Userlist.config)
14
+ return payload if payload.nil?
15
+ return payload if payload.is_a?(self)
16
+
17
+ payload = { identifier: payload } if payload.is_a?(String) || payload.is_a?(Numeric)
18
+
19
+ new(payload, config)
20
+ end
21
+
22
+ def relationship_names
23
+ @relationship_names ||= Set.new
24
+ end
25
+
26
+ protected
27
+
28
+ def has_one(name, type:) # rubocop:disable Naming/PredicateName
29
+ relationship_names << name.to_sym
30
+
31
+ generated_methods.class_eval <<-RUBY, __FILE__, __LINE__ + 1
32
+ def #{name}
33
+ #{type}.from_payload(payload[:#{name}], config)
34
+ end
35
+ RUBY
36
+ end
37
+
38
+ def has_many(name, type:) # rubocop:disable Naming/PredicateName
39
+ relationship_names << name.to_sym
40
+
41
+ generated_methods.class_eval <<-RUBY, __FILE__, __LINE__ + 1
42
+ def #{name}
43
+ ResourceCollection.new(payload[:#{name}], #{type}, config)
44
+ end
45
+ RUBY
46
+ end
47
+
48
+ private
49
+
50
+ def generated_methods
51
+ @generated_methods ||= Module.new.tap { |mod| include mod }
52
+ end
12
53
  end
13
54
 
14
- attr_reader :attributes
55
+ attr_reader :payload, :config
15
56
 
16
- def initialize(attributes = {})
17
- @attributes = attributes
57
+ def initialize(payload = {}, config = Userlist.config)
58
+ @payload = payload
59
+ @config = config
18
60
  end
19
61
 
20
62
  def respond_to_missing?(method, include_private = false)
21
63
  attribute = method.to_s.sub(/=$/, '')
64
+ payload.key?(attribute.to_sym) || super
65
+ end
66
+
67
+ def to_hash
68
+ Serializer.serialize(self)
69
+ end
70
+ alias to_h to_hash
71
+
72
+ def to_json(*args)
73
+ to_hash.to_json(*args)
74
+ end
75
+
76
+ def url
77
+ "#{self.class.endpoint}/#{identifier}"
78
+ end
79
+
80
+ def identifier
81
+ payload[:identifier]
82
+ end
83
+
84
+ def hash
85
+ self.class.hash & payload.hash
86
+ end
87
+
88
+ def eql?(other)
89
+ hash == other.hash
90
+ end
91
+ alias == eql?
92
+
93
+ def attribute_names
94
+ payload.keys.map(&:to_sym) - relationship_names
95
+ end
96
+
97
+ def relationship_names
98
+ self.class.relationship_names.to_a
99
+ end
22
100
 
23
- attributes.key?(attribute.to_sym) || super
101
+ def push?
102
+ true
24
103
  end
25
104
 
26
105
  private
@@ -28,9 +107,9 @@ module Userlist
28
107
  def method_missing(method, *args, &block)
29
108
  if method.to_s =~ /=$/
30
109
  attribute = method.to_s.sub(/=$/, '')
31
- attributes[attribute.to_sym] = args.first
32
- elsif attributes.key?(method.to_sym)
33
- attributes[method.to_sym]
110
+ payload[attribute.to_sym] = args.first
111
+ elsif payload.key?(method.to_sym)
112
+ payload[method.to_sym]
34
113
  else
35
114
  super
36
115
  end
@@ -0,0 +1,21 @@
1
+ module Userlist
2
+ class Push
3
+ class ResourceCollection
4
+ include Enumerable
5
+
6
+ attr_reader :collection, :type, :config
7
+
8
+ def initialize(collection, type, config = Userlist.config)
9
+ @collection = Array(collection)
10
+ @type = type
11
+ @config = config
12
+ end
13
+
14
+ def each
15
+ collection.each do |resource|
16
+ yield type.from_payload(resource, config)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,64 @@
1
+ module Userlist
2
+ class Push
3
+ class Serializer
4
+ def self.serialize(resource)
5
+ new.serialize(resource)
6
+ end
7
+
8
+ def serialize(resource)
9
+ resource = serialize_resource(resource) if resource.is_a?(Userlist::Push::Resource)
10
+ resource
11
+ end
12
+
13
+ private
14
+
15
+ def serialize_resource(resource)
16
+ return resource.identifier if serialized_resources.include?(resource)
17
+
18
+ serialized_resources << resource
19
+
20
+ return unless resource.push?
21
+
22
+ serialized = {}
23
+
24
+ resource.attribute_names.each do |name|
25
+ serialized[name] = resource.send(name)
26
+ end
27
+
28
+ resource.relationship_names.each do |name|
29
+ next unless result = serialize_relationship(resource.send(name))
30
+
31
+ serialized[name] = result
32
+ end
33
+
34
+ serialized
35
+ end
36
+
37
+ def serialize_relationship(relationship)
38
+ return unless relationship
39
+
40
+ case relationship
41
+ when Userlist::Push::ResourceCollection
42
+ serialize_collection(relationship)
43
+ when Userlist::Push::Resource
44
+ serialize_resource(relationship)
45
+ else
46
+ raise "Cannot serialize relationship type: #{relationship.class}"
47
+ end
48
+ end
49
+
50
+ def serialize_collection(collection)
51
+ serialized = collection
52
+ .map(&method(:serialize_relationship))
53
+ .compact
54
+ .reject(&:empty?)
55
+
56
+ serialized unless serialized.empty?
57
+ end
58
+
59
+ def serialized_resources
60
+ @serialized_resources ||= Set.new
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,17 +1,31 @@
1
- require 'userlist/push/strategies/null'
2
- require 'userlist/push/strategies/direct'
3
- require 'userlist/push/strategies/threaded'
4
-
5
1
  module Userlist
6
2
  class Push
7
3
  module Strategies
8
4
  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
-
5
+ strategy = lookup_strategy(strategy)
11
6
  strategy = strategy.new(config) if strategy.respond_to?(:new)
12
7
 
13
8
  strategy
14
9
  end
10
+
11
+ def self.lookup_strategy(strategy)
12
+ return strategy unless strategy.is_a?(Symbol) || strategy.is_a?(String)
13
+
14
+ require_strategy(strategy)
15
+ const_get(strategy.to_s.capitalize, false)
16
+ end
17
+
18
+ def self.strategy_defined?(strategy)
19
+ return true unless strategy.is_a?(Symbol) || strategy.is_a?(String)
20
+
21
+ const_defined?(strategy.to_s.capitalize, false)
22
+ end
23
+
24
+ def self.require_strategy(strategy)
25
+ return unless strategy.is_a?(Symbol) || strategy.is_a?(String)
26
+
27
+ require("userlist/push/strategies/#{strategy}") unless strategy_defined?(strategy)
28
+ end
15
29
  end
16
30
  end
17
31
  end
@@ -0,0 +1,37 @@
1
+ require 'sidekiq'
2
+
3
+ require 'userlist/push/strategies/sidekiq/worker'
4
+
5
+ module Userlist
6
+ class Push
7
+ module Strategies
8
+ class Sidekiq
9
+ def initialize(config = {})
10
+ @config = Userlist.config.merge(config)
11
+ end
12
+
13
+ def call(*args)
14
+ ::Sidekiq::Client.push(default_options.merge(options).merge('args' => args))
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :config
20
+
21
+ def options
22
+ @options ||= begin
23
+ options = config.push_strategy_options || {}
24
+ options.each_with_object({}) { |(k, v), h| h[k.to_s] = v }
25
+ end
26
+ end
27
+
28
+ def default_options
29
+ {
30
+ 'class' => 'Userlist::Push::Strategies::Sidekiq::Worker',
31
+ 'queue' => 'default'
32
+ }
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ require 'sidekiq'
2
+
3
+ module Userlist
4
+ class Push
5
+ module Strategies
6
+ class Sidekiq
7
+ class Worker
8
+ include ::Sidekiq::Worker
9
+
10
+ def perform(method, *args)
11
+ client = Userlist::Push::Client.new
12
+ client.public_send(method, *args)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,9 +1,16 @@
1
1
  module Userlist
2
2
  class Push
3
3
  class User < Resource
4
- def initialize(attributes = {})
5
- raise Userlist::ArgumentError, 'Missing required attributes hash' unless attributes
6
- raise Userlist::ArgumentError, 'Missing required parameter :identifier' unless attributes[:identifier]
4
+ include Operations::Create
5
+ include Operations::Delete
6
+
7
+ has_many :relationships, type: 'Userlist::Push::Relationship'
8
+ has_many :companies, type: 'Userlist::Push::Company'
9
+ has_one :company, type: 'Userlist::Push::Company'
10
+
11
+ def initialize(payload = {}, config = Userlist.config)
12
+ raise Userlist::ArgumentError, 'Missing required payload' unless payload
13
+ raise Userlist::ArgumentError, 'Missing required parameter :identifier' unless payload[:identifier]
7
14
 
8
15
  super
9
16
  end
@@ -1,11 +1,13 @@
1
1
  module Userlist
2
2
  class Token
3
- def self.generate(identifier, configuration = {})
3
+ def self.generate(user, configuration = {})
4
4
  config = Userlist.config.merge(configuration)
5
5
 
6
6
  raise Userlist::ConfigurationError, :push_key unless config.push_key
7
7
  raise Userlist::ConfigurationError, :push_id unless config.push_id
8
- raise Userlist::ArgumentError, 'Missing required identifier' unless identifier
8
+ raise Userlist::ArgumentError, 'Missing required user or identifier' unless user
9
+
10
+ user = Userlist::Push::User.from_payload(user, config)
9
11
 
10
12
  now = Time.now.utc.to_i
11
13
 
@@ -15,7 +17,7 @@ module Userlist
15
17
  }
16
18
 
17
19
  payload = {
18
- sub: identifier,
20
+ sub: user.identifier,
19
21
  exp: now + config.token_lifetime,
20
22
  iat: now
21
23
  }
@@ -1,3 +1,3 @@
1
1
  module Userlist
2
- VERSION = '0.4.1'.freeze
2
+ VERSION = '0.5.0'.freeze
3
3
  end
@@ -6,9 +6,9 @@ Gem::Specification.new do |spec|
6
6
  spec.name = 'userlist'
7
7
  spec.version = Userlist::VERSION
8
8
  spec.authors = ['Benedikt Deicke']
9
- spec.email = ['benedikt@userlist.io']
9
+ spec.email = ['benedikt@userlist.com']
10
10
 
11
- spec.summary = 'Ruby wrapper for the Userlist.io API'
11
+ spec.summary = 'Ruby wrapper for the Userlist API'
12
12
  spec.homepage = 'http://github.com/userlistio/userlist-ruby'
13
13
  spec.license = 'MIT'
14
14
 
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ['lib']
21
21
 
22
- spec.required_ruby_version = '>= 2.3'
22
+ spec.required_ruby_version = '>= 2.4'
23
23
 
24
24
  spec.add_development_dependency 'bundler', '>= 1.15'
25
25
  spec.add_development_dependency 'jwt', '~> 2.2'
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.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benedikt Deicke
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-16 00:00:00.000000000 Z
11
+ date: 2021-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -86,17 +86,17 @@ dependencies:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
88
  version: '1.22'
89
- description:
89
+ description:
90
90
  email:
91
- - benedikt@userlist.io
91
+ - benedikt@userlist.com
92
92
  executables: []
93
93
  extensions: []
94
94
  extra_rdoc_files: []
95
95
  files:
96
+ - ".github/workflows/test.yml"
96
97
  - ".gitignore"
97
98
  - ".rspec"
98
99
  - ".rubocop.yml"
99
- - ".travis.yml"
100
100
  - CODE_OF_CONDUCT.md
101
101
  - Gemfile
102
102
  - Guardfile
@@ -115,10 +115,15 @@ files:
115
115
  - lib/userlist/push/operations/create.rb
116
116
  - lib/userlist/push/operations/delete.rb
117
117
  - lib/userlist/push/relation.rb
118
+ - lib/userlist/push/relationship.rb
118
119
  - lib/userlist/push/resource.rb
120
+ - lib/userlist/push/resource_collection.rb
121
+ - lib/userlist/push/serializer.rb
119
122
  - lib/userlist/push/strategies.rb
120
123
  - lib/userlist/push/strategies/direct.rb
121
124
  - lib/userlist/push/strategies/null.rb
125
+ - lib/userlist/push/strategies/sidekiq.rb
126
+ - lib/userlist/push/strategies/sidekiq/worker.rb
122
127
  - lib/userlist/push/strategies/threaded.rb
123
128
  - lib/userlist/push/strategies/threaded/worker.rb
124
129
  - lib/userlist/push/user.rb
@@ -129,7 +134,7 @@ homepage: http://github.com/userlistio/userlist-ruby
129
134
  licenses:
130
135
  - MIT
131
136
  metadata: {}
132
- post_install_message:
137
+ post_install_message:
133
138
  rdoc_options: []
134
139
  require_paths:
135
140
  - lib
@@ -137,15 +142,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
137
142
  requirements:
138
143
  - - ">="
139
144
  - !ruby/object:Gem::Version
140
- version: '2.3'
145
+ version: '2.4'
141
146
  required_rubygems_version: !ruby/object:Gem::Requirement
142
147
  requirements:
143
148
  - - ">="
144
149
  - !ruby/object:Gem::Version
145
150
  version: '0'
146
151
  requirements: []
147
- rubygems_version: 3.0.4
148
- signing_key:
152
+ rubygems_version: 3.1.4
153
+ signing_key:
149
154
  specification_version: 4
150
- summary: Ruby wrapper for the Userlist.io API
155
+ summary: Ruby wrapper for the Userlist API
151
156
  test_files: []
@@ -1,7 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.3
4
- - 2.4
5
- - 2.5
6
- - 2.6
7
- - 2.7