active_model_serializers_pg 0.0.3 → 0.0.8
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 +10 -1
- data/Rakefile +11 -1
- data/active_model_serializers_pg.gemspec +3 -1
- 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/gemfiles/Gemfile.activerecord-6.0.x +5 -0
- data/lib/active_model_serializers/adapter/json_api_pg.rb +160 -47
- 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 +40 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ee3c698a1508537d359551eb125a45c38bd0921dfd059ae49422dd7d114aa7f2
|
|
4
|
+
data.tar.gz: 677ac55b0c35a51ba03b042b34ed3fd560b57d223b6687ca020eaa3b131aabda
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82f91e03d0b6440e6b6002909b51e346c5e21293659ca0fc80b976df30bcfc089a294b1c4f42a7ca20c01b20d612203ead218ec3c268d5bd5bb4832652df923d
|
|
7
|
+
data.tar.gz: d906a81cde8076d6150f6466263619f5283d3f5e1e9bbcedaef938f8f5633e2fdd2d1366ef3ca18343669773ef09809cc837df3cc99719ecaefbdae8a94ff2a7
|
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
|
|
|
@@ -110,7 +119,7 @@ To work on active\_model\_serializers\_pg locally, follow these steps:
|
|
|
110
119
|
4. Run `bundle exec rake db:create`, this will create the test database.
|
|
111
120
|
5. Run `bundle exec rake db:migrate`, this will set up the database tables required
|
|
112
121
|
by the test.
|
|
113
|
-
6. Run `bundle exec rake test:all` to run tests against all supported versions of Active Record (currently 5.0.x, 5.1.x, 5.2.x).
|
|
122
|
+
6. Run `bundle exec rake test:all` to run tests against all supported versions of Active Record (currently 5.0.x, 5.1.x, 5.2.x, 6.0.x).
|
|
114
123
|
You can also say `BUNDLE_GEMFILE=gemfiles/Gemfile.activerecord-5.2.x bundle exec rspec spec` to run against a specific version (and select specific tests).
|
|
115
124
|
|
|
116
125
|
Commands for building/releasing/installing:
|
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
|
|
@@ -141,7 +151,7 @@ namespace :test do
|
|
|
141
151
|
# Escape current bundler environment
|
|
142
152
|
Bundler.with_clean_env do
|
|
143
153
|
# Currently only supports Active Record v5.0-v5.2
|
|
144
|
-
%w(5.0.x 5.1.x 5.2.x).each do |version|
|
|
154
|
+
%w(5.0.x 5.1.x 5.2.x 6.0.x).each do |version|
|
|
145
155
|
sh "BUNDLE_GEMFILE='gemfiles/Gemfile.activerecord-#{version}' bundle install --quiet"
|
|
146
156
|
sh "BUNDLE_GEMFILE='gemfiles/Gemfile.activerecord-#{version}' bundle exec rspec spec"
|
|
147
157
|
end
|
|
@@ -19,12 +19,14 @@ Gem::Specification.new do |spec|
|
|
|
19
19
|
spec.require_paths = ["lib"]
|
|
20
20
|
|
|
21
21
|
spec.add_runtime_dependency 'active_model_serializers', '~> 0.10.8'
|
|
22
|
-
spec.add_runtime_dependency 'activerecord', '
|
|
22
|
+
spec.add_runtime_dependency 'activerecord', '>= 5.0'
|
|
23
23
|
|
|
24
24
|
spec.add_development_dependency 'bundler', '~> 1.3'
|
|
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)}"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
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)}"
|
|
183
209
|
end
|
|
184
210
|
end
|
|
185
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
|
|
@@ -456,11 +546,8 @@ class JsonApiPgSql
|
|
|
456
546
|
# TODO: preserve the whole custom relation, not just ordering
|
|
457
547
|
p = refl.ar_class.new
|
|
458
548
|
ordering = nil
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
ordering = p.send(refl.name).orders
|
|
462
|
-
ordering = child_resource.ar_class.default_scoped.orders if ordering.empty?
|
|
463
|
-
end
|
|
549
|
+
ordering = p.send(refl.name).arel.orders
|
|
550
|
+
ordering = child_resource.ar_class.default_scoped.arel.orders if ordering.empty?
|
|
464
551
|
ordering = ordering.map{|o|
|
|
465
552
|
case o
|
|
466
553
|
# TODO: The gsub is pretty awful....
|
|
@@ -521,39 +608,49 @@ class JsonApiPgSql
|
|
|
521
608
|
# e.g. buyer and seller for User.
|
|
522
609
|
# But we could group those and union just them, or even better do a DISTINCT ON (id).
|
|
523
610
|
# Since we don't get the id here that could be another CTE.
|
|
524
|
-
|
|
611
|
+
%Q{UNION SELECT j FROM "#{th.jbs_name}"}
|
|
525
612
|
}
|
|
526
613
|
end
|
|
527
614
|
|
|
528
615
|
def include_cte_join_condition(resource)
|
|
529
616
|
parent = resource.parent
|
|
530
617
|
if resource.belongs_to?
|
|
531
|
-
%Q{#{parent.cte_name}."#{resource.foreign_key}" = "#{resource.table_name}"."#{resource.primary_key}"}
|
|
618
|
+
%Q{"#{parent.cte_name}"."#{resource.foreign_key}" = "#{resource.table_name}"."#{resource.primary_key}"}
|
|
532
619
|
elsif resource.has_many? or resource.has_one?
|
|
533
|
-
%Q{#{parent.cte_name}."#{parent.primary_key}" = "#{resource.table_name}"."#{resource.foreign_key}"}
|
|
620
|
+
%Q{"#{parent.cte_name}"."#{parent.primary_key}" = "#{resource.table_name}"."#{resource.foreign_key}"}
|
|
534
621
|
else
|
|
535
622
|
raise "not supported relationship: #{resource.full_name}"
|
|
536
623
|
end
|
|
537
624
|
end
|
|
538
625
|
|
|
626
|
+
# See note in _jbs_name method for why we split each thing into two CTEs.
|
|
539
627
|
def include_cte(resource)
|
|
540
|
-
# Sometimes options[:fields] has plural keys and sometimes singular,
|
|
541
|
-
# so try both:
|
|
542
628
|
parent = resource.parent
|
|
543
629
|
<<~EOQ
|
|
544
630
|
SELECT DISTINCT ON ("#{resource.table_name}"."#{resource.primary_key}")
|
|
545
|
-
"#{resource.table_name}"
|
|
546
|
-
#{select_resource(resource)} AS j
|
|
631
|
+
"#{resource.table_name}".*
|
|
547
632
|
FROM "#{resource.table_name}"
|
|
548
|
-
JOIN #{parent.cte_name}
|
|
633
|
+
JOIN "#{parent.cte_name}"
|
|
549
634
|
ON #{include_cte_join_condition(resource)}
|
|
550
|
-
#{join_resource_relationships(resource)}
|
|
551
635
|
ORDER BY "#{resource.table_name}"."#{resource.primary_key}"
|
|
552
636
|
EOQ
|
|
553
637
|
end
|
|
554
638
|
|
|
639
|
+
# See note in _jbs_name method for why we split each thing into two CTEs.
|
|
640
|
+
def include_jbs(resource)
|
|
641
|
+
<<~EOQ
|
|
642
|
+
SELECT "#{resource.table_name}".*,
|
|
643
|
+
#{select_resource(resource)} AS j
|
|
644
|
+
FROM "#{resource.cte_name}" AS "#{resource.table_name}"
|
|
645
|
+
#{join_resource_relationships(resource)}
|
|
646
|
+
EOQ
|
|
647
|
+
end
|
|
648
|
+
|
|
555
649
|
def includes
|
|
556
|
-
@instance_options[:include] || []
|
|
650
|
+
@includes ||= (@instance_options[:include] || []).sort_by do |inc|
|
|
651
|
+
# Sort these by length so we never have bad foreign references in the CTEs:
|
|
652
|
+
inc.size
|
|
653
|
+
end
|
|
557
654
|
end
|
|
558
655
|
|
|
559
656
|
# Takes a dotted field name (not including the base resource)
|
|
@@ -572,13 +669,25 @@ class JsonApiPgSql
|
|
|
572
669
|
# Be careful: inc might have dots:
|
|
573
670
|
th = get_json_thing_from_base(inc)
|
|
574
671
|
<<~EOQ
|
|
575
|
-
#{th.cte_name} AS (
|
|
672
|
+
"#{th.cte_name}" AS (
|
|
576
673
|
#{include_cte(th)}
|
|
577
674
|
),
|
|
578
675
|
EOQ
|
|
579
676
|
}.join("\n")
|
|
580
677
|
end
|
|
581
678
|
|
|
679
|
+
def include_jbsses
|
|
680
|
+
includes.map { |inc|
|
|
681
|
+
# Be careful: inc might have dots:
|
|
682
|
+
th = get_json_thing_from_base(inc)
|
|
683
|
+
<<~EOQ
|
|
684
|
+
"#{th.jbs_name}" AS (
|
|
685
|
+
#{include_jbs(th)}
|
|
686
|
+
),
|
|
687
|
+
EOQ
|
|
688
|
+
}.join("\n")
|
|
689
|
+
end
|
|
690
|
+
|
|
582
691
|
def base_resource
|
|
583
692
|
@json_things[:base]
|
|
584
693
|
end
|
|
@@ -609,7 +718,7 @@ class JsonApiPgSql
|
|
|
609
718
|
resource.serializer._attributes.select{|f|
|
|
610
719
|
if ms.include? "include_#{f}?".to_sym
|
|
611
720
|
ser = resource.serializer.new(nil, @options)
|
|
612
|
-
ser.send("include_#{f}?".to_sym)
|
|
721
|
+
ser.send("include_#{f}?".to_sym)
|
|
613
722
|
else
|
|
614
723
|
true
|
|
615
724
|
end
|
|
@@ -623,7 +732,7 @@ class JsonApiPgSql
|
|
|
623
732
|
resource.serializer._reflections.keys.select{|f|
|
|
624
733
|
if ms.include? "include_#{f}?".to_sym
|
|
625
734
|
ser = resource.serializer.new(nil, @options)
|
|
626
|
-
ser.send("include_#{f}?".to_sym)
|
|
735
|
+
ser.send("include_#{f}?".to_sym)
|
|
627
736
|
else
|
|
628
737
|
true
|
|
629
738
|
end
|
|
@@ -656,6 +765,9 @@ class JsonApiPgSql
|
|
|
656
765
|
|
|
657
766
|
def _attribute_fields_for(resource)
|
|
658
767
|
attrs = Set.new(serializer_attributes(resource))
|
|
768
|
+
# JSON:API always excludes the `id`
|
|
769
|
+
# even if it's part of the serializer:
|
|
770
|
+
attrs = attrs - [resource.primary_key.to_sym]
|
|
659
771
|
fields_for(resource).select { |f| attrs.include? f }.to_a
|
|
660
772
|
end
|
|
661
773
|
|
|
@@ -687,14 +799,15 @@ class JsonApiPgSql
|
|
|
687
799
|
#{join_resource_relationships(base_resource)}
|
|
688
800
|
),
|
|
689
801
|
#{include_ctes}
|
|
690
|
-
|
|
802
|
+
#{include_jbsses}
|
|
803
|
+
all_jbsses AS (
|
|
691
804
|
SELECT '{}'::jsonb AS j
|
|
692
805
|
WHERE 1=0
|
|
693
806
|
#{include_selects.join("\n")}
|
|
694
807
|
),
|
|
695
808
|
inc AS (
|
|
696
809
|
SELECT COALESCE(jsonb_agg(j), '[]') AS j
|
|
697
|
-
FROM
|
|
810
|
+
FROM all_jbsses
|
|
698
811
|
)
|
|
699
812
|
SELECT jsonb_build_object('data', t2.j
|
|
700
813
|
#{maybe_included})
|
|
@@ -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.8
|
|
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: 2021-03-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: active_model_serializers
|
|
@@ -28,14 +28,14 @@ dependencies:
|
|
|
28
28
|
name: activerecord
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- - "
|
|
31
|
+
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
33
|
version: '5.0'
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
|
-
- - "
|
|
38
|
+
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '5.0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
@@ -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
|
|
@@ -168,10 +196,16 @@ files:
|
|
|
168
196
|
- gemfiles/Gemfile.activerecord-5.0.x
|
|
169
197
|
- gemfiles/Gemfile.activerecord-5.1.x
|
|
170
198
|
- gemfiles/Gemfile.activerecord-5.2.x
|
|
199
|
+
- gemfiles/Gemfile.activerecord-6.0.x
|
|
171
200
|
- lib/active_model_serializers/adapter/json_api_pg.rb
|
|
172
201
|
- lib/active_model_serializers_pg.rb
|
|
173
202
|
- lib/active_model_serializers_pg/collection_serializer.rb
|
|
174
203
|
- lib/active_model_serializers_pg/version.rb
|
|
204
|
+
- lib/generators/active_model_serializers_pg/active_model_serializers_pg_generator.rb
|
|
205
|
+
- lib/generators/active_record/active_model_serializers_pg_generator.rb
|
|
206
|
+
- lib/generators/active_record/templates/jsonb_dasherize.sql
|
|
207
|
+
- lib/generators/active_record/templates/migration.rb
|
|
208
|
+
- spec/generators/main_generator_spec.rb
|
|
175
209
|
- spec/serializer_spec.rb
|
|
176
210
|
- spec/spec_helper.rb
|
|
177
211
|
homepage: https://github.com/pjungwir/active_model_serializers_pg
|
|
@@ -193,10 +227,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
193
227
|
- !ruby/object:Gem::Version
|
|
194
228
|
version: '0'
|
|
195
229
|
requirements: []
|
|
196
|
-
rubygems_version: 3.0.
|
|
230
|
+
rubygems_version: 3.0.3
|
|
197
231
|
signing_key:
|
|
198
232
|
specification_version: 4
|
|
199
233
|
summary: Harness the power of PostgreSQL when crafting JSON reponses
|
|
200
234
|
test_files:
|
|
235
|
+
- spec/generators/main_generator_spec.rb
|
|
201
236
|
- spec/serializer_spec.rb
|
|
202
237
|
- spec/spec_helper.rb
|