activerecord-mysqlspatial-adapter 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,10 @@
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
+ * Reorganized the code a bit for better clarity.
7
+
1
8
  === 0.2.1 / 2010-12-27
2
9
 
3
10
  * Support for basic spatial equality queries. e.g. constructs such as:
@@ -7,13 +7,9 @@ using the {RGeo}[http://github.com/dazuma/rgeo] library to represent
7
7
  spatial data in Ruby. Like the standard mysql adapter, this adapter
8
8
  requires the mysql gem.
9
9
 
10
- === Usage
10
+ == What This Adapter Provides
11
11
 
12
- To use this adapter, add this gem, "activerecord-mysqlspatial-adapter",
13
- to your Gemfile, and then request the adapter name "mysqlspatial" in
14
- your database connection configuration (which, for a Rails application,
15
- is in the config/database.yml file). The other database connection
16
- configuration parameters are the same as for the stock mysql adapter.
12
+ === Spatial Migrations
17
13
 
18
14
  First, this adapter extends the migration syntax to support creating
19
15
  spatial columns and indexes. To create a spatial column, use the
@@ -25,24 +21,29 @@ column may need to be NOT NULL.
25
21
 
26
22
  Examples:
27
23
 
28
- create_table :spatial_table, :options => 'ENGINE=MyISAM' do |t|
29
- t.column :latlon, :point, :null => false
30
- t.line_string :path
31
- t.geometry :shape
32
- end
33
- change_table :spatial_table do |t|
34
- t.index :latlon, :spatial => true
35
- end
24
+ create_table :my_spatial_table, :options => 'ENGINE=MyISAM' do |t|
25
+ t.column :latlon, :point, :null => false
26
+ t.line_string :path
27
+ t.geometry :shape
28
+ end
29
+ change_table :my_spatial_table do |t|
30
+ t.index :latlon, :spatial => true
31
+ end
32
+
33
+ === Spatial Attributes
36
34
 
37
35
  When this adapter is in use, spatial attributes in your \ActiveRecord
38
36
  objects will have RGeo geometry values. You can set spatial attributes
39
37
  either to RGeo geometry objects, or to strings in WKT (well-known text)
40
38
  format, which the adapter will automatically convert to geometry objects.
41
39
 
42
- To specify the RGeo geometry factory, you can either set an explicit
43
- factory for a column, or provide a factory generator that will yield the
44
- appropriate factory for the table's spatial columns based on the value.
45
- 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
46
47
  \ActiveRecord class. For the latter, set the rgeo_factory_generator class
47
48
  attribute. This generator should understand at least the <tt>:srid</tt>
48
49
  options, which will be provided based on the SRID embedded in the value
@@ -53,58 +54,98 @@ the "rgeo-activerecord" gem.
53
54
 
54
55
  Examples, given the spatial table defined above:
55
56
 
56
- class SpatialTable < ActiveRecord::Base
57
-
58
- # By default, use the GEOS implementation for spatial columns.
59
- self.rgeo_factory_generator = RGeo::Geos.method(:factory)
60
-
61
- # But use a geographic implementation for the :latlon column.
62
- set_rgeo_factory_for_column(:latlon, RGeo::Geographic.spherical_factory)
63
-
64
- end
57
+ class MySpatialTable < ActiveRecord::Base
58
+
59
+ # By default, use the GEOS implementation for spatial columns.
60
+ self.rgeo_factory_generator = RGeo::Geos.method(:factory)
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
65
66
 
66
67
  Now you can interact with the data using the RGeo types:
67
68
 
68
- rec = SpatialTable.new
69
- rec.latlon = 'POINT(-122 47)' # You can set by feature object or WKT.
70
- loc = rec.latlon # Accessing always returns a feature object, in
71
- # this case, a geographic that understands latitude.
72
- loc.latitude # => 47
73
- rec.shape = loc # the factory for the :shape column is GEOS, so the
74
- # value will be cast from geographic to GEOS.
75
- 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
76
79
 
77
80
  You can create simple queries based on spatial equality in the same way
78
81
  you would on a scalar column:
79
82
 
80
- rec = SpatialTable.where(:latlon => RGeo::Geos.factory.point(-122, 47)).first
83
+ rec = MySpatialTable.where(:latlon => RGeo::Geos.factory.point(-122, 47)).first
81
84
 
82
85
  You can also use WKT:
83
86
 
84
- rec = SpatialTable.where(:latlon => 'POINT(-122 47)').first
87
+ rec = MySpatialTable.where(:latlon => 'POINT(-122 47)').first
88
+
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.
85
94
 
86
- Other types of queries currently must be written in SQL. However, we are
87
- investigating writing a set of Arel extensions for constructing arbitrary
88
- spatial queries.
95
+ == Installation And Configuration
89
96
 
90
- === Installation
97
+ === Installing The Adapter Gem
91
98
 
92
99
  This adapter has the following requirements:
93
100
 
94
101
  * Ruby 1.8.7 or later. Ruby 1.9.2 or later preferred.
95
102
  * MySQL server 5.0 or later required for spatial extensions.
96
103
  * \ActiveRecord 3.0.3 or later. Earlier versions will not work.
97
- * rgeo gem 0.2.3 or later.
98
- * rgeo-activerecord gem 0.2.1 or later.
104
+ * rgeo gem 0.2.4 or later.
105
+ * rgeo-activerecord gem 0.3.0 or later.
99
106
  * mysql gem 2.8 or later.
100
107
 
101
108
  Install this adapter as a gem:
102
109
 
103
- gem install activerecord-mysqlspatial-adapter
110
+ gem install activerecord-mysqlspatial-adapter
104
111
 
105
112
  See the README for the "rgeo" gem, a required dependency, for further
106
113
  installation information.
107
114
 
115
+ === Basic Setup
116
+
117
+ To use this adapter, add this gem, "activerecord-mysqlspatial-adapter",
118
+ to your Gemfile, and then request the adapter name "mysqlspatial" in
119
+ your database connection configuration (which, for a Rails application,
120
+ is in the config/database.yml file). The other database connection
121
+ configuration parameters are the same as for the stock mysql adapter,
122
+ so you can create a new Rails application using:
123
+
124
+ rails new my_app --database=mysql
125
+
126
+ ...and then just change the adapter name to "mysqlspatial".
127
+
128
+ == Additional Information
129
+
130
+ === Known bugs and limitations
131
+
132
+ This adapter is not yet well tested. There are probably some bugs and
133
+ holes in the functionality. We aren't using MySQL spatial extensions in
134
+ production at GeoPage, so we would appreciate testing help and feedback
135
+ from anyone who is.
136
+
137
+ One known issue is that if you want to use Rails's testing rake tasks and
138
+ you have spatial indexes in your schema, you should use the <tt>:sql</tt>
139
+ dump style. e.g. set <tt>config.active_record.schema_format = :sql</tt>.
140
+ The reason is that Rails's Ruby-format schema dumper does not preserve
141
+ the :options used to create the table, specifically setting the
142
+ Engine=MyISAM. Under MySQL Spatial, you may create spatial indexes only
143
+ on MyISAM tables; unfortunately, if you use the Ruby-format schema to
144
+ create your test databases, Rails does not transfer this information
145
+ properly, and your test tables are created as InnoDB. The workaround is
146
+ to use the <tt>:sql</tt> dump format. This has been reported as a bug in
147
+ Rails as of Rails 3.0.3, so we hope it will get rectified at some point.
148
+
108
149
  === Development and support
109
150
 
110
151
  Documentation is available at http://virtuoso.rubyforge.org/activerecord-mysqlspatial-adapter/README_rdoc.html
@@ -119,7 +160,8 @@ Contact the author at dazuma at gmail dot com.
119
160
 
120
161
  === Acknowledgments
121
162
 
122
- RGeo is written by Daniel Azuma (http://www.daniel-azuma.com).
163
+ The MysqlSpatial Adapter and its supporting libraries (including RGeo) are
164
+ written by Daniel Azuma (http://www.daniel-azuma.com).
123
165
 
124
166
  Development of RGeo is sponsored by GeoPage, Inc. (http://www.geopage.com).
125
167
 
data/Version CHANGED
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.3.0
@@ -38,34 +38,6 @@ require 'rgeo/active_record'
38
38
  require 'active_record/connection_adapters/mysql_adapter'
39
39
 
40
40
 
41
- # :stopdoc:
42
-
43
- module Arel
44
- module Visitors
45
-
46
- class MySQLSpatial < MySQL
47
-
48
- FUNC_MAP = {
49
- 'ST_WKTToSQL' => 'GeomFromText',
50
- 'ST_Equals' => 'Equals',
51
- }
52
-
53
- include ::RGeo::ActiveRecord::SpatialToSql
54
-
55
- def st_func(standard_name_)
56
- FUNC_MAP[standard_name_] || standard_name_
57
- end
58
-
59
- end
60
-
61
- VISITORS['mysqlspatial'] = ::Arel::Visitors::MySQLSpatial
62
-
63
- end
64
- end
65
-
66
- # :startdoc:
67
-
68
-
69
41
  # The activerecord-mysqlspatial-adapter gem installs the *mysqlspatial*
70
42
  # connection adapter into ActiveRecord.
71
43
 
@@ -97,155 +69,31 @@ module ActiveRecord
97
69
  default_flags_ = ::Mysql.const_defined?(:CLIENT_MULTI_RESULTS) ? ::Mysql::CLIENT_MULTI_RESULTS : 0
98
70
  default_flags_ |= ::Mysql::CLIENT_FOUND_ROWS if ::Mysql.const_defined?(:CLIENT_FOUND_ROWS)
99
71
  options_ = [config_[:host], config_[:username] ? config_[:username].to_s : 'root', config_[:password].to_s, config_[:database], config_[:port], config_[:socket], default_flags_]
100
- ConnectionAdapters::MysqlSpatialAdapter.new(mysql_, logger, options_, config_)
72
+ ::ActiveRecord::ConnectionAdapters::MysqlSpatialAdapter::MainAdapter.new(mysql_, logger, options_, config_)
101
73
  end
102
74
 
103
75
 
104
76
  end
105
77
 
106
78
 
107
- module ConnectionAdapters # :nodoc:
79
+ # All ActiveRecord adapters go in this namespace.
80
+ module ConnectionAdapters
108
81
 
109
- class MysqlSpatialAdapter < MysqlAdapter # :nodoc:
110
-
82
+ # The MysqlSpatial adapter
83
+ module MysqlSpatialAdapter
111
84
 
85
+ # The name returned by the adapter_name method of this adapter.
112
86
  ADAPTER_NAME = 'MysqlSpatial'.freeze
113
87
 
114
- NATIVE_DATABASE_TYPES = MysqlAdapter::NATIVE_DATABASE_TYPES.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"})
115
-
116
-
117
- def native_database_types
118
- NATIVE_DATABASE_TYPES
119
- end
120
-
121
-
122
- def adapter_name
123
- ADAPTER_NAME
124
- end
125
-
126
-
127
- def quote(value_, column_=nil)
128
- if ::RGeo::Feature::Geometry.check_type(value_)
129
- "GeomFromWKB(0x#{::RGeo::WKRep::WKBGenerator.new(:hex_format => true).generate(value_)},#{value_.srid})"
130
- else
131
- super
132
- end
133
- end
134
-
135
-
136
- def add_index(table_name_, column_name_, options_={})
137
- if options_[:spatial]
138
- index_name_ = index_name(table_name_, :column => Array(column_name_))
139
- if ::Hash === options_
140
- index_name_ = options_[:name] || index_name_
141
- end
142
- execute "CREATE SPATIAL INDEX #{index_name_} ON #{table_name_} (#{Array(column_name_).join(", ")})"
143
- else
144
- super
145
- end
146
- end
147
-
148
-
149
- def columns(table_name_, name_=nil)
150
- result_ = execute("SHOW FIELDS FROM #{quote_table_name(table_name_)}", :skip_logging)
151
- columns_ = []
152
- result_.each do |field_|
153
- columns_ << SpatialColumn.new(field_[0], field_[4], field_[1], field_[2] == "YES")
154
- end
155
- result_.free
156
- columns_
157
- end
158
-
159
-
160
- def indexes(table_name_, name_=nil)
161
- indexes_ = []
162
- current_index_ = nil
163
- result_ = execute("SHOW KEYS FROM #{quote_table_name(table_name_)}", name_)
164
- result_.each do |row_|
165
- if current_index_ != row_[2]
166
- next if row_[2] == "PRIMARY" # skip the primary key
167
- current_index_ = row_[2]
168
- indexes_ << ::RGeo::ActiveRecord::SpatialIndexDefinition.new(row_[0], row_[2], row_[1] == "0", [], [], row_[10] == 'SPATIAL')
169
- end
170
- indexes_.last.columns << row_[4]
171
- indexes_.last.lengths << row_[7]
172
- end
173
- result_.free
174
- indexes_
175
- end
176
-
177
-
178
- class SpatialColumn < ConnectionAdapters::MysqlColumn # :nodoc:
179
-
180
-
181
- def initialize(name_, default_, sql_type_=nil, null_=true)
182
- super(name_, default_,sql_type_, null_)
183
- @geometric_type = ::RGeo::ActiveRecord.geometric_type_from_name(sql_type_)
184
- @ar_class = ::ActiveRecord::Base
185
- end
186
-
187
-
188
- def set_ar_class(val_)
189
- @ar_class = val_
190
- end
191
-
192
-
193
- attr_reader :geometric_type
194
-
195
-
196
- def spatial?
197
- type == :geometry
198
- end
199
-
200
-
201
- def klass
202
- type == :geometry ? ::RGeo::Feature::Geometry : super
203
- end
204
-
205
-
206
- def type_cast(value_)
207
- type == :geometry ? SpatialColumn.convert_to_geometry(value_, @ar_class, name) : super
208
- end
209
-
210
-
211
- def type_cast_code(var_name_)
212
- type == :geometry ? "::ActiveRecord::ConnectionAdapters::MysqlSpatialAdapter::SpatialColumn.convert_to_geometry(#{var_name_}, self.class, #{name.inspect})" : super
213
- end
214
-
215
-
216
- private
217
-
218
- def simplified_type(sql_type_)
219
- sql_type_ =~ /geometry|point|linestring|polygon/i ? :geometry : super
220
- end
221
-
222
-
223
- def self.convert_to_geometry(input_, ar_class_, column_)
224
- case input_
225
- when ::RGeo::Feature::Geometry
226
- factory_ = ar_class_.rgeo_factory_for_column(column_, :srid => input_.srid)
227
- ::RGeo::Feature.cast(input_, factory_)
228
- when ::String
229
- marker_ = input_[4,1]
230
- if marker_ == "\x00" || marker_ == "\x01"
231
- factory_ = ar_class_.rgeo_factory_for_column(column_, :srid => input_[0,4].unpack(marker_ == "\x01" ? 'V' : 'N').first)
232
- ::RGeo::WKRep::WKBParser.new(factory_).parse(input_[4..-1])
233
- else
234
- factory_ = ar_class_.rgeo_factory_for_column(column_)
235
- ::RGeo::WKRep::WKTParser.new(factory_, :support_ewkt => true).parse(input_)
236
- end
237
- else
238
- nil
239
- end
240
- end
241
-
242
-
243
- end
244
-
245
-
246
88
  end
247
89
 
248
90
  end
249
91
 
250
92
 
251
93
  end
94
+
95
+
96
+ require 'active_record/connection_adapters/mysqlspatial_adapter/version.rb'
97
+ require 'active_record/connection_adapters/mysqlspatial_adapter/main_adapter.rb'
98
+ require 'active_record/connection_adapters/mysqlspatial_adapter/spatial_column.rb'
99
+ require 'active_record/connection_adapters/mysqlspatial_adapter/arel_tosql.rb'
@@ -0,0 +1,69 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # MysqlSpatial adapter for ActiveRecord
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2010 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ # :stopdoc:
38
+
39
+ module Arel
40
+ module Visitors
41
+
42
+ class MySQLSpatial < MySQL
43
+
44
+ FUNC_MAP = {
45
+ 'st_wkttosql' => 'GeomFromText',
46
+ 'st_wkbtosql' => 'GeomFromWKB',
47
+ 'st_length' => 'GLength',
48
+ }
49
+
50
+ include ::RGeo::ActiveRecord::SpatialToSql
51
+
52
+ def st_func(standard_name_)
53
+ if (name_ = FUNC_MAP[standard_name_.downcase])
54
+ name_
55
+ elsif standard_name_ =~ /^st_(\w+)$/i
56
+ $1
57
+ else
58
+ standard_name_
59
+ end
60
+ end
61
+
62
+ end
63
+
64
+ VISITORS['mysqlspatial'] = ::Arel::Visitors::MySQLSpatial
65
+
66
+ end
67
+ end
68
+
69
+ # :startdoc:
@@ -0,0 +1,138 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # MysqlSpatial adapter for ActiveRecord
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2010 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ # :stopdoc:
38
+
39
+ module ActiveRecord
40
+
41
+ module ConnectionAdapters
42
+
43
+ module MysqlSpatialAdapter
44
+
45
+
46
+ class MainAdapter < ConnectionAdapters::MysqlAdapter
47
+
48
+
49
+ NATIVE_DATABASE_TYPES = MysqlAdapter::NATIVE_DATABASE_TYPES.merge(:spatial => {:name => "geometry"})
50
+
51
+
52
+ def adapter_name
53
+ MysqlSpatialAdapter::ADAPTER_NAME
54
+ end
55
+
56
+
57
+ def spatial_column_constructor(name_)
58
+ ::RGeo::ActiveRecord::DEFAULT_SPATIAL_COLUMN_CONSTRUCTORS[name_]
59
+ end
60
+
61
+
62
+ def native_database_types
63
+ NATIVE_DATABASE_TYPES
64
+ end
65
+
66
+
67
+ def quote(value_, column_=nil)
68
+ if ::RGeo::Feature::Geometry.check_type(value_)
69
+ "GeomFromWKB(0x#{::RGeo::WKRep::WKBGenerator.new(:hex_format => true).generate(value_)},#{value_.srid})"
70
+ else
71
+ super
72
+ end
73
+ end
74
+
75
+
76
+ def type_to_sql(type_, limit_=nil, precision_=nil, scale_=nil)
77
+ if (info_ = spatial_column_constructor(type_.to_sym))
78
+ type_ = limit_[:type] || type_ if limit_.is_a?(::Hash)
79
+ type_ = 'geometry' if type_.to_s == 'spatial'
80
+ type_ = type_.to_s.gsub('_', '').upcase
81
+ end
82
+ super(type_, limit_, precision_, scale_)
83
+ end
84
+
85
+
86
+ def add_index(table_name_, column_name_, options_={})
87
+ if options_[:spatial]
88
+ index_name_ = index_name(table_name_, :column => Array(column_name_))
89
+ if ::Hash === options_
90
+ index_name_ = options_[:name] || index_name_
91
+ end
92
+ execute "CREATE SPATIAL INDEX #{index_name_} ON #{table_name_} (#{Array(column_name_).join(", ")})"
93
+ else
94
+ super
95
+ end
96
+ end
97
+
98
+
99
+ def columns(table_name_, name_=nil)
100
+ result_ = execute("SHOW FIELDS FROM #{quote_table_name(table_name_)}", :skip_logging)
101
+ columns_ = []
102
+ result_.each do |field_|
103
+ columns_ << SpatialColumn.new(field_[0], field_[4], field_[1], field_[2] == "YES")
104
+ end
105
+ result_.free
106
+ columns_
107
+ end
108
+
109
+
110
+ def indexes(table_name_, name_=nil)
111
+ indexes_ = []
112
+ current_index_ = nil
113
+ result_ = execute("SHOW KEYS FROM #{quote_table_name(table_name_)}", name_)
114
+ result_.each do |row_|
115
+ if current_index_ != row_[2]
116
+ next if row_[2] == "PRIMARY" # skip the primary key
117
+ current_index_ = row_[2]
118
+ indexes_ << ::RGeo::ActiveRecord::SpatialIndexDefinition.new(row_[0], row_[2], row_[1] == "0", [], [], row_[10] == 'SPATIAL')
119
+ end
120
+ last_index_ = indexes_.last
121
+ last_index_.columns << row_[4]
122
+ last_index_.lengths << row_[7] unless last_index_.spatial
123
+ end
124
+ result_.free
125
+ indexes_
126
+ end
127
+
128
+
129
+ end
130
+
131
+
132
+ end
133
+
134
+ end
135
+
136
+ end
137
+
138
+ # :startdoc:
@@ -0,0 +1,123 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # MysqlSpatial adapter for ActiveRecord
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2010 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ # :stopdoc:
38
+
39
+ module ActiveRecord
40
+
41
+ module ConnectionAdapters
42
+
43
+ module MysqlSpatialAdapter
44
+
45
+
46
+ class SpatialColumn < ConnectionAdapters::MysqlColumn
47
+
48
+
49
+ def initialize(name_, default_, sql_type_=nil, null_=true)
50
+ super(name_, default_, sql_type_, null_)
51
+ @geometric_type = ::RGeo::ActiveRecord.geometric_type_from_name(sql_type_)
52
+ if type == :spatial
53
+ @limit = {:type => @geometric_type.type_name.underscore}
54
+ end
55
+ @ar_class = ::ActiveRecord::Base
56
+ end
57
+
58
+
59
+ def set_ar_class(val_)
60
+ @ar_class = val_
61
+ end
62
+
63
+
64
+ attr_reader :geometric_type
65
+
66
+
67
+ def spatial?
68
+ type == :spatial
69
+ end
70
+
71
+
72
+ def klass
73
+ type == :spatial ? ::RGeo::Feature::Geometry : super
74
+ end
75
+
76
+
77
+ def type_cast(value_)
78
+ type == :spatial ? SpatialColumn.convert_to_geometry(value_, @ar_class, name) : super
79
+ end
80
+
81
+
82
+ def type_cast_code(var_name_)
83
+ type == :spatial ? "::ActiveRecord::ConnectionAdapters::MysqlSpatialAdapter::SpatialColumn.convert_to_geometry(#{var_name_}, self.class, #{name.inspect})" : super
84
+ end
85
+
86
+
87
+ private
88
+
89
+ def simplified_type(sql_type_)
90
+ sql_type_ =~ /geometry|point|linestring|polygon/i ? :spatial : super
91
+ end
92
+
93
+
94
+ def self.convert_to_geometry(input_, ar_class_, column_)
95
+ case input_
96
+ when ::RGeo::Feature::Geometry
97
+ factory_ = ar_class_.rgeo_factory_for_column(column_, :srid => input_.srid)
98
+ ::RGeo::Feature.cast(input_, factory_)
99
+ when ::String
100
+ marker_ = input_[4,1]
101
+ if marker_ == "\x00" || marker_ == "\x01"
102
+ factory_ = ar_class_.rgeo_factory_for_column(column_, :srid => input_[0,4].unpack(marker_ == "\x01" ? 'V' : 'N').first)
103
+ ::RGeo::WKRep::WKBParser.new(factory_).parse(input_[4..-1])
104
+ else
105
+ factory_ = ar_class_.rgeo_factory_for_column(column_)
106
+ ::RGeo::WKRep::WKTParser.new(factory_, :support_ewkt => true).parse(input_)
107
+ end
108
+ else
109
+ nil
110
+ end
111
+ end
112
+
113
+
114
+ end
115
+
116
+
117
+ end
118
+
119
+ end
120
+
121
+ end
122
+
123
+ # :startdoc:
@@ -0,0 +1,62 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # MysqlSpatial adapter for ActiveRecord
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2010 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ begin
38
+ require 'versionomy'
39
+ rescue ::LoadError
40
+ end
41
+
42
+
43
+ module ActiveRecord
44
+
45
+ module ConnectionAdapters
46
+
47
+ module MysqlSpatialAdapter
48
+
49
+
50
+ # Current version of MysqlSpatialAdapter as a frozen string
51
+ VERSION_STRING = ::File.read(::File.dirname(__FILE__)+'/../../../../Version').strip.freeze
52
+
53
+ # Current version of MysqlSpatialAdapter as a Versionomy object, if the
54
+ # Versionomy gem is available; otherwise equal to VERSION_STRING.
55
+ VERSION = defined?(::Versionomy) ? ::Versionomy.parse(VERSION_STRING) : VERSION_STRING
56
+
57
+
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -62,6 +62,11 @@ module RGeo
62
62
  end
63
63
 
64
64
 
65
+ def test_version
66
+ assert_not_nil(::ActiveRecord::ConnectionAdapters::MysqlSpatialAdapter::VERSION)
67
+ end
68
+
69
+
65
70
  def test_create_simple_geometry
66
71
  klass_ = create_ar_class
67
72
  klass_.connection.create_table(:spatial_test) do |t_|
@@ -161,29 +166,33 @@ module RGeo
161
166
  end
162
167
 
163
168
 
164
- def test_query_point
165
- klass_ = populate_ar_class(:latlon_point)
166
- obj_ = klass_.new
167
- obj_.latlon = @factory.point(1, 2)
168
- obj_.save!
169
- id_ = obj_.id
170
- obj2_ = klass_.where(:latlon => @factory.multi_point([@factory.point(1, 2)])).first
171
- assert_equal(id_, obj2_.id)
172
- obj3_ = klass_.where(:latlon => @factory.point(2, 2)).first
173
- assert_nil(obj3_)
169
+ def test_create_simple_geometry_using_shortcut
170
+ klass_ = create_ar_class
171
+ klass_.connection.create_table(:spatial_test) do |t_|
172
+ t_.geometry 'latlon'
173
+ end
174
+ assert_equal(::RGeo::Feature::Geometry, klass_.columns.last.geometric_type)
175
+ assert(klass_.cached_attributes.include?('latlon'))
174
176
  end
175
177
 
176
178
 
177
- def test_query_point_wkt
178
- klass_ = populate_ar_class(:latlon_point)
179
- obj_ = klass_.new
180
- obj_.latlon = @factory.point(1, 2)
181
- obj_.save!
182
- id_ = obj_.id
183
- obj2_ = klass_.where(:latlon => 'POINT(1 2)').first
184
- assert_equal(id_, obj2_.id)
185
- obj3_ = klass_.where(:latlon => 'POINT(2 2)').first
186
- assert_nil(obj3_)
179
+ def test_create_point_geometry_using_shortcut
180
+ klass_ = create_ar_class
181
+ klass_.connection.create_table(:spatial_test) do |t_|
182
+ t_.point 'latlon'
183
+ end
184
+ assert_equal(::RGeo::Feature::Point, klass_.columns.last.geometric_type)
185
+ assert(klass_.cached_attributes.include?('latlon'))
186
+ end
187
+
188
+
189
+ def test_create_geometry_using_limit
190
+ klass_ = create_ar_class
191
+ klass_.connection.create_table(:spatial_test) do |t_|
192
+ t_.spatial 'geom', :limit => {:type => :line_string}
193
+ end
194
+ assert_equal(::RGeo::Feature::LineString, klass_.columns.last.geometric_type)
195
+ assert(klass_.cached_attributes.include?('geom'))
187
196
  end
188
197
 
189
198
 
@@ -0,0 +1,125 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Tests for the MysqlSpatial ActiveRecord adapter
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2010 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+ require 'test/unit'
37
+ require 'rgeo/active_record/adapter_test_helper'
38
+
39
+
40
+ module RGeo
41
+ module ActiveRecord # :nodoc:
42
+ module MysqlSpatialAdapter # :nodoc:
43
+ module Tests # :nodoc:
44
+
45
+ class TestSpatialQueries < ::Test::Unit::TestCase # :nodoc:
46
+
47
+ DATABASE_CONFIG_PATH = ::File.dirname(__FILE__)+'/database.yml'
48
+ include AdapterTestHelper
49
+
50
+ define_test_methods do
51
+
52
+
53
+ def populate_ar_class(content_)
54
+ klass_ = create_ar_class
55
+ case content_
56
+ when :latlon_point
57
+ klass_.connection.create_table(:spatial_test) do |t_|
58
+ t_.column 'latlon', :point
59
+ end
60
+ when :path_linestring
61
+ klass_.connection.create_table(:spatial_test) do |t_|
62
+ t_.column 'path', :line_string
63
+ end
64
+ end
65
+ klass_
66
+ end
67
+
68
+
69
+ def test_query_point
70
+ klass_ = populate_ar_class(:latlon_point)
71
+ obj_ = klass_.new
72
+ obj_.latlon = @factory.point(1, 2)
73
+ obj_.save!
74
+ id_ = obj_.id
75
+ obj2_ = klass_.where(:latlon => @factory.multi_point([@factory.point(1, 2)])).first
76
+ assert_equal(id_, obj2_.id)
77
+ obj3_ = klass_.where(:latlon => @factory.point(2, 2)).first
78
+ assert_nil(obj3_)
79
+ end
80
+
81
+
82
+ def test_query_point_wkt
83
+ klass_ = populate_ar_class(:latlon_point)
84
+ obj_ = klass_.new
85
+ obj_.latlon = @factory.point(1, 2)
86
+ obj_.save!
87
+ id_ = obj_.id
88
+ obj2_ = klass_.where(:latlon => 'POINT(1 2)').first
89
+ assert_equal(id_, obj2_.id)
90
+ obj3_ = klass_.where(:latlon => 'POINT(2 2)').first
91
+ assert_nil(obj3_)
92
+ end
93
+
94
+
95
+ if ::RGeo::ActiveRecord.spatial_expressions_supported?
96
+
97
+
98
+ def test_query_st_length
99
+ klass_ = populate_ar_class(:path_linestring)
100
+ obj_ = klass_.new
101
+ obj_.path = @factory.line(@factory.point(1, 2), @factory.point(3, 2))
102
+ obj_.save!
103
+ id_ = obj_.id
104
+ obj2_ = klass_.where(klass_.arel_table[:path].st_length.eq(2)).first
105
+ assert_equal(id_, obj2_.id)
106
+ obj3_ = klass_.where(klass_.arel_table[:path].st_length.gt(3)).first
107
+ assert_nil(obj3_)
108
+ end
109
+
110
+
111
+ else
112
+
113
+ puts "WARNING: The current Arel does not support named functions. Spatial expression tests skipped."
114
+
115
+ end
116
+
117
+
118
+ end
119
+
120
+ end
121
+
122
+ end
123
+ end
124
+ end
125
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
8
- - 1
9
- version: 0.2.1
7
+ - 3
8
+ - 0
9
+ version: 0.3.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Daniel Azuma
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-12-27 00:00:00 -08:00
17
+ date: 2011-01-26 00:00:00 -08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -23,13 +23,13 @@ dependencies:
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
24
  none: false
25
25
  requirements:
26
- - - ">="
26
+ - - ~>
27
27
  - !ruby/object:Gem::Version
28
28
  segments:
29
29
  - 0
30
- - 2
31
- - 1
32
- version: 0.2.1
30
+ - 3
31
+ - 0
32
+ version: 0.3.0
33
33
  type: :runtime
34
34
  version_requirements: *id001
35
35
  - !ruby/object:Gem::Dependency
@@ -57,10 +57,15 @@ extra_rdoc_files:
57
57
  - History.rdoc
58
58
  - README.rdoc
59
59
  files:
60
+ - lib/active_record/connection_adapters/mysqlspatial_adapter/arel_tosql.rb
61
+ - lib/active_record/connection_adapters/mysqlspatial_adapter/main_adapter.rb
62
+ - lib/active_record/connection_adapters/mysqlspatial_adapter/spatial_column.rb
63
+ - lib/active_record/connection_adapters/mysqlspatial_adapter/version.rb
60
64
  - lib/active_record/connection_adapters/mysqlspatial_adapter.rb
61
65
  - History.rdoc
62
66
  - README.rdoc
63
67
  - test/tc_basic.rb
68
+ - test/tc_spatial_queries.rb
64
69
  - Version
65
70
  has_rdoc: true
66
71
  homepage: http://virtuoso.rubyforge.org/activerecord-mysqlspatial-adapter
@@ -98,3 +103,4 @@ specification_version: 3
98
103
  summary: An ActiveRecord adapter for MySQL Spatial Extensions, based on RGeo and the mysql gem.
99
104
  test_files:
100
105
  - test/tc_basic.rb
106
+ - test/tc_spatial_queries.rb