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