Skip to content

Commit

Permalink
Merge pull request #50 from felixbuenemann/add-support-for-single-rec…
Browse files Browse the repository at this point in the history
…ord-serialization

Add support for single record serialization
  • Loading branch information
felixbuenemann committed Apr 15, 2016
2 parents f302397 + 52fb8d1 commit 0c2d483
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 6 deletions.
1 change: 1 addition & 0 deletions lib/postgres_ext/serializers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ module Serializers
require 'active_model_serializers'

ActiveModel::ArraySerializer.send :prepend, PostgresExt::Serializers::ActiveModel::ArraySerializer
ActiveModel::Serializer.send :prepend, PostgresExt::Serializers::ActiveModel::Serializer
1 change: 1 addition & 0 deletions lib/postgres_ext/serializers/active_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ module ActiveModel
end

require 'postgres_ext/serializers/active_model/array_serializer'
require 'postgres_ext/serializers/active_model/serializer'
13 changes: 9 additions & 4 deletions lib/postgres_ext/serializers/active_model/array_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def _postgres_serializable_array
_reset_internal_state!
_include_relation_in_root(object, serializer: @options[:each_serializer], root: @options[:root])

jsons_select_manager = _results_table_arel
jsons_select_manager = _results_table_arel(@options[:single_record] ? @options[:root] : nil)
jsons_select_manager.with @_ctes

@_connection.select_value _to_sql(jsons_select_manager)
Expand Down Expand Up @@ -202,7 +202,7 @@ def _coalesce_arrays(column, aliaz = nil)
_postgres_function_node 'COALESCE', [column, Arel.sql("'{}'")], aliaz
end

def _results_table_arel
def _results_table_arel(singular_key = nil)
tables = []
@_results_tables.each do |key, array|
json_table = array
Expand All @@ -211,9 +211,14 @@ def _results_table_arel
json_table = Arel::Nodes::As.new json_table, Arel.sql("tbl")
json_table = Arel::Table.new(:t).from(json_table)

json_select_manager = @_connection.send('postgresql_version') >= 90300 ?
json_table.project("COALESCE(json_agg(tbl), '[]') as #{key}, 1 as match") :
json_select_manager = if singular_key.to_s == key.to_s
# Single resource is embedded as object instead of array.
json_table.project("COALESCE(row_to_json(tbl), '{}') as #{key}, 1 as match")
elsif @_connection.send('postgresql_version') >= 90300
json_table.project("COALESCE(json_agg(tbl), '[]') as #{key}, 1 as match")
else
json_table.project("COALESCE(array_to_json(array_agg(row_to_json(tbl))), '[]') as #{key}, 1 as match")
end

@_ctes << _postgres_cte_as("#{key}_as_json_array", _to_sql(json_select_manager))
tables << { table: "#{key}_as_json_array", column: key }
Expand Down
38 changes: 38 additions & 0 deletions lib/postgres_ext/serializers/active_model/serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module PostgresExt::Serializers::ActiveModel
module Serializer
def self.prepended(base)
class << base
prepend ClassMethods
end
end

module ClassMethods
# Wrap ActiveModel::Serializer.build_json
# to send single records through ArraySerializer
# enabling database serialization.
def build_json(controller, resource, options)
serializer_instance = super
return serializer_instance unless serializer_instance

default_options = controller.send(:default_serializer_options) || {}
options = default_options.merge(options || {})

if ActiveRecord::Base === resource && options[:root] != false && serializer_instance.root_name != false
options[:root] ||= serializer_instance.root_name
options[:each_serializer] = serializer_instance.class
options[:single_record] = options.fetch(:single_record, true)
options.delete(:serializer) # Reset to default ArraySerializer.

# Wrap Record in a Relation.
klass = resource.class
primary_key = klass.primary_key
resource = klass.where(primary_key => resource.send(primary_key)).limit(1)

super(controller, resource, options)
else
serializer_instance
end
end
end
end
end
60 changes: 58 additions & 2 deletions test/serializer_test.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
require 'test_helper'


describe 'ArraySerializer patch' do
let(:json_data) { ActiveModel::Serializer.build_json(controller, relation, options).to_json }
let(:serializer) { ActiveModel::Serializer.build_json(controller, relation, options) }
let(:json_data) { serializer.to_json }

context 'specify serializer' do
let(:relation) { Note.all }
Expand Down Expand Up @@ -70,4 +70,60 @@
json_data.must_equal json_expected
end
end

context 'serialize singular record' do
let(:relation) { Note.where(name: 'Title').first }
let(:controller) { NotesController.new }
let(:options) { }

before do
@note = Note.create content: 'Test', name: 'Title'
@tag = Tag.create name: 'My tag', note: @note, popular: true
end

it 'uses the array serializer' do
serializer.must_be_instance_of ActiveModel::ArraySerializer
end

it 'generates the proper json output' do
json_expected = %{{"note":{"id":#{@note.id},"content":"Test","name":"Title","tag_ids":[#{@tag.id}]},"tags":[{"id":#{@tag.id},"name":"My tag","note_id":#{@note.id}}]}}
json_data.must_equal json_expected
end
end

context 'serialize single record with custom serializer' do
let(:relation) { Note.where(name: 'Title').first }
let(:controller) { NotesController.new }
let(:options) { { serializer: OtherNoteSerializer } }

before do
@note = Note.create content: 'Test', name: 'Title'
@tag = Tag.create name: 'My tag', note: @note
end

it 'uses the array serializer' do
serializer.must_be_instance_of ActiveModel::ArraySerializer
end

it 'generates the proper json output' do
json_expected = %{{"other_note":{"id":#{@note.id},"name":"Title","tag_ids":[#{@tag.id}]},"tags":[{"id":#{@tag.id},"name":"My tag"}]}}
json_data.must_equal json_expected
end
end

context 'force single record mode' do
let(:relation) { Note.where(name: 'Title').limit(1) }
let(:controller) { NotesController.new }
let(:options) { { root: 'note', single_record: true } }

before do
@note = Note.create content: 'Test', name: 'Title'
@tag = Tag.create name: 'My tag', note: @note, popular: true
end

it 'generates the proper json output' do
json_expected = %{{"note":{"id":#{@note.id},"content":"Test","name":"Title","tag_ids":[#{@tag.id}]},"tags":[{"id":#{@tag.id},"name":"My tag","note_id":#{@note.id}}]}}
json_data.must_equal json_expected
end
end
end
1 change: 1 addition & 0 deletions test/sideloading_test.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require 'test_helper'

describe 'ArraySerializer patch' do
let(:json_data) { ActiveModel::Serializer.build_json(controller, relation, options).to_json }
Expand Down

0 comments on commit 0c2d483

Please sign in to comment.