contextio 0.5.0 → 1.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 (65) hide show
  1. data/.document +4 -0
  2. data/.gitignore +8 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -0
  5. data/ChangeLog.md +5 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE.md +20 -0
  8. data/README.md +62 -22
  9. data/Rakefile +46 -36
  10. data/contextio.gemspec +30 -0
  11. data/lib/contextio.rb +69 -583
  12. data/lib/contextio/account.rb +132 -0
  13. data/lib/contextio/account_collection.rb +57 -0
  14. data/lib/contextio/account_sync_data.rb +22 -0
  15. data/lib/contextio/api.rb +162 -0
  16. data/lib/contextio/api/association_helpers.rb +17 -0
  17. data/lib/contextio/api/resource.rb +230 -0
  18. data/lib/contextio/api/resource_collection.rb +174 -0
  19. data/lib/contextio/api/url_builder.rb +153 -0
  20. data/lib/contextio/body_part.rb +45 -0
  21. data/lib/contextio/body_part_collection.rb +13 -0
  22. data/lib/contextio/connect_token.rb +57 -0
  23. data/lib/contextio/connect_token_collection.rb +44 -0
  24. data/lib/contextio/contact.rb +43 -0
  25. data/lib/contextio/contact_collection.rb +21 -0
  26. data/lib/contextio/email_address.rb +53 -0
  27. data/lib/contextio/email_address_collection.rb +21 -0
  28. data/lib/contextio/email_settings.rb +146 -0
  29. data/lib/contextio/file.rb +92 -0
  30. data/lib/contextio/file_collection.rb +13 -0
  31. data/lib/contextio/folder.rb +56 -0
  32. data/lib/contextio/folder_collection.rb +18 -0
  33. data/lib/contextio/folder_sync_data.rb +32 -0
  34. data/lib/contextio/message.rb +96 -0
  35. data/lib/contextio/message_collection.rb +35 -0
  36. data/lib/contextio/oauth_provider.rb +29 -0
  37. data/lib/contextio/oauth_provider_collection.rb +46 -0
  38. data/lib/contextio/source.rb +55 -0
  39. data/lib/contextio/source_collection.rb +41 -0
  40. data/lib/contextio/source_sync_data.rb +23 -0
  41. data/lib/contextio/thread.rb +15 -0
  42. data/lib/contextio/thread_collection.rb +25 -0
  43. data/lib/contextio/version.rb +11 -0
  44. data/lib/contextio/webhook.rb +39 -0
  45. data/lib/contextio/webhook_collection.rb +26 -0
  46. data/spec/config.yml.example +3 -0
  47. data/spec/contextio/account_collection_spec.rb +78 -0
  48. data/spec/contextio/account_spec.rb +52 -0
  49. data/spec/contextio/api/association_helpers_spec.rb +28 -0
  50. data/spec/contextio/api/resource_collection_spec.rb +286 -0
  51. data/spec/contextio/api/resource_spec.rb +467 -0
  52. data/spec/contextio/api/url_builder_spec.rb +78 -0
  53. data/spec/contextio/api_spec.rb +123 -0
  54. data/spec/contextio/connect_token_collection_spec.rb +74 -0
  55. data/spec/contextio/connect_token_spec.rb +58 -0
  56. data/spec/contextio/email_settings_spec.rb +112 -0
  57. data/spec/contextio/oauth_provider_collection_spec.rb +36 -0
  58. data/spec/contextio/oauth_provider_spec.rb +120 -0
  59. data/spec/contextio/source_collection_spec.rb +57 -0
  60. data/spec/contextio/source_spec.rb +52 -0
  61. data/spec/contextio/version_spec.rb +10 -0
  62. data/spec/contextio_spec.rb +64 -0
  63. data/spec/spec_helper.rb +17 -0
  64. metadata +234 -12
  65. data/README.textile +0 -29
@@ -0,0 +1,174 @@
1
+ require_relative 'association_helpers'
2
+
3
+ class ContextIO
4
+ class API
5
+ # When `include`d into a class, this module provides some helper methods for
6
+ # various things a collections of resources will need or find useful.
7
+ module ResourceCollection
8
+ include Enumerable
9
+
10
+ # (see ContextIO#api)
11
+ attr_reader :api
12
+
13
+ # @!attribute [r] where_constraints
14
+ # A Hash of the constraints limiting this collection of resources.
15
+ attr_reader :where_constraints
16
+
17
+ # @private
18
+ #
19
+ # For internal use only. Users of this gem shouldn't be calling this
20
+ # directly.
21
+ #
22
+ # @param [API] api A handle on the Context.IO API.
23
+ # @param [Hash] options Optional params for the collection.
24
+ # @option options [Hash{Symbol => String, Numeric}] :where Where
25
+ # constraints that limit the resources that belong to this collection.
26
+ # @option options [Array<Hash>] :attribute_hashes An array of hashes
27
+ # describing the resources in this collection.
28
+ def initialize(api, options={})
29
+ @api = api
30
+ @where_constraints = options[:where] || {}
31
+ @attribute_hashes = options[:attribute_hashes]
32
+
33
+ self.class.associations.each do |association_name|
34
+ instance_variable_set("@#{association_name}", options[association_name.to_sym])
35
+ end
36
+ end
37
+
38
+ # @!attribute [r] resource_url
39
+ # @return [String] The URL that will fetch attributes from the API.
40
+ def resource_url
41
+ @resource_url ||= api.url_for(self)
42
+ end
43
+
44
+ # Iterates over the resources in question.
45
+ #
46
+ # @example
47
+ # contextio.connect_tokens.each do |connect_token|
48
+ # puts connect_token.email
49
+ # end
50
+ def each(&block)
51
+ attribute_hashes.each do |attribute_hash|
52
+ yield resource_class.new(api, attribute_hash.merge(associations_hash))
53
+ end
54
+ end
55
+
56
+ # Specify one or more constraints for limiting resources in this
57
+ # collection. See individual classes for the list of valid constraints.
58
+ # Not all collections have valid where constraints at all.
59
+ #
60
+ # This can be chained at need and doesn't actually cause the API to get
61
+ # hit until some iterator is called like `#each`.
62
+ #
63
+ # @example
64
+ # accounts = contextio.accounts
65
+ # accounts = accounts.where(email: 'some@email.com')
66
+ # accounts = accounts.where(status: 'OK')
67
+ #
68
+ # accounts.each do |account|
69
+ # # API gets hit for this call
70
+ # end
71
+ #
72
+ # @param [Hash{String, Symbol => String, Integer}] constraints A Hash
73
+ # mapping keys to the desired limiting values.
74
+ def where(constraints)
75
+ self.class.new(api, associations_hash.merge(where: where_constraints.merge(constraints)))
76
+ end
77
+
78
+ # Returns a resource with the given key.
79
+ #
80
+ # This is a lazy method, making no requests. When you try to access
81
+ # attributes on the object, or otherwise interact with it, it will actually
82
+ # make requests.
83
+ #
84
+ # @example
85
+ # provider = contextio.oauth_providers['1234']
86
+ #
87
+ # @param [String] key The Provider Consumer Key for the
88
+ # provider you want to interact with.
89
+ def [](key)
90
+ resource_class.new(api, resource_class.primary_key => key)
91
+ end
92
+
93
+ private
94
+
95
+ # @!attribute [r] attribute_hashes
96
+ # @return [Array<Hash>] An array of attribute hashes that describe, at
97
+ # least partially, the objects in this collection.
98
+ def attribute_hashes
99
+ @attribute_hashes ||= api.request(:get, resource_url, where_constraints)
100
+ end
101
+
102
+ # @!attribute [r] associations_hash
103
+ # @return [Hash{Symbol => Resource}] A hash of association names to the
104
+ # associated resource of that type.
105
+ def associations_hash
106
+ @associations_hash ||= self.class.associations.inject({}) do |memo, association_name|
107
+ memo[association_name.to_sym] = self.send(association_name)
108
+ memo
109
+ end
110
+ end
111
+
112
+ # Make sure a ResourceCollection has the declarative syntax handy.
113
+ def self.included(other_mod)
114
+ other_mod.extend(DeclarativeClassSyntax)
115
+ end
116
+
117
+ # This module contains helper methods for `API::ResourceCollection`s'
118
+ # class definitions. It gets `extend`ed into a class when
119
+ # `API::ResourceCollection` is `include`d.
120
+ module DeclarativeClassSyntax
121
+ # @!attribute [r] associations
122
+ # @return [Array<String] An array of the belong_to associations for
123
+ # the collection
124
+ def associations
125
+ @associations ||= []
126
+ end
127
+
128
+ # @!attribute [r] association_name
129
+ # @return [Symbol] The association name registered for this resource.
130
+ def association_name
131
+ @association_name
132
+ end
133
+
134
+ private
135
+
136
+ # Declares which class the `ResourceCollection` is intended to wrap. For
137
+ # best results, this should probably be a `Resource`. It defines an
138
+ # accessor for this class on instances of the collection, which is
139
+ # private. Make sure your collection class has required the file with
140
+ # the defeniiton of the class it wraps.
141
+ #
142
+ # @param [Class] klass The class that the collection, well, collects.
143
+ def resource_class=(klass)
144
+ define_method(:resource_class) do
145
+ klass
146
+ end
147
+ end
148
+
149
+ # Declares which class, if any, the collection belongs to. It defines an
150
+ # accessor for the belonged-to object.
151
+ #
152
+ # @param [Symbol] association_name The name of the association for the
153
+ # class in question. Singular classes will have singular names
154
+ # registered. For instance, :message should reger to the Message
155
+ # resource.
156
+ def belongs_to(association_name)
157
+ define_method(association_name) do
158
+ instance_variable_get("@#{association_name}")
159
+ end
160
+
161
+ associations << association_name
162
+ end
163
+
164
+ # Declares the association name for the resource.
165
+ #
166
+ # @param [String, Symbol] association_name The name.
167
+ def association_name=(association_name)
168
+ @association_name = association_name.to_sym
169
+ ContextIO::API::AssociationHelpers.register_resource(self, @association_name)
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,153 @@
1
+ require 'contextio/connect_token_collection'
2
+ require 'contextio/oauth_provider_collection'
3
+ require 'contextio/email_settings'
4
+ require 'contextio/account_collection'
5
+ require 'contextio/source_collection'
6
+ require 'contextio/folder_collection'
7
+ require 'contextio/message_collection'
8
+ require 'contextio/body_part_collection'
9
+ require 'contextio/thread_collection'
10
+ require 'contextio/webhook_collection'
11
+ require 'contextio/email_address_collection'
12
+ require 'contextio/contact_collection'
13
+ require 'contextio/file_collection'
14
+
15
+ class ContextIO
16
+ class API
17
+ class URLBuilder
18
+ class Error < StandardError; end
19
+
20
+ # Tells you the right URL for a resource to fetch attributes from.
21
+ #
22
+ # @param [ContextIO::Resource, ContextIO::ResourceCollection] resource The
23
+ # resource or resource collection.
24
+ #
25
+ # @return [String] The path for that resource in the API.
26
+ def self.url_for(resource)
27
+ if (builder = @registered_urls[resource.class])
28
+ builder.call(resource)
29
+ else
30
+ raise Error, "URL could not be built for unregistered Class: #{resource.class}."
31
+ end
32
+ end
33
+
34
+ # Register a block that calculates the URL for a given resource.
35
+ #
36
+ # @param [Class] resource_class The class of the resource you are
37
+ # registering.
38
+ # @param [Block] block The code that will compute the url for the
39
+ # resource. This is actually a path. Start after the version number of
40
+ # the API in the URL. When a URL is being calculated for a specific
41
+ # resource, the resource instance will be yielded to the block.
42
+ #
43
+ # @example For Accounts
44
+ # register_url ContextIO::Account do |account|
45
+ # "accounts/#{account.id}"
46
+ # end
47
+ def self.register_url(resource_class, &block)
48
+ @registered_urls ||= {}
49
+ @registered_urls[resource_class] = block
50
+ end
51
+
52
+ register_url ContextIO::ConnectToken do |connect_token|
53
+ if connect_token.account && connect_token.account.id
54
+ "accounts/#{connect_token.account.id}/connect_tokens/#{connect_token.token}"
55
+ else
56
+ "connect_tokens/#{connect_token.token}"
57
+ end
58
+ end
59
+
60
+ register_url ContextIO::ConnectTokenCollection do |connect_tokens|
61
+ if connect_tokens.account && connect_tokens.account.id
62
+ "accounts/#{connect_tokens.account.id}/connect_tokens"
63
+ else
64
+ 'connect_tokens'
65
+ end
66
+ end
67
+
68
+ register_url ContextIO::OAuthProvider do |oauth_provider|
69
+ "oauth_providers/#{oauth_provider.provider_consumer_key}"
70
+ end
71
+
72
+ register_url ContextIO::OAuthProviderCollection do
73
+ 'oauth_providers'
74
+ end
75
+
76
+ register_url ContextIO::EmailSettings do
77
+ 'discovery'
78
+ end
79
+
80
+ register_url ContextIO::Account do |account|
81
+ "accounts/#{account.id}"
82
+ end
83
+
84
+ register_url ContextIO::AccountCollection do
85
+ 'accounts'
86
+ end
87
+
88
+ register_url ContextIO::Source do |source|
89
+ "accounts/#{source.account.id}/sources/#{source.label}"
90
+ end
91
+
92
+ register_url ContextIO::SourceCollection do |sources|
93
+ "accounts/#{sources.account.id}/sources"
94
+ end
95
+
96
+ register_url ContextIO::FolderCollection do |folders|
97
+ "accounts/#{folders.source.account.id}/sources/#{folders.source.label}/folders"
98
+ end
99
+
100
+ register_url ContextIO::Message do |message|
101
+ "accounts/#{message.account.id}/messages/#{message.message_id}"
102
+ end
103
+
104
+ register_url ContextIO::MessageCollection do |messages|
105
+ "accounts/#{messages.account.id}/messages"
106
+ end
107
+
108
+ register_url ContextIO::BodyPartCollection do |parts|
109
+ "accounts/#{parts.message.account.id}/messages/#{parts.message.message_id}/body"
110
+ end
111
+
112
+ register_url ContextIO::Thread do |thread|
113
+ "accounts/#{thread.account.id}/threads/#{thread.gmail_thread_id}"
114
+ end
115
+
116
+ register_url ContextIO::ThreadCollection do |threads|
117
+ "accounts/#{threads.account.id}/threads"
118
+ end
119
+
120
+ register_url ContextIO::Webhook do |webhook|
121
+ "accounts/#{webhook.account.id}/webhooks/#{webhook.webhook_id}"
122
+ end
123
+
124
+ register_url ContextIO::WebhookCollection do |webhooks|
125
+ "accounts/#{webhooks.account.id}/webhooks"
126
+ end
127
+
128
+ register_url ContextIO::EmailAddress do |email_address|
129
+ "accounts/#{email_address.account.id}/email_addresses/#{email_address.email}"
130
+ end
131
+
132
+ register_url ContextIO::EmailAddressCollection do |email_addresses|
133
+ "accounts/#{email_addresses.account.id}/email_addresses"
134
+ end
135
+
136
+ register_url ContextIO::Contact do |contact|
137
+ "accounts/#{contact.account.id}/contacts/#{contact.email}"
138
+ end
139
+
140
+ register_url ContextIO::ContactCollection do |contacts|
141
+ "accounts/#{contacts.account.id}/contacts"
142
+ end
143
+
144
+ register_url ContextIO::File do |file|
145
+ "accounts/#{file.account.id}/files/#{file.file_id}"
146
+ end
147
+
148
+ register_url ContextIO::FileCollection do |files|
149
+ "accounts/#{files.account.id}/files"
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,45 @@
1
+ require 'contextio/api/association_helpers'
2
+
3
+ class ContextIO
4
+ class BodyPart
5
+ def self.association_name
6
+ :body_part
7
+ end
8
+ ContextIO::API::AssociationHelpers.register_resource(self, :body_part)
9
+
10
+ # (see ContextIO#api)
11
+ attr_reader :api, :message, :type, :charset, :delsp, :format, :content,
12
+ :body_section
13
+
14
+ # @private
15
+ #
16
+ # For internal use only. Users of this gem shouldn't be calling this
17
+ # directly.
18
+ #
19
+ # @param [API] api A handle on the Context.IO API.
20
+ # @param [Hash{String, Symbol => String, Numeric, Boolean}] options A Hash
21
+ # of attributes describing the resource.
22
+ def initialize(api, options = {})
23
+ @api = api
24
+ @message = options.delete(:message) || options.delete('message')
25
+
26
+ options.each do |key, value|
27
+ instance_variable_set("@#{key}", value)
28
+
29
+ unless self.respond_to?(key)
30
+ define_singleton_method(key) do
31
+ instance_variable_get("@#{key}")
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def html?
38
+ type == 'text/html'
39
+ end
40
+
41
+ def plain_text?
42
+ type == 'text/plain'
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'api/resource_collection'
2
+ require_relative 'body_part'
3
+
4
+ class ContextIO
5
+ class BodyPartCollection
6
+ include ContextIO::API::ResourceCollection
7
+
8
+ self.resource_class = ContextIO::BodyPart
9
+ self.association_name = :body_parts
10
+
11
+ belongs_to :message
12
+ end
13
+ end
@@ -0,0 +1,57 @@
1
+ require 'contextio/api/resource'
2
+
3
+ class ContextIO
4
+ # Represents a single connect token for an account. You can use this to
5
+ # inspect or delete the token. Most of the attributes are lazily loaded,
6
+ # meaning that the API won't get hit until you ask for an attribute the object
7
+ # doesn't already have (presumably from a previous API call).
8
+ class ConnectToken
9
+ include API::Resource
10
+
11
+ self.primary_key = :token
12
+ self.association_name = :connect_token
13
+
14
+ # @!attribute [r] account
15
+ #
16
+ # @return [ContextIO::Account, nil] The Account associated with this token,
17
+ # if any. Will fetch from the API if necessary.
18
+ belongs_to :account
19
+
20
+ # @!attribute [r] token
21
+ # @return [String] The token associated with this connect token. Will
22
+ # fetch from the API if necessary.
23
+ # @!attribute [r] email
24
+ # @return [String] The email address associated with this token. Will
25
+ # fetch from the API if necessary.
26
+ # @!attribute [r] used
27
+ # @return [Boolean] Whether this token has been used ot not. Will fetch
28
+ # from the API if necessary.
29
+ # @!attribute [r] callback_url
30
+ # @return [String] The url that Context.IO will redirect the user to when
31
+ # the account is created. Will fetch from the API if necessary.
32
+ # @!attribute [r] service_level
33
+ # @return [String] The Context.IO service level for this account. Will
34
+ # fetch from the API if necessary.
35
+ # @!attribute [r] first_name
36
+ # @return [String] The first name of the owner of the email account
37
+ # associated with this token. Will fetch from the API if necessary.
38
+ # @!attribute [r] last_name
39
+ # @return [String] The last name of the owner of the email account
40
+ # associated with this token. Will fetch from the API if necessary.
41
+ lazy_attributes :token, :email, :created, :used, :callback_url,
42
+ :service_level, :first_name, :last_name
43
+ private :created
44
+
45
+ # @!attribute [r] created_at
46
+ #
47
+ # @return [Time] The time this token was created. Will fetch from the API
48
+ # if necessary.
49
+ def created_at
50
+ @created_at ||= Time.at(created)
51
+ end
52
+
53
+ def delete
54
+ api.request(:delete, resource_url)['success']
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,44 @@
1
+ require_relative 'connect_token'
2
+ require_relative 'api/resource_collection'
3
+
4
+ class ContextIO
5
+ # Represents a collection of connect tokens for your account. You can use this
6
+ # to create a new token, fetch a specific one or iterate over them.
7
+ #
8
+ # @example You can iterate over them with `each`:
9
+ # contextio.connect_tokens.each do |connect_token|
10
+ # puts connect_token.email
11
+ # end
12
+ #
13
+ # @example You can lazily access a specific one with square brackets:
14
+ # connect_token = contextio.connect_tokens['some_token']
15
+ class ConnectTokenCollection
16
+ include ContextIO::API::ResourceCollection
17
+
18
+ self.resource_class = ContextIO::ConnectToken
19
+ self.association_name = :connect_tokens
20
+
21
+ belongs_to :account
22
+
23
+ # Creates a new connect token for your account.
24
+ #
25
+ # @param [String] callback_url The url that the user will be redirected to
26
+ # after OAuthing their account with Context.IO.
27
+ # @param [Hash{String, Symbol => String}] options Optional information you
28
+ # can provide at creation: email, service level, first_name and/or
29
+ # last_name.
30
+ #
31
+ # @return [ConnectToken] A new token instance based on the data you input.
32
+ def create(callback_url, options={})
33
+ result_hash = api.request(
34
+ :post,
35
+ resource_url,
36
+ options.merge(callback_url: callback_url)
37
+ )
38
+
39
+ result_hash.delete('success')
40
+
41
+ resource_class.new(api, result_hash)
42
+ end
43
+ end
44
+ end