├── .github └── FUNDING.yml ├── .gitignore ├── Gemfile ├── History.rdoc ├── README.rdoc ├── Rakefile ├── Version ├── activerecord-mysql2spatial-adapter.gemspec ├── lib └── active_record │ └── connection_adapters │ ├── mysql2spatial_adapter.rb │ └── mysql2spatial_adapter │ ├── arel_tosql.rb │ ├── column_methods.rb │ ├── main_adapter.rb │ ├── spatial_column.rb │ └── version.rb ├── rakefile_config.rb └── test ├── database-example.yml ├── tc_basic.rb └── tc_spatial_queries.rb /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: rgeo 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle 2 | *.class 3 | *.dSYM 4 | *.jar 5 | *.o 6 | *.rbc 7 | .#* 8 | .DS_Store 9 | .rbx/ 10 | /.idea 11 | /.rbenv-gemsets 12 | /.ruby-gemset 13 | /.ruby-version 14 | /Gemfile.lock 15 | /doc/ 16 | /pkg/ 17 | /test/database.yml 18 | /tmp/ 19 | Makefile* 20 | mkmf.log 21 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Gemfile used for CI testing. 3 | # 4 | # Copyright 2012 Daniel Azuma 5 | # 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright notice, 12 | # this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder, nor the names of any other 17 | # contributors to this software, may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | source 'https://rubygems.org' 33 | 34 | gemspec 35 | -------------------------------------------------------------------------------- /History.rdoc: -------------------------------------------------------------------------------- 1 | === 0.5.2 2 | 3 | * Code cleanup, whitespace, comments 4 | * Use Ruby 1.9+ syntax for hashes 5 | 6 | === 0.5.1 / 2017-07-04 7 | 8 | * Move dependency on rgeo-activerecord from unreleased 1.0 branch to 1.3.0 release. 9 | * Document how to run tests. 10 | 11 | === 0.5.0 / 2015-08-06 12 | 13 | * Compatibility with ActiveRecord >= 4.0, < 4.2 and with rgeo-activerecord 1.x. 14 | 15 | === 0.4.3 / 2012-08-02 16 | 17 | * The gemspec no longer includes the timestamp in the version, so that bundler can pull from github. (Reported by corneverbruggen) 18 | * Fix for compatibility with ActiveRecord >= 3.2.2. (Pull request by janv) 19 | 20 | === 0.4.2 / 2012-02-22 21 | 22 | * Some compatibility fixes for Rails 3.2. (Reported by Nicholas Zaillian and Ryan Williams with implementation help from Radek Paviensky.) 23 | * Now requires rgeo-activerecord 0.4.3. 24 | 25 | === 0.4.1 / 2011-09-07 26 | 27 | * Now requests mysql2 >= 0.2.x instead of >= 0.3.x since the latter requires Rails 3.1. (Reported by Greg Hazel) 28 | 29 | === 0.4.0 / 2011-08-15 30 | 31 | * Various fixes for Rails 3.1 compatibility. 32 | * Now requires rgeo-activerecord 0.4.0. 33 | * INCOMPATIBLE CHANGE: simple queries (e.g. MyClass.where(:latlon => my_point)) use an objective rather than spatial equality test. Earlier versions transformed this form to use st_equals, but now if you need to test for spatial equality, you'll need to call st_equals explicitly. I'm still evaluating which direction we want to go with this in the future, but we may be stuck with the current behavior because the hack required to transform these queries to use spatial equality was egregious and broke in Rails 3.1 with no clear workaround. 34 | 35 | === 0.3.3 / 2011-06-21 36 | 37 | * Require latest rgeo-activerecord to get some fixes. 38 | * Support hex format for attribute setting. 39 | * No longer raises exceptions if parse fails on attribute setting. (Reported by Daniel Hackney) 40 | 41 | === 0.3.2 / 2011-04-11 42 | 43 | * A .gemspec file is now available for gem building and bundler git integration. 44 | 45 | === 0.3.1 / 2011-02-28 46 | 47 | * Now requires rgeo-activerecord 0.3.1 (which brings a critical fix involving declaring multiple spatial columns in migration). 48 | 49 | === 0.3.0 / 2011-01-26 50 | 51 | * Reworked type and constraint handling, which should result in a large number of bug fixes, especially related to schema dumps. 52 | * Experimental support for complex spatial queries. (Requires Arel 2.1, which is expected to be released with Rails 3.1.) 53 | * The path to the Railtie is now different (see the README), though a compatibility wrapper has been left in the old location. 54 | * Reorganized the code a bit for better clarity. 55 | 56 | === 0.2.1 / 2010-12-27 57 | 58 | * Support for basic spatial equality queries. e.g. constructs such as: 59 | MyClass.where(:geom_column => factory.point(1, 2)) 60 | MyClass.where(:geom_column => 'POINT(1 2)') 61 | 62 | === 0.2.0 / 2010-12-07 63 | 64 | * Initial public alpha release. Spun activerecord-mysql2spatial-adapter off from the core rgeo gem. 65 | * You can now set the factory for a specific column by name. 66 | 67 | For earlier history, see the History file for the rgeo gem. 68 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | == MySQL2 Spatial \ActiveRecord Adapter 2 | 3 | The MySQL2 Spatial \ActiveRecord Adapter is an \ActiveRecord connection 4 | adapter based on the standard mysql2 adapter. It extends the standard 5 | adapter to provide support for spatial columns and indexes in MySQL, 6 | using the {RGeo}[http://github.com/rgeo/rgeo] library to represent 7 | spatial data in Ruby. Like the standard mysql2 adapter, this adapter 8 | requires the mysql2 gem. 9 | 10 | == What This Adapter Provides 11 | 12 | === Spatial Migrations 13 | 14 | First, this adapter extends the migration syntax to support creating 15 | spatial columns and indexes. To create a spatial column, use the 16 | :geometry type, or any of the OGC spatial types such as 17 | :point or :line_string. To create a spatial index, set 18 | the :spatial option to true. Remember that, on some versions of 19 | MySQL, only the MyISAM engine supports spatial indexes, and the indexed 20 | column may need to be NOT NULL. 21 | 22 | Examples (require update): 23 | 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 34 | 35 | When this adapter is in use, spatial attributes in your \ActiveRecord 36 | objects will have RGeo geometry values. You can set spatial attributes 37 | either to RGeo geometry objects, or to strings in WKT (well-known text) 38 | format, which the adapter will automatically convert to geometry objects. 39 | 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 set_rgeo_factory_for_column class method on your 47 | \ActiveRecord class. For the latter, set the rgeo_factory_generator class 48 | attribute. This generator should understand at least the :srid 49 | options, which will be provided based on the SRID embedded in the value 50 | itself, since MySQL does not support SRID or dimension constraints on 51 | spatial columns themselves. The set_rgeo_factory_for_column and 52 | rgeo_factory_generator methods are actually implemented and documented in 53 | the "rgeo-activerecord" gem. 54 | 55 | Examples, given the spatial table defined above: 56 | 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 66 | 67 | Now you can interact with the data using the RGeo types: 68 | 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 79 | 80 | You can create simple queries based on objective equality in the same way 81 | you would on a scalar column: 82 | 83 | rec = MySpatialTable.where(latlon: RGeo::Geos.factory.point(-122, 47)).first 84 | 85 | You can also use WKT: 86 | 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. 94 | 95 | == Installation And Configuration 96 | 97 | === Installing The Adapter Gem 98 | 99 | This adapter has the following requirements: 100 | 101 | * Ruby 1.9.3 or later. Ruby 2.0.0 or later preferred. 102 | * MySQL server 5.0 or later required for spatial extensions. 103 | * \ActiveRecord 4.0.0 or later. Earlier versions will not work. 104 | Should be compatible with Rails versions through 4.0.x-4.1.x. 105 | * mysql2 gem 0.2.13 or later. 106 | * rgeo gem 0.3.15 or later. 107 | * rgeo-activerecord gem 1.x. 108 | 109 | Install this adapter as a gem: 110 | 111 | gem install activerecord-mysql2spatial-adapter 112 | 113 | See the README for the "rgeo" gem, a required dependency, for further 114 | installation information. 115 | 116 | === Basic Setup 117 | 118 | To use this adapter, add this gem, "activerecord-mysql2spatial-adapter", 119 | to your Gemfile, and then request the adapter name "mysql2spatial" in 120 | your database connection configuration (which, for a Rails application, 121 | is in the config/database.yml file). The other database connection 122 | configuration parameters are the same as for the stock mysql2 adapter, 123 | so you can create a new Rails application using: 124 | 125 | rails new my_app --database=mysql 126 | 127 | ...and then just change the adapter name to "mysql2spatial". 128 | 129 | == Additional Information 130 | 131 | === Known bugs and limitations 132 | 133 | This adapter is not yet well tested. There are probably some bugs and 134 | holes in the functionality. We aren't using MySQL spatial extensions in 135 | production at GeoPage, so we would appreciate testing help and feedback 136 | from anyone who is. 137 | 138 | One known issue is that if you want to use Rails's testing rake tasks and 139 | you have spatial indexes in your schema, you should use the :sql 140 | dump style. e.g. set config.active_record.schema_format = :sql. 141 | The reason is that Rails's Ruby-format schema dumper does not preserve 142 | the :options used to create the table, specifically setting the 143 | Engine=MyISAM. Under MySQL Spatial, you may create spatial indexes only 144 | on MyISAM tables; unfortunately, if you use the Ruby-format schema to 145 | create your test databases, Rails does not transfer this information 146 | properly, and your test tables are created as InnoDB. The workaround is 147 | to use the :sql dump format. This has been reported as a bug in 148 | Rails as of Rails 3.0.3, so we hope it will get rectified at some point. 149 | 150 | === Development and support 151 | 152 | ==== Setup 153 | 154 | To install the most recent supported versions of required gems and run the tests against them, 155 | 156 | Install {GEOS}[https://trac.osgeo.org/geos/] as appropriate for your OS. 157 | rgeo works to some extent without GEOS, but this gem's tests require it. 158 | Be sure to install GEOS before installing gems so that it's compiled into rgeo. 159 | 160 | Create a test database configuration file test/database.yml with the following content. 161 | If you like you can copy test/database-example.yml to test/database.yml. 162 | Replace the database, username and password with correct values. 163 | 164 | adapter: mysql2spatial 165 | encoding: utf8 166 | reconnect: true 167 | host: localhost 168 | database: YOUR_DATABASE_NAME 169 | username: YOUR_USER_NAME 170 | password: PASSWORD_OR_NOTHING 171 | 172 | Then, 173 | 174 | bundle install 175 | bundle exec rake test 176 | 177 | Always do this before sharing a revision with others. 178 | 179 | ==== Resources 180 | 181 | Documentation is available at http://rgeo.github.com/activerecord-mysql2spatial-adapter/rdoc 182 | 183 | Source code is hosted on Github at http://github.com/rgeo/activerecord-mysql2spatial-adapter 184 | 185 | Contributions are welcome. Fork the project on Github. 186 | 187 | Report bugs on Github issues at http://github.org/rgeo/activerecord-mysql2spatial-adapter/issues 188 | 189 | Support available on the rgeo-users google group at http://groups.google.com/group/rgeo-users 190 | 191 | === Acknowledgments 192 | 193 | The Mysql2Spatial Adapter and its supporting libraries (including RGeo) 194 | are written by Daniel Azuma (http://www.daniel-azuma.com). 195 | 196 | Development is supported by Pirq. (http://www.pirq.com). 197 | 198 | This adapter implementation owes some debt to the spatial_adapter plugin 199 | (http://github.com/fragility/spatial_adapter). Although we made a few 200 | different design decisions for this adapter, studying the spatial_adapter 201 | source gave us a head start on the implementation. 202 | 203 | === License 204 | 205 | Copyright 2010-2012 Daniel Azuma 206 | 207 | All rights reserved. 208 | 209 | Redistribution and use in source and binary forms, with or without 210 | modification, are permitted provided that the following conditions are met: 211 | 212 | * Redistributions of source code must retain the above copyright notice, 213 | this list of conditions and the following disclaimer. 214 | * Redistributions in binary form must reproduce the above copyright notice, 215 | this list of conditions and the following disclaimer in the documentation 216 | and/or other materials provided with the distribution. 217 | * Neither the name of the copyright holder, nor the names of any other 218 | contributors to this software, may be used to endorse or promote products 219 | derived from this software without specific prior written permission. 220 | 221 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 222 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 223 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 224 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 225 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 226 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 227 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 228 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 229 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 230 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 231 | POSSIBILITY OF SUCH DAMAGE. 232 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # 2 | # Generic Gem Rakefile 3 | # 4 | # Copyright 2010-2012 Daniel Azuma 5 | # 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright notice, 12 | # this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder, nor the names of any other 17 | # contributors to this software, may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | # Load config if present 33 | 34 | config_path_ = ::File.expand_path('rakefile_config.rb', ::File.dirname(__FILE__)) 35 | load(config_path_) if ::File.exist?(config_path_) 36 | RAKEFILE_CONFIG = {}.freeze unless defined?(::RAKEFILE_CONFIG) 37 | 38 | # Gemspec 39 | 40 | require 'rubygems' 41 | gemspec_ = eval(::File.read(::Dir.glob('*.gemspec').first)) 42 | release_gemspec_ = eval(::File.read(::Dir.glob('*.gemspec').first)) 43 | release_gemspec_.version = gemspec_.version.to_s.sub(/\.nonrelease$/, '') 44 | 45 | # Platform info 46 | 47 | dlext_ = ::RbConfig::CONFIG['DLEXT'] 48 | 49 | platform_ = 50 | case ::RUBY_DESCRIPTION 51 | when /^jruby\s/ then :jruby 52 | when /^ruby\s/ then :mri 53 | when /^rubinius\s/ then :rubinius 54 | else :unknown 55 | end 56 | 57 | platform_suffix_ = 58 | case platform_ 59 | when :mri 60 | if ::RUBY_VERSION =~ /^1\.8\..*$/ 61 | 'mri18' 62 | elsif ::RUBY_VERSION =~ /^1\.9\..*$/ 63 | 'mri19' 64 | elsif ::RUBY_VERSION =~ /^2\..*$/ 65 | 'mri2' 66 | else 67 | raise "Unknown version of Matz Ruby Interpreter (#{::RUBY_VERSION})" 68 | end 69 | when :rubinius then 'rbx' 70 | when :jruby then 'jruby' 71 | else 'unknown' 72 | end 73 | 74 | # Directories 75 | 76 | doc_directory_ = ::RAKEFILE_CONFIG[:doc_directory] || 'doc' 77 | pkg_directory_ = ::RAKEFILE_CONFIG[:pkg_directory] || 'pkg' 78 | tmp_directory_ = ::RAKEFILE_CONFIG[:tmp_directory] || 'tmp' 79 | 80 | # Build tasks 81 | 82 | internal_ext_info_ = gemspec_.extensions.map do |extconf_path_| 83 | source_dir_ = ::File.dirname(extconf_path_) 84 | name_ = ::File.basename(source_dir_) 85 | { 86 | name: name_, 87 | source_dir: source_dir_, 88 | extconf_path: extconf_path_, 89 | source_glob: "#{source_dir_}/*.{c,h}", 90 | obj_glob: "#{source_dir_}/*.{o,dSYM}", 91 | suffix_makefile_path: "#{source_dir_}/Makefile_#{platform_suffix_}", 92 | built_lib_path: "#{source_dir_}/#{name_}.#{dlext_}", 93 | staged_lib_path: "#{source_dir_}/#{name_}_#{platform_suffix_}.#{dlext_}" 94 | } 95 | end 96 | internal_ext_info_ = [] if platform_ == :jruby 97 | 98 | internal_ext_info_.each do |info_| 99 | file info_[:staged_lib_path] => [info_[:suffix_makefile_path]] + ::Dir.glob(info_[:source_glob]) do 100 | ::Dir.chdir(info_[:source_dir]) do 101 | cp "Makefile_#{platform_suffix_}", 'Makefile' 102 | sh 'make' 103 | rm 'Makefile' 104 | end 105 | mv info_[:built_lib_path], info_[:staged_lib_path] 106 | rm_r ::Dir.glob(info_[:obj_glob]) 107 | end 108 | file info_[:suffix_makefile_path] => info_[:extconf_path] do 109 | ::Dir.chdir(info_[:source_dir]) do 110 | ruby 'extconf.rb' 111 | mv 'Makefile', "Makefile_#{platform_suffix_}" 112 | end 113 | end 114 | end 115 | 116 | task build_ext: internal_ext_info_.map { |info_| info_[:staged_lib_path] } do 117 | internal_ext_info_.each do |info_| 118 | target_prefix_ = target_name_ = nil 119 | ::Dir.chdir(info_[:source_dir]) do 120 | ruby 'extconf.rb' 121 | ::File.open('Makefile') do |file_| 122 | file_.each do |line_| 123 | if line_ =~ /^target_prefix\s*=\s*(\S+)\s/ 124 | target_prefix_ = Regexp.last_match(1) 125 | elsif line_ =~ /^TARGET\s*=\s*(\S+)\s/ 126 | target_name_ = Regexp.last_match(1) 127 | end 128 | end 129 | end 130 | rm 'Makefile' 131 | end 132 | unless target_prefix_ 133 | raise "Could not find target_prefix in makefile for #{info_[:name]}" 134 | end 135 | unless target_name_ 136 | raise "Could not find TARGET in makefile for #{info_[:name]}" 137 | end 138 | 139 | cp info_[:staged_lib_path], "lib#{target_prefix_}/#{target_name_}.#{dlext_}" 140 | end 141 | end 142 | 143 | # Clean task 144 | 145 | clean_files_ = [doc_directory_, pkg_directory_, tmp_directory_] + 146 | ::Dir.glob('ext/**/Makefile*') + 147 | ::Dir.glob('ext/**/*.{o,class,log,dSYM}') + 148 | ::Dir.glob('**/*.{bundle,so,dll,rbc,jar}') + 149 | ::Dir.glob('**/.rbx') + 150 | (::RAKEFILE_CONFIG[:extra_clean_files] || []) 151 | task :clean do 152 | clean_files_.each { |path_| rm_rf path_ } 153 | end 154 | 155 | # RDoc tasks 156 | 157 | task build_rdoc: "#{doc_directory_}/index.html" 158 | all_rdoc_files_ = ::Dir.glob('lib/**/*.rb') + gemspec_.extra_rdoc_files 159 | main_rdoc_file_ = ::RAKEFILE_CONFIG[:main_rdoc_file] 160 | if !main_rdoc_file_ && ::File.readable?('README.rdoc') 161 | main_rdoc_file_ = 'README.rdoc' 162 | end 163 | main_rdoc_file_ ||= ::Dir.glob('*.rdoc').first 164 | file "#{doc_directory_}/index.html" => all_rdoc_files_ do 165 | begin 166 | rm_r doc_directory_ 167 | rescue StandardError 168 | nil 169 | end 170 | args_ = [] 171 | args_ << '-o' << doc_directory_ 172 | args_ << '--main' << main_rdoc_file_ if main_rdoc_file_ 173 | args_ << '--title' << "#{::RAKEFILE_CONFIG[:product_visible_name] || gemspec_.name.capitalize} #{release_gemspec_.version} Documentation" 174 | args_ << '-f' << 'darkfish' 175 | args_ << '--verbose' if ::ENV['VERBOSE'] 176 | gem 'rdoc' 177 | require 'rdoc/rdoc' 178 | ::RDoc::RDoc.new.document(args_ + all_rdoc_files_) 179 | end 180 | 181 | # Gem release tasks 182 | 183 | task :build_other 184 | 185 | task build_gem: :build_other do 186 | ::Gem::Builder.new(gemspec_).build 187 | mkdir_p(pkg_directory_) 188 | mv "#{gemspec_.name}-#{gemspec_.version}.gem", "#{pkg_directory_}/" 189 | end 190 | 191 | task build_release: :build_other do 192 | ::Gem::Builder.new(release_gemspec_).build 193 | mkdir_p(pkg_directory_) 194 | mv "#{release_gemspec_.name}-#{release_gemspec_.version}.gem", "#{pkg_directory_}/" 195 | end 196 | 197 | task release_gem: :build_release do 198 | ::Dir.chdir(pkg_directory_) do 199 | sh "#{::RbConfig::TOPDIR}/bin/gem push #{release_gemspec_.name}-#{release_gemspec_.version}.gem" 200 | end 201 | end 202 | 203 | # Unit test task 204 | 205 | task test: %i[build_ext build_other] do 206 | $LOAD_PATH.unshift(::File.expand_path('lib', ::File.dirname(__FILE__))) 207 | test_files_ = if ::ENV['TESTCASE'] 208 | ::Dir.glob("test/#{::ENV['TESTCASE']}.rb") 209 | else 210 | ::Dir.glob('test/**/tc_*.rb') 211 | end 212 | test_files_.each do |path_| 213 | load path_ 214 | puts "Loaded testcase #{path_}" 215 | end 216 | end 217 | 218 | # Default task 219 | 220 | task default: %i[clean test] 221 | -------------------------------------------------------------------------------- /Version: -------------------------------------------------------------------------------- 1 | 0.5.2 2 | -------------------------------------------------------------------------------- /activerecord-mysql2spatial-adapter.gemspec: -------------------------------------------------------------------------------- 1 | # 2 | # MySQL2 Spatial ActiveRecord Adapter Gemspec 3 | # 4 | # Copyright 2011-2012 Daniel Azuma 5 | # 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright notice, 12 | # this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder, nor the names of any other 17 | # contributors to this software, may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | ::Gem::Specification.new do |s_| 33 | s_.name = 'activerecord-mysql2spatial-adapter' 34 | s_.summary = 'An ActiveRecord adapter for MySQL Spatial Extensions, based on RGeo and the mysql2 gem.' 35 | s_.description = 'This is an ActiveRecord connection adapter for MySQL Spatial Extensions. It is based on the stock MySQL2 adapter, but provides built-in support for spatial columns. It uses the RGeo library to represent spatial data in Ruby.' 36 | s_.version = ::File.read('Version').strip.to_s 37 | s_.author = 'Daniel Azuma' 38 | s_.email = 'dazuma@gmail.com' 39 | s_.required_ruby_version = '>= 1.9.3' 40 | s_.files = 41 | ::Dir.glob('lib/**/*.rb') + 42 | ::Dir.glob('test/**/*.rb') + 43 | ::Dir.glob('*.rdoc') + 44 | ['Version'] 45 | s_.extra_rdoc_files = ::Dir.glob('*.rdoc') 46 | s_.platform = ::Gem::Platform::RUBY 47 | s_.add_dependency('activerecord', '>= 4.0', '< 4.2') 48 | s_.add_dependency('rgeo-activerecord', '~> 1.3') 49 | s_.add_dependency('mysql2', '>= 0.2.13', '< 0.4.0') 50 | s_.add_development_dependency('rake', '>= 0.9.2') 51 | s_.add_development_dependency('rdoc', '>= 3.12') 52 | end 53 | -------------------------------------------------------------------------------- /lib/active_record/connection_adapters/mysql2spatial_adapter.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Mysql2Spatial adapter for ActiveRecord 3 | # 4 | # Copyright 2010 Daniel Azuma 5 | # 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright notice, 12 | # this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder, nor the names of any other 17 | # contributors to this software, may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | require 'rgeo/active_record' 33 | require 'active_record/connection_adapters/mysql2_adapter' 34 | 35 | # The activerecord-mysql2spatial-adapter gem installs the *mysql2spatial* 36 | # connection adapter into ActiveRecord. 37 | 38 | module ActiveRecord 39 | # ActiveRecord looks for the mysql2spatial_connection factory method in 40 | # this class. 41 | 42 | class Base 43 | # Create a mysql2spatial connection adapter. 44 | 45 | def self.mysql2spatial_connection(config_) 46 | config_[:username] = 'root' if config_[:username].nil? 47 | if ::Mysql2::Client.const_defined?(:FOUND_ROWS) 48 | config_[:flags] = ::Mysql2::Client::FOUND_ROWS 49 | end 50 | client_ = ::Mysql2::Client.new(config_.symbolize_keys) 51 | options_ = [config_[:host], config_[:username], config_[:password], config_[:database], config_[:port], config_[:socket], 0] 52 | ::ActiveRecord::ConnectionAdapters::Mysql2SpatialAdapter::MainAdapter.new(client_, logger, options_, config_) 53 | end 54 | end 55 | 56 | # All ActiveRecord adapters go in this namespace. 57 | module ConnectionAdapters 58 | # The Mysql2Spatial adapter 59 | module Mysql2SpatialAdapter 60 | # The name returned by the adapter_name method of this adapter. 61 | ADAPTER_NAME = 'Mysql2Spatial' 62 | end 63 | end 64 | end 65 | 66 | require 'active_record/connection_adapters/mysql2spatial_adapter/version.rb' 67 | require 'active_record/connection_adapters/mysql2spatial_adapter/column_methods.rb' # check if this works with Rails < 4.x 68 | require 'active_record/connection_adapters/mysql2spatial_adapter/main_adapter.rb' 69 | require 'active_record/connection_adapters/mysql2spatial_adapter/spatial_column.rb' 70 | require 'active_record/connection_adapters/mysql2spatial_adapter/arel_tosql.rb' 71 | -------------------------------------------------------------------------------- /lib/active_record/connection_adapters/mysql2spatial_adapter/arel_tosql.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Mysql2Spatial adapter for ActiveRecord 3 | # 4 | # Copyright 2010 Daniel Azuma 5 | # 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright notice, 12 | # this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder, nor the names of any other 17 | # contributors to this software, may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | # :stopdoc: 33 | 34 | module Arel 35 | module Visitors 36 | class MySQL2Spatial < MySQL 37 | if ::Arel::Visitors.const_defined?(:BindVisitor) 38 | include ::Arel::Visitors::BindVisitor 39 | end 40 | 41 | FUNC_MAP = { 42 | 'st_wkttosql' => 'GeomFromText', 43 | 'st_wkbtosql' => 'GeomFromWKB', 44 | 'st_length' => 'GLength' 45 | }.freeze 46 | 47 | include ::RGeo::ActiveRecord::SpatialToSql 48 | 49 | def st_func(standard_name_) 50 | if (name_ = FUNC_MAP[standard_name_.downcase]) 51 | name_ 52 | elsif standard_name_ =~ /^st_(\w+)$/i 53 | Regexp.last_match(1) 54 | else 55 | standard_name_ 56 | end 57 | end 58 | end 59 | 60 | VISITORS['mysql2spatial'] = ::Arel::Visitors::MySQL2Spatial 61 | end 62 | end 63 | 64 | # :startdoc: 65 | -------------------------------------------------------------------------------- /lib/active_record/connection_adapters/mysql2spatial_adapter/column_methods.rb: -------------------------------------------------------------------------------- 1 | module ActiveRecord 2 | module ConnectionAdapters 3 | module Mysql2SpatialAdapter 4 | module ColumnMethods 5 | def spatial(name, options = {}) 6 | unless options[:limit][:type] 7 | raise "You must set a type. For example: 't.spatial limit: { type: 'point' }'" 8 | end 9 | 10 | column(name, options[:limit][:type], options) 11 | end 12 | 13 | def geography(name, options = {}) 14 | column(name, :geography, options) 15 | end 16 | 17 | def geometry(name, options = {}) 18 | column(name, :geometry, options) 19 | end 20 | 21 | def geometry_collection(name, options = {}) 22 | column(name, :geometry_collection, options) 23 | end 24 | 25 | def line_string(name, options = {}) 26 | column(name, :line_string, options) 27 | end 28 | 29 | def multi_line_string(name, options = {}) 30 | column(name, :multi_line_string, options) 31 | end 32 | 33 | def multi_point(name, options = {}) 34 | column(name, :multi_point, options) 35 | end 36 | 37 | def multi_polygon(name, options = {}) 38 | column(name, :multi_polygon, options) 39 | end 40 | 41 | def point(name, options = {}) 42 | column(name, :point, options) 43 | end 44 | end 45 | 46 | ConnectionAdapters::TableDefinition.include ColumnMethods 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/active_record/connection_adapters/mysql2spatial_adapter/main_adapter.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Mysql2Spatial adapter for ActiveRecord 3 | # 4 | # Copyright 2010 Daniel Azuma 5 | # 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright notice, 12 | # this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder, nor the names of any other 17 | # contributors to this software, may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | # :stopdoc: 33 | 34 | module ActiveRecord 35 | module ConnectionAdapters 36 | module Mysql2SpatialAdapter 37 | class MainAdapter < ConnectionAdapters::Mysql2Adapter 38 | NATIVE_DATABASE_TYPES = Mysql2Adapter::NATIVE_DATABASE_TYPES.merge(spatial: { name: 'geometry' }) 39 | 40 | def initialize(*args_) 41 | super 42 | # Rails 3.2 way of defining the visitor: do so in the constructor 43 | if defined?(@visitor) && @visitor 44 | @visitor = ::Arel::Visitors::MySQL2Spatial.new(self) 45 | end 46 | end 47 | 48 | def set_rgeo_factory_settings(factory_settings_) 49 | @rgeo_factory_settings = factory_settings_ 50 | end 51 | 52 | def adapter_name 53 | Mysql2SpatialAdapter::ADAPTER_NAME 54 | end 55 | 56 | def spatial_column_constructor(name_) 57 | ::RGeo::ActiveRecord::DEFAULT_SPATIAL_COLUMN_CONSTRUCTORS[name_] 58 | end 59 | 60 | def native_database_types 61 | NATIVE_DATABASE_TYPES 62 | end 63 | 64 | def quote(value_, column_ = nil) 65 | if ::RGeo::Feature::Geometry.check_type(value_) 66 | "GeomFromWKB(0x#{::RGeo::WKRep::WKBGenerator.new(hex_format: true).generate(value_)},#{value_.srid})" 67 | else 68 | super 69 | end 70 | end 71 | 72 | def type_to_sql(type_, limit_ = nil, precision_ = nil, scale_ = nil) 73 | if (info_ = spatial_column_constructor(type_.to_sym)) 74 | type_ = limit_[:type] || type_ if limit_.is_a?(::Hash) 75 | type_ = 'geometry' if type_.to_s == 'spatial' 76 | type_ = type_.to_s.gsub('_', '').upcase 77 | end 78 | super(type_, limit_, precision_, scale_) 79 | end 80 | 81 | def add_index(table_name_, column_name_, options_ = {}) 82 | if options_[:spatial] 83 | index_name_ = index_name(table_name_, column: Array(column_name_)) 84 | index_name_ = options_[:name] || index_name_ if ::Hash === options_ 85 | execute "CREATE SPATIAL INDEX #{index_name_} ON #{table_name_} (#{Array(column_name_).join(', ')})" 86 | else 87 | super 88 | end 89 | end 90 | 91 | def columns(table_name_, _name_ = nil) 92 | result_ = execute("SHOW FIELDS FROM #{quote_table_name(table_name_)}", :skip_logging) 93 | columns_ = [] 94 | result_.each(symbolize_keys: true, as: :hash) do |field_| 95 | columns_ << SpatialColumn.new(@rgeo_factory_settings, table_name_.to_s, 96 | field_[:Field], field_[:Default], field_[:Type], field_[:Null] == 'YES') 97 | end 98 | columns_ 99 | end 100 | 101 | # Returns an array of indexes for the given table. 102 | def indexes(table_name_, name_ = nil) 103 | indexes_ = [] 104 | current_index_ = nil 105 | result_ = execute("SHOW KEYS FROM #{quote_table_name(table_name_)}", name_) 106 | result_.each(symbolize_keys: true, as: :hash) do |row_| 107 | if current_index_ != row_[:Key_name] 108 | next if row_[:Key_name] == 'PRIMARY' # skip the primary key 109 | 110 | current_index_ = row_[:Key_name] 111 | mysql_index_type = row_[:Index_type].downcase.to_sym 112 | index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil 113 | index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil 114 | options = [row_[:Table], row_[:Key_name], row_[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using] 115 | indexes_ << if mysql_index_type == :spatial 116 | options.push(true) 117 | ::RGeo::ActiveRecord::SpatialIndexDefinition.new(*options) 118 | else 119 | IndexDefinition.new(*options) 120 | end 121 | end 122 | last_index_ = indexes_.last 123 | last_index_.columns << row_[:Column_name] 124 | unless mysql_index_type == :spatial 125 | last_index_.lengths << row_[:Sub_part] 126 | end 127 | end 128 | indexes_ 129 | end 130 | end 131 | end 132 | end 133 | end 134 | 135 | # :startdoc: 136 | -------------------------------------------------------------------------------- /lib/active_record/connection_adapters/mysql2spatial_adapter/spatial_column.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Mysql2Spatial adapter for ActiveRecord 3 | # 4 | # Copyright 2010 Daniel Azuma 5 | # 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright notice, 12 | # this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder, nor the names of any other 17 | # contributors to this software, may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | # :stopdoc: 33 | 34 | module ActiveRecord 35 | module ConnectionAdapters 36 | module Mysql2SpatialAdapter 37 | # ActiveRecord 3.2 uses ConnectionAdapters::Mysql2Adapter::Column 38 | # whereas 3.0 and 3.1 use ConnectionAdapters::Mysql2Column 39 | column_base_class_ = defined?(ConnectionAdapters::Mysql2Adapter::Column) ? 40 | ConnectionAdapters::Mysql2Adapter::Column : ConnectionAdapters::Mysql2Column 41 | 42 | class SpatialColumn < column_base_class_ 43 | FACTORY_SETTINGS_CACHE = {}.freeze 44 | 45 | def initialize(factory_settings_, table_name_, name_, default_, sql_type_ = nil, null_ = true) 46 | @factory_settings = factory_settings_ 47 | @table_name = table_name_ 48 | super(name_, default_, sql_type_, null_) 49 | @geometric_type = ::RGeo::ActiveRecord.geometric_type_from_name(sql_type_) 50 | if type == :spatial 51 | @limit = { type: @geometric_type.type_name.underscore } 52 | end 53 | FACTORY_SETTINGS_CACHE[factory_settings_.object_id] = factory_settings_ 54 | end 55 | 56 | attr_reader :geometric_type 57 | 58 | def spatial? 59 | type == :spatial 60 | end 61 | 62 | def klass 63 | type == :spatial ? ::RGeo::Feature::Geometry : super 64 | end 65 | 66 | def type_cast(value_) 67 | if type == :spatial 68 | SpatialColumn.convert_to_geometry(value_, @factory_settings, @table_name, name) 69 | else 70 | super 71 | end 72 | end 73 | 74 | def type_cast_code(var_name_) 75 | if type == :spatial 76 | '::ActiveRecord::ConnectionAdapters::Mysql2SpatialAdapter::SpatialColumn.convert_to_geometry(' \ 77 | "#{var_name_}, ::ActiveRecord::ConnectionAdapters::Mysql2SpatialAdapter::SpatialColumn::" \ 78 | "FACTORY_SETTINGS_CACHE[#{@factory_settings.object_id}], #{@table_name.inspect}, #{name.inspect})" 79 | else 80 | super 81 | end 82 | end 83 | 84 | private 85 | 86 | def simplified_type(sql_type_) 87 | sql_type_ =~ /geometry|point|linestring|polygon/i ? :spatial : super 88 | end 89 | 90 | def self.convert_to_geometry(input_, factory_settings_, table_name_, column_) 91 | case input_ 92 | when ::RGeo::Feature::Geometry 93 | factory_ = factory_settings_.get_column_factory(table_name_, column_, srid: input_.srid) 94 | begin 95 | ::RGeo::Feature.cast(input_, factory_) 96 | rescue StandardError 97 | nil 98 | end 99 | when ::String 100 | marker_ = input_[4, 1] 101 | if marker_ == "\x00" || marker_ == "\x01" 102 | factory_ = factory_settings_.get_column_factory(table_name_, column_, 103 | srid: input_[0, 4].unpack1(marker_ == "\x01" ? 'V' : 'N')) 104 | begin 105 | ::RGeo::WKRep::WKBParser.new(factory_).parse(input_[4..-1]) 106 | rescue StandardError 107 | nil 108 | end 109 | elsif input_[0, 10] =~ /[0-9a-fA-F]{8}0[01]/ 110 | srid_ = input_[0, 8].to_i(16) 111 | srid_ = [srid_].pack('V').unpack1('N') if input[9, 1] == '1' 112 | factory_ = factory_settings_.get_column_factory(table_name_, column_, srid: srid_) 113 | begin 114 | ::RGeo::WKRep::WKBParser.new(factory_).parse(input_[8..-1]) 115 | rescue StandardError 116 | nil 117 | end 118 | else 119 | factory_ = factory_settings_.get_column_factory(table_name_, column_) 120 | begin 121 | ::RGeo::WKRep::WKTParser.new(factory_, support_ewkt: true).parse(input_) 122 | rescue StandardError 123 | nil 124 | end 125 | end 126 | end 127 | end 128 | end 129 | end 130 | end 131 | end 132 | 133 | # :startdoc: 134 | -------------------------------------------------------------------------------- /lib/active_record/connection_adapters/mysql2spatial_adapter/version.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Mysql2Spatial adapter for ActiveRecord 3 | # 4 | # Copyright 2010 Daniel Azuma 5 | # 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright notice, 12 | # this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder, nor the names of any other 17 | # contributors to this software, may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | begin 33 | require 'versionomy' 34 | rescue ::LoadError 35 | end 36 | 37 | module ActiveRecord 38 | module ConnectionAdapters 39 | module Mysql2SpatialAdapter 40 | # Current version of Mysql2SpatialAdapter as a frozen string 41 | VERSION_STRING = ::File.read(::File.dirname(__FILE__) + '/../../../../Version').strip.freeze 42 | 43 | # Current version of Mysql2SpatialAdapter as a Versionomy object, if the 44 | # Versionomy gem is available; otherwise equal to VERSION_STRING. 45 | VERSION = defined?(::Versionomy) ? ::Versionomy.parse(VERSION_STRING) : VERSION_STRING 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /rakefile_config.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MySQL2 Spatial Adapter Rakefile configuration 3 | # 4 | # Copyright 2012 Daniel Azuma 5 | # 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright notice, 12 | # this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder, nor the names of any other 17 | # contributors to this software, may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | RAKEFILE_CONFIG = { 33 | product_visible_name: 'MySQL2 Spatial ActiveRecord Adapter' 34 | }.freeze 35 | -------------------------------------------------------------------------------- /test/database-example.yml: -------------------------------------------------------------------------------- 1 | # Copy this file to database.yml in the same directory and fill in the empty fields 2 | adapter: mysql2spatial 3 | encoding: utf8 4 | reconnect: true 5 | host: localhost 6 | database: 7 | username: 8 | password: 9 | -------------------------------------------------------------------------------- /test/tc_basic.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Tests for the Mysql2Spatial ActiveRecord adapter 3 | # 4 | # Copyright 2010 Daniel Azuma 5 | # 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright notice, 12 | # this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder, nor the names of any other 17 | # contributors to this software, may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | require 'minitest/autorun' 33 | require 'rgeo/active_record/adapter_test_helper' 34 | 35 | module RGeo 36 | module ActiveRecord # :nodoc: 37 | module Mysql2SpatialAdapter # :nodoc: 38 | module Tests # :nodoc: 39 | class TestBasic < ::Minitest::Test # :nodoc: 40 | DATABASE_CONFIG_PATH = ::File.dirname(__FILE__) + '/database.yml' 41 | include RGeo::ActiveRecord::AdapterTestHelper 42 | 43 | define_test_methods do 44 | def populate_ar_class(content_) 45 | klass_ = create_ar_class 46 | case content_ 47 | when :latlon_point 48 | klass_.connection.create_table(:spatial_test) do |t_| 49 | t_.column 'latlon', :point 50 | end 51 | end 52 | klass_ 53 | end 54 | 55 | def test_version 56 | refute_nil(::ActiveRecord::ConnectionAdapters::Mysql2SpatialAdapter::VERSION) 57 | end 58 | 59 | def test_create_simple_geometry 60 | klass_ = create_ar_class 61 | klass_.connection.create_table(:spatial_test) do |t_| 62 | t_.column 'latlon', :geometry 63 | end 64 | assert_equal(::RGeo::Feature::Geometry, klass_.columns.last.geometric_type) 65 | assert(klass_.cached_attributes.include?('latlon')) 66 | end 67 | 68 | def test_create_point_geometry 69 | klass_ = create_ar_class 70 | klass_.connection.create_table(:spatial_test) do |t_| 71 | t_.column 'latlon', :point 72 | end 73 | assert_equal(::RGeo::Feature::Point, klass_.columns.last.geometric_type) 74 | assert(klass_.cached_attributes.include?('latlon')) 75 | end 76 | 77 | def test_create_geometry_with_index 78 | klass_ = create_ar_class 79 | klass_.connection.create_table(:spatial_test, options: 'ENGINE=MyISAM') do |t_| 80 | t_.column 'latlon', :geometry, null: false 81 | end 82 | klass_.connection.change_table(:spatial_test) do |t_| 83 | t_.index([:latlon], spatial: true) 84 | end 85 | assert(klass_.connection.indexes(:spatial_test).last.spatial) 86 | end 87 | 88 | def test_set_and_get_point 89 | klass_ = populate_ar_class(:latlon_point) 90 | obj_ = klass_.new 91 | assert_nil(obj_.latlon) 92 | obj_.latlon = @factory.point(1, 2) 93 | assert_equal(@factory.point(1, 2), obj_.latlon) 94 | assert_equal(3785, obj_.latlon.srid) 95 | end 96 | 97 | def test_set_and_get_point_from_wkt 98 | klass_ = populate_ar_class(:latlon_point) 99 | obj_ = klass_.new 100 | assert_nil(obj_.latlon) 101 | obj_.latlon = 'SRID=1000;POINT(1 2)' 102 | assert_equal(@factory.point(1, 2), obj_.latlon) 103 | assert_equal(1000, obj_.latlon.srid) 104 | end 105 | 106 | def test_save_and_load_point 107 | klass_ = populate_ar_class(:latlon_point) 108 | obj_ = klass_.new 109 | obj_.latlon = @factory.point(1, 2) 110 | obj_.save! 111 | id_ = obj_.id 112 | obj2_ = klass_.find(id_) 113 | assert_equal(@factory.point(1, 2), obj2_.latlon) 114 | assert_equal(3785, obj2_.latlon.srid) 115 | end 116 | 117 | def test_save_and_load_point_from_wkt 118 | klass_ = populate_ar_class(:latlon_point) 119 | obj_ = klass_.new 120 | obj_.latlon = 'SRID=1000;POINT(1 2)' 121 | obj_.save! 122 | id_ = obj_.id 123 | obj2_ = klass_.find(id_) 124 | assert_equal(@factory.point(1, 2), obj2_.latlon) 125 | assert_equal(1000, obj2_.latlon.srid) 126 | end 127 | 128 | def test_readme_example 129 | klass_ = create_ar_class 130 | klass_.connection.create_table(:spatial_test, options: 'ENGINE=MyISAM') do |t_| 131 | t_.column(:latlon, :point, null: false) 132 | t_.line_string(:path) 133 | t_.geometry(:shape) 134 | end 135 | klass_.connection.change_table(:spatial_test) do |t_| 136 | t_.index(:latlon, spatial: true) 137 | end 138 | klass_.class_eval do 139 | self.rgeo_factory_generator = ::RGeo::Geos.method(:factory) 140 | set_rgeo_factory_for_column(:latlon, ::RGeo::Geographic.spherical_factory) 141 | end 142 | rec_ = klass_.new 143 | rec_.latlon = 'POINT(-122 47)' 144 | loc_ = rec_.latlon 145 | assert_equal(47, loc_.latitude) 146 | rec_.shape = loc_ 147 | assert_equal(true, ::RGeo::Geos.is_geos?(rec_.shape)) 148 | end 149 | 150 | def test_create_simple_geometry_using_shortcut 151 | klass_ = create_ar_class 152 | klass_.connection.create_table(:spatial_test) do |t_| 153 | t_.geometry 'latlon' 154 | end 155 | assert_equal(::RGeo::Feature::Geometry, klass_.columns.last.geometric_type) 156 | assert(klass_.cached_attributes.include?('latlon')) 157 | end 158 | 159 | def test_create_point_geometry_using_shortcut 160 | klass_ = create_ar_class 161 | klass_.connection.create_table(:spatial_test) do |t_| 162 | t_.point 'latlon' 163 | end 164 | assert_equal(::RGeo::Feature::Point, klass_.columns.last.geometric_type) 165 | assert(klass_.cached_attributes.include?('latlon')) 166 | end 167 | 168 | def test_create_geometry_using_limit 169 | klass_ = create_ar_class 170 | klass_.connection.create_table(:spatial_test) do |t_| 171 | t_.spatial 'geom', limit: { type: :line_string } 172 | end 173 | assert_equal(::RGeo::Feature::LineString, klass_.columns.last.geometric_type) 174 | assert(klass_.cached_attributes.include?('geom')) 175 | end 176 | end 177 | end 178 | end 179 | end 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /test/tc_spatial_queries.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Tests for the Mysql2Spatial ActiveRecord adapter 3 | # 4 | # Copyright 2010 Daniel Azuma 5 | # 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright notice, 12 | # this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder, nor the names of any other 17 | # contributors to this software, may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | require 'minitest/autorun' 33 | require 'rgeo/active_record/adapter_test_helper' 34 | 35 | module RGeo 36 | module ActiveRecord # :nodoc: 37 | module Mysql2SpatialAdapter # :nodoc: 38 | module Tests # :nodoc: 39 | class TestSpatialQueries < ::Minitest::Test # :nodoc: 40 | DATABASE_CONFIG_PATH = ::File.dirname(__FILE__) + '/database.yml' 41 | include RGeo::ActiveRecord::AdapterTestHelper 42 | 43 | define_test_methods do 44 | def populate_ar_class(content_) 45 | klass_ = create_ar_class 46 | case content_ 47 | when :latlon_point 48 | klass_.connection.create_table(:spatial_test) do |t_| 49 | t_.column 'latlon', :point 50 | end 51 | when :path_linestring 52 | klass_.connection.create_table(:spatial_test) do |t_| 53 | t_.column 'path', :line_string 54 | end 55 | end 56 | klass_ 57 | end 58 | 59 | def test_query_point 60 | klass_ = populate_ar_class(:latlon_point) 61 | obj_ = klass_.new 62 | obj_.latlon = @factory.point(1, 2) 63 | obj_.save! 64 | id_ = obj_.id 65 | obj2_ = klass_.where(latlon: @factory.point(1, 2)).first 66 | assert_equal(id_, obj2_.id) 67 | obj3_ = klass_.where(latlon: @factory.point(2, 2)).first 68 | assert_nil(obj3_) 69 | end 70 | 71 | def _test_query_point_wkt 72 | klass_ = populate_ar_class(:latlon_point) 73 | obj_ = klass_.new 74 | obj_.latlon = @factory.point(1, 2) 75 | obj_.save! 76 | id_ = obj_.id 77 | obj2_ = klass_.where(latlon: 'POINT(1 2)').first 78 | assert_equal(id_, obj2_.id) 79 | obj3_ = klass_.where(latlon: 'POINT(2 2)').first 80 | assert_nil(obj3_) 81 | end 82 | 83 | if ::RGeo::ActiveRecord.spatial_expressions_supported? 84 | 85 | def test_query_st_length 86 | klass_ = populate_ar_class(:path_linestring) 87 | obj_ = klass_.new 88 | obj_.path = @factory.line(@factory.point(1, 2), @factory.point(3, 2)) 89 | obj_.save! 90 | id_ = obj_.id 91 | obj2_ = klass_.where(klass_.arel_table[:path].st_length.eq(2)).first 92 | assert_equal(id_, obj2_.id) 93 | obj3_ = klass_.where(klass_.arel_table[:path].st_length.gt(3)).first 94 | assert_nil(obj3_) 95 | end 96 | 97 | else 98 | puts 'WARNING: The current Arel does not support named functions. Spatial expression tests skipped.' 99 | end 100 | end 101 | end 102 | end 103 | end 104 | end 105 | end 106 | --------------------------------------------------------------------------------