contextio-lite 0.0.2
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 +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'
|