You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@usergrid.apache.org by sn...@apache.org on 2016/10/24 13:06:00 UTC
[15/83] [abbrv] usergrid git commit: Moving older SDKs to a
difference location and updating main README to link to new SDK locations.
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby-on-rails/lib/usergrid_ironhorse/query.rb
----------------------------------------------------------------------
diff --git a/sdks/other/ruby-on-rails/lib/usergrid_ironhorse/query.rb b/sdks/other/ruby-on-rails/lib/usergrid_ironhorse/query.rb
new file mode 100644
index 0000000..6562b8f
--- /dev/null
+++ b/sdks/other/ruby-on-rails/lib/usergrid_ironhorse/query.rb
@@ -0,0 +1,929 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# http://guides.rubyonrails.org/active_record_querying.html
+
+module Usergrid
+ module Ironhorse
+
+ class Query
+
+ RecordNotFound = ActiveRecord::RecordNotFound
+
+ def initialize(model_class)
+ @model_class = model_class
+ @options = {}
+ end
+
+ ## Initializes new record from relation while maintaining the current
+ ## scope.
+ ##
+ ## Expects arguments in the same format as +Base.new+.
+ ##
+ ## users = User.where(name: 'DHH')
+ ## user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil>
+ ##
+ ## You can also pass a block to new with the new record as argument:
+ ##
+ ## user = users.new { |user| user.name = 'Oscar' }
+ ## user.name # => Oscar
+ #def new(*args, &block)
+ # scoping { @model_class.new(*args, &block) }
+ #end
+
+ # Find by uuid or name - This can either be a specific uuid or name (1), a list of uuids
+ # or names (1, 5, 6), or an array of uuids or names ([5, 6, 10]).
+ # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
+ #
+ # Person.find(1) # returns the object for ID = 1
+ # Person.find("1") # returns the object for ID = 1
+ # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
+ # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
+ # Person.find([1]) # returns an array for the object with ID = 1
+ # Person.where("administrator = 1").order("created_on DESC").find(1)
+ #
+ def find(*ids)
+ raise RecordNotFound unless ids
+ ids = ids.first if ids.first.is_a? Array
+ @records = ids.collect { |id| find_one! id } # todo: can this be optimized in one call?
+ #entities = @model_class.resource[ids.join '&'].get.entities
+ #raise RecordNotFound unless (entities.size == ids.size)
+ #@records = entities.collect {|entity| @model_class.model_name.constantize.new(entity.data) }
+ @records.size == 1 ? @records.first : @records
+ end
+
+ # Finds the first record matching the specified conditions. There
+ # is no implied ordering so if order matters, you should specify it
+ # yourself.
+ #
+ # If no record is found, returns <tt>nil</tt>.
+ #
+ # Post.find_by name: 'Spartacus', rating: 4
+ # Post.find_by "published_at < ?", 2.weeks.ago
+ def find_by(*conditions)
+ where(*conditions).take
+ end
+
+ # Like <tt>find_by</tt>, except that if no record is found, raises
+ # an <tt>ActiveRecord::RecordNotFound</tt> error.
+ def find_by!(*conditions)
+ where(*conditions).take!
+ end
+
+ # Gives a record (or N records if a parameter is supplied) without any implied
+ # order.
+ #
+ # Person.take # returns an object fetched by SELECT * FROM people
+ # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
+ # Person.where(["name LIKE '%?'", name]).take
+ def take(limit=1)
+ limit(limit).to_a
+ end
+
+ # Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found. Note that <tt>take!</tt> accepts no arguments.
+ def take!
+ take or raise RecordNotFound
+ end
+
+ # Find the first record (or first N records if a parameter is supplied).
+ # If no order is defined it will order by primary key.
+ #
+ # Person.first # returns the first object fetched by SELECT * FROM people
+ # Person.where(["user_name = ?", user_name]).first
+ # Person.where(["user_name = :u", { :u => user_name }]).first
+ # Person.order("created_on DESC").offset(5).first
+ # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
+ def first(limit=1)
+ limit(limit).load.first
+ end
+
+ # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found. Note that <tt>first!</tt> accepts no arguments.
+ def first!
+ first or raise RecordNotFound
+ end
+
+ # Find the last record (or last N records if a parameter is supplied).
+ # If no order is defined it will order by primary key.
+ #
+ # Person.last # returns the last object fetched by SELECT * FROM people
+ # Person.where(["user_name = ?", user_name]).last
+ # Person.order("created_on DESC").offset(5).last
+ # Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
+ #
+ # Take note that in that last case, the results are sorted in ascending order:
+ #
+ # [#<Person id:2>, #<Person id:3>, #<Person id:4>]
+ #
+ # and not:
+ #
+ # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
+ def last(limit=1)
+ limit(limit).reverse_order.load.first
+ end
+
+ # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found. Note that <tt>last!</tt> accepts no arguments.
+ def last!
+ last or raise RecordNotFound
+ end
+
+ def each
+ to_a.each { |*block_args| yield(*block_args) }
+ while @response.data['cursor'] && !limit_value
+ next_page
+ to_a.each { |*block_args| yield(*block_args) }
+ end
+ end
+
+ def next_page
+ @options[:cursor] = @response.data['cursor']
+ @records = nil
+ load
+ self
+ end
+
+ # Returns +true+ if a record exists in the table that matches the +id+ or
+ # conditions given, or +false+ otherwise. The argument can take six forms:
+ #
+ # * String - Finds the record with a primary key corresponding to this
+ # string (such as <tt>'5'</tt>).
+ # * Array - Finds the record that matches these +find+-style conditions
+ # (such as <tt>['color = ?', 'red']</tt>).
+ # * Hash - Finds the record that matches these +find+-style conditions
+ # (such as <tt>{color: 'red'}</tt>).
+ # * +false+ - Returns always +false+.
+ # * No args - Returns +false+ if the table is empty, +true+ otherwise.
+ #
+ # For more information about specifying conditions as a Hash or Array,
+ # see the Conditions section in the introduction to ActiveRecord::Base.
+ def exists?(conditions=nil)
+ # todo: does not yet handle all conditions described above
+ case conditions
+ when Array, Hash
+ pluck :uuid
+ !where(conditions).take.empty?
+ else
+ !!find_one(conditions)
+ end
+ end
+ alias_method :any?, :exists?
+ alias_method :many?, :exists?
+
+ def limit(limit=1)
+ @options[:limit] = limit
+ self
+ end
+
+ def offset(num)
+ @options[:offset] = num
+ self
+ end
+
+ # Removes from the query the condition(s) specified in +skips+.
+ #
+ # Example:
+ #
+ # Post.order('id asc').except(:order) # discards the order condition
+ # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
+ #
+ def except(*skips)
+ skips.each {|option| @options.delete option}
+ end
+
+ # Removes any condition from the query other than the one(s) specified in +onlies+.
+ #
+ # Example:
+ #
+ # Post.order('id asc').only(:where) # discards the order condition
+ # Post.order('id asc').only(:where, :order) # uses the specified order
+ #
+ def only(*onlies)
+ @options.keys do |k|
+ unless onlines.include? k
+ @options.delete k
+ end
+ end
+ end
+
+ # Allows to specify an order attribute:
+ #
+ # User.order('name')
+ # => SELECT "users".* FROM "users" ORDER BY name
+ #
+ # User.order('name DESC')
+ # => SELECT "users".* FROM "users" ORDER BY name DESC
+ #
+ # User.order('name DESC, email')
+ # => SELECT "users".* FROM "users" ORDER BY name DESC, email
+ def order(*args)
+ @options[:order] << args
+ end
+
+ # Replaces any existing order defined on the relation with the specified order.
+ #
+ # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
+ #
+ # Subsequent calls to order on the same relation will be appended. For example:
+ #
+ # User.order('email DESC').reorder('id ASC').order('name ASC')
+ #
+ # generates a query with 'ORDER BY name ASC, id ASC'.
+ def reorder(*args)
+ @options[:order] = args
+ end
+
+ def all
+ @options[:conditions] = nil
+ self
+ end
+
+ # Works in two unique ways.
+ #
+ # First: takes a block so it can be used just like Array#select.
+ #
+ # Model.all.select { |m| m.field == value }
+ #
+ # This will build an array of objects from the database for the scope,
+ # converting them into an array and iterating through them using Array#select.
+ #
+ # Second: Modifies the SELECT statement for the query so that only certain
+ # fields are retrieved:
+ #
+ # Model.select(:field)
+ # # => [#<Model field:value>]
+ #
+ # Although in the above example it looks as though this method returns an
+ # array, it actually returns a relation object and can have other query
+ # methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
+ #
+ # The argument to the method can also be an array of fields.
+ #
+ # Model.select(:field, :other_field, :and_one_more)
+ # # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
+ #
+ def select(*fields)
+ if block_given?
+ to_a.select { |*block_args| yield(*block_args) }
+ else
+ raise ArgumentError, 'Call this with at least one field' if fields.empty?
+ clone.select!(*fields)
+ end
+ end
+
+ # Like #select, but modifies relation in place.
+ def select!(*fields)
+ @options[:select] ||= fields.join ','
+ self
+ end
+ alias_method :pluck, :select!
+
+ def reverse_order
+ @options[:reversed] = true
+ self
+ end
+
+ def readonly
+ @options[:readonly] = true
+ self
+ end
+
+ def to_a
+ load
+ @records
+ end
+
+ def as_json(options = nil) #:nodoc:
+ to_a.as_json(options)
+ end
+
+ # Returns size of the results (not size of the stored collection)
+ def size
+ loaded? ? @records.length : count
+ end
+
+ # true if there are no records
+ def empty?
+ return @records.empty? if loaded?
+
+ c = count
+ c.respond_to?(:zero?) ? c.zero? : c.empty?
+ end
+
+ # true if there are any records
+ def any?
+ if block_given?
+ to_a.any? { |*block_args| yield(*block_args) }
+ else
+ !empty?
+ end
+ end
+
+ # true if there is more than one record
+ def many?
+ if block_given?
+ to_a.many? { |*block_args| yield(*block_args) }
+ else
+ limit_value ? to_a.many? : size > 1
+ end
+ end
+
+ # find_all_by_
+ # find_by_
+ # find_first_by_
+ # find_last_by_
+ def method_missing(method_name, *args)
+
+ method = method_name.to_s
+
+ if method.end_with? '!'
+ method.chop!
+ error_on_empty = true
+ end
+
+ if method.start_with? 'find_all_by_'
+ attribs = method.gsub /^find_all_by_/, ''
+ elsif method.start_with? 'find_by_'
+ attribs = method.gsub /^find_by_/, ''
+ limit(1)
+ elsif method.start_with? 'find_first_by_'
+ limit(1)
+ find_first = true
+ attribs = method.gsub /^find_first_by_/, ''
+ elsif method.start_with? 'find_last_by_'
+ limit(1)
+ find_last = true
+ attribs = method.gsub /^find_last_by_/, ''
+ else
+ super
+ end
+
+ attribs = attribs.split '_and_'
+ conditions = {}
+ attribs.each { |attr| conditions[attr] = args.shift }
+
+ where(conditions, *args)
+ load
+ raise RecordNotFound if error_on_empty && @records.empty?
+ return @records.first if limit_value == 1
+ @records
+ end
+
+ # Tries to create a new record with the same scoped attributes
+ # defined in the relation. Returns the initialized object if validation fails.
+ #
+ # Expects arguments in the same format as +Base.create+.
+ #
+ # ==== Examples
+ # users = User.where(name: 'Oscar')
+ # users.create # #<User id: 3, name: "oscar", ...>
+ #
+ # users.create(name: 'fxn')
+ # users.create # #<User id: 4, name: "fxn", ...>
+ #
+ # users.create { |user| user.name = 'tenderlove' }
+ # # #<User id: 5, name: "tenderlove", ...>
+ #
+ # users.create(name: nil) # validation on name
+ # # #<User id: nil, name: nil, ...>
+ def create(*args, &block)
+ @model_class.create(*args, &block)
+ end
+
+ # Similar to #create, but calls +create!+ on the base class. Raises
+ # an exception if a validation error occurs.
+ #
+ # Expects arguments in the same format as <tt>Base.create!</tt>.
+ def create!(*args, &block)
+ @model_class.create!(*args, &block)
+ end
+
+ # Tries to load the first record; if it fails, then <tt>create</tt> is called with the same arguments as this method.
+ #
+ # Expects arguments in the same format as +Base.create+.
+ #
+ # ==== Examples
+ # # Find the first user named Pen�lope or create a new one.
+ # User.where(:first_name => 'Pen�lope').first_or_create
+ # # => <User id: 1, first_name: 'Pen�lope', last_name: nil>
+ #
+ # # Find the first user named Pen�lope or create a new one.
+ # # We already have one so the existing record will be returned.
+ # User.where(:first_name => 'Pen�lope').first_or_create
+ # # => <User id: 1, first_name: 'Pen�lope', last_name: nil>
+ #
+ # # Find the first user named Scarlett or create a new one with a particular last name.
+ # User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson')
+ # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
+ #
+ # # Find the first user named Scarlett or create a new one with a different last name.
+ # # We already have one so the existing record will be returned.
+ # User.where(:first_name => 'Scarlett').first_or_create do |user|
+ # user.last_name = "O'Hara"
+ # end
+ # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
+ def first_or_create(attributes={}, &block)
+ result = first
+ unless result
+ attributes = @options[:hash].merge(attributes) if @options[:hash]
+ result = create(attributes, &block)
+ end
+ result
+ end
+
+ # Like <tt>first_or_create</tt> but calls <tt>create!</tt> so an exception is raised if the created record is invalid.
+ #
+ # Expects arguments in the same format as <tt>Base.create!</tt>.
+ def first_or_create!(attributes={}, &block)
+ result = first
+ unless result
+ attributes = @options[:hash].merge(attributes) if @options[:hash]
+ result = create!(attributes, &block)
+ end
+ result
+ end
+
+ # Like <tt>first_or_create</tt> but calls <tt>new</tt> instead of <tt>create</tt>.
+ #
+ # Expects arguments in the same format as <tt>Base.new</tt>.
+ def first_or_initialize(attributes={}, &block)
+ result = first
+ unless result
+ attributes = @options[:hash].merge(attributes) if @options[:hash]
+ result = @model_class.new(attributes, &block)
+ end
+ result
+ end
+
+ # Destroys the records matching +conditions+ by instantiating each
+ # record and calling its +destroy+ method. Each object's callbacks are
+ # executed (including <tt>:dependent</tt> association options and
+ # +before_destroy+/+after_destroy+ Observer methods). Returns the
+ # collection of objects that were destroyed; each will be frozen, to
+ # reflect that no changes should be made (since they can't be
+ # persisted).
+ #
+ # Note: Instantiation, callback execution, and deletion of each
+ # record can be time consuming when you're removing many records at
+ # once. It generates at least one SQL +DELETE+ query per record (or
+ # possibly more, to enforce your callbacks). If you want to delete many
+ # rows quickly, without concern for their associations or callbacks, use
+ # +delete_all+ instead.
+ #
+ # ==== Parameters
+ #
+ # * +conditions+ - A string, array, or hash that specifies which records
+ # to destroy. If omitted, all records are destroyed. See the
+ # Conditions section in the introduction to ActiveRecord::Base for
+ # more information.
+ #
+ # ==== Examples
+ #
+ # Person.destroy_all("last_login < '2004-04-04'")
+ # Person.destroy_all(status: "inactive")
+ # Person.where(:age => 0..18).destroy_all
+ def destroy_all(conditions=nil)
+ if conditions
+ where(conditions).destroy_all
+ else
+ to_a.each {|object| object.destroy}
+ @records
+ end
+ end
+
+ # Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
+ # therefore all callbacks and filters are fired off before the object is deleted. This method is
+ # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
+ #
+ # This essentially finds the object (or multiple objects) with the given id, creates a new object
+ # from the attributes, and then calls destroy on it.
+ #
+ # ==== Parameters
+ #
+ # * +id+ - Can be either an Integer or an Array of Integers.
+ #
+ # ==== Examples
+ #
+ # # Destroy a single object
+ # Foo.destroy(1)
+ #
+ # # Destroy multiple objects
+ # foos = [1,2,3]
+ # Foo.destroy(foos)
+ def destroy(id)
+ if id.is_a?(Array)
+ id.map {|one_id| destroy(one_id)}
+ else
+ find(id).destroy
+ end
+ end
+
+ # Deletes the records matching +conditions+ without instantiating the records
+ # first, and hence not calling the +destroy+ method nor invoking callbacks. This
+ # is a single SQL DELETE statement that goes straight to the database, much more
+ # efficient than +destroy_all+. Be careful with relations though, in particular
+ # <tt>:dependent</tt> rules defined on associations are not honored. Returns the
+ # number of rows affected.
+ #
+ # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
+ # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
+ # Post.where(:person_id => 5).where(:category => ['Something', 'Else']).delete_all
+ #
+ # Both calls delete the affected posts all at once with a single DELETE statement.
+ # If you need to destroy dependent associations or call your <tt>before_*</tt> or
+ # +after_destroy+ callbacks, use the +destroy_all+ method instead.
+ #
+ # If a limit scope is supplied, +delete_all+ raises an ActiveRecord error:
+ #
+ # Post.limit(100).delete_all
+ # # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit scope
+ def delete_all(conditions=nil)
+ raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value
+
+ if conditions
+ where(conditions).delete_all
+ else
+ pluck :uuid
+ response = load
+ response.each {|entity| entity.delete} # todo: can this be optimized into one call?
+ response.size
+ end
+ end
+
+ # Deletes the row with a primary key matching the +id+ argument, using a
+ # SQL +DELETE+ statement, and returns the number of rows deleted. Active
+ # Record objects are not instantiated, so the object's callbacks are not
+ # executed, including any <tt>:dependent</tt> association options or
+ # Observer methods.
+ #
+ # You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
+ #
+ # Note: Although it is often much faster than the alternative,
+ # <tt>#destroy</tt>, skipping callbacks might bypass business logic in
+ # your application that ensures referential integrity or performs other
+ # essential jobs.
+ #
+ # ==== Examples
+ #
+ # # Delete a single row
+ # Foo.delete(1)
+ #
+ # # Delete multiple rows
+ # Foo.delete([2,3,4])
+ def delete(id_or_array)
+ if id_or_array.is_a? Array
+ id_or_array.each {|id| @model_class.resource[id].delete} # todo: can this be optimized into one call?
+ else
+ @model_class.resource[id_or_array].delete
+ end
+ end
+
+ # Updates all records with details given if they match a set of conditions supplied, limits and order can
+ # also be supplied. This method sends a single update straight to the database. It does not instantiate
+ # the involved models and it does not trigger Active Record callbacks or validations.
+ #
+ # ==== Parameters
+ #
+ # * +updates+ - hash of attribute updates
+ #
+ # ==== Examples
+ #
+ # # Update all customers with the given attributes
+ # Customer.update_all wants_email: true
+ #
+ # # Update all books with 'Rails' in their title
+ # Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
+ #
+ # # Update all books that match conditions, but limit it to 5 ordered by date
+ # Book.where('title LIKE ?', '%Rails%').order(:created).limit(5).update_all(:author => 'David')
+ def update_all(updates)
+ raise ArgumentError, "Empty list of attributes to change" if updates.blank?
+ raise ArgumentError, "updates must be a Hash" unless updates.is_a? Hash
+ run_update(updates)
+ end
+
+ # Looping through a collection of records from the database
+ # (using the +all+ method, for example) is very inefficient
+ # since it will try to instantiate all the objects at once.
+ #
+ # In that case, batch processing methods allow you to work
+ # with the records in batches, thereby greatly reducing memory consumption.
+ #
+ # The #find_each method uses #find_in_batches with a batch size of 1000 (or as
+ # specified by the +:batch_size+ option).
+ #
+ # Person.all.find_each do |person|
+ # person.do_awesome_stuff
+ # end
+ #
+ # Person.where("age > 21").find_each do |person|
+ # person.party_all_night!
+ # end
+ #
+ # You can also pass the +:start+ option to specify
+ # an offset to control the starting point.
+ def find_each(options = {})
+ find_in_batches(options) do |records|
+ records.each { |record| yield record }
+ end
+ end
+
+ # Yields each batch of records that was found by the find +options+ as
+ # an array. The size of each batch is set by the +:batch_size+
+ # option; the default is 1000.
+ #
+ # You can control the starting point for the batch processing by
+ # supplying the +:start+ option. This is especially useful if you
+ # want multiple workers dealing with the same processing queue. You can
+ # make worker 1 handle all the records between id 0 and 10,000 and
+ # worker 2 handle from 10,000 and beyond (by setting the +:start+
+ # option on that worker).
+ #
+ # It's not possible to set the order. That is automatically set to
+ # ascending on the primary key ("id ASC") to make the batch ordering
+ # work. This also mean that this method only works with integer-based
+ # primary keys. You can't set the limit either, that's used to control
+ # the batch sizes.
+ #
+ # Person.where("age > 21").find_in_batches do |group|
+ # sleep(50) # Make sure it doesn't get too crowded in there!
+ # group.each { |person| person.party_all_night! }
+ # end
+ #
+ # # Let's process the next 2000 records
+ # Person.all.find_in_batches(start: 2000, batch_size: 2000) do |group|
+ # group.each { |person| person.party_all_night! }
+ # end
+ def find_in_batches(options={})
+ options.assert_valid_keys(:start, :batch_size)
+
+ raise "Not yet implemented" # todo
+
+ start = options.delete(:start) || 0
+ batch_size = options.delete(:batch_size) || 1000
+
+ while records.any?
+ records_size = records.size
+ primary_key_offset = records.last.id
+
+ yield records
+
+ break if records_size < batch_size
+
+ if primary_key_offset
+ records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
+ else
+ raise "Primary key not included in the custom select clause"
+ end
+ end
+ end
+
+ # Updates an object (or multiple objects) and saves it to the database, if validations pass.
+ # The resulting object is returned whether the object was saved successfully to the database or not.
+ #
+ # ==== Parameters
+ #
+ # * +id+ - This should be the id or an array of ids to be updated.
+ # * +attributes+ - This should be a hash of attributes or an array of hashes.
+ #
+ # ==== Examples
+ #
+ # # Updates one record
+ # Person.update(15, user_name: 'Samuel', group: 'expert')
+ #
+ # # Updates multiple records
+ # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
+ # Person.update(people.keys, people.values)
+ def update(id, attributes)
+ if id.is_a?(Array)
+ id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
+ else
+ object = find(id)
+ object.update_attributes(attributes)
+ object
+ end
+ end
+
+ ## todo: scoping
+ ## Scope all queries to the current scope.
+ ##
+ ## Comment.where(:post_id => 1).scoping do
+ ## Comment.first # SELECT * FROM comments WHERE post_id = 1
+ ## end
+ ##
+ ## Please check unscoped if you want to remove all previous scopes (including
+ ## the default_scope) during the execution of a block.
+ #def scoping
+ # previous, @model_class.current_scope = @model_class.current_scope, self
+ # yield
+ #ensure
+ # klass.current_scope = previous
+ #end
+
+
+ # #where accepts conditions in one of several formats.
+ #
+ # === string
+ #
+ # A single string, without additional arguments, is used in the where clause of the query.
+ #
+ # Client.where("orders_count = '2'")
+ # # SELECT * where orders_count = '2';
+ #
+ # Note that building your own string from user input may expose your application
+ # to injection attacks if not done properly. As an alternative, it is recommended
+ # to use one of the following methods.
+ #
+ # === array
+ #
+ # If an array is passed, then the first element of the array is treated as a template, and
+ # the remaining elements are inserted into the template to generate the condition.
+ # Active Record takes care of building the query to avoid injection attacks, and will
+ # convert from the ruby type to the database type where needed. Elements are inserted
+ # into the string in the order in which they appear.
+ #
+ # User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
+ # # SELECT * WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # Alternatively, you can use named placeholders in the template, and pass a hash as the
+ # second element of the array. The names in the template are replaced with the corresponding
+ # values from the hash.
+ #
+ # User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
+ # # SELECT * WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # This can make for more readable code in complex queries.
+ #
+ # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
+ # than the previous methods; you are responsible for ensuring that the values in the template
+ # are properly quoted. The values are passed to the connector for quoting, but the caller
+ # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
+ # the values are inserted using the same escapes as the Ruby core method <tt>Kernel::sprintf</tt>.
+ #
+ # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
+ # # SELECT * WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # If #where is called with multiple arguments, these are treated as if they were passed as
+ # the elements of a single array.
+ #
+ # User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
+ # # SELECT * WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # When using strings to specify conditions, you can use any operator available from
+ # the database. While this provides the most flexibility, you can also unintentionally introduce
+ # dependencies on the underlying database. If your code is intended for general consumption,
+ # test with multiple database backends.
+ #
+ # === hash
+ #
+ # #where will also accept a hash condition, in which the keys are fields and the values
+ # are values to be searched for.
+ #
+ # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
+ #
+ # User.where({ name: "Joe", email: "joe@example.com" })
+ # # SELECT * WHERE name = 'Joe' AND email = 'joe@example.com'
+ #
+ # User.where({ name: ["Alice", "Bob"]})
+ # # SELECT * WHERE name IN ('Alice', 'Bob')
+ #
+ # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
+ # # SELECT * WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
+ #
+ # In the case of a belongs_to relationship, an association key can be used
+ # to specify the model if an ActiveRecord object is used as the value.
+ #
+ # author = Author.find(1)
+ #
+ # # The following queries will be equivalent:
+ # Post.where(:author => author)
+ # Post.where(:author_id => author)
+ #
+ # === empty condition
+ #
+ # If the condition returns true for blank?, then where is a no-op and returns the current relation.
+ #
+ def where(opts, *rest)
+ return self if opts.blank?
+ case opts
+ when Hash
+ @options[:hash] = opts # keep around for first_or_create stuff...
+ opts.each do |k,v|
+ # todo: can we support IN and BETWEEN syntax as documented above?
+ v = "'#{v}'" if v.is_a? String
+ query_conditions << "#{k} = #{v}"
+ end
+ when String
+ query_conditions << opts
+ when Array
+ query = opts.shift.gsub '?', "'%s'"
+ query = query % opts
+ query_conditions << query
+ end
+ self
+ end
+
+
+ protected
+
+
+ def limit_value
+ @options[:limit]
+ end
+
+ def query_conditions
+ @options[:conditions] ||= []
+ end
+
+ def loaded?
+ !!@records
+ end
+
+ def reversed?
+ !!@options[:reversed]
+ end
+
+ def find_one(id_or_name=nil)
+ begin
+ entity = @model_class.resource[id_or_name].query(nil, limit: 1).entity
+ @model_class.model_name.constantize.new(entity.data) if entity
+ rescue RestClient::ResourceNotFound
+ nil
+ end
+ end
+
+ def find_one!(id_or_name=nil)
+ find_one(id_or_name) or raise RecordNotFound
+ end
+
+ # Server-side options:
+ # Xql string Query in the query language
+ # type string Entity type to return
+ # Xreversed string Return results in reverse order
+ # connection string Connection type (e.g., "likes")
+ # start string First entity's UUID to return
+ # cursor string Encoded representation of the query position for paging
+ # Xlimit integer Number of results to return
+ # permission string Permission type
+ # Xfilter string Condition on which to filter
+ def query_options
+ # todo: support more options?
+ options = {}
+ options.merge!({:limit => limit_value.to_json}) if limit_value
+ options.merge!({:skip => @options[:skip].to_json}) if @options[:skip]
+ options.merge!({:reversed => reversed?.to_json}) if reversed?
+ options.merge!({:order => @options[:order]}) if @options[:order]
+ options.merge!({:cursor => @options[:cursor]}) if @options[:cursor]
+ options
+ end
+
+ def create_query
+ select = @options[:select] || '*'
+ where = ('where ' + query_conditions.join(' and ')) unless query_conditions.blank?
+ "select #{select} #{where}"
+ end
+
+ def run_query
+ @model_class.resource.query(create_query, query_options)
+ end
+
+ def run_update(attributes)
+ @model_class.resource.update_query(attributes, create_query, query_options)
+ end
+
+ def load
+ return if loaded?
+ begin
+ @response = run_query
+ if (!@options[:select] or @options[:select] == '*')
+ @records = @response.entities.collect {|r| @model_class.model_name.constantize.new(r.data)}
+ else # handle list
+ selects = @options[:select].split ','
+ @records = @response.entities.collect do |r|
+ data = {}
+ (0..selects.size).each do |i|
+ data[selects[i]] = r[i]
+ end
+ @model_class.model_name.constantize.new(data)
+ end
+ end
+ rescue RestClient::ResourceNotFound
+ @records = []
+ end
+ end
+ end
+ end
+end
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby-on-rails/lib/usergrid_ironhorse/user_context.rb
----------------------------------------------------------------------
diff --git a/sdks/other/ruby-on-rails/lib/usergrid_ironhorse/user_context.rb b/sdks/other/ruby-on-rails/lib/usergrid_ironhorse/user_context.rb
new file mode 100644
index 0000000..b7a0804
--- /dev/null
+++ b/sdks/other/ruby-on-rails/lib/usergrid_ironhorse/user_context.rb
@@ -0,0 +1,96 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+module Usergrid
+ module Ironhorse
+ module UserContext
+
+ # returns user if logged in, nil otherwise
+ # if session is passed, stores user_id and auth_token in the session
+ # and sets User as current_user
+ def authenticate(username, password, session=nil)
+ application = Usergrid::Application.new Usergrid::Ironhorse::Base.settings[:application_url]
+ begin
+ application.login username, password
+ user = new application.current_user.data
+ if session
+ session[:usergrid_user_id] = user.id
+ session[:usergrid_auth_token] = user.current_auth_token
+ set_thread_context(session)
+ Thread.current[:usergrid_current_user] = user
+ end
+ user
+ rescue
+ Rails.logger.info $!
+ raise $!
+ end
+ end
+
+ # clears auth from session and thread
+ def clear_authentication(session)
+ session[:usergrid_user_id] = nil
+ session[:usergrid_auth_token] = nil
+ clear_thread_context(session)
+ end
+
+ # allows admin actions to be done in a block
+ def as_admin(&block)
+ save_auth_token = Thread.current[:usergrid_auth_token]
+ begin
+ unless Base.settings[:auth_token]
+ resource = RestClient::Resource.new Base.settings[:application_url]
+ response = resource['token'].post grant_type: 'client_credentials', client_id: Base.settings[:client_id], client_secret: Base.settings[:client_secret]
+ Base.settings[:auth_token] = MultiJson.load(response)['access_token']
+ end
+ Thread.current[:usergrid_auth_token] = Base.settings[:auth_token]
+ yield block
+ ensure
+ Thread.current[:usergrid_auth_token] = save_auth_token
+ end
+ end
+
+ # sets auth for current thread
+ def set_thread_context(session)
+ Thread.current[:usergrid_user_id] = session[:usergrid_user_id]
+ Thread.current[:usergrid_auth_token] = session[:usergrid_auth_token]
+ Thread.current[:usergrid_current_user] = nil
+ end
+ alias_method :set_context, :set_thread_context
+
+ # clears auth from current thread
+ def clear_thread_context
+ Thread.current[:usergrid_user_id] = nil
+ Thread.current[:usergrid_auth_token] = nil
+ Thread.current[:usergrid_current_user] = nil
+ end
+ alias_method :clear_context, :clear_thread_context
+
+ # returns the auth token for the current thread
+ def current_auth_token
+ Thread.current[:usergrid_auth_token]
+ end
+
+ # does a find and return
+ def current_user
+ unless Thread.current[:usergrid_current_user]
+ Thread.current[:usergrid_current_user] = find(Thread.current[:usergrid_user_id]) if Thread.current[:usergrid_user_id]
+ end
+ Thread.current[:usergrid_current_user]
+ end
+
+ end
+ end
+end
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby-on-rails/lib/usergrid_ironhorse/version.rb
----------------------------------------------------------------------
diff --git a/sdks/other/ruby-on-rails/lib/usergrid_ironhorse/version.rb b/sdks/other/ruby-on-rails/lib/usergrid_ironhorse/version.rb
new file mode 100644
index 0000000..990b730
--- /dev/null
+++ b/sdks/other/ruby-on-rails/lib/usergrid_ironhorse/version.rb
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+module Usergrid
+ module Ironhorse
+ VERSION = '0.1.2'
+ end
+end
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby-on-rails/spec/spec_helper.rb
----------------------------------------------------------------------
diff --git a/sdks/other/ruby-on-rails/spec/spec_helper.rb b/sdks/other/ruby-on-rails/spec/spec_helper.rb
new file mode 100644
index 0000000..2cee2c2
--- /dev/null
+++ b/sdks/other/ruby-on-rails/spec/spec_helper.rb
@@ -0,0 +1,93 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'simplecov'
+require 'test/unit'
+require 'rspec'
+require 'yaml'
+require 'securerandom'
+require_relative '../lib/usergrid_ironhorse'
+
+ENV["RAILS_ENV"] ||= 'test'
+Dir[Pathname.new(File.dirname(__FILE__)).join("support/**/*.rb")].each { |f| require f }
+
+RSpec.configure do |config|
+ config.mock_with :rspec
+ config.expect_with :stdlib
+ config.expect_with :rspec
+end
+
+LOG = Logger.new(STDOUT)
+RestClient.log=LOG
+
+SimpleCov.at_exit do
+ SimpleCov.result.format!
+ #index = File.join(SimpleCov.coverage_path, 'index.html')
+ #`open #{index}` if File.exists?(index)
+end
+SimpleCov.start
+
+
+SPEC_SETTINGS = YAML::load_file(File.join File.dirname(__FILE__), 'spec_settings.yaml')
+Usergrid::Ironhorse::Base.configure! nil, nil
+
+def login_management
+ management = Usergrid::Resource.new(SPEC_SETTINGS[:api_url]).management
+ management.login SPEC_SETTINGS[:organization][:username], SPEC_SETTINGS[:organization][:password]
+ management
+end
+
+# ensure we are correctly setup (management login & organization)
+management = login_management
+
+begin
+ management.create_organization(SPEC_SETTINGS[:organization][:name],
+ SPEC_SETTINGS[:organization][:username],
+ SPEC_SETTINGS[:organization][:username],
+ "#{SPEC_SETTINGS[:organization][:username]}@email.com",
+ SPEC_SETTINGS[:organization][:password])
+ LOG.info "created organization with user #{SPEC_SETTINGS[:organization][:username]}@email.com"
+rescue
+ if MultiJson.load($!.response)['error'] == "duplicate_unique_property_exists"
+ LOG.debug "test organization exists"
+ else
+ raise $!
+ end
+end
+
+def create_random_application
+ management = login_management
+ organization = management.organization SPEC_SETTINGS[:organization][:name]
+ app_name = "_test_app_#{SecureRandom.hex}"
+ organization.create_application app_name
+ management.application SPEC_SETTINGS[:organization][:name], app_name
+end
+
+def delete_application(application)
+ management = login_management
+ application.auth_token = management.auth_token
+ application.delete rescue nil # not implemented on server yet
+end
+
+def create_random_user(application, login=false)
+ random = SecureRandom.hex
+ user_hash = {username: "username_#{random}",
+ password: random,
+ email: "#{random}@email.com",
+ name: "#{random} name" }
+ entity = application['users'].post(user_hash).entity
+ application.login user_hash[:username], user_hash[:password] if login
+ entity
+end
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby-on-rails/spec/spec_settings.yaml
----------------------------------------------------------------------
diff --git a/sdks/other/ruby-on-rails/spec/spec_settings.yaml b/sdks/other/ruby-on-rails/spec/spec_settings.yaml
new file mode 100644
index 0000000..815ba43
--- /dev/null
+++ b/sdks/other/ruby-on-rails/spec/spec_settings.yaml
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+:api_url: http://localhost:8080
+:organization:
+ :name: test-organization
+ :username: test
+ :password: test
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby-on-rails/spec/support/active_model_lint.rb
----------------------------------------------------------------------
diff --git a/sdks/other/ruby-on-rails/spec/support/active_model_lint.rb b/sdks/other/ruby-on-rails/spec/support/active_model_lint.rb
new file mode 100644
index 0000000..267be05
--- /dev/null
+++ b/sdks/other/ruby-on-rails/spec/support/active_model_lint.rb
@@ -0,0 +1,33 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# spec/support/active_model_lint.rb
+# adapted from rspec-rails: http://github.com/rspec/rspec-rails/blob/master/spec/rspec/rails/mocks/mock_model_spec.rb
+
+shared_examples_for "ActiveModel" do
+ include ActiveModel::Lint::Tests
+
+ # to_s is to support ruby-1.9
+ ActiveModel::Lint::Tests.public_instance_methods.map{|m| m.to_s}.grep(/^test/).each do |m|
+ example m.sub('_',' ') do
+ send m
+ end
+ end
+
+ def model
+ subject
+ end
+end
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby-on-rails/spec/usergrid_ironhorse/base_spec.rb
----------------------------------------------------------------------
diff --git a/sdks/other/ruby-on-rails/spec/usergrid_ironhorse/base_spec.rb b/sdks/other/ruby-on-rails/spec/usergrid_ironhorse/base_spec.rb
new file mode 100644
index 0000000..4aa1da6
--- /dev/null
+++ b/sdks/other/ruby-on-rails/spec/usergrid_ironhorse/base_spec.rb
@@ -0,0 +1,452 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+describe Usergrid::Ironhorse::Base do
+
+ it_should_behave_like 'ActiveModel'
+
+ class User < Usergrid::Ironhorse::Base
+ extend Usergrid::Ironhorse::UserContext
+ end
+
+ before :all do
+ @application = create_random_application
+ Foo.configure!(@application.url, @application.auth_token)
+ @user = create_random_user @application, true
+ @foo = (@application.create_entity 'foos', name: 'foo42', answer: 42).entity
+ end
+
+ after :all do
+ @foo.delete
+ @user.delete
+ #delete_application @application # not supported on server yet
+ end
+
+ class Foo < Usergrid::Ironhorse::Base; end
+ Foo.validates :name, :presence => true
+
+ class Bar < Usergrid::Ironhorse::Base; end
+
+ describe 'subclasses should be able to' do
+
+ it "do tasks as admin when requested" do
+ organization = @foo.management.organization SPEC_SETTINGS[:organization][:name]
+ organization.logout
+
+ # should fail under current user's context
+ expect {
+ organization.create_application "_test_app_#{SecureRandom.hex}"
+ }.to raise_error RestClient::Unauthorized
+
+ # should succeed under admin context
+ User.as_admin do
+ organization.create_application "_test_app_#{SecureRandom.hex}"
+ end
+ end
+
+ it "do tasks as admin if require_login is false" do
+ organization = @foo.management.organization SPEC_SETTINGS[:organization][:name]
+ organization.logout
+
+ # should fail under current user's context
+ expect {
+ organization.create_application "_test_app_#{SecureRandom.hex}"
+ }.to raise_error RestClient::Unauthorized
+
+ # should succeed once require_login is false
+ User.settings[:require_login] = false
+ organization.create_application "_test_app_#{SecureRandom.hex}"
+ User.settings[:require_login] = true
+ end
+
+ it 'be created and destroyed' do
+ foo = Foo.create name: 'foo man'
+ foo.persisted?.should be_true
+ foo.name.should eq 'foo man'
+ foo = Foo.find_by_name 'foo man'
+ foo.should_not be_nil
+ foo.destroy.should be_true
+ foo.persisted?.should be_false
+ foo = Foo.find_by_name 'foo man'
+ foo.should be_nil
+ end
+
+ it 'be changed and saved' do
+ foo = Foo.find_by_name @foo.name
+ foo.answer.should eq @foo.answer
+ foo.number = 43
+ foo.changed?.should be_true
+ foo.number.should == 43
+ foo.save!
+ foo = Foo.find_by_name @foo.name
+ foo.number.should == 43
+ end
+
+ it 'be reloaded' do
+ foo = Foo.find @foo.uuid
+ foo.answer = 44
+ foo.changed?.should be_true
+ foo.answer.should == 44
+ foo.reload
+ foo.answer.should == 42
+ end
+
+ it 'be found using find_by_name' do
+ foo = Foo.find_by_name @foo.name
+ foo.uuid.should_not be_nil
+ foo.name.should eq @foo.name
+ foo.should be_a Foo
+ foo.persisted?.should be_true
+ end
+
+ it 'be found using find_by_name!' do
+ foo = Foo.find_by_name! @foo.name
+ foo.uuid.should_not be_nil
+ foo.name.should eq @foo.name
+ foo.should be_a Foo
+ foo.persisted?.should be_true
+ end
+
+ it 'throw a RecordNotFound when find_by_name! misses' do
+ expect { Foo.find_by_name! 'name3' }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'add a validation' do
+ foo = Foo.new
+ foo.valid?.should be_false
+ foo.name = 'joe'
+ foo.valid?.should be_true
+ end
+
+ it 'fail to save an invalid record and save the errors' do
+ foo = Foo.new
+ foo.save.should be_false
+ foo.persisted?.should be_false
+ foo.errors.count.should == 1
+ foo.errors.get(:name).first.should eq "can't be blank"
+ end
+
+ it 'fail to create an invalid record and save the errors' do
+ foo = Foo.create
+ foo.persisted?.should be_false
+ foo.errors.count.should == 1
+ foo.errors.get(:name).first.should eq "can't be blank"
+ end
+
+ it 'fail to save! an invalid record' do
+ expect { Foo.new.save! }.to raise_error(ActiveRecord::RecordNotSaved)
+ end
+
+ it 'fail to create! an invalid record' do
+ expect { Foo.create! }.to raise_error(ActiveRecord::RecordNotSaved)
+ end
+
+ it 'retrieve first' do
+ foo2 = Foo.create! name: 'foo2'
+ foo = Foo.first
+ foo.uuid.should eq @foo.uuid
+ foo2.destroy
+ end
+
+ it 'retrieve last' do
+ foo2 = Foo.create! name: 'foo2'
+ foo = Foo.last
+ foo.uuid.should eq foo2.uuid
+ foo2.destroy
+ end
+
+ it 'take multiple' do
+ foo2 = Foo.create! name: 'foo2'
+ foos = Foo.take(2)
+ foos.size.should == 2
+ foos.first.uuid.should_not be_nil
+ foo2.destroy
+ end
+
+ it 'find multiple by id' do
+ foo2 = Foo.create name: 'foo2'
+ foos = Foo.find @foo.uuid, foo2.uuid
+ foos.size.should == 2
+ foos.first.uuid.should eq @foo.uuid
+ foos.last.uuid.should eq foo2.uuid
+ foo2.destroy
+ end
+
+ it 'find multiple by name' do
+ foo2 = Foo.create! name: 'foo2'
+ foos = Foo.find @foo.name, foo2.name
+ foos.size.should == 2
+ foos.first.uuid.should eq @foo.uuid
+ foos.last.uuid.should eq foo2.uuid
+ foo2.destroy
+ end
+
+ it 'check exists?(string)' do
+ Foo.exists?(@foo.name).should be_true
+ end
+
+ it 'check not exists?(string)' do
+ Foo.exists?('asdfasdf').should be_false
+ end
+
+ it 'check exists?(Array)' do
+ Foo.exists?(['name = ?', @foo.name]).should be_true
+ end
+
+ it 'check not exists?(Array)' do
+ Foo.exists?(['name = ?', 'asdfasdf']).should be_false
+ end
+
+ it 'check exists?(Hash)' do
+ Foo.exists?(name: @foo.name).should be_true
+ end
+
+ it 'check not exists?(Hash)' do
+ Foo.exists?(name: 'asfasdf').should be_false
+ end
+
+ it 'check exists?(nil)' do
+ Foo.exists?.should be_true
+ end
+
+ it 'check not exists?(nil)' do
+ Bar.exists?.should be_false
+ end
+
+ it 'perform first_or_create where found' do
+ foo = Foo.where(name: @foo.name).first_or_create
+ foo.uuid.should eq @foo.uuid
+ end
+
+ it 'perform first_or_create where not found' do
+ foo = Foo.where(name: 'foo2').first_or_create(number: 42)
+ foo.name.should eq 'foo2'
+ foo.number.should == 42
+ foo.destroy
+ end
+
+ it 'perform first_or_create! where found' do
+ foo = Foo.where(name: @foo.name).first_or_create!
+ foo.uuid.should eq @foo.uuid
+ end
+
+ it 'perform first_or_create! where not found' do
+ expect { Foo.where(nuumber: 'foo2').first_or_create! }.to raise_error(ActiveRecord::RecordNotSaved)
+ end
+
+ it 'perform first_or_initialize where found' do
+ foo = Foo.where(name: @foo.name).first_or_initialize(xxx: 42)
+ foo.uuid.should eq @foo.uuid
+ foo.xxx.should be_nil
+ end
+
+ it 'perform first_or_initialize where not found' do
+ foo = Foo.where(name: 'foo2').first_or_initialize(number: 42)
+ foo.name.should eq 'foo2'
+ foo.number.should == 42
+ foo.persisted?.should be_false
+ end
+
+ it "should destroy by id" do
+ foo = Foo.create! name: 'foo'
+ Foo.destroy(foo.id)
+ foo = Foo.find_by_name 'foo'
+ foo.should be_nil
+ end
+
+ it "should destroy by ids" do
+ foo = Foo.create! name: 'foo'
+ foo2 = Foo.create! name: 'foo2'
+ Foo.destroy([foo.id, foo2.id])
+ Foo.find_by_name('foo').should be_nil
+ Foo.find_by_name('foo2').should be_nil
+ end
+
+ it "should destroy_all" do
+ foo = Foo.create! name: 'foo', number: 42
+ foo2 = Foo.create! name: 'foo2', number: 42
+ Foo.destroy_all(number: 42)
+ Foo.find_by_name('foo').should be_nil
+ Foo.find_by_name('foo2').should be_nil
+ end
+
+ it "should delete by id" do
+ foo = Foo.create! name: 'foo'
+ Foo.delete(foo.id)
+ foo = Foo.find_by_name 'foo'
+ foo.should be_nil
+ end
+
+ it "should delete by ids" do
+ foo = Foo.create! name: 'foo'
+ foo2 = Foo.create! name: 'foo2'
+ Foo.delete([foo.id, foo2.id])
+ Foo.find_by_name('foo').should be_nil
+ Foo.find_by_name('foo2').should be_nil
+ end
+
+ it "should delete_all" do
+ foo = Foo.create! name: 'foo', number: 42
+ foo2 = Foo.create! name: 'foo2', number: 42
+ Foo.delete_all(number: 42)
+ Foo.find_by_name('foo').should be_nil
+ Foo.find_by_name('foo2').should be_nil
+ end
+
+ it "should update one" do
+ foo = Foo.create! name: 'foo', number: 42
+ Foo.update foo.uuid, { number: 43 }
+ foo.reload.number.should == 43
+ foo.destroy
+ end
+
+ it "should update multiple" do
+ foo = Foo.create! name: 'foo', number: 42
+ foo2 = Foo.create! name: 'foo2', number: 42
+ updates = { foo.uuid => {number: 43}, foo2.uuid => {number: 44}}
+ Foo.update(updates.keys, updates.values)
+ foo.reload.number.should == 43
+ foo2.reload.number.should == 44
+ foo.destroy
+ foo2.destroy
+ end
+
+ it "should update_all" do
+ foo = Foo.create! name: 'foo', number: 43
+ Foo.where(number: 43).update_all({number: 44})
+ Foo.find_by_number(44).should_not be_nil
+ foo.destroy
+ end
+
+ it "should fail on unaccessible mass assignment" do
+ Foo.attr_accessible :name
+ foo = Foo.create! name: 'foo', number: 43
+ foo.number.should_not eq 43
+ foo.update_attributes number: 44, foo: 'bar'
+ foo.number.should_not eq 44
+ foo.destroy
+ Foo._accessible_attributes = nil
+ end
+
+ it "should fail on protected mass assignment" do
+ Foo.attr_protected :number
+ foo = Foo.create! name: 'foo', number: 43
+ foo.number.should_not eq 43
+ foo.update_attributes number: 44, foo: 'bar'
+ foo.number.should_not eq 44
+ foo.destroy
+ Foo._protected_attributes = nil
+ end
+
+ it "should be able to page through results" do
+ bars = (1..15).collect do |i|
+ Bar.create! name: "name_#{i}", value: "value_#{i+1}"
+ end
+ query = Bar.all
+ page = query.to_a # first page
+ page.count.should eq 10
+ page = query.next_page # second page
+ count = 10
+ page.each do |bar|
+ bars[count].name.should eq bar.name
+ count += 1
+ end
+ count.should eq bars.count
+ bars.each {|bar| bar.delete}
+ end
+
+ it "should iterate past page boundaries" do
+ bars = (1..15).collect do |i|
+ Bar.create! name: "name_#{i}", value: "value_#{i+1}"
+ end
+ count = 0
+ Bar.all.each do |bar|
+ bars[count].name.should eq bar.name
+ count += 1
+ end
+ count.should eq bars.count
+ bars.each {|bar| bar.delete}
+ end
+
+ it "should honor limit" do
+ bars = (1..15).collect do |i|
+ Bar.create! name: "name_#{i}", value: "value_#{i+1}"
+ end
+ count = 0
+ Bar.limit(13).each do |bar|
+ bars[count].name.should eq bar.name
+ count += 1
+ end
+ count.should eq 13
+ bars.each {|bar| bar.delete}
+ end
+
+ it "perform as admin only when requested if require_login is true" do
+
+ organization = @foo.management.organization SPEC_SETTINGS[:organization][:name]
+
+ creds = nil
+ User.as_admin do
+ creds = organization.credentials
+ end
+
+ Usergrid::Ironhorse::Base.settings[:client_id] = creds.data.credentials.client_id
+ Usergrid::Ironhorse::Base.settings[:client_secret] = creds.data.credentials.client_secret
+ Usergrid::Ironhorse::Base.settings[:auth_token] = nil
+ User.clear_thread_context
+ organization.logout
+ User.settings[:require_login] = true
+
+ # should fail (login is required)
+ expect {
+ organization.create_application "_test_app_#{SecureRandom.hex}"
+ }.to raise_error RestClient::Unauthorized
+
+ # should succeed under admin context
+ User.as_admin do
+ organization.create_application "_test_app_#{SecureRandom.hex}"
+ end
+
+ Usergrid::Ironhorse::Base.settings[:client_id] = nil
+ Usergrid::Ironhorse::Base.settings[:client_secret] = nil
+ end
+
+ it "perform as admin if require_login is false" do
+
+ organization = @foo.management.organization SPEC_SETTINGS[:organization][:name]
+ organization.logout
+
+ creds = nil
+ User.as_admin do
+ creds = organization.credentials
+ end
+
+ Usergrid::Ironhorse::Base.settings[:client_id] = creds.data.credentials.client_id
+ Usergrid::Ironhorse::Base.settings[:client_secret] = creds.data.credentials.client_secret
+ Usergrid::Ironhorse::Base.settings[:auth_token] = nil
+ User.clear_thread_context
+ organization.logout
+
+ User.settings[:require_login] = false
+
+ # should succeed
+ organization.create_application "_test_app_#{SecureRandom.hex}"
+
+ Usergrid::Ironhorse::Base.settings[:client_id] = nil
+ Usergrid::Ironhorse::Base.settings[:client_secret] = nil
+ end
+ end
+end
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby-on-rails/usergrid_ironhorse.gemspec
----------------------------------------------------------------------
diff --git a/sdks/other/ruby-on-rails/usergrid_ironhorse.gemspec b/sdks/other/ruby-on-rails/usergrid_ironhorse.gemspec
new file mode 100644
index 0000000..430c5c1
--- /dev/null
+++ b/sdks/other/ruby-on-rails/usergrid_ironhorse.gemspec
@@ -0,0 +1,44 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# -*- encoding: utf-8 -*-
+lib = File.expand_path('../lib', __FILE__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
+require 'usergrid_ironhorse/version'
+
+Gem::Specification.new do |gem|
+ gem.name = "usergrid_ironhorse"
+ gem.version = Usergrid::Ironhorse::VERSION
+ gem.authors = ["Scott Ganyo"]
+ gem.email = ["scott@ganyo.com"]
+ gem.description = %q{Rails ActiveModel gem to access Usergrid / Apigee App Services}
+ gem.summary = %q{Usergrid_ironhorse enables simple ActiveModel access to Apigee's App Services
+ (aka Usergrid) REST API for Rails developers.}
+ gem.homepage = "https://github.com/scottganyo/usergrid_ironhorse"
+
+ gem.files = `git ls-files`.split($/)
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
+ gem.require_paths = ["lib"]
+
+ gem.add_dependency 'usergrid_iron', '0.9.1'
+ gem.add_dependency 'activemodel', '~> 3.2'
+ gem.add_dependency 'activerecord', '~> 3.2'
+ gem.add_dependency 'i18n'
+
+ gem.add_development_dependency 'rake'
+ gem.add_development_dependency 'rspec'
+ gem.add_development_dependency 'simplecov'
+end
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby/.gitignore
----------------------------------------------------------------------
diff --git a/sdks/other/ruby/.gitignore b/sdks/other/ruby/.gitignore
new file mode 100644
index 0000000..30b994d
--- /dev/null
+++ b/sdks/other/ruby/.gitignore
@@ -0,0 +1,18 @@
+*.gem
+*.rbc
+.bundle
+.config
+.yardoc
+Gemfile.lock
+InstalledFiles
+_yardoc
+coverage
+doc/
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+test/tmp
+test/version_tmp
+tmp
+.idea
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby/.rspec
----------------------------------------------------------------------
diff --git a/sdks/other/ruby/.rspec b/sdks/other/ruby/.rspec
new file mode 100644
index 0000000..c99d2e7
--- /dev/null
+++ b/sdks/other/ruby/.rspec
@@ -0,0 +1 @@
+--require spec_helper
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby/.rvmrc
----------------------------------------------------------------------
diff --git a/sdks/other/ruby/.rvmrc b/sdks/other/ruby/.rvmrc
new file mode 100644
index 0000000..55148ad
--- /dev/null
+++ b/sdks/other/ruby/.rvmrc
@@ -0,0 +1,2 @@
+rvm_gemset_create_on_use_flag=1
+rvm gemset use 'usergrid_iron'
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby/Gemfile
----------------------------------------------------------------------
diff --git a/sdks/other/ruby/Gemfile b/sdks/other/ruby/Gemfile
new file mode 100644
index 0000000..9a6d84d
--- /dev/null
+++ b/sdks/other/ruby/Gemfile
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+source 'https://rubygems.org'
+
+# Specify your gem's dependencies in usergrid_iron.gemspec
+gemspec
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby/LICENSE
----------------------------------------------------------------------
diff --git a/sdks/other/ruby/LICENSE b/sdks/other/ruby/LICENSE
new file mode 100644
index 0000000..ae1e83e
--- /dev/null
+++ b/sdks/other/ruby/LICENSE
@@ -0,0 +1,14 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby/README.md
----------------------------------------------------------------------
diff --git a/sdks/other/ruby/README.md b/sdks/other/ruby/README.md
new file mode 100644
index 0000000..2f6c594
--- /dev/null
+++ b/sdks/other/ruby/README.md
@@ -0,0 +1,268 @@
+# Ruby SDK
+
+Usergrid_iron enables simple, low-level Ruby access to Apigee's App Services (aka Usergrid)
+REST API with minimal dependencies.
+
+## Installation
+
+### Project
+Add the gem to your project's Gemfile:
+
+ gem 'usergrid_iron'
+
+Then rebuild your bundle:
+
+ $ bundle
+
+### Stand-alone script
+Just manually install the gem:
+
+ $ gem install usergrid_iron
+
+
+## Usage
+
+### Prerequisite
+You'll want to be at least a little bit familiar with Usergrid / Apigee's App Services before you jump in here - but it easy and it's great! Start here:
+
+ App Services docs: <http://apigee.com/docs/usergrid/>
+ Open source stack: <https://github.com/apigee/usergrid-stack>
+
+Awesome. Let's go!
+
+### Getting started with the Usergrid_iron SDK is simple!
+
+#### Let's start with the basics.
+For this example, we'll assume you've already set up an organization, application, and user -
+just fill in your own values in the code below.
+
+```ruby
+require 'usergrid_iron'
+
+# fill in your values here!
+usergrid_api = 'http://localhost:8080'
+organization = ''
+application = ''
+username = ''
+password = ''
+
+# create the base application resource
+# (this is a RestClient.resource)
+application = Usergrid::Application.new "#{usergrid_api}/#{organization}/#{application}"
+
+# login (note: not required for sandbox)
+application.login username, password
+
+# create and store a new dog on the server
+# (the "response" is an enhanced RestClient.response)
+response = application.create_dog breed: 'Black Mouth Cur', name: 'Old Yeller'
+
+# the response has returned the entity data
+# response.entity wraps it in an easy-to-use object
+dog = response.entity
+
+# it's persistent now, so it has a unique id
+uuid = dog.uuid
+
+# we can retrieve the dog by UUID using Hash syntax and calling get()
+# (all dogs are stored in the "dogs" collection)
+response = application["dogs"][uuid].get
+same_dog = response.entity
+
+# we could also retrieve the dog by name
+# we could also use path ('/') syntax instead of nested Hash
+# and we can even skip get() - entity() will do it for us
+same_dog = application["dogs/#{dog.name}"].entity
+
+# is it our dog? well, he knows his name!
+# (and we can retrieve its values by dot or Hash)
+puts "My dog's name is: #{same_dog.name} and his breed is #{same_dog['breed']}"
+```
+
+Well that was really easy. More comments than code! :)
+
+#### Let's try something slightly more complex.
+Let's say you've registered for an organization, but you don't have an application yet
+(or want to create a new one to work on). No worries, just fill in your organization and
+superuser credentials below, and follow along!
+(Better yet: If you used the Usergrid launcher and let it initialize your database,
+you shouldn't need to do anything!)
+
+```ruby
+ require 'usergrid_iron'
+
+ usergrid_api = 'http://localhost:8080'
+ org_name = 'test-organization'
+ username = 'test'
+ password = 'test'
+ app_name = 'dog_sitter'
+
+ ## first, let's get that setup out of the way... ##
+
+ # get a management context & login the superuser
+ management = Usergrid::Management.new usergrid_api
+ management.login username, password
+
+ # get the organization context & create a new application
+ organization = management.organization org_name
+ new_application = organization.create_application app_name
+
+ # create an user for our application
+ new_application.create_user username: 'username', password: 'password'
+
+ # login to our new application as our new user
+ application = organization.application app_name
+ application.login 'username', 'password'
+
+
+ ## now we can play with the puppies! ##
+
+ # we can start with our dog again
+ application.create_dog breed: 'Black Mouth Cur', name: 'Old Yeller'
+
+ # but this time let's create several more dogs all at once
+ application.create_dogs [
+ { breed: 'Catalan sheepdog', name: 'Einstein' },
+ { breed: 'Cocker Spaniel', name: 'Lady' },
+ { breed: 'Mixed', name: 'Benji' }]
+
+ # retrieve all the dogs (well, the first 'page' anyway) and tell them hi!
+ # note: we're calling collection() instead of entity() because we have several
+ dogs = application['dogs'].collection
+
+ # you can iterate a collection just like an array
+ dogs.each do |dog|
+ puts "Hello, #{dog.name}!"
+ end
+
+ # Let's get Benji ("Benji, come!"), but this time we'll retrieve by query
+ response = dogs.query "select * where name = 'Benji'"
+
+ # we could call "response.collection.first"
+ # but there's a shortcut: entity() will also return the first
+ benji = response.entity
+
+ # modify Benji's attributes & save to the server
+ benji.location = 'home' # use attribute access
+ benji['breed'] = 'American Cocker Spaniel' # or access attributes like a Hash
+ benji.save
+
+ # now query for the dogs that are home (should just be Benji)
+ dogs = application['dogs'].query("select * where location = 'home'").collection
+ if dogs.size == 1 && dogs.first.location == 'home'
+ puts "Benji's home!"
+ end
+
+```
+
+Whew. That's enough for now. But looking for a specific feature? Check out the [rspecs](http://github.com/scottganyo/usergrid_iron/tree/master/spec/usergrid/core),
+there are examples of nearly everything!
+
+
+## Contributing
+
+We welcome your enhancements!
+
+1. Fork it
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Write some broken rspecs.
+4. Fix the rspecs with your new code.
+3. Commit your changes (`git commit -am 'Added some feature'`)
+4. Push your changes to the upstream branch (`git push origin my-new-feature`)
+5. Create new Pull Request
+
+We've got 100% rspec coverage and we're looking to keep it that way!
+In order to run the tests, check out the Usergrid open source project
+(https://github.com/apigee/usergrid-stack), build, and launch it locally.
+
+(Note: If you change your local Usergrid setting from the default, be sure to update
+usergrid_iron/spec/spec_settings.yaml to match.)
+
+
+## Release notes
+
+### 0.9.2
+* New features
+ 1. delete_query (batch delete by query)
+
+### 0.9.1
+* New features
+ 1. may now login using credentials: application.login_credentials() or organization.login_credentials()
+
+### 0.9.0
+* Backend changes
+ 1. login function now uses POST instead of GET
+
+### 0.0.9
+* Backend changes
+ 1. made Resource::response accessor public to support ugc
+
+### 0.0.8
+* Bug fixes
+ 1. better handling of paging
+
+### 0.0.7
+* Bug fixes
+ 1. multiple_entities? should check data['list']
+
+### 0.0.6
+* New features
+ 1. iterators can now optionally cross page boundaries, eg. `collection.follow_cursor.each`
+ 2. added facebook_login(fb_access_token) method to application
+
+### 0.0.5
+* New features
+ 1. added create_* method for application
+* Backend changes
+ 1. eliminated serialization of reserved attributes
+* Incompatible changes
+ 1. deprecated `Application::create_user username, password, ...`, use `create_user username: 'user', password: 'password'`
+
+### 0.0.4
+* New features
+ 1. empty? check for collection
+ 2. update queries (batch update)
+* Backend changes
+ 1. Additional data sanity checks
+ 2. Additional flexibility in concat_urls
+ 3. Cleanup
+
+### 0.0.3
+* New features
+ 1. Support for lists returned when making parameterized queries:
+ <pre>select username, email where\u2026</pre>
+ or replacement queries:
+ <pre>select { user:username, email:email } where\u2026
+* Incompatible changes
+ 1. Application.create_user parameter change from:
+ <pre>create_user(username, name, email, password, invite=false)</pre>
+ to:
+ <pre>create_user(username, password, email=nil, name=nil, invite=false)</pre>
+* Backend changes
+ 1. Replaced json_pure dependency with multi_json
+
+
+## Notes
+
+The following features are not currently implemented on the server:
+
+* delete organization
+* delete application
+* delete user
+
+
+## License
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+the ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby/Rakefile
----------------------------------------------------------------------
diff --git a/sdks/other/ruby/Rakefile b/sdks/other/ruby/Rakefile
new file mode 100644
index 0000000..bc86b58
--- /dev/null
+++ b/sdks/other/ruby/Rakefile
@@ -0,0 +1,22 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#!/usr/bin/env rake
+require "bundler/gem_tasks"
+
+require 'rspec/core/rake_task'
+RSpec::Core::RakeTask.new(:spec)
+
+RSpec::Core::RakeTask.new("spec:coverage")
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby/bin/autospec
----------------------------------------------------------------------
diff --git a/sdks/other/ruby/bin/autospec b/sdks/other/ruby/bin/autospec
new file mode 100755
index 0000000..64dcb9c
--- /dev/null
+++ b/sdks/other/ruby/bin/autospec
@@ -0,0 +1,16 @@
+#!/usr/bin/env ruby
+#
+# This file was generated by Bundler.
+#
+# The application 'autospec' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('rspec-core', 'autospec')
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby/bin/htmldiff
----------------------------------------------------------------------
diff --git a/sdks/other/ruby/bin/htmldiff b/sdks/other/ruby/bin/htmldiff
new file mode 100755
index 0000000..c70e238
--- /dev/null
+++ b/sdks/other/ruby/bin/htmldiff
@@ -0,0 +1,16 @@
+#!/usr/bin/env ruby
+#
+# This file was generated by Bundler.
+#
+# The application 'htmldiff' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('diff-lcs', 'htmldiff')
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby/bin/ldiff
----------------------------------------------------------------------
diff --git a/sdks/other/ruby/bin/ldiff b/sdks/other/ruby/bin/ldiff
new file mode 100755
index 0000000..8e3524a
--- /dev/null
+++ b/sdks/other/ruby/bin/ldiff
@@ -0,0 +1,16 @@
+#!/usr/bin/env ruby
+#
+# This file was generated by Bundler.
+#
+# The application 'ldiff' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('diff-lcs', 'ldiff')
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby/bin/restclient
----------------------------------------------------------------------
diff --git a/sdks/other/ruby/bin/restclient b/sdks/other/ruby/bin/restclient
new file mode 100755
index 0000000..4d7bdcf
--- /dev/null
+++ b/sdks/other/ruby/bin/restclient
@@ -0,0 +1,16 @@
+#!/usr/bin/env ruby
+#
+# This file was generated by Bundler.
+#
+# The application 'restclient' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('rest-client', 'restclient')
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby/bin/rspec
----------------------------------------------------------------------
diff --git a/sdks/other/ruby/bin/rspec b/sdks/other/ruby/bin/rspec
new file mode 100755
index 0000000..0c86b5c
--- /dev/null
+++ b/sdks/other/ruby/bin/rspec
@@ -0,0 +1,16 @@
+#!/usr/bin/env ruby
+#
+# This file was generated by Bundler.
+#
+# The application 'rspec' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('rspec-core', 'rspec')
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ruby/lib/usergrid/core/application.rb
----------------------------------------------------------------------
diff --git a/sdks/other/ruby/lib/usergrid/core/application.rb b/sdks/other/ruby/lib/usergrid/core/application.rb
new file mode 100644
index 0000000..9cc85c4
--- /dev/null
+++ b/sdks/other/ruby/lib/usergrid/core/application.rb
@@ -0,0 +1,84 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module Usergrid
+ class Application < Resource
+
+ def initialize(url, options={})
+ org_name = url.split('/')[-2]
+ api_url = url[0..url.index(org_name)-2]
+ super url, api_url, options
+ end
+
+ # note: collection_name s/b plural, but the server will change it if not
+ def create_entity(collection_name, entity_data)
+ self[collection_name].post entity_data
+ end
+ alias_method :create_entities, :create_entity
+
+ # allow create_something(hash_or_array) method
+ def method_missing(method, *args, &block)
+ method_s = method.to_s
+ if method_s.start_with? 'create_'
+ entity = method_s.split('_')[1]
+ return _create_user *args if entity == 'user' && args[0].is_a?(String) # backwards compatibility
+ create_entity entity, *args
+ elsif method_s.end_with? 's' # shortcut for retrieving collections
+ self[method].query(*args)
+ else
+ super method, args, block
+ end
+ end
+
+ def counter_names
+ self['counters'].get.data.data
+ end
+
+ # other_params: 'start_time' (ms), 'end_time' (ms), 'resolution' (minutes)
+ def counter(name, other_params={})
+ options = other_params.merge({counter: name})
+ self['counters'].get({params: options})
+ end
+
+ # login with Facebook token. matching user will be created in usergrid as needed.
+ # usergrid auth token automatically set in auth header for future requests
+ def facebook_login(access_token)
+ params = { fb_access_token: access_token }
+ response = self['auth/facebook'].get({ params: params })
+ self.auth_token = response.data['access_token']
+ user_uuid = response.data['user']['uuid']
+ @current_user = self["/users/#{user_uuid}"].get.entity
+ response
+ end
+
+ def login_credentials(client_id, client_secret)
+ response = self['token'].post grant_type: 'client_credentials', client_id: client_id, client_secret: client_secret
+ self.auth_token = response.data['access_token']
+ end
+
+ private
+
+ def _create_user(username, password, email=nil, name=nil, invite=false)
+ LOG.warn "create_user(username, password, ...) is deprecated"
+ user_hash = { username: username,
+ password: password,
+ email: email,
+ name: name,
+ invite: invite }
+ create_entity 'users', user_hash
+ end
+
+ end
+end