relateiq_client 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +55 -0
- data/.rspec +2 -0
- data/.rubocop.yml +14 -0
- data/.simplecov +9 -0
- data/.travis.yml +6 -0
- data/.zenflow +11 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +112 -0
- data/Guardfile +17 -0
- data/README.md +39 -0
- data/Rakefile +6 -0
- data/lib/relateiq.rb +31 -0
- data/lib/relateiq/account.rb +43 -0
- data/lib/relateiq/contact.rb +127 -0
- data/lib/relateiq/list.rb +77 -0
- data/lib/relateiq/list_item.rb +107 -0
- data/lib/relateiq/service_factory.rb +12 -0
- data/lib/relateiq/utils/field_value_encoder.rb +73 -0
- data/lib/relateiq/version.rb +3 -0
- data/relateiq.gemspec +37 -0
- data/spec/fixtures/account_by_id.json +6 -0
- data/spec/fixtures/contact.json +46 -0
- data/spec/fixtures/contacts.json +0 -0
- data/spec/fixtures/contacts_by_email.json +34 -0
- data/spec/fixtures/list_item.json +72 -0
- data/spec/fixtures/list_items_by_contact_id.json +37 -0
- data/spec/fixtures/lists.json +1373 -0
- data/spec/integration/relateiq_integration_spec.rb +69 -0
- data/spec/lib/relateiq/account_spec.rb +66 -0
- data/spec/lib/relateiq/contact_spec.rb +161 -0
- data/spec/lib/relateiq/list_item_spec.rb +133 -0
- data/spec/lib/relateiq/list_spec.rb +145 -0
- data/spec/lib/relateiq/service_factory_spec.rb +8 -0
- data/spec/lib/relateiq/utils/field_value_encoder_spec.rb +91 -0
- data/spec/relate_iq_spec.rb +43 -0
- data/spec/spec_helper.rb +44 -0
- metadata +278 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
module RelateIq
|
2
|
+
class List
|
3
|
+
attr_accessor :id,
|
4
|
+
:title,
|
5
|
+
:list_type,
|
6
|
+
:modified_date,
|
7
|
+
:fields
|
8
|
+
|
9
|
+
def initialize(attrs = {})
|
10
|
+
if attrs.key? :listType
|
11
|
+
initialize_from_api(attrs)
|
12
|
+
else
|
13
|
+
initialize_from_user(attrs)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.resource
|
18
|
+
@resource ||= ServiceFactory.get_endpoint('lists')
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.all
|
22
|
+
@all ||= from_json(resource.get)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.find_by_title(title)
|
26
|
+
all.find { |l| l.title.downcase == title.downcase }
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.find(id)
|
30
|
+
all.find { |l| l.id == id }
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.clean_cache
|
34
|
+
@all = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.from_json(json_string)
|
38
|
+
lists_hash = JSON.parse(json_string, symbolize_names: true)
|
39
|
+
if lists_hash.key? :objects
|
40
|
+
lists_hash[:objects].map { |li| List.new(li) }
|
41
|
+
else
|
42
|
+
List.new(lists_hash)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def upsert_item(list_item_hash)
|
47
|
+
list_item_hash.merge!(list_id: id)
|
48
|
+
list_item_class.new(list_item_hash).save
|
49
|
+
end
|
50
|
+
|
51
|
+
def items_by_contact_id(contact_id)
|
52
|
+
list_item_class.find_by_contact(id, contact_id)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def list_item_class
|
58
|
+
@list_item_class ||= ListItem
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize_from_api(attrs)
|
62
|
+
@id = attrs.fetch(:id)
|
63
|
+
@title = attrs.fetch(:title)
|
64
|
+
@fields = attrs.fetch(:fields)
|
65
|
+
@list_type = attrs.fetch(:listType)
|
66
|
+
@modified_date = attrs.fetch(:modifiedDate)
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize_from_user(attrs)
|
70
|
+
@id = attrs.fetch(:id, nil)
|
71
|
+
@title = attrs.fetch(:title, nil)
|
72
|
+
@list_type = attrs.fetch(:list_type, nil)
|
73
|
+
@modified_date = attrs.fetch(:modified_date, nil)
|
74
|
+
@fields = attrs.fetch(:fields, nil)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module RelateIq
|
2
|
+
class ListItem
|
3
|
+
attr_accessor :list_id,
|
4
|
+
:id,
|
5
|
+
:name,
|
6
|
+
:account_id,
|
7
|
+
:contact_ids,
|
8
|
+
:field_values
|
9
|
+
|
10
|
+
# field values always contains decoded values
|
11
|
+
# for example { 'Status' => 'Application Submitted' }
|
12
|
+
|
13
|
+
def initialize(attrs = {})
|
14
|
+
if attrs.key? :listId
|
15
|
+
initialize_from_api(attrs)
|
16
|
+
else
|
17
|
+
initialize_by_user(attrs)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize_by_user(attrs)
|
22
|
+
@list_id = attrs.fetch(:list_id)
|
23
|
+
@id = attrs.fetch(:id, nil)
|
24
|
+
@name = attrs.fetch(:name, nil)
|
25
|
+
@account_id = attrs.fetch(:account_id, nil)
|
26
|
+
@contact_ids = attrs.fetch(:contact_ids, [])
|
27
|
+
@field_values = attrs.fetch(:field_values, [])
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize_from_api(attrs)
|
31
|
+
@list_id = attrs.fetch(:listId)
|
32
|
+
@id = attrs.fetch(:id, nil)
|
33
|
+
@name = attrs.fetch(:name, nil)
|
34
|
+
@account_id = attrs.fetch(:accountId, nil)
|
35
|
+
@contact_ids = attrs.fetch(:contactIds, [])
|
36
|
+
@field_values = decode_field_values(attrs.fetch(:fieldValues, nil))
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.resource(list_id)
|
40
|
+
@resource ||= ServiceFactory.get_endpoint('lists')
|
41
|
+
@resource["#{list_id}/listitems"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.from_json(json_string)
|
45
|
+
list_item_hash = JSON.parse(json_string, symbolize_names: true)
|
46
|
+
if list_item_hash.key? :objects
|
47
|
+
list_item_hash[:objects].map { |li| ListItem.new(li) }
|
48
|
+
else
|
49
|
+
ListItem.new(list_item_hash)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.create(attrs)
|
54
|
+
ListItem.new(attrs).save
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.find_by_contact(list_id, contact_id)
|
58
|
+
from_json(resource(list_id)["?contactIds=#{contact_id}"].get)
|
59
|
+
end
|
60
|
+
|
61
|
+
def save
|
62
|
+
if id
|
63
|
+
ListItem.from_json(ListItem.resource(list_id)["#{id}"].put to_json)
|
64
|
+
else
|
65
|
+
ListItem.from_json(ListItem.resource(list_id).post to_json)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_json
|
70
|
+
to_hash.to_json
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def to_hash
|
76
|
+
result = { 'listId' => list_id }
|
77
|
+
result.merge!('id' => id) if id
|
78
|
+
result.merge!('name' => name) if name
|
79
|
+
result.merge!('accountId' => account_id) if account_id && list.list_type == 'account'
|
80
|
+
result.merge!('contactIds' => contact_ids) if contact_ids && contact_ids.count > 0
|
81
|
+
result.merge!('fieldValues' => encode_field_values(field_values)) if field_values && field_values.count > 0
|
82
|
+
result
|
83
|
+
end
|
84
|
+
|
85
|
+
def encode_field_values(values)
|
86
|
+
return [] unless values
|
87
|
+
encoder = field_value_encoder.new(list: list)
|
88
|
+
encoded_values = {}
|
89
|
+
values.each { |fv| encoded_values.merge!(encoder.encode(fv)) }
|
90
|
+
encoded_values
|
91
|
+
end
|
92
|
+
|
93
|
+
def decode_field_values(values)
|
94
|
+
return [] unless values
|
95
|
+
encoder = field_value_encoder.new(list: list)
|
96
|
+
values.map { |k, v| encoder.decode(k => v) }
|
97
|
+
end
|
98
|
+
|
99
|
+
def field_value_encoder
|
100
|
+
@field_value_encoder ||= RelateIq::Utils::FieldValueEncoder
|
101
|
+
end
|
102
|
+
|
103
|
+
def list
|
104
|
+
List.find(list_id)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module RelateIq
|
2
|
+
class ServiceFactory
|
3
|
+
def self.get_endpoint(endpoint)
|
4
|
+
RestClient::Resource.new(
|
5
|
+
"#{RelateIq.configuration.base_url}/#{endpoint}",
|
6
|
+
user: RelateIq.configuration.username,
|
7
|
+
password: RelateIq.configuration.password,
|
8
|
+
headers: { content_type: :json, accept: :json }
|
9
|
+
)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module RelateIq
|
2
|
+
module Utils
|
3
|
+
class FieldValueEncoder
|
4
|
+
attr_accessor :list
|
5
|
+
|
6
|
+
def initialize(attrs = {})
|
7
|
+
@list = attrs.fetch(:list)
|
8
|
+
end
|
9
|
+
|
10
|
+
def encode(decoded_value)
|
11
|
+
return {} if decoded_value.nil?
|
12
|
+
name = decoded_value.keys[0]
|
13
|
+
values = decoded_value.values[0]
|
14
|
+
field = find_field_by_name(name)
|
15
|
+
encoded_value = encode_values(field, values)
|
16
|
+
return {} if encoded_value == []
|
17
|
+
{ "#{field[:id]}" => encoded_value }
|
18
|
+
end
|
19
|
+
|
20
|
+
def decode(encoded_value)
|
21
|
+
return {} if encoded_value.nil?
|
22
|
+
id = encoded_value.keys[0]
|
23
|
+
values = encoded_value.values[0]
|
24
|
+
field = find_field(id)
|
25
|
+
{ "#{field[:name]}" => decode_values(field, values) }
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def decode_values(field, field_values)
|
31
|
+
result = []
|
32
|
+
field_values.each { |fv| result << list_option_value(field, fv[:raw], 'id', 'display') }
|
33
|
+
result.count > 1 ? result : result[0]
|
34
|
+
end
|
35
|
+
|
36
|
+
def encode_values(field, field_values)
|
37
|
+
result = []
|
38
|
+
to_array(field_values).each do |fv|
|
39
|
+
list_option = list_option_value(field, fv, 'display', 'id')
|
40
|
+
result << { 'raw' => list_option } unless list_option.nil?
|
41
|
+
end
|
42
|
+
result
|
43
|
+
end
|
44
|
+
|
45
|
+
def list_option_value(field, value, key, value_key)
|
46
|
+
return nil if value.nil?
|
47
|
+
list_options = field[:listOptions]
|
48
|
+
if list_options && list_options.count > 0
|
49
|
+
option = list_options.find { |lo| lo[key.to_sym].downcase == value.downcase }
|
50
|
+
option ||= fail(FieldListOptionNotFoundError, key => value)
|
51
|
+
option[value_key.to_sym]
|
52
|
+
else
|
53
|
+
value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def find_field_by_name(name)
|
58
|
+
list.fields.find { |f| f[:name] == name } || fail(FieldNotFoundError, name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_field(id)
|
62
|
+
list.fields.find { |f| f[:id] == id.to_s } || fail(FieldNotFoundError, id)
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_array(value)
|
66
|
+
value.is_a?(Array) ? value : [value]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class FieldNotFoundError < StandardError; end
|
71
|
+
class FieldListOptionNotFoundError < StandardError; end
|
72
|
+
end
|
73
|
+
end
|
data/relateiq.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'relateiq/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'relateiq_client'
|
8
|
+
s.version = RelateIq::VERSION
|
9
|
+
s.authors = ['Dorian Karter']
|
10
|
+
s.email = 'jobs@doriankarter.com'
|
11
|
+
|
12
|
+
s.summary = 'Wrapper client for RelateIQ API'
|
13
|
+
s.description = 'Wrapper client for RelateIQ allows using RelateIQ API from ruby'
|
14
|
+
s.homepage = 'https://github.com/dkarter/relate_iq_client'
|
15
|
+
s.license = 'MIT'
|
16
|
+
|
17
|
+
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
|
19
|
+
s.bindir = 'bin'
|
20
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
21
|
+
s.require_paths = [ 'lib' ]
|
22
|
+
|
23
|
+
s.add_dependency 'rest-client', '~> 1.7'
|
24
|
+
|
25
|
+
s.add_development_dependency 'bundler', '~> 1.7'
|
26
|
+
s.add_development_dependency 'rake', '~> 10.0'
|
27
|
+
s.add_development_dependency 'rspec', '~>3.1'
|
28
|
+
s.add_development_dependency 'pry', '~> 0.10'
|
29
|
+
s.add_development_dependency 'pry-stack_explorer', '~> 0.4.9'
|
30
|
+
s.add_development_dependency 'zenflow'
|
31
|
+
s.add_development_dependency 'rubocop'
|
32
|
+
s.add_development_dependency 'simplecov'
|
33
|
+
s.add_development_dependency 'simplecov-json'
|
34
|
+
s.add_development_dependency 'cadre'
|
35
|
+
s.add_development_dependency 'codeclimate-test-reporter'
|
36
|
+
s.add_development_dependency 'webmock'
|
37
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
{
|
2
|
+
"id": "contactid",
|
3
|
+
"modifiedDate": 1424713865596,
|
4
|
+
"properties": {
|
5
|
+
"address": [
|
6
|
+
{
|
7
|
+
"value": "688 Dink rest, East Lu, Z2K 8OW",
|
8
|
+
"metadata": {}
|
9
|
+
}
|
10
|
+
],
|
11
|
+
"company": [
|
12
|
+
{
|
13
|
+
"value": "Bolstr",
|
14
|
+
"metadata": {}
|
15
|
+
},
|
16
|
+
{
|
17
|
+
"value": "Bolstr, Inc.",
|
18
|
+
"metadata": {}
|
19
|
+
}
|
20
|
+
],
|
21
|
+
"phone": [
|
22
|
+
{
|
23
|
+
"value": "+44 (0)5426658700",
|
24
|
+
"metadata": {}
|
25
|
+
}
|
26
|
+
],
|
27
|
+
"name": [
|
28
|
+
{
|
29
|
+
"value": "Kipp Rice",
|
30
|
+
"metadata": {}
|
31
|
+
}
|
32
|
+
],
|
33
|
+
"email": [
|
34
|
+
{
|
35
|
+
"value": "gulgowski.devyn@example.com",
|
36
|
+
"metadata": {}
|
37
|
+
}
|
38
|
+
],
|
39
|
+
"liurl": [
|
40
|
+
{
|
41
|
+
"value": "https://www.linkedin.com/in/doriankarter",
|
42
|
+
"metadata": {}
|
43
|
+
}
|
44
|
+
]
|
45
|
+
}
|
46
|
+
}
|
File without changes
|
@@ -0,0 +1,34 @@
|
|
1
|
+
{
|
2
|
+
"objects": [
|
3
|
+
{
|
4
|
+
"id": "contactid",
|
5
|
+
"modifiedDate": 1424713865596,
|
6
|
+
"properties": {
|
7
|
+
"address": [
|
8
|
+
{
|
9
|
+
"value": "688 Dink rest, East Lu, Z2K 8OW",
|
10
|
+
"metadata": {}
|
11
|
+
}
|
12
|
+
],
|
13
|
+
"phone": [
|
14
|
+
{
|
15
|
+
"value": "+44 (0)5426658700",
|
16
|
+
"metadata": {}
|
17
|
+
}
|
18
|
+
],
|
19
|
+
"name": [
|
20
|
+
{
|
21
|
+
"value": "Kipp Rice",
|
22
|
+
"metadata": {}
|
23
|
+
}
|
24
|
+
],
|
25
|
+
"email": [
|
26
|
+
{
|
27
|
+
"value": "gulgowski.devyn@example.com",
|
28
|
+
"metadata": {}
|
29
|
+
}
|
30
|
+
]
|
31
|
+
}
|
32
|
+
}
|
33
|
+
]
|
34
|
+
}
|
@@ -0,0 +1,72 @@
|
|
1
|
+
{
|
2
|
+
"id": "listitemid",
|
3
|
+
"listId": "list2id",
|
4
|
+
"version": 1,
|
5
|
+
"createdDate": 1417755155000,
|
6
|
+
"modifiedDate": 1421866566579,
|
7
|
+
"name": "Mack Koch",
|
8
|
+
"accountId": "accountid",
|
9
|
+
"contactIds": [
|
10
|
+
"contactid1",
|
11
|
+
"contactid2"
|
12
|
+
],
|
13
|
+
"fieldValues": {
|
14
|
+
"0": [
|
15
|
+
{
|
16
|
+
"raw": "3"
|
17
|
+
}
|
18
|
+
],
|
19
|
+
"15": [
|
20
|
+
{
|
21
|
+
"raw": "Boulder"
|
22
|
+
}
|
23
|
+
],
|
24
|
+
"25": [
|
25
|
+
{
|
26
|
+
"raw": "5"
|
27
|
+
}
|
28
|
+
],
|
29
|
+
"27": [
|
30
|
+
{
|
31
|
+
"raw": "30000"
|
32
|
+
}
|
33
|
+
],
|
34
|
+
"40": [
|
35
|
+
{
|
36
|
+
"raw": "1417755155526"
|
37
|
+
}
|
38
|
+
],
|
39
|
+
"43": [
|
40
|
+
{
|
41
|
+
"raw": "ec805c1d1effbc2768ce94108e30eeae74e15931"
|
42
|
+
}
|
43
|
+
],
|
44
|
+
"44": [
|
45
|
+
{
|
46
|
+
"raw": "1"
|
47
|
+
}
|
48
|
+
],
|
49
|
+
"45": [
|
50
|
+
{
|
51
|
+
"raw": "3"
|
52
|
+
}
|
53
|
+
],
|
54
|
+
"46": [
|
55
|
+
{
|
56
|
+
"raw": "18"
|
57
|
+
}
|
58
|
+
]
|
59
|
+
},
|
60
|
+
"linkedItemIds": {
|
61
|
+
"5ff139a60e6220c263558fbc0f26786050a70b34.accounts": [
|
62
|
+
{
|
63
|
+
"itemId": "4d7f9d97f6c00d41fcbfffad489f60f9d8cbfeb1"
|
64
|
+
}
|
65
|
+
],
|
66
|
+
"list.cd669e1c0fa10a6a98639489807851415d3499d1": [
|
67
|
+
{
|
68
|
+
"itemId": "d7e41e76a945d9811aa7981fdd6a4ee5c28fdc05"
|
69
|
+
}
|
70
|
+
]
|
71
|
+
}
|
72
|
+
}
|