activerecord-postgis-adapter 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.rdoc CHANGED
@@ -1,3 +1,11 @@
1
+ === 0.3.0 / 2011-01-26
2
+
3
+ * Reworked type and constraint handling, which should result in a large number of bug fixes, especially related to schema dumps.
4
+ * Experimental support for complex spatial queries. (Requires Arel 2.1, which is expected to be released with Rails 3.1.)
5
+ * The path to the Railtie is now different (see the README), though a compatibility wrapper has been left in the old location.
6
+ * Getting index information from the ActiveRecord class now properly recognizes spatial-ness.
7
+ * Reorganized the code a bit for better clarity.
8
+
1
9
  === 0.2.3 / 2011-01-06
2
10
 
3
11
  * Many of ActiveRecord's rake tasks weren't working because they need to know about every adapter explicitly. I hesitate to call this "fixed" since I see it as a problem in ActiveRecord, but we now at least have a workaround so the rake tasks will run properly. (Reported by Tad Thorley.)
data/README.rdoc CHANGED
@@ -7,7 +7,9 @@ the {RGeo}[http://github.com/dazuma/rgeo] library to represent spatial
7
7
  data in Ruby. Like the standard postgresql adapter, this adapter requires
8
8
  the pg gem.
9
9
 
10
- === What This Adapter Provides
10
+ == What This Adapter Provides
11
+
12
+ === Spatial Migrations
11
13
 
12
14
  First, this adapter extends the migration syntax to support creating
13
15
  spatial columns and indexes. To create a spatial column, use the
@@ -19,24 +21,29 @@ creating the index.
19
21
 
20
22
  Examples:
21
23
 
22
- create_table :spatial_table do |t|
23
- t.column :shape, :geometry # or t.geometry :shape
24
- t.line_string :path
25
- t.column :latlon, :point, :geographic => true
26
- end
27
- change_table :spatial_table do |t|
28
- t.index :latlon, :spatial => true
29
- end
24
+ create_table :my_spatial_table do |t|
25
+ t.column :shape, :geometry # or t.geometry :shape
26
+ t.line_string :path, :srid => 3785
27
+ t.point :latlon, :geographic => true
28
+ end
29
+ change_table :my_spatial_table do |t|
30
+ t.index :latlon, :spatial => true
31
+ end
32
+
33
+ === Spatial Attributes
30
34
 
31
35
  When this adapter is in use, spatial attributes in your \ActiveRecord
32
36
  objects will have RGeo geometry values. You can set spatial attributes
33
37
  either to RGeo geometry objects, or to strings in WKT (well-known text)
34
38
  format, which the adapter will automatically convert to geometry objects.
35
39
 
36
- To specify the RGeo geometry factory, you can either set an explicit
37
- factory for a column, or provide a factory generator that will yield the
38
- appropriate factory for the table's spatial columns based on their types.
39
- For the former, call the set_rgeo_factory_for_column class method on your
40
+ Spatial objects in RGeo are tied to a factory that specifies the
41
+ coordinate system as well as other behaviors of the object. You must
42
+ therefore specify a factory for each spatial column (attribute) in your
43
+ ActiveRecord class. You can either set an explicit factory for a specific
44
+ column, or provide a factory generator that will yield the appropriate
45
+ factory for the table's spatial columns based on their types. For the
46
+ former, call the <tt>set_rgeo_factory_for_column</tt> class method on your
40
47
  \ActiveRecord class. For the latter, set the rgeo_factory_generator class
41
48
  attribute. This generator should understand the usual <tt>:srid</tt>,
42
49
  <tt>:has_z_coordinate</tt>, and <tt>:has_m_coordinate</tt> options. It
@@ -47,40 +54,45 @@ the "rgeo-activerecord" gem.
47
54
 
48
55
  Examples, given the spatial table defined above:
49
56
 
50
- class SpatialTable < ActiveRecord::Base
51
-
52
- # By default, use the GEOS implementation for spatial columns.
53
- self.rgeo_factory_generator = RGeo::Geos.method(:factory)
54
-
55
- # But use a geographic implementation for the :latlon column.
56
- set_rgeo_factory_for_column(:latlon, RGeo::Geographic.spherical_factory)
57
-
58
- end
57
+ class MySpatialTable < ActiveRecord::Base
58
+
59
+ # By default, use the GEOS implementation for spatial columns.
60
+ self.rgeo_factory_generator = RGeo::Geos.factory_generator
61
+
62
+ # But use a geographic implementation for the :latlon column.
63
+ set_rgeo_factory_for_column(:latlon, RGeo::Geographic.spherical_factory)
64
+
65
+ end
59
66
 
60
67
  Now you can interact with the data using the RGeo types:
61
68
 
62
- rec = SpatialTable.new
63
- rec.latlon = 'POINT(-122 47)' # You can set by feature object or WKT.
64
- loc = rec.latlon # Accessing always returns a feature object, in
65
- # this case, a geographic that understands latitude.
66
- loc.latitude # => 47
67
- rec.shape = loc # the factory for the :shape column is GEOS, so the
68
- # value will be cast from geographic to GEOS.
69
- RGeo::Geos.is_geos?(rec.shape) # => true
69
+ rec = MySpatialTable.new
70
+ rec.latlon = 'POINT(-122 47)' # You can set by feature object or WKT.
71
+ loc = rec.latlon # Accessing always returns a feature object, in
72
+ # this case, a geographic that understands latitude.
73
+ loc.latitude # => 47
74
+ rec.shape = loc # the factory for the :shape column is GEOS, so the
75
+ # value will be cast from geographic to GEOS.
76
+ RGeo::Geos.is_geos?(rec.shape) # => true
77
+
78
+ === Spatial Queries
70
79
 
71
80
  You can create simple queries based on spatial equality in the same way
72
81
  you would on a scalar column:
73
82
 
74
- rec = SpatialTable.where(:latlon => RGeo::Geos.factory.point(-122, 47)).first
83
+ rec = MySpatialTable.where(:latlon => RGeo::Geos.factory.point(-122, 47)).first
75
84
 
76
85
  You can also use WKT:
77
86
 
78
- rec = SpatialTable.where(:latlon => 'POINT(-122 47)').first
87
+ rec = MySpatialTable.where(:latlon => 'POINT(-122 47)').first
79
88
 
80
- Other types of queries (e.g. radius searches) currently must be written
81
- in SQL. We are investigating writing a set of Arel extensions for
82
- constructing arbitrary spatial queries. However, these extensions will
83
- probably require Rails 3.1.
89
+ The adapter also provides experimental support for more complex queries
90
+ such as radius searches. However, these extensions require Arel 2.1
91
+ (which is scheduled for release with Rails 3.1). We do not have these
92
+ documented yet, and the syntax is subject to change. For now, you should
93
+ write more complex queries in SQL.
94
+
95
+ == Installation And Configuration
84
96
 
85
97
  === Installing The Adapter Gem
86
98
 
@@ -90,73 +102,115 @@ This adapter has the following requirements:
90
102
  * PostGIS 1.5 or later.
91
103
  * \ActiveRecord 3.0.3 or later. Earlier versions will not work.
92
104
  * rgeo gem 0.2.4 or later.
93
- * rgeo-activerecord gem 0.2.2 or later.
105
+ * rgeo-activerecord gem 0.3.0 or later.
94
106
  * pg gem 0.10 or later.
95
107
 
96
108
  Install this adapter as a gem:
97
109
 
98
- gem install activerecord-postgis-adapter
110
+ gem install activerecord-postgis-adapter
99
111
 
100
112
  See the README for the "rgeo" gem, a required dependency, for further
101
113
  installation information.
102
114
 
103
- === Setting Up The Adapter
115
+ === Basic Setup
104
116
 
105
117
  To use this adapter, add this gem, "activerecord-postgis-adapter", to
106
118
  your Gemfile, and then request the adapter name "postgis" in your
107
119
  database connection configuration (which, for a Rails application, is in
108
- the config/database.yml file).
120
+ the config/database.yml file). Most of the rest of the configuration
121
+ parameters are identical to those used by the stock "postgresql" adapter,
122
+ so you can create a new Rails application using:
123
+
124
+ rails new my_app --database=postgresql
109
125
 
110
- Furthermore, the PostGIS adapter includes a special railtie that provides
126
+ ...and then just change the adapter name to "postgis".
127
+
128
+ Next, the PostGIS adapter includes a special railtie that provides
111
129
  support for PostGIS databases in ActiveRecord's rake tasks. This railtie
112
130
  is required in order to run, e.g., rake test. To install this railtie,
113
131
  you should add this line to your config/application.rb:
114
132
 
115
- require 'rgeo/active_record/postgis_adapter/railtie'
133
+ require 'active_record/connection_adapters/postgis_adapter/railtie'
116
134
 
117
135
  Note that this railtie must load after the ActiveRecord railtie. That is,
118
136
  the above require command should appear after <tt>require 'rails/all'</tt>.
119
137
 
120
138
  Besides the adapter name "postgis", most of the other database connection
121
139
  configuration parameters are the same as for the stock postgresql adapter.
122
- However, there are a few special parameters you may want to set.
123
-
124
- The <i>script_dir</i> parameter provides the path to the directory
125
- containing the SQL scripts for PostGIS installation. i.e. this could be
126
- <tt>/usr/local/share/contrib/postgis-1.5</tt>. This directory should
127
- contain the files <tt>postgis.sql</tt> and <tt>spatial_ref_sys.sql</tt>.
128
- It is used by the db:create rake task to add the PostGIS types, functions,
129
- and tables to a newly created database. If you do not provide this
130
- parameter, you will need to add these objects to the database manually.
140
+ However, there are a couple minor differences:
141
+
142
+ The <i>script_dir</i> parameter is specific to the PostGIS adapter, and
143
+ provides the path to the directory containing the SQL scripts for PostGIS
144
+ installation. This directory should contain the files <tt>postgis.sql</tt>
145
+ and <tt>spatial_ref_sys.sql</tt>. (A common setting for this directory
146
+ might be <tt>/usr/local/share/contrib/postgis-1.5</tt>.) It is used by the
147
+ db:create rake task to add the PostGIS types, functions, and tables to a
148
+ newly created database. If you do not provide this parameter, you will
149
+ need to add these objects to the database manually.
131
150
  Generally, therefore, this parameter is required at least for the test
132
151
  database, which is usually automatically created by the rake tasks.
133
152
 
134
- Because PostGIS adds many objects to the database, you may encounter a few
135
- issues when running Rails rake tasks that manage databases. Specifically:
136
-
137
- * Dumping the structure as sql (rake db:structure:dump) may include all
138
- the PostGIS definitions as well, cluttering your SQL dump.
139
- * Rake tasks that automatically create the test database (e.g. rake test)
140
- may emit a number of errors because PostGIS definitions are being added
141
- twice to the database: once on creation, and again because they are
142
- showing up in the SQL dump that ActiveRecord uses to copy the schema.
143
-
144
- Because of these issues, it is recommended that you include the schema
145
- name "postgis" in the <i>schema_search_path</i> parameter. The PostGIS
146
- adapter treats this schema name as special, in that:
153
+ If the schema name "postgis" is included in the <i>schema_search_path</i>
154
+ parameter, the PostGIS adapter treats it as special, in that:
147
155
 
148
156
  * The db:create rake task will automatically create the "postgis" schema,
149
- and will create all the PostGIS objects in that schema.
157
+ and, if <i>script_dir</i> is set, it will create all the PostGIS objects
158
+ within that schema.
150
159
  * Dumping the structure as sql will omit objects in the "postgis" schema.
160
+ This is often useful if you don't want all the PostGIS definitions
161
+ cluttering up your SQL dump.
162
+
163
+ This can be useful in managing the PostGIS definitions in your database,
164
+ as described below.
165
+
166
+ === Dealing With PostGIS Definitions
151
167
 
152
- As a result, the PostGIS definitions will no longer appear in the SQL
153
- dump file, effectively eliminating the above issues.
168
+ PostGIS adds many objects (types, functions, triggers, meta-information
169
+ tables, and other elements) to a PostgreSQL database. These objects are
170
+ required for PostGIS to do its magic, but they can be a hassle when you
171
+ are managing a database using Rails and \ActiveRecord. For example:
172
+
173
+ * Dumping the structure as sql (rake db:structure:dump) may include all
174
+ the PostGIS definitions as well, cluttering your SQL dump.
175
+ * Rake tasks that automatically create the test database (e.g. rake test)
176
+ may fail or emit a number of errors, because PostGIS definitions are
177
+ either missing from or being added twice to the test database.
178
+
179
+ To deal with these issues, we recommend the following technique:
180
+
181
+ * Set <i>script_dir</i> in both your development and test database
182
+ configurations. This will cause the PostGIS Adapter to automatically
183
+ add the PostGIS definitions and spatial references to your databases
184
+ when rake db:create is run.
185
+ * Include "postgis" in your <i>schema_search_path</i> for both your
186
+ development and test databases. It is recommended that you include it
187
+ as the <i>last</i> element, so that your application's tables don't get
188
+ added to it by default. For example:
189
+ schema_search_path: public,postgis
190
+ The PostGIS Adapter responds to this special name by sandboxing the
191
+ PostGIS definitions into it when rake db:create is run, and it omits
192
+ this schema when running SQL structure dumps.
193
+
194
+ Finally, you generally should _not_ set the \ActiveRecord schema format
195
+ to <tt>:sql</tt>. You should leave it set to <tt>:ruby</tt>. The reason
196
+ is that SQL structure dumps do not currently properly emit the correct
197
+ <tt>AddGeometryColumn</tt> calls to create geometry columns. As a result,
198
+ the <tt>geometry_columns</tt> table will not be properly populated, among
199
+ other issues. Instead, the schema.rb output by the Ruby schema format
200
+ should properly replicate the schema. This is a known issue that we are
201
+ investigating.
202
+
203
+ == Additional Information
154
204
 
155
205
  === Known bugs and limitations
156
206
 
157
- A few minor holes still exist in this adapter. Notably, using \ActiveRecord
158
- to list indexes doesn't properly identify GiST (spatial) indexes as such.
159
- This adapter is also not yet well tested.
207
+ * Dumping as SQL (i.e. rake db:structure:dump) does not properly emit
208
+ <tt>AddGeometryColumn</tt> calls, and so does not completely create the
209
+ spatial schema (e.g. it fails to add the proper row to the
210
+ <tt>geometry_columns</tt> table.) Because of this, you should not depend
211
+ on a SQL dump to be an accurate representation of the schema. (That is,
212
+ you should not set <tt>config.active_record.schema_format</tt> to
213
+ <tt>:sql</tt>.)
160
214
 
161
215
  === Development and support
162
216
 
@@ -172,7 +226,8 @@ Contact the author at dazuma at gmail dot com.
172
226
 
173
227
  === Acknowledgments
174
228
 
175
- RGeo is written by Daniel Azuma (http://www.daniel-azuma.com).
229
+ The PostGIS Adapter and its supporting libraries (including RGeo) are
230
+ written by Daniel Azuma (http://www.daniel-azuma.com).
176
231
 
177
232
  Development of RGeo is sponsored by GeoPage, Inc. (http://www.geopage.com).
178
233
 
data/Version CHANGED
@@ -1 +1 @@
1
- 0.2.3
1
+ 0.3.0
@@ -38,33 +38,6 @@ require 'rgeo/active_record'
38
38
  require 'active_record/connection_adapters/postgresql_adapter'
39
39
 
40
40
 
41
- # :stopdoc:
42
-
43
- module Arel
44
- module Visitors
45
-
46
- class PostGIS < PostgreSQL
47
-
48
- FUNC_MAP = {
49
- 'ST_WKTToSQL' => 'GeomFromEWKT',
50
- }
51
-
52
- include ::RGeo::ActiveRecord::SpatialToSql
53
-
54
- def st_func(standard_name_)
55
- FUNC_MAP[standard_name_] || standard_name_
56
- end
57
-
58
- end
59
-
60
- VISITORS['postgis'] = ::Arel::Visitors::PostGIS
61
-
62
- end
63
- end
64
-
65
- # :startdoc:
66
-
67
-
68
41
  # The activerecord-postgis-adapter gem installs the *postgis*
69
42
  # connection adapter into ActiveRecord.
70
43
 
@@ -95,376 +68,37 @@ module ActiveRecord
95
68
 
96
69
  # The postgres drivers don't allow the creation of an unconnected PGconn object,
97
70
  # so just pass a nil connection object for the time being.
98
- ConnectionAdapters::PostgisAdapter.new(nil, logger, [host_, port_, nil, nil, database_, username_, password_], config_)
71
+ ::ActiveRecord::ConnectionAdapters::PostGISAdapter::MainAdapter.new(nil, logger, [host_, port_, nil, nil, database_, username_, password_], config_)
99
72
  end
100
73
 
101
74
 
102
75
  end
103
76
 
104
77
 
105
- module ConnectionAdapters # :nodoc:
78
+ # All ActiveRecord adapters go in this namespace.
79
+ module ConnectionAdapters
106
80
 
107
-
108
- class PostgisAdapter < PostgreSQLAdapter # :nodoc:
109
-
81
+ # The PostGIS Adapter
82
+ module PostGISAdapter
110
83
 
84
+ # The name returned by the adapter_name method of this adapter.
111
85
  ADAPTER_NAME = 'PostGIS'.freeze
112
86
 
113
- @@native_database_types = nil
114
-
115
-
116
- def native_database_types
117
- @@native_database_types ||= super.merge(:geometry => {:name => "geometry"}, :point => {:name => "point"}, :line_string => {:name => "linestring"}, :polygon => {:name => "polygon"}, :geometry_collection => {:name => "geometrycollection"}, :multi_point => {:name => "multipoint"}, :multi_line_string => {:name => "multilinestring"}, :multi_polygon => {:name => "multipolygon"})
118
- end
119
-
120
-
121
- def adapter_name
122
- ADAPTER_NAME
123
- end
124
-
125
-
126
- def postgis_lib_version
127
- unless defined?(@postgis_lib_version)
128
- @postgis_lib_version = select_value("SELECT PostGIS_Lib_Version()") rescue nil
129
- end
130
- @postgis_lib_version
131
- end
132
-
133
-
134
- def srs_database_columns
135
- {:srtext_column => 'srtext', :proj4text_column => 'proj4text', :auth_name_column => 'auth_name', :auth_srid_column => 'auth_srid'}
136
- end
137
-
138
-
139
- def quote(value_, column_=nil)
140
- if ::RGeo::Feature::Geometry.check_type(value_)
141
- "'#{::RGeo::WKRep::WKBGenerator.new(:hex_format => true, :type_format => :ewkb, :emit_ewkb_srid => true).generate(value_)}'"
142
- else
143
- super
144
- end
145
- end
146
-
147
-
148
- def columns(table_name_, name_=nil) #:nodoc:
149
- table_name_ = table_name_.to_s
150
- spatial_info_ = spatial_column_info(table_name_)
151
- column_definitions(table_name_).collect do |name_, type_, default_, notnull_|
152
- SpatialColumn.new(name_, default_, type_, notnull_ == 'f', type_ =~ /geometry/i ? spatial_info_[name_] : nil)
153
- end
154
- end
155
-
156
-
157
- def create_table(table_name_, options_={})
158
- table_name_ = table_name_.to_s
159
- table_definition_ = SpatialTableDefinition.new(self)
160
- table_definition_.primary_key(options_[:primary_key] || ::ActiveRecord::Base.get_primary_key(table_name_.singularize)) unless options_[:id] == false
161
- yield table_definition_ if block_given?
162
- if options_[:force] && table_exists?(table_name_)
163
- drop_table(table_name_, options_)
164
- end
165
-
166
- create_sql_ = "CREATE#{' TEMPORARY' if options_[:temporary]} TABLE "
167
- create_sql_ << "#{quote_table_name(table_name_)} ("
168
- create_sql_ << table_definition_.to_sql
169
- create_sql_ << ") #{options_[:options]}"
170
- execute create_sql_
171
-
172
- table_definition_.non_geographic_spatial_columns.each do |col_|
173
- type_ = col_.type.to_s.gsub('_', '').upcase
174
- has_z_ = col_.has_z?
175
- has_m_ = col_.has_m?
176
- type_ = "#{type_}M" if has_m_ && !has_z_
177
- dimensions_ = 2
178
- dimensions_ += 1 if has_z_
179
- dimensions_ += 1 if has_m_
180
- execute("SELECT AddGeometryColumn('#{quote_string(table_name_)}', '#{quote_string(col_.name.to_s)}', #{col_.srid}, '#{quote_string(type_)}', #{dimensions_})")
181
- end
182
- end
183
-
184
-
185
- def drop_table(table_name_, options_={})
186
- execute("DELETE from geometry_columns where f_table_name='#{quote_string(table_name_.to_s)}'")
187
- super
188
- end
189
-
190
-
191
- def add_column(table_name_, column_name_, type_, options_={})
192
- table_name_ = table_name_.to_s
193
- if ::RGeo::ActiveRecord::GEOMETRY_TYPES.include?(type_.to_sym)
194
- type_ = type_.to_s.gsub('_', '').upcase
195
- has_z_ = options_[:has_z]
196
- has_m_ = options_[:has_m]
197
- srid_ = (options_[:srid] || 4326).to_i
198
- if options_[:geographic]
199
- type_ << 'Z' if has_z_
200
- type_ << 'M' if has_m_
201
- execute("ALTER TABLE #{quote_table_name(table_name_)} ADD COLUMN #{quote_column_name(column_name_)} GEOGRAPHY(#{type_},#{srid_})")
202
- change_column_default(table_name_, column_name_, options_[:default]) if options_include_default?(options_)
203
- change_column_null(table_name_, column_name_, false, options_[:default]) if options_[:null] == false
204
- else
205
- type_ = "#{type_}M" if has_m_ && !has_z_
206
- dimensions_ = 2
207
- dimensions_ += 1 if has_z_
208
- dimensions_ += 1 if has_m_
209
- execute("SELECT AddGeometryColumn('#{quote_string(table_name_)}', '#{quote_string(column_name_.to_s)}', #{srid_}, '#{quote_string(type_)}', #{dimensions_})")
210
- end
211
- else
212
- super
213
- end
214
- end
215
-
216
-
217
- def remove_column(table_name_, *column_names_)
218
- column_names_ = column_names_.flatten.map{ |n_| n_.to_s }
219
- spatial_info_ = spatial_column_info(table_name_)
220
- remaining_column_names_ = []
221
- column_names_.each do |name_|
222
- if spatial_info_.include?(name_)
223
- execute("SELECT DropGeometryColumn('#{quote_string(table_name_.to_s)}','#{quote_string(name_)}')")
224
- else
225
- remaining_column_names_ << name_.to_sym
226
- end
227
- end
228
- if remaining_column_names_.size > 0
229
- super(table_name_, *remaining_column_names_)
230
- end
231
- end
232
-
233
-
234
- def add_index(table_name_, column_name_, options_={})
235
- table_name_ = table_name_.to_s
236
- column_names_ = ::Array.wrap(column_name_)
237
- index_name_ = index_name(table_name_, :column => column_names_)
238
- gist_clause_ = ''
239
- index_type_ = ''
240
- if ::Hash === options_ # legacy support, since this param was a string
241
- index_type_ = 'UNIQUE' if options_[:unique]
242
- index_name_ = options_[:name].to_s if options_.key?(:name)
243
- gist_clause_ = 'USING GIST' if options_[:spatial]
244
- else
245
- index_type_ = options_
246
- end
247
- if index_name_.length > index_name_length
248
- raise ::ArgumentError, "Index name '#{index_name_}' on table '#{table_name_}' is too long; the limit is #{index_name_length} characters"
249
- end
250
- if index_name_exists?(table_name_, index_name_, false)
251
- raise ::ArgumentError, "Index name '#{index_name_}' on table '#{table_name_}' already exists"
252
- end
253
- quoted_column_names_ = quoted_columns_for_index(column_names_, options_).join(", ")
254
- execute "CREATE #{index_type_} INDEX #{quote_column_name(index_name_)} ON #{quote_table_name(table_name_)} #{gist_clause_} (#{quoted_column_names_})"
255
- end
256
-
257
-
258
- def spatial_column_info(table_name_)
259
- info_ = query("SELECT * FROM geometry_columns WHERE f_table_name='#{quote_string(table_name_.to_s)}'")
260
- result_ = {}
261
- info_.each do |row_|
262
- name_ = row_[3]
263
- type_ = row_[6]
264
- dimension_ = row_[4].to_i
265
- has_m_ = type_ =~ /m$/i ? true : false
266
- type_.sub!(/m$/, '')
267
- has_z_ = dimension_ > 3 || dimension_ == 3 && !has_m_
268
- result_[name_] = {
269
- :name => name_,
270
- :type => type_,
271
- :dimension => dimension_,
272
- :srid => row_[5].to_i,
273
- :has_z => has_z_,
274
- :has_m => has_m_,
275
- }
276
- end
277
- result_
278
- end
279
-
280
-
281
- class SpatialTableDefinition < ConnectionAdapters::TableDefinition # :nodoc:
282
-
283
- attr_reader :spatial_columns
284
-
285
- def initialize(base_)
286
- super
287
- end
288
-
289
- def column(name_, type_, options_={})
290
- super
291
- col_ = self[name_]
292
- if ::RGeo::ActiveRecord::GEOMETRY_TYPES.include?(col_.type.to_sym)
293
- col_.extend(GeometricColumnDefinitionMethods) unless col_.respond_to?(:geographic?)
294
- col_.set_geographic(options_[:geographic])
295
- col_.set_srid((options_[:srid] || 4326).to_i)
296
- col_.set_has_z(options_[:has_z])
297
- col_.set_has_m(options_[:has_m])
298
- end
299
- self
300
- end
301
-
302
- def to_sql
303
- @columns.find_all{ |c_| !c_.respond_to?(:geographic?) || c_.geographic? }.map{ |c_| c_.to_sql } * ', '
304
- end
305
-
306
- def non_geographic_spatial_columns
307
- @columns.find_all{ |c_| c_.respond_to?(:geographic?) && !c_.geographic? }
308
- end
309
-
310
- end
311
-
312
-
313
- module GeometricColumnDefinitionMethods # :nodoc:
314
-
315
- def geographic?
316
- defined?(@geographic) && @geographic
317
- end
318
-
319
- def srid
320
- defined?(@srid) ? @srid : 4326
321
- end
322
-
323
- def has_z?
324
- defined?(@has_z) && @has_z
325
- end
326
-
327
- def has_m?
328
- defined?(@has_m) && @has_m
329
- end
330
-
331
- def set_geographic(value_)
332
- @geographic = value_ ? true : false
333
- end
334
-
335
- def set_srid(value_)
336
- @srid = value_
337
- end
338
-
339
- def set_has_z(value_)
340
- @has_z = value_ ? true : false
341
- end
342
-
343
- def set_has_m(value_)
344
- @has_m = value_ ? true : false
345
- end
346
-
347
- def sql_type
348
- type_ = type.to_s.upcase.gsub('_', '')
349
- type_ << 'Z' if has_z?
350
- type_ << 'M' if has_m?
351
- "GEOGRAPHY(#{type_},#{srid})"
352
- end
353
-
354
- end
355
-
356
-
357
- class SpatialColumn < ConnectionAdapters::PostgreSQLColumn # :nodoc:
358
-
359
-
360
- def initialize(name_, default_, sql_type_=nil, null_=true, opts_=nil)
361
- super(name_, default_, sql_type_, null_)
362
- @geographic = sql_type_ =~ /^geography/ ? true : false
363
- if opts_
364
- @geometric_type = ::RGeo::ActiveRecord.geometric_type_from_name(opts_[:type])
365
- @srid = opts_[:srid].to_i
366
- @has_z = opts_[:has_z]
367
- @has_m = opts_[:has_m]
368
- elsif @geographic
369
- if sql_type_ =~ /geography\((\w+[^,zm])(z?)(m?),(\d+)\)/i
370
- @has_z = $2.length > 0
371
- @has_m = $3.length > 0
372
- @srid = $4.to_i
373
- @geometric_type = ::RGeo::ActiveRecord.geometric_type_from_name($1)
374
- else
375
- @geometric_type = ::RGeo::Feature::Geometry
376
- @srid = 4326
377
- @has_z = @has_m = false
378
- end
379
- else
380
- @geometric_type = @has_z = @has_m = nil
381
- @srid = 0
382
- end
383
- @ar_class = ::ActiveRecord::Base
384
- end
385
-
386
-
387
- def set_ar_class(val_)
388
- @ar_class = val_
389
- end
390
-
391
-
392
- attr_reader :srid
393
- attr_reader :geometric_type
394
- attr_reader :has_z
395
- attr_reader :has_m
396
-
397
-
398
- def spatial?
399
- type == :geometry
400
- end
401
-
402
-
403
- def geographic?
404
- @geographic
405
- end
406
-
407
-
408
- def klass
409
- type == :geometry ? ::RGeo::Feature::Geometry : super
410
- end
411
-
412
-
413
- def type_cast(value_)
414
- type == :geometry ? SpatialColumn.convert_to_geometry(value_, @ar_class, name, @geographic, @srid, @has_z, @has_m) : super
415
- end
416
-
417
-
418
- def type_cast_code(var_name_)
419
- type == :geometry ? "::ActiveRecord::ConnectionAdapters::PostgisAdapter::SpatialColumn.convert_to_geometry(#{var_name_}, self.class, #{name.inspect}, #{@geographic ? 'true' : 'false'}, #{@srid.inspect}, #{@has_z ? 'true' : 'false'}, #{@has_m ? 'true' : 'false'})" : super
420
- end
421
-
422
-
423
- private
424
-
425
-
426
- def simplified_type(sql_type_)
427
- sql_type_ =~ /geography|geometry|point|linestring|polygon/i ? :geometry : super
428
- end
429
-
430
-
431
- def self.convert_to_geometry(input_, ar_class_, column_, geographic_, srid_, has_z_, has_m_)
432
- case input_
433
- when ::RGeo::Feature::Geometry
434
- factory_ = ar_class_.rgeo_factory_for_column(column_, :srid => srid_, :has_z_coordinate => has_z_, :has_m_coordinate => has_m_, :geographic => geographic_)
435
- ::RGeo::Feature.cast(input_, factory_)
436
- when ::String
437
- if input_.length == 0
438
- nil
439
- else
440
- factory_ = ar_class_.rgeo_factory_for_column(column_, :srid => srid_, :has_z_coordinate => has_z_, :has_m_coordinate => has_m_, :geographic => geographic_)
441
- marker_ = input_[0,1]
442
- if marker_ == "\x00" || marker_ == "\x01"
443
- ::RGeo::WKRep::WKBParser.new(factory_, :support_ewkb => true).parse(input_) rescue nil
444
- elsif input_[0,4] =~ /[0-9a-fA-F]{4}/
445
- ::RGeo::WKRep::WKBParser.new(factory_, :support_ewkb => true).parse_hex(input_) rescue nil
446
- else
447
- ::RGeo::WKRep::WKTParser.new(factory_, :support_ewkt => true).parse(input_) rescue nil
448
- end
449
- end
450
- else
451
- nil
452
- end
453
- end
454
-
455
-
456
- end
457
-
458
-
459
87
  end
460
88
 
461
-
462
89
  end
463
90
 
464
91
 
465
92
  end
466
93
 
467
94
 
95
+ require 'active_record/connection_adapters/postgis_adapter/version.rb'
96
+ require 'active_record/connection_adapters/postgis_adapter/main_adapter.rb'
97
+ require 'active_record/connection_adapters/postgis_adapter/spatial_table_definition.rb'
98
+ require 'active_record/connection_adapters/postgis_adapter/spatial_column.rb'
99
+ require 'active_record/connection_adapters/postgis_adapter/arel_tosql.rb'
100
+
101
+
468
102
  ignore_tables_ = ::ActiveRecord::SchemaDumper.ignore_tables
469
103
  ignore_tables_ << 'geometry_columns' unless ignore_tables_.include?('geometry_columns')
470
104
  ignore_tables_ << 'spatial_ref_sys' unless ignore_tables_.include?('spatial_ref_sys')