contextio-lite 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +114 -0
- data/Rakefile +2 -0
- data/contextio-lite.gemspec +27 -0
- data/lib/contextio.rb +14 -0
- data/lib/contextio/api/abstract_api.rb +213 -0
- data/lib/contextio/api/association_helpers.rb +17 -0
- data/lib/contextio/api/resource.rb +248 -0
- data/lib/contextio/api/resource_collection.rb +193 -0
- data/lib/contextio/lite.rb +43 -0
- data/lib/contextio/lite/api.rb +30 -0
- data/lib/contextio/lite/email_account.rb +48 -0
- data/lib/contextio/lite/email_account_collection.rb +44 -0
- data/lib/contextio/lite/folder.rb +18 -0
- data/lib/contextio/lite/folder_collection.rb +17 -0
- data/lib/contextio/lite/message.rb +77 -0
- data/lib/contextio/lite/message_collection.rb +15 -0
- data/lib/contextio/lite/url_builder.rb +95 -0
- data/lib/contextio/lite/user.rb +61 -0
- data/lib/contextio/lite/user_collection.rb +40 -0
- data/lib/contextio/lite/webhook.rb +43 -0
- data/lib/contextio/lite/webhook_collection.rb +28 -0
- metadata +139 -0
@@ -0,0 +1,248 @@
|
|
1
|
+
require_relative 'association_helpers'
|
2
|
+
|
3
|
+
module ContextIO
|
4
|
+
module API
|
5
|
+
# When `include`d into a class, this module provides some helper methods for
|
6
|
+
# various things a singular resource will need or find useful.
|
7
|
+
module Resource
|
8
|
+
# (see ContextIO#api)
|
9
|
+
attr_reader :api
|
10
|
+
|
11
|
+
# @!attribute [r] with_constraints
|
12
|
+
# A Hash of the constraints limiting this resource.
|
13
|
+
attr_reader :with_constraints
|
14
|
+
|
15
|
+
# @private
|
16
|
+
#
|
17
|
+
# For internal use only. Users of this gem shouldn't be calling this
|
18
|
+
# directly.
|
19
|
+
#
|
20
|
+
# @param [API] api A handle on the Context.IO API.
|
21
|
+
# @param [Hash{String, Symbol => String, Numeric, Boolean}] options A Hash
|
22
|
+
# of attributes describing the resource.
|
23
|
+
def initialize(api, options = {})
|
24
|
+
@options ||= options
|
25
|
+
validate_options
|
26
|
+
@with_constraints = @options[:with] || {}
|
27
|
+
|
28
|
+
@api = api
|
29
|
+
|
30
|
+
@options.each do |key, value|
|
31
|
+
key = key.to_s.gsub('-', '_')
|
32
|
+
|
33
|
+
if self.class.associations.include?(key.to_sym) && value.is_a?(Array)
|
34
|
+
association_class = ContextIO::API::AssociationHelpers.class_for_association_name(key.to_sym)
|
35
|
+
|
36
|
+
value = association_class.new(api, self.class.association_name => self, attribute_hashes: value)
|
37
|
+
end
|
38
|
+
|
39
|
+
instance_variable_set("@#{key}", value)
|
40
|
+
|
41
|
+
unless self.respond_to?(key)
|
42
|
+
define_singleton_method(key) do
|
43
|
+
instance_variable_get("@#{key}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# @!attribute [r] resource_url
|
50
|
+
# @return [String] The URL that will fetch attributes from the API.
|
51
|
+
def resource_url
|
52
|
+
@resource_url ||= api.url_for(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Deletes the resource.
|
56
|
+
#
|
57
|
+
# @return [Boolean] Whether the deletion worked or not.
|
58
|
+
def delete
|
59
|
+
api.request(:delete, resource_url)['success']
|
60
|
+
end
|
61
|
+
|
62
|
+
# @!attribute [r] api_attributes
|
63
|
+
# @return [{String => Numeric, String, Hash, Array, Boolean}] The
|
64
|
+
# attributes returned from the API as a Hash. If it hasn't been
|
65
|
+
# populated, it will ask the API and populate it.
|
66
|
+
def api_attributes
|
67
|
+
@api_attributes ||= fetch_attributes
|
68
|
+
end
|
69
|
+
|
70
|
+
# @!attribute [r] primary_key
|
71
|
+
# @return [String, Symbol] The name of the key used to build the resource
|
72
|
+
# URL.
|
73
|
+
def primary_key
|
74
|
+
self.class.primary_key
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def with(constraints)
|
79
|
+
constraints.each{|i,c| constraints[i] = (c ? 1 : 0) if c == !!c }
|
80
|
+
self.class.new(api, @options.merge(with: with_constraints.merge(constraints)))
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# Make sure a Resource has the declarative syntax handy.
|
86
|
+
def self.included(other_mod)
|
87
|
+
other_mod.extend(DeclarativeClassSyntax)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Raises ArgumentError unless the primary key or the resource URL is
|
91
|
+
# supplied. Use this to ensure that the initializer has or can build the
|
92
|
+
# right URL to fetch its self.
|
93
|
+
def validate_options
|
94
|
+
required_keys = ['resource_url', :resource_url]
|
95
|
+
|
96
|
+
unless self.primary_key.nil?
|
97
|
+
required_keys << primary_key.to_s
|
98
|
+
required_keys << primary_key.to_sym
|
99
|
+
end
|
100
|
+
|
101
|
+
if (@options.keys & required_keys).empty?
|
102
|
+
raise ArgumentError, "Required option missing. Make sure you have either resource_url or #{primary_key}."
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Fetches attributes from the API for the resource. Relies on having a
|
107
|
+
# handle on a `ContextIO::API` via an `api` method and on a `resource_url`
|
108
|
+
# method that returns the path for the resource.
|
109
|
+
#
|
110
|
+
# Defines getter methods for any attributes that come back and don't
|
111
|
+
# already have them. This way, if the API expands, the gem will still let
|
112
|
+
# users get attributes we didn't explicitly declare as lazy.
|
113
|
+
#
|
114
|
+
# @return [{String => Numeric, String, Hash, Array, Boolean}] The
|
115
|
+
# attributes returned from the API as a Hash. If it hasn't been
|
116
|
+
# populated, it will ask the API and populate it.
|
117
|
+
def fetch_attributes
|
118
|
+
api.request(:get, resource_url, with_constraints).inject({}) do |memo, (key, value)|
|
119
|
+
key = key.to_s.gsub('-', '_')
|
120
|
+
|
121
|
+
unless respond_to?(key)
|
122
|
+
self.define_singleton_method(key) do
|
123
|
+
value
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
memo[key] = value
|
128
|
+
|
129
|
+
memo
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# This module contains helper methods for `API::Resource`s' class
|
134
|
+
# definitions. It gets `extend`ed into a class when `API::Resource` is
|
135
|
+
# `include`d.
|
136
|
+
module DeclarativeClassSyntax
|
137
|
+
def primary_key
|
138
|
+
@primary_key
|
139
|
+
end
|
140
|
+
|
141
|
+
# @!attribute [r] association_name
|
142
|
+
# @return [Symbol] The association name registered for this resource.
|
143
|
+
def association_name
|
144
|
+
@association_name
|
145
|
+
end
|
146
|
+
|
147
|
+
# @!attribute [r] associations
|
148
|
+
# @return [Array<String] An array of the belong_to associations for
|
149
|
+
# the collection
|
150
|
+
def associations
|
151
|
+
@associations ||= []
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
# Declares the primary key used to build the resource URL. Consumed by
|
157
|
+
# `Resource#validate_options`.
|
158
|
+
#
|
159
|
+
# @param [String, Symbol] key Primary key name.
|
160
|
+
def primary_key=(key)
|
161
|
+
@primary_key = key
|
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
|
+
|
172
|
+
# Declares a list of attributes to be lazily loaded from the API. Getter
|
173
|
+
# methods are written for each attribute. If the user asks for one and
|
174
|
+
# the object in question doesn't have it already, then it will look for
|
175
|
+
# it in the api_attributes Hash.
|
176
|
+
#
|
177
|
+
# @example an example of the generated methods
|
178
|
+
# def some_attribute
|
179
|
+
# return @some_attribute if instance_variable_defined?(@some_attribute)
|
180
|
+
# api_attributes["some_attribute"]
|
181
|
+
# end
|
182
|
+
#
|
183
|
+
# @param [Array<String, Symbol>] attributes Attribute names.
|
184
|
+
def lazy_attributes(*attributes)
|
185
|
+
attributes.each do |attribute_name|
|
186
|
+
define_method(attribute_name) do
|
187
|
+
return instance_variable_get("@#{attribute_name}") if instance_variable_defined?("@#{attribute_name}")
|
188
|
+
api_attributes[attribute_name.to_s]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Declares that this resource is related to a single instance of another
|
194
|
+
# resource. This related resource will be lazily created as it can be,
|
195
|
+
# but in some cases may cause an API call.
|
196
|
+
#
|
197
|
+
# @param [Symbol] association_name The name of the association for the
|
198
|
+
# class in question. Singular classes will have singular names
|
199
|
+
# registered. For instance, :message should reger to the Message
|
200
|
+
# resource.
|
201
|
+
def belongs_to(association_name)
|
202
|
+
define_method(association_name) do
|
203
|
+
if instance_variable_defined?("@#{association_name}")
|
204
|
+
instance_variable_get("@#{association_name}")
|
205
|
+
else
|
206
|
+
association_attrs = api_attributes[association_name.to_s]
|
207
|
+
association_class = Contextio::API::AssociationHelpers.class_for_association_name(association_name)
|
208
|
+
|
209
|
+
if association_attrs && !association_attrs.empty?
|
210
|
+
instance_variable_set("@#{association_name}", association_class.new(api, association_attrs))
|
211
|
+
else
|
212
|
+
nil
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
associations << association_name.to_sym
|
218
|
+
end
|
219
|
+
|
220
|
+
# Declares that this resource is related to a collection of another
|
221
|
+
# resource. These related resources will be lazily created as they can
|
222
|
+
# be, but in some cases may cause an API call.
|
223
|
+
#
|
224
|
+
# @param [Symbol] association_name The name of the association for the
|
225
|
+
# class in question. Collection classes will have plural names
|
226
|
+
# registered. For instance, :messages should reger to the
|
227
|
+
# MessageCollection resource.
|
228
|
+
def has_many(association_name)
|
229
|
+
define_method(association_name) do
|
230
|
+
association_class = ContextIO::API::AssociationHelpers.class_for_association_name(association_name)
|
231
|
+
|
232
|
+
instance_variable_get("@#{association_name}") ||
|
233
|
+
instance_variable_set(
|
234
|
+
"@#{association_name}",
|
235
|
+
association_class.new(
|
236
|
+
api,
|
237
|
+
self.class.association_name => self,
|
238
|
+
attribute_hashes: api_attributes[association_name.to_s]
|
239
|
+
)
|
240
|
+
)
|
241
|
+
end
|
242
|
+
|
243
|
+
associations << association_name.to_sym
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module ContextIO
|
2
|
+
module API
|
3
|
+
# When `include`d into a class, this module provides some helper methods for
|
4
|
+
# various things a collections of resources will need or find useful.
|
5
|
+
module ResourceCollection
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# (see ContextIO#api)
|
9
|
+
attr_reader :api
|
10
|
+
|
11
|
+
# @!attribute [r] where_constraints
|
12
|
+
# A Hash of the constraints limiting this collection of resources.
|
13
|
+
attr_reader :where_constraints
|
14
|
+
|
15
|
+
# @private
|
16
|
+
#
|
17
|
+
# For internal use only. Users of this gem shouldn't be calling this
|
18
|
+
# directly.
|
19
|
+
#
|
20
|
+
# @param [API] api A handle on the Context.IO API.
|
21
|
+
# @param [Hash] options Optional params for the collection.
|
22
|
+
# @option options [Hash{Symbol => String, Numeric}] :where Where
|
23
|
+
# constraints that limit the resources that belong to this collection.
|
24
|
+
# @option options [Array<Hash>] :attribute_hashes An array of hashes
|
25
|
+
# describing the resources in this collection.
|
26
|
+
def initialize(api, options={})
|
27
|
+
@api = api
|
28
|
+
@where_constraints = options[:where] || {}
|
29
|
+
@attribute_hashes = options[:attribute_hashes]
|
30
|
+
|
31
|
+
self.class.associations.each do |association_name|
|
32
|
+
instance_variable_set("@#{association_name}", options[association_name.to_sym])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @!attribute [r] resource_url
|
37
|
+
# @return [String] The URL that will fetch attributes from the API.
|
38
|
+
def resource_url
|
39
|
+
@resource_url ||= api.url_for(self)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Iterates over the resources in question.
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# contextio.connect_tokens.each do |connect_token|
|
46
|
+
# puts connect_token.email
|
47
|
+
# end
|
48
|
+
def each(&block)
|
49
|
+
attribute_hashes.each do |attribute_hash|
|
50
|
+
yield resource_class.new(api, attribute_hash.merge(associations_hash))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the number of elements in self. May be zero.
|
55
|
+
#
|
56
|
+
# @note Calling this method will load the collection if not already loaded.
|
57
|
+
def size
|
58
|
+
attribute_hashes.size
|
59
|
+
end
|
60
|
+
alias :length :size
|
61
|
+
alias :count :size
|
62
|
+
|
63
|
+
# Returns true if self contains no elements.
|
64
|
+
#
|
65
|
+
# @note Calling this method will load the collection if not already loaded.
|
66
|
+
def empty?
|
67
|
+
size == 0
|
68
|
+
end
|
69
|
+
|
70
|
+
# Specify one or more constraints for limiting resources in this
|
71
|
+
# collection. See individual classes in the
|
72
|
+
# [Context.IO docs](http://context.io/docs/2.0/) for the list of valid constraints.
|
73
|
+
# Not all collections have valid where constraints at all.
|
74
|
+
#
|
75
|
+
# This can be chained at need and doesn't actually cause the API to get
|
76
|
+
# hit until some iterator is called like `#each`.
|
77
|
+
#
|
78
|
+
# @example
|
79
|
+
# accounts = contextio.accounts
|
80
|
+
# accounts = accounts.where(email: 'some@email.com')
|
81
|
+
# accounts = accounts.where(status: 'OK')
|
82
|
+
#
|
83
|
+
# accounts.each do |account|
|
84
|
+
# # API gets hit for this call
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# @param [Hash{String, Symbol => String, Integer}] constraints A Hash
|
88
|
+
# mapping keys to the desired limiting values.
|
89
|
+
def where(constraints)
|
90
|
+
constraints.each{|i,c| constraints[i] = (c ? 1 : 0) if c == !!c }
|
91
|
+
self.class.new(api, associations_hash.merge(where: where_constraints.merge(constraints)))
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns a resource with the given key.
|
95
|
+
#
|
96
|
+
# This is a lazy method, making no requests. When you try to access
|
97
|
+
# attributes on the object, or otherwise interact with it, it will actually
|
98
|
+
# make requests.
|
99
|
+
#
|
100
|
+
# @example
|
101
|
+
# provider = contextio.oauth_providers['1234']
|
102
|
+
#
|
103
|
+
# @param [String] key The Provider Consumer Key for the
|
104
|
+
# provider you want to interact with.
|
105
|
+
def [](key)
|
106
|
+
resource_class.new(api, associations_hash.merge(resource_class.primary_key => key))
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# @!attribute [r] attribute_hashes
|
112
|
+
# @return [Array<Hash>] An array of attribute hashes that describe, at
|
113
|
+
# least partially, the objects in this collection.
|
114
|
+
def attribute_hashes
|
115
|
+
@attribute_hashes ||= api.request(:get, resource_url, where_constraints)
|
116
|
+
end
|
117
|
+
|
118
|
+
# @!attribute [r] associations_hash
|
119
|
+
# @return [Hash{Symbol => Resource}] A hash of association names to the
|
120
|
+
# associated resource of that type.
|
121
|
+
def associations_hash
|
122
|
+
@associations_hash ||= self.class.associations.inject({}) do |memo, association_name|
|
123
|
+
if (association = self.send(association_name))
|
124
|
+
memo[association_name.to_sym] = association
|
125
|
+
end
|
126
|
+
|
127
|
+
memo
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Make sure a ResourceCollection has the declarative syntax handy.
|
132
|
+
def self.included(other_mod)
|
133
|
+
other_mod.extend(DeclarativeClassSyntax)
|
134
|
+
end
|
135
|
+
|
136
|
+
# This module contains helper methods for `API::ResourceCollection`s'
|
137
|
+
# class definitions. It gets `extend`ed into a class when
|
138
|
+
# `API::ResourceCollection` is `include`d.
|
139
|
+
module DeclarativeClassSyntax
|
140
|
+
# @!attribute [r] associations
|
141
|
+
# @return [Array<String] An array of the belong_to associations for
|
142
|
+
# the collection
|
143
|
+
def associations
|
144
|
+
@associations ||= []
|
145
|
+
end
|
146
|
+
|
147
|
+
# @!attribute [r] association_name
|
148
|
+
# @return [Symbol] The association name registered for this resource.
|
149
|
+
def association_name
|
150
|
+
@association_name
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
# Declares which class the `ResourceCollection` is intended to wrap. For
|
156
|
+
# best results, this should probably be a `Resource`. It defines an
|
157
|
+
# accessor for this class on instances of the collection, which is
|
158
|
+
# private. Make sure your collection class has required the file with
|
159
|
+
# the defeniiton of the class it wraps.
|
160
|
+
#
|
161
|
+
# @param [Class] klass The class that the collection, well, collects.
|
162
|
+
def resource_class=(klass)
|
163
|
+
define_method(:resource_class) do
|
164
|
+
klass
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Declares which class, if any, the collection belongs to. It defines an
|
169
|
+
# accessor for the belonged-to object.
|
170
|
+
#
|
171
|
+
# @param [Symbol] association_name The name of the association for the
|
172
|
+
# class in question. Singular classes will have singular names
|
173
|
+
# registered. For instance, :message should reger to the Message
|
174
|
+
# resource.
|
175
|
+
def belongs_to(association_name)
|
176
|
+
define_method(association_name) do
|
177
|
+
instance_variable_get("@#{association_name}")
|
178
|
+
end
|
179
|
+
|
180
|
+
associations << association_name
|
181
|
+
end
|
182
|
+
|
183
|
+
# Declares the association name for the resource.
|
184
|
+
#
|
185
|
+
# @param [String, Symbol] association_name The name.
|
186
|
+
def association_name=(association_name)
|
187
|
+
@association_name = association_name.to_sym
|
188
|
+
ContextIO::API::AssociationHelpers.register_resource(self, @association_name)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module ContextIO
|
2
|
+
class Lite
|
3
|
+
include ContextIO
|
4
|
+
|
5
|
+
|
6
|
+
# Creates a new `ContextIO` instance and makes a new handle for the API.
|
7
|
+
# This is your entry point to your Context.IO account. For a web app, you
|
8
|
+
# probably want to instantiate this in some kind of initializer and keep it
|
9
|
+
# around for the life of the process.
|
10
|
+
#
|
11
|
+
# @param [String] key Your OAuth consumer key for your Context.IO account
|
12
|
+
# @param [String] secret Your OAuth consumer secret for your Context.IO
|
13
|
+
# account
|
14
|
+
# @param [Hash] opts Optional options for OAuth connections. ie. :timeout and :open_timeout are supported
|
15
|
+
def initialize(key, secret, opts={})
|
16
|
+
@api = API.new(key, secret, opts)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Your entry point for dealing with users.
|
20
|
+
#
|
21
|
+
# @return [Users] Allows you to work with the email accounts for
|
22
|
+
# your account as a group.
|
23
|
+
def users
|
24
|
+
UserCollection.new(api)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
require_relative 'api/association_helpers'
|
30
|
+
require_relative 'api/resource'
|
31
|
+
require_relative 'api/resource_collection'
|
32
|
+
|
33
|
+
require_relative 'lite/api'
|
34
|
+
require_relative 'lite/email_account'
|
35
|
+
require_relative 'lite/email_account_collection'
|
36
|
+
require_relative 'lite/folder'
|
37
|
+
require_relative 'lite/folder_collection'
|
38
|
+
require_relative 'lite/message'
|
39
|
+
require_relative 'lite/message_collection'
|
40
|
+
require_relative 'lite/user'
|
41
|
+
require_relative 'lite/user_collection'
|
42
|
+
require_relative 'lite/webhook'
|
43
|
+
require_relative 'lite/webhook_collection'
|