diff --git a/Changes.md b/Changes.md index 35ec04d..0485765 100644 --- a/Changes.md +++ b/Changes.md @@ -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. diff --git a/README.md b/README.md index a96f3a2..9978922 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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) @@ -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) @@ -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) @@ -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 @@ -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 @@ -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 @@ -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. diff --git a/bin/console b/bin/console index a195c58..98b81ad 100755 --- a/bin/console +++ b/bin/console @@ -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. diff --git a/lib/parse/query/constraint.rb b/lib/parse/query/constraint.rb index 5063bbf..cd60f72 100644 --- a/lib/parse/query/constraint.rb +++ b/lib/parse/query/constraint.rb @@ -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) diff --git a/lib/parse/query/constraints.rb b/lib/parse/query/constraints.rb index be5c6f5..f95ef5f 100644 --- a/lib/parse/query/constraints.rb +++ b/lib/parse/query/constraints.rb @@ -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 diff --git a/test/lib/parse/query/constraints/polygon_test.rb b/test/lib/parse/query/constraints/polygon_test.rb new file mode 100644 index 0000000..0bdbb00 --- /dev/null +++ b/test/lib/parse/query/constraints/polygon_test.rb @@ -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 diff --git a/test/test_helper.rb b/test/test_helper.rb index 659835b..95cc030 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -3,6 +3,7 @@ require 'byebug' require_relative '../lib/parse/stack.rb' +Parse.use_shortnames! module ConstraintTests