barley 0.2.0 → 0.4.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 +4 -4
- data/README.md +47 -8
- data/lib/barley/serializable.rb +18 -2
- data/lib/barley/serializer.rb +180 -17
- data/lib/barley/version.rb +1 -1
- data/lib/barley.rb +1 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: faa7d627d11906ae112f0a8fd640f0a2d90acacc114c63297a26e227d5120c3d
|
4
|
+
data.tar.gz: c36d22bee59469e7e98f481086a5a32a75ac96d9bab984330a4d945c97efa5f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3adb8da65be384daf38feaf056541f6b3e2c2f7f400917ed2da43a9f382e3bbc28afdad2437fe7489bdcd1ccd3980a587697525f0b493e1ec7b5374acfb61bb8
|
7
|
+
data.tar.gz: a359ed1a3c15f8efdd62ec86112163edc8d423f9d0d8c8790e781c651ac208dfa722a42bb4c93f822b5bbfc2a072626bbf1c9ad088c24354828745476247fc58
|
data/README.md
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
-

|
2
2
|
|
3
3
|
Barley is a dead simple, fast, and efficient ActiveModel JSON serializer.
|
4
4
|
|
5
|
-
Cerealize your ActiveModel objects into flat
|
5
|
+
Cerealize your ActiveModel objects into flat hashes with a dead simple, yet versatile DSL, and caching baked in. Our daily bread is to make your API faster.
|
6
6
|
|
7
7
|
You don't believe us? Check out the [benchmarks](#benchmarks). 😎
|
8
8
|
|
9
|
+
## API documentation
|
10
|
+
[Check out the API documentation here](https://rubydoc.info/github/MoskitoHero/barley/main).
|
11
|
+
|
9
12
|
## Usage
|
10
13
|
Add the `Barley::Serializable` module to your ActiveModel object.
|
11
14
|
|
@@ -21,10 +24,20 @@ Then define your attributes and associations in a serializer class.
|
|
21
24
|
```ruby
|
22
25
|
# /app/serializers/user_serializer.rb
|
23
26
|
class UserSerializer < Barley::Serializer
|
24
|
-
attributes :
|
25
|
-
|
26
|
-
|
27
|
-
|
27
|
+
attributes id: Types::Strict::Integer, :name # multiple attributes, optional type checking with dry-types
|
28
|
+
attribute :email # single attribute
|
29
|
+
attribute :value, type: Types::Coercible::Integer # optional type checking with dry-types
|
30
|
+
|
31
|
+
many :posts # relations
|
32
|
+
one :group, serializer: CustomGroupSerializer # custom serializer
|
33
|
+
many :related_users, key: :friends, cache: true # custom key, and caching
|
34
|
+
one :profile, cache: { expires_in: 1.day } do # cache definition, and block (on associations) for nested, on-the-fly serializer
|
35
|
+
attributes :avatar, :social_url
|
36
|
+
attribute :badges do
|
37
|
+
object.badges.map(&:display_name) # use object in a block to return custom code
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
28
41
|
end
|
29
42
|
```
|
30
43
|
|
@@ -234,6 +247,27 @@ Barley.configure do |config|
|
|
234
247
|
end
|
235
248
|
```
|
236
249
|
|
250
|
+
## Type checking
|
251
|
+
Barley can check the type of the object you are serializing with the [dry-types](https://dry-rb.org/gems/dry-types/main/) gem.
|
252
|
+
|
253
|
+
It will raise an error if the object is not of the expected type, or coerce it to the correct type and perform constraints checks.
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
module Types
|
257
|
+
include Dry.Types()
|
258
|
+
end
|
259
|
+
|
260
|
+
class UserSerializer < Barley::Serializer
|
261
|
+
attributes id: Types::Strict::Integer, name: Types::Strict::String, email: Types::Strict::String.constrained(format: URI::MailTo::EMAIL_REGEXP)
|
262
|
+
|
263
|
+
attribute :role, type: Types::Coercible::String do
|
264
|
+
object.role.integer_or_string_coercible_value
|
265
|
+
end
|
266
|
+
end
|
267
|
+
```
|
268
|
+
|
269
|
+
Check out [dry-types](https://dry-rb.org/gems/dry-types/main/) for all options and available types.
|
270
|
+
|
237
271
|
## Breakfast mode 🤡 (coming soon)
|
238
272
|
You will soon be able to replace all occurrences of `Serializer` with `Cerealizer` in your codebase. Just for fun. And for free.
|
239
273
|
|
@@ -264,14 +298,14 @@ Ah ah ah. This is so funny.
|
|
264
298
|
*Note: we are thinking about adding a `Surrealizer` class for the most advanced users. Stay tuned.*
|
265
299
|
|
266
300
|
## JSON:API
|
267
|
-
|
301
|
+
Barley does not serialize to the JSON:API standard. We prefer to keep it simple and fast.
|
268
302
|
|
269
303
|
## Benchmarks
|
270
304
|
This gem is blazing fast and efficient. It is 2 to 3 times faster than [ActiveModel::Serializer](https://github.com/rails-api/active_model_serializers) and twice as fast as [FastJsonapi](https://github.com/Netflix/fast_jsonapi). Memory object allocation is also much lower.
|
271
305
|
|
272
306
|
With caching enabled, it is just mind-blowing. We think. *Disclaimer: we do not serialize to the JSON:API standard, so that might be the reason why we are so fast.*
|
273
307
|
|
274
|
-
This is the result we get with the benchmark script used in the AMS repo on an Apple Silicon M1Pro processor.
|
308
|
+
This is the result we get with the benchmark script used in the AMS repo on an Apple Silicon M1Pro processor. [Check it out for yourself here](https://github.com/MoskitoHero/active_model_serializers/tree/benchmarks).
|
275
309
|
|
276
310
|
```shell
|
277
311
|
bundle exec ruby benchmark.rb
|
@@ -351,3 +385,8 @@ ams : 1299674 allocated - 28.20x more
|
|
351
385
|
|
352
386
|
## License
|
353
387
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
388
|
+
|
389
|
+
## Credits
|
390
|
+
Barley is brought to you by the developer team from [StockPro](https://www.stock-pro.fr/).
|
391
|
+
|
392
|
+
[](https://www.stock-pro.fr/)
|
data/lib/barley/serializable.rb
CHANGED
@@ -4,6 +4,7 @@ module Barley
|
|
4
4
|
# Makes a Model serializable
|
5
5
|
#
|
6
6
|
# * Allows setting a default model Serializer
|
7
|
+
#
|
7
8
|
# @example
|
8
9
|
# class Item < ApplicationRecord
|
9
10
|
# include Barley::Serializable
|
@@ -19,10 +20,15 @@ module Barley
|
|
19
20
|
class_methods do
|
20
21
|
# @example without cache
|
21
22
|
# serializer ItemSerializer
|
23
|
+
#
|
22
24
|
# @example with cache
|
23
25
|
# serializer ItemSerializer, cache: true
|
26
|
+
#
|
24
27
|
# @example with cache and expires_in
|
25
28
|
# serializer ItemSerializer, cache: {expires_in: 1.hour}
|
29
|
+
#
|
30
|
+
# @param klass [Class] the serializer class
|
31
|
+
# @param cache [Boolean, Hash<Symbol, ActiveSupport::Duration>] whether to cache the result, or a hash with options for the cache
|
26
32
|
def serializer(klass, cache: false)
|
27
33
|
define_method(:serializer) do
|
28
34
|
klass.new(self, cache: cache)
|
@@ -33,9 +39,19 @@ module Barley
|
|
33
39
|
included do
|
34
40
|
serializer "#{self}Serializer".constantize
|
35
41
|
|
36
|
-
|
42
|
+
# Serializes the model
|
43
|
+
#
|
44
|
+
# @note this method does not provide default rails options like `only` or `except`.
|
45
|
+
# This is because the Barley serializer should be the only place where the attributes are defined.
|
46
|
+
#
|
47
|
+
# @param serializer [Class] the serializer to use
|
48
|
+
# @param cache [Boolean, Hash<Symbol, ActiveSupport::Duration>] whether to cache the result, or a hash with options for the cache
|
49
|
+
# @param root [Boolean] whether to include the root key in the hash
|
50
|
+
#
|
51
|
+
# @return [Hash] the serialized attributes
|
52
|
+
def as_json(serializer: nil, cache: false, root: false)
|
37
53
|
serializer ||= self.serializer.class
|
38
|
-
serializer.new(self, cache: cache).
|
54
|
+
serializer.new(self, cache: cache, root: root).serializable_hash
|
39
55
|
end
|
40
56
|
end
|
41
57
|
end
|
data/lib/barley/serializer.rb
CHANGED
@@ -5,29 +5,118 @@ module Barley
|
|
5
5
|
attr_accessor :object
|
6
6
|
|
7
7
|
class << self
|
8
|
+
# Defines attributes for the serializer
|
9
|
+
#
|
10
|
+
# Accepts either a list of symbols or a hash of symbols and Dry::Types, or a mix of both
|
11
|
+
#
|
12
|
+
# @example only symbols
|
13
|
+
# attributes :id, :name, :email
|
14
|
+
# # => {id: 1234, name: "John Doe", email: "john.doe@example"}
|
15
|
+
#
|
16
|
+
# @example with types
|
17
|
+
# attributes id: Types::Strict::Integer, name: Types::Strict::String, email: Types::Strict::String
|
18
|
+
# # => {id: 1234, name: "John Doe", email: "john.doe@example"}
|
19
|
+
#
|
20
|
+
# @example with types and symbols
|
21
|
+
# attributes :id, name: Types::Strict::String, email: Types::Strict::String
|
22
|
+
# # => {id: 1234, name: "John Doe", email: "john.doe@example"}
|
23
|
+
#
|
24
|
+
# @see Serializer#attribute
|
25
|
+
#
|
26
|
+
# @param keys [Hash<Symbol, Dry::Types>, Array<Symbol>] mix of symbols and hashes of symbols and Dry::Types
|
8
27
|
def attributes(*keys)
|
28
|
+
if keys.last.is_a?(Hash)
|
29
|
+
keys.pop.each do |key, type|
|
30
|
+
attribute(key, type: type)
|
31
|
+
end
|
32
|
+
end
|
9
33
|
keys.each do |key|
|
10
|
-
|
11
|
-
|
34
|
+
if key.is_a?(Hash)
|
35
|
+
attribute(key.keys.first, type: key.values.first)
|
36
|
+
else
|
37
|
+
attribute(key)
|
12
38
|
end
|
13
|
-
set_class_iv(:@defined_attributes, key)
|
14
39
|
end
|
15
40
|
end
|
16
41
|
|
17
|
-
|
42
|
+
# Defines a single attribute for the serializer
|
43
|
+
#
|
44
|
+
# Type checking is done with Dry::Types. If a type is not provided, the value is returned as is.
|
45
|
+
# Dry::Types can be used to coerce the value to the desired type and to check constraints.
|
46
|
+
#
|
47
|
+
# @see https://dry-rb.org/gems/dry-types/main/
|
48
|
+
#
|
49
|
+
# @raise [Dry::Types::ConstraintError] if the type does not match
|
50
|
+
#
|
51
|
+
# @example simple attribute
|
52
|
+
# attribute :id
|
53
|
+
# # => {id: 1234}
|
54
|
+
#
|
55
|
+
# @example attribute with a different key name
|
56
|
+
# attribute :name, key_name: :full_name
|
57
|
+
# # => {full_name: "John Doe"}
|
58
|
+
#
|
59
|
+
# @example attribute with a type
|
60
|
+
# attribute :email, type: Types::Strict::String
|
61
|
+
# # => {email: "john.doe@example"}
|
62
|
+
#
|
63
|
+
# @example attribute with a type and a block
|
64
|
+
# attribute :email, type: Types::Strict::String do
|
65
|
+
# object.email.upcase
|
66
|
+
# end
|
67
|
+
# # => {email: "JOHN.DOE@EXAMPLE"}
|
68
|
+
#
|
69
|
+
# @param key [Symbol] the attribute name
|
70
|
+
# @param key_name [Symbol] the key name in the hash
|
71
|
+
# @param type [Dry::Types] the type to use, or coerce the value to
|
72
|
+
# @param block [Proc] a block to use to compute the value
|
73
|
+
def attribute(key, key_name: nil, type: nil, &block)
|
18
74
|
key_name ||= key
|
19
75
|
if block
|
20
76
|
define_method(key_name) do
|
21
|
-
instance_eval(&block)
|
77
|
+
type.nil? ? instance_eval(&block) : type[instance_eval(&block)]
|
22
78
|
end
|
23
79
|
else
|
24
80
|
define_method(key_name) do
|
25
|
-
object.send(key)
|
81
|
+
type.nil? ? object.send(key) : type[object.send(key)]
|
26
82
|
end
|
27
83
|
end
|
28
84
|
set_class_iv(:@defined_attributes, key_name)
|
29
85
|
end
|
30
86
|
|
87
|
+
# Defines a single association for the serializer
|
88
|
+
#
|
89
|
+
# @example using the default serializer of the associated model
|
90
|
+
# one :group
|
91
|
+
# # => {group: {id: 1234, name: "Group 1"}}
|
92
|
+
#
|
93
|
+
# @example using a custom serializer
|
94
|
+
# one :group, serializer: MyCustomGroupSerializer
|
95
|
+
# # => {group: {id: 1234, name: "Group 1"}}
|
96
|
+
#
|
97
|
+
# @example using a block with an inline serializer definition
|
98
|
+
# one :group do
|
99
|
+
# attributes :id, :name
|
100
|
+
# end
|
101
|
+
# # => {group: {id: 1234, name: "Group 1"}}
|
102
|
+
#
|
103
|
+
# @example using a different key name
|
104
|
+
# one :group, key_name: :my_group
|
105
|
+
# # => {my_group: {id: 1234, name: "Group 1"}}
|
106
|
+
#
|
107
|
+
# @example using cache
|
108
|
+
# one :group, cache: true
|
109
|
+
# # => {group: {id: 1234, name: "Group 1"}}
|
110
|
+
#
|
111
|
+
# @example using cache and expires_in
|
112
|
+
# one :group, cache: {expires_in: 1.hour}
|
113
|
+
# # => {group: {id: 1234, name: "Group 1"}}
|
114
|
+
#
|
115
|
+
# @param key [Symbol] the association name
|
116
|
+
# @param key_name [Symbol] the key name in the hash
|
117
|
+
# @param serializer [Class] the serializer to use
|
118
|
+
# @param cache [Boolean, Hash<Symbol, ActiveSupport::Duration>] whether to cache the result, or a hash with options for the cache
|
119
|
+
# @param block [Proc] a block to use to define the serializer inline
|
31
120
|
def one(key, key_name: nil, serializer: nil, cache: false, &block)
|
32
121
|
key_name ||= key
|
33
122
|
if block
|
@@ -40,11 +129,44 @@ module Barley
|
|
40
129
|
return {} if element.nil?
|
41
130
|
|
42
131
|
el_serializer = serializer || element.serializer.class
|
43
|
-
el_serializer.new(element, cache: cache).
|
132
|
+
el_serializer.new(element, cache: cache).serializable_hash
|
44
133
|
end
|
45
134
|
set_class_iv(:@defined_attributes, key_name)
|
46
135
|
end
|
47
136
|
|
137
|
+
# Defines a collection association for the serializer
|
138
|
+
#
|
139
|
+
# @example using the default serializer of the associated model
|
140
|
+
# many :groups
|
141
|
+
# # => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}
|
142
|
+
#
|
143
|
+
# @example using a custom serializer
|
144
|
+
# many :groups, serializer: MyCustomGroupSerializer
|
145
|
+
# # => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}
|
146
|
+
#
|
147
|
+
# @example using a block with an inline serializer definition
|
148
|
+
# many :groups do
|
149
|
+
# attributes :id, :name
|
150
|
+
# end
|
151
|
+
# # => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}
|
152
|
+
#
|
153
|
+
# @example using a different key name
|
154
|
+
# many :groups, key_name: :my_groups
|
155
|
+
# # => {my_groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}
|
156
|
+
#
|
157
|
+
# @example using cache
|
158
|
+
# many :groups, cache: true
|
159
|
+
# # => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}
|
160
|
+
#
|
161
|
+
# @example using cache and expires_in
|
162
|
+
# many :groups, cache: {expires_in: 1.hour}
|
163
|
+
# # => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}
|
164
|
+
#
|
165
|
+
# @param key [Symbol] the association name
|
166
|
+
# @param key_name [Symbol] the key name in the hash
|
167
|
+
# @param serializer [Class] the serializer to use
|
168
|
+
# @param cache [Boolean, Hash<Symbol, ActiveSupport::Duration>] whether to cache the result, or a hash with options for the cache
|
169
|
+
# @param block [Proc] a block to use to define the serializer inline
|
48
170
|
def many(key, key_name: nil, serializer: nil, cache: false, &block)
|
49
171
|
key_name ||= key
|
50
172
|
if block
|
@@ -57,11 +179,17 @@ module Barley
|
|
57
179
|
return [] if elements.empty?
|
58
180
|
|
59
181
|
el_serializer = serializer || elements.first.serializer.class
|
60
|
-
elements.map { |element| el_serializer.new(element, cache: cache).
|
182
|
+
elements.map { |element| el_serializer.new(element, cache: cache).serializable_hash }.reject(&:blank?)
|
61
183
|
end
|
62
184
|
set_class_iv(:@defined_attributes, key_name)
|
63
185
|
end
|
64
186
|
|
187
|
+
# Either sets or appends a key to an instance variable
|
188
|
+
#
|
189
|
+
# @api private
|
190
|
+
#
|
191
|
+
# @param iv [Symbol] the instance variable to set
|
192
|
+
# @param key [Symbol] the key to add to the instance variable
|
65
193
|
def set_class_iv(iv, key)
|
66
194
|
instance_variable_defined?(iv) ? instance_variable_get(iv) << key : instance_variable_set(iv, [key])
|
67
195
|
end
|
@@ -69,10 +197,16 @@ module Barley
|
|
69
197
|
|
70
198
|
# @example with cache
|
71
199
|
# Barley::Serializer.new(object, cache: true)
|
200
|
+
#
|
72
201
|
# @example with cache and expires_in
|
73
202
|
# Barley::Serializer.new(object, cache: {expires_in: 1.hour})
|
74
|
-
|
203
|
+
#
|
204
|
+
# @param object [Object] the object to serialize
|
205
|
+
# @param cache [Boolean, Hash<Symbol, ActiveSupport::Duration>] a boolean to cache the result, or a hash with options for the cache
|
206
|
+
# @param root [Boolean] whether to include the root key in the hash
|
207
|
+
def initialize(object, cache: false, root: false)
|
75
208
|
@object = object
|
209
|
+
@root = root
|
76
210
|
@cache, @expires_in = if cache.is_a?(Hash)
|
77
211
|
[true, cache[:expires_in]]
|
78
212
|
else
|
@@ -80,39 +214,68 @@ module Barley
|
|
80
214
|
end
|
81
215
|
end
|
82
216
|
|
83
|
-
|
217
|
+
# Serializes the object
|
218
|
+
#
|
219
|
+
# @return [Hash] the serializable hash
|
220
|
+
def serializable_hash
|
84
221
|
if @cache
|
85
|
-
|
86
|
-
|
87
|
-
_as_json
|
222
|
+
Barley::Cache.fetch(cache_base_key, expires_in: @expires_in) do
|
223
|
+
_serializable_hash
|
88
224
|
end
|
89
225
|
else
|
90
|
-
|
226
|
+
_serializable_hash
|
91
227
|
end
|
92
228
|
end
|
93
229
|
|
230
|
+
# Clears the cache for the object
|
231
|
+
#
|
232
|
+
# @param key [String] the cache key
|
233
|
+
#
|
234
|
+
# @return [Boolean] whether the cache was cleared
|
94
235
|
def clear_cache(key: cache_base_key)
|
95
236
|
Barley::Cache.delete(key)
|
96
237
|
end
|
97
238
|
|
98
239
|
private
|
99
240
|
|
241
|
+
# @api private
|
242
|
+
#
|
243
|
+
# @return [String] the cache key
|
100
244
|
def cache_base_key
|
101
|
-
|
245
|
+
if object.updated_at.present?
|
246
|
+
"#{object.class.name&.underscore}/#{object.id}/#{object.updated_at&.to_i}/barley_cache/"
|
247
|
+
else
|
248
|
+
"#{object.class.name&.underscore}/#{object.id}/barley_cache/"
|
249
|
+
end
|
102
250
|
end
|
103
251
|
|
252
|
+
# @api private
|
253
|
+
#
|
254
|
+
# @return [Array<Symbol>] the defined attributes
|
104
255
|
def defined_attributes
|
105
256
|
self.class.instance_variable_get(:@defined_attributes)
|
106
257
|
end
|
107
258
|
|
108
|
-
|
259
|
+
# Serializes the object
|
260
|
+
#
|
261
|
+
# @api private
|
262
|
+
#
|
263
|
+
# @return [Hash] the serializable hash
|
264
|
+
def _serializable_hash
|
109
265
|
hash = {}
|
110
266
|
|
111
267
|
defined_attributes.each do |key|
|
112
268
|
hash[key] = send(key)
|
113
269
|
end
|
114
270
|
|
115
|
-
hash
|
271
|
+
@root ? {root_key => hash} : hash
|
272
|
+
end
|
273
|
+
|
274
|
+
# @api private
|
275
|
+
#
|
276
|
+
# @return [Symbol] the root key, based on the class name
|
277
|
+
def root_key
|
278
|
+
object.class.name.underscore.to_sym
|
116
279
|
end
|
117
280
|
end
|
118
281
|
end
|
data/lib/barley/version.rb
CHANGED
data/lib/barley.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: barley
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cedric Delalande
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-10-
|
11
|
+
date: 2023-10-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 6.1.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dry-types
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.7.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.7.1
|
27
41
|
description: Cerealize your ActiveModel objects into flat JSON objects with a dead
|
28
42
|
simple DSL. Our daily bread is to make your API faster.
|
29
43
|
email:
|