userlist 0.2.2 → 0.6.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.
@@ -10,10 +10,25 @@ module Userlist
10
10
 
11
11
  def initialize(config = {})
12
12
  @config = Userlist.config.merge(config)
13
+
14
+ raise Userlist::ConfigurationError, :push_key unless @config.push_key
15
+ raise Userlist::ConfigurationError, :push_endpoint unless @config.push_endpoint
16
+ end
17
+
18
+ def get(endpoint)
19
+ request(Net::HTTP::Get, endpoint)
20
+ end
21
+
22
+ def post(endpoint, payload = nil)
23
+ request(Net::HTTP::Post, endpoint, payload)
24
+ end
25
+
26
+ def put(endpoint, payload = nil)
27
+ request(Net::HTTP::Put, endpoint, payload)
13
28
  end
14
29
 
15
- def post(endpoint, payload = {})
16
- request(endpoint, payload)
30
+ def delete(endpoint)
31
+ request(Net::HTTP::Delete, endpoint)
17
32
  end
18
33
 
19
34
  private
@@ -33,12 +48,12 @@ module Userlist
33
48
  end
34
49
  end
35
50
 
36
- def request(path, payload = {})
37
- request = Net::HTTP::Post.new(path)
51
+ def request(method, path, payload = nil)
52
+ request = method.new(path)
38
53
  request['Accept'] = 'application/json'
39
54
  request['Authorization'] = "Push #{token}"
40
55
  request['Content-Type'] = 'application/json; charset=UTF-8'
41
- request.body = JSON.dump(payload)
56
+ request.body = JSON.generate(payload) if payload
42
57
 
43
58
  logger.debug "Sending #{request.method} to #{URI.join(endpoint, request.path)} with body #{request.body}"
44
59
 
@@ -0,0 +1,23 @@
1
+ module Userlist
2
+ class Push
3
+ class Company < Resource
4
+ include Operations::Create
5
+ include Operations::Delete
6
+
7
+ def self.endpoint
8
+ '/companies'
9
+ end
10
+
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]
18
+
19
+ super
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ module Userlist
2
+ class Push
3
+ class Event < Resource
4
+ include Operations::Create
5
+
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]
13
+
14
+ super
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
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ module Userlist
2
+ class Push
3
+ module Operations
4
+ module Create
5
+ module ClassMethods
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?
10
+ end
11
+
12
+ alias push create
13
+ end
14
+
15
+ def self.included(base)
16
+ base.extend(ClassMethods)
17
+ end
18
+
19
+ def create?
20
+ push?
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ module Userlist
2
+ class Push
3
+ module Operations
4
+ module Delete
5
+ module ClassMethods
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?
10
+ end
11
+ end
12
+
13
+ def self.included(base)
14
+ base.extend(ClassMethods)
15
+ end
16
+
17
+ def delete?
18
+ true
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ module Userlist
2
+ class Push
3
+ class Relation
4
+ def initialize(scope, type, operations = [])
5
+ @scope = scope
6
+ @type = type
7
+
8
+ operations.each { |operation| singleton_class.send(:include, operation::ClassMethods) }
9
+ end
10
+
11
+ attr_reader :scope, :type
12
+
13
+ def from_payload(payload, config = self.config)
14
+ type.from_payload(payload, config)
15
+ end
16
+
17
+ def endpoint
18
+ type.endpoint
19
+ end
20
+
21
+ def strategy
22
+ scope.strategy
23
+ end
24
+
25
+ def config
26
+ scope.config
27
+ end
28
+ end
29
+ end
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
@@ -0,0 +1,125 @@
1
+ module Userlist
2
+ class Push
3
+ class Resource
4
+ class << self
5
+ def resource_name
6
+ name.split('::')[-1]
7
+ end
8
+
9
+ def endpoint
10
+ "/#{resource_name.downcase}s"
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
59
+ end
60
+
61
+ attr_reader :payload, :config
62
+
63
+ def initialize(payload = {}, config = Userlist.config)
64
+ @payload = payload
65
+ @config = config
66
+ end
67
+
68
+ def respond_to_missing?(method, include_private = false)
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
106
+
107
+ def push?
108
+ true
109
+ end
110
+
111
+ private
112
+
113
+ def method_missing(method, *args, &block)
114
+ if method.to_s =~ /=$/
115
+ attribute = method.to_s.sub(/=$/, '')
116
+ payload[attribute.to_sym] = args.first
117
+ elsif payload.key?(method.to_sym)
118
+ payload[method.to_sym]
119
+ else
120
+ super
121
+ end
122
+ end
123
+ end
124
+ end
125
+ 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
@@ -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