Skip to content

Commit

Permalink
Support for withinPolygon query. Closes #31
Browse files Browse the repository at this point in the history
  • Loading branch information
apersaud committed Jun 11, 2017
1 parent 679b3c5 commit f25cf7d
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 5 deletions.
1 change: 1 addition & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### 1.7.0
- NEW: You can use `set_default_acl` to set default ACLs for your subclasses.
- NEW: Support for `withinPolygon` query constraint.
- Refactoring of the default ACL system and deprecation of `Parse::Object.acl`
- Parse::ACL.everyone returns an ACL instance with public read and writes.
- Documentation updates.
Expand Down
28 changes: 24 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ result = Parse.call_function :myFunctionName, {param: value}
- [Parse::ACL](#parseacl)
- [Parse::Session](#parsesession)
- [Parse::Installation](#parseinstallation)
- [Parse::Product](#parseproduct)
- [Parse::Role](#parserole)
- [Parse::User](#parseuser)
- [Signup](#signup)
Expand Down Expand Up @@ -188,6 +189,7 @@ result = Parse.call_function :myFunctionName, {param: value}
- [Creating, Saving and Deleting Records](#creating-saving-and-deleting-records)
- [Create](#create)
- [Saving](#saving)
- [Saving applying User ACLs](#saving-applying-user-acls)
- [Raising an exception when save fails](#raising-an-exception-when-save-fails)
- [Modifying Associations](#modifying-associations)
- [Batch Requests](#batch-requests)
Expand Down Expand Up @@ -229,6 +231,7 @@ result = Parse.call_function :myFunctionName, {param: value}
- [Geo Queries](#geo-queries)
- [Max Distance Constraint](#max-distance-constraint)
- [Bounding Box Constraint](#bounding-box-constraint)
- [Polygon Area Constraint](#polygon-area-constraint)
- [Relational Queries](#relational-queries)
- [Compound Queries](#compound-queries)
- [Query Scopes](#query-scopes)
Expand All @@ -244,7 +247,8 @@ result = Parse.call_function :myFunctionName, {param: value}
- [Register Webhooks](#register-webhooks)
- [Parse REST API Client](#parse-rest-api-client)
- [Request Caching](#request-caching)
- [Development](#development)
- [Contributing](#contributing)
- [License](#license)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Expand Down Expand Up @@ -1761,7 +1765,7 @@ The `where` clause is based on utilizing a set of constraints on the defined col
{ :column.constraint => value }
```

## Query Constraints
## [Query Constraints](https://www.modernistik.com/gems/parse-stack/Parse/Constraint.html)
Most of the constraints supported by Parse are available to `Parse::Query`. Assuming you have a column named `field`, here are some examples. For an explanation of the constraints, please see [Parse Query Constraints documentation](http://docs.parseplatform.org/rest/guide/#queries). You can build your own custom query constraints by creating a `Parse::Constraint` subclass. For all these `where` clauses assume `q` is a `Parse::Query` object.

#### Equals
Expand Down Expand Up @@ -1974,7 +1978,7 @@ Song.all :artist => Artist.pointer(artist_id)
Song.all :artist => Parse::Pointer.new("Artist", artist_id)
```

### Geo Queries
### [Geo Queries](https://www.modernistik.com/gems/parse-stack/Parse/Constraint/NearSphereQueryConstraint.html)
Equivalent to the `$nearSphere` Parse query operation. This is only applicable if the field is of type `GeoPoint`. This will query Parse and return a list of results ordered by distance with the nearest object being first.

```ruby
Expand All @@ -2000,7 +2004,7 @@ PlaceObject.all :location.near => geopoint.max_miles(10)

We will support `$maxDistanceInKilometers` (for kms) and `$maxDistanceInRadians` (for radian angle) in the future.

#### Bounding Box Constraint
#### [Bounding Box Constraint](https://www.modernistik.com/gems/parse-stack/Parse/Constraint/WithinGeoBoxQueryConstraint.html)
Equivalent to the `$within` Parse query operation and `$box` geopoint constraint. The rectangular bounding box is defined by a southwest point as the first parameter, followed by the a northeast point. Please note that Geo box queries that cross the international date lines are not currently supported by Parse.

```ruby
Expand All @@ -2015,6 +2019,22 @@ ne = Parse::GeoPoint.new 36.12, -115.31 # Las Vegas
PlaceObject.all :location.within_box => [sw,ne]
```

#### [Polygon Area Constraint](https://www.modernistik.com/gems/parse-stack/Parse/Constraint/WithinPolygonQueryConstraint.html)
Equivalent to the `$geoWithin` Parse query operation and `$polygon` geopoint constraint. The polygon area is described by a list of `Parse::GeoPoint` objects and should contain 3 or more points. This feature is only available in Parse-Server version 2.4.2 and later.

```ruby
# As many points as you want, minimum 3
q.where :field.within_polygon => [geopoint1, geopoint2, geopoint3]

# Polygon for the Bermuda Triangle
bermuda = Parse::GeoPoint.new 32.3078000,-64.7504999 # Bermuda
miami = Parse::GeoPoint.new 25.7823198,-80.2660226 # Miami, FL
san_juan = Parse::GeoPoint.new 18.3848232,-66.0933608 # San Juan, PR

# get all sunken ships inside the Bermuda Triangle
SunkenShip.all :location.within_polygon => [bermuda, san_juan, miami]
```

### Relational Queries
Equivalent to the `$relatedTo` Parse query operation. If you want to retrieve objects that are members of a `Relation` field in your Parse class.

Expand Down
4 changes: 4 additions & 0 deletions bin/console
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ def setup

end
puts "Type 'setup' to connect to Parse-Server"

# Create shortnames
Parse.use_shortnames!

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

Expand Down
2 changes: 1 addition & 1 deletion lib/parse/query/constraint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def formatted_value(value)
d = { __type: Parse::Model::TYPE_DATE, iso: d.iso8601(3) } if d.respond_to?(:iso8601)
d = d.pointer if d.respond_to?(:pointer) #simplified query object
d = d.to_s if d.is_a?(Regexp)
#d = d.pointer if d.is_a?(Parse::Object) #simplified query object
# d = d.pointer if d.is_a?(Parse::Object) #simplified query object
# d = d.compile
if d.is_a?(Parse::Query)
compiled = d.compile(encode: false, includeClassName: true)
Expand Down
44 changes: 44 additions & 0 deletions lib/parse/query/constraints.rb
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,50 @@ def build
end
end

# Equivalent to the `$geoWithin` Parse query operation and `$polygon` geopoints
# constraint. The polygon area is defined by a list of {Parse::GeoPoint}
# objects that make up the enclosed area. A polygon query should have 3 or more geopoints.
# Please note that some Geo queries that cross the international date lines are not currently
# supported by Parse.
#
# # As many points as you want, minimum 3
# q.where :field.within_polygon => [geopoint1, geopoint2, geopoint3]
#
# # Polygon for the Bermuda Triangle
# bermuda = Parse::GeoPoint.new 32.3078000,-64.7504999 # Bermuda
# miami = Parse::GeoPoint.new 25.7823198,-80.2660226 # Miami, FL
# san_juan = Parse::GeoPoint.new 18.3848232,-66.0933608 # San Juan, PR
#
# # get all sunken ships inside the Bermuda Triangle
# SunkenShip.all :location.within_polygon => [bermuda, san_juan, miami]
#
class WithinPolygonQueryConstraint < Constraint
# @!method within_polygon
# A registered method on a symbol to create the constraint. Maps to Parse
# operator "$geoWithin" with "$polygon" subconstraint. Takes an array of {Parse::GeoPoint} objects.
# @example
# # As many points as you want
# q.where :field.within_polygon => [geopoint1, geopoint2, geopoint3]
# @return [WithinPolygonQueryConstraint]
# @version 1.7.0 (requires Server v2.4.2 or later)
contraint_keyword :$geoWithin
register :within_polygon

# @return [Hash] the compiled constraint.
def build
geopoint_values = formatted_value
unless geopoint_values.is_a?(Array) &&
geopoint_values.all? {|point| point.is_a?(Parse::GeoPoint) } &&
geopoint_values.count > 2
raise ArgumentError, '[Parse::Query] Invalid query value parameter passed to'\
' `within_polygon` constraint: Value must be an array with 3'\
' or more `Parse::GeoPoint` objects'
end

{ @operation.operand => { :$geoWithin => { :$polygon => geopoint_values } } }
end
end

end

end
57 changes: 57 additions & 0 deletions test/lib/parse/query/constraints/polygon_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require_relative '../../../../test_helper'

class TestWithinPolygonQueryConstraint < Minitest::Test
extend Minitest::Spec::DSL
include ConstraintTests

def setup
@klass = Parse::Constraint::WithinPolygonQueryConstraint
@key = :$geoWithin
@operand = :within_polygon
@keys = [:within_polygon]
@skip_scalar_values_test = true

@bermuda = Parse::GeoPoint.new 32.3078000,-64.7504999 # Bermuda
@miami = Parse::GeoPoint.new 25.7823198,-80.2660226 # Miami, FL
@san_juan = Parse::GeoPoint.new 18.3848232,-66.0933608 # San Juan, PR
@san_diego = Parse::GeoPoint.new 32.9201332,-117.1088263
end

def build(value)
{"field" => { @key => { :$polygon => value } } }
end

def test_argument_error
triangle = [@bermuda, @miami] # missing one
assert_raises(ArgumentError) { User.query(:location.within_polygon => nil).compile }
assert_raises(ArgumentError) { User.query(:location.within_polygon => []).compile }
assert_raises(ArgumentError) { User.query(:location.within_polygon => [@bermuda, 2343]).compile }
assert_raises(ArgumentError) { User.query(:location.within_polygon => triangle).compile }
triangle.push @san_juan
refute_raises(ArgumentError) { User.query(:location.within_polygon => triangle).compile }
quad = triangle + [@san_diego]
refute_raises(ArgumentError) { User.query(:location.within_polygon => quad).compile }
end

def test_compiled_query
triangle = [@bermuda, @miami, @san_juan]
compiled_query = {"location"=>{"$geoWithin"=>{"$polygon"=>[
{:__type=>"GeoPoint", :latitude=>32.3078, :longitude=>-64.7504999},
{:__type=>"GeoPoint", :latitude=>25.7823198, :longitude=>-80.2660226},
{:__type=>"GeoPoint", :latitude=>18.3848232, :longitude=>-66.0933608}
]}}}
query = User.query(:location.within_polygon => [@bermuda, @miami, @san_juan])
assert_equal query.compile_where.as_json, compiled_query

compiled_query = {"location"=>{"$geoWithin"=>{"$polygon"=>[
{:__type=>"GeoPoint", :latitude=>32.9201332, :longitude=>-117.1088263},
{:__type=>"GeoPoint", :latitude=>25.7823198, :longitude=>-80.2660226},
{:__type=>"GeoPoint", :latitude=>18.3848232, :longitude=>-66.0933608},
{:__type=>"GeoPoint", :latitude=>32.3078, :longitude=>-64.7504999}
]}}}
query = User.query(:location.within_polygon => [@san_diego, @miami, @san_juan, @bermuda])
assert_equal query.compile_where.as_json, compiled_query
end


end
1 change: 1 addition & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'byebug'
require_relative '../lib/parse/stack.rb'

Parse.use_shortnames!

module ConstraintTests

Expand Down

0 comments on commit f25cf7d

Please sign in to comment.