active_model_serializers_pg 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 749a92cbe1a5ef80dbfcf46cda82127108e0497465258e19a2023e0847b9696d
4
- data.tar.gz: 32956f1353aa108fd173d02b2da79391ba45c286efc1e6080bb4e4927f630bf2
3
+ metadata.gz: 4bdfe8c6e1f9a0fbca6e444a7594474d43d9415008c86f6fe30964088fc8c1f7
4
+ data.tar.gz: 0e5a9f884cda2663bf2f36051ad42367381ea5f61798048cd4ae774c19c76265
5
5
  SHA512:
6
- metadata.gz: f9861a254e392c60be71bb396497e2f9fb226c6c9b1054635d1bd6dcb3bbd04992e5126d084a2f80d277c1d78603e9f82fb9eb166f6d545c373576eefa8d7fc7
7
- data.tar.gz: ac39133be71300ecea935c95f090e2282b48bd55cd00431e905e04cf32974efec50e8fa34f6b2b4ec10652ae24201eb33c55b7c0c6ef42d8731a35144d761f42
6
+ metadata.gz: 44df1b79df5306e323a62c6fd0a6d0b52e6f2da6da8d6a6eb494fb440a9e09909ecd476cf563fa873a42bddedc40dbdc0efefb95cf13982ca69684b11c93050d
7
+ data.tar.gz: d08476c02adb38cb0978910b0d2448c0f09019ba52592132105612846e51d883d5ab0f26c6ad0f3eaf66c3757f3a93ab59831bfb6652459d47cdf8de37ba342d
data/README.md CHANGED
@@ -6,7 +6,7 @@ This gem adds support for Rails 5 and AMS 0.10.
6
6
  In addition we provide output in [JSON:API](https://jsonapi.org/) format.
7
7
  (I'd like to add normal JSON output too, so let me know if that would be helpful to you.)
8
8
 
9
- Building your JSON is Postgres can reduce your response time 10-100x.
9
+ Building your JSON in Postgres can reduce your response time 10 – 100x.
10
10
  You skip instantiating thousands of Ruby objects (and garbage collecting them later),
11
11
  and Postgres can generate the JSON far more quickly than AMS.
12
12
 
@@ -45,6 +45,16 @@ You could also turn it on for everything but then set `adapter: :json_api` for a
45
45
 
46
46
  Note this gem also respects `ActiveModelSerializers.config.key_transform = :dash`, if you are using that.
47
47
 
48
+ ### Supports
49
+
50
+ Here are some other details we support:
51
+
52
+ - `belongs_to`, `has_one`, and `has_many` associations.
53
+ - If you serialize an `enum` you get the string values, not integers.
54
+ - You can serialize an `alias`'d association.
55
+ - We preserve SQL ordering from a model's `default_scope`.
56
+ - We preserve SQL ordering attached to an association.
57
+
48
58
  ### Methods in Serializers and Models
49
59
 
50
60
  If you are using methods to compute properties for your JSON responses
@@ -68,6 +78,26 @@ There is no instance of MyModel created so sql computed properties needs to be
68
78
  a class method. Right now, this string is used as a SQL literal, so be sure to
69
79
  *not* use untrusted values in the return value.
70
80
 
81
+ Similarly we also look for a `foo__sql` method
82
+ for relationships that aren't ActiveRecord associations.
83
+ It must return an `ActiveRecord::Relation` (not a `String`),
84
+ and we will run its SQL inside a `LEFT OUTER JOIN LATERAL`
85
+ (so it has access to the parent table). For example:
86
+
87
+ ```ruby
88
+ class Book < ActiveRecord::Base
89
+
90
+ def essays_by_same_author
91
+ Essay.where(author_id: author_id)
92
+ end
93
+
94
+ def self.essays_by_same_author__sql
95
+ Essay.where("books.author_id = essays.author_id")
96
+ end
97
+
98
+ end
99
+ ```
100
+
71
101
  ## Developing
72
102
 
73
103
  To work on active\_model\_serializers\_pg locally, follow these steps:
@@ -89,6 +119,18 @@ Commands for building/releasing/installing:
89
119
  * `rake install`
90
120
  * `rake release`
91
121
 
122
+ ### TODO
123
+
124
+ Here are things I'd like to support but don't yet:
125
+
126
+ - Use Arel to generate all the SQL.
127
+ - More support of custom scopes attached to associations.
128
+ - Add a non-JSON:API adapter, for traditional JSON output.
129
+ - Have all the tests verify they output the asme JSON as the built-in AMS serializers.
130
+ - Look at AMS's own tests for more features we should support.
131
+ - HABTM associations?
132
+ - `has_many through:` associations?
133
+
92
134
  ## Authors
93
135
 
94
136
  Paul Jungwirth
data/Rakefile CHANGED
@@ -82,6 +82,14 @@ namespace :db do
82
82
  ActiveRecord::Base.connection.create_table :notes, force: true do |t|
83
83
  t.string "name"
84
84
  t.string "content"
85
+ t.integer "state", null: false, default: 0
86
+ t.datetime "created_at"
87
+ t.datetime "updated_at"
88
+ end
89
+
90
+ ActiveRecord::Base.connection.create_table :long_notes, force: true do |t|
91
+ t.string "name"
92
+ t.text "long_content"
85
93
  t.datetime "created_at"
86
94
  t.datetime "updated_at"
87
95
  end
@@ -94,6 +102,14 @@ namespace :db do
94
102
  t.datetime "updated_at"
95
103
  end
96
104
 
105
+ ActiveRecord::Base.connection.create_table :long_tags, force: true do |t|
106
+ t.integer "long_note_id"
107
+ t.string "long_name"
108
+ t.boolean "popular"
109
+ t.datetime "created_at"
110
+ t.datetime "updated_at"
111
+ end
112
+
97
113
  ActiveRecord::Base.connection.create_table :offers, force: true do |t|
98
114
  t.integer "created_by_id"
99
115
  t.integer "reviewed_by_id"
@@ -1,10 +1,13 @@
1
1
  require 'active_model_serializers'
2
2
 
3
+ # We don't really need this
4
+ # because we try to inject our own CollectionSerializer,
5
+ # but keeping it lets us give a nice warning message
6
+ # instead of simply crashing:
3
7
  module ActiveModel
4
8
  class Serializer
5
9
  class CollectionSerializer
6
10
  def element_serializer
7
- # TODO: This is probably not set every time
8
11
  options[:serializer]
9
12
  end
10
13
  end
@@ -56,6 +59,15 @@ module ActiveModelSerializers
56
59
  sql
57
60
  end
58
61
 
62
+ def self.warn_about_collection_serializer
63
+ msg = "You are using an ordinary AMS CollectionSerializer with the json_api_pg adapter, which probably means Rails is pointlessly loading all your ActiveRecord instances *and* running the build JSON-building query in Postgres."
64
+ if Object.const_defined? 'Rails'
65
+ Rails.logger.warn msg
66
+ else
67
+ puts "WARN: #{msg}"
68
+ end
69
+ end
70
+
59
71
  end
60
72
  end
61
73
  end
@@ -317,12 +329,15 @@ class JsonApiPgSql
317
329
  # Make a JsonThing for everything,
318
330
  # cached as the full_name:
319
331
 
320
- # User.where is a Relation, but plain User is not:
332
+ # Watch out: User.where is a Relation, but plain User is not:
321
333
  ar_class = ActiveRecord::Relation === base_relation ? base_relation.klass : base_relation
322
334
 
323
335
  case base_serializer
324
336
  when ActiveModel::Serializer::CollectionSerializer
325
- # base_serializer = base_serializer.to_a.first
337
+ ActiveModelSerializers::Adapter::JsonApiPg.warn_about_collection_serializer
338
+ base_serializer = base_serializer.element_serializer
339
+ @many = true
340
+ when ActiveModelSerializersPg::CollectionSerializer
326
341
  base_serializer = base_serializer.element_serializer
327
342
  @many = true
328
343
  else
@@ -1 +1,6 @@
1
1
  require 'active_model_serializers/adapter/json_api_pg'
2
+ require 'active_model_serializers_pg/collection_serializer'
3
+
4
+ # We have to inject our own CollectionSerializer to avoid materializing ActiveRecord::Relations.
5
+ # See more detailed notes in our class:
6
+ ActiveModel::Serializer.config.collection_serializer = ActiveModelSerializersPg::CollectionSerializer
@@ -0,0 +1,117 @@
1
+ # This is a near-verbatim copy of ActiveModel::Serializer::CollectionSerializer,
2
+ # but we patch the `initialize` method to avoid loading the ActiveRecord::Relation.
3
+ # It's still possible to load it if you call `each`, but we do it lazily.
4
+ # This is based on AMS 0.10.8. For each of updates we mark each of our changes
5
+ # with a `PATCHED` comment.
6
+ #
7
+ # It would be nicer to keep this class mostly empty
8
+ # and just delegate to an ActiveModel::Serializer::CollectionSerializer instance when needed,
9
+ # but then we'd still have to instantiate it eventually,
10
+ # any time we proxy a call (not just each),
11
+ # and that materializes the Relation.
12
+ # TODO: Is it possible to replace its `initialize` class? Or is that too wild even for Ruby?
13
+ module ActiveModelSerializersPg
14
+ class CollectionSerializer
15
+ include Enumerable
16
+ # PATCHED: implement this below so we don't need @serializers
17
+ # delegate :each, to: :@serializers
18
+
19
+ attr_reader :object, :root
20
+
21
+ def initialize(resources, options = {})
22
+ @object = resources
23
+ @options = options
24
+ @root = options[:root]
25
+ # PATCHED: We don't want to iterate unless we have to:
26
+ # @serializers = serializers_from_resources
27
+ end
28
+
29
+ # PATCH: Give ourselves access to the serializer for the individual elements:
30
+ def element_serializer
31
+ options[:serializer]
32
+ end
33
+
34
+ def success?
35
+ true
36
+ end
37
+
38
+ # @api private
39
+ def serializable_hash(adapter_options, options, adapter_instance)
40
+ options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
41
+ options[:cached_attributes] ||= ActiveModel::Serializer.cache_read_multi(self, adapter_instance, options[:include_directive])
42
+ serializers.map do |serializer|
43
+ serializer.serializable_hash(adapter_options, options, adapter_instance)
44
+ end
45
+ end
46
+
47
+ # TODO: unify naming of root, json_key, and _type. Right now, a serializer's
48
+ # json_key comes from the root option or the object's model name, by default.
49
+ # But, if a dev defines a custom `json_key` method with an explicit value,
50
+ # we have no simple way to know that it is safe to call that instance method.
51
+ # (which is really a class property at this point, anyhow).
52
+ # rubocop:disable Metrics/CyclomaticComplexity
53
+ # Disabling cop since it's good to highlight the complexity of this method by
54
+ # including all the logic right here.
55
+ def json_key
56
+ return root if root
57
+ # 1. get from options[:serializer] for empty resource collection
58
+ key = object.empty? &&
59
+ (explicit_serializer_class = options[:serializer]) &&
60
+ explicit_serializer_class._type
61
+ # 2. get from first serializer instance in collection
62
+ key ||= (serializer = serializers.first) && serializer.json_key
63
+ # 3. get from collection name, if a named collection
64
+ key ||= object.respond_to?(:name) ? object.name && object.name.underscore : nil
65
+ # 4. key may be nil for empty collection and no serializer option
66
+ key &&= key.pluralize
67
+ # 5. fail if the key cannot be determined
68
+ key || fail(ArgumentError, 'Cannot infer root key from collection type. Please specify the root or each_serializer option, or render a JSON String')
69
+ end
70
+ # rubocop:enable Metrics/CyclomaticComplexity
71
+
72
+ def paginated?
73
+ ActiveModelSerializers.config.jsonapi_pagination_links_enabled &&
74
+ object.respond_to?(:current_page) &&
75
+ object.respond_to?(:total_pages) &&
76
+ object.respond_to?(:size)
77
+ end
78
+
79
+ # PATCHED: Add a replacement to `each` so we don't change the interface:
80
+ def each
81
+ Rails.logger.debug caller.join("\n")
82
+ Enumerator.new do |y|
83
+ serializers_from_resources.each do |ser|
84
+ y.yield ser
85
+ end
86
+ end
87
+ end
88
+
89
+ protected
90
+
91
+ attr_reader :serializers, :options
92
+
93
+ private
94
+
95
+ def serializers_from_resources
96
+ puts "options here"
97
+ pp options
98
+ serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer)
99
+ object.map do |resource|
100
+ serializer_from_resource(resource, serializer_context_class, options)
101
+ end
102
+ end
103
+
104
+ def serializer_from_resource(resource, serializer_context_class, options)
105
+ serializer_class = options.fetch(:serializer) do
106
+ serializer_context_class.serializer_for(resource, namespace: options[:namespace])
107
+ end
108
+
109
+ if serializer_class.nil?
110
+ ActiveModelSerializers.logger.debug "No serializer found for resource: #{resource.inspect}"
111
+ throw :no_serializer
112
+ else
113
+ serializer_class.new(resource, options.except(:serializer))
114
+ end
115
+ end
116
+ end
117
+ end
@@ -1,3 +1,3 @@
1
1
  module ActiveModelSerializersPg
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
@@ -134,6 +134,68 @@ describe 'ArraySerializer' do
134
134
  }.to_json
135
135
  expect(json_data).to eq json_expected
136
136
  end
137
+
138
+ end
139
+
140
+ context 'with dasherized keys and types' do
141
+ let(:relation) { LongNote.where(name: 'Title').first }
142
+ let(:controller) { LongNotesController.new }
143
+ let(:options) { { include: ['long_tags'] } }
144
+
145
+ before do
146
+ @note = LongNote.create long_content: 'Test', name: 'Title'
147
+ @tag = LongTag.create long_name: 'My tag', long_note: @note, popular: true
148
+ @old_key_setting = ActiveModelSerializers.config.key_transform
149
+ ActiveModelSerializers.config.key_transform = :dash
150
+ end
151
+
152
+ after do
153
+ ActiveModelSerializers.config.key_transform = @old_key_setting
154
+ end
155
+
156
+ it 'generates the proper json output' do
157
+ json_expected = {
158
+ data: {
159
+ id: @note.id.to_s,
160
+ type: 'long-notes',
161
+ attributes: { name: 'Title', 'long-content' => 'Test' },
162
+ relationships: { 'long-tags': { data: [{id: @tag.id.to_s, type: 'long-tags'}] } }
163
+ },
164
+ included: [
165
+ {
166
+ id: @tag.id.to_s,
167
+ type: 'long-tags',
168
+ attributes: { 'long-name' => 'My tag' },
169
+ relationships: { 'long-note' => { data: { id: @note.id.to_s, type: 'long-notes' } } },
170
+ }
171
+ ]
172
+ }.to_json
173
+ expect(json_data).to eq json_expected
174
+ end
175
+ end
176
+
177
+ context 'with aliased association' do
178
+ let(:relation) { Tag.first }
179
+ let(:controller) { TagsController.new }
180
+ let(:options) { { serializer: TagWithAliasedNoteSerializer } }
181
+
182
+ before do
183
+ @note = Note.create content: 'Test', name: 'Title'
184
+ @tag = Tag.create name: 'My tag', note: @note, popular: true
185
+ end
186
+
187
+ it 'generates the proper json output' do
188
+ json_expected = {
189
+ data: {
190
+ id: @tag.id.to_s,
191
+ type: 'tags',
192
+ attributes: { name: 'My tag' },
193
+ relationships: { aliased_note: { data: {id: @note.id.to_s, type: 'notes'} } }
194
+ }
195
+ }.to_json
196
+ expect(json_data).to eq json_expected
197
+ end
198
+
137
199
  end
138
200
 
139
201
  context 'serialize single record with custom serializer' do
@@ -241,7 +303,21 @@ describe 'ArraySerializer' do
241
303
  end
242
304
 
243
305
  it 'does not instantiate ruby objects for relations' do
244
- expect(relation).not_to receive(:to_a)
306
+ # It would be nice to do this instead:
307
+ #
308
+ # expect(relation).not_to receive(:load)
309
+ #
310
+ # or
311
+ #
312
+ # expect(relation).not_to receive(:records)
313
+ #
314
+ # But that causes an infinite loop because rspec works by raising an exception,
315
+ # and ActiveRecord notices that and wants to print an error message,
316
+ # calling `inspect` on the Relation, which tries to load the relation, which....
317
+ # The test still fails, but not in an articulate or informative way.
318
+ # TODO: We could probably write our own customer matcher to work around that,
319
+ # but this does the job for now:
320
+ expect(ActiveModelSerializers::Adapter::JsonApiPg).not_to receive(:warn_about_collection_serializer)
245
321
  json_data
246
322
  end
247
323
  end
@@ -273,7 +349,7 @@ describe 'ArraySerializer' do
273
349
  end
274
350
 
275
351
  it 'does not instantiate ruby objects for relations' do
276
- expect(relation).not_to receive(:to_a)
352
+ expect(ActiveModelSerializers::Adapter::JsonApiPg).not_to receive(:warn_about_collection_serializer)
277
353
  json_data
278
354
  end
279
355
  end
@@ -307,12 +383,11 @@ describe 'ArraySerializer' do
307
383
  end
308
384
 
309
385
  it 'generates the proper json output for the serializer' do
310
- puts json_data
311
386
  expect(json_data).to eq @json_expected
312
387
  end
313
388
 
314
389
  it 'does not instantiate ruby objects for relations' do
315
- expect(relation).not_to receive(:to_a)
390
+ expect(ActiveModelSerializers::Adapter::JsonApiPg).not_to receive(:warn_about_collection_serializer)
316
391
  json_data
317
392
  end
318
393
  end
@@ -354,7 +429,7 @@ describe 'ArraySerializer' do
354
429
  end
355
430
 
356
431
  it 'does not instantiate ruby objects for relations' do
357
- expect(relation).not_to receive(:to_a)
432
+ expect(ActiveModelSerializers::Adapter::JsonApiPg).not_to receive(:warn_about_collection_serializer)
358
433
  json_data
359
434
  end
360
435
  end
@@ -373,7 +448,7 @@ describe 'ArraySerializer' do
373
448
  end
374
449
 
375
450
  it 'does not instantiate ruby objects for relations' do
376
- expect(relation).not_to receive(:to_a)
451
+ expect(ActiveModelSerializers::Adapter::JsonApiPg).not_to receive(:warn_about_collection_serializer)
377
452
  json_data
378
453
  end
379
454
  end
@@ -402,11 +477,40 @@ describe 'ArraySerializer' do
402
477
  end
403
478
 
404
479
  it 'does not instantiate ruby objects for relations' do
405
- expect(relation).not_to receive(:to_a)
480
+ expect(ActiveModelSerializers::Adapter::JsonApiPg).not_to receive(:warn_about_collection_serializer)
406
481
  json_data
407
482
  end
408
483
  end
409
484
 
485
+ context 'with enums' do
486
+ let(:relation) { Note.order(:id) }
487
+ let(:controller) { NotesController.new }
488
+ let(:options) { { each_serializer: NoteWithStateSerializer, fields: { note: [:name, :state] } } }
489
+
490
+ before do
491
+ @note1 = Note.create content: 'Test', name: 'Title 1', state: Note::Published
492
+ @note2 = Note.create content: 'Test', name: 'Title 2', state: Note::Deleted
493
+ end
494
+
495
+ it 'converts enum ints to strings' do
496
+ json_expected = {
497
+ data: [
498
+ {
499
+ id: @note1.id.to_s,
500
+ type: 'notes',
501
+ attributes: { name: 'Title 1', state: 'published' },
502
+ },
503
+ {
504
+ id: @note2.id.to_s,
505
+ type: 'notes',
506
+ attributes: { name: 'Title 2', state: 'deleted' },
507
+ }
508
+ ]
509
+ }.to_json
510
+ expect(json_data).to eq json_expected
511
+ end
512
+ end
513
+
410
514
  context 'support for include_[attrbute]' do
411
515
  let(:relation) { User.all }
412
516
  let(:controller) { UsersController.new }
@@ -565,15 +669,7 @@ describe 'ArraySerializer' do
565
669
 
566
670
  pending 'obeys serializer option in has_many relationship' # Does AMS 0.10 still support this?
567
671
 
568
- pending 'obeys overall :include option'
569
-
570
672
  pending 'obeys :include option in serializer association' # Does AMS 0.10 still support this?
571
673
 
572
- pending 'figures out aliased associations'
573
-
574
- pending 'makes dasherized keys and types'
575
-
576
- pending 'serializes enums'
577
-
578
674
  pending 'uses __sql methods for relationships'
579
675
  end
data/spec/spec_helper.rb CHANGED
@@ -61,6 +61,10 @@ class Note < ActiveRecord::Base
61
61
  has_many :sorted_tags
62
62
  has_many :custom_sorted_tags, lambda { order(:name) }, class_name: 'Tag'
63
63
  has_many :popular_tags, lambda { where(popular: true) }, class_name: 'Tag'
64
+ Draft = :draft
65
+ Published = :published
66
+ Deleted = :deleted
67
+ enum state: [Draft, Published, Deleted]
64
68
  end
65
69
 
66
70
  class NotesController < TestController; end
@@ -70,6 +74,22 @@ class NoteSerializer < ActiveModel::Serializer
70
74
  has_many :tags
71
75
  end
72
76
 
77
+ class NoteWithStateSerializer < ActiveModel::Serializer
78
+ attributes :content, :name, :state
79
+ has_many :tags
80
+ end
81
+
82
+ class LongNote < ActiveRecord::Base
83
+ has_many :long_tags
84
+ end
85
+
86
+ class LongNotesController < TestController; end
87
+
88
+ class LongNoteSerializer < ActiveModel::Serializer
89
+ attributes :long_content, :name
90
+ has_many :long_tags
91
+ end
92
+
73
93
  class ShortTagSerializer < ActiveModel::Serializer
74
94
  attributes :id, :name
75
95
  end
@@ -105,6 +125,7 @@ end
105
125
 
106
126
  class Tag < ActiveRecord::Base
107
127
  belongs_to :note
128
+ alias :aliased_note :note
108
129
  end
109
130
 
110
131
  class SortedTag < Tag
@@ -112,6 +133,15 @@ class SortedTag < Tag
112
133
  default_scope { order(:name) }
113
134
  end
114
135
 
136
+ class LongTag < ActiveRecord::Base
137
+ belongs_to :long_note
138
+ end
139
+
140
+ class LongTagSerializer < ActiveModel::Serializer
141
+ attributes :long_name
142
+ belongs_to :long_note
143
+ end
144
+
115
145
  class TagWithNote < Tag
116
146
  belongs_to :note
117
147
  default_scope { joins(:note) }
@@ -129,6 +159,11 @@ class TagWithNoteSerializer < ActiveModel::Serializer
129
159
  has_one :note
130
160
  end
131
161
 
162
+ class TagWithAliasedNoteSerializer < ActiveModel::Serializer
163
+ attributes :name
164
+ has_one :aliased_note
165
+ end
166
+
132
167
  class User < ActiveRecord::Base
133
168
  has_many :offers, foreign_key: :created_by_id, inverse_of: :created_by
134
169
  has_many :reviewed_offers, foreign_key: :reviewed_by_id, inverse_of: :reviewed_by, class_name: 'Offer'
@@ -172,7 +207,6 @@ DatabaseCleaner.strategy = :deletion
172
207
  RSpec.configure do |config|
173
208
  config.before(:each) do
174
209
  DatabaseCleaner.start
175
- # FactoryBot.lint
176
210
  end
177
211
 
178
212
  config.after(:each) do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_model_serializers_pg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul A. Jungwirth
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-12 00:00:00.000000000 Z
11
+ date: 2019-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: active_model_serializers
@@ -170,6 +170,7 @@ files:
170
170
  - gemfiles/Gemfile.activerecord-5.2.x
171
171
  - lib/active_model_serializers/adapter/json_api_pg.rb
172
172
  - lib/active_model_serializers_pg.rb
173
+ - lib/active_model_serializers_pg/collection_serializer.rb
173
174
  - lib/active_model_serializers_pg/version.rb
174
175
  - spec/serializer_spec.rb
175
176
  - spec/spec_helper.rb