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
|
+
module Intercom
|
2
|
+
module ApiOperations
|
3
|
+
module Count
|
4
|
+
module ClassMethods
|
5
|
+
def count
|
6
|
+
singular_resource_name = Utils.resource_class_to_singular_name(self)
|
7
|
+
Intercom::Count.send("#{singular_resource_name}_count")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
base.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'intercom/traits/api_resource'
|
2
|
+
|
3
|
+
module Intercom
|
4
|
+
module ApiOperations
|
5
|
+
module Delete
|
6
|
+
|
7
|
+
def delete
|
8
|
+
collection_name = Utils.resource_class_to_collection_name(self.class)
|
9
|
+
Intercom.delete("/#{collection_name}/#{id}", {})
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Intercom
|
2
|
+
module ApiOperations
|
3
|
+
module Find
|
4
|
+
module ClassMethods
|
5
|
+
def find(params)
|
6
|
+
raise BadRequestError, "#{self}#find takes a hash as it's parameter but you supplied #{params.inspect}" unless params.is_a? Hash
|
7
|
+
collection_name = Utils.resource_class_to_collection_name(self)
|
8
|
+
if params[:id]
|
9
|
+
response = Intercom.get("/#{collection_name}/#{params[:id]}", {})
|
10
|
+
else
|
11
|
+
response = Intercom.get("/#{collection_name}", params)
|
12
|
+
end
|
13
|
+
from_api(response)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
base.extend(ClassMethods)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'intercom/collection_proxy'
|
2
|
+
|
3
|
+
module Intercom
|
4
|
+
module ApiOperations
|
5
|
+
module FindAll
|
6
|
+
module ClassMethods
|
7
|
+
def find_all(params)
|
8
|
+
raise BadRequestError, "#{self}#find takes a hash as it's parameter but you supplied #{params.inspect}" unless params.is_a? Hash
|
9
|
+
collection_name = Utils.resource_class_to_collection_name(self)
|
10
|
+
finder_details = {}
|
11
|
+
if params[:id] && !type_switched_finder?(params)
|
12
|
+
finder_details[:url] = "/#{collection_name}/#{params[:id]}"
|
13
|
+
finder_details[:params] = {}
|
14
|
+
else
|
15
|
+
finder_details[:url] = "/#{collection_name}"
|
16
|
+
finder_details[:params] = params
|
17
|
+
end
|
18
|
+
CollectionProxy.new(collection_name, finder_details)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def type_switched_finder?(params)
|
24
|
+
params.include?(:type)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.included(base)
|
29
|
+
base.extend(ClassMethods)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'intercom/collection_proxy'
|
2
|
+
|
3
|
+
module Intercom
|
4
|
+
module ApiOperations
|
5
|
+
module List # TODO: Should we rename to All
|
6
|
+
module ClassMethods
|
7
|
+
def all
|
8
|
+
CollectionProxy.new(Utils.resource_class_to_collection_name(self))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Intercom
|
2
|
+
module ApiOperations
|
3
|
+
module Load
|
4
|
+
def load
|
5
|
+
collection_name = Utils.resource_class_to_collection_name(self.class)
|
6
|
+
if id
|
7
|
+
response = Intercom.get("/#{collection_name}/#{id}", {})
|
8
|
+
else
|
9
|
+
raise "Cannot load #{self.class} as it does not have a valid id."
|
10
|
+
end
|
11
|
+
from_response(response)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'intercom/traits/api_resource'
|
2
|
+
|
3
|
+
module Intercom
|
4
|
+
module ApiOperations
|
5
|
+
module Save
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def create(params)
|
9
|
+
instance = self.new(params)
|
10
|
+
instance.mark_fields_as_changed!(params.keys)
|
11
|
+
instance.save
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.included(base)
|
16
|
+
base.extend(ClassMethods)
|
17
|
+
end
|
18
|
+
|
19
|
+
def save
|
20
|
+
collection_name = Utils.resource_class_to_collection_name(self.class)
|
21
|
+
if id_present? && !posted_updates?
|
22
|
+
response = Intercom.put("/#{collection_name}/#{id}", to_submittable_hash)
|
23
|
+
else
|
24
|
+
response = Intercom.post("/#{collection_name}", to_submittable_hash.merge(identity_hash))
|
25
|
+
end
|
26
|
+
from_response(response) if response # may be nil we received back a 202
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def id_present?
|
32
|
+
id && id.to_s != ''
|
33
|
+
end
|
34
|
+
|
35
|
+
def posted_updates?
|
36
|
+
respond_to?(:update_verb) && update_verb == 'post'
|
37
|
+
end
|
38
|
+
|
39
|
+
def identity_hash
|
40
|
+
respond_to?(:identity_vars) ? to_hash.slice(*(identity_vars.map(&:to_s))) : {}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "intercom/utils"
|
2
|
+
require "ext/hash"
|
3
|
+
|
4
|
+
module Intercom
|
5
|
+
class CollectionProxy
|
6
|
+
|
7
|
+
attr_reader :resource_name
|
8
|
+
|
9
|
+
def initialize(resource_name, finder_details = {})
|
10
|
+
@resource_name = resource_name
|
11
|
+
@resource_class = Utils.constantize_resource_name(resource_name)
|
12
|
+
@finder_url = (finder_details[:url] || "/#{@resource_name}")
|
13
|
+
@finder_params = (finder_details[:params] || {})
|
14
|
+
end
|
15
|
+
|
16
|
+
def each(&block)
|
17
|
+
next_page = nil
|
18
|
+
loop do
|
19
|
+
if next_page
|
20
|
+
response_hash = Intercom.get(next_page, {})
|
21
|
+
else
|
22
|
+
response_hash = Intercom.get(@finder_url, @finder_params)
|
23
|
+
end
|
24
|
+
deserialize_response_hash(response_hash, block)
|
25
|
+
next_page = extract_next_link(response_hash)
|
26
|
+
break if next_page.nil?
|
27
|
+
end
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](target_index)
|
32
|
+
self.each_with_index do |item, index|
|
33
|
+
return item if index == target_index
|
34
|
+
end
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
include Enumerable
|
39
|
+
|
40
|
+
def count
|
41
|
+
raise NoMethodError, "undefined method `count' for #{self.class}. Consider using the dedicated Intercom::Count interface if suitable"
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def resource_class; @resource_class; end
|
47
|
+
|
48
|
+
def deserialize_response_hash(response_hash, block)
|
49
|
+
top_level_type = response_hash.delete('type')
|
50
|
+
top_level_entity_key = Utils.entity_key_from_type(top_level_type)
|
51
|
+
response_hash[top_level_entity_key].each do |object_json|
|
52
|
+
block.call Lib::TypedJsonDeserializer.new(object_json).deserialize
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def paging_info_present?(response_hash)
|
57
|
+
!!(response_hash['pages'] && response_hash['pages']['type'])
|
58
|
+
end
|
59
|
+
|
60
|
+
def extract_next_link(response_hash)
|
61
|
+
return nil unless paging_info_present?(response_hash)
|
62
|
+
paging_info = response_hash.delete('pages')
|
63
|
+
paging_info["next"]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'intercom/api_operations/count'
|
2
|
+
require 'intercom/api_operations/list'
|
3
|
+
require 'intercom/api_operations/find'
|
4
|
+
require 'intercom/api_operations/find_all'
|
5
|
+
require 'intercom/api_operations/save'
|
6
|
+
require 'intercom/api_operations/load'
|
7
|
+
require 'intercom/extended_api_operations/users'
|
8
|
+
require 'intercom/extended_api_operations/tags'
|
9
|
+
require 'intercom/traits/incrementable_attributes'
|
10
|
+
require 'intercom/traits/api_resource'
|
11
|
+
|
12
|
+
module Intercom
|
13
|
+
class Company
|
14
|
+
include ApiOperations::Count
|
15
|
+
include ApiOperations::List
|
16
|
+
include ApiOperations::Find
|
17
|
+
include ApiOperations::FindAll
|
18
|
+
include ApiOperations::Save
|
19
|
+
include ApiOperations::Load
|
20
|
+
include ExtendedApiOperations::Users
|
21
|
+
include ExtendedApiOperations::Tags
|
22
|
+
include Traits::IncrementableAttributes
|
23
|
+
include Traits::ApiResource
|
24
|
+
|
25
|
+
def identity_vars ; [:id, :company_id] ; end
|
26
|
+
def flat_store_attributes ; [:custom_attributes] ; end
|
27
|
+
def update_verb ; 'post' ; end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'intercom/extended_api_operations/reply'
|
2
|
+
require 'intercom/api_operations/find_all'
|
3
|
+
require 'intercom/api_operations/find'
|
4
|
+
require 'intercom/api_operations/load'
|
5
|
+
require 'intercom/traits/api_resource'
|
6
|
+
|
7
|
+
module Intercom
|
8
|
+
class Conversation
|
9
|
+
include ExtendedApiOperations::Reply
|
10
|
+
include ApiOperations::FindAll
|
11
|
+
include ApiOperations::Find
|
12
|
+
include ApiOperations::Load
|
13
|
+
include Traits::ApiResource
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'intercom/traits/api_resource'
|
2
|
+
require 'intercom/api_operations/find'
|
3
|
+
require 'intercom/traits/generic_handler_binding'
|
4
|
+
require 'intercom/generic_handlers/count'
|
5
|
+
|
6
|
+
module Intercom
|
7
|
+
class Count
|
8
|
+
include ApiOperations::Find
|
9
|
+
include Traits::ApiResource
|
10
|
+
include Traits::GenericHandlerBinding
|
11
|
+
include GenericHandlers::Count
|
12
|
+
|
13
|
+
def self.fetch_for_app
|
14
|
+
Intercom::Count.find({})
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.fetch_broken_down_count(entity_to_count, count_context)
|
18
|
+
Intercom::Count.find(:type => entity_to_count, :count => count_context)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Intercom
|
2
|
+
|
3
|
+
# Base class exception from which all public Intercom exceptions will be derived
|
4
|
+
class IntercomError < StandardError
|
5
|
+
attr_reader :http_code, :application_error_code
|
6
|
+
def initialize(message, http_code: nil, application_error_code: application_error_code)
|
7
|
+
@http_code = http_code
|
8
|
+
@application_error_code = application_error_code
|
9
|
+
super(message)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Raised when the credentials you provide don't match a valid account on Intercom.
|
14
|
+
# Check that you have set <b>Intercom.app_id=</b> and <b>Intercom.app_api_key=</b> correctly.
|
15
|
+
class AuthenticationError < IntercomError; end
|
16
|
+
|
17
|
+
# Raised when something does wrong on within the Intercom API service.
|
18
|
+
class ServerError < IntercomError; end
|
19
|
+
|
20
|
+
# Raised when we have bad gateway errors.
|
21
|
+
class BadGatewayError < IntercomError; end
|
22
|
+
|
23
|
+
# Raised when we reach socket connect timeout
|
24
|
+
class ServiceUnavailableError < IntercomError; end
|
25
|
+
|
26
|
+
# Raised when requesting resources on behalf of a user that doesn't exist in your application on Intercom.
|
27
|
+
class ResourceNotFound < IntercomError; end
|
28
|
+
|
29
|
+
# Raised when the request has a bad syntax
|
30
|
+
class BadRequestError < IntercomError; end
|
31
|
+
|
32
|
+
# Raised when you have exceed the API rate limit
|
33
|
+
class RateLimitExceeded < IntercomError; end
|
34
|
+
|
35
|
+
# Raised when the request throws an error not accounted for
|
36
|
+
class UnexpectedError < IntercomError; end
|
37
|
+
|
38
|
+
# Raised when you try to call a non-setter method that does not exist on an object
|
39
|
+
class Intercom::AttributeNotSetError < IntercomError ; end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Non-public errors (internal to the gem)
|
43
|
+
#
|
44
|
+
|
45
|
+
# Base class exception from which all public Intercom exceptions will be derived
|
46
|
+
class IntercomInternalError < StandardError; end
|
47
|
+
|
48
|
+
# Raised when we attempt to handle a method missing but are unsuccessful
|
49
|
+
class Intercom::NoMethodMissingHandler < IntercomInternalError; end
|
50
|
+
|
51
|
+
class Intercom::DeserializationError < IntercomInternalError; end
|
52
|
+
end
|
data/lib/intercom/event.rb
CHANGED
@@ -1,106 +1,9 @@
|
|
1
|
-
require 'intercom/
|
2
|
-
require 'intercom/
|
3
|
-
|
1
|
+
require 'intercom/api_operations/save'
|
2
|
+
require 'intercom/traits/api_resource'
|
4
3
|
|
5
4
|
module Intercom
|
6
|
-
|
7
|
-
##
|
8
|
-
# Represents an Event
|
9
|
-
#
|
10
|
-
# An event consists of an event_name and a user the event applies to. The user is identified via email or id.
|
11
|
-
# Additionally, a created timestamp is required.
|
12
|
-
#
|
13
|
-
# == Examples
|
14
|
-
#
|
15
|
-
# event = Intercom::Event.create(:event_name => "post", :user => current_user, :created_at => Time.now)
|
16
|
-
#
|
17
|
-
# You can also create an user-event and save it like this:
|
18
|
-
# event = Intercom::Event.new
|
19
|
-
# event.event_name = "publish-post"
|
20
|
-
# event.user = current_user
|
21
|
-
# event.created_at = Time.now
|
22
|
-
# event.metadata = {
|
23
|
-
# :title => 'Gravity Review',
|
24
|
-
# :link => 'https://example.org/posts/22',
|
25
|
-
# :comments => 'https://example.org/posts/22/comments'
|
26
|
-
# }
|
27
|
-
# event.save
|
28
|
-
#
|
29
|
-
# == Batch
|
30
|
-
#
|
31
|
-
# Events can be created in batches, and sent as one request. To do some, create events
|
32
|
-
# without calling .create, as follows:
|
33
|
-
#
|
34
|
-
# event = Intercom::Event.new
|
35
|
-
# event.event_name = "publish-post"
|
36
|
-
# event.user = current_user
|
37
|
-
#
|
38
|
-
# Then pass them to the save_batch_events class method, along with an (optional) default user:
|
39
|
-
#
|
40
|
-
# Intercom::Event.save_batch_events(events, default_user)
|
41
|
-
#
|
42
|
-
# Any events without a user will be assigned to the default_user.
|
43
|
-
#
|
44
|
-
# Note: if you do not supply a created time, the current time in UTC will be used. Events that have the same
|
45
|
-
# user, name, and created time (to second granularity) may be treated as duplicates by the server.
|
46
|
-
|
47
5
|
class Event
|
48
|
-
|
49
|
-
include
|
50
|
-
|
51
|
-
attr_accessor :event_name, :user, :created_at # required
|
52
|
-
attr_accessor :metadata, :type
|
53
|
-
|
54
|
-
def initialize(attributes={})
|
55
|
-
from_hash(attributes)
|
56
|
-
end
|
57
|
-
|
58
|
-
##
|
59
|
-
# Creates a new Event using params and saves it
|
60
|
-
# @see #save
|
61
|
-
def self.create(params)
|
62
|
-
params[:created_at] ||= Time.now
|
63
|
-
requires_parameters(params, %W(event_name user created_at))
|
64
|
-
Event.new(params).save
|
65
|
-
end
|
66
|
-
|
67
|
-
##
|
68
|
-
# Save the Event
|
69
|
-
def save
|
70
|
-
raise ArgumentError.new("Missing User") if user.nil?
|
71
|
-
Event.save_batch_events([self])
|
72
|
-
self
|
73
|
-
end
|
74
|
-
|
75
|
-
##
|
76
|
-
# Save a list of Events, with an optional base_user
|
77
|
-
def self.save_batch_events(events, base_user=nil)
|
78
|
-
hash = { :type => 'event.list', :data => []}
|
79
|
-
hash[:user] = user_hash(base_user) if base_user
|
80
|
-
events.each do |event|
|
81
|
-
hash[:data] << event.event_hash
|
82
|
-
end
|
83
|
-
post_to_intercom(hash)
|
84
|
-
end
|
85
|
-
|
86
|
-
def self.user_hash(user)
|
87
|
-
user.user_id ? { :user_id => user.user_id } : { :email => user.email }
|
88
|
-
end
|
89
|
-
|
90
|
-
def self.post_to_intercom(hash)
|
91
|
-
Intercom.post('/events', hash)
|
92
|
-
end
|
93
|
-
|
94
|
-
def user_hash
|
95
|
-
Event.user_hash(user)
|
96
|
-
end
|
97
|
-
|
98
|
-
def event_hash
|
99
|
-
event = { :event_name => event_name, :created => created_at.nil? ? Time.now.utc.to_i : created_at.to_i }
|
100
|
-
event[:type] = type.nil? ? 'event' : type
|
101
|
-
event[:user] = user_hash unless user.nil?
|
102
|
-
event[:metadata] = metadata unless metadata.nil?
|
103
|
-
event
|
104
|
-
end
|
6
|
+
include ApiOperations::Save
|
7
|
+
include Traits::ApiResource
|
105
8
|
end
|
106
9
|
end
|