barley 0.2.0 → 0.4.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: bee5cfe197a0a824c4edd25ab6ad77628c14732bafc2ff09ceb64e08e24d6aaf
4
- data.tar.gz: 5d060056d2de5fb5b8c64ca78b84a5faeb39fb4e7714e8de07647a26bfeeb76b
3
+ metadata.gz: faa7d627d11906ae112f0a8fd640f0a2d90acacc114c63297a26e227d5120c3d
4
+ data.tar.gz: c36d22bee59469e7e98f481086a5a32a75ac96d9bab984330a4d945c97efa5f6
5
5
  SHA512:
6
- metadata.gz: 67d5c179e808312c73d5058e634c4ee2a7237d702b931580fd6a2c5e73085c51839a0e1debb8591f172cc696a8cbb9b694ccd7c740880d5fd384995a28011115
7
- data.tar.gz: 5db28559be764eff3ef9990925896c6e1ce11005011335ab993f4fd346313cf0874bd22e7abdfc815616b95b0820c51648ecc22ca4243993b1d5dc9a4f200988
6
+ metadata.gz: 3adb8da65be384daf38feaf056541f6b3e2c2f7f400917ed2da43a9f382e3bbc28afdad2437fe7489bdcd1ccd3980a587697525f0b493e1ec7b5374acfb61bb8
7
+ data.tar.gz: a359ed1a3c15f8efdd62ec86112163edc8d423f9d0d8c8790e781c651ac208dfa722a42bb4c93f822b5bbfc2a072626bbf1c9ad088c24354828745476247fc58
data/README.md CHANGED
@@ -1,11 +1,14 @@
1
- ![Barley loqo](./img/barley.png)
1
+ ![Barley loqo](https://i.imgur.com/am0emi4.png)
2
2
 
3
3
  Barley is a dead simple, fast, and efficient ActiveModel JSON serializer.
4
4
 
5
- Cerealize your ActiveModel objects into flat JSON objects with a dead simple DSL. Our daily bread is to make your API faster.
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 :id, :name, :email, :created_at, :updated_at
25
-
26
- many :posts
27
- one :group
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
- No. Not yet. Maybe never. We don't know. We don't care. We don't use it. We don't like it. We don't want to. We don't have time. We don't have money. We don't have a life. We don't have a girlfriend. We don't have a boyfriend. We don't have a dog. We don't have a cat. We are generating this readme with Copilot.
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. We will push this benchmark as soon as possible so you can see for yourself.
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
+ [![Barley is brought to you by StockPro](https://i.imgur.com/5a0veEG.png)](https://www.stock-pro.fr/)
@@ -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
- def as_json(serializer: nil, cache: false)
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).as_json
54
+ serializer.new(self, cache: cache, root: root).serializable_hash
39
55
  end
40
56
  end
41
57
  end
@@ -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
- define_method(key) do
11
- object.send(key)
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
- def attribute(key, key_name: nil, &block)
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).as_json
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).as_json }.reject(&:blank?)
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
- def initialize(object, cache: false)
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
- def as_json
217
+ # Serializes the object
218
+ #
219
+ # @return [Hash] the serializable hash
220
+ def serializable_hash
84
221
  if @cache
85
- cache_key = cache_base_key
86
- Barley::Cache.fetch(cache_key, expires_in: @expires_in) do
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
- _as_json
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
- "#{object.class.name&.underscore}/#{object.id}/#{object.updated_at.to_i}/as_json/"
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
- def _as_json
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
@@ -1,3 +1,3 @@
1
1
  module Barley
2
- VERSION = "0.2.0"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/barley.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "barley/version"
2
2
  require "barley/railtie"
3
3
  require "barley/configuration"
4
+ require "dry-types"
4
5
 
5
6
  module Barley
6
7
  mattr_accessor :config
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.2.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 00:00:00.000000000 Z
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: