userlist 1.0.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b77ff6c1af95fbe8c6e90914fc03840ca25c53b5fa1cafd0e5f50c670f41117
4
- data.tar.gz: 51f8e21e0641f04f1e33f52f97d86ae11935bb8d6596dec3acc3b8b9708a15ea
3
+ metadata.gz: 9ed2e089f640ba0d4865236a53b5744e9edfbbe4400337b789c6e09c80474870
4
+ data.tar.gz: ce7ad3b24cc47645ee91d69aa27f312628f840c427c52f49ae38823abf9ad1d8
5
5
  SHA512:
6
- metadata.gz: dcd7b3e74eb02dad0b26682a8f35ca34c90511059d43c867e6b0c7ef6c051d1c8f45d9e57e9be88a40a24af3980f33a326239f541254806cf4da9fd7fb6db4f3
7
- data.tar.gz: 62d43c452750bfca92a29c18ee1ff4f20fd924b18bc26afbe535567bdd159fde9bfbd8c33f25a53b6b6c3b0ca5ae5617f11077dadfeafafde0c352debdad3af8
6
+ metadata.gz: 6c0b7d45d3edcb1a07e10e759b6d3911ff594f6feceadc7fb17eef9961ea693853e570e9223ce837cc11cc04b025fd227ed42ba4c5e35501406f9a636d39f22b
7
+ data.tar.gz: 64146b1ba4fda2069e74e230e81c7d1447166b03775fe048ba821d7ec04702ab69ea85a116ffb0243f4f3a4e01b83e7e53c28fef7813f9351851428bd6343743
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.4
2
+ TargetRubyVersion: 3.0
3
3
  NewCops: enable
4
4
  Exclude:
5
5
  - 'bin/*'
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased (main)
4
4
 
5
+ ## v1.1.0 (2025-10-17)
6
+
7
+ - Allow specifying companies in messages
8
+ - Add reply_to support to DeliveryMethod for ActionMailer integration
9
+ - Fixes issue with deleting resources when they are not pushed by default
10
+
5
11
  ## v1.0.0 (2025-04-08)
6
12
 
7
13
  - Updates ActiveJob Worker to retry on errors with polynomially longer wait times, up to 10 attempts
data/Gemfile CHANGED
@@ -4,6 +4,11 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
5
  gemspec
6
6
 
7
+ gem 'jwt', '~> 2.2'
8
+ gem 'rake', '~> 13.0'
9
+ gem 'rspec', '~> 3.0'
10
+ gem 'webmock', '~> 3.18'
11
+
7
12
  gem 'guard-rspec', '~> 4.7'
8
13
  gem 'guard-rubocop', '~> 1.3'
9
14
  gem 'rubocop', '~> 1.45'
@@ -75,12 +75,12 @@ module Userlist
75
75
  key?(name.to_sym) || super
76
76
  end
77
77
 
78
- def method_missing(name, *args, &block)
78
+ def method_missing(name, ...)
79
79
  if respond_to_missing?(name)
80
80
  name = name.to_s
81
81
  method = name =~ /=$/ ? :[]= : :[]
82
82
  name = name.sub(/=$/, '').to_sym
83
- send(method, name, *args, &block)
83
+ send(method, name, ...)
84
84
  else
85
85
  super
86
86
  end
@@ -19,12 +19,15 @@ private
19
19
  {
20
20
  to: serialize_address(mail.to),
21
21
  from: serialize_address(mail.from),
22
+ reply_to: serialize_address(mail.reply_to),
22
23
  subject: mail.subject,
23
24
  body: serialize_body(mail.body)
24
25
  }.compact
25
26
  end
26
27
 
27
28
  def serialize_address(address)
29
+ return if address.nil? || Array(address).empty?
30
+
28
31
  Array(address).map(&:to_s)
29
32
  end
30
33
 
@@ -1,7 +1,7 @@
1
1
  module Userlist
2
2
  class Push
3
3
  class Company < Resource
4
- include Operations::Create
4
+ include Operations::Push
5
5
  include Operations::Delete
6
6
 
7
7
  def self.endpoint
@@ -1,7 +1,7 @@
1
1
  module Userlist
2
2
  class Push
3
3
  class Event < Resource
4
- include Operations::Create
4
+ include Operations::Push
5
5
 
6
6
  has_one :user, type: 'Userlist::Push::User'
7
7
  has_one :company, type: 'Userlist::Push::Company'
@@ -1,9 +1,10 @@
1
1
  module Userlist
2
2
  class Push
3
3
  class Message < Resource
4
- include Operations::Create
4
+ include Operations::Push
5
5
 
6
6
  has_one :user, type: 'Userlist::Push::User'
7
+ has_one :company, type: 'Userlist::Push::Company'
7
8
 
8
9
  def initialize(payload = {}, config = Userlist.config)
9
10
  raise Userlist::ArgumentError, 'Missing required payload' unless payload
@@ -5,8 +5,9 @@ module Userlist
5
5
  module ClassMethods
6
6
  def delete(payload = {}, config = self.config)
7
7
  return false unless resource = from_payload(payload, config)
8
+ return false unless resource.delete?
8
9
 
9
- strategy.call(:delete, endpoint, resource) if resource.delete?
10
+ strategy.call(:delete, endpoint, resource.for_context(:delete))
10
11
  end
11
12
  end
12
13
 
@@ -1,24 +1,22 @@
1
1
  module Userlist
2
2
  class Push
3
3
  module Operations
4
- module Create
4
+ module Push
5
5
  module ClassMethods
6
- def create(payload = {}, config = self.config)
6
+ def push(payload = {}, config = self.config)
7
7
  return false unless resource = from_payload(payload, config)
8
+ return false unless resource.push?
8
9
 
9
- strategy.call(:post, endpoint, resource) if resource.create?
10
+ strategy.call(:post, endpoint, resource.for_context(:push))
10
11
  end
11
12
 
12
- alias push create
13
+ alias create push
14
+ alias update push
13
15
  end
14
16
 
15
17
  def self.included(base)
16
18
  base.extend(ClassMethods)
17
19
  end
18
-
19
- def create?
20
- push?
21
- end
22
20
  end
23
21
  end
24
22
  end
@@ -1,7 +1,7 @@
1
1
  module Userlist
2
2
  class Push
3
3
  class Relationship < Resource
4
- include Operations::Create
4
+ include Operations::Push
5
5
  include Operations::Delete
6
6
 
7
7
  has_one :user, type: 'Userlist::Push::User'
@@ -19,18 +19,18 @@ module Userlist
19
19
  new(payload, config)
20
20
  end
21
21
 
22
- def relationship_names
23
- @relationship_names ||= relationships.keys
22
+ def association_names
23
+ @association_names ||= associations.keys
24
24
  end
25
25
 
26
- def relationships
27
- @relationships ||= {}
26
+ def associations
27
+ @associations ||= {}
28
28
  end
29
29
 
30
30
  protected
31
31
 
32
32
  def has_one(name, type:) # rubocop:disable Naming/PredicateName
33
- relationships[name.to_sym] = { type: type }
33
+ associations[name.to_sym] = { type: type }
34
34
 
35
35
  generated_methods.class_eval <<-RUBY, __FILE__, __LINE__ + 1
36
36
  def #{name} # def company
@@ -40,14 +40,14 @@ module Userlist
40
40
  end
41
41
 
42
42
  def has_many(name, **options) # rubocop:disable Naming/PredicateName
43
- relationships[name.to_sym] = options
43
+ associations[name.to_sym] = options
44
44
 
45
45
  generated_methods.class_eval <<-RUBY, __FILE__, __LINE__ + 1
46
46
  def #{name} # def companies
47
- relationship = self.class.relationships[:#{name}] # relationship = self.class.relationships[:companies]
47
+ associations = self.class.associations[:#{name}] # associations = self.class.associations[:companies]
48
48
  #
49
- ResourceCollection.new(payload[:#{name}], relationship, self, config) # ResourceCollection.new(payload[:companies], relationship, self, config)
50
- end #
49
+ ResourceCollection.new(payload[:#{name}], associations, self, config) # ResourceCollection.new(payload[:companies], associations, self, config)
50
+ end # end
51
51
  RUBY
52
52
  end
53
53
 
@@ -58,13 +58,14 @@ module Userlist
58
58
  end
59
59
  end
60
60
 
61
- attr_reader :payload, :config
61
+ attr_reader :payload, :config, :context
62
62
 
63
63
  def initialize(payload = {}, config = Userlist.config)
64
64
  raise Userlist::ArgumentError, 'Missing required payload' unless payload
65
65
 
66
66
  @payload = payload
67
67
  @config = config
68
+ @context = :push
68
69
  end
69
70
 
70
71
  def respond_to_missing?(method, include_private = false)
@@ -73,7 +74,7 @@ module Userlist
73
74
  end
74
75
 
75
76
  def to_hash
76
- Serializer.serialize(self)
77
+ Serializer.serialize(self, context: context)
77
78
  end
78
79
  alias to_h to_hash
79
80
 
@@ -95,17 +96,23 @@ module Userlist
95
96
  alias == eql?
96
97
 
97
98
  def attribute_names
98
- payload.keys.map(&:to_sym) - relationship_names
99
+ payload.keys.map(&:to_sym) - association_names
99
100
  end
100
101
 
101
- def relationship_names
102
- self.class.relationship_names.to_a
102
+ def association_names
103
+ self.class.association_names.to_a
103
104
  end
104
105
 
105
106
  def push?
106
107
  true
107
108
  end
108
109
 
110
+ def for_context(context)
111
+ dup.tap do |instance|
112
+ instance.instance_variable_set(:@context, context)
113
+ end
114
+ end
115
+
109
116
  private
110
117
 
111
118
  def method_missing(method, *args, &block)
@@ -3,8 +3,14 @@ require 'set'
3
3
  module Userlist
4
4
  class Push
5
5
  class Serializer
6
- def self.serialize(resource)
7
- new.serialize(resource)
6
+ def self.serialize(resource, **options)
7
+ new(**options).serialize(resource)
8
+ end
9
+
10
+ attr_reader :context
11
+
12
+ def initialize(context:)
13
+ @context = context
8
14
  end
9
15
 
10
16
  def serialize(resource)
@@ -12,6 +18,13 @@ module Userlist
12
18
  resource
13
19
  end
14
20
 
21
+ def serialize?(resource)
22
+ method_name = "#{context}?"
23
+
24
+ resource.respond_to?(method_name) &&
25
+ resource.public_send(method_name)
26
+ end
27
+
15
28
  private
16
29
 
17
30
  def serialize_resource(resource)
@@ -19,7 +32,7 @@ module Userlist
19
32
 
20
33
  serialized_resources << resource
21
34
 
22
- return unless resource.push?
35
+ return unless serialize?(resource)
23
36
 
24
37
  serialized = {}
25
38
 
@@ -27,8 +40,8 @@ module Userlist
27
40
  serialized[name] = resource.send(name)
28
41
  end
29
42
 
30
- resource.relationship_names.each do |name|
31
- next unless result = serialize_relationship(resource.send(name))
43
+ resource.association_names.each do |name|
44
+ next unless result = serialize_association(resource.send(name))
32
45
 
33
46
  serialized[name] = result
34
47
  end
@@ -36,22 +49,22 @@ module Userlist
36
49
  serialized
37
50
  end
38
51
 
39
- def serialize_relationship(relationship)
40
- return unless relationship
52
+ def serialize_association(association)
53
+ return unless association
41
54
 
42
- case relationship
55
+ case association
43
56
  when Userlist::Push::ResourceCollection
44
- serialize_collection(relationship)
57
+ serialize_collection(association)
45
58
  when Userlist::Push::Resource
46
- serialize_resource(relationship)
59
+ serialize_resource(association)
47
60
  else
48
- raise "Cannot serialize relationship type: #{relationship.class}"
61
+ raise "Cannot serialize association type: #{association.class}"
49
62
  end
50
63
  end
51
64
 
52
65
  def serialize_collection(collection)
53
66
  serialized = collection
54
- .map(&method(:serialize_relationship))
67
+ .map(&method(:serialize_association))
55
68
  .compact
56
69
  .reject(&:empty?)
57
70
 
@@ -27,7 +27,7 @@ module Userlist
27
27
  def options
28
28
  @options ||= begin
29
29
  options = config.push_strategy_options || {}
30
- options.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
30
+ options.transform_keys(&:to_sym)
31
31
  end
32
32
  end
33
33
 
@@ -25,7 +25,7 @@ module Userlist
25
25
  def options
26
26
  @options ||= begin
27
27
  options = config.push_strategy_options || {}
28
- options.each_with_object({}) { |(k, v), h| h[k.to_s] = v }
28
+ options.transform_keys(&:to_s)
29
29
  end
30
30
  end
31
31
 
@@ -18,14 +18,12 @@ module Userlist
18
18
  logger.info 'Starting worker thread...'
19
19
 
20
20
  loop do
21
- begin
22
- method, *args = *queue.pop
23
- break if method == :stop
24
-
25
- retryable.attempt { client.public_send(method, *args) }
26
- rescue StandardError => e
27
- logger.error "Failed to deliver payload: [#{e.class.name}] #{e.message}"
28
- end
21
+ method, *args = *queue.pop
22
+ break if method == :stop
23
+
24
+ retryable.attempt { client.public_send(method, *args) }
25
+ rescue StandardError => e
26
+ logger.error "Failed to deliver payload: [#{e.class.name}] #{e.message}"
29
27
  end
30
28
 
31
29
  logger.info "Worker thread exited with #{queue.size} tasks still in the queue..."
@@ -1,7 +1,7 @@
1
1
  module Userlist
2
2
  class Push
3
3
  class User < Resource
4
- include Operations::Create
4
+ include Operations::Push
5
5
  include Operations::Delete
6
6
 
7
7
  has_many :relationships, type: 'Userlist::Push::Relationship', inverse: :user
data/lib/userlist/push.rb CHANGED
@@ -5,7 +5,7 @@ require 'userlist/push/resource'
5
5
  require 'userlist/push/resource_collection'
6
6
  require 'userlist/push/relation'
7
7
 
8
- require 'userlist/push/operations/create'
8
+ require 'userlist/push/operations/push'
9
9
  require 'userlist/push/operations/delete'
10
10
 
11
11
  require 'userlist/push/user'
@@ -38,39 +38,39 @@ module Userlist
38
38
  attr_reader :config, :strategy
39
39
 
40
40
  def events
41
- @events ||= Relation.new(self, Event, [Operations::Create])
41
+ @events ||= Relation.new(self, Event, [Operations::Push])
42
42
  end
43
43
 
44
44
  def users
45
- @users ||= Relation.new(self, User, [Operations::Create, Operations::Delete])
45
+ @users ||= Relation.new(self, User, [Operations::Push, Operations::Delete])
46
46
  end
47
47
 
48
48
  def companies
49
- @companies ||= Relation.new(self, Company, [Operations::Create, Operations::Delete])
49
+ @companies ||= Relation.new(self, Company, [Operations::Push, Operations::Delete])
50
50
  end
51
51
 
52
52
  def relationships
53
- @relationships ||= Relation.new(self, Relationship, [Operations::Create, Operations::Delete])
53
+ @relationships ||= Relation.new(self, Relationship, [Operations::Push, Operations::Delete])
54
54
  end
55
55
 
56
56
  def messages
57
- @messages ||= Relation.new(self, Message, [Operations::Create])
57
+ @messages ||= Relation.new(self, Message, [Operations::Push])
58
58
  end
59
59
 
60
60
  def event(payload = {})
61
- events.create(payload)
61
+ events.push(payload)
62
62
  end
63
63
 
64
64
  def user(payload = {})
65
- users.create(payload)
65
+ users.push(payload)
66
66
  end
67
67
 
68
68
  def company(payload = {})
69
- companies.create(payload)
69
+ companies.push(payload)
70
70
  end
71
71
 
72
72
  def message(payload = {})
73
- messages.create(payload)
73
+ messages.push(payload)
74
74
  end
75
75
 
76
76
  alias track event
@@ -33,17 +33,15 @@ module Userlist
33
33
 
34
34
  def attempt
35
35
  (0..@retries).each do |attempt|
36
- begin
37
- if attempt.positive?
38
- milliseconds = delay(attempt)
39
- logger.debug "Retrying in #{milliseconds}ms, #{@retries - attempt} retries left"
40
- sleep(milliseconds / 1000.0)
41
- end
42
-
43
- return yield
44
- rescue Userlist::Error => e
45
- raise e unless retry?(e)
36
+ if attempt.positive?
37
+ milliseconds = delay(attempt)
38
+ logger.debug "Retrying in #{milliseconds}ms, #{@retries - attempt} retries left"
39
+ sleep(milliseconds / 1000.0)
46
40
  end
41
+
42
+ return yield
43
+ rescue Userlist::Error => e
44
+ raise e unless retry?(e)
47
45
  end
48
46
 
49
47
  logger.debug 'Retries exhausted, giving up'
@@ -1,3 +1,3 @@
1
1
  module Userlist
2
- VERSION = '1.0.0'.freeze
2
+ VERSION = '1.1.0'.freeze
3
3
  end
data/userlist.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.email = ['benedikt@userlist.com']
10
10
 
11
11
  spec.summary = 'Ruby wrapper for the Userlist API'
12
- spec.homepage = 'http://github.com/userlistio/userlist-ruby'
12
+ spec.homepage = 'http://github.com/userlist/userlist-ruby'
13
13
  spec.license = 'MIT'
14
14
 
15
15
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
@@ -19,13 +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.4'
23
-
24
- spec.add_development_dependency 'bundler', '>= 1.15'
25
- spec.add_development_dependency 'jwt', '~> 2.2'
26
- spec.add_development_dependency 'rake', '~> 13.0'
27
- spec.add_development_dependency 'rspec', '~> 3.0'
28
- spec.add_development_dependency 'webmock', '~> 3.18'
22
+ spec.required_ruby_version = '>= 3.0'
29
23
 
30
24
  spec.metadata = { 'rubygems_mfa_required' => 'true' }
31
25
  end
metadata CHANGED
@@ -1,84 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: userlist
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benedikt Deicke
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-04-08 00:00:00.000000000 Z
11
- dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: bundler
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - ">="
17
- - !ruby/object:Gem::Version
18
- version: '1.15'
19
- type: :development
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - ">="
24
- - !ruby/object:Gem::Version
25
- version: '1.15'
26
- - !ruby/object:Gem::Dependency
27
- name: jwt
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '2.2'
33
- type: :development
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '2.2'
40
- - !ruby/object:Gem::Dependency
41
- name: rake
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '13.0'
47
- type: :development
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '13.0'
54
- - !ruby/object:Gem::Dependency
55
- name: rspec
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '3.0'
61
- type: :development
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: '3.0'
68
- - !ruby/object:Gem::Dependency
69
- name: webmock
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '3.18'
75
- type: :development
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: '3.18'
10
+ date: 2025-10-17 00:00:00.000000000 Z
11
+ dependencies: []
82
12
  email:
83
13
  - benedikt@userlist.com
84
14
  executables: []
@@ -107,8 +37,8 @@ files:
107
37
  - lib/userlist/push/company.rb
108
38
  - lib/userlist/push/event.rb
109
39
  - lib/userlist/push/message.rb
110
- - lib/userlist/push/operations/create.rb
111
40
  - lib/userlist/push/operations/delete.rb
41
+ - lib/userlist/push/operations/push.rb
112
42
  - lib/userlist/push/relation.rb
113
43
  - lib/userlist/push/relationship.rb
114
44
  - lib/userlist/push/resource.rb
@@ -128,7 +58,7 @@ files:
128
58
  - lib/userlist/token.rb
129
59
  - lib/userlist/version.rb
130
60
  - userlist.gemspec
131
- homepage: http://github.com/userlistio/userlist-ruby
61
+ homepage: http://github.com/userlist/userlist-ruby
132
62
  licenses:
133
63
  - MIT
134
64
  metadata:
@@ -140,7 +70,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
140
70
  requirements:
141
71
  - - ">="
142
72
  - !ruby/object:Gem::Version
143
- version: '2.4'
73
+ version: '3.0'
144
74
  required_rubygems_version: !ruby/object:Gem::Requirement
145
75
  requirements:
146
76
  - - ">="