adequate_json 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5b468fd9f03d2c352e7773d349b63961b989d8c6a6f019b97c140f950568bc3f
4
+ data.tar.gz: 15f7842cd39c754e8a4df4f5509e04152de63aa20a2cf2b34dbd6d773cc54099
5
+ SHA512:
6
+ metadata.gz: 153f935993fbc16160897afec263173ef90ec68b6e8ae66c32c2f83db74aa4fb6489d917e04ffd294427b8eb7b92c63c9de3dff2dcc00ceadc0bf30dfbcff804
7
+ data.tar.gz: b789c736fe4610b0e27e0b9bf4dd60e9d333cb456805363e9f45ffe9d1a86247530da0c9f2febcfb61e058f4359f291ffebee2624d3c5f58aa359a509846f272
@@ -0,0 +1,35 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ "main" ]
13
+ pull_request:
14
+ branches: [ "main" ]
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ test:
21
+
22
+ runs-on: ubuntu-latest
23
+ strategy:
24
+ matrix:
25
+ ruby-version: ['3.3.1']
26
+
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - name: Set up Ruby
30
+ uses: ruby/setup-ruby@v1 # v1.146.0
31
+ with:
32
+ ruby-version: ${{ matrix.ruby-version }}
33
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
34
+ - name: Run tests
35
+ run: bundle exec rspec
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .rspec_status
2
+ Gemfile.lock
data/.rubocop.yml ADDED
@@ -0,0 +1,7 @@
1
+ Metrics/BlockLength:
2
+ Exclude:
3
+ - spec/**/*_spec.rb
4
+ - adequate_json.gemspec
5
+
6
+ Style/Documentation:
7
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.1
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in adequate_json.gemspec
8
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Issam Tribak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,300 @@
1
+ # AdequateJSON
2
+
3
+ AdequateJSON is a serialization gem for Ruby on Rails APIs. It is easy to use, versatile,
4
+ [fast](#performances), and promotes API best practices.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'adequate_json'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install adequate_json
21
+
22
+ ## Usage
23
+
24
+ If you'd like to tinker with AdequateJSON on your own, see the
25
+ [sample Rails API](https://github.com/EverestHC-mySofie/adequate_json_sample).
26
+
27
+ ### At the controller level
28
+
29
+ If you're using — and you probably are — a controller inheriting `ActionController::API`,
30
+ AdequateJSON adds a `render_json` method to controllers, to which you can provide a model
31
+ instance, or a collection of models:
32
+
33
+ ```ruby
34
+ class CategoriesController < ActionController::API
35
+ def index
36
+ render_json Category.order(:name)
37
+ end
38
+
39
+ def show
40
+ render_json Category.find(params[:id])
41
+ end
42
+ end
43
+ ```
44
+
45
+ AdequateJSON automatically infers the type of serializer it should use
46
+ based on the model type, see the [next section](#your-first-serializer).
47
+
48
+ ### Your first serializer
49
+
50
+ When rendering a model, AdequateJSON searches for the corresponding serializing
51
+ class in the `Serializers` module (or the module you specify using the
52
+ [configuration](#configuration)).
53
+
54
+ Each serializer is a class extending `AdequateJSON::Base`, and defining one or several
55
+ variants to build (defaulting to `:default` for single objects and `:no_wrapper`
56
+ for collections):
57
+
58
+ ```ruby
59
+ # app/serializers/category.rb
60
+
61
+ class Serializers::Category < AdequateJSON::Base
62
+ builder do |json, category| # Same as builder(:default)
63
+ json.category do
64
+ json.(category, :id, :name, :created_at, :updated_at)
65
+ end
66
+ end
67
+
68
+ builder(:no_wrapper) do |json, category|
69
+ json.(category, :id, :name)
70
+ end
71
+ end
72
+ ```
73
+
74
+ AdequateJSON is based on Jbuilder. For all JSON manipulation methods available,
75
+ have a look at [Jbuilder's DSL documentation](https://github.com/rails/jbuilder).
76
+
77
+ ### Choosing a variant
78
+
79
+ Builder variants default to `:default` for single objects, and `:no_wrapper` for
80
+ collection items. To use another variant, specify it as a keyword argument:
81
+
82
+ ```ruby
83
+ class ProductsController < ActionController::API
84
+ def index
85
+ render_json Product.order(:name), variant: :header
86
+ end
87
+
88
+ def show
89
+ render_json Product.find(params[:id]), variant: :full
90
+ end
91
+ end
92
+ ```
93
+
94
+ ### Reuse and composition
95
+
96
+ Each variant can be reused inside other variant using the `serialize` helper
97
+ method, that runs on the same JSON builder. This allows for reuse of builders
98
+ as partials, or make a variant "inherit" another one:
99
+
100
+ ```ruby
101
+ class Serializers::Product < AdequateJSON::Base
102
+ builder do |json, product| # Same as builder(:default)
103
+ json.product do
104
+ serialize product, variant: :no_wrapper
105
+ end
106
+ end
107
+
108
+ builder(:full) do |json, product|
109
+ json.product do
110
+ serialize product, variant: :no_wrapper
111
+ serialize product.colors
112
+ serialize product.sizes
113
+ serialize product.category
114
+ end
115
+ end
116
+
117
+ builder(:no_wrapper) do |json, product|
118
+ serialize product, variant: :header
119
+ json.(product, :description, :created_at, :updated_at)
120
+ end
121
+
122
+ builder(:header) do |json, product|
123
+ json.(product, :id, :name, :price)
124
+ serialize product.category
125
+ end
126
+ end
127
+ ```
128
+
129
+ ### Supported source objects
130
+
131
+ AdequateJSON uses built-in serializers for hashes and collections and,
132
+ when serializing objects, will use the `#model_name` property to retrieve
133
+ the name of the serializer to search for.
134
+
135
+ If you need to change the type of serializer for a given model, you may define
136
+ the `serializer` method on the model:
137
+
138
+ ```ruby
139
+ class Book < ApplicationRecord
140
+ # ...
141
+
142
+ def serializer = :product
143
+ end
144
+ ```
145
+
146
+ In last resort, you may also specify explicitely the serializer class to use:
147
+
148
+ ```ruby
149
+ render_json Serializers::Product.new(Book.find(params[:id])).to_builder
150
+ ```
151
+
152
+ ### Serializing multiple objects at once
153
+
154
+ To serialize more complex structures, simply use a hash:
155
+
156
+ ```ruby
157
+ class ProductsController < ActionController::API
158
+ def show
159
+ render_json { product: Product.find(params[:id]), categories: Category.order(:name) }
160
+ end
161
+ end
162
+ ```
163
+
164
+ ### Pagination
165
+
166
+ As soon as you've added [Kaminari](https://github.com/kaminari/kaminari)
167
+ to your Gemfile and paginate a collection, AdequateJSON automatically appends
168
+ the `pagination` property to the JSON output:
169
+
170
+ ```ruby
171
+ class ProductsController < ActionController::API
172
+ def index
173
+ render_json Product.order(:name).page(params[:page]).per(10)
174
+ end
175
+ end
176
+ ```
177
+
178
+ ```
179
+ {
180
+ "collection": [
181
+ {
182
+ "id": "a9342787-0d24-43cf-8791-3a512f9e9bd4",
183
+ ...
184
+ },
185
+ ...
186
+ ],
187
+ "pagination": {
188
+ "current_page": 1,
189
+ "total_count": 289,
190
+ "next_page": 2,
191
+ "previous_page": null,
192
+ "total_pages": 29,
193
+ }
194
+ }
195
+ ```
196
+
197
+ If you'd like AdequateJSON to support other pagination gems, feel
198
+ free to craft a pull-request or to open an
199
+ [issue](https://github.com/EverestHC-mySofie/adequate_json/issues),
200
+ we'd be glad to help.
201
+
202
+ ### Rendering errors
203
+
204
+ Specify an error code, and AdequateJSON will try to localize it
205
+ with a message found in one of your `locales/*.yml` files. The i18n
206
+ scope defaults to `api.errors`, you may change it in the
207
+ [configuration](#configuration). It also automatically renders errors
208
+ based on ActiveModel messages.
209
+
210
+ ```ruby
211
+ class ProductsController < ApplicationController
212
+ # ...
213
+
214
+ def create
215
+ product = Product.new(product_params)
216
+ if product.save
217
+ render_json product
218
+ else
219
+ render_error :invalid_model, product
220
+ end
221
+ end
222
+ end
223
+ ```
224
+
225
+ Given a locale key `api.errors.invalid_model`, the `render_error`
226
+ call would produce:
227
+
228
+ ```json
229
+ {
230
+ "error": {
231
+ "code": "invalid_model",
232
+ "message": "The object couldn't be saved",
233
+ "details": {
234
+ "category": [
235
+ "must exist"
236
+ ],
237
+ "name": [
238
+ "can't be blank"
239
+ ],
240
+ "description": [
241
+ "can't be blank"
242
+ ],
243
+ "price": [
244
+ "can't be blank",
245
+ "is not a number"
246
+ ]
247
+ }
248
+ }
249
+ }
250
+ ```
251
+
252
+ If you need to return errors for nested models and attributes,
253
+ you may use the `includes` keyword argument:
254
+
255
+ ```ruby
256
+ render_error :invalid_model, product, includes: { category: :shop }
257
+ ```
258
+
259
+ ### Configuration
260
+
261
+ All configuration options are available through a block yielded by
262
+ the `AdequateJSON.configure` method.
263
+
264
+ The configuration code should take place in the `application.rb` file
265
+ or one of the Rails environment configurations (`production.rb`,
266
+ `development.rb`, etc.), not in an initializer:
267
+
268
+ ```ruby
269
+ module AdequateJsonSample
270
+ class Application < Rails::Application
271
+
272
+ AdequateJson.configure do |c|
273
+ c.serializers_module :json # defaults to :serializers
274
+ c.use_model_name_for_collection_key true # defaults to `false`
275
+ c.collection_key :list # defaults to `collection`
276
+ c.i18n_errors_scope %i[controllers error] # defaults to `%i[api errors]`
277
+ end
278
+ end
279
+ ```
280
+
281
+ ## Performances
282
+
283
+ We've written a small benchmark to see how AdequateJSON performs compared
284
+ to [ActiveModelSerializers](https://github.com/rails-api/active_model_serializers).
285
+ The benchmark consists in serializing 10 times a collection of 10 000 objects
286
+ (the lower the better) and displaying the min, max and average processing times:
287
+
288
+ ```
289
+ $ bundle e ruby benchmark.rb
290
+ AdequateJSON - min: 75.68, max: 95.1 - average: 81.5 ms
291
+ ActiveModelSerializers - min: 471.04, max: 582.73 - average: 491.63 ms
292
+ ```
293
+
294
+ ## Contributing
295
+
296
+ Bug reports and pull requests are welcome on GitHub at https://github.com/EverestHC-mySofie/adequate_json.
297
+
298
+ ## License
299
+
300
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'adequate_json/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'adequate_json'
9
+ spec.version = AdequateJson::VERSION
10
+ spec.authors = ['Jef Mathiot', 'Issam Tribak', 'Wilfried Tacquard']
11
+ spec.email = ['jeff.mathiot@gmail.com', 'issam.tribak@mysofie.fr', 'wilfried.tacquard@mysofie.fr']
12
+
13
+ spec.summary = 'Yet another JSON serialization library'
14
+ spec.description = 'Adequate Json is a gem that simplifies the process of serializing JSON for API responses.'
15
+ spec.homepage = 'https://github.com/EverestHC-mySofie/adequate_json'
16
+ spec.license = 'MIT'
17
+
18
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
19
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
20
+ if spec.respond_to?(:metadata)
21
+ spec.metadata['homepage_uri'] = spec.homepage
22
+ spec.metadata['source_code_uri'] = 'https://github.com/EverestHC-mySofie/adequate_json'
23
+ spec.metadata['changelog_uri'] = 'https://github.com/EverestHC-mySofie/adequate_json'
24
+ else
25
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
26
+ 'public gem pushes.'
27
+ end
28
+
29
+ # Specify which files should be added to the gem when it is released.
30
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
32
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
33
+ end
34
+ spec.bindir = 'exe'
35
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
+ spec.require_paths = ['lib']
37
+
38
+ spec.add_dependency 'jbuilder'
39
+
40
+ spec.add_development_dependency 'attr_extras'
41
+ spec.add_development_dependency 'bundler', '~> 2.5'
42
+ spec.add_development_dependency 'rake', '>= 12.3.3'
43
+ spec.add_development_dependency 'rspec', '~> 3.0'
44
+ spec.add_development_dependency 'rubocop'
45
+
46
+ spec.required_ruby_version = '~> 3.0'
47
+ end
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'adequate_json'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdequateJson
4
+ class Base
5
+ include Resolver
6
+ include Jsonizer
7
+
8
+ def initialize(model, json = nil, variant: nil)
9
+ @model = model
10
+ @variant = variant
11
+ @json = json
12
+ end
13
+
14
+ def to_builder
15
+ variant = @variant || :default
16
+ builder = self.class.builders[variant]
17
+ raise "Unknown serializer variant #{variant} for #{self.class.name}" if builder.nil?
18
+
19
+ yield_builder builder
20
+ end
21
+
22
+ protected
23
+
24
+ def yield_builder(builder)
25
+ if @json.nil?
26
+ Jbuilder.new do |json|
27
+ @json = json
28
+ instance_exec json, @model, @variant, &builder
29
+ end
30
+ else
31
+ instance_exec @json, @model, @variant, &builder
32
+ end
33
+ end
34
+
35
+ def serialize(model, **options)
36
+ return if model.nil?
37
+
38
+ choose_serializer(model, **options).to_builder
39
+ end
40
+
41
+ class << self
42
+ def builder(variant = nil, &block)
43
+ builders[variant || :default] = block
44
+ end
45
+
46
+ def builders
47
+ @builders ||= {}
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'adequate_json/base'
4
+ module AdequateJson
5
+ class Collection < AdequateJson::Base
6
+ def initialize(collection, json = nil, variant: nil)
7
+ @first_level = true if json.nil?
8
+ super
9
+ @variant ||= :no_wrapper
10
+ end
11
+
12
+ def to_builder
13
+ with_jbuilder do |json|
14
+ json.set!(collection_key) do
15
+ json.array! @model do |item|
16
+ serialize item, variant: @variant
17
+ end
18
+ end
19
+ attach_pagination(json)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def collection_key
26
+ return @model.model_name.plural if !@first_level || AdequateJson.configuration.use_model_name_for_collection_key
27
+
28
+ AdequateJson.configuration.collection_key
29
+ end
30
+
31
+ def attach_pagination(json)
32
+ return unless @first_level && @model.respond_to?(:current_page)
33
+
34
+ json.pagination do
35
+ json.current_page @model.current_page
36
+ json.total_count @model.total_count
37
+ json.next_page @model.next_page
38
+ json.previous_page @model.prev_page
39
+ json.total_pages @model.total_pages
40
+ end
41
+ end
42
+
43
+ def with_jbuilder
44
+ yield @json if @json
45
+ Jbuilder.new do |json|
46
+ @json = json
47
+ yield json
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdequateJson
4
+ class Configuration
5
+ attr_accessor :use_model_name_for_collection_key, :collection_key, :serializers_module, :i18n_errors_scope
6
+
7
+ def initialize
8
+ @use_model_name_for_collection_key = false
9
+ @collection_key = :collection
10
+ @serializers_module = :serializers
11
+ @i18n_errors_scope = %i[api errors]
12
+ end
13
+
14
+ def serializers_module_const
15
+ AdequateJson.configuration.serializers_module.to_s.camelcase.constantize
16
+ end
17
+ end
18
+
19
+ class ConfigurationBuilder
20
+ def initialize(configuration)
21
+ @configuration = configuration
22
+ end
23
+
24
+ def method_missing(name, *args, **kwargs)
25
+ @configuration.send("#{name}=", *args, **kwargs)
26
+ end
27
+
28
+ def respond_to_missing?(name, _ = false)
29
+ false unless @configuration.respond_to?(name)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdequateJson
4
+ class Error < Base
5
+ builder do |json, model, error, message, includes|
6
+ json.error do
7
+ json.code error
8
+ json.message message
9
+ if model
10
+ json.details do
11
+ includes_to_errors(model, includes, model.errors.messages.dup).each do |model_name, errors|
12
+ json.set! model_name, errors
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ def initialize(model, error = nil, message = nil, includes = nil)
20
+ super(model)
21
+ @error = error
22
+ @message = message
23
+ @includes = includes
24
+ end
25
+
26
+ def includes_to_errors(model, includes, attributes = {})
27
+ unless model.nil?
28
+ each_inclusion(includes) do |child, value|
29
+ submodel = model.send(child)
30
+ subattributes = attributes[submodel.model_name.to_s.underscore] = submodel.errors.messages.dup
31
+ includes_to_errors(submodel, value, subattributes)
32
+ end
33
+ end
34
+ attributes
35
+ end
36
+
37
+ def each_inclusion(includes, &)
38
+ unless includes.respond_to?(:keys)
39
+ includes = [includes].flatten.compact.inject({}) do |hash, key|
40
+ hash.tap do
41
+ hash[key] = nil
42
+ end
43
+ end
44
+ end
45
+ includes.each(&)
46
+ end
47
+
48
+ def yield_builder(builder)
49
+ Jbuilder.new do |json|
50
+ instance_exec json, @model, @error, @message, @includes, &builder
51
+ end
52
+ end
53
+
54
+ class << self
55
+ def for(error, model = nil, includes = nil)
56
+ new(model, error, I18n.t(error, scope: AdequateJson.configuration.i18n_errors_scope), includes)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdequateJson
4
+ class Hash
5
+ include Resolver
6
+ include Jsonizer
7
+
8
+ def initialize(hash, json = nil, variants: {}, **)
9
+ @hash = hash
10
+ @json = json
11
+ @variants = variants
12
+ end
13
+
14
+ def to_builder
15
+ if @json.nil?
16
+ Jbuilder.new do |json|
17
+ @json = json
18
+ serialize_hash
19
+ end
20
+ else
21
+ serialize_hash
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def serialize_hash
28
+ @hash.each do |key, value|
29
+ if value.respond_to?(:to_i) || value.frozen?
30
+ @json.set!(key, value)
31
+ else
32
+ @json.set!(key) do
33
+ choose_serializer(value, variant: @variants[key] || :no_wrapper).to_builder
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdequateJson
4
+ module Jsonizer
5
+ def to_json(*_args)
6
+ to_builder.target!
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdequateJson
4
+ module Loader
5
+ class << self
6
+ def autoload_serializers(module_name)
7
+ path = "app/#{module_name}"
8
+ module_name.to_s.camelize.tap do |type_name|
9
+ unless Object.const_defined?(type_name)
10
+ m = Object.const_set(type_name, Module.new)
11
+ # Listen for Zeitwerk code reloading and clear the serializers cache
12
+ Rails.autoloaders.main.on_setup do
13
+ AdequateJson::Resolver::Cache.reset!
14
+ end
15
+ Rails.autoloaders.main.push_dir(path, namespace: m)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ if defined?(::Rails::Railtie)
23
+ class Railtie < ::Rails::Railtie
24
+ config.before_initialize do
25
+ Loader.autoload_serializers AdequateJson.configuration.serializers_module
26
+ end
27
+
28
+ config.after_initialize do
29
+ ActionController::API.include AdequateJson::Serializer
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/string/inflections'
4
+
5
+ module AdequateJson
6
+ module Resolver
7
+ def choose_serializer(model, **args)
8
+ if model.respond_to?(:to_hash)
9
+ AdequateJson::Hash.new(model.to_hash, @json, **args)
10
+ elsif model.respond_to?(:each)
11
+ AdequateJson::Collection.new(model, @json, **args)
12
+ else
13
+ model_serializer(model, **args)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def model_serializer(model, **args)
20
+ serializer_id = (model.respond_to?(:serializer) && model.serializer) || model.model_name.name
21
+ klazz = serializer_cache.get(serializer_id) ||
22
+ serializer_cache.set(serializer_id, resolve_serializer(serializer_id))
23
+ klazz.new(model, @json, **args)
24
+ end
25
+
26
+ def resolve_serializer(symbol)
27
+ klazz = symbol.to_s.camelcase
28
+ return serializers_module.const_get(klazz, false) if serializers_module.const_defined?(klazz, false)
29
+
30
+ raise "Unable to find serializer for #{klazz}"
31
+ end
32
+
33
+ def serializers_module
34
+ AdequateJson.configuration.serializers_module_const
35
+ end
36
+
37
+ def serializer_cache
38
+ Cache
39
+ end
40
+
41
+ class Cache
42
+ class << self
43
+ def get(serializer_id)
44
+ store[serializer_id]
45
+ end
46
+
47
+ def set(serializer_id, serializer)
48
+ store[serializer_id] = serializer
49
+ serializer
50
+ end
51
+
52
+ def store
53
+ @store ||= {}
54
+ end
55
+
56
+ def reset!
57
+ @store = {}
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdequateJson
4
+ module Serializer
5
+ include AdequateJson::Resolver
6
+
7
+ def render_json(model, variant: nil, **options)
8
+ render json: choose_serializer(model, variant: variant), **options
9
+ end
10
+
11
+ def render_error(error, model = nil, includes: nil, **options)
12
+ render json: Error.for(error, model, includes), **options
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdequateJson
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jbuilder'
4
+ require 'adequate_json/resolver'
5
+ require 'adequate_json/jsonizer'
6
+ require 'adequate_json/base'
7
+ require 'adequate_json/error'
8
+ require 'adequate_json/hash'
9
+ require 'adequate_json/collection'
10
+ require 'adequate_json/configuration'
11
+ require 'adequate_json/serializer'
12
+ require 'adequate_json/version'
13
+
14
+ module AdequateJson
15
+ class << self
16
+ def configure
17
+ yield ConfigurationBuilder.new(configuration)
18
+ end
19
+
20
+ def configuration
21
+ @configuration ||= Configuration.new
22
+ end
23
+ end
24
+ end
25
+
26
+ require 'adequate_json/railtie' if defined?(::Rails::Railtie)
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: adequate_json
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jef Mathiot
8
+ - Issam Tribak
9
+ - Wilfried Tacquard
10
+ autorequire:
11
+ bindir: exe
12
+ cert_chain: []
13
+ date: 2024-11-20 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: jbuilder
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '0'
29
+ - !ruby/object:Gem::Dependency
30
+ name: attr_extras
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ - !ruby/object:Gem::Dependency
44
+ name: bundler
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '2.5'
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '2.5'
57
+ - !ruby/object:Gem::Dependency
58
+ name: rake
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 12.3.3
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 12.3.3
71
+ - !ruby/object:Gem::Dependency
72
+ name: rspec
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - "~>"
76
+ - !ruby/object:Gem::Version
77
+ version: '3.0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: '3.0'
85
+ - !ruby/object:Gem::Dependency
86
+ name: rubocop
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ description: Adequate Json is a gem that simplifies the process of serializing JSON
100
+ for API responses.
101
+ email:
102
+ - jeff.mathiot@gmail.com
103
+ - issam.tribak@mysofie.fr
104
+ - wilfried.tacquard@mysofie.fr
105
+ executables: []
106
+ extensions: []
107
+ extra_rdoc_files: []
108
+ files:
109
+ - ".github/workflows/ruby.yml"
110
+ - ".gitignore"
111
+ - ".rubocop.yml"
112
+ - ".ruby-version"
113
+ - Gemfile
114
+ - LICENSE.txt
115
+ - README.md
116
+ - Rakefile
117
+ - adequate_json.gemspec
118
+ - bin/console
119
+ - bin/setup
120
+ - lib/adequate_json.rb
121
+ - lib/adequate_json/base.rb
122
+ - lib/adequate_json/collection.rb
123
+ - lib/adequate_json/configuration.rb
124
+ - lib/adequate_json/error.rb
125
+ - lib/adequate_json/hash.rb
126
+ - lib/adequate_json/jsonizer.rb
127
+ - lib/adequate_json/railtie.rb
128
+ - lib/adequate_json/resolver.rb
129
+ - lib/adequate_json/serializer.rb
130
+ - lib/adequate_json/version.rb
131
+ homepage: https://github.com/EverestHC-mySofie/adequate_json
132
+ licenses:
133
+ - MIT
134
+ metadata:
135
+ homepage_uri: https://github.com/EverestHC-mySofie/adequate_json
136
+ source_code_uri: https://github.com/EverestHC-mySofie/adequate_json
137
+ changelog_uri: https://github.com/EverestHC-mySofie/adequate_json
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - "~>"
145
+ - !ruby/object:Gem::Version
146
+ version: '3.0'
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubygems_version: 3.5.23
154
+ signing_key:
155
+ specification_version: 4
156
+ summary: Yet another JSON serialization library
157
+ test_files: []