active_model_serializers_pg 0.0.2 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +9 -0
- data/Rakefile +10 -0
- data/active_model_serializers_pg.gemspec +2 -0
- data/gemfiles/Gemfile.activerecord-5.0.x +1 -1
- data/gemfiles/Gemfile.activerecord-5.1.x +1 -1
- data/gemfiles/Gemfile.activerecord-5.2.x +1 -1
- data/lib/active_model_serializers/adapter/json_api_pg.rb +158 -42
- data/lib/active_model_serializers_pg/collection_serializer.rb +13 -15
- data/lib/active_model_serializers_pg/version.rb +1 -1
- data/lib/generators/active_model_serializers_pg/active_model_serializers_pg_generator.rb +20 -0
- data/lib/generators/active_record/active_model_serializers_pg_generator.rb +43 -0
- data/lib/generators/active_record/templates/jsonb_dasherize.sql +32 -0
- data/lib/generators/active_record/templates/migration.rb +11 -0
- data/spec/generators/main_generator_spec.rb +23 -0
- data/spec/serializer_spec.rb +129 -13
- data/spec/spec_helper.rb +30 -2
- metadata +37 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 709fe0bce48637cbd10e3f8a4cc66d9332da0a36ea2158153711c2f49bcdc82f
|
4
|
+
data.tar.gz: bbc3e6c16daf9b8c28ce8d105d1dc355c35b1188b935051447c6b5fab96acda0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c323f581ecea8145344d4fe95df91a76d548762de320a43513eb7081842d30b28e81fb33a4adc3cca173964d17ce5ba7c384e27043cf9aca68e0ca161b9b83b9
|
7
|
+
data.tar.gz: 076e6dad498e0d0533fe63f4a396099c61557c7b2a05a17ebe910f2caca61b11d4cf005351570e7a4579447e9c427ff7298b759f35a15bfe27749144bee44292
|
data/README.md
CHANGED
@@ -31,6 +31,13 @@ Or install it yourself as:
|
|
31
31
|
|
32
32
|
gem install active_model_serializers_pg
|
33
33
|
|
34
|
+
### Migrations
|
35
|
+
|
36
|
+
This gem depends on a SQL function to dasherize hstore/json/jsonb columns, so you must add its migration to your project and run it, like this:
|
37
|
+
|
38
|
+
rails g active_model_serializers_pg
|
39
|
+
rake db:migrate
|
40
|
+
|
34
41
|
## Usage
|
35
42
|
|
36
43
|
You can enable in-Postgres serialization for everything by putting this in a Rails initializer:
|
@@ -52,8 +59,10 @@ Here are some other details we support:
|
|
52
59
|
- `belongs_to`, `has_one`, and `has_many` associations.
|
53
60
|
- If you serialize an `enum` you get the string values, not integers.
|
54
61
|
- You can serialize an `alias`'d association.
|
62
|
+
- You can serialize an `alias_attribute`'d column.
|
55
63
|
- We preserve SQL ordering from a model's `default_scope`.
|
56
64
|
- We preserve SQL ordering attached to an association.
|
65
|
+
- When dasherizing we also dasherize json/jsonb/hstore contents (like standard AMS).
|
57
66
|
|
58
67
|
### Methods in Serializers and Models
|
59
68
|
|
data/Rakefile
CHANGED
@@ -72,9 +72,17 @@ namespace :db do
|
|
72
72
|
task :migrate => :load_db_settings do
|
73
73
|
ActiveRecord::Base.establish_connection
|
74
74
|
|
75
|
+
ActiveRecord::Base.connection.execute "CREATE EXTENSION hstore"
|
76
|
+
|
75
77
|
ActiveRecord::Base.connection.create_table :people, force: true do |t|
|
76
78
|
t.string "first_name"
|
77
79
|
t.string "last_name"
|
80
|
+
t.json "options"
|
81
|
+
t.jsonb "prefs"
|
82
|
+
t.hstore "settings"
|
83
|
+
t.json "selfies", array: true
|
84
|
+
t.jsonb "portraits", array: true
|
85
|
+
t.hstore "landscapes", array: true
|
78
86
|
t.datetime "created_at"
|
79
87
|
t.datetime "updated_at"
|
80
88
|
end
|
@@ -131,6 +139,8 @@ namespace :db do
|
|
131
139
|
t.datetime "updated_at"
|
132
140
|
end
|
133
141
|
|
142
|
+
ActiveRecord::Base.connection.execute File.read(File.expand_path('../lib/generators/active_record/templates/jsonb_dasherize.sql', __FILE__))
|
143
|
+
|
134
144
|
puts 'Database migrated'
|
135
145
|
end
|
136
146
|
end
|
@@ -25,6 +25,8 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.add_development_dependency 'actionpack', '> 4.0'
|
26
26
|
spec.add_development_dependency 'rake'
|
27
27
|
spec.add_development_dependency 'rspec'
|
28
|
+
spec.add_development_dependency 'rspec-rails'
|
29
|
+
spec.add_development_dependency 'generator_spec'
|
28
30
|
spec.add_development_dependency 'bourne', '~> 1.3.0'
|
29
31
|
spec.add_development_dependency 'database_cleaner'
|
30
32
|
spec.add_development_dependency 'dotenv'
|
@@ -8,7 +8,7 @@ module ActiveModel
|
|
8
8
|
class Serializer
|
9
9
|
class CollectionSerializer
|
10
10
|
def element_serializer
|
11
|
-
options[:serializer]
|
11
|
+
options && options[:serializer]
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -23,7 +23,16 @@ module ActiveModelSerializers
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def to_json(options={})
|
26
|
-
|
26
|
+
if relation.nil?
|
27
|
+
ret = { data: [] }
|
28
|
+
if includes.any?
|
29
|
+
# TODO: Can included ever be non-empty when the main data is empty?
|
30
|
+
ret[:included] = []
|
31
|
+
end
|
32
|
+
ret.to_json
|
33
|
+
else
|
34
|
+
connection.select_value serializer_sql
|
35
|
+
end
|
27
36
|
end
|
28
37
|
|
29
38
|
def relation
|
@@ -32,6 +41,10 @@ module ActiveModelSerializers
|
|
32
41
|
|
33
42
|
private
|
34
43
|
|
44
|
+
def includes
|
45
|
+
instance_options && instance_options[:include] || []
|
46
|
+
end
|
47
|
+
|
35
48
|
def connection
|
36
49
|
@connection ||= relation.connection
|
37
50
|
end
|
@@ -42,8 +55,12 @@ module ActiveModelSerializers
|
|
42
55
|
when ActiveRecord::Relation
|
43
56
|
o
|
44
57
|
when Array
|
45
|
-
|
46
|
-
|
58
|
+
o2 = o.first
|
59
|
+
if o2.nil?
|
60
|
+
nil
|
61
|
+
else
|
62
|
+
o2.class.where(id: o.map(&:id))
|
63
|
+
end
|
47
64
|
when ActiveRecord::Base
|
48
65
|
o.class.where(id: o.id)
|
49
66
|
else
|
@@ -60,7 +77,7 @@ module ActiveModelSerializers
|
|
60
77
|
end
|
61
78
|
|
62
79
|
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
|
80
|
+
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 JSON-building query in Postgres."
|
64
81
|
if Object.const_defined? 'Rails'
|
65
82
|
Rails.logger.warn msg
|
66
83
|
else
|
@@ -88,11 +105,11 @@ end
|
|
88
105
|
# i.e. how you got here, not how you'd leave:
|
89
106
|
# "Reflection" seems to be the internal ActiveRecord lingo
|
90
107
|
# for a belongs_to or has_many relationship.
|
91
|
-
# (The public documentation calls these "associations"
|
92
|
-
# I think
|
108
|
+
# (The public documentation calls these "associations".
|
109
|
+
# I think older versions of Rails even used that internally,
|
93
110
|
# but nowadays the method names use "reflection".)
|
94
111
|
class JsonThing
|
95
|
-
attr_reader :ar_class, :full_name, :name, :serializer, :serializer_options, :json_key, :json_type, :reflection, :parent, :cte_name
|
112
|
+
attr_reader :ar_class, :full_name, :name, :serializer, :serializer_options, :json_key, :json_type, :reflection, :parent, :cte_name, :jbs_name
|
96
113
|
delegate :table_name, :primary_key, to: :ar_class
|
97
114
|
delegate :foreign_key, :belongs_to?, :has_many?, :has_one?, to: :reflection
|
98
115
|
|
@@ -113,6 +130,7 @@ class JsonThing
|
|
113
130
|
@parent = parent_json_thing
|
114
131
|
|
115
132
|
@cte_name = _cte_name
|
133
|
+
@jbs_name = _jbs_name
|
116
134
|
@sql_methods = {}
|
117
135
|
end
|
118
136
|
|
@@ -123,26 +141,17 @@ class JsonThing
|
|
123
141
|
JsonThing.new(refl.klass, "#{full_name}.#{reflection_name}", nil, serializer_options, refl, self)
|
124
142
|
end
|
125
143
|
|
126
|
-
# Gets the attributes (i.e. scalar fields) on the AR class
|
127
|
-
# as a Set of symbols.
|
128
|
-
# TODO: tests
|
129
|
-
def declared_attributes
|
130
|
-
@declared_attributes ||= Set.new(@ar_class.attribute_types.keys.map(&:to_sym))
|
131
|
-
end
|
132
|
-
|
133
144
|
def enum?(field)
|
134
145
|
@ar_class.attribute_types[field.to_s].is_a? ActiveRecord::Enum::EnumType
|
135
146
|
end
|
136
147
|
|
137
|
-
#
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
}
|
145
|
-
]
|
148
|
+
# Checks for alias_attribute and gets to the real attribute name.
|
149
|
+
def unaliased(field_name)
|
150
|
+
ret = field_name
|
151
|
+
while field_name = @ar_class.attribute_aliases[field_name.to_s]
|
152
|
+
ret = field_name
|
153
|
+
end
|
154
|
+
ret
|
146
155
|
end
|
147
156
|
|
148
157
|
# TODO: tests
|
@@ -179,10 +188,38 @@ class JsonThing
|
|
179
188
|
if parent.nil?
|
180
189
|
't'
|
181
190
|
else
|
182
|
-
"cte_#{Digest::SHA256.hexdigest(full_name).first(10)}"
|
191
|
+
"cte_#{_cte_name_human_part}_#{Digest::SHA256.hexdigest(full_name).first(10)}"
|
183
192
|
end
|
184
193
|
end
|
185
194
|
|
195
|
+
# Each thing has both a `cte_foo` CTE and a `jbs_foo` CTE.
|
196
|
+
# (jbs stands for "JSONBs" and is meant to take 3 chars like `cte`.)
|
197
|
+
# The former is just the relevant records,
|
198
|
+
# and the second builds the JSON object for each record.
|
199
|
+
# We need to split things into phases like this
|
200
|
+
# because of the JSON:API `relationships` item,
|
201
|
+
# which can contain references in *both directions*.
|
202
|
+
# In that case Postgres will object to our circular dependency.
|
203
|
+
# But with two phases, every jbs_* CTE only depends on cte_* CTEs.
|
204
|
+
def _jbs_name
|
205
|
+
if parent.nil?
|
206
|
+
't'
|
207
|
+
else
|
208
|
+
"jbs_#{_cte_name_human_part}_#{Digest::SHA256.hexdigest(full_name).first(10)}"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Gets a more informative name for the CTE based on the include key.
|
213
|
+
# This makes reading the big SQL query easier, especially for debugging.
|
214
|
+
# Note that Postgres's max identifier length is 63 chars (unless you compile yourself),
|
215
|
+
# and we are spending 4+4+11=19 chars elsewhere on `rel_cte_XXX_1234567890`.
|
216
|
+
# So this method can't return more than 63-19=44 chars.
|
217
|
+
#
|
218
|
+
# Since we quote the CTE names, we don't actually need to remove dots in the name!
|
219
|
+
def _cte_name_human_part
|
220
|
+
@cte_name_human_part ||= full_name[0, 44]
|
221
|
+
end
|
222
|
+
|
186
223
|
def _sql_method(field)
|
187
224
|
m = "#{field}__sql".to_sym
|
188
225
|
if ar_class.respond_to?(m)
|
@@ -321,6 +358,15 @@ end
|
|
321
358
|
class JsonApiPgSql
|
322
359
|
attr_reader :base_serializer, :base_relation
|
323
360
|
|
361
|
+
def self.json_column_type
|
362
|
+
# These classes may not exist, depending on the Rails version:
|
363
|
+
@@json_column_type = if Rails::VERSION::STRING >= '5.2'
|
364
|
+
'ActiveRecord::Type::Json'
|
365
|
+
else
|
366
|
+
'ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Json'
|
367
|
+
end.constantize
|
368
|
+
end
|
369
|
+
|
324
370
|
def initialize(base_serializer, base_relation, instance_options, options)
|
325
371
|
@base_relation = base_relation
|
326
372
|
@instance_options = instance_options
|
@@ -397,10 +443,54 @@ class JsonApiPgSql
|
|
397
443
|
elsif resource.has_sql_method?(field)
|
398
444
|
resource.sql_method(field)
|
399
445
|
else
|
400
|
-
|
446
|
+
field = resource.unaliased(field)
|
447
|
+
# Standard AMS dasherizes json/jsonb/hstore columns,
|
448
|
+
# so we have to do the same:
|
449
|
+
if ActiveModelSerializers.config.key_transform == :dash
|
450
|
+
cl = resource.ar_class.attribute_types[field.to_s]
|
451
|
+
if column_is_jsonb? cl
|
452
|
+
%Q{jsonb_dasherize("#{resource.table_name}"."#{field}")}
|
453
|
+
elsif column_is_jsonb_array? cl
|
454
|
+
# TODO: Could be faster:
|
455
|
+
# If we made the jsonb_dasherize function smarter so it could handle jsonb[],
|
456
|
+
# we wouldn't have to build a json object from the array then cast to jsonb[].
|
457
|
+
%Q{jsonb_dasherize(array_to_json("#{resource.table_name}"."#{field}")::jsonb)}
|
458
|
+
elsif column_is_castable_to_jsonb? cl
|
459
|
+
# Fortunately we can cast hstore to jsonb,
|
460
|
+
# which gives us a solution that works whether or not the hstore extension is installed.
|
461
|
+
# Defining an hstore_dasherize function would work only if the extension were present.
|
462
|
+
%Q{jsonb_dasherize("#{resource.table_name}"."#{field}"::jsonb)}
|
463
|
+
elsif column_is_castable_to_jsonb_array? cl
|
464
|
+
# TODO: Could be faster (see above):
|
465
|
+
%Q{jsonb_dasherize(array_to_json("#{resource.table_name}"."#{field}"::jsonb[])::jsonb)}
|
466
|
+
else
|
467
|
+
%Q{"#{resource.table_name}"."#{field}"}
|
468
|
+
end
|
469
|
+
else
|
470
|
+
%Q{"#{resource.table_name}"."#{field}"}
|
471
|
+
end
|
401
472
|
end
|
402
473
|
end
|
403
474
|
|
475
|
+
def column_is_jsonb?(column_class)
|
476
|
+
column_class.is_a? ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb
|
477
|
+
end
|
478
|
+
|
479
|
+
def column_is_jsonb_array?(column_class)
|
480
|
+
column_class.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array) and
|
481
|
+
column_class.subtype.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb)
|
482
|
+
end
|
483
|
+
|
484
|
+
def column_is_castable_to_jsonb?(column_class)
|
485
|
+
column_class.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Hstore) or
|
486
|
+
column_class.is_a?(self.class.json_column_type)
|
487
|
+
end
|
488
|
+
|
489
|
+
def column_is_castable_to_jsonb_array?(column_class)
|
490
|
+
column_class.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array) and
|
491
|
+
column_is_castable_to_jsonb?(column_class.subtype)
|
492
|
+
end
|
493
|
+
|
404
494
|
def select_resource_relationship_links(resource, reflection)
|
405
495
|
reflection.links.map {|link_name, link_parts|
|
406
496
|
<<~EOQ
|
@@ -423,7 +513,7 @@ class JsonApiPgSql
|
|
423
513
|
refl = resource.reflection
|
424
514
|
<<~EOQ
|
425
515
|
'#{resource.json_key}',
|
426
|
-
jsonb_build_object(#{refl.include_data ? %Q{'data', rel_#{resource.cte_name}.j} : ''}
|
516
|
+
jsonb_build_object(#{refl.include_data ? %Q{'data', "rel_#{resource.cte_name}".j} : ''}
|
427
517
|
#{refl.include_data && refl.links.any? ? ',' : ''}
|
428
518
|
#{refl.links.any? ? %Q{'links', jsonb_build_object(#{select_resource_relationship_links(resource, refl)})} : ''})
|
429
519
|
EOQ
|
@@ -521,39 +611,49 @@ class JsonApiPgSql
|
|
521
611
|
# e.g. buyer and seller for User.
|
522
612
|
# But we could group those and union just them, or even better do a DISTINCT ON (id).
|
523
613
|
# Since we don't get the id here that could be another CTE.
|
524
|
-
|
614
|
+
%Q{UNION SELECT j FROM "#{th.jbs_name}"}
|
525
615
|
}
|
526
616
|
end
|
527
617
|
|
528
618
|
def include_cte_join_condition(resource)
|
529
619
|
parent = resource.parent
|
530
620
|
if resource.belongs_to?
|
531
|
-
%Q{#{parent.cte_name}."#{resource.foreign_key}" = "#{resource.table_name}"."#{resource.primary_key}"}
|
621
|
+
%Q{"#{parent.cte_name}"."#{resource.foreign_key}" = "#{resource.table_name}"."#{resource.primary_key}"}
|
532
622
|
elsif resource.has_many? or resource.has_one?
|
533
|
-
%Q{#{parent.cte_name}."#{parent.primary_key}" = "#{resource.table_name}"."#{resource.foreign_key}"}
|
623
|
+
%Q{"#{parent.cte_name}"."#{parent.primary_key}" = "#{resource.table_name}"."#{resource.foreign_key}"}
|
534
624
|
else
|
535
625
|
raise "not supported relationship: #{resource.full_name}"
|
536
626
|
end
|
537
627
|
end
|
538
628
|
|
629
|
+
# See note in _jbs_name method for why we split each thing into two CTEs.
|
539
630
|
def include_cte(resource)
|
540
|
-
# Sometimes options[:fields] has plural keys and sometimes singular,
|
541
|
-
# so try both:
|
542
631
|
parent = resource.parent
|
543
632
|
<<~EOQ
|
544
633
|
SELECT DISTINCT ON ("#{resource.table_name}"."#{resource.primary_key}")
|
545
|
-
"#{resource.table_name}"
|
546
|
-
#{select_resource(resource)} AS j
|
634
|
+
"#{resource.table_name}".*
|
547
635
|
FROM "#{resource.table_name}"
|
548
|
-
JOIN #{parent.cte_name}
|
636
|
+
JOIN "#{parent.cte_name}"
|
549
637
|
ON #{include_cte_join_condition(resource)}
|
550
|
-
#{join_resource_relationships(resource)}
|
551
638
|
ORDER BY "#{resource.table_name}"."#{resource.primary_key}"
|
552
639
|
EOQ
|
553
640
|
end
|
554
641
|
|
642
|
+
# See note in _jbs_name method for why we split each thing into two CTEs.
|
643
|
+
def include_jbs(resource)
|
644
|
+
<<~EOQ
|
645
|
+
SELECT "#{resource.table_name}".*,
|
646
|
+
#{select_resource(resource)} AS j
|
647
|
+
FROM "#{resource.cte_name}" AS "#{resource.table_name}"
|
648
|
+
#{join_resource_relationships(resource)}
|
649
|
+
EOQ
|
650
|
+
end
|
651
|
+
|
555
652
|
def includes
|
556
|
-
@instance_options[:include] || []
|
653
|
+
@includes ||= (@instance_options[:include] || []).sort_by do |inc|
|
654
|
+
# Sort these by length so we never have bad foreign references in the CTEs:
|
655
|
+
inc.size
|
656
|
+
end
|
557
657
|
end
|
558
658
|
|
559
659
|
# Takes a dotted field name (not including the base resource)
|
@@ -572,13 +672,25 @@ class JsonApiPgSql
|
|
572
672
|
# Be careful: inc might have dots:
|
573
673
|
th = get_json_thing_from_base(inc)
|
574
674
|
<<~EOQ
|
575
|
-
#{th.cte_name} AS (
|
675
|
+
"#{th.cte_name}" AS (
|
576
676
|
#{include_cte(th)}
|
577
677
|
),
|
578
678
|
EOQ
|
579
679
|
}.join("\n")
|
580
680
|
end
|
581
681
|
|
682
|
+
def include_jbsses
|
683
|
+
includes.map { |inc|
|
684
|
+
# Be careful: inc might have dots:
|
685
|
+
th = get_json_thing_from_base(inc)
|
686
|
+
<<~EOQ
|
687
|
+
"#{th.jbs_name}" AS (
|
688
|
+
#{include_jbs(th)}
|
689
|
+
),
|
690
|
+
EOQ
|
691
|
+
}.join("\n")
|
692
|
+
end
|
693
|
+
|
582
694
|
def base_resource
|
583
695
|
@json_things[:base]
|
584
696
|
end
|
@@ -609,7 +721,7 @@ class JsonApiPgSql
|
|
609
721
|
resource.serializer._attributes.select{|f|
|
610
722
|
if ms.include? "include_#{f}?".to_sym
|
611
723
|
ser = resource.serializer.new(nil, @options)
|
612
|
-
ser.send("include_#{f}?".to_sym)
|
724
|
+
ser.send("include_#{f}?".to_sym)
|
613
725
|
else
|
614
726
|
true
|
615
727
|
end
|
@@ -623,7 +735,7 @@ class JsonApiPgSql
|
|
623
735
|
resource.serializer._reflections.keys.select{|f|
|
624
736
|
if ms.include? "include_#{f}?".to_sym
|
625
737
|
ser = resource.serializer.new(nil, @options)
|
626
|
-
ser.send("include_#{f}?".to_sym)
|
738
|
+
ser.send("include_#{f}?".to_sym)
|
627
739
|
else
|
628
740
|
true
|
629
741
|
end
|
@@ -656,6 +768,9 @@ class JsonApiPgSql
|
|
656
768
|
|
657
769
|
def _attribute_fields_for(resource)
|
658
770
|
attrs = Set.new(serializer_attributes(resource))
|
771
|
+
# JSON:API always excludes the `id`
|
772
|
+
# even if it's part of the serializer:
|
773
|
+
attrs = attrs - [resource.primary_key.to_sym]
|
659
774
|
fields_for(resource).select { |f| attrs.include? f }.to_a
|
660
775
|
end
|
661
776
|
|
@@ -687,14 +802,15 @@ class JsonApiPgSql
|
|
687
802
|
#{join_resource_relationships(base_resource)}
|
688
803
|
),
|
689
804
|
#{include_ctes}
|
690
|
-
|
805
|
+
#{include_jbsses}
|
806
|
+
all_jbsses AS (
|
691
807
|
SELECT '{}'::jsonb AS j
|
692
808
|
WHERE 1=0
|
693
809
|
#{include_selects.join("\n")}
|
694
810
|
),
|
695
811
|
inc AS (
|
696
812
|
SELECT COALESCE(jsonb_agg(j), '[]') AS j
|
697
|
-
FROM
|
813
|
+
FROM all_jbsses
|
698
814
|
)
|
699
815
|
SELECT jsonb_build_object('data', t2.j
|
700
816
|
#{maybe_included})
|
@@ -15,6 +15,7 @@ module ActiveModelSerializersPg
|
|
15
15
|
include Enumerable
|
16
16
|
# PATCHED: implement this below so we don't need @serializers
|
17
17
|
# delegate :each, to: :@serializers
|
18
|
+
delegate :each, to: :serializers
|
18
19
|
|
19
20
|
attr_reader :object, :root
|
20
21
|
|
@@ -22,8 +23,13 @@ module ActiveModelSerializersPg
|
|
22
23
|
@object = resources
|
23
24
|
@options = options
|
24
25
|
@root = options[:root]
|
25
|
-
# PATCHED: We don't want to
|
26
|
-
#
|
26
|
+
# PATCHED: We don't want to materialize a Relation by iterating unless we have to.
|
27
|
+
# On the other hand, if we don't have a serializer we *do* want to `throw :no_serializer`
|
28
|
+
# right away. That should only happen for basic types (like a String or Hash),
|
29
|
+
# so we act lazy when we have a Relation, and eager otherwise:
|
30
|
+
unless resources.is_a? ActiveRecord::Relation
|
31
|
+
@serializers = serializers_from_resources
|
32
|
+
end
|
27
33
|
end
|
28
34
|
|
29
35
|
# PATCH: Give ourselves access to the serializer for the individual elements:
|
@@ -76,25 +82,17 @@ module ActiveModelSerializersPg
|
|
76
82
|
object.respond_to?(:size)
|
77
83
|
end
|
78
84
|
|
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
85
|
protected
|
90
86
|
|
91
|
-
attr_reader :
|
87
|
+
attr_reader :options
|
88
|
+
|
89
|
+
def serializers
|
90
|
+
@serializers ||= serializers_from_resources
|
91
|
+
end
|
92
92
|
|
93
93
|
private
|
94
94
|
|
95
95
|
def serializers_from_resources
|
96
|
-
puts "options here"
|
97
|
-
pp options
|
98
96
|
serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer)
|
99
97
|
object.map do |resource|
|
100
98
|
serializer_from_resource(resource, serializer_context_class, options)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module ActiveModelSerializersPg
|
4
|
+
module Generators
|
5
|
+
class ActiveModelSerializersPgGenerator < Rails::Generators::NamedBase
|
6
|
+
Rails::Generators::ResourceHelpers
|
7
|
+
|
8
|
+
# The ORM generator assumes you're passing a name argument,
|
9
|
+
# but we don't need one, so we give it a default value:
|
10
|
+
argument :name, type: :string, default: "ignored"
|
11
|
+
source_root File.expand_path("../templates", __FILE__)
|
12
|
+
|
13
|
+
namespace :active_model_serializers_pg
|
14
|
+
hook_for :orm, required: true, name: "ignored"
|
15
|
+
|
16
|
+
desc "Creates an active_model_serialiers_pg database migration"
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Generators
|
5
|
+
class ActiveModelSerializersPgGenerator < ActiveRecord::Generators::Base
|
6
|
+
argument :name, type: :string, default: "ignored"
|
7
|
+
source_root File.expand_path("../templates", __FILE__)
|
8
|
+
|
9
|
+
def write_migration
|
10
|
+
migration_template "migration.rb", "#{migration_path}/ams_pg_create_dasherize_functions.rb"
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def read_sql(funcname)
|
16
|
+
File.read(File.join(File.expand_path('../templates', __FILE__), "#{funcname}.sql"))
|
17
|
+
end
|
18
|
+
|
19
|
+
def migration_exists?(table_name)
|
20
|
+
Dir.glob("#{File.join destination_root, migration_path}/[0-9]*_*.rb").grep(/\d+_ams_pg_create_dasherize_functions.rb$/).first
|
21
|
+
end
|
22
|
+
|
23
|
+
def migration_path
|
24
|
+
if Rails.version >= '5.0.3'
|
25
|
+
db_migrate_path
|
26
|
+
else
|
27
|
+
@migration_path ||= File.join "db", "migrate"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def migration_version
|
32
|
+
if Rails.version.start_with? '5'
|
33
|
+
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def jsonb_dasherize
|
38
|
+
read_sql('jsonb_dasherize')
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
CREATE FUNCTION jsonb_dasherize(j jsonb)
|
2
|
+
RETURNS jsonb
|
3
|
+
IMMUTABLE
|
4
|
+
AS
|
5
|
+
$$
|
6
|
+
DECLARE
|
7
|
+
t text;
|
8
|
+
ret jsonb;
|
9
|
+
BEGIN
|
10
|
+
t := jsonb_typeof(j);
|
11
|
+
IF t = 'object' THEN
|
12
|
+
SELECT COALESCE(jsonb_object_agg(REPLACE(k, '_', '-'), jsonb_dasherize(v)), '{}')
|
13
|
+
INTO ret
|
14
|
+
FROM jsonb_each(j) AS t(k, v);
|
15
|
+
RETURN ret;
|
16
|
+
ELSIF t = 'array' THEN
|
17
|
+
SELECT COALESCE(jsonb_agg(jsonb_dasherize(elem)), '[]')
|
18
|
+
INTO ret
|
19
|
+
FROM jsonb_array_elements(j) AS t(elem);
|
20
|
+
RETURN ret;
|
21
|
+
ELSIF t IS NULL THEN
|
22
|
+
-- This should never happen internally
|
23
|
+
-- (thankfully, lest jsonb_set return NULL and destroy everything),
|
24
|
+
-- but only from a passed-in NULL.
|
25
|
+
RETURN NULL;
|
26
|
+
ELSE
|
27
|
+
-- string/number/null:
|
28
|
+
RETURN j;
|
29
|
+
END IF;
|
30
|
+
END;
|
31
|
+
$$
|
32
|
+
LANGUAGE plpgsql;
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'generators/active_model_serializers_pg/active_model_serializers_pg_generator'
|
3
|
+
|
4
|
+
describe ActiveModelSerializersPg::Generators::ActiveModelSerializersPgGenerator, type: :generator do
|
5
|
+
destination File.expand_path "../../../tmp", __FILE__
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
prepare_destination
|
9
|
+
end
|
10
|
+
|
11
|
+
it "creates the migration file" do
|
12
|
+
run_generator
|
13
|
+
expect(destination_root).to have_structure {
|
14
|
+
directory "db" do
|
15
|
+
directory "migrate" do
|
16
|
+
migration "ams_pg_create_dasherize_functions" do
|
17
|
+
contains "class AmsPgCreateDasherizeFunctions"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
data/spec/serializer_spec.rb
CHANGED
@@ -25,7 +25,7 @@ describe 'ArraySerializer' do
|
|
25
25
|
{
|
26
26
|
id: @note.id.to_s,
|
27
27
|
type: 'notes',
|
28
|
-
attributes: {
|
28
|
+
attributes: {name: 'Title'},
|
29
29
|
relationships: {tags: {data: [{id: @tag.id.to_s, type: 'tags'}]}},
|
30
30
|
}
|
31
31
|
]
|
@@ -65,7 +65,7 @@ describe 'ArraySerializer' do
|
|
65
65
|
{
|
66
66
|
id: person.id.to_s,
|
67
67
|
type: 'people',
|
68
|
-
attributes: {
|
68
|
+
attributes: { full_name: 'Test User', attendance_name: 'User, Test' },
|
69
69
|
}
|
70
70
|
]
|
71
71
|
}.to_json
|
@@ -80,7 +80,7 @@ describe 'ArraySerializer' do
|
|
80
80
|
{
|
81
81
|
id: person.id.to_s,
|
82
82
|
type: 'people',
|
83
|
-
attributes: {
|
83
|
+
attributes: {full_name: 'Test User', attendance_name: 'ADMIN User, Test'}
|
84
84
|
}
|
85
85
|
]
|
86
86
|
}.to_json
|
@@ -113,6 +113,42 @@ describe 'ArraySerializer' do
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
|
+
context 'serialize an array instead of a relation' do
|
117
|
+
let(:relation) { [Note.where(name: 'Title').first] }
|
118
|
+
let(:controller) { NotesController.new }
|
119
|
+
let(:options) { }
|
120
|
+
|
121
|
+
before do
|
122
|
+
@note = Note.create content: 'Test', name: 'Title'
|
123
|
+
@tag = Tag.create name: 'My tag', note: @note, popular: true
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'generates the proper json output' do
|
127
|
+
json_expected = {
|
128
|
+
data: [
|
129
|
+
{
|
130
|
+
id: @note.id.to_s,
|
131
|
+
type: 'notes',
|
132
|
+
attributes: { name: 'Title', content: 'Test' },
|
133
|
+
relationships: { tags: { data: [{id: @tag.id.to_s, type: 'tags'}] } }
|
134
|
+
}
|
135
|
+
]
|
136
|
+
}.to_json
|
137
|
+
expect(json_data).to eq json_expected
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'serialize an empty array' do
|
142
|
+
let(:relation) { [] }
|
143
|
+
let(:controller) { NotesController.new }
|
144
|
+
let(:options) { }
|
145
|
+
|
146
|
+
it 'generates the proper json output' do
|
147
|
+
json_expected = { data: [] }.to_json
|
148
|
+
expect(json_data).to eq json_expected
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
116
152
|
context 'serialize singular record' do
|
117
153
|
let(:relation) { Note.where(name: 'Title').first }
|
118
154
|
let(:controller) { NotesController.new }
|
@@ -134,7 +170,6 @@ describe 'ArraySerializer' do
|
|
134
170
|
}.to_json
|
135
171
|
expect(json_data).to eq json_expected
|
136
172
|
end
|
137
|
-
|
138
173
|
end
|
139
174
|
|
140
175
|
context 'with dasherized keys and types' do
|
@@ -174,6 +209,65 @@ describe 'ArraySerializer' do
|
|
174
209
|
end
|
175
210
|
end
|
176
211
|
|
212
|
+
context 'with dasherized json columns' do
|
213
|
+
let(:relation) { Person.all }
|
214
|
+
let(:controller) { PeopleController.new }
|
215
|
+
let(:person) {
|
216
|
+
Person.create first_name: 'Test',
|
217
|
+
last_name: 'User',
|
218
|
+
options: { 'foo_foo': 'baz', bar: [{ 'jar_jar': 'binks' }] },
|
219
|
+
prefs: { 'foo_foo': 'baz', bar: [{ 'jar_jar': 'binks' }] },
|
220
|
+
settings: { 'foo_foo': 'bar' },
|
221
|
+
selfies: [ { 'photo_resolution': '200x200' } ],
|
222
|
+
portraits: [ { 'photo_resolution': '150x200' } ],
|
223
|
+
landscapes: [ { 'photo_resolution': '200x150' } ]
|
224
|
+
}
|
225
|
+
let(:options) { { each_serializer: PersonWithJsonSerializer } }
|
226
|
+
|
227
|
+
before do
|
228
|
+
@old_key_setting = ActiveModelSerializers.config.key_transform
|
229
|
+
ActiveModelSerializers.config.key_transform = :dash
|
230
|
+
end
|
231
|
+
|
232
|
+
after do
|
233
|
+
ActiveModelSerializers.config.key_transform = @old_key_setting
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'generates the proper json output' do
|
237
|
+
json_expected = {
|
238
|
+
data: [
|
239
|
+
{
|
240
|
+
id: person.id.to_s,
|
241
|
+
type: 'people',
|
242
|
+
attributes: {
|
243
|
+
prefs: {
|
244
|
+
bar: [{ 'jar-jar' => 'binks'}],
|
245
|
+
'foo-foo' => 'baz',
|
246
|
+
},
|
247
|
+
options: {
|
248
|
+
bar: [{ 'jar-jar' => 'binks'}],
|
249
|
+
'foo-foo' => 'baz',
|
250
|
+
},
|
251
|
+
selfies: [
|
252
|
+
{ 'photo-resolution' => '200x200' },
|
253
|
+
],
|
254
|
+
settings: {
|
255
|
+
'foo-foo' => 'bar'
|
256
|
+
},
|
257
|
+
portraits: [
|
258
|
+
{ 'photo-resolution' => '150x200' },
|
259
|
+
],
|
260
|
+
landscapes: [
|
261
|
+
{ 'photo-resolution' => '200x150' },
|
262
|
+
],
|
263
|
+
},
|
264
|
+
}
|
265
|
+
]
|
266
|
+
}.to_json
|
267
|
+
expect(json_data).to eq json_expected
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
177
271
|
context 'with aliased association' do
|
178
272
|
let(:relation) { Tag.first }
|
179
273
|
let(:controller) { TagsController.new }
|
@@ -195,7 +289,29 @@ describe 'ArraySerializer' do
|
|
195
289
|
}.to_json
|
196
290
|
expect(json_data).to eq json_expected
|
197
291
|
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context 'with aliased attribute' do
|
295
|
+
let(:relation) { Tag.first }
|
296
|
+
let(:controller) { TagsController.new }
|
297
|
+
let(:options) { { serializer: TagWithAliasedNameSerializer } }
|
298
|
+
|
299
|
+
before do
|
300
|
+
@note = Note.create content: 'Test', name: 'Title'
|
301
|
+
@tag = Tag.create name: 'My tag', note: @note, popular: true
|
302
|
+
end
|
198
303
|
|
304
|
+
it 'generates the proper json output' do
|
305
|
+
json_expected = {
|
306
|
+
data: {
|
307
|
+
id: @tag.id.to_s,
|
308
|
+
type: 'tags',
|
309
|
+
attributes: { 'aliased_name' => 'My tag' },
|
310
|
+
relationships: { note: { data: {id: @note.id.to_s, type: 'notes'} } }
|
311
|
+
}
|
312
|
+
}.to_json
|
313
|
+
expect(json_data).to eq json_expected
|
314
|
+
end
|
199
315
|
end
|
200
316
|
|
201
317
|
context 'serialize single record with custom serializer' do
|
@@ -213,7 +329,7 @@ describe 'ArraySerializer' do
|
|
213
329
|
data: {
|
214
330
|
id: @note.id.to_s,
|
215
331
|
type: 'notes',
|
216
|
-
attributes: {
|
332
|
+
attributes: { name: 'Title' },
|
217
333
|
relationships: { tags: { data: [{id: @tag.id.to_s, type: 'tags'}] } }
|
218
334
|
}
|
219
335
|
}.to_json
|
@@ -367,7 +483,7 @@ describe 'ArraySerializer' do
|
|
367
483
|
{
|
368
484
|
id: tag.id.to_s,
|
369
485
|
type: 'tags',
|
370
|
-
attributes: {
|
486
|
+
attributes: { name: 'My tag' },
|
371
487
|
relationships: { note: { data: { id: note.id.to_s, type: 'notes' } } },
|
372
488
|
}
|
373
489
|
],
|
@@ -405,7 +521,7 @@ describe 'ArraySerializer' do
|
|
405
521
|
{
|
406
522
|
id: reviewer.id.to_s,
|
407
523
|
type: 'users',
|
408
|
-
attributes: {
|
524
|
+
attributes: { name: 'Peter' },
|
409
525
|
relationships: {
|
410
526
|
offers: { data: [] },
|
411
527
|
reviewed_offers: { data: [{id: offer.id.to_s, type: 'offers'}] },
|
@@ -414,7 +530,7 @@ describe 'ArraySerializer' do
|
|
414
530
|
{
|
415
531
|
id: user.id.to_s,
|
416
532
|
type: 'users',
|
417
|
-
attributes: {
|
533
|
+
attributes: { name: 'John' },
|
418
534
|
relationships: {
|
419
535
|
offers: { data: [{id: offer.id.to_s, type: 'offers'}] },
|
420
536
|
reviewed_offers: { data: [] },
|
@@ -465,7 +581,7 @@ describe 'ArraySerializer' do
|
|
465
581
|
{
|
466
582
|
id: tag.id.to_s,
|
467
583
|
type: 'tag_with_notes',
|
468
|
-
attributes: {
|
584
|
+
attributes: { name: 'My tag' },
|
469
585
|
relationships: { note: { data: { id: note.id.to_s, type: 'notes' } } },
|
470
586
|
}
|
471
587
|
]
|
@@ -524,7 +640,7 @@ describe 'ArraySerializer' do
|
|
524
640
|
{
|
525
641
|
id: @user.id.to_s,
|
526
642
|
type: 'users',
|
527
|
-
attributes: {
|
643
|
+
attributes: {name: 'John', mobile: '51111111'},
|
528
644
|
relationships: {
|
529
645
|
offers: {data: []},
|
530
646
|
address: {data: {id: address.id.to_s, type: 'addresses'}},
|
@@ -544,7 +660,7 @@ describe 'ArraySerializer' do
|
|
544
660
|
{
|
545
661
|
id: @user.id.to_s,
|
546
662
|
type: 'users',
|
547
|
-
attributes: {
|
663
|
+
attributes: {name: 'John'},
|
548
664
|
relationships: {
|
549
665
|
offers: {data: []},
|
550
666
|
reviewed_offers: {data: []},
|
@@ -572,7 +688,7 @@ describe 'ArraySerializer' do
|
|
572
688
|
{
|
573
689
|
id: note.id.to_s,
|
574
690
|
type: 'notes',
|
575
|
-
attributes: {
|
691
|
+
attributes: {},
|
576
692
|
relationships: {
|
577
693
|
sorted_tags: {
|
578
694
|
data: [
|
@@ -608,7 +724,7 @@ describe 'ArraySerializer' do
|
|
608
724
|
{
|
609
725
|
id: note.id.to_s,
|
610
726
|
type: 'notes',
|
611
|
-
attributes: {
|
727
|
+
attributes: {},
|
612
728
|
relationships: {
|
613
729
|
custom_sorted_tags: {
|
614
730
|
data: [
|
data/spec/spec_helper.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
-
require '
|
2
|
-
require 'action_controller'
|
1
|
+
require 'rails/all'
|
3
2
|
require 'rspec'
|
3
|
+
require 'rspec/rails'
|
4
4
|
require 'bourne'
|
5
5
|
require 'database_cleaner'
|
6
6
|
require 'active_model_serializers'
|
7
7
|
require 'action_controller/serialization'
|
8
8
|
require 'active_model_serializers_pg'
|
9
|
+
require 'generator_spec'
|
9
10
|
if ENV['TEST_UNPATCHED_AMS']
|
10
11
|
ActiveModelSerializers.config.adapter = :json_api
|
11
12
|
else
|
@@ -24,6 +25,17 @@ end
|
|
24
25
|
require 'dotenv'
|
25
26
|
Dotenv.load
|
26
27
|
|
28
|
+
# Need this or Rails.application is nil just below:
|
29
|
+
module TestApp
|
30
|
+
class Application < ::Rails::Application
|
31
|
+
config.root = File.dirname(__FILE__)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Need this line or `hook_for` in our generators is ignored,
|
36
|
+
# so our migration generator doesn't run:
|
37
|
+
Rails.application.load_generators
|
38
|
+
|
27
39
|
ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'])
|
28
40
|
|
29
41
|
class TestController < ActionController::Base
|
@@ -56,6 +68,16 @@ class PersonSerializer < ActiveModel::Serializer
|
|
56
68
|
end
|
57
69
|
end
|
58
70
|
|
71
|
+
class PersonWithJsonSerializer < ActiveModel::Serializer
|
72
|
+
attributes :id,
|
73
|
+
:options, # json
|
74
|
+
:prefs, # jsonb
|
75
|
+
:settings, # hstore
|
76
|
+
:selfies, # json[]
|
77
|
+
:portraits, # jsonb[]
|
78
|
+
:landscapes # hstore[]
|
79
|
+
end
|
80
|
+
|
59
81
|
class Note < ActiveRecord::Base
|
60
82
|
has_many :tags
|
61
83
|
has_many :sorted_tags
|
@@ -126,6 +148,7 @@ end
|
|
126
148
|
class Tag < ActiveRecord::Base
|
127
149
|
belongs_to :note
|
128
150
|
alias :aliased_note :note
|
151
|
+
alias_attribute :aliased_name, :name
|
129
152
|
end
|
130
153
|
|
131
154
|
class SortedTag < Tag
|
@@ -164,6 +187,11 @@ class TagWithAliasedNoteSerializer < ActiveModel::Serializer
|
|
164
187
|
has_one :aliased_note
|
165
188
|
end
|
166
189
|
|
190
|
+
class TagWithAliasedNameSerializer < ActiveModel::Serializer
|
191
|
+
attributes :aliased_name
|
192
|
+
has_one :note
|
193
|
+
end
|
194
|
+
|
167
195
|
class User < ActiveRecord::Base
|
168
196
|
has_many :offers, foreign_key: :created_by_id, inverse_of: :created_by
|
169
197
|
has_many :reviewed_offers, foreign_key: :reviewed_by_id, inverse_of: :reviewed_by, class_name: 'Offer'
|
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.
|
4
|
+
version: 0.0.7
|
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:
|
11
|
+
date: 2020-09-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: active_model_serializers
|
@@ -94,6 +94,34 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec-rails
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: generator_spec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
97
125
|
- !ruby/object:Gem::Dependency
|
98
126
|
name: bourne
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -172,6 +200,11 @@ files:
|
|
172
200
|
- lib/active_model_serializers_pg.rb
|
173
201
|
- lib/active_model_serializers_pg/collection_serializer.rb
|
174
202
|
- lib/active_model_serializers_pg/version.rb
|
203
|
+
- lib/generators/active_model_serializers_pg/active_model_serializers_pg_generator.rb
|
204
|
+
- lib/generators/active_record/active_model_serializers_pg_generator.rb
|
205
|
+
- lib/generators/active_record/templates/jsonb_dasherize.sql
|
206
|
+
- lib/generators/active_record/templates/migration.rb
|
207
|
+
- spec/generators/main_generator_spec.rb
|
175
208
|
- spec/serializer_spec.rb
|
176
209
|
- spec/spec_helper.rb
|
177
210
|
homepage: https://github.com/pjungwir/active_model_serializers_pg
|
@@ -193,10 +226,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
193
226
|
- !ruby/object:Gem::Version
|
194
227
|
version: '0'
|
195
228
|
requirements: []
|
196
|
-
rubygems_version: 3.0.
|
229
|
+
rubygems_version: 3.0.8
|
197
230
|
signing_key:
|
198
231
|
specification_version: 4
|
199
232
|
summary: Harness the power of PostgreSQL when crafting JSON reponses
|
200
233
|
test_files:
|
234
|
+
- spec/generators/main_generator_spec.rb
|
201
235
|
- spec/serializer_spec.rb
|
202
236
|
- spec/spec_helper.rb
|