userializer 0.1.3 → 0.3.0

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: 176f64904bdd2aebe075ad93bb12ff1f900bdf5624b2a8a1c86001d7e9de497d
4
- data.tar.gz: e7ca4ff8d4436e8880772f4e8c526137669b9993fdce4e87f23674d92ab071fd
3
+ metadata.gz: f3ef32ea4a17b463844f6c5f59453e206253c59c21735ea9aa6e97c353db2c3d
4
+ data.tar.gz: 77c4cdcba82ef5a5470da04db24e2984ae7754d8ba25593c0097470f3c0fdf65
5
5
  SHA512:
6
- metadata.gz: 51e1b63bcd632e558169f670fef2c83a5360fc8c0cce079504088b0574c972050f523ffcc93b5f6fd3ce970e0bb7c20d9d9873a47ad7587051012b7b96853052
7
- data.tar.gz: b9d61df410bf4d35d7f91d66fa439999c424164acf159990ca8105ed9795889e5619f3fa22b6cde3d8018a846dd9cd917caf6131334c257dd3027c3c0dd61873
6
+ metadata.gz: 4e48196e2d4264d655eb4da209e7635c56f4849eac2e977522379f358ccf7969cd6189bf3fe5675a721ef3ac9f2e89392099541189ef97f503a1ed1c0ea94f4f
7
+ data.tar.gz: d8a897a3e951c27488e700fb1f80385501367088219ab7b24505f1a7420b478f5171734f4641d319c9b8d9bf2a119407a8f2012c1bf4d5e6926709b33c99c7db
@@ -0,0 +1,18 @@
1
+ version: 2
2
+ jobs:
3
+ run_tests:
4
+ docker:
5
+ - image: circleci/ruby:2.7
6
+
7
+ working_directory: ~/userializer
8
+
9
+ steps:
10
+ - checkout
11
+ - run: bundle install --path=vendor/bundle
12
+ - run: bundle exec rspec --color --require spec_helper --format progress spec
13
+
14
+ workflows:
15
+ version: 2
16
+ test:
17
+ jobs:
18
+ - run_tests
data/.gitignore CHANGED
@@ -9,3 +9,4 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+ Gemfile.lock
data/README.md CHANGED
@@ -20,7 +20,179 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- TODO: Write usage instructions here
23
+ USerializer's DSL is relatively close to Active Model Serializer's,
24
+ while having a few additional features including:
25
+ * Attributes Conditional Declaration
26
+ * Attributes Inline Definition
27
+
28
+ ### Attributes Conditional Declaration
29
+
30
+ USerializer allows you to dynamically decide wether an attribute should
31
+ be serialized or not by passing its definition an `if` block as follows:
32
+ ```ruby
33
+ attributes :conditional_attr, if: proc { |_, opts| ... }
34
+ ```
35
+
36
+ Eg: Let's say you want to serialize an `Order` object but want to
37
+ include its `price` only if it's superior to *10*, your serializer
38
+ would look like the following:
39
+ ```ruby
40
+ class Order < ActiveRecord::Base
41
+ def price
42
+ 10
43
+ end
44
+ end
45
+
46
+ class OrderSerializer < USerializer::BaseSerializer
47
+ attributes :price, if: proc do |obj, _|
48
+ obj.price > 10
49
+ end
50
+ end
51
+ ```
52
+
53
+ In that case for example, the `price` attribute would be omitted from
54
+ the final response.
55
+
56
+ ### Attributes Inline Definition
57
+
58
+ Using AMS, the only way to rewrite an attribute prior to serialization
59
+ is to override it using a method with the same name, leading to
60
+ something like this:
61
+ ```ruby
62
+ class MyObject < ActiveRecord::Base
63
+ def random_attr
64
+ 0
65
+ end
66
+ end
67
+
68
+ class MyObjectSerializer < ActiveModel::Serializer
69
+ attributes :random_attr
70
+
71
+ def random_attr
72
+ object.random_attr + 1
73
+ end
74
+ end
75
+ ```
76
+
77
+ While this code works perfectly, it pushes the serialized attribute
78
+ value definition back from its declaration, causing developers to lose
79
+ focus when listing their serialized attributes because the overriding is
80
+ done farther.
81
+
82
+ With USerializer, all of this is done in an inline way, so that you can
83
+ override the attribute's value while declaring using a block as
84
+ follows:
85
+ ```ruby
86
+ attributes :your_attribute do |object, _|
87
+ ...
88
+ end
89
+ ```
90
+
91
+ Our `random_attr` serialization would then looks like this with
92
+ USerializer:
93
+ ```ruby
94
+ class MyObjectSerializer < USerializer::BaseSerializer
95
+ attributes :random_attr do |object, _|
96
+ object.random_attr + 1
97
+ end
98
+ end
99
+ ```
100
+
101
+ Way nicer, right?
102
+
103
+ ### Relationships
104
+
105
+ Just like AMS, USerializer supports `has_one` and `has_many`
106
+ relationships
107
+
108
+ ### Serialized Output
109
+
110
+ The following outputs will be based an on our `Order` object in
111
+ different situations:
112
+
113
+ * Order is serialized without any relationships:
114
+ ```json
115
+ {
116
+ "order": {
117
+ "id": 1,
118
+ "attr_1": "value_1",
119
+ "attr_2": "value_2",
120
+ "attr_3": "value_3",
121
+ }
122
+ }
123
+ ```
124
+
125
+ * Order has a `has_one` relationship with a `Client` model
126
+ ```json
127
+ {
128
+ "clients": [
129
+ {
130
+ "id": 4,
131
+ "name": "userializer client",
132
+ ...
133
+ }
134
+ ],
135
+ "order": {
136
+ "id": 1,
137
+ "attr_1": "value_1",
138
+ "attr_2": "value_2",
139
+ "attr_3": "value_3",
140
+ "client_id": 4
141
+ }
142
+ }
143
+ ```
144
+
145
+ * Order has a `has_many` relationship with an `Article` model
146
+ ```json
147
+ {
148
+ "articles": [
149
+ {
150
+ "id": 1,
151
+ "name": "Article #1",
152
+ ...
153
+ },
154
+ {
155
+ "id": 1,
156
+ "name": "Article #2",
157
+ ...
158
+ }
159
+ ],
160
+ "order": {
161
+ "id": 1,
162
+ "attr_1": "value_1",
163
+ "attr_2": "value_2",
164
+ "attr_3": "value_3",
165
+ "article_ids": [1, 2]
166
+ }
167
+ }
168
+ ```
169
+
170
+ ### CompositeSerializer
171
+
172
+ Imagine you have a compound of different data that you want to return to the same payload.
173
+ For example, you have an **array** of a `Foo` class and a `Bar` value to return.
174
+ You can use a `CompositeSerializer` to serialize both.
175
+
176
+ ```ruby
177
+ array_foo = [Foo.new, Foo.new]
178
+ bar = Bar.new
179
+
180
+ CompositeSerializer.new(
181
+ { key_foo: array_foo, key_bar: bar },
182
+ each_serializer: { key_foo: FooCustomSerializer },
183
+ serializer: { key_bar: BarSerializer },
184
+ root: { key_foo: :foo_root, key_bar: :bar_root }
185
+ ).to_json
186
+ ```
187
+
188
+ this will render:
189
+
190
+ ```json
191
+ {
192
+ "foo_root": [{... foo1 attributes ...}, {... foo2 attributes ...}],
193
+ "bar_root": {... bar attributes ...}
194
+ }
195
+ ```
24
196
 
25
197
  ## Development
26
198
 
data/lib/userializer.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'userializer/version'
2
2
  require 'userializer/base_serializer'
3
3
  require 'userializer/array_serializer'
4
+ require 'userializer/composite_serializer'
4
5
 
5
6
  module USerializer
6
7
  NS_SEPARATOR = '::'.freeze
@@ -19,14 +19,18 @@ module USerializer
19
19
  ActiveSupport::Inflector.underscore(obj_class.name).split('/').last
20
20
  ).to_sym if obj_class
21
21
 
22
- @serializer = opts[:each_serializer] || USerializer.infered_serializer_class(
23
- obj_class
24
- )
22
+ serializer = opts[:each_serializer]
23
+
24
+ @serializer = if serializer&.is_a?(Proc)
25
+ serializer
26
+ elsif serializer
27
+ proc { serializer }
28
+ end
25
29
  end
26
30
 
27
31
  def merge_root(res, opts)
28
32
  @objs.each do |obj|
29
- @serializer.new(obj, @opts).merge_root(res, @root_key, false, opts)
33
+ serializer(obj, opts).merge_root(res, @root_key, false, opts)
30
34
  end
31
35
  end
32
36
 
@@ -46,5 +50,14 @@ module USerializer
46
50
  end
47
51
 
48
52
  def scope; @opts[:scope]; end
53
+
54
+ private
55
+
56
+ def serializer(obj, opts)
57
+ return @serializer.call(obj, opts).new(obj, @opts) if @serializer
58
+ return obj.serialize if obj.respond_to?(:serialize)
59
+
60
+ USerializer.infered_serializer_class(obj.class).new(obj, @opts)
61
+ end
49
62
  end
50
63
  end
@@ -8,8 +8,8 @@ module USerializer
8
8
  class BaseSerializer
9
9
  class << self
10
10
  def inherited(subclass)
11
- subclass.attrs = self.attrs || { id: Attribute.new(:id, {}, nil) }
12
- subclass.relations = self.relations || {}
11
+ subclass.attrs = self.attrs.dup || { id: Attribute.new(:id, {}, nil) }
12
+ subclass.relations = self.relations.dup || {}
13
13
  end
14
14
 
15
15
  def attributes(*attrs, &block)
@@ -0,0 +1,78 @@
1
+ require 'oj'
2
+
3
+ module USerializer
4
+ class CompositeObject
5
+ def initialize(obj, opts = {})
6
+ @obj = obj
7
+ @opts = opts
8
+ @root_key = opts[:root].to_sym
9
+ serializer = opts[:serializer]
10
+ @serializer = if serializer.is_a?(Proc)
11
+ serializer
12
+ elsif serializer
13
+ proc { serializer }
14
+ end
15
+ end
16
+
17
+ def merge_root(res, opts)
18
+ serializer(@obj, opts).merge_root(res, @root_key, true, opts)
19
+ end
20
+
21
+ private
22
+
23
+ def serializer(obj, opts)
24
+ return @serializer.call(obj, opts).new(obj, @opts) if @serializer
25
+ return obj.serialize if obj.respond_to?(:serialize)
26
+
27
+ USerializer.infered_serializer_class(obj.class).new(obj, @opts)
28
+ end
29
+ end
30
+
31
+ class CompositeSerializer
32
+ def initialize(objs, opts = {})
33
+ @opts = opts
34
+ @objs = compose_objs(objs)
35
+ end
36
+
37
+ def merge_root(res, opts)
38
+ @objs.each do |obj|
39
+ obj.merge_root(res, opts)
40
+ end
41
+ end
42
+
43
+ def to_hash
44
+ res = {}
45
+
46
+ merge_root(res, @opts)
47
+ res
48
+ end
49
+
50
+ def serialize(*_args)
51
+ to_hash
52
+ end
53
+
54
+ def to_json(*_args)
55
+ Oj.dump(to_hash, mode: :compat)
56
+ end
57
+
58
+ private
59
+
60
+ def compose_objs(objs)
61
+ objs.map do |(key, obj)|
62
+ opts = options_for(key)
63
+
64
+ if obj.is_a? Enumerable
65
+ ArraySerializer.new(obj, opts)
66
+ else
67
+ CompositeObject.new(obj, opts)
68
+ end
69
+ end
70
+ end
71
+
72
+ def options_for(key)
73
+ @opts.reduce({}) do |acc, (opt_key, values)|
74
+ acc.merge(opt_key.to_sym => values[key.to_sym])
75
+ end.compact.merge(root: key) { |_, x, y| x || y }
76
+ end
77
+ end
78
+ end
@@ -8,21 +8,31 @@ module USerializer
8
8
  @opts = opts
9
9
  @id_key = "#{ActiveSupport::Inflector.singularize(key)}_ids".to_sym
10
10
 
11
+ @embed_key = opts[:embed_key] || :id
11
12
  @conditional_block = opts[:if] || proc { true }
12
13
  end
13
14
 
14
15
  def merge_attributes(res, ser, opts)
15
16
  return unless @conditional_block.call(ser.object, opts)
16
17
 
17
- res[@id_key] = (ser.send(@key) || []).compact.map(&:id).compact
18
+ res[@id_key] = (entities(ser) || []).compact.map do |obj|
19
+ obj.nil? ? nil : obj.send(@embed_key)
20
+ end.compact
18
21
  end
19
22
 
20
23
  def merge_root(res, ser, opts)
21
- objs = ser.send(@key) || []
24
+ objs = entities(ser) || []
22
25
 
23
26
  return if objs.empty? || !@conditional_block.call(ser.object, opts)
24
27
 
25
28
  ArraySerializer.new(objs, @opts).merge_root(res, opts)
26
29
  end
30
+
31
+ def entities(ser)
32
+ obj = ser.send(@key) || []
33
+ return obj unless @opts[:scope]
34
+
35
+ obj.send(@opts[:scope])
36
+ end
27
37
  end
28
38
  end
@@ -6,8 +6,15 @@ module USerializer
6
6
  @id_key = "#{key}_id".to_sym
7
7
  @root_key = opts[:root]&.to_sym
8
8
 
9
- @serializer = opts[:serializer]
9
+ serializer = opts[:serializer]
10
10
 
11
+ @serializer = if serializer&.is_a?(Proc)
12
+ @serializer = serializer
13
+ elsif serializer
14
+ proc { serializer }
15
+ end
16
+
17
+ @embed_key = opts[:embed_key] || :id
11
18
  @conditional_block = opts[:if] || proc { true }
12
19
  end
13
20
 
@@ -17,7 +24,7 @@ module USerializer
17
24
  return unless @conditional_block.call(ser.object, opts)
18
25
 
19
26
  obj = ser.send(@key)
20
- res[@id_key] = obj&.id
27
+ res[@id_key] = obj.nil? ? nil : obj.send(@embed_key)
21
28
  end
22
29
 
23
30
  def merge_root(res, ser, opts)
@@ -25,13 +32,13 @@ module USerializer
25
32
 
26
33
  return if obj.nil? || !@conditional_block.call(ser.object, opts)
27
34
 
28
- serializer(obj).merge_root(res, root_key(obj), false, opts)
35
+ serializer(obj, opts).merge_root(res, root_key(obj), false, opts)
29
36
  end
30
37
 
31
38
  private
32
39
 
33
- def serializer(obj)
34
- return @serializer.new(obj, @opts) if @serializer
40
+ def serializer(obj, opts)
41
+ return @serializer.call(obj, opts).new(obj, @opts) if @serializer
35
42
  return obj.serialize if obj.respond_to?(:serialize)
36
43
 
37
44
  USerializer.infered_serializer_class(obj.class).new(obj, @opts)
@@ -1,3 +1,3 @@
1
1
  module USerializer
2
- VERSION = "0.1.3"
2
+ VERSION = "0.3.0"
3
3
  end
data/userializer.gemspec CHANGED
@@ -20,8 +20,8 @@ Gem::Specification.new do |spec|
20
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
21
  spec.require_paths = ["lib"]
22
22
 
23
- spec.add_development_dependency "bundler", "~> 1.16"
24
- spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "bundler", "~> 2.0"
24
+ spec.add_development_dependency "rake", "~> 13.0"
25
25
  spec.add_development_dependency "rspec", "~> 3.0"
26
26
  spec.add_dependency "oj"
27
27
  spec.add_dependency "activesupport"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: userializer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexis Montagne
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-05-10 00:00:00.000000000 Z
11
+ date: 2021-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.16'
19
+ version: '2.0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.16'
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -87,11 +87,11 @@ executables: []
87
87
  extensions: []
88
88
  extra_rdoc_files: []
89
89
  files:
90
+ - ".circleci/config.yml"
90
91
  - ".gitignore"
91
92
  - ".rspec"
92
93
  - ".travis.yml"
93
94
  - Gemfile
94
- - Gemfile.lock
95
95
  - README.md
96
96
  - Rakefile
97
97
  - bin/console
@@ -100,6 +100,7 @@ files:
100
100
  - lib/userializer/array_serializer.rb
101
101
  - lib/userializer/attribute.rb
102
102
  - lib/userializer/base_serializer.rb
103
+ - lib/userializer/composite_serializer.rb
103
104
  - lib/userializer/has_many.rb
104
105
  - lib/userializer/has_one.rb
105
106
  - lib/userializer/version.rb
@@ -107,7 +108,7 @@ files:
107
108
  homepage: https://github.com/upfluence/userializer
108
109
  licenses: []
109
110
  metadata: {}
110
- post_install_message:
111
+ post_install_message:
111
112
  rdoc_options: []
112
113
  require_paths:
113
114
  - lib
@@ -122,9 +123,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
123
  - !ruby/object:Gem::Version
123
124
  version: '0'
124
125
  requirements: []
125
- rubyforge_project:
126
- rubygems_version: 2.7.3
127
- signing_key:
126
+ rubygems_version: 3.1.4
127
+ signing_key:
128
128
  specification_version: 4
129
129
  summary: Write a short summary, because RubyGems requires one.
130
130
  test_files: []
data/Gemfile.lock DELETED
@@ -1,50 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- userializer (0.1.3)
5
- activesupport
6
- oj
7
-
8
- GEM
9
- remote: https://rubygems.org/
10
- specs:
11
- activesupport (5.2.3)
12
- concurrent-ruby (~> 1.0, >= 1.0.2)
13
- i18n (>= 0.7, < 2)
14
- minitest (~> 5.1)
15
- tzinfo (~> 1.1)
16
- concurrent-ruby (1.1.5)
17
- diff-lcs (1.3)
18
- i18n (1.6.0)
19
- concurrent-ruby (~> 1.0)
20
- minitest (5.11.3)
21
- oj (3.7.12)
22
- rake (10.4.2)
23
- rspec (3.8.0)
24
- rspec-core (~> 3.8.0)
25
- rspec-expectations (~> 3.8.0)
26
- rspec-mocks (~> 3.8.0)
27
- rspec-core (3.8.0)
28
- rspec-support (~> 3.8.0)
29
- rspec-expectations (3.8.3)
30
- diff-lcs (>= 1.2.0, < 2.0)
31
- rspec-support (~> 3.8.0)
32
- rspec-mocks (3.8.0)
33
- diff-lcs (>= 1.2.0, < 2.0)
34
- rspec-support (~> 3.8.0)
35
- rspec-support (3.8.0)
36
- thread_safe (0.3.6)
37
- tzinfo (1.2.5)
38
- thread_safe (~> 0.1)
39
-
40
- PLATFORMS
41
- ruby
42
-
43
- DEPENDENCIES
44
- bundler (~> 1.16)
45
- rake (~> 10.0)
46
- rspec (~> 3.0)
47
- userializer!
48
-
49
- BUNDLED WITH
50
- 1.16.1