├── .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 |
--------------------------------------------------------------------------------