usergrid_ironhorse 0.0.2

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