Skip to content

Commit ac60c3b

Browse files
committed
fix: add test helper
1 parent f4b695e commit ac60c3b

File tree

8 files changed

+499
-44
lines changed

8 files changed

+499
-44
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@
1010
.idea
1111
test/dummy/log/
1212
test/dummy/tmp/
13-
.env
13+
.env
14+
vendor

README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,90 @@ puts location.coordinates.x # -5.923647
139139
puts location.coordinates.y # 35.790897
140140
```
141141

142+
## Testing
143+
144+
When testing spatial functionality in your Rails application, this gem provides helpful test utilities:
145+
146+
```ruby
147+
# In your test_helper.rb or rails_helper.rb
148+
require 'activerecord-postgis/test_helper'
149+
150+
class ActiveSupport::TestCase
151+
include ActiveRecordPostgis::TestHelper
152+
end
153+
154+
# Or for RSpec
155+
RSpec.configure do |config|
156+
config.include ActiveRecordPostgis::TestHelper
157+
end
158+
```
159+
160+
### Test Helper Methods
161+
162+
```ruby
163+
class LocationTest < ActiveSupport::TestCase
164+
def test_spatial_operations
165+
# Create test geometries
166+
point1 = create_point(-5.9, 35.8)
167+
point2 = create_point(-5.91, 35.81)
168+
polygon = create_test_polygon
169+
170+
location = Location.create!(coordinates: point1, boundary: polygon)
171+
172+
# Traditional assertions
173+
assert_spatial_equal point1, location.coordinates
174+
assert_within_distance point1, point2, 200 # meters
175+
assert_contains polygon, point1
176+
177+
# New chainable syntax (recommended)
178+
assert_spatial_column(location.coordinates)
179+
.has_srid(4326)
180+
.is_type(:point)
181+
.is_geographic
182+
183+
assert_spatial_column(location.boundary)
184+
.is_type(:polygon)
185+
.has_srid(4326)
186+
end
187+
188+
def test_3d_geometry
189+
point_3d = create_point(1.0, 2.0, srid: 4326, z: 10.0)
190+
191+
assert_spatial_column(point_3d)
192+
.has_z
193+
.has_srid(4326)
194+
.is_type(:point)
195+
.is_cartesian
196+
end
197+
end
198+
```
199+
200+
**Available Test Helpers:**
201+
202+
**Traditional Assertions:**
203+
- `assert_spatial_equal(expected, actual)` - Assert spatial objects are equal
204+
- `assert_within_distance(point1, point2, distance)` - Assert points within distance
205+
- `assert_contains(container, contained)` - Assert geometry contains another
206+
- `assert_within(inner, outer)` - Assert geometry is within another
207+
- `assert_intersects(geom1, geom2)` - Assert geometries intersect
208+
- `assert_disjoint(geom1, geom2)` - Assert geometries don't intersect
209+
210+
**Chainable Spatial Column Assertions:**
211+
- `assert_spatial_column(geometry).has_z` - Assert has Z dimension
212+
- `assert_spatial_column(geometry).has_m` - Assert has M dimension
213+
- `assert_spatial_column(geometry).has_srid(srid)` - Assert SRID value
214+
- `assert_spatial_column(geometry).is_type(type)` - Assert geometry type
215+
- `assert_spatial_column(geometry).is_geographic` - Assert geographic factory
216+
- `assert_spatial_column(geometry).is_cartesian` - Assert cartesian factory
217+
218+
**Geometry Factories:**
219+
- `create_point(x, y, srid: 4326)` - Create test points
220+
- `create_test_polygon(srid: 4326)` - Create test polygons
221+
- `create_test_linestring(srid: 4326)` - Create test linestrings
222+
- `factory(srid: 4326, geographic: false)` - Get geometry factory
223+
- `geographic_factory(srid: 4326)` - Get geographic factory
224+
- `cartesian_factory(srid: 0)` - Get cartesian factory
225+
142226
## Features
143227

144228
🌍 **Complete PostGIS Type Support**
@@ -163,6 +247,7 @@ puts location.coordinates.y # 35.790897
163247
- Works with existing PostgreSQL tools
164248
- Clear error messages and debugging
165249
- Full RGeo integration
250+
- Comprehensive test helpers for spatial assertions
166251

167252
## Acknowledgments
168253

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveRecord
4+
module ConnectionAdapters
5+
module PostGIS
6+
# Test helpers for spatial data assertions
7+
module TestHelpers
8+
# Assert that two spatial objects are equal
9+
def assert_spatial_equal(expected, actual, msg = nil)
10+
msg ||= "Expected spatial object #{expected.as_text} but got #{actual.as_text}"
11+
12+
if expected.respond_to?(:equals?) && actual.respond_to?(:equals?)
13+
assert expected.equals?(actual), msg
14+
else
15+
assert_equal expected.to_s, actual.to_s, msg
16+
end
17+
end
18+
19+
# Assert that a point is within a specified distance of another point
20+
def assert_within_distance(point1, point2, distance, msg = nil)
21+
actual_distance = point1.distance(point2)
22+
msg ||= "Distance #{actual_distance} exceeds maximum allowed distance of #{distance}"
23+
assert actual_distance <= distance, msg
24+
end
25+
26+
# Assert that a geometry contains another geometry
27+
def assert_contains(container, contained, msg = nil)
28+
msg ||= "Expected #{container.as_text} to contain #{contained.as_text}"
29+
assert container.contains?(contained), msg
30+
end
31+
32+
# Assert that a geometry is within another geometry
33+
def assert_within(inner, outer, msg = nil)
34+
msg ||= "Expected #{inner.as_text} to be within #{outer.as_text}"
35+
assert inner.within?(outer), msg
36+
end
37+
38+
# Assert that two geometries intersect
39+
def assert_intersects(geom1, geom2, msg = nil)
40+
msg ||= "Expected #{geom1.as_text} to intersect #{geom2.as_text}"
41+
assert geom1.intersects?(geom2), msg
42+
end
43+
44+
# Assert that two geometries do not intersect
45+
def assert_disjoint(geom1, geom2, msg = nil)
46+
msg ||= "Expected #{geom1.as_text} to be disjoint from #{geom2.as_text}"
47+
assert geom1.disjoint?(geom2), msg
48+
end
49+
50+
# Assert that a geometry has the expected SRID
51+
def assert_srid(geometry, expected_srid, msg = nil)
52+
actual_srid = geometry.srid
53+
msg ||= "Expected SRID #{expected_srid} but got #{actual_srid}"
54+
assert_equal expected_srid, actual_srid, msg
55+
end
56+
57+
# Chainable spatial column assertion builder
58+
def assert_spatial_column(geometry, msg_prefix = nil)
59+
SpatialColumnAssertion.new(geometry, self, msg_prefix)
60+
end
61+
62+
# Assert that a geometry is of the expected type
63+
def assert_geometry_type(geometry, expected_type, msg = nil)
64+
actual_type = geometry.geometry_type.type_name.downcase
65+
expected_type = expected_type.to_s.downcase.gsub("_", "")
66+
actual_type = actual_type.gsub("_", "")
67+
msg ||= "Expected geometry type #{expected_type} but got #{actual_type}"
68+
assert_equal expected_type, actual_type, msg
69+
end
70+
71+
# Legacy methods for backward compatibility
72+
def assert_has_z(geometry, msg = nil)
73+
assert_spatial_column(geometry, msg).has_z
74+
end
75+
76+
def assert_has_m(geometry, msg = nil)
77+
assert_spatial_column(geometry, msg).has_m
78+
end
79+
80+
# Create a point for testing
81+
def create_point(x, y, srid: 4326, z: nil, m: nil)
82+
if z || m
83+
# Use cartesian factory for 3D/4D points
84+
factory = RGeo::Cartesian.preferred_factory(srid: srid, has_z_coordinate: !!z, has_m_coordinate: !!m)
85+
if z && m
86+
factory.point(x, y, z, m)
87+
elsif z
88+
factory.point(x, y, z)
89+
elsif m
90+
factory.point(x, y, 0, m) # Default Z to 0 for M-only
91+
else
92+
factory.point(x, y)
93+
end
94+
else
95+
factory = RGeo::Geographic.simple_mercator_factory(srid: srid)
96+
factory.point(x, y)
97+
end
98+
end
99+
100+
# Create a test polygon for testing
101+
def create_test_polygon(srid: 4326)
102+
factory = RGeo::Geographic.simple_mercator_factory(srid: srid)
103+
factory.polygon(
104+
factory.linear_ring([
105+
factory.point(0, 0),
106+
factory.point(0, 1),
107+
factory.point(1, 1),
108+
factory.point(1, 0),
109+
factory.point(0, 0)
110+
])
111+
)
112+
end
113+
114+
# Create a test linestring for testing
115+
def create_test_linestring(srid: 4326)
116+
factory = RGeo::Geographic.simple_mercator_factory(srid: srid)
117+
factory.line_string([
118+
factory.point(0, 0),
119+
factory.point(1, 1),
120+
factory.point(2, 0)
121+
])
122+
end
123+
end
124+
125+
# Chainable spatial column assertion class
126+
class SpatialColumnAssertion
127+
def initialize(geometry, test_case, msg_prefix = nil)
128+
@geometry = geometry
129+
@test_case = test_case
130+
@msg_prefix = msg_prefix
131+
end
132+
133+
def has_z
134+
msg = build_message("to have Z dimension")
135+
has_z = detect_has_z(@geometry)
136+
@test_case.assert has_z, msg
137+
self
138+
end
139+
140+
def has_m
141+
msg = build_message("to have M dimension")
142+
has_m = detect_has_m(@geometry)
143+
@test_case.assert has_m, msg
144+
self
145+
end
146+
147+
def has_srid(expected_srid)
148+
msg = build_message("to have SRID #{expected_srid}")
149+
actual_srid = @geometry.srid
150+
@test_case.assert_equal expected_srid, actual_srid, msg
151+
self
152+
end
153+
154+
def is_type(expected_type)
155+
msg = build_message("to be of type #{expected_type}")
156+
actual_type = @geometry.geometry_type.type_name.downcase
157+
expected_type = expected_type.to_s.downcase.gsub("_", "")
158+
actual_type = actual_type.gsub("_", "")
159+
@test_case.assert_equal expected_type, actual_type, msg
160+
self
161+
end
162+
163+
def is_geographic
164+
msg = build_message("to be geographic")
165+
# Check if factory is geographic
166+
is_geo = @geometry.factory.respond_to?(:spherical?) && @geometry.factory.spherical?
167+
@test_case.assert is_geo, msg
168+
self
169+
end
170+
171+
def is_cartesian
172+
msg = build_message("to be cartesian")
173+
# Check if factory is cartesian
174+
is_cart = !(@geometry.factory.respond_to?(:spherical?) && @geometry.factory.spherical?)
175+
@test_case.assert is_cart, msg
176+
self
177+
end
178+
179+
private
180+
181+
def build_message(expectation)
182+
prefix = @msg_prefix ? "#{@msg_prefix}: " : ""
183+
"#{prefix}Expected geometry #{expectation}"
184+
end
185+
186+
def detect_has_z(geometry)
187+
return geometry.has_z_coordinate? if geometry.respond_to?(:has_z_coordinate?)
188+
return geometry.has_z? if geometry.respond_to?(:has_z?)
189+
return !geometry.z.nil? if geometry.respond_to?(:z)
190+
false
191+
end
192+
193+
def detect_has_m(geometry)
194+
return geometry.has_m_coordinate? if geometry.respond_to?(:has_m_coordinate?)
195+
return geometry.has_m? if geometry.respond_to?(:has_m?)
196+
return !geometry.m.nil? if geometry.respond_to?(:m)
197+
false
198+
end
199+
end
200+
end
201+
end
202+
end
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "../active_record/connection_adapters/postgis/test_helpers"
4+
5+
module ActiveRecordPostgis
6+
module TestHelper
7+
include ActiveRecord::ConnectionAdapters::PostGIS::TestHelpers
8+
9+
# Additional convenience methods for PostGIS testing
10+
def factory(srid: 4326, geographic: false)
11+
if geographic
12+
RGeo::Geographic.spherical_factory(srid: srid)
13+
else
14+
RGeo::Cartesian.preferred_factory(srid: srid)
15+
end
16+
end
17+
18+
def geographic_factory(srid: 4326)
19+
RGeo::Geographic.spherical_factory(srid: srid)
20+
end
21+
22+
def cartesian_factory(srid: 0)
23+
RGeo::Cartesian.preferred_factory(srid: srid)
24+
end
25+
26+
def spatial_factory_store
27+
RGeo::ActiveRecord::SpatialFactoryStore.instance
28+
end
29+
30+
def reset_spatial_store
31+
spatial_factory_store.clear
32+
spatial_factory_store.default = nil
33+
end
34+
35+
# Create a test table with spatial columns
36+
def create_spatial_table(table_name, connection = ActiveRecord::Base.connection)
37+
connection.create_table table_name, force: true do |t|
38+
t.st_point :coordinates, srid: 4326
39+
t.st_point :location, srid: 4326, geographic: true
40+
t.st_polygon :boundary, srid: 4326
41+
t.st_line_string :path, srid: 4326
42+
t.timestamps
43+
end
44+
end
45+
46+
# Clean up spatial tables after tests
47+
def drop_spatial_table(table_name, connection = ActiveRecord::Base.connection)
48+
connection.drop_table table_name if connection.table_exists?(table_name)
49+
end
50+
end
51+
end

0 commit comments

Comments
 (0)