userializer 0.1.3 → 0.3.0

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: 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