active_model_serializers_pg 0.0.5 → 0.0.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0fb040d4836aa84e29ba59590ac8bc4373528e31e5186cec1168538d52384fd0
4
- data.tar.gz: a2ef8e881e358597aeb30145a73cbfdff5b7ed663d1c614ba5a40cbceba9d772
3
+ metadata.gz: d35fba508c14ac3f2dac43976aa98853847901d1854a60cdfc17e9307d7bc054
4
+ data.tar.gz: de31287ee80e62dbc4ea06dcef31269d70f1ecdb7611ab6021d56d671057aea7
5
5
  SHA512:
6
- metadata.gz: 30889a0c217b98a0a79296379fd7c399b414b223a3568133e796ef605ae938d013957052c939c65bed5a75fed3e64d08025bc83729a92d620d3fdb905aca13dd
7
- data.tar.gz: 7ff699ff57696cece4ed3f54a2f3d8583f1d2bd2089f65ab842972d9c22c9a2a86360c20e4bd0e043f2532db8f54c332804e3038c3d65f35c3af6be4f297a56e
6
+ metadata.gz: ef15cf70f75a4d508d963593fc806ab388d9f07b295e5a8a393bdcd2ddb1b95094c47f7229ab4113646454211b1dd4550111dc79902c6baf39ddaaa36a4c16a2
7
+ data.tar.gz: 6221b9fa05569ad2fdd6dd874d326363ea698c6a7442a253e25d349adc046814edf1c67ec68a9ddb0f9fe0126052006261eee969093b78462a027bdb4fa94245
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:
data/Rakefile CHANGED
@@ -72,9 +72,14 @@ 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"
78
83
  t.datetime "created_at"
79
84
  t.datetime "updated_at"
80
85
  end
@@ -131,6 +136,8 @@ namespace :db do
131
136
  t.datetime "updated_at"
132
137
  end
133
138
 
139
+ ActiveRecord::Base.connection.execute File.read(File.expand_path('../lib/generators/active_record/templates/jsonb_dasherize.sql', __FILE__))
140
+
134
141
  puts 'Database migrated'
135
142
  end
136
143
  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'
@@ -2,4 +2,4 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec :path => '..'
4
4
 
5
- gem "activerecord", "~>5.0.0"
5
+ gem "rails", "~>5.0.0"
@@ -2,4 +2,4 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec :path => '..'
4
4
 
5
- gem "activerecord", "~>5.1.0"
5
+ gem "rails", "~>5.1.0"
@@ -2,4 +2,4 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec :path => '..'
4
4
 
5
- gem "activerecord", "~>5.2.0"
5
+ gem "rails", "~>5.2.0"
@@ -141,28 +141,10 @@ class JsonThing
141
141
  JsonThing.new(refl.klass, "#{full_name}.#{reflection_name}", nil, serializer_options, refl, self)
142
142
  end
143
143
 
144
- # Gets the attributes (i.e. scalar fields) on the AR class
145
- # as a Set of symbols.
146
- # TODO: tests
147
- def declared_attributes
148
- @declared_attributes ||= Set.new(@ar_class.attribute_types.keys.map(&:to_sym))
149
- end
150
-
151
144
  def enum?(field)
152
145
  @ar_class.attribute_types[field.to_s].is_a? ActiveRecord::Enum::EnumType
153
146
  end
154
147
 
155
- # Gets the reflections (aka associations) of the AR class
156
- # as a Hash from symbol to a subclass of ActiveRecord::Reflection.
157
- # TODO: tests
158
- def declared_reflections
159
- @declared_reflections ||= Hash[
160
- @ar_class.reflections.map{|k, v|
161
- [k.to_sym, v]
162
- }
163
- ]
164
- end
165
-
166
148
  # Checks for alias_attribute and gets to the real attribute name.
167
149
  def unaliased(field_name)
168
150
  ret = field_name
@@ -376,6 +358,15 @@ end
376
358
  class JsonApiPgSql
377
359
  attr_reader :base_serializer, :base_relation
378
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
+
379
370
  def initialize(base_serializer, base_relation, instance_options, options)
380
371
  @base_relation = base_relation
381
372
  @instance_options = instance_options
@@ -452,7 +443,26 @@ class JsonApiPgSql
452
443
  elsif resource.has_sql_method?(field)
453
444
  resource.sql_method(field)
454
445
  else
455
- %Q{"#{resource.table_name}"."#{resource.unaliased(field)}"}
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
+ case resource.ar_class.attribute_types[field.to_s]
451
+ when ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Hstore
452
+ # Fortunately we can cast hstore to jsonb,
453
+ # which gives us a solution that works whether or not the hstore extension is installed.
454
+ # Defining an hstore_dasherize function would work only if the extension were present.
455
+ %Q{jsonb_dasherize("#{resource.table_name}"."#{field}"::jsonb)}
456
+ when ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb
457
+ %Q{jsonb_dasherize("#{resource.table_name}"."#{field}")}
458
+ when self.class.json_column_type
459
+ %Q{jsonb_dasherize("#{resource.table_name}"."#{field}"::jsonb)}
460
+ else
461
+ %Q{"#{resource.table_name}"."#{field}"}
462
+ end
463
+ else
464
+ %Q{"#{resource.table_name}"."#{field}"}
465
+ end
456
466
  end
457
467
  end
458
468
 
@@ -1,3 +1,3 @@
1
1
  module ActiveModelSerializersPg
2
- VERSION = '0.0.5'
2
+ VERSION = '0.0.6'
3
3
  end
@@ -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,11 @@
1
+ class AmsPgCreateDasherizeFunctions < ActiveRecord::Migration<%= migration_version %>
2
+
3
+ def up
4
+ execute %q{<%= jsonb_dasherize %>}
5
+ end
6
+
7
+ def down
8
+ execute "DROP FUNCTION jsonb_dasherize(jsonb)"
9
+ end
10
+
11
+ end
@@ -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
@@ -209,6 +209,53 @@ describe 'ArraySerializer' do
209
209
  end
210
210
  end
211
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
+ }
222
+ let(:options) { { each_serializer: PersonWithJsonSerializer } }
223
+
224
+ before do
225
+ @old_key_setting = ActiveModelSerializers.config.key_transform
226
+ ActiveModelSerializers.config.key_transform = :dash
227
+ end
228
+
229
+ after do
230
+ ActiveModelSerializers.config.key_transform = @old_key_setting
231
+ end
232
+
233
+ it 'generates the proper json output' do
234
+ json_expected = {
235
+ data: [
236
+ {
237
+ id: person.id.to_s,
238
+ type: 'people',
239
+ attributes: {
240
+ prefs: {
241
+ bar: [{ 'jar-jar' => 'binks'}],
242
+ 'foo-foo' => 'baz',
243
+ },
244
+ options: {
245
+ bar: [{ 'jar-jar' => 'binks'}],
246
+ 'foo-foo' => 'baz',
247
+ },
248
+ settings: {
249
+ 'foo-foo' => 'bar'
250
+ },
251
+ },
252
+ }
253
+ ]
254
+ }.to_json
255
+ expect(json_data).to eq json_expected
256
+ end
257
+ end
258
+
212
259
  context 'with aliased association' do
213
260
  let(:relation) { Tag.first }
214
261
  let(:controller) { TagsController.new }
data/spec/spec_helper.rb CHANGED
@@ -1,11 +1,12 @@
1
- require 'active_record'
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,10 @@ class PersonSerializer < ActiveModel::Serializer
56
68
  end
57
69
  end
58
70
 
71
+ class PersonWithJsonSerializer < ActiveModel::Serializer
72
+ attributes :id, :options, :prefs, :settings
73
+ end
74
+
59
75
  class Note < ActiveRecord::Base
60
76
  has_many :tags
61
77
  has_many :sorted_tags
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.5
4
+ version: 0.0.6
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-22 00:00:00.000000000 Z
11
+ date: 2019-10-25 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
@@ -198,5 +231,6 @@ 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