userlist 0.2.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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