activerecord-spatial 2.0.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|