userlist 0.4.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c8436f1e9a2b5dff59fcceedbe0ce2d334c8288ce231eb0db7da388881efb099
4
- data.tar.gz: 882e26f3f57562b399c43ad9e47aa16f008e1593042d490ea2b1f0611239347d
3
+ metadata.gz: 8e20da18840923e5ac9df604853c38b2ffe949d3dea840eb6a9ff10889bb12b4
4
+ data.tar.gz: eebd30d5ac3e947cdddc60200666e6912e467940e534c6c1e9c80d1995f309b0
5
5
  SHA512:
6
- metadata.gz: 3224339ad8347c83f530907f0a968b0e245d1082c4337becc9be5395437cf39ef6370195e9fcf7a0361d60c34f6acf112a28ca43a3ed202af157bcd192ed0e68
7
- data.tar.gz: 433c0b6ee940f418f2d4de5ed6dd5cf161f1cc4f10c68a938106591e5518e8e653697cc8b625f3cee591082b528b914d937c01d331ad053cfc6ddfed90d808f5
6
+ metadata.gz: 3dbc899903623868bb8b595f02cf38949de23260756134a80c94518dbae826ffdd41ea2c3895201e42e96827f4152157c3518ffee00cd9528e675d2adb1788c0
7
+ data.tar.gz: 76e7cbd7847f1b5d48468529314198fa813df674bbc40d4d4eaa41f6269b54234bd9be6c850b291ab12250e5b9da8b26082a07b7511d3956c3c96ffde757e996
@@ -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
data/.rubocop.yml CHANGED
@@ -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/).
@@ -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
@@ -62,7 +63,7 @@ module Userlist
62
63
  end
63
64
 
64
65
  def [](key)
65
- config[key] || parent && parent[key]
66
+ config.key?(key) ? config[key] : parent && parent[key]
66
67
  end
67
68
 
68
69
  def []=(key, value)
@@ -11,8 +11,8 @@ module Userlist
11
11
  def initialize(config = {})
12
12
  @config = Userlist.config.merge(config)
13
13
 
14
- raise Userlist::ConfigurationError, :push_key unless config.push_key
15
- raise Userlist::ConfigurationError, :push_endpoint unless config.push_endpoint
14
+ raise Userlist::ConfigurationError, :push_key unless @config.push_key
15
+ raise Userlist::ConfigurationError, :push_endpoint unless @config.push_endpoint
16
16
  end
17
17
 
18
18
  def get(endpoint)
@@ -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', inverse: :company
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,30 @@
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' unless payload[:user]
13
+ raise Userlist::ArgumentError, 'Missing required parameter :company' unless payload[:company]
14
+
15
+ super
16
+ end
17
+
18
+ def url
19
+ raise Userlist::Error, "Cannot generate url for #{self.class.name} without a user" unless user
20
+ raise Userlist::Error, "Cannot generate url for #{self.class.name} without a company" unless company
21
+
22
+ "#{self.class.endpoint}/#{user.identifier}/#{company.identifier}"
23
+ end
24
+
25
+ def push?
26
+ user&.push? && company&.push?
27
+ end
28
+ end
29
+ end
30
+ end
@@ -9,18 +9,103 @@ 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 ||= relationships.keys
24
+ end
25
+
26
+ def relationships
27
+ @relationships ||= {}
28
+ end
29
+
30
+ protected
31
+
32
+ def has_one(name, type:) # rubocop:disable Naming/PredicateName
33
+ relationships[name.to_sym] = { type: type }
34
+
35
+ generated_methods.class_eval <<-RUBY, __FILE__, __LINE__ + 1
36
+ def #{name}
37
+ #{type}.from_payload(payload[:#{name}], config)
38
+ end
39
+ RUBY
40
+ end
41
+
42
+ def has_many(name, **options) # rubocop:disable Naming/PredicateName
43
+ relationships[name.to_sym] = options
44
+
45
+ generated_methods.class_eval <<-RUBY, __FILE__, __LINE__ + 1
46
+ def #{name}
47
+ relationship = self.class.relationships[:#{name}]
48
+
49
+ ResourceCollection.new(payload[:#{name}], relationship, self, config)
50
+ end
51
+ RUBY
52
+ end
53
+
54
+ private
55
+
56
+ def generated_methods
57
+ @generated_methods ||= Module.new.tap { |mod| include mod }
58
+ end
12
59
  end
13
60
 
14
- attr_reader :attributes
61
+ attr_reader :payload, :config
15
62
 
16
- def initialize(attributes = {})
17
- @attributes = attributes
63
+ def initialize(payload = {}, config = Userlist.config)
64
+ @payload = payload
65
+ @config = config
18
66
  end
19
67
 
20
68
  def respond_to_missing?(method, include_private = false)
21
69
  attribute = method.to_s.sub(/=$/, '')
70
+ payload.key?(attribute.to_sym) || super
71
+ end
72
+
73
+ def to_hash
74
+ Serializer.serialize(self)
75
+ end
76
+ alias to_h to_hash
77
+
78
+ def to_json(*args)
79
+ to_hash.to_json(*args)
80
+ end
81
+
82
+ def url
83
+ "#{self.class.endpoint}/#{identifier}"
84
+ end
85
+
86
+ def identifier
87
+ payload[:identifier]
88
+ end
89
+
90
+ def hash
91
+ self.class.hash & payload.hash
92
+ end
93
+
94
+ def eql?(other)
95
+ hash == other.hash
96
+ end
97
+ alias == eql?
98
+
99
+ def attribute_names
100
+ payload.keys.map(&:to_sym) - relationship_names
101
+ end
102
+
103
+ def relationship_names
104
+ self.class.relationship_names.to_a
105
+ end
22
106
 
23
- attributes.key?(attribute.to_sym) || super
107
+ def push?
108
+ true
24
109
  end
25
110
 
26
111
  private
@@ -28,9 +113,9 @@ module Userlist
28
113
  def method_missing(method, *args, &block)
29
114
  if method.to_s =~ /=$/
30
115
  attribute = method.to_s.sub(/=$/, '')
31
- attributes[attribute.to_sym] = args.first
32
- elsif attributes.key?(method.to_sym)
33
- attributes[method.to_sym]
116
+ payload[attribute.to_sym] = args.first
117
+ elsif payload.key?(method.to_sym)
118
+ payload[method.to_sym]
34
119
  else
35
120
  super
36
121
  end
@@ -0,0 +1,32 @@
1
+ module Userlist
2
+ class Push
3
+ class ResourceCollection
4
+ include Enumerable
5
+
6
+ attr_reader :collection, :relationship, :owner, :config
7
+
8
+ def initialize(collection, relationship, owner, config = Userlist.config)
9
+ @collection = Array(collection)
10
+ @relationship = relationship
11
+ @owner = owner
12
+ @config = config
13
+ end
14
+
15
+ def each
16
+ collection.each do |resource|
17
+ resource[inverse] = owner if inverse && resource.is_a?(Hash)
18
+
19
+ yield type.from_payload(resource, config)
20
+ end
21
+ end
22
+
23
+ def type
24
+ Object.const_get(relationship[:type])
25
+ end
26
+
27
+ def inverse
28
+ relationship[:inverse]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,66 @@
1
+ require 'set'
2
+
3
+ module Userlist
4
+ class Push
5
+ class Serializer
6
+ def self.serialize(resource)
7
+ new.serialize(resource)
8
+ end
9
+
10
+ def serialize(resource)
11
+ resource = serialize_resource(resource) if resource.is_a?(Userlist::Push::Resource)
12
+ resource
13
+ end
14
+
15
+ private
16
+
17
+ def serialize_resource(resource)
18
+ return resource.identifier if serialized_resources.include?(resource)
19
+
20
+ serialized_resources << resource
21
+
22
+ return unless resource.push?
23
+
24
+ serialized = {}
25
+
26
+ resource.attribute_names.each do |name|
27
+ serialized[name] = resource.send(name)
28
+ end
29
+
30
+ resource.relationship_names.each do |name|
31
+ next unless result = serialize_relationship(resource.send(name))
32
+
33
+ serialized[name] = result
34
+ end
35
+
36
+ serialized
37
+ end
38
+
39
+ def serialize_relationship(relationship)
40
+ return unless relationship
41
+
42
+ case relationship
43
+ when Userlist::Push::ResourceCollection
44
+ serialize_collection(relationship)
45
+ when Userlist::Push::Resource
46
+ serialize_resource(relationship)
47
+ else
48
+ raise "Cannot serialize relationship type: #{relationship.class}"
49
+ end
50
+ end
51
+
52
+ def serialize_collection(collection)
53
+ serialized = collection
54
+ .map(&method(:serialize_relationship))
55
+ .compact
56
+ .reject(&:empty?)
57
+
58
+ serialized unless serialized.empty?
59
+ end
60
+
61
+ def serialized_resources
62
+ @serialized_resources ||= Set.new
63
+ end
64
+ end
65
+ end
66
+ 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
@@ -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
@@ -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
@@ -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', inverse: :user
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 or :email' unless payload[:identifier] || payload[:email]
7
14
 
8
15
  super
9
16
  end
data/lib/userlist/push.rb CHANGED
@@ -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
@@ -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.0'.freeze
2
+ VERSION = '0.7.0'.freeze
3
3
  end
data/lib/userlist.rb CHANGED
@@ -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
data/userlist.gemspec CHANGED
@@ -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.0
4
+ version: 0.7.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-11 00:00:00.000000000 Z
11
+ date: 2022-02-02 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.2.32
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: []
data/.travis.yml DELETED
@@ -1,7 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.3
4
- - 2.4
5
- - 2.5
6
- - 2.6
7
- - 2.7