contextio 0.5.0 → 1.0.0

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