activerecord-mysql2spatial-adapter 0.2.1 → 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,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:
data/README.rdoc CHANGED
@@ -7,13 +7,9 @@ using the {RGeo}[http://github.com/dazuma/rgeo] library to represent
7
7
  spatial data in Ruby. Like the standard mysql2 adapter, this adapter
8
8
  requires the mysql2 gem.
9
9
 
10
- === Usage
10
+ == What This Adapter Provides
11
11
 
12
- To use this adapter, add this gem, "activerecord-mysql2spatial-adapter",
13
- to your Gemfile, and then request the adapter name "mysql2spatial" 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 mysql2 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
  * mysql2 gem 0.2.6 or later.
100
107
 
101
108
  Install this adapter as a gem:
102
109
 
103
- gem install activerecord-mysql2spatial-adapter
110
+ gem install activerecord-mysql2spatial-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-mysql2spatial-adapter",
118
+ to your Gemfile, and then request the adapter name "mysql2spatial" 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 mysql2 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 "mysql2spatial".
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-mysql2spatial-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 Mysql2Spatial Adapter and its supporting libraries (including RGeo)
164
+ are 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/mysql2_adapter'
39
39
 
40
40
 
41
- # :stopdoc:
42
-
43
- module Arel
44
- module Visitors
45
-
46
- class MySQL2Spatial < 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['mysql2spatial'] = ::Arel::Visitors::MySQL2Spatial
62
-
63
- end
64
- end
65
-
66
- # :startdoc:
67
-
68
-
69
41
  # The activerecord-mysql2spatial-adapter gem installs the *mysql2spatial*
70
42
  # connection adapter into ActiveRecord.
71
43
 
@@ -87,153 +59,31 @@ module ActiveRecord
87
59
  end
88
60
  client_ = ::Mysql2::Client.new(config_.symbolize_keys)
89
61
  options_ = [config_[:host], config_[:username], config_[:password], config_[:database], config_[:port], config_[:socket], 0]
90
- ConnectionAdapters::Mysql2SpatialAdapter.new(client_, logger, options_, config_)
62
+ ::ActiveRecord::ConnectionAdapters::Mysql2SpatialAdapter::MainAdapter.new(client_, logger, options_, config_)
91
63
  end
92
64
 
93
65
 
94
66
  end
95
67
 
96
68
 
97
- module ConnectionAdapters # :nodoc:
69
+ # All ActiveRecord adapters go in this namespace.
70
+ module ConnectionAdapters
98
71
 
99
- class Mysql2SpatialAdapter < Mysql2Adapter # :nodoc:
100
-
72
+ # The Mysql2Spatial adapter
73
+ module Mysql2SpatialAdapter
101
74
 
75
+ # The name returned by the adapter_name method of this adapter.
102
76
  ADAPTER_NAME = 'Mysql2Spatial'.freeze
103
77
 
104
- NATIVE_DATABASE_TYPES = Mysql2Adapter::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"})
105
-
106
-
107
- def native_database_types
108
- NATIVE_DATABASE_TYPES
109
- end
110
-
111
-
112
- def adapter_name
113
- ADAPTER_NAME
114
- end
115
-
116
-
117
- def quote(value_, column_=nil)
118
- if ::RGeo::Feature::Geometry.check_type(value_)
119
- "GeomFromWKB(0x#{::RGeo::WKRep::WKBGenerator.new(:hex_format => true).generate(value_)},#{value_.srid})"
120
- else
121
- super
122
- end
123
- end
124
-
125
-
126
- def add_index(table_name_, column_name_, options_={})
127
- if options_[:spatial]
128
- index_name_ = index_name(table_name_, :column => Array(column_name_))
129
- if ::Hash === options_
130
- index_name_ = options_[:name] || index_name_
131
- end
132
- execute "CREATE SPATIAL INDEX #{index_name_} ON #{table_name_} (#{Array(column_name_).join(", ")})"
133
- else
134
- super
135
- end
136
- end
137
-
138
-
139
- def columns(table_name_, name_=nil)
140
- result_ = execute("SHOW FIELDS FROM #{quote_table_name(table_name_)}", :skip_logging)
141
- columns_ = []
142
- result_.each(:symbolize_keys => true, :as => :hash) do |field_|
143
- columns_ << SpatialColumn.new(field_[:Field], field_[:Default], field_[:Type], field_[:Null] == "YES")
144
- end
145
- columns_
146
- end
147
-
148
-
149
- def indexes(table_name_, name_=nil)
150
- indexes_ = []
151
- current_index_ = nil
152
- result_ = execute("SHOW KEYS FROM #{quote_table_name(table_name_)}", name_)
153
- result_.each(:symbolize_keys => true, :as => :hash) do |row_|
154
- if current_index_ != row_[:Key_name]
155
- next if row_[:Key_name] == 'PRIMARY' # skip the primary key
156
- current_index_ = row_[:Key_name]
157
- indexes_ << ::RGeo::ActiveRecord::SpatialIndexDefinition.new(row_[:Table], row_[:Key_name], row_[:Non_unique] == 0, [], [], row_[:Index_type] == 'SPATIAL')
158
- end
159
- indexes_.last.columns << row_[:Column_name]
160
- indexes_.last.lengths << row_[:Sub_part]
161
- end
162
- indexes_
163
- end
164
-
165
-
166
- class SpatialColumn < ConnectionAdapters::Mysql2Column # :nodoc:
167
-
168
-
169
- def initialize(name_, default_, sql_type_=nil, null_=true)
170
- super(name_, default_,sql_type_, null_)
171
- @geometric_type = ::RGeo::ActiveRecord.geometric_type_from_name(sql_type_)
172
- @ar_class = ::ActiveRecord::Base
173
- end
174
-
175
-
176
- def set_ar_class(val_)
177
- @ar_class = val_
178
- end
179
-
180
-
181
- attr_reader :geometric_type
182
-
183
-
184
- def spatial?
185
- type == :geometry
186
- end
187
-
188
-
189
- def klass
190
- type == :geometry ? ::RGeo::Feature::Geometry : super
191
- end
192
-
193
-
194
- def type_cast(value_)
195
- type == :geometry ? SpatialColumn.convert_to_geometry(value_, @ar_class, name) : super
196
- end
197
-
198
-
199
- def type_cast_code(var_name_)
200
- type == :geometry ? "::ActiveRecord::ConnectionAdapters::Mysql2SpatialAdapter::SpatialColumn.convert_to_geometry(#{var_name_}, self.class, #{name.inspect})" : super
201
- end
202
-
203
-
204
- private
205
-
206
- def simplified_type(sql_type_)
207
- sql_type_ =~ /geometry|point|linestring|polygon/i ? :geometry : super
208
- end
209
-
210
-
211
- def self.convert_to_geometry(input_, ar_class_, column_)
212
- case input_
213
- when ::RGeo::Feature::Geometry
214
- factory_ = ar_class_.rgeo_factory_for_column(column_, :srid => input_.srid)
215
- ::RGeo::Feature.cast(input_, factory_)
216
- when ::String
217
- marker_ = input_[4,1]
218
- if marker_ == "\x00" || marker_ == "\x01"
219
- factory_ = ar_class_.rgeo_factory_for_column(column_, :srid => input_[0,4].unpack(marker_ == "\x01" ? 'V' : 'N').first)
220
- ::RGeo::WKRep::WKBParser.new(factory_).parse(input_[4..-1])
221
- else
222
- factory_ = ar_class_.rgeo_factory_for_column(column_)
223
- ::RGeo::WKRep::WKTParser.new(factory_, :support_ewkt => true).parse(input_)
224
- end
225
- else
226
- nil
227
- end
228
- end
229
-
230
-
231
- end
232
-
233
-
234
78
  end
235
79
 
236
80
  end
237
81
 
238
82
 
239
83
  end
84
+
85
+
86
+ require 'active_record/connection_adapters/mysql2spatial_adapter/version.rb'
87
+ require 'active_record/connection_adapters/mysql2spatial_adapter/main_adapter.rb'
88
+ require 'active_record/connection_adapters/mysql2spatial_adapter/spatial_column.rb'
89
+ require 'active_record/connection_adapters/mysql2spatial_adapter/arel_tosql.rb'
@@ -0,0 +1,69 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Mysql2Spatial 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 MySQL2Spatial < 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['mysql2spatial'] = ::Arel::Visitors::MySQL2Spatial
65
+
66
+ end
67
+ end
68
+
69
+ # :startdoc:
@@ -0,0 +1,136 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Mysql2Spatial 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 Mysql2SpatialAdapter
44
+
45
+
46
+ class MainAdapter < ConnectionAdapters::Mysql2Adapter
47
+
48
+
49
+ NATIVE_DATABASE_TYPES = Mysql2Adapter::NATIVE_DATABASE_TYPES.merge(:spatial => {:name => "geometry"})
50
+
51
+
52
+ def adapter_name
53
+ Mysql2SpatialAdapter::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(:symbolize_keys => true, :as => :hash) do |field_|
103
+ columns_ << SpatialColumn.new(field_[:Field], field_[:Default], field_[:Type], field_[:Null] == "YES")
104
+ end
105
+ columns_
106
+ end
107
+
108
+
109
+ def indexes(table_name_, name_=nil)
110
+ indexes_ = []
111
+ current_index_ = nil
112
+ result_ = execute("SHOW KEYS FROM #{quote_table_name(table_name_)}", name_)
113
+ result_.each(:symbolize_keys => true, :as => :hash) do |row_|
114
+ if current_index_ != row_[:Key_name]
115
+ next if row_[:Key_name] == 'PRIMARY' # skip the primary key
116
+ current_index_ = row_[:Key_name]
117
+ indexes_ << ::RGeo::ActiveRecord::SpatialIndexDefinition.new(row_[:Table], row_[:Key_name], row_[:Non_unique] == 0, [], [], row_[:Index_type] == 'SPATIAL')
118
+ end
119
+ last_index_ = indexes_.last
120
+ last_index_.columns << row_[:Column_name]
121
+ last_index_.lengths << row_[:Sub_part] unless last_index_.spatial
122
+ end
123
+ indexes_
124
+ end
125
+
126
+
127
+ end
128
+
129
+
130
+ end
131
+
132
+ end
133
+
134
+ end
135
+
136
+ # :startdoc:
@@ -0,0 +1,123 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Mysql2Spatial 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 Mysql2SpatialAdapter
44
+
45
+
46
+ class SpatialColumn < ConnectionAdapters::Mysql2Column
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::Mysql2SpatialAdapter::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
+ # Mysql2Spatial 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 Mysql2SpatialAdapter
48
+
49
+
50
+ # Current version of Mysql2SpatialAdapter as a frozen string
51
+ VERSION_STRING = ::File.read(::File.dirname(__FILE__)+'/../../../../Version').strip.freeze
52
+
53
+ # Current version of Mysql2SpatialAdapter 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
data/test/tc_basic.rb CHANGED
@@ -62,6 +62,11 @@ module RGeo
62
62
  end
63
63
 
64
64
 
65
+ def test_version
66
+ assert_not_nil(::ActiveRecord::ConnectionAdapters::Mysql2SpatialAdapter::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 Mysql2Spatial 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 Mysql2SpatialAdapter # :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/mysql2spatial_adapter/arel_tosql.rb
61
+ - lib/active_record/connection_adapters/mysql2spatial_adapter/main_adapter.rb
62
+ - lib/active_record/connection_adapters/mysql2spatial_adapter/spatial_column.rb
63
+ - lib/active_record/connection_adapters/mysql2spatial_adapter/version.rb
60
64
  - lib/active_record/connection_adapters/mysql2spatial_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-mysql2spatial-adapter
@@ -98,3 +103,4 @@ specification_version: 3
98
103
  summary: An ActiveRecord adapter for MySQL Spatial Extensions, based on RGeo and the mysql2 gem.
99
104
  test_files:
100
105
  - test/tc_basic.rb
106
+ - test/tc_spatial_queries.rb