intercom 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -8
  3. data/Gemfile +3 -0
  4. data/README.md +208 -52
  5. data/changes.txt +3 -0
  6. data/intercom.gemspec +2 -2
  7. data/lib/ext/hash.rb +18 -0
  8. data/lib/intercom.rb +38 -43
  9. data/lib/intercom/api_operations/count.rb +16 -0
  10. data/lib/intercom/api_operations/delete.rb +15 -0
  11. data/lib/intercom/api_operations/find.rb +22 -0
  12. data/lib/intercom/api_operations/find_all.rb +33 -0
  13. data/lib/intercom/api_operations/list.rb +17 -0
  14. data/lib/intercom/api_operations/load.rb +15 -0
  15. data/lib/intercom/api_operations/save.rb +44 -0
  16. data/lib/intercom/collection_proxy.rb +66 -0
  17. data/lib/intercom/company.rb +29 -0
  18. data/lib/intercom/conversation.rb +15 -0
  19. data/lib/intercom/count.rb +21 -0
  20. data/lib/intercom/errors.rb +52 -0
  21. data/lib/intercom/event.rb +4 -101
  22. data/lib/intercom/extended_api_operations/reply.rb +16 -0
  23. data/lib/intercom/extended_api_operations/tags.rb +14 -0
  24. data/lib/intercom/extended_api_operations/users.rb +17 -0
  25. data/lib/intercom/generic_handlers/base_handler.rb +22 -0
  26. data/lib/intercom/generic_handlers/count.rb +59 -0
  27. data/lib/intercom/generic_handlers/tag.rb +71 -0
  28. data/lib/intercom/generic_handlers/tag_find_all.rb +47 -0
  29. data/lib/intercom/lib/dynamic_accessors.rb +59 -0
  30. data/lib/intercom/lib/dynamic_accessors_on_method_missing.rb +53 -0
  31. data/lib/intercom/lib/flat_store.rb +31 -0
  32. data/lib/intercom/lib/typed_json_deserializer.rb +52 -0
  33. data/lib/intercom/message.rb +9 -0
  34. data/lib/intercom/note.rb +14 -42
  35. data/lib/intercom/request.rb +40 -4
  36. data/lib/intercom/segment.rb +14 -0
  37. data/lib/intercom/tag.rb +19 -78
  38. data/lib/intercom/traits/api_resource.rb +120 -0
  39. data/lib/intercom/traits/dirty_tracking.rb +33 -0
  40. data/lib/intercom/traits/generic_handler_binding.rb +29 -0
  41. data/lib/intercom/traits/incrementable_attributes.rb +23 -0
  42. data/lib/intercom/user.rb +25 -361
  43. data/lib/intercom/utils.rb +50 -0
  44. data/lib/intercom/version.rb +1 -1
  45. data/spec/spec_helper.rb +64 -33
  46. data/spec/unit/intercom/collection_proxy_spec.rb +34 -0
  47. data/spec/unit/intercom/event_spec.rb +25 -0
  48. data/spec/unit/intercom/{flat_store_spec.rb → lib/flat_store_spec.rb} +7 -7
  49. data/spec/unit/intercom/note_spec.rb +5 -4
  50. data/spec/unit/intercom/tag_spec.rb +3 -3
  51. data/spec/unit/intercom/traits/api_resource_spec.rb +79 -0
  52. data/spec/unit/intercom/user_spec.rb +101 -119
  53. data/spec/unit/intercom_spec.rb +7 -7
  54. metadata +50 -26
  55. data/lib/intercom/flat_store.rb +0 -27
  56. data/lib/intercom/hashable_object.rb +0 -22
  57. data/lib/intercom/impression.rb +0 -63
  58. data/lib/intercom/message_thread.rb +0 -189
  59. data/lib/intercom/requires_parameters.rb +0 -10
  60. data/lib/intercom/social_profile.rb +0 -24
  61. data/lib/intercom/unix_timestamp_unwrapper.rb +0 -12
  62. data/lib/intercom/user_collection_proxy.rb +0 -52
  63. data/lib/intercom/user_resource.rb +0 -82
  64. data/spec/integration/fixtures/v1-user.json +0 -45
  65. data/spec/integration/fixtures/v1-users-impression.json +0 -3
  66. data/spec/integration/fixtures/v1-users-message_thread.json +0 -44
  67. data/spec/integration/fixtures/v1-users-message_threads.json +0 -46
  68. data/spec/integration/fixtures/v1-users-note.json +0 -49
  69. data/spec/integration/fixtures/v1-users.json +0 -144
  70. data/spec/integration/intercom_api_integration_spec.rb +0 -134
  71. data/spec/unit/intercom/impression_spec.rb +0 -18
  72. data/spec/unit/intercom/message_thread_spec.rb +0 -74
  73. data/spec/unit/intercom/user_collection_proxy_spec.rb +0 -46
  74. data/spec/unit/intercom/user_event_spec.rb +0 -83
  75. 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