contextio-lite 0.0.2

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