usergrid_ironhorse 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.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm_gemset_create_on_use_flag=1
2
+ rvm gemset use 'usergrid_ironhorse'
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in usergrid_ironhorse.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Scott Ganyo
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # Usergrid_ironhorse
2
+
3
+ Usergrid_ironhorse is based on Usergrid_iron and enables Ruby or Rails applications
4
+ native Rails-style access to Apigee's App Services (aka Usergrid) REST API.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'usergrid_ironhorse'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install usergrid_ironhorse
19
+
20
+
21
+ ## Usage
22
+
23
+ ### Not familiar with Usergrid / Apigee's App Services?
24
+
25
+ #### It's great stuff! Check it out, here:
26
+
27
+ Docs: <http://apigee.com/docs/usergrid/>
28
+ Open source: <https://github.com/apigee/usergrid-stack>
29
+
30
+ ### Getting started with the Usergrid_ironhorse SDK is super simple!
31
+
32
+ * Add 'gem usergrid_ironhorse' to your Gemfile
33
+ * Create a 'config/usergrid.yml' file that looks something like this (the
34
+ auth_token is your application admin token):
35
+ <pre>
36
+ development:
37
+ :application_url: http://localhost:8080/my-organization/my-application
38
+ :auth_token: YWMtc4WjqhcbEeK6UhQQn9SVgQAAATpryjMnLy9oFaPbP-0qIxoUx_4vtaOmpmE
39
+
40
+ development:
41
+ :application_url: http://localhost:8080/my-organization/my-application
42
+ :auth_token: YWMtc4WjqhcbEeK6UhQQn9SVgQAAATpryjMnLy9oFaPbP-0qIxoUx_4vtaOmpmE
43
+
44
+ production:
45
+ :application_url: http://api.usergrid.com/my-organization/my-application
46
+ :auth_token: YWMtc4WjqhcbEeK6UhQQn9SVgQAAATpryjMnLy9oFaPbP-0qIxoUx_4vtaOmpmE
47
+ </pre>
48
+ * Your User model should subclass Usergrid::Ironhorse::Base and extend
49
+ Usergrid::Ironhorse::UserContext like so:
50
+ <pre>
51
+ class User < Usergrid::Ironhorse::Base
52
+ extend Usergrid::Ironhorse::UserContext
53
+
54
+ end
55
+ </pre>
56
+ * Set up your authentication
57
+ * Use `User.authenticate(username, password, session)` to login.
58
+ * Use `User.clear_authentication(session)` to log out.
59
+ * Propogate the authentication in your ApplicationController:
60
+ <pre>
61
+ before_filter :set_thread_context
62
+ def set_thread_context
63
+ User.set_thread_context session
64
+ end
65
+ </pre>
66
+ * Optionally, if you need to access the User from your view, you may add something
67
+ like the following to your ApplicationController:
68
+ <pre>
69
+ helper_method :current_user
70
+ def current_user
71
+ User.current_user
72
+ end
73
+ </pre>
74
+
75
+
76
+ ## Contributing
77
+
78
+ We welcome your enhancements!
79
+
80
+ 1. Fork it
81
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
82
+ 3. Write some broken rspecs.
83
+ 4. Fix the rspecs with your new code.
84
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
85
+ 4. Push your changes to the upstream branch (`git push origin my-new-feature`)
86
+ 5. Create new Pull Request
87
+
88
+ We've got 100% rspec coverage and we're looking to keep it that way!*
89
+ (*Not yet, but soon)
90
+ In order to run the tests, check out the Usergrid open source project
91
+ (https://github.com/apigee/usergrid-stack), build, and launch it locally.
92
+
93
+ (Note: If you change your local Usergrid settings from the default, be sure to update
94
+ usergrid_ironhorse/spec/spec_settings.yaml to match.)
95
+
96
+
97
+ ## Release notes
98
+
99
+ ### 0.0.2
100
+ * New Features
101
+ 1. Authentication and user propagation features
102
+ ### 0.0.1
103
+ * Initial commit
104
+ 1. Support for most ActiveModel stuff including Validations
105
+ 1. No scoping support
106
+
107
+
108
+ ## Copyright
109
+ Copyright (c) 2012 Scott Ganyo
110
+
111
+ Licensed under the Apache License, Version 2.0 (the "License");
112
+ you may not use the included files except in compliance with the License.
113
+
114
+ You may obtain a copy of the License at
115
+
116
+ <http://www.apache.org/licenses/LICENSE-2.0>
117
+
118
+ Unless required by applicable law or agreed to in writing, software distributed under
119
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
120
+ either express or implied. See the License for the specific language governing permissions and
121
+ limitations under the License.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ RSpec::Core::RakeTask.new("spec:coverage")
@@ -0,0 +1,7 @@
1
+ class Hash
2
+
3
+ def nested_under_indifferent_access
4
+ self
5
+ end
6
+
7
+ end
@@ -0,0 +1,24 @@
1
+ require 'logger'
2
+ require 'active_model'
3
+ require 'rest-client'
4
+ require 'active_support'
5
+ require 'usergrid_iron'
6
+ require 'active_record/errors'
7
+
8
+ module Usergrid
9
+ module Ironhorse
10
+
11
+ Dir[Pathname.new(File.dirname(__FILE__)).join("extensions/**/*.rb")].each { |f| require f }
12
+
13
+ USERGRID_PATH = File.join File.dirname(__FILE__), 'usergrid_ironhorse'
14
+
15
+ def self.usergrid_path *path
16
+ File.join USERGRID_PATH, *path
17
+ end
18
+
19
+ require usergrid_path('base')
20
+ require usergrid_path('query')
21
+
22
+ autoload :UserContext, usergrid_path('user_context')
23
+ end
24
+ end
@@ -0,0 +1,360 @@
1
+ require 'active_record/validations'
2
+ require 'active_record/errors'
3
+ require 'active_record/callbacks'
4
+
5
+ module Usergrid
6
+ module Ironhorse
7
+
8
+ class Base
9
+ include ActiveModel::AttributeMethods
10
+ include ActiveModel::Conversion
11
+ include ActiveModel::Validations
12
+ include ActiveModel::Dirty
13
+ include ActiveModel::Serialization
14
+ extend ActiveModel::Naming
15
+ extend ActiveModel::Callbacks
16
+
17
+ RESERVED_ATTRIBUTES = %w(metadata created modified uuid type uri)
18
+
19
+ define_model_callbacks :create, :destroy, :save, :update
20
+
21
+ # todo: determine the subset to support...
22
+ # unsupported: :group, :joins, :preload, :eager_load, :includes, :from, :lock,
23
+ # :having, :create_with, :uniq, :references, :none, :count,
24
+ # :average, :minimum, :maximum, :sum, :calculate, :ids
25
+ # :find_each, :find_in_batches, :offset, :readonly
26
+
27
+ #delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :to => :all
28
+ #delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :all
29
+ #delegate :find_by, :find_by!, :to => :all
30
+ #delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :all
31
+ #delegate :find_each, :find_in_batches, :to => :all
32
+ #delegate :select, :group, :order, :except, :reorder, :limit, :offset,
33
+ # :where, :preload, :eager_load, :includes, :from, :lock, :readonly,
34
+ # :having, :create_with, :uniq, :references, :none, :to => :all
35
+ #delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :all
36
+
37
+ @@settings ||= nil
38
+
39
+ attr_accessor :attributes
40
+
41
+ HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
42
+ RecordNotSaved = ActiveRecord::RecordNotSaved
43
+
44
+ def initialize(attrs=nil)
45
+ attrs = HashWithIndifferentAccess.new attrs
46
+ unless attrs.has_key? :uuid
47
+ assign_attributes attrs
48
+ end
49
+ @attributes = attrs
50
+ end
51
+
52
+ def self.configure!(application_url, auth_token)
53
+ @@settings = HashWithIndifferentAccess.new application_url: application_url, auth_token: auth_token
54
+ end
55
+
56
+ def self.settings
57
+ return @@settings if @@settings
58
+ path = "config/usergrid.yml"
59
+ environment = defined?(Rails) && Rails.respond_to?(:env) ? Rails.env : ENV['RACK_ENV']
60
+ @@settings = HashWithIndifferentAccess.new YAML.load(ERB.new(File.new(path).read).result)[environment]
61
+ end
62
+
63
+ # forward to all
64
+ def self.method_missing(method, *args, &block)
65
+ all.send method, *args, &block
66
+ end
67
+
68
+ # forward to all
69
+ def method_missing(method, *args, &block)
70
+ if args.size == 0
71
+ attributes[method]
72
+ elsif args.size == 1 && method[-1] == '='
73
+ attr = method[0..-2]
74
+ if attributes[attr] != args[0]
75
+ attribute_will_change!(attr)
76
+ attributes[attr] = args[0]
77
+ end
78
+ else
79
+ all.send method, *args, &block
80
+ end
81
+ end
82
+
83
+ # todo: scopes
84
+ def self.all
85
+ unscoped
86
+ end
87
+
88
+ #def self.scope(symbol, scope)
89
+ # @scopes[symbol] = scope
90
+ #end
91
+
92
+ #def self.current_scope
93
+ # @current_scope ||= default_scope
94
+ #end
95
+ #
96
+ #def self.current_scope=(scope)
97
+ # @current_scope = scope
98
+ #end
99
+ #
100
+ #def self.default_scope
101
+ # @default_scope ||= unscoped
102
+ #end
103
+
104
+ def self.unscoped
105
+ Query.new(self)
106
+ end
107
+
108
+ def self.create(attributes=nil, options=nil, &block)
109
+ if attributes.is_a?(Array)
110
+ attributes.collect { |attr| create(attr, options, &block) }
111
+ else
112
+ object = new(attributes, &block)
113
+ object.save
114
+ object
115
+ end
116
+ end
117
+
118
+ def self.create!(attributes=nil, options=nil, &block)
119
+ if attributes.is_a?(Array)
120
+ attributes.collect {|attr| create!(attr, options, &block)}
121
+ else
122
+ object = new(attributes)
123
+ yield(object) if block_given?
124
+ object.save!
125
+ object
126
+ end
127
+ end
128
+
129
+ def self.class_attributes
130
+ @class_attributes ||= {}
131
+ end
132
+
133
+ # Returns true if the record is persisted, i.e. it's not a new record and it was
134
+ # not destroyed, otherwise returns false.
135
+ def persisted?
136
+ !(new_record? || destroyed?)
137
+ end
138
+
139
+ def new_record?
140
+ !self.uuid
141
+ end
142
+
143
+ def self.group
144
+ model_name.plural.downcase
145
+ end
146
+
147
+ # Creates a Usergrid::Resource
148
+ def self.resource
149
+ app = Usergrid::Application.new settings[:application_url]
150
+ if Thread.current[:auth_token]
151
+ app.auth_token = Thread.current[:auth_token]
152
+ else
153
+ app.auth_token = settings[:auth_token]
154
+ end
155
+ app[group]
156
+ end
157
+
158
+ # Saves the model.
159
+ #
160
+ # If the model is new a record gets created in the database, otherwise
161
+ # the existing record gets updated.
162
+ #
163
+ # By default, save always run validations. If any of them fail the action
164
+ # is cancelled and +save+ returns +false+. However, if you supply
165
+ # :validate => false, validations are bypassed altogether. See
166
+ # ActiveRecord::Validations for more information.
167
+ #
168
+ # There's a series of callbacks associated with +save+. If any of the
169
+ # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
170
+ # +save+ returns +false+. See ActiveRecord::Callbacks for further
171
+ # details.
172
+ def save
173
+ begin
174
+ create_or_update
175
+ rescue ActiveRecord::RecordInvalid
176
+ false
177
+ end
178
+ end
179
+
180
+ # Saves the model.
181
+ #
182
+ # If the model is new a record gets created in the database, otherwise
183
+ # the existing record gets updated.
184
+ #
185
+ # With <tt>save!</tt> validations always run. If any of them fail
186
+ # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
187
+ # for more information.
188
+ #
189
+ # There's a series of callbacks associated with <tt>save!</tt>. If any of
190
+ # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
191
+ # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
192
+ # ActiveRecord::Callbacks for further details.
193
+ def save!
194
+ create_or_update or raise RecordNotSaved
195
+ end
196
+
197
+ # Deletes the record in the database and freezes this instance to
198
+ # reflect that no changes should be made (since they can't be
199
+ # persisted). Returns the frozen instance.
200
+ #
201
+ # The row is simply removed with an SQL +DELETE+ statement on the
202
+ # record's primary key, and no callbacks are executed.
203
+ #
204
+ # To enforce the object's +before_destroy+ and +after_destroy+
205
+ # callbacks, Observer methods, or any <tt>:dependent</tt> association
206
+ # options, use <tt>#destroy</tt>.
207
+ def delete
208
+ self.class.delete(id) if persisted?
209
+ @destroyed = true
210
+ freeze
211
+ end
212
+
213
+ # Deletes the record in the database and freezes this instance to reflect
214
+ # that no changes should be made (since they can't be persisted).
215
+ #
216
+ # There's a series of callbacks associated with <tt>destroy</tt>. If
217
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
218
+ # and <tt>destroy</tt> returns +false+. See
219
+ # ActiveRecord::Callbacks for further details.
220
+ def destroy
221
+ raise ReadOnlyRecord if readonly?
222
+ # todo: callbacks?
223
+ instance_resource.delete if persisted?
224
+ @destroyed = true
225
+ freeze
226
+ end
227
+
228
+ # Deletes the record in the database and freezes this instance to reflect
229
+ # that no changes should be made (since they can't be persisted).
230
+ #
231
+ # There's a series of callbacks associated with <tt>destroy!</tt>. If
232
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
233
+ # and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See
234
+ # ActiveRecord::Callbacks for further details.
235
+ def destroy!
236
+ destroy || raise(ActiveRecord::RecordNotDestroyed)
237
+ end
238
+
239
+ # Returns true if this object has been destroyed, otherwise returns false.
240
+ def destroyed?
241
+ !!@destroyed
242
+ end
243
+
244
+ # Reloads the attributes of this object from the database.
245
+ def reload
246
+ return false if !persisted?
247
+ fresh_object = self.class.find(id)
248
+ refresh_data fresh_object.instance_variable_get('@attributes')
249
+ self
250
+ end
251
+
252
+ # Updates the attributes of the model from the passed-in hash and saves the
253
+ # record, all wrapped in a transaction. If the object is invalid, the saving
254
+ # will fail and false will be returned.
255
+ def update_attributes(attributes)
256
+ assign_attributes attributes
257
+ save
258
+ end
259
+
260
+ # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
261
+ # of +save+, so an exception is raised if the record is invalid.
262
+ def update_attributes!(attributes)
263
+ assign_attributes attributes
264
+ save!
265
+ end
266
+
267
+ # Note that whenever you include ActiveModel::AttributeMethods in your class,
268
+ # it requires you to implement an +attributes+ method which returns a hash
269
+ # with each attribute name in your model as hash key and the attribute value as
270
+ # hash value.
271
+ #
272
+ # Hash keys must be strings.
273
+ def attributes
274
+ @attributes ||= self.class.class_attributes.clone
275
+ end
276
+
277
+ def id; self.uuid end
278
+ def created_at; self.created end
279
+ def updated_at; self.modified end
280
+
281
+
282
+ protected
283
+
284
+
285
+ def assign_attributes(attrs)
286
+ attrs.each do |attr,value|
287
+ attr = attr.to_s
288
+ unless attributes[attr] == value
289
+ attribute_will_change!(attr) unless RESERVED_ATTRIBUTES.include? attr
290
+ attributes[attr] = value
291
+ end
292
+ end
293
+ end
294
+
295
+ def create_or_update
296
+ raise ReadOnlyRecord if readonly?
297
+ if valid?
298
+ run_callbacks :save do
299
+ return new_record? ? do_create : do_update
300
+ end
301
+ end
302
+ false
303
+ end
304
+
305
+ def do_create
306
+ group_resource.post(unsaved_attributes) do |resp, req, res, &block|
307
+ if resp.code.to_s == "200" || resp.code.to_s == "201"
308
+ refresh_data resp.entity_data
309
+ return true
310
+ else
311
+ errors.add(resp.code.to_s, resp)
312
+ return false
313
+ end
314
+ end
315
+ end
316
+
317
+ def do_update
318
+ return false unless changed?
319
+
320
+ instance_resource.put(unsaved_attributes) do |resp, req, res, &block|
321
+ if resp.code.to_s == "200" || resp.code.to_s == "201"
322
+ refresh_data resp.entity_data
323
+ return true
324
+ else
325
+ errors.add(resp.code, resp)
326
+ return false
327
+ end
328
+ end
329
+ end
330
+
331
+ def unsaved_attributes
332
+ HashWithIndifferentAccess[changed.collect {|k| [k, attributes[k]]}]
333
+ end
334
+
335
+ def group_resource
336
+ self.class.resource
337
+ end
338
+
339
+ def instance_resource
340
+ self.class.resource["#{self.id}"]
341
+ end
342
+
343
+ def refresh_data(entity_data)
344
+ @previously_changed = changes
345
+ @changed_attributes.clear
346
+ @attributes = HashWithIndifferentAccess.new entity_data
347
+ end
348
+
349
+ def attribute_will_change!(attr)
350
+ begin
351
+ value = __send__(attr)
352
+ value = value.duplicable? ? value.clone : value
353
+ rescue TypeError, NoMethodError
354
+ end
355
+
356
+ changed_attributes[attr] = value unless changed_attributes.include?(attr)
357
+ end
358
+ end
359
+ end
360
+ end