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.
@@ -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'