intercom 1.0.0 → 2.0.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 +4 -4
- data/.travis.yml +0 -8
- data/Gemfile +3 -0
- data/README.md +208 -52
- data/changes.txt +3 -0
- data/intercom.gemspec +2 -2
- data/lib/ext/hash.rb +18 -0
- data/lib/intercom.rb +38 -43
- data/lib/intercom/api_operations/count.rb +16 -0
- data/lib/intercom/api_operations/delete.rb +15 -0
- data/lib/intercom/api_operations/find.rb +22 -0
- data/lib/intercom/api_operations/find_all.rb +33 -0
- data/lib/intercom/api_operations/list.rb +17 -0
- data/lib/intercom/api_operations/load.rb +15 -0
- data/lib/intercom/api_operations/save.rb +44 -0
- data/lib/intercom/collection_proxy.rb +66 -0
- data/lib/intercom/company.rb +29 -0
- data/lib/intercom/conversation.rb +15 -0
- data/lib/intercom/count.rb +21 -0
- data/lib/intercom/errors.rb +52 -0
- data/lib/intercom/event.rb +4 -101
- data/lib/intercom/extended_api_operations/reply.rb +16 -0
- data/lib/intercom/extended_api_operations/tags.rb +14 -0
- data/lib/intercom/extended_api_operations/users.rb +17 -0
- data/lib/intercom/generic_handlers/base_handler.rb +22 -0
- data/lib/intercom/generic_handlers/count.rb +59 -0
- data/lib/intercom/generic_handlers/tag.rb +71 -0
- data/lib/intercom/generic_handlers/tag_find_all.rb +47 -0
- data/lib/intercom/lib/dynamic_accessors.rb +59 -0
- data/lib/intercom/lib/dynamic_accessors_on_method_missing.rb +53 -0
- data/lib/intercom/lib/flat_store.rb +31 -0
- data/lib/intercom/lib/typed_json_deserializer.rb +52 -0
- data/lib/intercom/message.rb +9 -0
- data/lib/intercom/note.rb +14 -42
- data/lib/intercom/request.rb +40 -4
- data/lib/intercom/segment.rb +14 -0
- data/lib/intercom/tag.rb +19 -78
- data/lib/intercom/traits/api_resource.rb +120 -0
- data/lib/intercom/traits/dirty_tracking.rb +33 -0
- data/lib/intercom/traits/generic_handler_binding.rb +29 -0
- data/lib/intercom/traits/incrementable_attributes.rb +23 -0
- data/lib/intercom/user.rb +25 -361
- data/lib/intercom/utils.rb +50 -0
- data/lib/intercom/version.rb +1 -1
- data/spec/spec_helper.rb +64 -33
- data/spec/unit/intercom/collection_proxy_spec.rb +34 -0
- data/spec/unit/intercom/event_spec.rb +25 -0
- data/spec/unit/intercom/{flat_store_spec.rb → lib/flat_store_spec.rb} +7 -7
- data/spec/unit/intercom/note_spec.rb +5 -4
- data/spec/unit/intercom/tag_spec.rb +3 -3
- data/spec/unit/intercom/traits/api_resource_spec.rb +79 -0
- data/spec/unit/intercom/user_spec.rb +101 -119
- data/spec/unit/intercom_spec.rb +7 -7
- metadata +50 -26
- data/lib/intercom/flat_store.rb +0 -27
- data/lib/intercom/hashable_object.rb +0 -22
- data/lib/intercom/impression.rb +0 -63
- data/lib/intercom/message_thread.rb +0 -189
- data/lib/intercom/requires_parameters.rb +0 -10
- data/lib/intercom/social_profile.rb +0 -24
- data/lib/intercom/unix_timestamp_unwrapper.rb +0 -12
- data/lib/intercom/user_collection_proxy.rb +0 -52
- data/lib/intercom/user_resource.rb +0 -82
- data/spec/integration/fixtures/v1-user.json +0 -45
- data/spec/integration/fixtures/v1-users-impression.json +0 -3
- data/spec/integration/fixtures/v1-users-message_thread.json +0 -44
- data/spec/integration/fixtures/v1-users-message_threads.json +0 -46
- data/spec/integration/fixtures/v1-users-note.json +0 -49
- data/spec/integration/fixtures/v1-users.json +0 -144
- data/spec/integration/intercom_api_integration_spec.rb +0 -134
- data/spec/unit/intercom/impression_spec.rb +0 -18
- data/spec/unit/intercom/message_thread_spec.rb +0 -74
- data/spec/unit/intercom/user_collection_proxy_spec.rb +0 -46
- data/spec/unit/intercom/user_event_spec.rb +0 -83
- data/spec/unit/intercom/user_resource_spec.rb +0 -13
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'intercom/traits/api_resource'
|
2
|
+
|
3
|
+
module Intercom
|
4
|
+
module ExtendedApiOperations
|
5
|
+
module Reply
|
6
|
+
|
7
|
+
def reply(reply_data)
|
8
|
+
collection_name = Utils.resource_class_to_collection_name(self.class)
|
9
|
+
# TODO: For server, we should not need to merge in :conversation_id here (already in the URL)
|
10
|
+
response = Intercom.post("/#{collection_name}/#{id}/reply", reply_data.merge(:conversation_id => id))
|
11
|
+
from_response(response)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'intercom/traits/api_resource'
|
2
|
+
|
3
|
+
module Intercom
|
4
|
+
module ExtendedApiOperations
|
5
|
+
module Tags
|
6
|
+
|
7
|
+
def tags
|
8
|
+
collection_name = Utils.resource_class_to_collection_name(self.class)
|
9
|
+
self.id ? Intercom::Tag.send("find_all_for_#{collection_name}", :id => id) : []
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'intercom/traits/api_resource'
|
2
|
+
|
3
|
+
module Intercom
|
4
|
+
module ExtendedApiOperations
|
5
|
+
module Users
|
6
|
+
|
7
|
+
def users
|
8
|
+
collection_name = Utils.resource_class_to_collection_name(self.class)
|
9
|
+
finder_details = {}
|
10
|
+
finder_details[:url] = "/#{collection_name}/#{id}/users"
|
11
|
+
finder_details[:params] = {}
|
12
|
+
CollectionProxy.new("users", finder_details)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Intercom
|
2
|
+
module GenericHandlers
|
3
|
+
class BaseHandler
|
4
|
+
attr_reader :method_sym, :arguments, :entity
|
5
|
+
|
6
|
+
def initialize(method_sym, arguments, entity)
|
7
|
+
@method_sym = method_sym
|
8
|
+
@arguments = arguments
|
9
|
+
@entity = entity
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_string
|
13
|
+
method_sym.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def raise_no_method_missing_handler
|
17
|
+
raise Intercom::NoMethodMissingHandler, "Could not handle '#{method_string}'"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'intercom/generic_handlers/base_handler'
|
2
|
+
|
3
|
+
module Intercom
|
4
|
+
module GenericHandlers
|
5
|
+
module Count
|
6
|
+
module ClassMethods
|
7
|
+
def generic_count(method_sym, *arguments, &block)
|
8
|
+
|
9
|
+
handler_class = Class.new(GenericHandlers::BaseHandler) do
|
10
|
+
def handle
|
11
|
+
match = method_string.match(GenericHandlers::Count.count_breakdown_matcher)
|
12
|
+
if match && match[1] && match[2] && match[3].nil?
|
13
|
+
do_broken_down_count(match[1], match[2])
|
14
|
+
elsif method_string.end_with? '_count'
|
15
|
+
return do_count
|
16
|
+
else
|
17
|
+
raise_no_method_missing_handler
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def do_count
|
24
|
+
entity.fetch_for_app.send(appwide_entity_to_count)['count']
|
25
|
+
rescue Intercom::AttributeNotSetError => ex
|
26
|
+
# Indicates this this kind of counting is not supported
|
27
|
+
raise_no_method_missing_handler
|
28
|
+
end
|
29
|
+
|
30
|
+
def do_broken_down_count(entity_to_count, count_context)
|
31
|
+
result = entity.fetch_broken_down_count(entity_to_count, count_context)
|
32
|
+
result.send(entity_to_count)[count_context]
|
33
|
+
rescue Intercom::BadRequestError => ex
|
34
|
+
# Indicates this this kind of counting is not supported
|
35
|
+
ex.application_error_code == 'parameter_invalid' ? raise_no_method_missing_handler : raise
|
36
|
+
end
|
37
|
+
|
38
|
+
def appwide_entity_to_count; method_string.gsub(/_count$/, ''); end
|
39
|
+
end
|
40
|
+
|
41
|
+
handler_class.new(method_sym, arguments, self).handle
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.count_breakdown_matcher
|
47
|
+
/([[:alnum:]]+)_counts_for_each_([[:alnum:]]+)/
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.handles_method?(method_sym)
|
51
|
+
method_sym.to_s.end_with? '_count' or method_sym.to_s.match(count_breakdown_matcher)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.included(base)
|
55
|
+
base.extend(ClassMethods)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'intercom/generic_handlers/base_handler'
|
2
|
+
|
3
|
+
module Intercom
|
4
|
+
module GenericHandlers
|
5
|
+
module Tag
|
6
|
+
module ClassMethods
|
7
|
+
def generic_tag(method_sym, *arguments, &block)
|
8
|
+
|
9
|
+
handler_class = Class.new(GenericHandlers::BaseHandler) do
|
10
|
+
def handle
|
11
|
+
if method_string.start_with? 'tag_'
|
12
|
+
return do_tagging
|
13
|
+
elsif method_string.start_with? 'untag_'
|
14
|
+
return do_untagging
|
15
|
+
else
|
16
|
+
raise_no_method_missing_handler
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def do_tagging
|
23
|
+
entity.create(:name => arguments[0], tagging_context.to_sym => tag_object_list(arguments))
|
24
|
+
end
|
25
|
+
|
26
|
+
def do_untagging
|
27
|
+
current_tag = find_tag(arguments[0])
|
28
|
+
return untag_via_save(current_tag) if current_tag
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_tag(name_arg)
|
32
|
+
Intercom::Tag.find(:name => name_arg)
|
33
|
+
rescue Intercom::ResourceNotFound
|
34
|
+
return nil # Ignore if tag has since been deleted
|
35
|
+
end
|
36
|
+
|
37
|
+
def untag_via_save(current_tag)
|
38
|
+
current_tag.name = arguments[0]
|
39
|
+
current_tag.send("#{untagging_context}=", untag_object_list(arguments))
|
40
|
+
current_tag.save
|
41
|
+
end
|
42
|
+
|
43
|
+
def tag_object_list(args)
|
44
|
+
to_tag_object_list = args[1].map { |id| { :id => id } }
|
45
|
+
end
|
46
|
+
|
47
|
+
def untag_object_list(args)
|
48
|
+
to_tag = tag_object_list(args)
|
49
|
+
to_tag.map { |tag_object| tag_object[:untag] = true }
|
50
|
+
to_tag
|
51
|
+
end
|
52
|
+
|
53
|
+
def tagging_context; method_string.gsub(/^tag_/, ''); end
|
54
|
+
def untagging_context; method_string.gsub(/^untag_/, ''); end
|
55
|
+
end
|
56
|
+
|
57
|
+
handler_class.new(method_sym, arguments, self).handle
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.handles_method?(method_sym)
|
63
|
+
method_sym.to_s.start_with?('tag_') || method_sym.to_s.start_with?('untag_')
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.included(base)
|
67
|
+
base.extend(ClassMethods)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'intercom/generic_handlers/base_handler'
|
2
|
+
|
3
|
+
module Intercom
|
4
|
+
module GenericHandlers
|
5
|
+
module TagFindAll
|
6
|
+
module ClassMethods
|
7
|
+
def generic_tag_find_all(method_sym, *arguments, &block)
|
8
|
+
|
9
|
+
handler_class = Class.new(GenericHandlers::BaseHandler) do
|
10
|
+
def handle
|
11
|
+
if method_string.start_with? 'find_all_for_'
|
12
|
+
return do_tag_find_all
|
13
|
+
else
|
14
|
+
raise_no_method_missing_handler
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def do_tag_find_all
|
21
|
+
Intercom::Tag.find_all(cleaned_arguments.merge(:taggable_type => Utils.singularize(context)))
|
22
|
+
end
|
23
|
+
|
24
|
+
def cleaned_arguments
|
25
|
+
cleaned_args = arguments[0]
|
26
|
+
cleaned_args[:taggable_id] = cleaned_args.delete(:id) if cleaned_args.has_key?(:id)
|
27
|
+
cleaned_args
|
28
|
+
end
|
29
|
+
|
30
|
+
def context; method_string.gsub(/^find_all_for_/, ''); end
|
31
|
+
end
|
32
|
+
|
33
|
+
handler_class.new(method_sym, arguments, self).handle
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.handles_method?(method_sym)
|
39
|
+
method_sym.to_s.start_with? 'find_all_for_'
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.included(base)
|
43
|
+
base.extend(ClassMethods)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Intercom
|
2
|
+
module Lib
|
3
|
+
module DynamicAccessors
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def define_accessors(attribute, value, object)
|
8
|
+
klass = object.class
|
9
|
+
if attribute.to_s.end_with? '_at'
|
10
|
+
define_date_based_accessors(attribute, value, klass)
|
11
|
+
elsif object.flat_store_attribute?(attribute)
|
12
|
+
define_flat_store_based_accessors(attribute, value, klass)
|
13
|
+
else
|
14
|
+
define_standard_accessors(attribute, value, klass)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def define_flat_store_based_accessors(attribute, value, klass)
|
21
|
+
klass.class_eval %Q"
|
22
|
+
def #{attribute}=(value)
|
23
|
+
mark_field_as_changed!(:#{attribute})
|
24
|
+
@#{attribute} = Intercom::Lib::FlatStore.new(value)
|
25
|
+
end
|
26
|
+
def #{attribute}
|
27
|
+
@#{attribute}
|
28
|
+
end
|
29
|
+
"
|
30
|
+
end
|
31
|
+
|
32
|
+
def define_date_based_accessors(attribute, value, klass)
|
33
|
+
klass.class_eval %Q"
|
34
|
+
def #{attribute}=(value)
|
35
|
+
mark_field_as_changed!(:#{attribute})
|
36
|
+
@#{attribute} = value.nil? ? nil : value.to_i
|
37
|
+
end
|
38
|
+
def #{attribute}
|
39
|
+
@#{attribute}.nil? ? nil : Time.at(@#{attribute})
|
40
|
+
end
|
41
|
+
"
|
42
|
+
end
|
43
|
+
|
44
|
+
def define_standard_accessors(attribute, value, klass)
|
45
|
+
klass.class_eval %Q"
|
46
|
+
def #{attribute}=(value)
|
47
|
+
mark_field_as_changed!(:#{attribute})
|
48
|
+
@#{attribute} = value
|
49
|
+
end
|
50
|
+
def #{attribute}
|
51
|
+
@#{attribute}
|
52
|
+
end
|
53
|
+
"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Intercom
|
2
|
+
module Lib
|
3
|
+
class DynamicAccessorsOnMethodMissing
|
4
|
+
|
5
|
+
attr_reader :method_sym, :method_string, :arguments, :object, :klass
|
6
|
+
|
7
|
+
def initialize(method_sym, *arguments, object)
|
8
|
+
@method_sym = method_sym
|
9
|
+
@method_string = method_sym.to_s
|
10
|
+
@arguments = arguments
|
11
|
+
@klass = object.class
|
12
|
+
@object = object
|
13
|
+
end
|
14
|
+
|
15
|
+
def define_accessors_or_call(&block)
|
16
|
+
return yield if not_an_accessor?
|
17
|
+
if setter?
|
18
|
+
Lib::DynamicAccessors.define_accessors(attribute_name, *arguments, object)
|
19
|
+
object.send(method_sym, *arguments)
|
20
|
+
else # getter
|
21
|
+
if trying_to_access_private_variable?
|
22
|
+
yield
|
23
|
+
else
|
24
|
+
raise Intercom::AttributeNotSetError, attribute_not_set_error_message
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def not_an_accessor?
|
32
|
+
(method_string.end_with? '?') || (method_string.end_with? '!') || arguments.length > 1
|
33
|
+
end
|
34
|
+
|
35
|
+
def setter?
|
36
|
+
method_string.end_with? '='
|
37
|
+
end
|
38
|
+
|
39
|
+
def attribute_name
|
40
|
+
method_string.gsub(/=$/, '')
|
41
|
+
end
|
42
|
+
|
43
|
+
def trying_to_access_private_variable?
|
44
|
+
object.instance_variable_defined?("@#{method_string}")
|
45
|
+
end
|
46
|
+
|
47
|
+
def attribute_not_set_error_message
|
48
|
+
"'#{method_string}' called on #{klass} but it has not been set an " +
|
49
|
+
"attribute or does not exist as a method"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Intercom
|
2
|
+
module Lib
|
3
|
+
|
4
|
+
# Sub-class of {Hash} for storing custom data attributes.
|
5
|
+
# Doesn't allow nested Hashes or Arrays. And requires {String} or {Symbol} keys.
|
6
|
+
class FlatStore < Hash
|
7
|
+
|
8
|
+
def initialize(attributes={})
|
9
|
+
(attributes).each do |key, value|
|
10
|
+
validate_key_and_value(key, value)
|
11
|
+
self[key] = value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def []=(key, value)
|
16
|
+
validate_key_and_value(key, value)
|
17
|
+
super(key.to_s, value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](key)
|
21
|
+
super(key.to_s)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def validate_key_and_value(key, value)
|
26
|
+
raise ArgumentError.new("This does not support nested data structures (key: #{key}, value: #{value}") if value.is_a?(Array) || value.is_a?(Hash)
|
27
|
+
raise ArgumentError.new("Key must be String or Symbol: #{key}") unless key.is_a?(String) || key.is_a?(Symbol)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Intercom
|
2
|
+
module Lib
|
3
|
+
|
4
|
+
# Responsibility: To decide whether we are deserializing a collection or an
|
5
|
+
# entity of a particular type and to dispatch deserialization
|
6
|
+
class TypedJsonDeserializer
|
7
|
+
attr_reader :json
|
8
|
+
|
9
|
+
def initialize(json)
|
10
|
+
@json = json
|
11
|
+
end
|
12
|
+
|
13
|
+
def deserialize
|
14
|
+
if blank_object_type?(object_type)
|
15
|
+
raise DeserializationError, "No type field was found to facilitate deserialization"
|
16
|
+
elsif list_object_type?(object_type)
|
17
|
+
deserialize_collection(json[object_entity_key])
|
18
|
+
else # singular object type
|
19
|
+
deserialize_object(json)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def blank_object_type?(object_type)
|
26
|
+
object_type.nil? || object_type == ''
|
27
|
+
end
|
28
|
+
|
29
|
+
def list_object_type?(object_type)
|
30
|
+
object_type.end_with?('.list')
|
31
|
+
end
|
32
|
+
|
33
|
+
def deserialize_collection(collection_json)
|
34
|
+
collection_json.map { |item_json| TypedJsonDeserializer.new(item_json).deserialize }
|
35
|
+
end
|
36
|
+
|
37
|
+
def deserialize_object(object_json)
|
38
|
+
entity_class = Utils.constantize_singular_resource_name(object_entity_key)
|
39
|
+
entity_class.from_api(object_json)
|
40
|
+
end
|
41
|
+
|
42
|
+
def object_type
|
43
|
+
@object_type ||= json['type']
|
44
|
+
end
|
45
|
+
|
46
|
+
def object_entity_key
|
47
|
+
@object_entity_key ||= Utils.entity_key_from_type(object_type)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|