Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #7527 from guedes/pg9.2_json_support

AR supporting new JSON data type on PostgreSQL >= 9.2
  • Loading branch information...
commit a690935804a845a22298ad7a066f8c50404952a8 2 parents ddaeaef + 3b516b5
Rafael Mendonça França rafaelfranca authored
5 activerecord/CHANGELOG.md
View
@@ -14,6 +14,11 @@
*Ian Lesperance*
+* Allow JSON columns to be created in PostgreSQL and properly encoded/decoded
+ to/from database.
+
+ *Dickson S. Guedes*
+
* Fix time column type casting for invalid time string values to correctly return nil.
*Adam Meehan*
1  activerecord/lib/active_record/connection_adapters/column.rb
View
@@ -124,6 +124,7 @@ def type_cast_code(var_name)
when :boolean then "#{klass}.value_to_boolean(#{var_name})"
when :hstore then "#{klass}.string_to_hstore(#{var_name})"
when :inet, :cidr then "#{klass}.string_to_cidr(#{var_name})"
+ when :json then "#{klass}.string_to_json(#{var_name})"
else var_name
end
end
16 activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
View
@@ -37,6 +37,22 @@ def string_to_hstore(string)
end
end
+ def json_to_string(object)
+ if Hash === object
+ ActiveSupport::JSON.encode(object)
+ else
+ object
+ end
+ end
+
+ def string_to_json(string)
+ if String === string
+ ActiveSupport::JSON.decode(string)
+ else
+ string
+ end
+ end
+
def string_to_cidr(string)
if string.nil?
nil
9 activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
View
@@ -145,6 +145,14 @@ def type_cast(value)
end
end
+ class Json < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::PostgreSQLColumn.string_to_json value
+ end
+ end
+
class TypeMap
def initialize
@mapping = {}
@@ -244,6 +252,7 @@ def self.registered_type?(name)
register_type 'polygon', OID::Identity.new
register_type 'circle', OID::Identity.new
register_type 'hstore', OID::Hstore.new
+ register_type 'json', OID::Json.new
register_type 'cidr', OID::Cidr.new
alias_type 'inet', 'cidr'
8 activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
View
@@ -22,6 +22,7 @@ def quote(value, column = nil) #:nodoc:
when Hash
case column.sql_type
when 'hstore' then super(PostgreSQLColumn.hstore_to_string(value), column)
+ when 'json' then super(PostgreSQLColumn.json_to_string(value), column)
else super
end
when IPAddr
@@ -66,8 +67,11 @@ def type_cast(value, column)
return super unless 'bytea' == column.sql_type
{ :value => value, :format => 1 }
when Hash
- return super unless 'hstore' == column.sql_type
- PostgreSQLColumn.hstore_to_string(value)
+ case column.sql_type
+ when 'hstore' then PostgreSQLColumn.hstore_to_string(value)
+ when 'json' then PostgreSQLColumn.json_to_string(value)
+ else super
+ end
when IPAddr
return super unless ['inet','cidr'].includes? column.sql_type
PostgreSQLColumn.cidr_to_string(value)
13 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
View
@@ -106,6 +106,9 @@ def self.extract_value_from_default(default)
# Hstore
when /\A'(.*)'::hstore\z/
$1
+ # JSON
+ when /\A'(.*)'::json\z/
+ $1
# Object identifier types
when /\A-?\d+\z/
$1
@@ -201,6 +204,9 @@ def simplified_type(field_type)
# UUID type
when 'uuid'
:uuid
+ # JSON type
+ when 'json'
+ :json
# Small and big integer types
when /^(?:small|big)int$/
:integer
@@ -267,6 +273,10 @@ def macaddr(name, options = {})
def uuid(name, options = {})
column(name, 'uuid', options)
end
+
+ def json(name, options = {})
+ column(name, 'json', options)
+ end
end
ADAPTER_NAME = 'PostgreSQL'
@@ -290,7 +300,8 @@ def uuid(name, options = {})
inet: { name: "inet" },
cidr: { name: "cidr" },
macaddr: { name: "macaddr" },
- uuid: { name: "uuid" }
+ uuid: { name: "uuid" },
+ json: { name: "json" }
}
include Quoting
69 activerecord/test/cases/adapters/postgresql/json_test.rb
View
@@ -0,0 +1,69 @@
+# encoding: utf-8
+
+require "cases/helper"
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlJSONTest < ActiveRecord::TestCase
+ class JsonDataType < ActiveRecord::Base
+ self.table_name = 'json_data_type'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ begin
+ @connection.create_table('json_data_type') do |t|
+ t.json 'payload', :default => {}
+ end
+ rescue ActiveRecord::StatementInvalid
+ return skip "do not test on PG without json"
+ end
+ @column = JsonDataType.columns.find { |c| c.name == 'payload' }
+ end
+
+ def teardown
+ @connection.execute 'drop table if exists json_data_type'
+ end
+
+ def test_column
+ assert_equal :json, @column.type
+ end
+
+ def test_type_cast_json
+ assert @column
+
+ data = "{\"a_key\":\"a_value\"}"
+ hash = @column.class.string_to_json data
+ assert_equal({'a_key' => 'a_value'}, hash)
+ assert_equal({'a_key' => 'a_value'}, @column.type_cast(data))
+
+ assert_equal({}, @column.type_cast("{}"))
+ assert_equal({'key'=>nil}, @column.type_cast('{"key": null}'))
+ assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast(%q({"c":"}", "\"a\"":"b \"a b"})))
+ end
+
+ def test_rewrite
+ @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
+ x = JsonDataType.first
+ x.payload = { '"a\'' => 'b' }
+ assert x.save!
+ end
+
+ def test_select
+ @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
+ x = JsonDataType.first
+ assert_equal({'k' => 'v'}, x.payload)
+ end
+
+ def test_select_multikey
+ @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|
+ x = JsonDataType.first
+ assert_equal({'k1' => 'v1', 'k2' => 'v2', 'k3' => [1,2,3]}, x.payload)
+ end
+
+ def test_null_json
+ @connection.execute %q|insert into json_data_type (payload) VALUES(null)|
+ x = JsonDataType.first
+ assert_equal(nil, x.payload)
+ end
+end
7 activerecord/test/cases/schema_dumper_test.rb
View
@@ -236,6 +236,13 @@ def test_schema_dump_includes_xml_shorthand_definition
end
end
+ def test_schema_dump_includes_json_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_json_data_type"} =~ output
+ assert_match %r|t.json "json_data", :default => {}|, output
+ end
+ end
+
def test_schema_dump_includes_inet_shorthand_definition
output = standard_dump
if %r{create_table "postgresql_network_address"} =~ output
11 activerecord/test/schema/postgresql_specific_schema.rb
View
@@ -1,7 +1,7 @@
ActiveRecord::Schema.define do
%w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids
- postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
+ postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type).each do |table_name|
execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
@@ -82,6 +82,15 @@
_SQL
end
+ if 't' == select_value("select 'json'=ANY(select typname from pg_type)")
+ execute <<_SQL
+ CREATE TABLE postgresql_json_data_type (
+ id SERIAL PRIMARY KEY,
+ json_data json default '{}'::json
+ );
+_SQL
+ end
+
execute <<_SQL
CREATE TABLE postgresql_moneys (
id SERIAL PRIMARY KEY,
Please sign in to comment.
Something went wrong with that request. Please try again.