active_record_mysql_spatial 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c1fb8466cfa0fa405fd35472dfa05122712db0dc27b7a3daad65d6c0329736ab
4
+ data.tar.gz: c58c26a2abff58ae9d9c32e710b7898db5648a5980db891deb71911b82336140
5
+ SHA512:
6
+ metadata.gz: ded6ba2548871b914758b5306c8b0b6b68f3922e059c3895e2e938e7a7fe6cb781f42e084d46b9871ba71e7a429097f2b9ed52d66b5e415f1bd2132b9d17b7c1
7
+ data.tar.gz: 162ab35dd4901102eb470bf71c7086ea1febd6de8d24695415a4be983df2b51cf1bb0399b0dac7e9fa1b0aca32e7cbea945fd588ffbcfd14e2f1de27b14876e0
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # ActiveRecordMysqlSpatial
2
+
3
+ TODO: Delete this and the text below, and describe your gem
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/active_record_mysql_spatial`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ ## Installation
8
+
9
+ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
+
11
+ Install the gem and add to the application's Gemfile by executing:
12
+
13
+ $ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ $ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Development
24
+
25
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
26
+
27
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
+
29
+ ## Contributing
30
+
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/active_record_mysql_spatial.
32
+
33
+ ## License
34
+
35
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rubocop/rake_task'
5
+
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: :rubocop
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/active_record_mysql_spatial/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'active_record_mysql_spatial'
7
+ spec.version = ActiveRecordMysqlSpatial::VERSION
8
+ spec.authors = ['Alpha']
9
+ spec.email = ['alphanolucifer@gmail.com']
10
+
11
+ spec.summary = 'MySQL Spatial Data Types for ActiveRecord'
12
+ spec.description = 'MySQL Spatial Data Types for ActiveRecord'
13
+ spec.homepage = 'https://github.com/zgid123/active_record_mysql_spatial'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 2.6.0'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+
19
+ spec.files = Dir.chdir(__dir__) do
20
+ `git ls-files -z`.split("\x0").reject do |f|
21
+ (File.expand_path(f) == __FILE__) ||
22
+ f.start_with?(
23
+ *%w[
24
+ bin/
25
+ test/
26
+ spec/
27
+ features/
28
+ .git
29
+ .circleci
30
+ appveyor
31
+ examples/
32
+ Gemfile
33
+ .rubocop.yml
34
+ .vscode/settings.json
35
+ LICENSE.txt
36
+ lefthook.yml
37
+ ]
38
+ )
39
+ end
40
+ end
41
+
42
+ spec.require_paths = ['lib']
43
+ spec.bindir = 'exe'
44
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
45
+
46
+ spec.add_runtime_dependency 'rgeo', '~> 3.0', '>= 3.0.1'
47
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module ActiveRecordMysqlSpatial
6
+ module ActiveRecord
7
+ module ColumnMethods
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ define_column_methods :point,
12
+ :linestring,
13
+ :multilinestring
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordMysqlSpatial
4
+ module ActiveRecord
5
+ module MySQL
6
+ class Base < ::ActiveRecord::Type::Json
7
+ attr_reader :raw,
8
+ :error
9
+
10
+ def type
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def deserialize(value)
15
+ return value if value.is_a?(self.class)
16
+
17
+ cast_value(value)
18
+ end
19
+
20
+ def serialize(value)
21
+ if value.is_a?(self.class)
22
+ value
23
+ else
24
+ return unless valid_hash?(value)
25
+
26
+ cast_value(value)
27
+ end
28
+ end
29
+
30
+ def to_sql
31
+ raise NotImplementedError
32
+ end
33
+
34
+ def to_coordinates_sql
35
+ raise NotImplementedError
36
+ end
37
+
38
+ private
39
+
40
+ def cast_value(value)
41
+ raise NotImplementedError
42
+ end
43
+
44
+ def extract_coordinates(attributes, type: :linestring)
45
+ return [drill_coordinates(attributes) || [], true] if valid_hash?(attributes, type: type)
46
+ return [[], false] unless attributes.is_a?(String)
47
+
48
+ if attributes.encoding.to_s == 'ASCII-8BIT'
49
+ linestring = Geometry.parse_bin(attributes)
50
+
51
+ [linestring.coordinates, false]
52
+ elsif Geometry.valid_spatial?(attributes)
53
+ spatial_data = Geometry.from_text(attributes)
54
+
55
+ [spatial_data.coordinates, true]
56
+ else
57
+ parsed_json = ::ActiveSupport::JSON.decode(attributes)
58
+
59
+ [parsed_json['coordinates'] || [], true]
60
+ end
61
+ end
62
+
63
+ def valid_hash?(attributes, type: :linestring)
64
+ return false unless attributes.is_a?(Hash)
65
+
66
+ valid_coord_hash = attributes.key?(:coordinates) || attributes.key?('coordinates') || attributes.key?(:coordinate) || attributes.key?('coordinate')
67
+
68
+ return valid_coord_hash unless type == :point
69
+
70
+ valid_coord_hash || attributes.key?(:x) || attributes.key?('x') || attributes.key?(:y) || attributes.key?('y')
71
+ end
72
+
73
+ def drill_coordinates(attributes)
74
+ attributes[:coordinates].presence || attributes['coordinates'].presence || attributes[:coordinate].presence || attributes['coordinate']
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative 'point'
5
+
6
+ module ActiveRecordMysqlSpatial
7
+ module ActiveRecord
8
+ module MySQL
9
+ class Linestring < Base
10
+ attr_reader :coordinates
11
+
12
+ def type
13
+ :linestring
14
+ end
15
+
16
+ def to_sql
17
+ return nil if @coordinates.blank?
18
+
19
+ "LineString(#{to_coordinates_sql})"
20
+ end
21
+
22
+ def to_coordinates_sql
23
+ @coordinates.map(&:to_coordinate_sql).compact.join(', ')
24
+ end
25
+
26
+ def ==(other)
27
+ coordinates.each_with_index do |coord, index|
28
+ return false if coord != other.coordinates[index]
29
+ end
30
+
31
+ true
32
+ end
33
+
34
+ private
35
+
36
+ def cast_value(value)
37
+ @raw = value
38
+ coordinates, create_raw = extract_coordinates(value)
39
+
40
+ @raw = Geometry.from_coordinates(coordinates).as_binary if create_raw
41
+
42
+ @coordinates = coordinates.map do |coord|
43
+ Point.new.send(:cast_value, coord)
44
+ end
45
+
46
+ self
47
+ rescue StandardError => e
48
+ @error = e.message
49
+
50
+ self
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative 'linestring'
5
+
6
+ module ActiveRecordMysqlSpatial
7
+ module ActiveRecord
8
+ module MySQL
9
+ class Multilinestring < Base
10
+ attr_accessor :items
11
+
12
+ def type
13
+ :multilinestring
14
+ end
15
+
16
+ def to_sql
17
+ return nil if @items.blank?
18
+
19
+ "MultiLineString(#{to_coordinates_sql})"
20
+ end
21
+
22
+ def to_coordinates_sql
23
+ items.map { |linestring| "(#{linestring.to_coordinates_sql})" }.join(',')
24
+ end
25
+
26
+ def ==(other)
27
+ items.each_with_index do |item, index|
28
+ return false if item != other.items[index]
29
+ end
30
+
31
+ true
32
+ end
33
+
34
+ private
35
+
36
+ def cast_value(value)
37
+ @raw = value
38
+ coordinates, create_raw = extract_coordinates(value)
39
+
40
+ @raw = Geometry.from_coordinates(coordinates, type: :multilinestring).as_binary if create_raw
41
+
42
+ @items = coordinates.map do |linestring|
43
+ Linestring.new.send(
44
+ :cast_value,
45
+ {
46
+ coordinates: linestring
47
+ }
48
+ )
49
+ end
50
+
51
+ self
52
+ rescue StandardError => e
53
+ @error = e.message
54
+
55
+ self
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordMysqlSpatial
4
+ module ActiveRecord
5
+ module MySQL
6
+ class Point < Base
7
+ attr_reader :x,
8
+ :y
9
+
10
+ def type
11
+ :point
12
+ end
13
+
14
+ def serialize(value)
15
+ if value.is_a?(Point)
16
+ value
17
+ else
18
+ return unless valid_hash?(value, type: :point)
19
+
20
+ cast_value(value)
21
+ end
22
+ end
23
+
24
+ def to_sql
25
+ return nil if @x.nil? || @y.nil?
26
+
27
+ "Point(#{to_coordinate_sql})"
28
+ end
29
+
30
+ def to_coordinate_sql
31
+ return nil if @x.nil? || @y.nil?
32
+
33
+ "#{@x} #{@y}"
34
+ end
35
+
36
+ alias to_coordinates_sql to_coordinate_sql
37
+
38
+ def ==(other)
39
+ x == other.x && y == other.y
40
+ end
41
+
42
+ private
43
+
44
+ def cast_value(value)
45
+ @raw = value
46
+ coordinate, create_raw = extract_coordinates(value, type: :point)
47
+
48
+ @raw = Geometry.from_coordinates(coordinate, type: :point).as_binary if create_raw
49
+
50
+ @x, @y = if coordinate.is_a?(Array) && coordinate.present?
51
+ [coordinate[0], coordinate[1]]
52
+ elsif value.is_a?(Array)
53
+ [value[0], value[1]]
54
+ elsif value.is_a?(Hash)
55
+ [value[:x] || value['x'], value[:y] || value['y']]
56
+ else
57
+ [nil, nil]
58
+ end
59
+
60
+ self
61
+ rescue StandardError => e
62
+ @error = e.message
63
+
64
+ self
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module ActiveRecordMysqlSpatial
6
+ module ActiveRecord
7
+ module NativeTypes
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ class_eval do
12
+ def native_database_types
13
+ self.class.const_get('NATIVE_DATABASE_TYPES').merge(
14
+ point: {
15
+ name: 'point'
16
+ },
17
+ linestring: {
18
+ name: 'linestring'
19
+ },
20
+ multilinestring: {
21
+ name: 'multilinestring'
22
+ }
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'mysql/linestring'
4
+ require_relative 'mysql/multilinestring'
5
+ require_relative 'mysql/point'
6
+
7
+ module ActiveRecordMysqlSpatial
8
+ module ActiveRecord
9
+ module Quoting
10
+ def quote(value)
11
+ case value
12
+ when MySQL::Linestring, MySQL::Multilinestring, MySQL::Point
13
+ quote_geom(value)
14
+ else
15
+ super
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def quote_geom(value)
22
+ spatial_raw = value.to_sql
23
+
24
+ return 'NULL' if spatial_raw.blank?
25
+
26
+ "ST_GeomFromText('#{spatial_raw}')"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ require_relative 'mysql/linestring'
6
+ require_relative 'mysql/multilinestring'
7
+ require_relative 'mysql/point'
8
+
9
+ module ActiveRecordMysqlSpatial
10
+ module ActiveRecord
11
+ module RegisterTypes
12
+ extend ActiveSupport::Concern
13
+
14
+ included do
15
+ class_eval do
16
+ class << self
17
+ private
18
+
19
+ def custom_initialize_type_map(type_map)
20
+ type_map.register_type('point', MySQL::Point.new)
21
+ type_map.register_type('linestring', MySQL::Linestring.new)
22
+ type_map.register_type('multilinestring', MySQL::Multilinestring.new)
23
+ end
24
+ end
25
+
26
+ remove_const('TYPE_MAP')
27
+ const_set(
28
+ 'TYPE_MAP',
29
+ ::ActiveRecord::Type::TypeMap.new.tap do |m|
30
+ initialize_type_map(m)
31
+ custom_initialize_type_map(m)
32
+ end
33
+ )
34
+ end
35
+
36
+ ::ActiveRecord::Type.register(:point, MySQL::Point, adapter: :mysql2)
37
+ ::ActiveRecord::Type.register(:linestring, MySQL::Linestring, adapter: :mysql2)
38
+ ::ActiveRecord::Type.register(:multilinestring, MySQL::Multilinestring, adapter: :mysql2)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordMysqlSpatial
4
+ class Geometry
5
+ class << self
6
+ def parse_bin(bin)
7
+ parser.parse(bin[4..])
8
+ end
9
+
10
+ def from_coordinates(coordinates, type: :linestring)
11
+ send("#{type}_from_coords", coordinates)
12
+ end
13
+
14
+ def from_text(value)
15
+ cartesian_factory.parse_wkt(value)
16
+ rescue StandardError
17
+ nil
18
+ end
19
+
20
+ def valid_spatial?(value)
21
+ point?(value) || linestring?(value) || multilinestring?(value)
22
+ end
23
+
24
+ def point?(value)
25
+ /^point\(/i.match?(value)
26
+ end
27
+
28
+ def linestring?(value)
29
+ /^linestring\(/i.match?(value)
30
+ end
31
+
32
+ def multilinestring?(value)
33
+ /^multilinestring\(/i.match?(value)
34
+ end
35
+
36
+ private
37
+
38
+ def point_from_coords(coordinate)
39
+ x, y = coordinate
40
+
41
+ cartesian_factory.point(x, y)
42
+ end
43
+
44
+ def linestring_from_coords(coordinates)
45
+ points = coordinates.map do |x, y|
46
+ cartesian_factory.point(x, y)
47
+ end
48
+
49
+ cartesian_factory.line_string(points)
50
+ end
51
+
52
+ def multilinestring_from_coords(coordinates)
53
+ line_strings = coordinates.map do |coordinate|
54
+ linestring_from_coords(coordinate)
55
+ end
56
+
57
+ cartesian_factory.multi_line_string(line_strings)
58
+ end
59
+
60
+ def parser
61
+ @parser ||= RGeo::WKRep::WKBParser.new
62
+ end
63
+
64
+ def cartesian_factory
65
+ @cartesian_factory ||= RGeo::Cartesian::Factory.new
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordMysqlSpatial
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rgeo'
4
+
5
+ require_relative 'active_record_mysql_spatial/active_record/column_methods'
6
+ require_relative 'active_record_mysql_spatial/active_record/mysql/linestring'
7
+ require_relative 'active_record_mysql_spatial/active_record/mysql/multilinestring'
8
+ require_relative 'active_record_mysql_spatial/active_record/mysql/point'
9
+ require_relative 'active_record_mysql_spatial/active_record/native_types'
10
+ require_relative 'active_record_mysql_spatial/active_record/quoting'
11
+ require_relative 'active_record_mysql_spatial/active_record/register_types'
12
+ require_relative 'active_record_mysql_spatial/geometry'
13
+ require_relative 'active_record_mysql_spatial/version'
14
+
15
+ module ActiveRecordMysqlSpatial
16
+ class Error < StandardError; end
17
+
18
+ class Railtie < Rails::Railtie
19
+ initializer 'active_record.override_mysql_spatial' do
20
+ ::ActiveSupport.on_load(:active_record) do
21
+ ::ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.include ActiveRecordMysqlSpatial::ActiveRecord::Quoting
22
+ ::ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.include ActiveRecordMysqlSpatial::ActiveRecord::NativeTypes
23
+
24
+ ::ActiveRecord::ConnectionAdapters::Mysql2Adapter.include ActiveRecordMysqlSpatial::ActiveRecord::RegisterTypes
25
+
26
+ ::ActiveRecord::ConnectionAdapters::MySQL::Table.include ActiveRecordMysqlSpatial::ActiveRecord::ColumnMethods
27
+
28
+ ::ActiveRecord::ConnectionAdapters::MySQL::TableDefinition.include ActiveRecordMysqlSpatial::ActiveRecord::ColumnMethods
29
+ end
30
+ end
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_record_mysql_spatial
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alpha
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-09-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rgeo
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 3.0.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 3.0.1
33
+ description: MySQL Spatial Data Types for ActiveRecord
34
+ email:
35
+ - alphanolucifer@gmail.com
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - README.md
41
+ - Rakefile
42
+ - active_record_mysql_spatial.gemspec
43
+ - lib/active_record_mysql_spatial.rb
44
+ - lib/active_record_mysql_spatial/active_record/column_methods.rb
45
+ - lib/active_record_mysql_spatial/active_record/mysql/base.rb
46
+ - lib/active_record_mysql_spatial/active_record/mysql/linestring.rb
47
+ - lib/active_record_mysql_spatial/active_record/mysql/multilinestring.rb
48
+ - lib/active_record_mysql_spatial/active_record/mysql/point.rb
49
+ - lib/active_record_mysql_spatial/active_record/native_types.rb
50
+ - lib/active_record_mysql_spatial/active_record/quoting.rb
51
+ - lib/active_record_mysql_spatial/active_record/register_types.rb
52
+ - lib/active_record_mysql_spatial/geometry.rb
53
+ - lib/active_record_mysql_spatial/version.rb
54
+ homepage: https://github.com/zgid123/active_record_mysql_spatial
55
+ licenses:
56
+ - MIT
57
+ metadata:
58
+ homepage_uri: https://github.com/zgid123/active_record_mysql_spatial
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 2.6.0
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.4.13
75
+ signing_key:
76
+ specification_version: 4
77
+ summary: MySQL Spatial Data Types for ActiveRecord
78
+ test_files: []