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", "[email protected]"]) + # # SELECT * WHERE name = 'Joe' AND email = '[email protected]'; + # + # 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: "[email protected]" }]) + # # SELECT * WHERE name = 'Joe' AND email = '[email protected]'; + # + # 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", "[email protected]"]) + # # SELECT * WHERE name = 'Joe' AND email = '[email protected]'; + # + # 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: "[email protected]" }) + # # SELECT * WHERE name = 'Joe' AND email = '[email protected]'; + # + # 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: "[email protected]" }) + # # SELECT * WHERE name = 'Joe' AND email = '[email protected]' + # + # 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 = ["[email protected]"] + 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â¦</pre> + or replacement queries: + <pre>select { user:username, email:email } where⦠+* 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
