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.
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