barley 0.3.0 → 0.4.1

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: be82895a01fc1f0be3170a9a105b7fdf20b7e98d31a77c74c5d9f67068814dec
4
- data.tar.gz: 276dcc80814079aed898b1e4d1d9e5b6bf3bc4b8b6b86f2b5cff701961d2b877
3
+ metadata.gz: d882b89bf1bf77259eda96813c4732ef1b286142ecb29fcb945837b743e089c9
4
+ data.tar.gz: 87dc3b213a614957a668c08865b87eb44f805cf76b79a60c710025a7d18257d8
5
5
  SHA512:
6
- metadata.gz: 9f3482dc0562669991a64403c9c5cee510d61cc5ec79affa3715eb203ac1a48f1f15c676778d380eccbfe6efc57281eae6c179ab4378a62d35c31b0fd65529ea
7
- data.tar.gz: b9d2a98edf8704acf8b8466042b924b771384e1756fd06f9d23939f9bd0fb4368a4934303057edd0a53d0c8f487dc93c10649e39fe17b3031189f383a668ec54
6
+ metadata.gz: a7eb159a6e40374364504f5c828d7e1f82e7a581bd89fe374182916b0dbc6863caaea779acf409e58c8fe60ff3614acdfa944bb3f74dbd808957925db146dded
7
+ data.tar.gz: d84b0157c80e83cfa28dbafda0fd300f2774c4362b5e1e47f9fc0381fb0cd8713dce2f1b27f4cfb9896af2c11d354d92b8df0dcfaee6a94bb69e6aca738e45b2
data/README.md CHANGED
@@ -1,11 +1,18 @@
1
- ![Barley loqo](./img/barley.png)
1
+ ![Barley loqo](https://i.imgur.com/am0emi4.png)
2
2
 
3
- Barley is a dead simple, fast, and efficient ActiveModel JSON serializer.
3
+ ![Test suite badge](https://github.com/MoskitoHero/barley/actions/workflows/ruby.yml/badge.svg)
4
+ [![Gem Version](https://badge.fury.io/rb/barley.svg)](https://badge.fury.io/rb/barley)
5
+ ![Static Badge](https://img.shields.io/badge/Cereal%20-%20100%25%20-%20darklime)
4
6
 
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.
7
+ Barley is a dead simple, fast, and efficient ActiveModel serializer.
8
+
9
+ Cerealize your ActiveModel objects into flat hashes with a dead simple, yet versatile DSL, and caching and type-checking baked in. Our daily bread is to make your API faster.
6
10
 
7
11
  You don't believe us? Check out the [benchmarks](#benchmarks). 😎
8
12
 
13
+ ## API documentation
14
+ [Check out the API documentation here](https://rubydoc.info/github/MoskitoHero/barley/main).
15
+
9
16
  ## Usage
10
17
  Add the `Barley::Serializable` module to your ActiveModel object.
11
18
 
@@ -21,23 +28,30 @@ Then define your attributes and associations in a serializer class.
21
28
  ```ruby
22
29
  # /app/serializers/user_serializer.rb
23
30
  class UserSerializer < Barley::Serializer
24
- attributes :id, :name,
25
- attribute :email # single attribute
31
+
32
+ attributes id: Types::Strict::Integer, :name
33
+
34
+ attribute :email
35
+ attribute :value, type: Types::Coercible::Integer
26
36
 
27
- many :posts # relations
28
- one :group, serializer: CustomGroupSerializer # custom serializer
29
- many :related_users, key: :friends, cache: true # custom key, and caching
30
- one :profile, cache: { expires_in: 1.day } do # cache definition, and block (on associations) for nested, on-the-fly serializer
37
+ many :posts
38
+
39
+ one :group, serializer: CustomGroupSerializer
40
+
41
+ many :related_users, key: :friends, cache: true
42
+
43
+ one :profile, cache: { expires_in: 1.day } do
31
44
  attributes :avatar, :social_url
45
+
32
46
  attribute :badges do
33
- object.badges.map(&:display_name) # use object in a block to return custom code
47
+ object.badges.map(&:display_name)
34
48
  end
35
49
  end
36
50
 
37
51
  end
38
52
  ```
39
53
 
40
- The just use the `as_json` method on your model.
54
+ Then just use the `as_json` method on your model.
41
55
 
42
56
  ```ruby
43
57
  user = User.find(1)
@@ -243,6 +257,27 @@ Barley.configure do |config|
243
257
  end
244
258
  ```
245
259
 
260
+ ## Type checking
261
+ Barley can check the type of the object you are serializing with the [dry-types](https://dry-rb.org/gems/dry-types/main/) gem.
262
+
263
+ 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.
264
+
265
+ ```ruby
266
+ module Types
267
+ include Dry.Types()
268
+ end
269
+
270
+ class UserSerializer < Barley::Serializer
271
+ attributes id: Types::Strict::Integer, name: Types::Strict::String, email: Types::Strict::String.constrained(format: URI::MailTo::EMAIL_REGEXP)
272
+
273
+ attribute :role, type: Types::Coercible::String do
274
+ object.role.integer_or_string_coercible_value
275
+ end
276
+ end
277
+ ```
278
+
279
+ Check out [dry-types](https://dry-rb.org/gems/dry-types/main/) for all options and available types.
280
+
246
281
  ## Breakfast mode 🤡 (coming soon)
247
282
  You will soon be able to replace all occurrences of `Serializer` with `Cerealizer` in your codebase. Just for fun. And for free.
248
283
 
@@ -361,7 +396,16 @@ ams : 1299674 allocated - 28.20x more
361
396
  ## License
362
397
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
363
398
 
399
+ ## Contributing
400
+ You can contribute in several ways: reporting bugs, suggesting features, or contributing code. See [our contributing guidelines](CONTRIBUTING.md)
401
+
402
+ Make sure you adhere to [our code of conduct](CODE_OF_CONDUCT.md). We aim to keep this project open and inclusive.
403
+
404
+ ## Security
405
+
406
+ Please refer to our [security guidelines](SECURITY.md)
407
+
364
408
  ## Credits
365
409
  Barley is brought to you by the developer team from [StockPro](https://www.stock-pro.fr/).
366
410
 
367
- [![Barley is brought to you by StockPro](img/stockpro.png)](https://www.stock-pro.fr/)
411
+ [![Barley is brought to you by StockPro](https://i.imgur.com/5a0veEG.png)](https://www.stock-pro.fr/)
data/Rakefile CHANGED
@@ -1,3 +1,12 @@
1
1
  require "bundler/setup"
2
2
 
3
3
  require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ t.libs << "test"
9
+ end
10
+ desc "Run tests"
11
+
12
+ task default: :test
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Barley
4
+ class Error < StandardError
5
+ end
6
+ end
@@ -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,23 +20,55 @@ 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
- define_method(:serializer) do
28
- klass.new(self, cache: cache)
33
+ # We need to silence the warnings because we are defining a method with the same name as the parameter
34
+ # This avoids :
35
+ # - warning: method redefined; discarding old serializer
36
+ # - warning: previous definition of serializer was here
37
+ Kernel.silence_warnings do
38
+ define_method(:serializer) do
39
+ klass.new(self, cache: cache)
40
+ end
29
41
  end
30
42
  end
31
43
  end
32
44
 
33
45
  included do
34
- serializer "#{self}Serializer".constantize
46
+ begin
47
+ serializer "#{self}Serializer".constantize
48
+ rescue NameError
49
+ raise Barley::Error, "Could not find serializer for #{self}. Please define a #{self}Serializer class."
50
+ end
35
51
 
36
- def as_json(serializer: nil, cache: false, root: false)
37
- serializer ||= self.serializer.class
38
- serializer.new(self, cache: cache, root: root).serializable_hash
52
+ # Serializes the model
53
+ #
54
+ # @note this method does not provide default rails options like `only` or `except`.
55
+ # This is because the Barley serializer should be the only place where the attributes are defined.
56
+ #
57
+ # @option options [Class] :serializer the serializer to use
58
+ # @option options [Boolean, Hash<Symbol, ActiveSupport::Duration>] :cache whether to cache the result, or a hash with options for the cache
59
+ # @option options [Boolean] :root whether to include the root key
60
+ #
61
+ # @return [Hash] the serialized attributes
62
+ def as_json(options = nil)
63
+ options ||= {}
64
+ serializer = options[:serializer] || self.serializer.class
65
+ cache = options[:cache] || false
66
+ root = options[:root] || false
67
+ begin
68
+ serializer.new(self, cache: cache, root: root).serializable_hash
69
+ rescue NameError
70
+ raise Barley::Error, "Could not find serializer for #{self}. Please define a #{serializer} class."
71
+ end
39
72
  end
40
73
  end
41
74
  end
@@ -5,29 +5,116 @@ module Barley
5
5
  attr_accessor :object
6
6
 
7
7
  class << self
8
+ attr_accessor :defined_attributes
9
+
10
+ # Defines attributes for the serializer
11
+ #
12
+ # Accepts either a list of symbols or a hash of symbols and Dry::Types, or a mix of both
13
+ #
14
+ # @example only symbols
15
+ # attributes :id, :name, :email
16
+ # # => {id: 1234, name: "John Doe", email: "john.doe@example"}
17
+ #
18
+ # @example with types
19
+ # attributes id: Types::Strict::Integer, name: Types::Strict::String, email: Types::Strict::String
20
+ # # => {id: 1234, name: "John Doe", email: "john.doe@example"}
21
+ #
22
+ # @example with types and symbols
23
+ # attributes :id, name: Types::Strict::String, email: Types::Strict::String
24
+ # # => {id: 1234, name: "John Doe", email: "john.doe@example"}
25
+ #
26
+ # @see Serializer#attribute
27
+ #
28
+ # @param keys [Hash<Symbol, Dry::Types>, Array<Symbol>] mix of symbols and hashes of symbols and Dry::Types
8
29
  def attributes(*keys)
30
+ if keys.last.is_a?(Hash)
31
+ keys.pop.each do |key, type|
32
+ attribute(key, type: type)
33
+ end
34
+ end
9
35
  keys.each do |key|
10
- define_method(key) do
11
- object.send(key)
36
+ if key.is_a?(Hash)
37
+ attribute(key.keys.first, type: key.values.first)
38
+ else
39
+ attribute(key)
12
40
  end
13
- set_class_iv(:@defined_attributes, key)
14
41
  end
15
42
  end
16
43
 
17
- def attribute(key, key_name: nil, &block)
44
+ # Defines a single attribute for the serializer
45
+ #
46
+ # Type checking is done with Dry::Types. If a type is not provided, the value is returned as is.
47
+ # Dry::Types can be used to coerce the value to the desired type and to check constraints.
48
+ #
49
+ # @see https://dry-rb.org/gems/dry-types/main/
50
+ #
51
+ # @raise [Dry::Types::ConstraintError] if the type does not match
52
+ #
53
+ # @example simple attribute
54
+ # attribute :id
55
+ # # => {id: 1234}
56
+ #
57
+ # @example attribute with a different key name
58
+ # attribute :name, key_name: :full_name
59
+ # # => {full_name: "John Doe"}
60
+ #
61
+ # @example attribute with a type
62
+ # attribute :email, type: Types::Strict::String
63
+ # # => {email: "john.doe@example"}
64
+ #
65
+ # @example attribute with a type and a block
66
+ # attribute :email, type: Types::Strict::String do
67
+ # object.email.upcase
68
+ # end
69
+ # # => {email: "JOHN.DOE@EXAMPLE"}
70
+ #
71
+ # @param key [Symbol] the attribute name
72
+ # @param key_name [Symbol] the key name in the hash
73
+ # @param type [Dry::Types] the type to use, or coerce the value to
74
+ # @param block [Proc] a block to use to compute the value
75
+ def attribute(key, key_name: nil, type: nil, &block)
18
76
  key_name ||= key
19
- if block
20
- define_method(key_name) do
21
- instance_eval(&block)
22
- end
23
- else
24
- define_method(key_name) do
25
- object.send(key)
26
- end
77
+ define_method(key_name) do
78
+ value = block ? instance_eval(&block) : object.send(key)
79
+ type.nil? ? value : type[value]
27
80
  end
28
- set_class_iv(:@defined_attributes, key_name)
81
+
82
+ self.defined_attributes = (defined_attributes || []) << key_name
29
83
  end
30
84
 
85
+ # Defines a single association for the serializer
86
+ #
87
+ # @example using the default serializer of the associated model
88
+ # one :group
89
+ # # => {group: {id: 1234, name: "Group 1"}}
90
+ #
91
+ # @example using a custom serializer
92
+ # one :group, serializer: MyCustomGroupSerializer
93
+ # # => {group: {id: 1234, name: "Group 1"}}
94
+ #
95
+ # @example using a block with an inline serializer definition
96
+ # one :group do
97
+ # attributes :id, :name
98
+ # end
99
+ # # => {group: {id: 1234, name: "Group 1"}}
100
+ #
101
+ # @example using a different key name
102
+ # one :group, key_name: :my_group
103
+ # # => {my_group: {id: 1234, name: "Group 1"}}
104
+ #
105
+ # @example using cache
106
+ # one :group, cache: true
107
+ # # => {group: {id: 1234, name: "Group 1"}}
108
+ #
109
+ # @example using cache and expires_in
110
+ # one :group, cache: {expires_in: 1.hour}
111
+ # # => {group: {id: 1234, name: "Group 1"}}
112
+ #
113
+ # @param key [Symbol] the association name
114
+ # @param key_name [Symbol] the key name in the hash
115
+ # @param serializer [Class] the serializer to use
116
+ # @param cache [Boolean, Hash<Symbol, ActiveSupport::Duration>] whether to cache the result, or a hash with options for the cache
117
+ # @param block [Proc] a block to use to define the serializer inline
31
118
  def one(key, key_name: nil, serializer: nil, cache: false, &block)
32
119
  key_name ||= key
33
120
  if block
@@ -42,9 +129,42 @@ module Barley
42
129
  el_serializer = serializer || element.serializer.class
43
130
  el_serializer.new(element, cache: cache).serializable_hash
44
131
  end
45
- set_class_iv(:@defined_attributes, key_name)
132
+ self.defined_attributes = (defined_attributes || []) << key_name
46
133
  end
47
134
 
135
+ # Defines a collection association for the serializer
136
+ #
137
+ # @example using the default serializer of the associated model
138
+ # many :groups
139
+ # # => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}
140
+ #
141
+ # @example using a custom serializer
142
+ # many :groups, serializer: MyCustomGroupSerializer
143
+ # # => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}
144
+ #
145
+ # @example using a block with an inline serializer definition
146
+ # many :groups do
147
+ # attributes :id, :name
148
+ # end
149
+ # # => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}
150
+ #
151
+ # @example using a different key name
152
+ # many :groups, key_name: :my_groups
153
+ # # => {my_groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}
154
+ #
155
+ # @example using cache
156
+ # many :groups, cache: true
157
+ # # => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}
158
+ #
159
+ # @example using cache and expires_in
160
+ # many :groups, cache: {expires_in: 1.hour}
161
+ # # => {groups: [{id: 1234, name: "Group 1"}, {id: 5678, name: "Group 2"}]}
162
+ #
163
+ # @param key [Symbol] the association name
164
+ # @param key_name [Symbol] the key name in the hash
165
+ # @param serializer [Class] the serializer to use
166
+ # @param cache [Boolean, Hash<Symbol, ActiveSupport::Duration>] whether to cache the result, or a hash with options for the cache
167
+ # @param block [Proc] a block to use to define the serializer inline
48
168
  def many(key, key_name: nil, serializer: nil, cache: false, &block)
49
169
  key_name ||= key
50
170
  if block
@@ -59,18 +179,19 @@ module Barley
59
179
  el_serializer = serializer || elements.first.serializer.class
60
180
  elements.map { |element| el_serializer.new(element, cache: cache).serializable_hash }.reject(&:blank?)
61
181
  end
62
- set_class_iv(:@defined_attributes, key_name)
63
- end
64
-
65
- def set_class_iv(iv, key)
66
- instance_variable_defined?(iv) ? instance_variable_get(iv) << key : instance_variable_set(iv, [key])
182
+ self.defined_attributes = (defined_attributes || []) << key_name
67
183
  end
68
184
  end
69
185
 
70
186
  # @example with cache
71
187
  # Barley::Serializer.new(object, cache: true)
188
+ #
72
189
  # @example with cache and expires_in
73
190
  # Barley::Serializer.new(object, cache: {expires_in: 1.hour})
191
+ #
192
+ # @param object [Object] the object to serialize
193
+ # @param cache [Boolean, Hash<Symbol, ActiveSupport::Duration>] a boolean to cache the result, or a hash with options for the cache
194
+ # @param root [Boolean] whether to include the root key in the hash
74
195
  def initialize(object, cache: false, root: false)
75
196
  @object = object
76
197
  @root = root
@@ -81,6 +202,9 @@ module Barley
81
202
  end
82
203
  end
83
204
 
205
+ # Serializes the object
206
+ #
207
+ # @return [Hash] the serializable hash
84
208
  def serializable_hash
85
209
  if @cache
86
210
  Barley::Cache.fetch(cache_base_key, expires_in: @expires_in) do
@@ -91,12 +215,20 @@ module Barley
91
215
  end
92
216
  end
93
217
 
218
+ # Clears the cache for the object
219
+ #
220
+ # @param key [String] the cache key
221
+ #
222
+ # @return [Boolean] whether the cache was cleared
94
223
  def clear_cache(key: cache_base_key)
95
224
  Barley::Cache.delete(key)
96
225
  end
97
226
 
98
227
  private
99
228
 
229
+ # @api private
230
+ #
231
+ # @return [String] the cache key
100
232
  def cache_base_key
101
233
  if object.updated_at.present?
102
234
  "#{object.class.name&.underscore}/#{object.id}/#{object.updated_at&.to_i}/barley_cache/"
@@ -105,10 +237,18 @@ module Barley
105
237
  end
106
238
  end
107
239
 
240
+ # @api private
241
+ #
242
+ # @return [Array<Symbol>] the defined attributes
108
243
  def defined_attributes
109
- self.class.instance_variable_get(:@defined_attributes)
244
+ self.class.defined_attributes
110
245
  end
111
246
 
247
+ # Serializes the object
248
+ #
249
+ # @api private
250
+ #
251
+ # @return [Hash] the serializable hash
112
252
  def _serializable_hash
113
253
  hash = {}
114
254
 
@@ -119,6 +259,9 @@ module Barley
119
259
  @root ? {root_key => hash} : hash
120
260
  end
121
261
 
262
+ # @api private
263
+ #
264
+ # @return [Symbol] the root key, based on the class name
122
265
  def root_key
123
266
  object.class.name.underscore.to_sym
124
267
  end
@@ -1,3 +1,3 @@
1
1
  module Barley
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.1"
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.3.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cedric Delalande
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-18 00:00:00.000000000 Z
11
+ date: 2023-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,8 +24,23 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 6.1.0
27
- description: Cerealize your ActiveModel objects into flat JSON objects with a dead
28
- simple DSL. Our daily bread is to make your API faster.
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
41
+ description: Cerealize your ActiveModel objects into flat hashes with a dead simple,
42
+ yet versatile DSL, and caching and type-checking baked in. Our daily bread is to
43
+ make your API faster.
29
44
  email:
30
45
  - weengs@moskitohero.com
31
46
  executables: []
@@ -38,6 +53,7 @@ files:
38
53
  - lib/barley.rb
39
54
  - lib/barley/cache.rb
40
55
  - lib/barley/configuration.rb
56
+ - lib/barley/error.rb
41
57
  - lib/barley/railtie.rb
42
58
  - lib/barley/serializable.rb
43
59
  - lib/barley/serializer.rb
@@ -60,7 +76,8 @@ metadata:
60
76
  homepage_uri: https://github.com/moskitohero/barley
61
77
  source_code_uri: https://github.com/moskitohero/barley
62
78
  changelog_uri: https://github.com/moskitohero/barley/CHANGELOG.md
63
- post_install_message:
79
+ documentation_uri: https://rubydoc.info/github/MoskitoHero/barley/main
80
+ post_install_message:
64
81
  rdoc_options: []
65
82
  require_paths:
66
83
  - lib
@@ -75,8 +92,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
92
  - !ruby/object:Gem::Version
76
93
  version: '0'
77
94
  requirements: []
78
- rubygems_version: 3.2.33
79
- signing_key:
95
+ rubygems_version: 3.3.26
96
+ signing_key:
80
97
  specification_version: 4
81
- summary: Barley is a dead simple, fast, and efficient ActiveModel JSON serializer.
98
+ summary: Barley is a dead simple, fast, and efficient ActiveModel serializer.
82
99
  test_files: []