active_model_serializers_pg 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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