GithubHelp home page GithubHelp logo

parse-ruby-client's Introduction

!! NOTE !!

The master branch has a lot of changes that the current release doesn't have.

The version on RubyGems (0.3.0) has its corresponding tag, so use those docs instead.

Alternatively, you can set up your Gemfile as such:

gem 'parse-ruby-client', git: 'https://github.com/adelevie/parse-ruby-client.git'

and it'll use the current master branch in your app.

Deprecated Functions

In Feb 2017 we merged breakging changes in preparation for a v1.0 release. After a year of deprecation notices, we felt it was reasonable to do now. Unfortunately, not being current on rubygems this was hard to do safely and we apologize for any headache this may cause.

If you want to freeze your build and continue using the (now unsupported) Parse.init, change your gemfile to the following line:

gem 'parse-ruby-client', git: 'https://github.com/adelevie/parse-ruby-client.git', ref: '98596a04dfc30295c1d078c58c31b4cea299e8ca'

Thanks for sticking with us!

Summary

parse-ruby-client lets you interact with Parse using Ruby. There are many uses. For example:

  • a web application can show data from Parse on a website.
  • you can upload large amounts of data that will later be consumed in a mobile app.
  • you can download recent data to run your own custom analytics.
  • you can export all of your data if you no longer want to use Parse.

Quick Reference

Installation

gem install parse-ruby-client or add gem "parse-ruby-client" to your Gemfile.

We support Ruby 2.1 and newer and JRuby 9000. Older versions are not supported, though the library might still work nonetheless.

Client initialization

require 'parse-ruby-client'

Parse.create :application_id      => "<your_app_id>", # required
             :host                => 'http://localhost:1337', # required
             :path                => '/parse', # optional, defaults to '/parse'
             :master_key          => "<your_master_key>", # optional, defaults to nil
             :api_key             => "<your_api_key>", # optional, defaults to nil
             :quiet               => true | false,  # optional, defaults to false
             :get_method_override => true | false, # optional, defaults to true

Gem Version

Travis

Coverage Status

Code Climate

Table of Contents generated with DocToc

Parse App Config Parameters

Application config parameters are read-only and must be set via the Parse web application. However, you can access the values with:

client.application_config # => {"welcomeMessage" => "Welcome to The Internet!", "winningNumber" => 42}

Objects

The design philosophy behind parse-ruby-client is to stay out of the way as much as possible. Parse Objects, at the most basic level, act like Ruby Hash objects with Parse-specific methods tacked on.

Creating Objects

game_score = client.object("GameScore")
game_score["score"] = 1337
game_score["playerName"] = "Sean Plott"
game_score["cheatMode"] = false
result = game_score.save
puts result

This will return:

{"score"=>1337,
 "playerName"=>"Sean Plott",
 "cheatMode"=>false,
 "createdAt"=>"2013-01-19T21:01:33.562Z",
 "objectId"=>"GeqPWJdNqp"}

Retrieving Objects

The easiest way to retrieve Objects is with client.query:

game_score_query = client.query("GameScore")
game_score_query.eq("objectId", "GeqPWJdNqp")
game_score = game_score_query.get
puts game_score

This will return:

[{"score"=>1337,
  "playerName"=>"Sean Plott",
  "createdAt"=>"2013-01-19T21:01:33.562Z",
  "updatedAt"=>"2013-01-19T21:01:33.562Z",
  "objectId"=>"GeqPWJdNqp"}]

Notice that this is an Array of results. For more information on queries, see Queries.

When retrieving objects that have pointers to children, you can fetch child objects by setting the include attribute. For instance, to fetch the object pointed to by the "game" key:

game_score_query = client.query("GameScore")
game_score_query.eq("objectId", "GeqPWJdNqp")
game_score_query.include = "game"
game_score = game_score_query.get

You can include multiple children pointers by separating key names by commas:

game_score_query.include = "game,genre"

Updating Objects

To change the data on an object that already exists, just call Parse::Object#save on it. Any keys you don't specify will remain unchanged, so you can update just a subset of the object's data. For example, if we wanted to change the score field of our object:

game_score = client.query("GameScore").eq("objectId", "GeqPWJdNqp").get.first
game_score["score"] = 73453
result = game_score.save
puts result

This will return:

{"score"=>73453,
 "playerName"=>"Sean Plott",
 "createdAt"=>"2013-01-19T21:01:33.562Z",
 "updatedAt"=>"2013-01-19T21:16:34.395Z",
 "objectId"=>"GeqPWJdNqp"}

Counters

To help with storing counter-type data, Parse provides the ability to atomically increment (or decrement) any number field. So, we can increment the score field like so:

game_score = client.query("GameScore").eq("objectId", "GeqPWJdNqp").get.first
game_score["score"] = Parse::Increment.new(1)
game_score.save

You can also use a negative amount to decrement.

Arrays

To help with storing array data, there are three operations that can be used to atomically change an array field:

  1. Parse::Object#array_add(field, value) appends the given array of objects to the end of an array field.
  2. Parse::Object#array_add_unique(field, value) adds only the given objects which aren't already contained in an array field to that field. The position of the insert is not guaranteed.
  3. Parse::Object#array_remove(field, value) removes all instances of each given object from an array field.

Each method takes an array of objects to add or remove in the "objects" key. For example, we can add items to the set-like "skills" field like so:

game_score = client.query("GameScore").eq("objectId", "5iEEIxM4MW").get.first
game_score.array_add_unique("skills", ["flying", "kungfu"])
game_score.save
puts game_score["skills"]

This will return:

[["flying", "kungfu"]]

Relations

In order to update Relation types, Parse provides special operators to atomically add and remove objects to a relation. So, we can add an object to a relation like so:

game_score = client.query("GameScore").eq("objectId", "5iEEIxM4MW").get.first
player = client.query("Player").eq("objectId", "GLtvtEaGKa").get.first
game_score.array_add_relation("opponents", player.pointer)
game_score.save
game_score["opponents"] #=> #<Parse::ArrayOp:0x007fbe98931508 @operation="AddRelation", @objects=[Player:GLtvtEaGKa]>
game_score["opponents"].objects.first #=> Player:GLtvtEaGKa

To remove an object from a relation, you can do:

game_score.array_remove_relation("opponents", player.pointer)

Deleting Objects

To delete an object from the Parse Cloud, call Parse::Object#parse_delete. For example:

game_score = client.query("GameScore").eq("objectId", "5iEEIxM4MW").get.first
game_score.parse_delete
client.query("GameScore").eq("objectId", "5iEEIxM4MW").get.length #=> 0

You can delete a single field from an object by using the Parse::Object#delete_field operation:

# TODO: This method is not yet implemented.

Batch Operations

To reduce the amount of time spent on network round trips, you can create, update, or delete several objects in one call, using the batch endpoint.

parse-ruby-client provides a "manual" way to construct Batch Operations, as well as some convenience methods. The commands are run in the order they are given. For example, to create a couple of GameScore objects using the "manual" style, use Parse::Batch#add_request. #add_request takes a Hash with "method", "path", and "body" keys that specify the HTTP command that would normally be used for that command.

batch = client.batch
batch.add_request({
  "method" => "POST",
  "path"   => "/1/classes/GameScore"
  "body"   => {
    "score"      => 1337,
    "playerName" => "Sean Plott"
  }
})
batch.add_request({
  "method" => "POST",
  "path"   => "/1/classes/GameScore"
  "body"   => {
    "score"      => 1338,
    "playerName" => "ZeroCool"
  }
})
batch.run!

Because manually constructing "path" values is repetitive, you can use Parse::Batch#create_object, Parse::Batch#update_object, and Parse::Batch#delete_object. Each of these methods takes an instance of Parse::Object as the only argument. Then you just call Parse::Batch#run!. For example:

batch = client.batch
# making a few GameScore objects and adding them to the batch operation.
[1, 2, 3, 4, 5].each do |i|
  gs = Parse::Object.new("GameScore")
  gs["score"] = "#{i}"
  batch.create_object(gs)
end
batch.run!

The response from batch will be an Array with the same number of elements as the input list. Each item in the Array will be a Hash with either the "success" or "error" field set. The value of success will be the normal response to the equivalent REST command:

{
  "success" => {
    "createdAt" => "2012-06-15T16:59:11.276Z",
    "objectId"  => "YAfSAWwXbL"
  }
}

The value of "error" will be a Hash with a numeric code and error string:

{
  "error" => {
    "code"  => 101,
    "error" => "object not found for delete"
  }
}

Data Types

So far we have only used values that can be encoded with standard JSON. The Parse mobile client libraries also support dates, binary data, and relational data. In parse-ruby-client, these values are encoded as JSON hashes with the __type field set to indicate their type, so you can read or write these fields if you use the correct encoding. See https://github.com/adelevie/parse-ruby-client/blob/master/lib/parse/datatypes.rb for implementation details of several common data types.

Dates

Use Parse::Date::new to create a date object:

date_time = DateTime.now
parse_date = Parse::Date.new(date_time)

Dates are useful in combination with the built-in createdAt and updatedAt fields. For example, to retrieve objects created since a particular time, just encode a Date in a comparison query:

game_score = client.query("GameScore").tap do |q|
  q.greater_than("createdAt", Parse::Date.new(DateTime.now)) # query options explained in more detail later in this document
end.get.first

Parse::Date::new can take a DateTime, iso Hash, or a String that can be parsed by DateTime#parse as the sole argument.

The Parse::Date API is not set in stone and will likely change following the suggestions discussed here: #35. The current methods probably will not go away, but some newer, easier methods will be added.

Bytes

Parse::Bytes contains an attribute, base64, which contains a base64 encoding of binary data. The specific base64 encoding is the one used by MIME, and does not contain whitespace.

data = "TG9va3MgbGlrZSB5b3UgZm91bmQgYW4gZWFzdGVyIEVnZy4gTWF5YmUgaXQn\ncyB0aW1lIHlvdSB0b29rIGEgTWluZWNyYWZ0IGJyZWFrPw==\n" # base64 encoded data
bytes = Parse::Bytes.new(data)

Pointers

The Pointer type is used when mobile code sets a PFObject (iOS SDK) or ParseObject (Android SDK) as the value of another object. It contains the className and objectId of the referred-to value.

pointer = Parse::Pointer.new({"className" => "gameScore", "objectId" => "GeqPWJdNqp"})

Pointers to user objects have a className of _User. Prefixing with an underscore is forbidden for developer-defined classes and signifies the class is a special built-in.

If you already have a Parse::Object, you can get its Pointer very easily:

game_score.pointer

Relation

The Relation type is used for many-to-many relations when the mobile uses PFRelation (iOS SDK) or ParseRelation (Android SDK) as a value. It has a className that is the class name of the target objects.

Note: The REST API embeds the configuration parameters in a key called 'params' which is omitted for you by the client.

# TODO: There is no Ruby object representation of this type, yet.

Future data types and namespacing

Though this is something parse-ruby-client will take care for you, it's worth noting:

When more data types are added, they will also be represented as hashes with a __type field set, so you may not use this field yourself on JSON objects.

Queries

Queries are created like so:

query = client.query("GameScore")

Basic Queries

You can retrieve multiple objects at once by calling #get:

query.get

The return value is an Array of Parse::Object instances:

[{"score"=>100,
  "player"=>player:qPHDUbBbjj,
  "createdAt"=>"2012-10-10T00:16:10.846Z",
  "updatedAt"=>"2012-10-10T00:16:10.846Z",
  "objectId"=>"6ff54A5OCY"},
 {"score"=>1337,
  "playerName"=>"Sean Plott",
  "createdAt"=>"2013-01-05T22:51:56.033Z",
  "updatedAt"=>"2013-01-05T22:51:56.033Z",
  "objectId"=>"MpPBAHsqNg"},
 {"score"=>1337,
  "playerName"=>"Sean Plott",
  "createdAt"=>"2013-01-05T22:53:22.609Z",
  "updatedAt"=>"2013-01-05T22:53:22.609Z",
  "objectId"=>"T1dj8cWwYJ"}]]

Query Contraints

There are several ways to put constraints on the objects found, using various methods of Parse::Query. The most basic is Parse::Query#eq:

query = client.query("GameScore").eq("playerName", "Sean Plott")

Other constraint methods include:

`#less_than(field, value)` Less Than
`#less_eq(field, value)` Less Than or Equal To
`#greater_than(field, value)` Greater Than
`#greater_eq(field, value)` Greater Than Or Equal To
`#not_eq(field, value)` Not Equal To
`#value_in(field, values)` Contained In
`#value_not_in(field, values)` Not Contained in
`#exists(field, value=true)` A value is set for the key
`#contains_all(field, values)` Contains all values in the array
`Parse::Query#select` TODO: `$select` not yet implemented. This matches a value for a key in the result of a different query

For example, to retrieve scores between 1000 and 3000, including the endpoints, we could issue:

scores = client.query("GameScore").tap do |q|
  q.greater_eq("score", 1000)
  q.less_eq("score", 3000)
end.get

To retrieve scores equal to an odd number below 10, we could issue:

scores = client.query("GameScore").tap do |q|
  q.value_in("score", [1,3,5,7,9])
end.get

To retrieve scores not by a given list of players we could issue:

scores = client.query("GameScore").tap do |q|
  q.value_not_in("playerName", ["Jonathan Walsh","Dario Wunsch","Shawn Simon"])
end.get

To retrieve documents with the score set, we could issue:

scores = client.query("GameScore").tap do |q|
  q.exists("score") # defaults to `true`
end.get

To retrieve documents without the score set, we could issue:

scores = client.query("GameScore").tap do |q|
  q.exists("score", false)
end.get

If you have a class containing sports teams and you store a user's hometown in the user class, you can issue one query to find the list of users whose hometown teams have winning records. The query would look like:

users = client.query("_User").tap do |users_query|
  users_query.eq("hometown", {
    "$select" => {
      "query" => {
        "className" => "Team",
        "where" => {
          "winPct" => {"$gt" => 0.5}
        }
      },
    "key" => "city"
    }
  })
end.get

Currently, there is no convenience method provided for $select queries. However, they are still possible. This is a good example of the flexibility of parse-ruby-client. You usually do not need to wait for a feature to be added in order to user it. If you have a good idea on what a convencience method for this should look like, please file an issue, or even better, submit a pull request.

You can use the Parse::Query#order_by method to specify a field to sort by. By default, everything is ordered ascending. Thus, to retrieve scores in ascending order:

scores = client.query("GameScore").tap do |q|
  q.order_by = "score"
end.get

And to retrieve scores in descending order:

scores = client.query("GameScore").tap do |q|
  q.order_by = "score"
  q.order = :descending
end.get

You can sort by multiple fields by passing order a comma-separated list. Currently, there is no convenience method to accomplish this. However, you can still manually construct an order string. To retrieve documents that are ordered by scores in ascending order and the names in descending order:

scores = client.query("GameScore").tap do |q|
  q.order_by = "score,-name"
end.get

You can use the limit and skip parameters for pagination. limit defaults to 100, but anything from 1 to 1000 is a valid limit. Thus, to retrieve 200 objects after skipping the first 400:

scores = client.query("GameScore").tap do |q|
  q.limit = 200
  q.skip = 400
end.get

You can use keys to only get specified fields back. objectId, createdAt, and updatedAt are always returned, and other fields are supplied as a comma separated string.

scores = Parse::Query.new("GameScore").tap do |q|
  q.keys = "score,name"
end.get

All of these parameters can be used in combination with each other.

Queries on Array Values

For keys with an array type, you can find objects where the key's array value contains 2 by:

randos = client.query("RandomObject").eq("arrayKey", 2).get

You can also query that the array contains multiple objects by using contains all, for example you can return objects that have the array values 2 AND 3 by:

randos = client.query("RandomObject").eq("arrayKey", [2, 3]).get

Relational Queries

There are several ways to issue queries for relational data. For example, if each Comment has a Post object in its post field, you can fetch comments for a particular Post:

comments = client.query("Comment").tap do |q|
  q.eq("post", Parse::Pointer.new({
    "className" => "Post",
    "objectId"  => "8TOXdXf3tz"
  }))
end.get

If you want to retrieve objects where a field contains an object that matches another query, you can use the Parse::Query#in_query(field, query=nil) method. Note that the default limit of 100 and maximum limit of 1000 apply to the inner query as well, so with large data sets you may need to construct queries carefully to get the desired behavior. For example, imagine you have Post class and a Comment class, where each Comment has a relation to its parent Post. You can find comments on posts with images by doing:

comments = client.query("Comment").tap do |comments_query|
  comments_query.in_query("post", client.query("Post").tap do |posts_query|
    posts_query.exists("image")
  end)
end.get

Note: You must pass an instance of Parse::Query as the second argument for Parse::Query#query_in. You cannot manually construct queries for this.

TODO: Implement this:

If you want to retrieve objects where a field contains an object that does not match another query, you can use the $notInQuery operator. Imagine you have Post class and a Comment class, where each Comment has a relation to its parent Post. You can find comments on posts without images by doing:

If you want to retrieve objects that are members of Relation field of a parent object, you can use the Parse::Query#related_to(field, value) method. Imagine you have a Post class and User class, where each Post can be liked by many users. If the Users that liked a Post was stored in a Relation on the post under the key likes, you, can the find the users that liked a particular post by:

users = client.query("_User").tap do |q|
  q.related_to("likes", Parse::Pointer.new({
    "className" => "Post",
    "objectId" => "8TOXdXf3tz"
  }))
end.get

In some situations, you want to return multiple types of related objects in one query. You can do this by passing the field to include in the include parameter. For example, let's say you are retrieving the last ten comments, and you want to retrieve their related posts at the same time:

comments = client.query("Comment").tap do |q|
  q.order_by = "createdAt"
  q.order    = :descending
  q.limit    = 10
  q.include  = "post"
end.get

Instead of being represented as a Pointer, the post field is now expanded into the whole object. __type is set to Object and className is provided as well. For example, a Pointer to a Post could be represented as:

{
  "__type" => "Pointer",
  "className" => "Post",
  "objectId" => "8TOXdXf3tz"
}

When the query is issued with an include parameter for the key holding this pointer, the pointer will be expanded to:

{
  "__type" => "Object",
  "className" => "Post",
  "objectId" => "8TOXdXf3tz",
  "createdAt" => "2011-12-06T20:59:34.428Z",
  "updatedAt" => "2011-12-06T20:59:34.428Z",
  "otherFields" => "willAlsoBeIncluded"
}

You can also do multi level includes using dot notation. If you wanted to include the post for a comment and the post's author as well you can do:

comments = client.query("Comment").tap do |q|
  q.order_by = "createdAt"
  q.order    = :descending
  q.limit    = 10
  q.include  = "post.author"
end.get

You can issue a query with multiple fields included by passing a comma-separated list of keys as the include parameter:

comments = client.query("Comment").tap do |q|
  q.include("post,author")
comments = Parse::Query.new("Comment").tap do |q|
  q.include = "post,author"
end.get

Counting Objects

If you are limiting your query, or if there are a very large number of results, and you want to know how many total results there are without returning them all, you can use the count parameter. For example, if you only care about the number of games played by a particular player:

count = client.query("GameScore").tap do |q|
  q.eq("playerName", "Jonathan Walsh")
  q.limit = 0
  q.count
end.get

With a nonzero limit, that request would return results as well as the count.

Compound Queries

If you want to find objects that match one of several queries, you can use Parse::Query#or method, with an Array as its value. For instance, if you want to find players with either have a lot of wins or a few wins, you can do:

players = client.query("Player").tap do |q|
  q.greater_than("wins", 150)
  q.or(client.query("Player").tap do |or_query|
    or_query.less_than("wins, 5")
  end)
end.get

Users

Many apps have a unified login that works across the mobile app and other systems. Accessing user accounts through parse-ruby-client lets you build this functionality on top of Parse.

In general, users have the same features as other objects, such as the flexible schema. The differences are that user objects must have a username and password, the password is automatically encrypted and stored securely, and Parse enforces the uniqueness of the username and email fields.

Signing Up

Signing up a new user differs from creating a generic object in that the username and password fields are required. The password field is handled differently than the others; it is encrypted when stored in the Parse Cloud and never returned to any client request.

You can ask Parse to verify user email addresses in your application settings page. With this setting enabled, all new user registrations with an email field will generate an email confirmation at that address. You can check whether the user has verified their email with the emailVerified field.

To sign up a new user, create a new Parse::User object and then call #save on it:

user = client.user({
  :username => "cooldude6",
  :password => "p_n7!-e8",
  :phone => "415-392-0202"
})
user.save

The response body is a Parse::User object containing the objectId, the createdAt timestamp of the newly-created object, and the sessionToken which can be used to authenticate subsequent requests as this user:

{"username"=>"cooldude6",
 "phone"=>"415-392-0202",
 "createdAt"=>"2013-01-31T15:22:40.339Z",
 "objectId"=>"2bMfWZQ9Ob",
 "sessionToken"=>"zrGuvs3psdndaqswhf0smupsodflkqbFdwRs"}

Logging In

After you allow users to sign up, you need to let them log in to their account with a username and password in the future. To do this, call Parse::User#authenticate(username, password):

user = Parse::User.authenticate("cooldude6", "p_n7!-e8", client)

The response body is a Parse::User object containing all the user-provided fields except password. It also contains the createdAt, updatedAt, objectId, and sessionToken fields:

{"username"=>"cooldude6",
 "phone"=>"415-392-0202",
 "createdAt"=>"2013-01-31T15:22:40.339Z",
 "updatedAt"=>"2013-01-31T15:22:40.339Z",
 "objectId"=>"2bMfWZQ9Ob",
 "sessionToken"=>"uvs3aspasdnlksdasqu178qaq0smupso"}

Verifying Emails

Enabling email verification in an application's settings allows the application to reserve part of its experience for users with confirmed email addresses. Email verification adds the emailVerified field to the User object. When a User's email is set or modified, emailVerified is set to false. Parse then emails the user a link which will set emailVerified to true.

There are three emailVerified states to consider:

  1. true - the user confirmed his or her email address by clicking on the link Parse emailed them. Users can never have a true value when the user account is first created.

  2. false - at the time the User object was last refreshed, the user had not confirmed his or her email address. If emailVerified is false, consider refreshing the User object.

  3. missing - the User was created when email verification was off or the User does not have an email.

Requesting A Password Reset

You can initiate password resets for users who have emails associated with their account. To do this, use Parse::User::reset_password:

resp = Parse::User.reset_password("[email protected]", client)
puts resp #=> {}

If successful, the response body is an empty Hash object.

Retrieving Users

You can also retrieve the contents of a user object by using Parse::Query. For example, to retrieve the user created above:

user = client.query("_User").eq("objectId", "2bMfWZQ9Ob").get.first

The response body is a Parse::User object containing all the user-provided fields except password. It also contains the createdAt, updatedAt, and objectId fields:

{"username"=>"cooldude6",
 "phone"=>"415-392-0202",
 "createdAt"=>"2013-01-31T15:22:40.339Z",
 "updatedAt"=>"2013-01-31T15:22:40.339Z",
 "objectId"=>"2bMfWZQ9Ob"}

Updating Users

TODO: Implement this!

In normal usage, nobody except the user is allowed to modify their own data. To authenticate themselves, the user must add a X-Parse-Session-Token header to the request with the session token provided by the signup or login method.

To change the data on a user that already exists, send a PUT request to the user URL. Any keys you don't specify will remain unchanged, so you can update just a subset of the user's data. username and password may be changed, but the new username must not already be in use.

For example, if we wanted to change the phone number for cooldude6:

user = client.query("_User").eq("objectId", "2bMfWZQ9Ob").get.first
user["phone"] = "415-369-6201"
user.save

Currently returns the following error:

Parse::ParseProtocolError: 206: Parse::UserCannotBeAlteredWithoutSessionError

Querying

You can retrieve multiple users at once by using Parse::Query:

users = client.query("_User").get

The return value is an Array of Parse::User objects:

[{"username"=>"fake_person",
  "createdAt"=>"2012-04-20T20:07:32.295Z",
  "updatedAt"=>"2012-04-20T20:07:32.295Z",
  "objectId"=>"AAVwfClOx9"},
 {"username"=>"fake_person222",
  "createdAt"=>"2012-04-20T20:07:32.946Z",
  "updatedAt"=>"2012-04-20T20:07:32.946Z",
  "objectId"=>"0W1Gj1CXqU"}]

All of the options for queries that work for regular objects also work for user objects, so check the section on Querying Objects for more details.

Deleting Users

TODO: Implement this!

Proposed api:

To delete a user from the Parse Cloud, call #parse_delete on it:

user.parse_delete

Linking Users

TODO: Implement this! See https://parseplatform.github.io/docs/rest/guide/#linking-users

Parse allows you to link your users with services like Twitter and Facebook, enabling your users to sign up or log into your application using their existing identities. This is accomplished through the sign-up and update REST endpoints by providing authentication data for the service you wish to link to a user in the authData field. Once your user is associated with a service, the authData for the service will be stored with the user and is retrievable by logging in.

authData is a JSON object with keys for each linked service containing the data below. In each case, you are responsible for completing the authentication flow (e.g. OAuth 1.0a) to obtain the information the the service requires for linking.

Facebook authData contents:

{
  "facebook" => {
    "id" => "user's Facebook id number as a string",
    "access_token" => "an authorized Facebook access token for the user",
    "expiration_date" => "token expiration date of the format: yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
  }
}

Twitter authData contents:

{
  "twitter" => {
    "id" => "user's Twitter id number as a string",
    "screen_name" => "user's Twitter screen name",
    "consumer_key" => "your application's consumer key",
    "consumer_secret" => "your application's consumer secret",
    "auth_token" => "an authorized Twitter token for the user with your application",
    "auth_token_secret" => "the secret associated with the auth_token"
  }
}

Anonymous user authData contents:

{
  "anonymous" => {
    "id" => "random UUID with lowercase hexadecimal digits"
  }
}

Signing Up and Logging In

Todo: Implement this!

Signing a user up with a linked service and logging them in with that service uses the same POST request, in which the authData for the user is specified. For example, to sign up or log in with a user's Twitter account:

# should look something like this:
twitter_user = Parse::User::Twitter.new({
  "id" => "12345678",
  "screen_name" => "ParseIt",
  "consumer_key" => "SaMpLeId3X7eLjjLgWEw",
  "consumer_secret" => "SaMpLew55QbMR0vTdtOACfPXa5UdO2THX1JrxZ9s3c",
  "auth_token" => "12345678-SaMpLeTuo3m2avZxh5cjJmIrAfx4ZYyamdofM7IjU",
  "auth_token_secret" => "SaMpLeEb13SpRzQ4DAIzutEkCE2LBIm2ZQDsP3WUU"
})
twitter_user.save

Parse then verifies that the provided authData is valid and checks to see if a user is already associated with this data. If so, it returns a status code of 200 OK and the details (including a sessionToken for the user).

With a response body like:

{
  "username" => "Parse",
  "createdAt" => "2012-02-28T23:49:36.353Z",
  "updatedAt" => "2012-02-28T23:49:36.353Z",
  "objectId" => "uMz0YZeAqc",
  "sessionToken" => "samplei3l83eerhnln0ecxgy5",
  "authData" => {
    "twitter" => {
      "id" => "12345678",
      "screen_name" => "ParseIt",
      "consumer_key" => "SaMpLeId3X7eLjjLgWEw",
      "consumer_secret" => "SaMpLew55QbMR0vTdtOACfPXa5UdO2THX1JrxZ9s3c",
      "auth_token" => "12345678-SaMpLeTuo3m2avZxh5cjJmIrAfx4ZYyamdofM7IjU",
      "auth_token_secret" => "SaMpLeEb13SpRzQ4DAIzutEkCE2LBIm2ZQDsP3WUU"
    }
  }
}

If the user has never been linked with this account, you will instead receive a status code of 201 Created, indicating that a new user was created.

The body of the response will contain the objectId, createdAt, sessionToken, and an automatically-generated unique username. For example:

{
  "username" => "iwz8sna7sug28v4eyu7t89fij",
  "createdAt" => "2012-02-28T23:49:36.353Z",
  "objectId" => "uMz0YZeAqc",
  "sessionToken" => "samplei3l83eerhnln0ecxgy5"
}

Linking

TODO: Implement this!

Linking an existing user with a service like Facebook or Twitter uses a PUT request to associate authData with the user. For example, linking a user with a Facebook account would use a request like this:

# should look something like this:

user = client.query("_User").eq("objectId", "2bMfWZQ9Ob").get.first
user.link_to_facebook!({
  "id" => "123456789",
  "access_token" => "SaMpLeAAibS7Q55FSzcERWIEmzn6rosftAr7pmDME10008bWgyZAmv7mziwfacNOhWkgxDaBf8a2a2FCc9Hbk9wAsqLYZBLR995wxBvSGNoTrEaL",
  "expiration_date" => "2012-02-28T23:49:36.353Z"
})

# or

user.link_to_twitter!({...})

After linking your user to a service, you can authenticate them using matching authData.

Unlinking

TODO: Implement this!

Unlinking an existing user with a service also uses a PUT request to clear authData from the user by setting the authData for the service to null. For example, unlinking a user with a Facebook account would use a request like this:

# should look something like this:

user = client.query("_User").eq("objectId", "2bMfWZQ9Ob").get.first
user.unlink_from_facebook!

Security

TODO: Implement this!

When you access Parse via the REST API key, access can be restricted by ACL just like in the iOS and Android SDKs. You can still read and modify acls via the REST API, just by accessing the "ACL" key of an object.

The ACL is formatted as a JSON object where the keys are either object ids or the special key "*" to indicate public access permissions. The values of the ACL are "permission objects", JSON objects whose keys are the permission name and the value is always true.

For example, if you want the user with id "3KmCvT7Zsb" to have read and write access to an object, plus the object should be publicly readable, that corresponds to an ACL of:

{
  "3KmCvT7Zsb": {
    "read": true,
    "write": true
  },
  "*": {
    "read": true
  }
}

If you want to access your data ignoring all ACLs, you can use the master key provided on the Dashboard. Instead of the X-Parse-REST-API-Key header, set the X-Parse-Master-Key header. For backward compatibility, you can also do master-level authentication using HTTP Basic Auth, passing the application id as the username and the master key as the password. For security, the master key should not be distributed to end users, but if you are running code in a trusted environment, feel free to use the master key for authentication.

Roles

TODO: Implement this!

See https://parseplatform.github.io/docs/rest/guide/#roles

Files

Uploading Files

To upload a file to Parse, use Parse::File. You must include the "Content-Type" parameter when instantiating. Keep in mind that files are limited to 10 megabytes. Here's a simple example that'll create a file named hello.txt containing a string:

file = client.file({
  :body => "Hello World!",
  :local_filename => "hello.txt",
  :content_type => "text/plain"
})
file.save

The response body is a Hash object containing the name of the file, which is the original file name prefixed with a unique identifier in order to prevent name collisions. This means, you can save files by the same name, and the files will not overwrite one another.

{"url"=>
  "http://something.com/somewhere/98f06e15-d6e6-42a9-a9cd-7d28ec98052c-hello.txt",
 "name"=>"98f06e15-d6e6-42a9-a9cd-7d28ec98052c-hello.txt"}

To upload an image, the syntax is a little bit different. Here's an example that will upload the image parsers.jpg from the current directory:

photo = client.file({
  :body => IO.read("test/parsers.jpg"),
  :local_filename => "parsers.jpg",
  :content_type => "image/jpeg"
})
photo.save

Associating with Objects

After files are uploaded, you can associate them with Parse objects:

photo = client.file({
  :body => IO.read("test/parsers.jpg"),
  :local_filename => "parsers.jpg",
  :content_type => "image/jpeg"
})
photo.save
player_profile = client.object("PlayerProfile").tap do |p|
  p["name"] = "All the Parsers"
  p["picture"] = photo
end.save

Deleting Files

TODO: Implement this!

Push Notifications

Parse allows you to send push notifications to iOS and Android devices.

Notifications by default are set for iOS and Android. You can set certain notifications to only be sent to iOS or Android by setting the type to ios or android.

For config/installation: https://parseplatform.github.io/docs/rest/guide/#push-notification

Using Channels

To send a notification to the "Giants" channel, as given at: https://parseplatform.github.io/docs/rest/guide/#using-channels

data = { :alert => "This is a notification from Parse" }
push = client.push(data, "Giants")
push.type = "ios"
push.save

Without a specific channel, by default it sends to all installations.

Using Advanced Targeting

To send a notification to installations where injuryReports is true, as given at: https://parseplatform.github.io/docs/rest/guide/#sending-pushes-to-queries

data = { :alert => "This is a notification from Parse" }
push = client.push(data)
push.type = "ios"
query = client.query(Parse::Protocol::CLASS_INSTALLATION).eq('injuryReports', true)
push.where = query.where
push.save

Installations

Creating Installations

installation = client.installation.tap do |i|
  i.device_token = 'mobile_app_device_token'
  i.device_type = 'ios'
  i.channels = ['my-channel-name']
end
installation.save

Retrieving Installations

installation = client.installation('objectId').get
# Same as
installation = Parse::Installation.new('objectId', client)
installation.get

Updating installations

installation = client.installation('objectId')
installation.channels = ['', 'my-channel-name']
installation.badge = 5
installation.save

GeoPoints

Parse allows you to associate real-world latitude and longitude coordinates with an object. Adding a GeoPoint data type to a class allows queries to take into account the proximity of an object to a reference point. This allows you to easily do things like find out which user is closest to another user or which places are closest to a user.

GeoPoint

To associate a point with an object you will need to embed a GeoPoint data type into your object. This is done by using a JSON object with __type set to the string GeoPoint and numeric values being set for the latitude and longitude keys. For example, to create an object containing a point under the "location" key with a latitude of 40.0 degrees and -30.0 degrees longitude:

place = client.object("PlaceObject").tap do |p|
  p["location"] = Parse::GeoPoint.new({
    "latitude" => 40.0,
    "longitude" => -30.0
  })
end.save

GeoQueries

TODO: Implement this!

Now that you have a bunch of objects with spatial coordinates, it would be nice to find out which objects are closest to a point. This can be done by using a GeoPoint data type with query on the field using $nearSphere. Getting a list of ten places that are closest to a user may look something like:

# should look something like this:
places = client.query("PlaceObject").tap do |q|
  q.near("location", {
    "latitude" => 30.0,
    "longitude" => -20.0
  })
end.get

See https://parseplatform.github.io/docs/rest/guide/#geo-queries for the rest of the geo query types to be implemented.

Caveats

At the moment there are a couple of things to watch out for:

  1. Each PFObject class may only have one key with a PFGeoPoint object.

  2. Points should not equal or exceed the extreme ends of the ranges. Latitude should not be -90.0 or 90.0. Longitude should not be -180.0 or 180.0. Attempting to use GeoPoint's with latitude and/or longitude outside these ranges will cause an error.

Contributors

This project would not be where it is today without the generous help provided by its many contributors:

  1. Adam Alpern (created this project) (https://github.com/aalpern) (https://www.gittip.com/on/github/aalpern/)
  2. Eric Jensen (https://github.com/ericcj) (https://www.gittip.com/on/github/ericcj/)
  3. Ben Cherry (https://github.com/bcherry) (https://www.gittip.com/bcherry/)
  4. Tikhon Bernstam (https://github.com/tikhon) (https://www.gittip.com/on/github/tikhon/)
  5. Tony Amoyal (https://github.com/tamoyal) (https://www.gittip.com/on/github/tamoyal/)
  6. Glenn Goodrich (https://github.com/ruprict) (https://www.gittip.com/on/github/ruprict/)
  7. Jeremy Schoenherr (https://github.com/jeremyschoenherr) (https://www.gittip.com/on/github/jeremyschoenherr/)
  8. Dean Perry (https://github.com/deanperry) (https://www.gittip.com/deanperry/)
  9. vircheck (https://www.gittip.com/on/github/vircheck/)
  10. Jacob Eiting (https://github.com/jeiting) (https://www.gittip.com/on/github/jeiting/)
  11. Guy Maliar (https://github.com/gmaliar) (https://www.gittip.com/on/github/gmaliar/)
  12. Ivan Fuller (https://github.com/ifuller) (https://www.gittip.com/on/github/ifuller/)
  13. Leandro S. (https://github.com/lsiqueira) (https://www.gittip.com/on/github/lsiqueira/)
  14. Brian Hammond (https://github.com/fictorial) (https://www.gittip.com/on/github/fictorial/)

parse-ruby-client's People

Contributors

aalpern avatar adelevie avatar bcherry avatar chrisb avatar chytreg avatar ck3g avatar crabbits avatar deanpcmad avatar ericcj avatar fabiomassimo avatar fictorial avatar gmaliar avatar ifuller avatar imownbey avatar interstateone avatar jastkand avatar jeiting avatar jeremyschoenherr avatar kidlab avatar lsiqueira avatar lucacioria avatar olleolleolle avatar rhymes avatar ruprict avatar sheerun avatar tamoyal avatar technohippy avatar tiagopog avatar tikhon avatar xavdid avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

parse-ruby-client's Issues

createdAt and updatedAt are left in Parse::Object hash as Strings

which means they're passed around as strings which will never match conditions if you do something like:

2.0.0-p0 :011 > Parse::Query.new('_User').greater_than('createdAt', Parse::Query.new('_User').get.first['createdAt']).get
2013-05-23 17:16:06 [INFO] Parse query for /1/users {"where"=>"{}"}
2013-05-23 17:16:07 [INFO] Parse query for /1/users {"where"=>"{"createdAt":{"$gt":"2013-05-02T15:25:43.714Z"}}"}

order is not working...

Any news on how you are doing on this issue?

I was trying to use the parse_resource also but doesn't seem to implement the order

provide async api everywhere we provide a synchronous one: Query#get, Object#save, etc.

we could split out a version of this gem as an eventmachine protocol, and might want to do that anyways, but it would be nice to offer an async api without requiring they be using eventmachine. faraday appears to be the best way to do that, so we would need to get #88 merged then implement asynchronous versions of the api like:

query.get_async do |results|

access results Array here like you would from the synchronously returned one

end

these would use a Faraday middleware to call the block you pass as in http://blog.carbonfive.com/2013/03/15/parallel-http-requests-with-faraday-and-typhoeus/ with us passing the block through env via:

@session.get(...) do |request|
request.options["pf_block"] = block
end

and could therefore be nested inside the parallel faraday api as follows:

foos = nil
bars = nil

Parse.client.session.in_parallel do 
  Parse::Query.new("Foo").get_async do |f|
    foos = f
  end

  Parse::Query.new("Bar").get_async do |b|
    bars = b
  end
end

puts foos + bars

it appears that nesting async calls within the callbacks of others would work

separate Query#count method from Query#get just like the official parse sdk's

instead of returning a hash with count and potentially results in it. this would fix the documentation too, which makes it look like it just returns an integer. we could go back to strong validation in Query#count to require the response to be a json hash with "count" and similar for Query#get and "results" then instead of allowing whatever through. there also seems to be unnecessary stubbing in test_count.

User::authenticate doesn't work

For some reason, the password isn't getting sent to Parse when authenticating users. Here's Parse's doc on the method. This one's really baffling.

The relevant files are lib/parse/user.rb, lib/parse/protocol.rb (where some of the constants are defined), lib/parse/object.rb (since Parse::User inherits from Parse::Object), and probably lib/parse/client.rb.

$ rake test
WARNING: 'require 'rake/rdoctask'' is deprecated.  Please use 'require 'rdoc/task' (in RDoc 2.4.2+)' instead.
    at /Users/adelevie/.rvm/gems/ruby-1.9.2-p290/gems/rake-0.9.2.2/lib/rake/rdoctask.rb
/Users/adelevie/.rvm/rubies/ruby-1.9.2-p290/bin/ruby -I"lib:lib:test" -I"/Users/adelevie/.rvm/gems/ruby-1.9.2-p290/gems/rake-0.9.2.2/lib" "/Users/adelevie/.rvm/gems/ruby-1.9.2-p290/gems/rake-0.9.2.2/lib/rake/rake_test_loader.rb" "test/**/test_*.rb" 
Loaded suite /Users/adelevie/.rvm/gems/ruby-1.9.2-p290/gems/rake-0.9.2.2/lib/rake/rake_test_loader
Started
............E.
Finished in 1.717421 seconds.

  1) Error:
test_login(TestUser):
Parse::ParseError: 201: missing user password
    /Users/adelevie/programming/parse-ruby-client/lib/parse/client.rb:47:in `request'
    /Users/adelevie/programming/parse-ruby-client/lib/parse/user.rb:14:in `authenticate'
    /Users/adelevie/programming/parse-ruby-client/test/test_user.rb:33:in `test_login'

14 tests, 29 assertions, 0 failures, 1 errors, 0 skips

Query conditions completely ignored

With parse-ruby-client 0.1.2 and ruby 1.8.7 on Mac OS X 10.7.5, I am seeing a major issue in which query conditions are ignored.

I adapted one of your unit tests to reduce the issue to its core.

require 'rubygems'
require 'json'
require 'parse-ruby-client'

Parse.init

foo = Parse::Object.new "Post"
foo["random"] = rand
foo.save
foo_query = Parse::Query.new("Post").eq("random", foo["random"])
print "1 == #{foo_query.get.size}\n"

bar = Parse::Object.new "Post"
bar["random"] = rand
bar.save
bar_query = Parse::Query.new("Post").eq("random", bar["random"])
print "1 == #{foo_query.get.size}\n"

query = foo_query.or(bar_query)
print "2 == #{foo_query.get.size}\n"

This is printing

1 == 1
1 == 2 <-- error
2 == 2 

The Post objects on Parse (as per the Data Browser):

objectId     random 
RqfRUhQLF4   0.959963811455423
6oF7tJXjvW   0.754638272517693

weird behavior with files

1.9.3p194 :069 > colleague
#=>
{
                   "user" => _User:bKQ4rNeSqp,
                  "email" => "[email protected]",
               "facebook" => "",
    "methodOfLastContact" => "sms",
                   "name" => "Foo Bar",
                 "number" => "1 234 5678",
                  "photo" => #<Parse::File:0x007fa4090c49c8 @url="http://files.parse.com/f75-68d6-4694-ac03-aed9af458d7b/2c2f54cd-792d-453e-833b-63b14f1a7066-file">,
              "createdAt" => "2013-03-10T15:10:02.355Z",
              "updatedAt" => "2013-03-10T15:10:22.673Z",
               "objectId" => "5kGUdPgy9G"
}
1.9.3p194 :070 > colleague.save
Parse::ParseProtocolError: 111: File name must be a string.
  from /Users/mikesilvis/.rvm/gems/ruby-1.9.3-p194/gems/parse-ruby-client-0.1.14/lib/parse/client.rb:100:in `request'
    from /Users/mikesilvis/.rvm/gems/ruby-1.9.3-p194/gems/parse-ruby-client-0.1.14/lib/parse/object.rb:105:in `save'
    from (irb):70
    from /Users/mikesilvis/.rvm/rubies/ruby-1.9.3-p194/bin/irb:16:in `<main>'
1.9.3p194 :071 > Parse::Query.new("Colleague").eq("objectId", "5kGUdPgy9G").get.first.save
Parse::ParseProtocolError: 111: File name must be a string.
    from /Users/mikesilvis/.rvm/gems/ruby-1.9.3-p194/gems/parse-ruby-client-0.1.14/lib/parse/client.rb:100:in `request'
    from /Users/mikesilvis/.rvm/gems/ruby-1.9.3-p194/gems/parse-ruby-client-0.1.14/lib/parse/object.rb:105:in `save'
    from (irb):71
    from /Users/mikesilvis/.rvm/rubies/ruby-1.9.3-p194/bin/irb:16:in `<main>'
1.9.3p194 :072 >

Error "new object used in context requiring pointer" when trying to use DateTime

I'm trying to get the count of objects created after a certain day, and keep getting the error "new object used in context requiring pointer". This is my code:

  item_count = Parse::Query.new("Item").tap do |q|
    q.limit = 0
    q.less_than("createdAt", Parse::Object.new(DateTime.now))
    q.count
  end.get
  @item_count = item_count["count"]

Trying to create the special Parse "Installation" object for push notifications creates a new custom class called "Instllation"

When trying to create a new Installation object for push notifications, instead of getting a new object from the Parse "special type" Installation, a new custom class is being created.

Code:
new_user = Parse::Object.new "Installation"
new_user["deviceType"] = "ios"
new_user["deviceToken"] = ios_token
new_user["channels"] = Array.new
new_user["channels"] << ""
new_user.save

DSL for Parse::Query#in_query

Here's the current usage:

Assume I have the classes Post, Tags, and PostTags (which joins the first two).
def in_query(field, query)
query_hash = {Parse::Protocol::KEY_CLASS_NAME => query.class_name, "where" => query.where}
add_constraint(field, "$inQuery" => query_hash)
self
end

post_tags = Parse::Query.new("PostTag").tap do |q|
  q.in_query("tag", Parse::Query.new("Tag").tap do |tq|
    tq.eq("category", "Person")
  end)
end.get

And here's what I propose:

post_tags = Parse::Query.new("PostTag").tap do |q|
  q.in_query("tag") do |tag_query|
    tag_query.eq("category", "Person")
  end
end.get

Is this something anyone (@ericcj, @jamonholmgren) would want?

rcov problem

Hi,
I try to run test locally on my machine but it says that:
during bundle install for rcov => Ruby 1.9 is not supported. Please switch to simplecov
Checked the gem github page
https://github.com/relevance/rcov#why-what

Really strage thing for me is: Why Travis CI is working correctly?
In .travis.yml is 1.9.2 and 1.9.3 ruby. Anyone could explain it?

Suggestions for 1.0 milestone

Discuss. Feel free to write the problem inline as a comment or just to link to an issue or PR.

Ideally, full parity with all of Parse's REST features would be nice. Wondering how others feel?

In some ways, I'm really happy with the stability of the codebase. Everything is pretty modular and magic-free. That said, some issues like delta-saving are still bugging me.

Offering commit access

If (nearly) anyone reading this would like, I'm offering commit access to this project. I'm not planning on decreasing my time and effort spent here, but wanted to offer others the chance to move the project forward.

@ericcj, I know I've asked you before and you were not interested, but the offer is on the table if you ever change your ming.

As I said before, I still plan on actively maintaining and developing, but this could be nice opportunity for anyone looking to step up their open source experience.

Cannot update object with User as property

Let's say I have "Game" class and one object of that class has a property "firstPlayer" and another "secondPlayer".

This is what ap prints out for this game object's firstPlayer.

"firstPlayer" => {
          "coins" => 400,
    "displayName" => "Run4TheCup",
       "username" => "run4thecup",
      "createdAt" => "2012-12-31T01:27:53.792Z",
      "updatedAt" => "2013-01-19T04:39:17.992Z",
       "objectId" => "rLp66RJ6OH",
         "__type" => "Object",
      "className" => "_User"
},

Doing a save on this game object is failing:

/Users/bhammond/.rvm/gems/ruby-1.9.3-p374/gems/parse-ruby-client-0.1.12/lib/parse/client.rb:65:in request': 111: invalid type for key firstPlayer, expected *_User, but got object (Parse::ParseProtocolError) from /Users/bhammond/.rvm/gems/ruby-1.9.3-p374/gems/parse-ruby-client-0.1.12/lib/parse/object.rb:105:insave'

Multi threading issue.

Hey,

I have been running my code in a single thread without issue. But as soon as I run two threads problems arise.

Parse::ParseProtocolError
: unauthorized
/home/deploy/.rvm/gems/ruby-2.0.0-p195/gems/parse-ruby-client-0.1.15/lib/parse/client.rb:106:in `request'
/home/deploy/.rvm/gems/ruby-2.0.0-p195/gems/parse-ruby-client-0.1.15/lib/parse/object.rb:120:in `save'
/home/deploy/gametime-backend/lib/stubhub_page.rb:24:in `putsSh'
/home/deploy/gametime-backend/lib/stubhub_page/login.rb:17:in `block in process'
/home/deploy/gametime-backend/lib/stubhub_page/login.rb:16:in `each'
/home/deploy/gametime-backend/lib/stubhub_page/login.rb:16:in `process'
/home/deploy/gametime-backend/lib/stubhub.rb:48:in `block (2 levels) in submit'
/home/deploy/gametime-backend/lib/stubhub.rb:42:in `each'
/home/deploy/gametime-backend/lib/stubhub.rb:42:in `block in submit'
/home/deploy/gametime-backend/lib/stubhub.rb:40:in `each'
/home/deploy/gametime-backend/lib/stubhub.rb:40:in `submit'
/home/deploy/gametime-backend/app/purchase_flow.rb:73:in `block (2 levels) in <top (required)>'

Occasionally I get the following error, which seems related:

WARN -- : Retrying Parse Error #<Patron::TimeoutError: easy handled already used in multi handle> on request /1/classes/PurchaseFlow nil "{...}]}}" response nil
...
[2013-07-17 18:40:29] ERROR Patron::Error: easy handled already used in multi handle
/home/bwinter/.rvm/gems/ruby-2.0.0-head/gems/patron-0.4.18/lib/patron/session.rb:223:in `handle_request'

I think it's unrelated but occasionally it segfaults as well (I think this is a selenium issue). The full error is massive so I will supply that upon request:

/home/bwinter/.rvm/gems/ruby-1.9.3-p429/gems/childprocess-0.3.9/lib/childprocess/abstract_process.rb:138: [BUG] Segmentation fault

I am not quite sure what's causing the issue here. I create my parse object then create a new thread that starts using it. If I remove every 'parse.save' in my code I can run multiple threads w/o issue. So I can only assume Parse is somehow causing these issues. Maybe the shared application_id/api_keys...

Parse.init(:application_id => "...", :api_key => "...")
parse = Parse::Object.new("PurchaseFlow", initial_data).save
Thread.new{ Processor.new.submit(parse) }

As you can see from my stack traces I have tried quite a few versions of ruby (1.9.3, 2.0.0-193p,247p,270p), none of them seemed to help.

Gemfile:

    braintree (2.23.0)
      builder (>= 2.0.0)
    builder (3.2.2)
    childprocess (0.3.9)
      ffi (~> 1.0, >= 1.0.11)
    excon (0.16.10)
    ffi (1.9.0)
    iron_core (0.6.2)
      rest (>= 2.2.0)
    iron_mq (4.0.3)
      iron_core (>= 0.5.1)
    json (1.7.7)
    jwt (0.1.8)
      multi_json (>= 1.5)
    kgio (2.8.0)
    mandrill-api (1.0.37)
      excon (~> 0.16.0)
      json (~> 1.7.7)
    mime-types (1.23)
    multi_json (1.7.7)
    net-http-persistent (2.8)
    parse-ruby-client (0.1.15)
      iron_mq
      patron
    patron (0.4.18)
    rack (1.5.2)
    rack-protection (1.5.0)
      rack
    raindrops (0.11.0)
    rest (2.6.3)
      net-http-persistent
      rest-client (>= 0.3.0)
    rest-client (1.6.7)
      mime-types (>= 1.16)
    rmagick (2.13.2)
    rubyzip (0.9.9)
    selenium-webdriver (2.33.0)
      childprocess (>= 0.2.5)
      multi_json (~> 1.0)
      rubyzip
      websocket (~> 1.0.4)
    sinatra (1.4.3)
      rack (~> 1.4)
      rack-protection (~> 1.4)
      tilt (~> 1.3, >= 1.3.4)
    tilt (1.4.1)
    twilio-ruby (3.9.0)
      builder (>= 2.1.2)
      jwt (>= 0.1.2)
      multi_json (>= 1.3.0)
    unicorn (4.6.3)
      kgio (~> 2.6)
      rack
      raindrops (~> 0.7)
    websocket (1.0.7)
    zbar (0.2.2)
      ffi (>= 1.0.0)

If there is any missing information that might help, let me know.

Thanks,
Brendan

Limit attribute on Query is not working

Am I doing something wrong?

Here is the example:

valor_100primeiros = 0

query_hundred_values = Parse::Query.new(akatu_class)
query_hundred_values.limit = 200
hundred_values = query_hundred_values.get
hundred_values.each { |linha| valor_100primeiros += 1}

print valor_100primeiros

The output is:

100

Boolean

I don't think using regular ruby booleans is working. I'm trying to set something like Parse::Object['item'] = false and it doesn't give me an error, but It never saves in the API and doesn't fail with an error. Everything else saves, but it ignores the item with the boolean value

should retry on server-side errors

#<JSON::ParserError: 757: unexpected token at '<html><body><h1>502 Bad Gateway</h1>

#<JSON::ParserError: 757: unexpected token at '<html><body><h1>503 Service Unavailable</h1>
No server is available to handle this request.
</body></html>

#<JSON::ParserError: A JSON text must at least contain two octets!>
gems/ruby-1.9.3-p194/gems/json-1.7.3/lib/json/common.rb:155:in `initialize' -- ge
ms/ruby-1.9.3-p194/gems/json-1.7.3/lib/json/common.rb:155:in `new' -- gems/ruby-1.9.3-p194/gems/json-1
.7.3/lib/json/common.rb:155:in `parse' -- gems/ruby-1.9.3-p194/gems/parse-ruby-client-0.1.2.ericcj.1/l
ib/parse/client.rb:58:in `request' -- gems/ruby-1.9.3-p194/gems/parse-ruby-client-0.1.2.ericcj.1/lib/p
arse/query.rb:82:in `get'

non-object hashes (such as ACL) are made into faulty Parse::Object instances in JSON parsing

this one is pretty simple, here's a test that illustrates the failure:

def test_hashes
  VCR.use_cassette('test_hashes', :record => :new_episodes) do
    foo = Parse::Object.new("Foobar", "foobar" => { "baz" => "123" })
    assert_equal Hash, foo["foobar"].class

    foo.save
    assert_equal Hash, foo["foobar"].class

    foo = Parse.get("Foobar", foo.id)
    assert_equal Hash, foo["foobar"].class
  end
end

The failure is <Hash> expected but was <Parse::Object> (in the third assertion). The resulting Parse::Object doesn't have a class_name nor an objectId. It should just stay a hash.

cant push with quey "where"

This is not working

push = Parse::PushMK.new({"alert" => "I'm sending this push to all my app users!"})
push.where = {"deviceToken" => "mytoken"}
push.save

with error message

Can't set channels for a query-targeted push

this is because @channel is set when "where" is defined. There should be in class Push

      if @where
        body.merge!({ :where => @where })
        body.delete :channel
      end

Invalid type for key location, expected geopoint

I'm trying to use the Gem's GeoType datatype like this:

def save
  if (!valid?)
    return false
  else
    parking = Parse::Object.new("Parking")

  data =
  {
    "longitude" => 40.0,
    "latitude" => -30.0
  }

  point = Parse::GeoPoint.new(data)

  parking["name"]             = name
  parking["address"]          = address
  parking["city"]             = city
  parking["location"]         = point
  parking["contributorName"]  = contributor_name
  parking["contributorEmail"] = contributor_email

  if (parking.save)
    return true
  end
end

end

and it returns me : " Invalid type for key location, expected geopoint"

I had to use:

parking["location"] = {"__type" => "GeoPoint", "latitude" => latitude.to_f, "longitude" => longitude.to_f}

in order to make this work.

Is this normal? What's the purpose of the GeoPoint class in the gem if we have submit an hash? At first I thought it was for fetching data only, but I still access the longitude/latitude using <%= parking["location"]["longitude"] %> ...

Thank you !

Deleting a user fails

I've passed the master key in as an arg to Parse.init.

Here is the query and how I'm trying to delete the user objects.

users = Parse::Query.new("_User").get
users.each do |u|·
puts u.class
u.parse_delete·
end

I'm getting the Parse::UserCannotBeAlteredWithoutSessionError. Is there a different way I should be retrieving users that will make this work?

Relational data seems broken

When i try to run the following example from the ReadMe:

post = Parse::Object.new "Post"
post.save

comment = Parse::Object.new "Comment"
comment.save

post.array_add_relation("comments", comment.pointer)
post.save

It just gives me the following error:

undefined method `array_add_relation' for #<Parse::Object:0x007f80d441d820>

Are the relationships broken or am I doing something wrong?

Saving nested object raises on pointer

I was using the latest released gem and noticed that nested objects weren't created.
Upgraded to the latest master but I still cannot save nested objects as pointers.

With a hash:

inspection_site = { attr: 'test' }

field_inspection = Parse::Object.new('FieldInspection')
field_inspection['inspectionSite'] = inspection_site
field_inspection.save

/Users/kain/.rvm/gems/ruby-2.0.0-p247@im/bundler/gems/parse-ruby-client-0eb452b0882d/lib/parse/client.rb:117:in `request': 111: invalid type for key inspectionSite, expected *FieldInspectionSite, but got object (Parse::ParseProtocolError)

With a Parse object:

inspection_site = Parse::Object.new('FieldInspectionSite')

field_inspection = Parse::Object.new('FieldInspection')
field_inspection['inspectionSite']  = inspection_site
field_inspection.save

/Users/kain/.rvm/gems/ruby-2.0.0-p247@im/bundler/gems/parse-ruby-client-0eb452b0882d/lib/parse/util.rb:56:in `pointerize_value': new object used in context requiring pointer FieldInspectionSite: [snip]

Related commit: 653145a

If I save the FieldInspection object first then the field_inspection.save works.
Am I doing something wrong or is it a bug?

Updating a fetched object fails.

If you fetch an object from the server using a query, update it, then save it, the save fails because the 'updatedAt' key is not being removed from the batch. The fix is to add the 'updateAt' key to the RESERVED_KEYS list in protocol.rb, patch incoming.

Abstracted batch methods

I just want to run through this before I submit a PR.

  • get_object_count(class_name)
    abstracts a count query
  • get_all_objects(class_name)
    hits a max of 11 000
  • delete_all_objects(class_name or array of Parse::Objects)
    hits a max of 11 000 with class_name, but can run more than once to get around that

I've got them in util.rb right now, but that doesn't seem quite perfect. They're batch/query operations but wouldn't be instance methods of those objects. Thoughts?

Any other methods that come to mind?

creating Installations

I see this is TODO

is there a way using Parse::Object to do so ? or another not so straightforward way ?

thanks.
Jodi

Better support for Batch Operations

See https://parse.com/docs/rest#objects-batch

Parse now lets you send up to 20 operations per request. I've added basic support for this:

batch = Parse::Batch.new
batch.add_request({
  "method" => "POST",
  "path" => "/1/classes/GameScore",
  "body" => {
    "score" => 1337,
    "playerName" => "Sean Plott"
  }
})
resp = batch.run!

While this is nice to get started, I don't like how you still need to manually construct the "path" value. First, the version number should be implied since it's already stored in Parse::Protocol. Second, shouldn't there be a better and more concise way to construct the rest of the path?

Perhaps something like this would be better:

batch = Parse::Batch.new
20.times do |i|
  obj = Parse::Object.new("Widget")
  obj["foo"] = i
  batch.add_object(obj)
end
batch.run!

If the object has an objectId, Parse::Batch would then know to send a PUT request to the endpoint "/1/classes/Widget" with the proper attributes in the payload. If, there is no objectId, then Parse::Batch would send a POST instead. I'm not quite sure how this would/should handle deletes, but I'd be open to suggestions.

@ericcj, do you have any thoughts on this?

A JSON text must at least contain two octets!

I think this might be an issue in the JSON parser?

    [2013-07-31T21:32:05.718626 #9653]  WARN -- : Retrying Parse Error #<JSON::ParserError: A JSON text must at least contain two octets!> on request /1/users nil response #<Patron::Response @status_line='HTTP/1.1 502 BAD_GATEWAY'>

/Users/ddavtian/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/json/common.rb:155:in `initialize': A JSON text must at least contain two octets! (JSON::ParserError)
from /Users/ddavtian/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/json/common.rb:155:in `new'
from /Users/ddavtian/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/json/common.rb:155:in `parse'
from /Users/ddavtian/.rvm/gems/ruby-2.0.0-p0/gems/parse-ruby-client-0.1.15/lib/parse/client.rb:102:in `request'
from /Users/ddavtian/.rvm/gems/ruby-2.0.0-p0/gems/parse-ruby-client-0.1.15/lib/parse/query.rb:127:in `get'
from new_facebook_user_notify.rb:56:in `compareFacebookFriendsToParse'
from new_facebook_user_notify.rb:99:in `<main>'

Need version bump to 0.0.6 after fix for #8

0.0.5, the latest gem available, still lacks the "updatedAt" fix. Can I request a version bump, please, so we can deploy code that this depends on into an environment in which we can't manually build the gems? Thanks in advance...

save silently ignores boolean fields set to false

this is the code to reproduce the bug:

# first, I drop MyClass in the parse.com data browser
# create and save object
o = Parse::Object.new("MyClass")
o["booleanField1"] = false
o["booleanField2"] = true
o = o.save
puts o["booleanField1"] # false
puts o["booleanField2"] # true
# get object and read boolean field
a = Parse::Query.new("MyClass")
    .eq("objectId", o["objectId"])
    .get.first
puts a["booleanField1"] # nil
puts a["booleanField2"] # true

The online parse data browser confirms this, the column "booleanField1" is completely missing. I put in lib/client.rb, function request(..), this debug print:

puts body

and I get this:

{"booleanField1":{"__op":"Delete"},"booleanField2":true}

the actual request sent to parse servers is wrong, and it seems a false is interpreted as some kind of delete field..

I'm on ruby 1.9.3p286 and parse-ruby-client 0.1.14

Multi Threading | Sidekiq

Hi!

I am in the same situation as #98

I've been running some code single threaded for a few months without any problems. That code calls Parse::Push. Recently, we move some of that code to use http://sidekiq.org/ and we are seeing some error for the first time. They are:

Parse::ParseProtocolError: : unauthorized
Patron::HostResolutionError
Patron::URLFormatError

Not sure if all the exceptions are related to thread safety. I'll investigate further.

I am not a thread safety expert, but I would love to collaborate on making this gem thread safe. At least, the push notifications part. 😄

A quick peak into the code reveals:

https://github.com/adelevie/parse-ruby-client/blob/master/lib/parse/client.rb#L183

Is a singleton needed for the client?

Parse::UserCannotBeAlteredWithoutSessionError

I have an admin interface and I am trying to edit users through the interface and I am seeing the following error being thrown: Parse::UserCannotBeAlteredWithoutSessionError

I am indeed using the MASTER-KEY on the account as such.

Parse.init :application_id => "APP_ID_GOES_HERE",
:master_key => "MASTER_KEY_GOES_HERE"

Any help is very appreciated.

Support $include operator

which would require refactoring how pointers are handled. probably worth folding them into Parse::Object so it has a fetch_if_needed and transparently converts to a pointer on save like the official SDK's. have to look into how those SDK's support the Parse "Object" type that allows you to save an entire hash into a column though and replicate that behavior too

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.