Created
May 1, 2017 21:56
-
-
Save mickey/3f9186bbee1eefa98076a891303e5e83 to your computer and use it in GitHub Desktop.
ActiveRecord patches for partial SPATIAL and JSON support in MySQL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "spec_helper" | |
describe ActiveRecord::Type::JSON do | |
class TestJSON < ActiveRecord::Base | |
end | |
before do | |
connection = ActiveRecord::Base.connection | |
if connection.table_exists?(:test_jsons) | |
connection.drop_table(:test_jsons) | |
end | |
connection.create_table(:test_jsons) do |t| | |
t.json :data | |
end | |
end | |
let(:json_data) { {'foo' => 'bar', 'baz' => [1,2,3,'yolo']} } | |
it "saves, get and updates a json field", truncation: true do | |
json = TestJSON.create!({ | |
data: json_data | |
}) | |
expect(json.reload.data).to eq(json_data) | |
json = TestJSON.new({ | |
data: json_data | |
}) | |
json.save! | |
expect(json.reload.data).to eq(json_data) | |
json.reload | |
expect(json.reload.data).to eq(json_data) | |
json.update_attributes!({ | |
data: ['apple', 'banana'] | |
}) | |
expect(json.reload.data).to eq(['apple', 'banana']) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "spec_helper" | |
describe ActiveRecord::Type::MultiPolygon do | |
class TestMultiPolygon < ActiveRecord::Base | |
end | |
before do | |
connection = ActiveRecord::Base.connection | |
if connection.table_exists?(:test_multi_polygons) | |
connection.drop_table(:test_multi_polygons) | |
end | |
connection.create_table(:test_multi_polygons) do |t| | |
t.multipolygon :geom, null: false | |
end | |
connection.add_index :test_multi_polygons, :geom, type: :spatial | |
end | |
let(:multipolygon_wkt) { 'MULTIPOLYGON(((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))' } | |
let(:srid) { 4326 } | |
it "saves multipolygons", truncation: true do | |
TestMultiPolygon.create!({ | |
geom: { value: multipolygon_wkt, srid: srid} | |
}) | |
end | |
it "cannot deserialize multipolygons", truncation: true do | |
record = TestMultiPolygon.create!({ | |
geom: { value: multipolygon_wkt, srid: srid} | |
}) | |
expect{ record.reload.geom }.to raise_error(RuntimeError, "Cannot deserialize multipolygons") | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "spec_helper" | |
describe ActiveRecord::Type::Point do | |
class TestPoint < ActiveRecord::Base | |
end | |
before do | |
connection = ActiveRecord::Base.connection | |
if connection.table_exists?(:test_points) | |
connection.drop_table(:test_points) | |
end | |
connection.create_table(:test_points) do |t| | |
t.point :coordinates, null: false | |
end | |
connection.add_index :test_points, :coordinates, type: :spatial | |
end | |
it "saves, get and updates a point", truncation: true do | |
point = TestPoint.create!({ | |
coordinates: [1.1, 1.2] | |
}) | |
expect(point.reload.coordinates).to eq([1.1, 1.2]) | |
point = TestPoint.new({ | |
coordinates: [1.3, 1.4] | |
}) | |
point.save! | |
expect(point.reload.coordinates).to eq([1.3, 1.4]) | |
point.reload | |
expect(point.reload.coordinates).to eq([1.3, 1.4]) | |
point.update_attributes!({ | |
coordinates: [3.1, 4.1] | |
}) | |
expect(point.reload.coordinates).to eq([3.1, 4.1]) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
raise "This monkey patch is only guaranteed for Rails 4.2.x" unless Rails.version =~ /4\.2\.\d+/ | |
module ActiveRecord | |
module Type | |
class JSON < Value | |
def binary? | |
false | |
end | |
def type | |
:json | |
end | |
def type_cast_for_database(value) | |
Oj.dump(value) | |
end | |
def type_cast(value) | |
if value.is_a?(::String) | |
Oj.load(value) | |
else | |
value | |
end | |
end | |
def changed_in_place?(raw_old_value, new_value) | |
type_cast(raw_old_value) != type_cast(type_cast_for_database(new_value)) | |
end | |
def type_cast_for_schema(value) | |
"json" | |
end | |
end | |
end | |
end | |
module ActiveRecord::ConnectionAdapters | |
class TableDefinition | |
def json(name, options = {}) | |
column(name, :json, options) | |
end | |
end | |
class AbstractMysqlAdapter | |
def initialize_type_map_with_json(m) | |
initialize_type_map_without_json(m) | |
m.register_type %r(json)i, ActiveRecord::Type::JSON.new | |
end | |
alias_method_chain :initialize_type_map, :json | |
end | |
end | |
ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::NATIVE_DATABASE_TYPES[:json] = { name: "json" } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
raise "This monkey patch is only guaranteed for Rails 4.2.x" unless Rails.version =~ /4\.2\.\d+/ | |
module ActiveRecord | |
module Type | |
class MultiPolygon < Value | |
def binary? | |
true | |
end | |
def type | |
:multipolygon | |
end | |
def type_cast_for_database(value) | |
value | |
end | |
def type_cast(value) | |
raise RuntimeError, "Cannot deserialize multipolygons" if value.is_a?(::String) | |
value | |
end | |
def type_cast_for_schema(value) | |
"multipolygon" | |
end | |
end | |
end | |
end | |
module ActiveRecord::ConnectionAdapters | |
class TableDefinition | |
def multipolygon(name, options = {}) | |
column(name, :multipolygon, options) | |
end | |
end | |
class AbstractMysqlAdapter | |
def quote_with_multipolygon(value, column = nil) | |
if column && column.cast_type.is_a?(ActiveRecord::Type::MultiPolygon) && value.is_a?(Hash) | |
"ST_MultiPolygonFromText('#{value[:value]}', #{value.fetch(:srid, 4326).to_i})" | |
else | |
quote_without_multipolygon(value, column) | |
end | |
end | |
alias_method_chain :quote, :multipolygon | |
def initialize_type_map_with_multipolygon(m) | |
initialize_type_map_without_multipolygon(m) | |
m.register_type %r(multipolygon)i, ActiveRecord::Type::MultiPolygon.new | |
end | |
alias_method_chain :initialize_type_map, :multipolygon | |
end | |
end | |
ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::NATIVE_DATABASE_TYPES[:multipolygon] = { name: "multipolygon" } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
raise "This monkey patch is only guaranteed for Rails 4.2.x" unless Rails.version =~ /4\.2\.\d+/ | |
module ActiveRecord | |
module Type | |
class Point < Value | |
def binary? | |
true | |
end | |
def type | |
:point | |
end | |
def type_cast_for_database(value) | |
value | |
end | |
def type_cast(value) | |
if value.is_a?(Array) | |
value | |
else | |
value.unpack('LcLd2')[-2..-1] | |
end | |
end | |
def type_cast_for_schema(value) | |
"point" | |
end | |
end | |
end | |
end | |
module ActiveRecord::ConnectionAdapters | |
class TableDefinition | |
def point(name, options = {}) | |
column(name, :point, options) | |
end | |
end | |
class AbstractMysqlAdapter | |
def quote_with_point(value, column = nil) | |
if column && column.cast_type.is_a?(ActiveRecord::Type::Point) && value.is_a?(Array) | |
"POINT(#{value[0]}, #{value[1]})" | |
else | |
quote_without_point(value, column) | |
end | |
end | |
alias_method_chain :quote, :point | |
# Column prefix lengths are prohibited for SPATIAL indexes. The full width of each column is indexed. | |
# http://dev.mysql.com/doc/refman/5.7/en/create-index.html | |
def indexes_with_geometry(table_name, name = nil) | |
indexes = indexes_without_geometry(table_name, name) | |
indexes.each do |index| | |
index.lengths = [] if index.type == :spatial && index.lengths.present? | |
end | |
indexes | |
end | |
alias_method_chain :indexes, :geometry | |
def initialize_type_map_with_point(m) | |
initialize_type_map_without_point(m) | |
m.register_type %r(point)i, ActiveRecord::Type::Point.new | |
end | |
alias_method_chain :initialize_type_map, :point | |
end | |
end | |
ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::NATIVE_DATABASE_TYPES[:point] = { name: "point" } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Arel | |
module Predications | |
def json_contains(value) | |
Nodes::NamedFunction.new('JSON_CONTAINS', [self, Nodes.build_quoted(value.to_json)]) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment