juso 0.1.0 → 1.0.2

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: d887123b941a26f478f70d16785ff8dbb715ddd2bd6ac4454f5ab94091cae491
4
- data.tar.gz: 799410137cc3491745f6907b5d3481bc33e905d56277a5fc49e6c431f698b544
3
+ metadata.gz: be611b823c4dbebdbd7882f6d9d71c1bf05fbd38c72a6e57a515560fb5efc6b3
4
+ data.tar.gz: da19722184dea68868395b0db2f7bbbafe093abf2f55d6c64abc3fd2faf5c339
5
5
  SHA512:
6
- metadata.gz: f7cf662800ae228c3b7c9971352c64dd4f1b67d8aad1c2eb83a98ea80b758d99660e875bd063c843ac172dcb10f583a36fb3738ddf229eeed151df1dc6857940
7
- data.tar.gz: ae70c822282eceaac1d3cca0190cf35b8104575363978ac0860551b1cb2b92ef4b7ea9b871bb364adc23a8517353a391a16f7471cc40dc80b2905e13043b39e6
6
+ metadata.gz: 6937d6359d6ee9589a7d88e605d873d743769f9161a66277027b962d1a96342334f653d0ab8c0dcd234ec9617570723f8826e53753bf9e2acace75cd90110171
7
+ data.tar.gz: b431598fde992ec3da065898c6a42a8e4c874c2f9d01efd5a0c76dff8767636b27f411acf0051504cbc407109ecd26228ec70d9f3870e22374edc86449e7314d
@@ -8,8 +8,10 @@ jobs:
8
8
  timeout-minutes: 10
9
9
 
10
10
  strategy:
11
+ fail-fast: false
11
12
  matrix:
12
- ruby-version: [3.1.0-preview1, 3.0.2, 2.7.4, 2.6.8]
13
+ # ruby-version: [3.1.0-preview1, 3.0.2, 2.7.4, 2.6.8]
14
+ ruby-version: [3.0.2, 2.7.4, 2.6.8]
13
15
 
14
16
  steps:
15
17
  - uses: actions/checkout@v2
@@ -22,3 +24,29 @@ jobs:
22
24
 
23
25
  - name: Run test
24
26
  run: bundle exec rake test
27
+
28
+ - name: Run test (rails)
29
+ env:
30
+ RAILS_TEST: true
31
+ run: |
32
+ bundle exec rails db:setup
33
+ bundle exec rake test
34
+
35
+ benchmark:
36
+ runs-on: ubuntu-latest
37
+ timeout-minutes: 10
38
+
39
+ steps:
40
+ - uses: actions/checkout@v2
41
+
42
+ - name: Set up Ruby
43
+ uses: ruby/setup-ruby@v1
44
+ with:
45
+ ruby-version: 3.0.2
46
+ bundler-cache: true
47
+
48
+ - name: Run benchmark (collection)
49
+ run: ruby benchmark/collection.rb
50
+
51
+ - name: Run benchmark (single)
52
+ run: ruby benchmark/single_resource.rb
data/.gitignore CHANGED
@@ -6,3 +6,7 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+
10
+ test/dummy/log/*.log
11
+ test/dummy/db/development.sqlite3
12
+ test/dummy/db/test.sqlite3
data/Gemfile CHANGED
@@ -10,3 +10,6 @@ gem "rake", "~> 13.0"
10
10
  gem "minitest", "~> 5.0"
11
11
 
12
12
  gem "rubocop", "~> 1.7"
13
+
14
+ gem 'sqlite3'
15
+ gem 'json_expressions'
data/Gemfile.lock CHANGED
@@ -1,16 +1,131 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- juso (0.1.0)
4
+ juso (1.0.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
+ actioncable (6.1.4.1)
10
+ actionpack (= 6.1.4.1)
11
+ activesupport (= 6.1.4.1)
12
+ nio4r (~> 2.0)
13
+ websocket-driver (>= 0.6.1)
14
+ actionmailbox (6.1.4.1)
15
+ actionpack (= 6.1.4.1)
16
+ activejob (= 6.1.4.1)
17
+ activerecord (= 6.1.4.1)
18
+ activestorage (= 6.1.4.1)
19
+ activesupport (= 6.1.4.1)
20
+ mail (>= 2.7.1)
21
+ actionmailer (6.1.4.1)
22
+ actionpack (= 6.1.4.1)
23
+ actionview (= 6.1.4.1)
24
+ activejob (= 6.1.4.1)
25
+ activesupport (= 6.1.4.1)
26
+ mail (~> 2.5, >= 2.5.4)
27
+ rails-dom-testing (~> 2.0)
28
+ actionpack (6.1.4.1)
29
+ actionview (= 6.1.4.1)
30
+ activesupport (= 6.1.4.1)
31
+ rack (~> 2.0, >= 2.0.9)
32
+ rack-test (>= 0.6.3)
33
+ rails-dom-testing (~> 2.0)
34
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
35
+ actiontext (6.1.4.1)
36
+ actionpack (= 6.1.4.1)
37
+ activerecord (= 6.1.4.1)
38
+ activestorage (= 6.1.4.1)
39
+ activesupport (= 6.1.4.1)
40
+ nokogiri (>= 1.8.5)
41
+ actionview (6.1.4.1)
42
+ activesupport (= 6.1.4.1)
43
+ builder (~> 3.1)
44
+ erubi (~> 1.4)
45
+ rails-dom-testing (~> 2.0)
46
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
47
+ activejob (6.1.4.1)
48
+ activesupport (= 6.1.4.1)
49
+ globalid (>= 0.3.6)
50
+ activemodel (6.1.4.1)
51
+ activesupport (= 6.1.4.1)
52
+ activerecord (6.1.4.1)
53
+ activemodel (= 6.1.4.1)
54
+ activesupport (= 6.1.4.1)
55
+ activestorage (6.1.4.1)
56
+ actionpack (= 6.1.4.1)
57
+ activejob (= 6.1.4.1)
58
+ activerecord (= 6.1.4.1)
59
+ activesupport (= 6.1.4.1)
60
+ marcel (~> 1.0.0)
61
+ mini_mime (>= 1.1.0)
62
+ activesupport (6.1.4.1)
63
+ concurrent-ruby (~> 1.0, >= 1.0.2)
64
+ i18n (>= 1.6, < 2)
65
+ minitest (>= 5.1)
66
+ tzinfo (~> 2.0)
67
+ zeitwerk (~> 2.3)
9
68
  ast (2.4.2)
69
+ builder (3.2.4)
70
+ concurrent-ruby (1.1.9)
71
+ crass (1.0.6)
72
+ erubi (1.10.0)
73
+ globalid (1.0.0)
74
+ activesupport (>= 5.0)
75
+ i18n (1.8.11)
76
+ concurrent-ruby (~> 1.0)
77
+ json_expressions (0.9.0)
78
+ loofah (2.12.0)
79
+ crass (~> 1.0.2)
80
+ nokogiri (>= 1.5.9)
81
+ mail (2.7.1)
82
+ mini_mime (>= 0.1.1)
83
+ marcel (1.0.2)
84
+ method_source (1.0.0)
85
+ mini_mime (1.1.2)
86
+ mini_portile2 (2.6.1)
10
87
  minitest (5.14.4)
88
+ nio4r (2.5.8)
89
+ nokogiri (1.12.5)
90
+ mini_portile2 (~> 2.6.1)
91
+ racc (~> 1.4)
92
+ nokogiri (1.12.5-arm64-darwin)
93
+ racc (~> 1.4)
94
+ nokogiri (1.12.5-x86_64-linux)
95
+ racc (~> 1.4)
11
96
  parallel (1.21.0)
12
97
  parser (3.0.2.0)
13
98
  ast (~> 2.4.1)
99
+ racc (1.6.0)
100
+ rack (2.2.3)
101
+ rack-test (1.1.0)
102
+ rack (>= 1.0, < 3)
103
+ rails (6.1.4.1)
104
+ actioncable (= 6.1.4.1)
105
+ actionmailbox (= 6.1.4.1)
106
+ actionmailer (= 6.1.4.1)
107
+ actionpack (= 6.1.4.1)
108
+ actiontext (= 6.1.4.1)
109
+ actionview (= 6.1.4.1)
110
+ activejob (= 6.1.4.1)
111
+ activemodel (= 6.1.4.1)
112
+ activerecord (= 6.1.4.1)
113
+ activestorage (= 6.1.4.1)
114
+ activesupport (= 6.1.4.1)
115
+ bundler (>= 1.15.0)
116
+ railties (= 6.1.4.1)
117
+ sprockets-rails (>= 2.0.0)
118
+ rails-dom-testing (2.0.3)
119
+ activesupport (>= 4.2.0)
120
+ nokogiri (>= 1.6)
121
+ rails-html-sanitizer (1.4.2)
122
+ loofah (~> 2.3)
123
+ railties (6.1.4.1)
124
+ actionpack (= 6.1.4.1)
125
+ activesupport (= 6.1.4.1)
126
+ method_source
127
+ rake (>= 0.13)
128
+ thor (~> 1.0)
14
129
  rainbow (3.0.0)
15
130
  rake (13.0.6)
16
131
  regexp_parser (2.1.1)
@@ -27,7 +142,22 @@ GEM
27
142
  rubocop-ast (1.13.0)
28
143
  parser (>= 3.0.1.1)
29
144
  ruby-progressbar (1.11.0)
145
+ sprockets (4.0.2)
146
+ concurrent-ruby (~> 1.0)
147
+ rack (> 1, < 3)
148
+ sprockets-rails (3.4.1)
149
+ actionpack (>= 5.2)
150
+ activesupport (>= 5.2)
151
+ sprockets (>= 3.0.0)
152
+ sqlite3 (1.4.2)
153
+ thor (1.1.0)
154
+ tzinfo (2.0.4)
155
+ concurrent-ruby (~> 1.0)
30
156
  unicode-display_width (2.1.0)
157
+ websocket-driver (0.7.5)
158
+ websocket-extensions (>= 0.1.0)
159
+ websocket-extensions (0.1.5)
160
+ zeitwerk (2.5.1)
31
161
 
32
162
  PLATFORMS
33
163
  arm64-darwin-20
@@ -35,10 +165,13 @@ PLATFORMS
35
165
  x86_64-linux
36
166
 
37
167
  DEPENDENCIES
168
+ json_expressions
38
169
  juso!
39
170
  minitest (~> 5.0)
171
+ rails (~> 6.1.4)
40
172
  rake (~> 13.0)
41
173
  rubocop (~> 1.7)
174
+ sqlite3
42
175
 
43
176
  BUNDLED WITH
44
177
  2.2.22
data/README.md CHANGED
@@ -7,6 +7,16 @@
7
7
  Juso is simple, fast and explicit JSON Serializer.
8
8
  Juso means 13 (thirteen) in Japanese.
9
9
 
10
+ ## Motivation
11
+
12
+ #### Japanese
13
+
14
+ Juso は juso というメソッドを定義することで JSON 化が可能になります。Ruby の Hash や Array を用いて定義すれば良いので、覚えることが非常に少ないことが特徴です。また、暗黙的な挙動で非公開にすべき属性が公開されることを防ぎます。
15
+
16
+ Ruby on Rails においては Model のクラスにそのまま定義することができるため、初期の導入としてはシンプルでわかりやすいです。[^1]
17
+
18
+ [^1]: as_json メソッドで頑張ることもできますが、より宣言的で分かりやすいはずです
19
+
10
20
  ## Installation
11
21
 
12
22
  Add this line to your application's Gemfile:
@@ -26,7 +36,7 @@ Or install it yourself as:
26
36
  ## Usage
27
37
 
28
38
  1. Include `Juso::Serializable` to your class.
29
- 2. Define as_juso_json(context) method.
39
+ 2. Define juso(context) method.
30
40
  3. Use Juso.generate(object) method to generate json.
31
41
 
32
42
  ```ruby
@@ -35,7 +45,7 @@ class User < ApplicationRecord
35
45
 
36
46
  # ...
37
47
 
38
- def as_juso_json(context)
48
+ def juso(context)
39
49
  {
40
50
  id: id,
41
51
  nickname: nickname,
@@ -50,7 +60,7 @@ class Team < ApplicationRecord
50
60
 
51
61
  # ...
52
62
 
53
- def as_juso_json(context)
63
+ def juso(context)
54
64
  {
55
65
  id: id,
56
66
  name: name,
@@ -70,17 +80,66 @@ Juso.generate(team)
70
80
 
71
81
  #### Japanese
72
82
 
73
- as_juso_json メソッドは以下のインスタンスしか返してはいけません
83
+ juso メソッドは以下のインスタンスしか返してはいけません
74
84
 
75
85
  - Numeric Class
76
86
  - String Class
77
- - Null Class
87
+ - Nil Class
88
+ - True Class
89
+ - False Class
78
90
  - Hash Class
79
91
  - Array Class
80
- - Juso::Serializable をinclude したクラス
81
- - Date / DateTime / ActiveSupport::TimeWithZone
92
+ - - ActiveRecord::Relation
93
+ - Juso::Serializable include したクラス
94
+ - Date / DateTime
95
+ - - ActiveSupport::TimeWithZone
96
+
97
+ 再帰的に juso の処理が適用されるため、Array の要素や Hash の value も同様のルールが適用されます
98
+
99
+ ### Juso::Context
100
+
101
+ #### Japanese
102
+
103
+ juso メソッドには Context オブジェクトが渡されます。これによって、Juso.generate から各 juso メソッドにシリアライズのオプションを伝播させることができます
104
+
105
+ ### Juso.wrap
106
+
107
+ ```ruby
108
+ class UserSerializer
109
+ include Juso::Serializable
110
+
111
+ # ...
112
+
113
+ def juso(context)
114
+ {
115
+ id: @user.id,
116
+
117
+ # use PostSerializer#juso method. Each post passes into PostSerializer object.
118
+ posts: Juso.wrap(@user.posts, PostSerializer),
119
+
120
+ # use Team#juso method
121
+ team: @user.team,
122
+ }
123
+ end
124
+ end
125
+
126
+ class PostSerializer
127
+ include Juso::Serializable
128
+
129
+ def initialize(post)
130
+ @post = post
131
+ end
132
+
133
+ def juso(context)
134
+ # do something with @post...
135
+ end
136
+ end
137
+ ```
138
+
139
+ #### Japanese
82
140
 
83
- 再帰的にjusoの処理が適用されるため、Arrayの要素やHashのvalueも同様のルールが適用されます
141
+ Juso.wrap(object, serializable_class) ユーティリティを使うことで、特定のクラスに処理を委譲できます。
142
+ コード例だと、 @user.posts は Post の ActiveRecord::Relation を返しますが、通常であれば Post インスタンスの juso メソッドが使われるのに対して、各 Post インスタンスを PostSerializer でラップします。PostSerializer#juso メソッドが呼ばれることになります。
84
143
 
85
144
  ## Development
86
145
 
data/Rakefile CHANGED
@@ -6,11 +6,17 @@ require "rake/testtask"
6
6
  Rake::TestTask.new(:test) do |t|
7
7
  t.libs << "test"
8
8
  t.libs << "lib"
9
- t.test_files = FileList["test/**/*_test.rb"]
10
- end
11
9
 
12
- require "rubocop/rake_task"
10
+ files = FileList["test/juso_test.rb"]
11
+
12
+ if ENV['RAILS_TEST']
13
+ files << 'test/rails_test.rb'
14
+ end
15
+
16
+ t.test_files = files
17
+ end
13
18
 
14
- RuboCop::RakeTask.new
19
+ # require "rubocop/rake_task"
20
+ # RuboCop::RakeTask.new
15
21
 
16
- task default: %i[test rubocop]
22
+ task default: %i[test]
@@ -0,0 +1,472 @@
1
+ # original: https://github.com/okuramasafumi/alba/blob/main/benchmark/collection.rb
2
+
3
+ # Benchmark script to run varieties of JSON serializers
4
+ # Fetch juso from local, otherwise fetch latest from RubyGems
5
+
6
+ # --- Bundle dependencies ---
7
+
8
+ require "bundler/inline"
9
+
10
+ gemfile(true) do
11
+ source "https://rubygems.org"
12
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
13
+
14
+ gem "active_model_serializers"
15
+ gem "activerecord", "6.1.3"
16
+ gem "alba"
17
+ gem "juso", path: '../'
18
+ gem "benchmark-ips"
19
+ gem "benchmark-memory"
20
+ gem "blueprinter"
21
+ gem "jbuilder"
22
+ gem "jserializer"
23
+ gem "jsonapi-serializer" # successor of fast_jsonapi
24
+ gem "multi_json"
25
+ gem "panko_serializer"
26
+ gem "pg"
27
+ gem "primalize"
28
+ gem "oj"
29
+ gem "representable"
30
+ gem "simple_ams"
31
+ gem "sqlite3"
32
+ end
33
+
34
+ # --- Test data model setup ---
35
+
36
+ require "pg"
37
+ require "active_record"
38
+ require "active_record/connection_adapters/postgresql_adapter"
39
+ require "logger"
40
+ require "oj"
41
+ require "sqlite3"
42
+ Oj.optimize_rails
43
+
44
+ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
45
+ # ActiveRecord::Base.logger = Logger.new($stdout)
46
+
47
+ ActiveRecord::Schema.define do
48
+ create_table :posts, force: true do |t|
49
+ t.string :body
50
+ end
51
+
52
+ create_table :comments, force: true do |t|
53
+ t.integer :post_id
54
+ t.string :body
55
+ t.integer :commenter_id
56
+ end
57
+
58
+ create_table :users, force: true do |t|
59
+ t.string :name
60
+ end
61
+ end
62
+
63
+ class Post < ActiveRecord::Base
64
+ has_many :comments
65
+ has_many :commenters, through: :comments, class_name: 'User', source: :commenter
66
+
67
+ def attributes
68
+ {id: nil, body: nil, commenter_names: commenter_names}
69
+ end
70
+
71
+ def commenter_names
72
+ commenters.pluck(:name)
73
+ end
74
+ end
75
+
76
+ class Comment < ActiveRecord::Base
77
+ belongs_to :post
78
+ belongs_to :commenter, class_name: 'User'
79
+
80
+ def attributes
81
+ {id: nil, body: nil}
82
+ end
83
+ end
84
+
85
+ class User < ActiveRecord::Base
86
+ has_many :comments
87
+ end
88
+
89
+ # --- Juso serializers ---
90
+
91
+ require "juso"
92
+
93
+ class Comment
94
+ include ::Juso::Serializable
95
+
96
+ def juso(context)
97
+ {id: id, body: body}
98
+ end
99
+ end
100
+
101
+ class Post
102
+ include ::Juso::Serializable
103
+
104
+ def juso(context)
105
+ {id: id, body: body, commenter_names: commenter_names, comments: comments}
106
+ end
107
+ end
108
+
109
+ # --- Alba serializers ---
110
+
111
+ require "alba"
112
+
113
+ class AlbaCommentResource
114
+ include ::Alba::Resource
115
+ attributes :id, :body
116
+ end
117
+
118
+ class AlbaPostResource
119
+ include ::Alba::Resource
120
+ attributes :id, :body
121
+ attribute :commenter_names do |post|
122
+ post.commenters.pluck(:name)
123
+ end
124
+ many :comments, resource: AlbaCommentResource
125
+ end
126
+
127
+ # --- ActiveModelSerializer serializers ---
128
+
129
+ require "active_model_serializers"
130
+
131
+ ActiveModelSerializers.logger = Logger.new(nil)
132
+
133
+ class AMSCommentSerializer < ActiveModel::Serializer
134
+ attributes :id, :body
135
+ end
136
+
137
+ class AMSPostSerializer < ActiveModel::Serializer
138
+ attributes :id, :body
139
+ attribute :commenter_names
140
+ has_many :comments, serializer: AMSCommentSerializer
141
+
142
+ def commenter_names
143
+ object.commenters.pluck(:name)
144
+ end
145
+ end
146
+
147
+ # --- Blueprint serializers ---
148
+
149
+ require "blueprinter"
150
+
151
+ class CommentBlueprint < Blueprinter::Base
152
+ fields :id, :body
153
+ end
154
+
155
+ class PostBlueprint < Blueprinter::Base
156
+ fields :id, :body, :commenter_names
157
+ association :comments, blueprint: CommentBlueprint
158
+
159
+ def commenter_names
160
+ commenters.pluck(:name)
161
+ end
162
+ end
163
+
164
+ # --- JBuilder serializers ---
165
+
166
+ require "jbuilder"
167
+
168
+ class Post
169
+ def to_builder
170
+ Jbuilder.new do |post|
171
+ post.call(self, :id, :body, :commenter_names, :comments)
172
+ end
173
+ end
174
+
175
+ def commenter_names
176
+ commenters.pluck(:name)
177
+ end
178
+ end
179
+
180
+ class Comment
181
+ def to_builder
182
+ Jbuilder.new do |comment|
183
+ comment.call(self, :id, :body)
184
+ end
185
+ end
186
+ end
187
+
188
+ # --- Jserializer serializers ---
189
+
190
+ require 'jserializer'
191
+
192
+ class JserializerCommentSerializer < Jserializer::Base
193
+ attributes :id, :body
194
+ end
195
+
196
+ class JserializerPostSerializer < Jserializer::Base
197
+ attributes :id, :body, :commenter_names
198
+ has_many :comments, serializer: JserializerCommentSerializer
199
+ def commenter_names
200
+ object.commenters.pluck(:name)
201
+ end
202
+ end
203
+
204
+ # --- JSONAPI:Serializer serializers / (successor of fast_jsonapi) ---
205
+
206
+ class JsonApiStandardCommentSerializer
207
+ include JSONAPI::Serializer
208
+
209
+ attribute :id
210
+ attribute :body
211
+ end
212
+
213
+ class JsonApiStandardPostSerializer
214
+ include JSONAPI::Serializer
215
+
216
+ # set_type :post # optional
217
+ attribute :id
218
+ attribute :body
219
+ attribute :commenter_names
220
+
221
+ attribute :comments do |post|
222
+ post.comments.map { |comment| JsonApiSameFormatCommentSerializer.new(comment) }
223
+ end
224
+ end
225
+
226
+ # --- JSONAPI:Serializer serializers that format the code the same flat way as the other gems here ---
227
+
228
+ # code to convert from JSON:API output to "flat" JSON, like the other serializers build
229
+ class JsonApiSameFormatSerializer
230
+ include JSONAPI::Serializer
231
+
232
+ def as_json(*_options)
233
+ hash = serializable_hash
234
+
235
+ if hash[:data].is_a? Hash
236
+ hash[:data][:attributes]
237
+
238
+ elsif hash[:data].is_a? Array
239
+ hash[:data].pluck(:attributes)
240
+
241
+ elsif hash[:data].nil?
242
+ { }
243
+
244
+ else
245
+ raise "unexpected data type #{hash[:data].class}"
246
+ end
247
+ end
248
+ end
249
+
250
+ class JsonApiSameFormatCommentSerializer < JsonApiSameFormatSerializer
251
+ attribute :id
252
+ attribute :body
253
+ end
254
+
255
+ class JsonApiSameFormatPostSerializer < JsonApiSameFormatSerializer
256
+ attribute :id
257
+ attribute :body
258
+ attribute :commenter_names
259
+
260
+ attribute :comments do |post|
261
+ post.comments.map { |comment| JsonApiSameFormatCommentSerializer.new(comment) }
262
+ end
263
+ end
264
+
265
+ # --- Panko serializers ---
266
+ #
267
+
268
+ require "panko_serializer"
269
+
270
+ class PankoCommentSerializer < Panko::Serializer
271
+ attributes :id, :body
272
+ end
273
+
274
+
275
+ class PankoPostSerializer < Panko::Serializer
276
+ attributes :id, :body, :commenter_names
277
+
278
+ has_many :comments, serializer: PankoCommentSerializer
279
+
280
+ def commenter_names
281
+ object.comments.pluck(:name)
282
+ end
283
+ end
284
+
285
+ # --- Primalize serializers ---
286
+ #
287
+ class PrimalizeCommentResource < Primalize::Single
288
+ attributes id: integer, body: string
289
+ end
290
+
291
+ class PrimalizePostResource < Primalize::Single
292
+ alias post object
293
+
294
+ attributes(
295
+ id: integer,
296
+ body: string,
297
+ comments: array(primalize(PrimalizeCommentResource)),
298
+ commenter_names: array(string),
299
+ )
300
+
301
+ def commenter_names
302
+ post.commenters.pluck(:name)
303
+ end
304
+ end
305
+
306
+ class PrimalizePostsResource < Primalize::Many
307
+ attributes posts: enumerable(PrimalizePostResource)
308
+ end
309
+
310
+ # --- Representable serializers ---
311
+
312
+ require "representable"
313
+
314
+ class CommentRepresenter < Representable::Decorator
315
+ include Representable::JSON
316
+
317
+ property :id
318
+ property :body
319
+ end
320
+
321
+ class PostsRepresenter < Representable::Decorator
322
+ include Representable::JSON::Collection
323
+
324
+ items class: Post do
325
+ property :id
326
+ property :body
327
+ property :commenter_names
328
+ collection :comments
329
+ end
330
+
331
+ def commenter_names
332
+ commenters.pluck(:name)
333
+ end
334
+ end
335
+
336
+ # --- SimpleAMS serializers ---
337
+
338
+ require "simple_ams"
339
+
340
+ class SimpleAMSCommentSerializer
341
+ include SimpleAMS::DSL
342
+
343
+ attributes :id, :body
344
+ end
345
+
346
+ class SimpleAMSPostSerializer
347
+ include SimpleAMS::DSL
348
+
349
+ attributes :id, :body
350
+ attribute :commenter_names
351
+ has_many :comments, serializer: SimpleAMSCommentSerializer
352
+
353
+ def commenter_names
354
+ object.commenters.pluck(:name)
355
+ end
356
+ end
357
+
358
+ # --- Test data creation ---
359
+
360
+ 100.times do |i|
361
+ post = Post.create!(body: "post#{i}")
362
+ user1 = User.create!(name: "John#{i}")
363
+ user2 = User.create!(name: "Jane#{i}")
364
+ 10.times do |n|
365
+ post.comments.create!(commenter: user1, body: "Comment1_#{i}_#{n}")
366
+ post.comments.create!(commenter: user2, body: "Comment2_#{i}_#{n}")
367
+ end
368
+ end
369
+
370
+ posts = Post.all.to_a
371
+
372
+ # --- Store the serializers in procs ---
373
+
374
+ alba = Proc.new { AlbaPostResource.new(posts).serialize }
375
+ alba_inline = Proc.new do
376
+ Alba.serialize(posts) do
377
+ attributes :id, :body
378
+ attribute :commenter_names do |post|
379
+ post.commenters.pluck(:name)
380
+ end
381
+ many :comments do
382
+ attributes :id, :body
383
+ end
384
+ end
385
+ end
386
+
387
+ juso = Proc.new do
388
+ Juso.generate(posts)
389
+ end
390
+
391
+ ams = Proc.new { ActiveModelSerializers::SerializableResource.new(posts, {}).as_json }
392
+ blueprinter = Proc.new { PostBlueprint.render(posts) }
393
+ jbuilder = Proc.new do
394
+ Jbuilder.new do |json|
395
+ json.array!(posts) do |post|
396
+ json.post post.to_builder
397
+ end
398
+ end.target!
399
+ end
400
+ jserializer = Proc.new { JserializerPostSerializer.new(posts, is_collection: true).to_json }
401
+ jsonapi = proc { JsonApiStandardPostSerializer.new(posts).to_json }
402
+ jsonapi_same_format = proc { JsonApiSameFormatPostSerializer.new(posts).to_json }
403
+ panko = proc { Panko::ArraySerializer.new(posts, each_serializer: PankoPostSerializer).to_json }
404
+ primalize = proc { PrimalizePostsResource.new(posts: posts).to_json }
405
+ rails = Proc.new do
406
+ ActiveSupport::JSON.encode(posts.map{ |post| post.serializable_hash(include: :comments) })
407
+ end
408
+ representable = Proc.new { PostsRepresenter.new(posts).to_json }
409
+ simple_ams = Proc.new { SimpleAMS::Renderer::Collection.new(posts, serializer: SimpleAMSPostSerializer).to_json }
410
+
411
+ # --- Execute the serializers to check their output ---
412
+
413
+ puts "Serializer outputs ----------------------------------"
414
+ {
415
+ alba: alba,
416
+ alba_inline: alba_inline,
417
+ juso: juso,
418
+ ams: ams,
419
+ blueprinter: blueprinter,
420
+ jbuilder: jbuilder, # different order
421
+ jserializer: jserializer,
422
+ jsonapi: jsonapi, # nested JSON:API format
423
+ jsonapi_same_format: jsonapi_same_format,
424
+ panko: panko,
425
+ primalize: primalize,
426
+ rails: rails,
427
+ representable: representable,
428
+ simple_ams: simple_ams,
429
+ }.each { |name, serializer| puts "#{name.to_s.ljust(24, ' ')} #{serializer.call}" }
430
+
431
+ # --- Run the benchmarks ---
432
+
433
+ require 'benchmark/ips'
434
+ Benchmark.ips do |x|
435
+ x.report(:alba, &alba)
436
+ x.report(:alba_inline, &alba_inline)
437
+ x.report(:juso, &juso)
438
+ x.report(:ams, &ams)
439
+ x.report(:blueprinter, &blueprinter)
440
+ x.report(:jbuilder, &jbuilder)
441
+ x.report(:jserializer, &jserializer)
442
+ x.report(:jsonapi, &jsonapi)
443
+ x.report(:jsonapi_same_format, &jsonapi_same_format)
444
+ x.report(:panko, &panko)
445
+ x.report(:primalize, &primalize)
446
+ x.report(:rails, &rails)
447
+ x.report(:representable, &representable)
448
+ x.report(:simple_ams, &simple_ams)
449
+
450
+ x.compare!
451
+ end
452
+
453
+
454
+ require 'benchmark/memory'
455
+ Benchmark.memory do |x|
456
+ x.report(:alba, &alba)
457
+ x.report(:alba_inline, &alba_inline)
458
+ x.report(:juso, &juso)
459
+ x.report(:ams, &ams)
460
+ x.report(:blueprinter, &blueprinter)
461
+ x.report(:jbuilder, &jbuilder)
462
+ x.report(:jserializer, &jserializer)
463
+ x.report(:jsonapi, &jsonapi)
464
+ x.report(:jsonapi_same_format, &jsonapi_same_format)
465
+ x.report(:panko, &panko)
466
+ x.report(:primalize, &primalize)
467
+ x.report(:rails, &rails)
468
+ x.report(:representable, &representable)
469
+ x.report(:simple_ams, &simple_ams)
470
+
471
+ x.compare!
472
+ end
@@ -1,7 +1,7 @@
1
1
  # original from https://github.com/okuramasafumi/alba/blob/main/benchmark/single_resource.rb
2
2
 
3
3
  # Benchmark script to run varieties of JSON serializers
4
- # Fetch Alba from local, otherwise fetch latest from RubyGems
4
+ # Fetch juso from local, otherwise fetch latest from RubyGems
5
5
 
6
6
  # --- Bundle dependencies ---
7
7
 
@@ -22,10 +22,10 @@ gemfile(true) do
22
22
  gem "jserializer"
23
23
  gem "jsonapi-serializer" # successor of fast_jsonapi
24
24
  gem "multi_json"
25
- # gem "panko_serializer"
26
- # gem "pg"
25
+ gem "panko_serializer"
26
+ gem "pg"
27
27
  gem "primalize"
28
- # gem "oj"
28
+ gem "oj"
29
29
  gem "representable"
30
30
  gem "simple_ams"
31
31
  gem "sqlite3"
@@ -33,14 +33,14 @@ end
33
33
 
34
34
  # --- Test data model setup ---
35
35
 
36
- # require "pg"
36
+ require "pg"
37
37
  require "active_record"
38
- # require "active_record/connection_adapters/postgresql_adapter"
38
+ require "active_record/connection_adapters/postgresql_adapter"
39
39
  require "active_record/connection_adapters/sqlite3_adapter"
40
40
  require "logger"
41
- # require "oj"
41
+ require "oj"
42
42
  require "sqlite3"
43
- # Oj.optimize_rails
43
+ Oj.optimize_rails
44
44
 
45
45
  ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
46
46
  # ActiveRecord::Base.logger = Logger.new($stdout)
@@ -90,13 +90,11 @@ end
90
90
  # --- Juso serializers ---
91
91
 
92
92
  require "juso"
93
- require 'active_record' # hack for CollectionProxy
94
- Juso.reset_collection_classes
95
93
 
96
94
  class Comment
97
95
  include ::Juso::Serializable
98
96
 
99
- def as_juso_json(context)
97
+ def juso(context)
100
98
  {id: id, body: body}
101
99
  end
102
100
  end
@@ -104,7 +102,7 @@ end
104
102
  class Post
105
103
  include ::Juso::Serializable
106
104
 
107
- def as_juso_json(context)
105
+ def juso(context)
108
106
  {id: id, body: body, commenter_names: commenter_names, comments: comments}
109
107
  end
110
108
  end
@@ -266,22 +264,22 @@ end
266
264
 
267
265
  # --- Panko serializers ---
268
266
  #
269
- # require "panko_serializer"
270
- #
271
- # class PankoCommentSerializer < Panko::Serializer
272
- # attributes :id, :body
273
- # end
274
- #
275
- #
276
- # class PankoPostSerializer < Panko::Serializer
277
- # attributes :id, :body, :commenter_names
278
- #
279
- # has_many :comments, serializer: PankoCommentSerializer
280
- #
281
- # def commenter_names
282
- # object.comments.pluck(:name)
283
- # end
284
- # end
267
+ require "panko_serializer"
268
+
269
+ class PankoCommentSerializer < Panko::Serializer
270
+ attributes :id, :body
271
+ end
272
+
273
+
274
+ class PankoPostSerializer < Panko::Serializer
275
+ attributes :id, :body, :commenter_names
276
+
277
+ has_many :comments, serializer: PankoCommentSerializer
278
+
279
+ def commenter_names
280
+ object.comments.pluck(:name)
281
+ end
282
+ end
285
283
 
286
284
  # --- Primalize serializers ---
287
285
  #
@@ -363,8 +361,6 @@ post.reload
363
361
 
364
362
  juso = Proc.new do
365
363
  Juso.generate(post)
366
- rescue
367
- binding.irb
368
364
  end
369
365
 
370
366
  alba = Proc.new { AlbaPostResource.new(post).serialize }
@@ -385,8 +381,8 @@ jbuilder = Proc.new { post.to_builder.target! }
385
381
  jserializer = Proc.new { JserializerPostSerializer.new(post).to_json }
386
382
  jsonapi = proc { JsonApiStandardPostSerializer.new(post).to_json }
387
383
  jsonapi_same_format = proc { JsonApiSameFormatPostSerializer.new(post).to_json }
388
- # panko = proc { PankoPostSerializer.new.serialize_to_json(post) }
389
- panko = proc { 'nop' }
384
+ panko = proc { PankoPostSerializer.new.serialize_to_json(post) }
385
+ # panko = proc { 'nop' }
390
386
  primalize = proc { PrimalizePostResource.new(post).to_json }
391
387
  rails = Proc.new { ActiveSupport::JSON.encode(post.serializable_hash(include: :comments)) }
392
388
  representable = Proc.new { PostRepresenter.new(post).to_json }
data/juso.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = "juso is simple json serializer for web application"
13
13
  spec.homepage = "https://github.com/ykpythemind/juso"
14
14
  spec.license = "MIT"
15
- spec.required_ruby_version = ">= 2.4.0"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
16
 
17
17
  # spec.metadata["allowed_push_host"] = "TODO: Set to 'https://mygemserver.com'"
18
18
 
@@ -34,4 +34,6 @@ Gem::Specification.new do |spec|
34
34
 
35
35
  # For more information and examples about making a new gem, checkout our
36
36
  # guide at: https://bundler.io/guides/creating_gem.html
37
+
38
+ spec.add_development_dependency "rails", "~> 6.1.4"
37
39
  end
data/lib/juso/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Juso
4
- VERSION = "0.1.0"
4
+ VERSION = "1.0.2"
5
5
  end
data/lib/juso.rb CHANGED
@@ -9,63 +9,77 @@ module Juso
9
9
  class Error < StandardError; end
10
10
 
11
11
  module Serializable
12
- def as_juso_json(_)
12
+ def juso(_)
13
13
  nil
14
14
  end
15
15
  end
16
16
 
17
- # Juso Context is serializer context
18
- # xxxxx
17
+ # Juso::Context is serializer context
19
18
  class Context
20
- def initialize(serializer_type: :default)
21
- @serializer_type = serializer_type.to_sym
19
+ DEFAULT_OPTS = {}.freeze
20
+
21
+ def initialize(serializer: :default, options: DEFAULT_OPTS)
22
+ @serializer = serializer
23
+ @options = options
22
24
  end
23
25
 
24
- attr_reader :serializer_type
26
+ attr_reader :serializer, :options
25
27
  end
26
28
 
27
- def self.generate(object, context: Context.new)
28
- JSON.fast_generate(_generate(object, context))
29
+ # Juso.generate generates json string
30
+ def self.generate(object, context = Context.new)
31
+ JSON.fast_generate(_g(object, context))
29
32
  end
30
33
 
31
- # generate returns hash (as json)
32
- def self._generate(object, context)
34
+ # generate returns object (as json)
35
+ def self._g(object, context)
33
36
  case object
34
- when nil, Numeric, String
37
+ when nil, Numeric, String, true, false
35
38
  return object
36
39
  when Hash
37
40
  return object.each_with_object({}) do |(k, v), acc|
38
- acc[k] = _generate(v, context)
41
+ acc[k] = _g(v, context)
39
42
  end
40
43
  when Serializable
41
- # respond_to?(:as_juso_json) のほうが良い可能性ある?
42
- return _generate(object.as_juso_json(context), context)
44
+ # respond_to?(:juso) のほうが良い可能性ある?
45
+ return _g(object.juso(context), context)
43
46
  when *collection_classes
44
- return object.to_a.map { |o| _generate(o, context) }
47
+ return object.to_a.map { |o| _g(o, context) }
45
48
  when *date_classes
46
- return object.iso8601
49
+ return object.iso8601(context.options[:juso_time_n_digits] || 0)
47
50
  else
48
- # TODO: fallback to respond_to?(:as_juso_json) and warn?
51
+ # TODO: fallback to respond_to?(:juso) and warn?
49
52
 
50
53
  raise Error.new("cannot serialize object: #{object}. you must include Juso::Serializable")
51
54
  end
52
55
  end
53
56
 
57
+ # Juso.wrap is utility for wrapping object with Juso::Serializable class
58
+ def self.wrap(object, klass)
59
+ if collection_classes.any? { |arrayish| object.is_a?(arrayish) }
60
+ object.to_a.map { |o| klass.new(o) }
61
+ else
62
+ klass.new(object)
63
+ end
64
+ end
65
+
54
66
  def self.collection_classes
55
- @collection_classes
67
+ @collection_classes ||= default_collection_classes
56
68
  end
57
69
 
58
- def self.reset_collection_classes
59
- @collection_classes =
60
- if defined?(ActiveRecord)
61
- [Array, ActiveRecord::Relation, ActiveRecord::Associations::CollectionProxy]
62
- else
63
- [Array]
64
- end
70
+ def self.default_collection_classes
71
+ if defined?(ActiveRecord)
72
+ [Array, ActiveRecord::Relation]
73
+ else
74
+ [Array]
75
+ end
65
76
  end
66
77
 
67
78
  def self.date_classes
68
- # TODO: FIX / memorize
79
+ @date_classes ||= default_date_classes
80
+ end
81
+
82
+ def self.default_date_classes
69
83
  if defined?(ActiveSupport::TimeWithZone)
70
84
  [Date, DateTime, ActiveSupport::TimeWithZone]
71
85
  else
@@ -73,5 +87,5 @@ module Juso
73
87
  end
74
88
  end
75
89
 
76
- reset_collection_classes
90
+ private_class_method :default_collection_classes, :default_date_classes
77
91
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: juso
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - ykpythemind
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-23 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2021-12-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 6.1.4
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 6.1.4
13
27
  description: juso is simple json serializer for web application
14
28
  email:
15
29
  - yukibukiyou@gmail.com
@@ -26,6 +40,7 @@ files:
26
40
  - LICENSE.txt
27
41
  - README.md
28
42
  - Rakefile
43
+ - benchmark/collection.rb
29
44
  - benchmark/single_resource.rb
30
45
  - bin/console
31
46
  - bin/setup
@@ -48,7 +63,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
48
63
  requirements:
49
64
  - - ">="
50
65
  - !ruby/object:Gem::Version
51
- version: 2.4.0
66
+ version: 2.6.0
52
67
  required_rubygems_version: !ruby/object:Gem::Requirement
53
68
  requirements:
54
69
  - - ">="