activerecord-spatial 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.rubocop.yml +3314 -1052
- data/.travis.yml +17 -0
- data/Gemfile +1 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -12
- data/activerecord-spatial.gemspec +1 -1
- data/lib/activerecord-spatial.rb +1 -0
- data/lib/activerecord-spatial/active_record.rb +1 -0
- data/lib/activerecord-spatial/active_record/connection_adapters/postgresql/adapter_extensions.rb +1 -0
- data/lib/activerecord-spatial/active_record/connection_adapters/postgresql/adapter_extensions/active_record.rb +4 -3
- data/lib/activerecord-spatial/active_record/connection_adapters/postgresql/postgis.rb +1 -0
- data/lib/activerecord-spatial/active_record/connection_adapters/postgresql/unknown_srid.rb +2 -1
- data/lib/activerecord-spatial/active_record/models/geography_column.rb +1 -0
- data/lib/activerecord-spatial/active_record/models/geometry_column.rb +1 -0
- data/lib/activerecord-spatial/active_record/models/spatial_column.rb +1 -0
- data/lib/activerecord-spatial/active_record/models/spatial_ref_sys.rb +1 -0
- data/lib/activerecord-spatial/associations.rb +4 -12
- data/lib/activerecord-spatial/associations/active_record.rb +96 -54
- data/lib/activerecord-spatial/associations/base.rb +2 -19
- data/lib/activerecord-spatial/associations/reflection/spatial_reflection.rb +1 -0
- data/lib/activerecord-spatial/associations/spatial_association.rb +41 -0
- data/lib/activerecord-spatial/spatial_columns.rb +1 -0
- data/lib/activerecord-spatial/spatial_function.rb +2 -1
- data/lib/activerecord-spatial/spatial_scope_constants.rb +1 -0
- data/lib/activerecord-spatial/spatial_scope_constants/postgis_2_0.rb +1 -0
- data/lib/activerecord-spatial/spatial_scope_constants/postgis_2_2.rb +1 -0
- data/lib/activerecord-spatial/spatial_scope_constants/postgis_legacy.rb +1 -0
- data/lib/activerecord-spatial/spatial_scopes.rb +14 -13
- data/lib/activerecord-spatial/version.rb +2 -1
- data/test/accessors_geographies_tests.rb +2 -1
- data/test/accessors_geometries_tests.rb +2 -1
- data/test/adapter_tests.rb +5 -4
- data/test/associations_tests.rb +40 -25
- data/test/geography_column_tests.rb +2 -1
- data/test/geometry_column_tests.rb +2 -1
- data/test/models/bar.rb +1 -9
- data/test/models/blort.rb +1 -7
- data/test/models/foo.rb +3 -9
- data/test/models/foo3d.rb +1 -9
- data/test/models/foo_geography.rb +1 -8
- data/test/models/zortable.rb +1 -9
- data/test/schema.rb +54 -0
- data/test/spatial_function_tests.rb +2 -1
- data/test/spatial_scopes_geographies_tests.rb +2 -1
- data/test/spatial_scopes_tests.rb +2 -1
- data/test/test_helper.rb +57 -91
- metadata +9 -7
- data/lib/activerecord-spatial/associations/preloader/spatial_association.rb +0 -57
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
|
@@ -107,21 +107,15 @@ See the test/database.yml file for example settings.
|
|
|
107
107
|
|
|
108
108
|
== ActiveRecord Versions Supported
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
https://github.com/dark-panda/geos-extensions . We have pulled the ActiveRecord
|
|
114
|
-
extensions out of that gem and are instead packaging them here, thus allowing
|
|
115
|
-
you to use the actual Geos extensions in an unfettered way. If you wish to
|
|
116
|
-
use the spatial extensions with versions of Rails prior to 5, see versions of
|
|
117
|
-
the ActiveRecordSpatial gem prior to version 1.0.0.
|
|
110
|
+
Major version updates now coincide with Rails version updates. This particular
|
|
111
|
+
version works with Rails 6.0. For older versions of Rails, see older versions of
|
|
112
|
+
the gem.
|
|
118
113
|
|
|
119
114
|
== PostGIS and PostgreSQL Versions Supported
|
|
120
115
|
|
|
121
|
-
As of this writing, things we test on PostgreSQL
|
|
122
|
-
Some features are only available
|
|
123
|
-
|
|
124
|
-
2.0.
|
|
116
|
+
As of this writing, things we test on PostgreSQL 11 and PostGIS 2.5+.
|
|
117
|
+
Some features are only available on newer versions of PostGIS such as certain
|
|
118
|
+
scopes and the like.
|
|
125
119
|
|
|
126
120
|
PostgreSQL 9.0 and below are currently not supported as some of the SQL we
|
|
127
121
|
produce to create the spatial queries is not supported in older PostgreSQL
|
|
@@ -21,6 +21,6 @@ Gem::Specification.new do |s|
|
|
|
21
21
|
s.homepage = 'https://github.com/dark-panda/activerecord-spatial'
|
|
22
22
|
s.require_paths = ['lib']
|
|
23
23
|
|
|
24
|
-
s.add_dependency('rails', ['>=
|
|
24
|
+
s.add_dependency('rails', ['>= 6.0'])
|
|
25
25
|
s.add_dependency('geos-extensions', ['>= 0.5'])
|
|
26
26
|
end
|
data/lib/activerecord-spatial.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
|
|
2
3
|
module ActiveRecord
|
|
3
4
|
module Type
|
|
@@ -37,10 +38,10 @@ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:geom
|
|
|
37
38
|
|
|
38
39
|
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
|
|
39
40
|
prepend(Module.new do
|
|
40
|
-
def initialize_type_map(type_map)
|
|
41
|
+
def initialize_type_map(m = type_map)
|
|
41
42
|
super
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
m.register_type 'geometry', ::ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Geometry.new
|
|
44
|
+
m.register_type 'geography', ::ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Geography.new
|
|
44
45
|
end
|
|
45
46
|
end)
|
|
46
47
|
end
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
|
|
2
3
|
require 'activerecord-spatial/associations/base'
|
|
3
4
|
require 'activerecord-spatial/associations/reflection/spatial_reflection'
|
|
4
|
-
require 'activerecord-spatial/associations/
|
|
5
|
+
require 'activerecord-spatial/associations/spatial_association'
|
|
5
6
|
require 'activerecord-spatial/associations/active_record'
|
|
6
7
|
|
|
7
8
|
module ActiveRecordSpatial::Associations
|
|
8
9
|
module ClassMethods #:nodoc:
|
|
9
|
-
def has_many_spatially(name, scope = nil, options
|
|
10
|
-
if scope.is_a?(Hash)
|
|
11
|
-
options = scope
|
|
12
|
-
scope = nil
|
|
13
|
-
end
|
|
14
|
-
|
|
10
|
+
def has_many_spatially(name, scope = nil, **options, &extension)
|
|
15
11
|
options = build_options(options)
|
|
16
12
|
|
|
17
13
|
unless ActiveRecordSpatial::SpatialScopeConstants::RELATIONSHIPS.include?(options[:relationship].to_s)
|
|
@@ -20,11 +16,7 @@ module ActiveRecordSpatial::Associations
|
|
|
20
16
|
|
|
21
17
|
reflection = ActiveRecord::Associations::Builder::Spatial.build(self, name, scope, options, &extension)
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
ActiveRecord::Reflection.add_reflection(self, name, reflection)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
reflection
|
|
19
|
+
ActiveRecord::Reflection.add_reflection(self, name, reflection)
|
|
28
20
|
end
|
|
29
21
|
end
|
|
30
22
|
end
|
|
@@ -1,69 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
|
|
2
3
|
module ActiveRecord
|
|
3
4
|
module Associations #:nodoc:
|
|
4
|
-
class
|
|
5
|
-
|
|
6
|
-
private
|
|
5
|
+
class SpatialAssociationScope < AssociationScope #:nodoc:
|
|
6
|
+
INSTANCE = create
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
class << self
|
|
9
|
+
def scope(association)
|
|
10
|
+
ActiveRecord::Associations::SpatialAssociationScope::INSTANCE.scope(association)
|
|
11
|
+
end
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
end
|
|
13
|
+
def get_bind_values(owner, chain)
|
|
14
|
+
binds = []
|
|
15
|
+
last_reflection = chain.last
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
keys.each do |key|
|
|
19
|
-
memo[key] ||= []
|
|
20
|
-
memo[key] << record
|
|
21
|
-
end
|
|
22
|
-
end
|
|
17
|
+
if last_reflection.type
|
|
18
|
+
binds << owner.class.polymorphic_name
|
|
23
19
|
end
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
owner = owners_by_key[convert_key(key)]
|
|
29
|
-
association = owner.association(reflection.name)
|
|
30
|
-
association.set_inverse_instance(record)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
owners.each_with_object({}) do |owner, result|
|
|
35
|
-
result[owner] = records[convert_key(owner[owner_key_name])] || []
|
|
21
|
+
chain.each_cons(2).each do |reflection, next_reflection|
|
|
22
|
+
if reflection.type
|
|
23
|
+
binds << next_reflection.klass.polymorphic_name
|
|
36
24
|
end
|
|
37
25
|
end
|
|
38
|
-
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
class SpatialAssociation < HasManyAssociation #:nodoc:
|
|
42
|
-
def association_scope
|
|
43
|
-
return unless klass
|
|
44
|
-
|
|
45
|
-
@association_scope ||= SpatialAssociationScope.scope(self, klass.connection)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
private
|
|
49
|
-
|
|
50
|
-
def get_records
|
|
51
|
-
scope.to_a
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
class SpatialAssociationScope < AssociationScope #:nodoc:
|
|
56
|
-
INSTANCE = create
|
|
57
|
-
|
|
58
|
-
class << self
|
|
59
|
-
def scope(association, connection)
|
|
60
|
-
INSTANCE.scope(association, connection)
|
|
26
|
+
binds
|
|
61
27
|
end
|
|
62
28
|
end
|
|
63
29
|
|
|
64
|
-
def last_chain_scope(scope,
|
|
30
|
+
def last_chain_scope(scope, reflection, owner)
|
|
65
31
|
geom_options = {
|
|
66
|
-
class:
|
|
32
|
+
class: reflection.klass
|
|
67
33
|
}
|
|
68
34
|
|
|
69
35
|
if reflection.geom.is_a?(Hash)
|
|
@@ -74,15 +40,91 @@ module ActiveRecord
|
|
|
74
40
|
geom_options[:name] = reflection.geom
|
|
75
41
|
end
|
|
76
42
|
|
|
43
|
+
table = reflection.aliased_table
|
|
77
44
|
scope = scope.send("st_#{reflection.relationship}", geom_options, reflection.scope_options)
|
|
78
45
|
|
|
79
46
|
if reflection.type
|
|
80
|
-
polymorphic_type = transform_value(owner.class.
|
|
81
|
-
scope = scope
|
|
47
|
+
polymorphic_type = transform_value(owner.class.polymorphic_name)
|
|
48
|
+
scope = apply_scope(scope, table, reflection.type, polymorphic_type)
|
|
82
49
|
end
|
|
83
50
|
|
|
84
51
|
scope
|
|
85
52
|
end
|
|
86
53
|
end
|
|
54
|
+
|
|
55
|
+
class Preloader #:nodoc:
|
|
56
|
+
class SpatialAssociation < Association #:nodoc:
|
|
57
|
+
SPATIAL_FIELD_ALIAS = '__spatial_ids__'
|
|
58
|
+
SPATIAL_JOIN_NAME = '__spatial_ids_join__'
|
|
59
|
+
SPATIAL_JOIN_QUOTED_NAME = %{"#{SPATIAL_JOIN_NAME}"}
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def records_for(ids)
|
|
64
|
+
join_name = reflection.active_record.quoted_table_name
|
|
65
|
+
column = %{#{SPATIAL_JOIN_QUOTED_NAME}.#{klass.quoted_primary_key}}
|
|
66
|
+
geom = {
|
|
67
|
+
class: reflection.active_record,
|
|
68
|
+
table_alias: SPATIAL_JOIN_NAME
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if reflection.options[:geom].is_a?(Hash)
|
|
72
|
+
geom.merge!(reflection.options[:geom])
|
|
73
|
+
else
|
|
74
|
+
geom[:column] = reflection.options[:geom]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
where_function = klass.send(
|
|
78
|
+
"st_#{reflection.options[:relationship]}",
|
|
79
|
+
geom,
|
|
80
|
+
(reflection.options[:scope_options] || {}).merge(
|
|
81
|
+
column: reflection.options[:foreign_geom]
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
spatial_scope = scope
|
|
86
|
+
.select(%{#{klass.quoted_table_name}.*, array_to_string(array_agg(#{column}), ',') AS "#{SPATIAL_FIELD_ALIAS}"})
|
|
87
|
+
.joins(
|
|
88
|
+
"INNER JOIN #{join_name} AS #{SPATIAL_JOIN_QUOTED_NAME} ON (" +
|
|
89
|
+
where_function.where_clause.send(:predicates).join(' AND ') +
|
|
90
|
+
')'
|
|
91
|
+
)
|
|
92
|
+
.where(klass.arel_table.alias(SPATIAL_JOIN_NAME)[klass.primary_key].in(ids))
|
|
93
|
+
.group(klass.arel_table[klass.primary_key])
|
|
94
|
+
|
|
95
|
+
spatial_scope.load do |record|
|
|
96
|
+
record[SPATIAL_FIELD_ALIAS].split(',').each do |spatial_id|
|
|
97
|
+
owner = owners_by_key[convert_key(spatial_id)].first
|
|
98
|
+
|
|
99
|
+
association = owner.association(reflection.name)
|
|
100
|
+
association.set_inverse_instance(record)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def records_by_owner
|
|
106
|
+
@records_by_owner ||= preloaded_records.each_with_object({}.compare_by_identity) do |record, result|
|
|
107
|
+
record[SPATIAL_FIELD_ALIAS].split(',').each do |spatial_id|
|
|
108
|
+
owners_by_key[convert_key(spatial_id)].each do |owner|
|
|
109
|
+
(result[owner] ||= []) << record
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
prepend(Module.new do
|
|
119
|
+
def preloader_for(reflection, owners)
|
|
120
|
+
return super unless reflection.is_a?(ActiveRecord::Reflection::SpatialReflection)
|
|
121
|
+
return AlreadyLoaded if owners.first.association(reflection.name).loaded?
|
|
122
|
+
|
|
123
|
+
reflection.check_preloadable!
|
|
124
|
+
|
|
125
|
+
SpatialAssociation
|
|
126
|
+
end
|
|
127
|
+
end)
|
|
128
|
+
end
|
|
87
129
|
end
|
|
88
130
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
|
|
2
3
|
module ActiveRecord
|
|
3
4
|
module Associations #:nodoc:
|
|
@@ -13,7 +14,7 @@ module ActiveRecord
|
|
|
13
14
|
:inverse_of
|
|
14
15
|
].freeze
|
|
15
16
|
|
|
16
|
-
def macro
|
|
17
|
+
def self.macro
|
|
17
18
|
SPATIAL_MACRO
|
|
18
19
|
end
|
|
19
20
|
|
|
@@ -21,24 +22,6 @@ module ActiveRecord
|
|
|
21
22
|
super + VALID_SPATIAL_OPTIONS - INVALID_SPATIAL_OPTIONS
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
|
-
|
|
25
|
-
class Preloader #:nodoc:
|
|
26
|
-
class SpatialAssociation < HasMany #:nodoc:
|
|
27
|
-
SPATIAL_FIELD_ALIAS = '__spatial_ids__'.freeze
|
|
28
|
-
SPATIAL_JOIN_NAME = '__spatial_ids_join__'.freeze
|
|
29
|
-
SPATIAL_JOIN_QUOTED_NAME = %{"#{SPATIAL_JOIN_NAME}"}.freeze
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
prepend(Module.new do
|
|
33
|
-
def preloader_for(reflection, *args)
|
|
34
|
-
if reflection.is_a?(ActiveRecord::Reflection::SpatialReflection)
|
|
35
|
-
SpatialAssociation
|
|
36
|
-
else
|
|
37
|
-
super
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end)
|
|
41
|
-
end
|
|
42
25
|
end
|
|
43
26
|
end
|
|
44
27
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Associations
|
|
5
|
+
class SpatialAssociation < HasManyAssociation #:nodoc:
|
|
6
|
+
def association_scope
|
|
7
|
+
return unless klass
|
|
8
|
+
|
|
9
|
+
@association_scope ||= SpatialAssociationScope.scope(self)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def find_target
|
|
15
|
+
scope = self.scope
|
|
16
|
+
return scope.to_a if skip_statement_cache?(scope)
|
|
17
|
+
|
|
18
|
+
conn = klass.connection
|
|
19
|
+
|
|
20
|
+
# Since we're not using binds, we need to disable the scope cache,
|
|
21
|
+
# basically, as otherwise the non-bound parameters we use will cause
|
|
22
|
+
# cache misses that basically ignore subsequent scopes. This would
|
|
23
|
+
# be much better to remove completely, but this will do for now
|
|
24
|
+
# until we can find a better solution.
|
|
25
|
+
reflection.clear_association_scope_cache
|
|
26
|
+
|
|
27
|
+
sc = reflection.association_scope_cache(conn, owner) do |params|
|
|
28
|
+
as = SpatialAssociationScope.create { params.bind }
|
|
29
|
+
target_scope.merge!(as.scope(self))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
binds = SpatialAssociationScope.get_bind_values(owner, reflection.chain)
|
|
33
|
+
sc.execute(binds, conn) { |record| set_inverse_instance(record) } || []
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def association_key_name
|
|
37
|
+
SPATIAL_FIELD_ALIAS
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|