choron_support 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +2 -6
- data/docs/props.md +17 -217
- data/lib/choron_support/as_props.rb +11 -15
- data/lib/choron_support/props/attributes.rb +183 -349
- data/lib/choron_support/props/base.rb +16 -25
- data/lib/choron_support/props/private/setting.rb +42 -0
- data/lib/choron_support/props/private/type_builder.rb +129 -0
- data/lib/choron_support/props/private/type_generator.rb +62 -0
- data/lib/choron_support/version.rb +1 -1
- data/lib/choron_support.rb +6 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f5e95a92c449d959b9e9522a05e244461e98fb078b5405c93ceb873fdc5c49b
|
4
|
+
data.tar.gz: cec6ee644f7813d21e24039aadd30a6047e3a708a48cced1412f593c68be626a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a77b9097a63624fc09fdea17886eb85861c37272058938739692b812fd728d658602020c1b56f0ac3d2ed7f08e51f3b9b234dac28d23e9590620b81ac6794e08
|
7
|
+
data.tar.gz: 5ceff8def9787ebf4bdadf656173669f5ef1271cf27e08b41ec22b122b134f1f3e19ef70f59674a3a714eb4061e617fd381ea0a84a8c542b2bb6f9803fc02cba
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -25,13 +25,9 @@ ChoronSupport.using :all
|
|
25
25
|
|
26
26
|
* 必要に応じて各種モジュールをincludeすることで利用できます
|
27
27
|
|
28
|
-
###
|
29
|
-
|
30
|
-
モデルをJSON(キーがローキャメルケース)に変換するための仕組みです。
|
31
|
-
|
32
|
-
* 詳細な使い方は [テストファイル](./spec/choron_support/as_props_spec.rb) を参照ください。
|
33
|
-
* 詳細な実装は [こちら](./lib/choron_support/as_props.rb) です。
|
28
|
+
### Props
|
34
29
|
|
30
|
+
* [Props ドキュメント](./docs/props.md)を参照ください
|
35
31
|
|
36
32
|
### Mask
|
37
33
|
|
data/docs/props.md
CHANGED
@@ -11,225 +11,25 @@ Choronでは画面側の処理をReact + Typescrip の組み合わせで実現
|
|
11
11
|
一般的なJSON化ツールの違いとしては、Javascript側の記法・慣習を優先するため、
|
12
12
|
JSONのキーをローキャメルケース(fullName, isAdult,など)に自動変換します
|
13
13
|
|
14
|
-
##
|
14
|
+
## 使い方
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
* ChoronSupport::Props::Attributes
|
21
|
-
の2つのクラス・モジュールを継承&includedしたProps用の基底クラスを用意しています。
|
22
|
-
|
23
|
-
```app/models/props/base.rb
|
24
|
-
class Props::Base < ChoronSupport::Props::Base
|
25
|
-
include ChoronSupport::Props::Attributes
|
26
|
-
end
|
27
|
-
```
|
28
|
-
|
29
|
-
そして以下の命名ルールにより、モデルごとのPropsクラスを作成しています
|
30
|
-
* app/models/props/${モデル名}.rb
|
31
|
-
* 例: app/models/props/user.rb
|
32
|
-
これは ChoronSupport::AsProps モジュールがモデル名から自動で props/**/*.rb を探し出してインスタンスを生成してくれる処理に由来します。
|
33
|
-
|
34
|
-
```app/models/props/user.rb
|
35
|
-
class Props::Foo < Props::Base
|
36
|
-
attributes :id, :name, :full_name
|
37
|
-
attributes :full_name, to: :self
|
38
|
-
def full_name
|
39
|
-
"#{model.first_name} #{model.last_name}"
|
40
|
-
end
|
41
|
-
end
|
42
|
-
```
|
43
|
-
|
44
|
-
これで準備は完了です。
|
45
|
-
以下のようにController等で利用ができます
|
46
|
-
|
47
|
-
```app/controllers/users_controller.rb
|
48
|
-
class UsersController < ApplicationController
|
49
|
-
def index
|
50
|
-
users = User.all
|
51
|
-
props = {
|
52
|
-
users: users.as_props
|
53
|
-
}
|
54
|
-
|
55
|
-
render react_file(props: props)
|
56
|
-
end
|
57
|
-
|
58
|
-
def show
|
59
|
-
user = User.find(params[:id])
|
60
|
-
props = {
|
61
|
-
user: user.as_props
|
62
|
-
}
|
63
|
-
|
64
|
-
render react_file(props: props)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
```
|
68
|
-
|
69
|
-
サンプルコードからわかるように `#as_props` というメソッドはモデルおよびRelationの両方で利用が可能なように拡張をしています。
|
70
|
-
|
71
|
-
この拡張を使うためには ChoronSupport::AsProps モジュールを ApplicationRecord にinclude する必要があります。
|
72
|
-
|
73
|
-
Choronではすでに実施済です。
|
74
|
-
|
75
|
-
```app/models/application_record.rb
|
76
|
-
class ApplicationRecord < ActiveRecord::Base
|
77
|
-
include ChoronSupport::AsProps
|
78
|
-
end
|
79
|
-
```
|
80
|
-
|
81
|
-
もしPropsクラスをわざわざ作らず、モデルのカラムをそのまま全てローキャメルケースで出したい時もあると思います。そのときは
|
82
|
-
|
83
|
-
```
|
84
|
-
user.as_props(:model)
|
85
|
-
```
|
86
|
-
|
87
|
-
と第一引数で `:model` を渡すことで、カラム全てをprops化します。
|
88
|
-
※これは個人情報など思わぬ値も出力してしまう可能性もあるため、安全な処理にだけ使うことを推奨します
|
89
|
-
|
90
|
-
また、キーワード引数を使うことでパラメーターを渡すことも可能です。
|
91
|
-
これを利用すれば、Props側を利用する側から細かいPropsの出力調整ができます。
|
92
|
-
|
93
|
-
```app/models/props/foo.rb
|
94
|
-
class Props::Foo < Props::Base
|
95
|
-
attributes :id, :name
|
96
|
-
attributes :full_name, if: :show_name?
|
97
|
-
def show_name?
|
98
|
-
# #params で as_props 実行時のキーワード引数にアクセスができる
|
99
|
-
params[:show_name].present?
|
100
|
-
end
|
101
|
-
end
|
102
|
-
```
|
103
|
-
|
104
|
-
これは以下のように利用できます
|
105
|
-
|
106
|
-
```
|
107
|
-
foo = Foo.find(1)
|
108
|
-
foo.as_props(show_name: true)
|
109
|
-
```
|
110
|
-
|
111
|
-
また、この `DSL` を使うことで以下の情報がメタ情報として自動的に出力されます。
|
112
|
-
* type: Props化を行なったクラスの名前(名前空間あり)
|
113
|
-
* modelName: Props化を行なったモデルの名前(名前空間なし)
|
114
|
-
|
115
|
-
```app/models/props/foos/bar.rb
|
116
|
-
class Props::Foos::Bar < Props::Base
|
117
|
-
attributes :id, :name
|
16
|
+
```ruby
|
17
|
+
class Props::Samples::Foo < Props::Base
|
18
|
+
attribute :id
|
19
|
+
attribute :full_name
|
118
20
|
end
|
119
|
-
```
|
120
|
-
|
121
|
-
Foos::Bar.new.as_props
|
122
|
-
#=> { id: x, name: "xxx", type: "Foos::Bar", modelName: "Bar" }
|
123
|
-
|
124
|
-
もし `RAILS_ENV=test` のときはさらに Props化を行なったクラスもメタ情報として付与されます。
|
125
|
-
|
126
|
-
Foos::Bar.new.as_props
|
127
|
-
#=> { id: x, name: "xxx", type: "Foos::Bar", modelName: "Bar", propsClassName: "Props::Foos::Bar" }
|
128
|
-
|
129
|
-
このメタ情報を使うことでテストの簡略化も可能となります。
|
130
|
-
|
131
|
-
例えば Controller のテストを以下のように書くことができます。
|
132
|
-
|
133
|
-
```spec/requests/foos_controller_spec.rb
|
134
|
-
describe FoosController, type: :request do
|
135
|
-
describe "GET /foos/:id" do
|
136
|
-
let!(:id) { foo.id }
|
137
|
-
let!(:foo) { create(:foo) }
|
138
|
-
it "詳細画面が表示されること" do
|
139
|
-
is_expected.to eq 200
|
140
|
-
|
141
|
-
react = rendered_react("foo/show")
|
142
|
-
props = react.props
|
143
|
-
# 細かい値の設定はProps側の単体テストで担保しているため、ここでは使われているPropsのみ検証する
|
144
|
-
expect(props[:foo][:propsClassName]).to eq "Props::Foos::Bar"
|
145
|
-
# Choron では専用のマッチャーがあるため以下のように記載も可能
|
146
|
-
expect(props[:foo]).to be_use_props(Props::Foos::Bar)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
```
|
151
|
-
|
152
|
-
### DSLの細かい使い方
|
153
|
-
* ChoronSupport::Props::Attributesのソースコードを参照ください
|
154
|
-
* https://github.com/mksava/choron_support/blob/main/lib/choron_support/props/attributes.rb
|
155
|
-
|
156
|
-
### Propsの設計思想
|
157
|
-
ChoronでのPropsはattributesのDSLでif文の指定が可能です。
|
158
|
-
そうでなくてもpropsメソッドをオーバーライドすることでさらに細かい調整が可能です。
|
159
|
-
|
160
|
-
しかしPropsの設計思想は「1つのPropsで複数のパターンのJSONを作成する」よりも「複数のパターンがある分、Propsクラスを作成する」にあります。
|
161
|
-
|
162
|
-
たとえば「User」には個人情報が含まれいるため、Propsの出力を制御したいときは
|
163
|
-
|
164
|
-
* 一般的な利用
|
165
|
-
* `app/models/props/user.rb`
|
166
|
-
* スタッフなど個人情報にアクセス可能なユーザからの利用
|
167
|
-
* `app/models/props/users/staff.rb`
|
168
|
-
* ログインしているユーザ自身が自分自身の情報を見たいときに利用
|
169
|
-
* `app/models/props/users/current.rb`
|
170
|
-
|
171
|
-
というようにPropsクラスを複数作成することを検討してください。
|
172
|
-
このとき、各Propsクラスは以下のように `#as_props` の第一引数を指定することで利用できます
|
173
|
-
|
174
|
-
```
|
175
|
-
# app/models/props/users/general.rb
|
176
|
-
users = Users.all.as_props(:general)
|
177
|
-
# app/models/props/users/staff.rb
|
178
|
-
users = Users.all.as_props(:staff)
|
179
|
-
# app/models/props/users/current.rb
|
180
|
-
users = Users.all.as_props(:current)
|
181
|
-
```
|
182
|
-
|
183
|
-
## サンプルコード
|
184
|
-
|
185
|
-
* Props を作成するときはこのサンプルコードを優先的に参考にしてください
|
186
|
-
* Sample の部分は適宜作成したいモデル名に変更してください
|
187
|
-
|
188
|
-
### ActiveRecord
|
189
|
-
|
190
|
-
```app/models/sample.rb
|
191
|
-
class Sample < ApplicationRecord
|
192
|
-
end
|
193
|
-
```
|
194
|
-
|
195
|
-
```app/controllers/samples_controller.rb
|
196
|
-
class SamplesController < ApplicationController
|
197
|
-
def index
|
198
|
-
samples = Sample.all
|
199
|
-
props = {
|
200
|
-
samples: samples.as_props(:general)
|
201
|
-
}
|
202
|
-
|
203
|
-
render react_file(props: props)
|
204
|
-
end
|
205
|
-
end
|
206
|
-
```
|
207
|
-
|
208
|
-
```app/models/props/sample.rb
|
209
|
-
class Props::Sample < Props::Base
|
210
|
-
attributes :id, :name, :created_at, :updated_at
|
211
|
-
attributes :is_sample?, to: :self
|
212
21
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
end
|
22
|
+
sample = Sample.new(id: 100, full_name: "mksava")
|
23
|
+
sample.as_props(:foo)
|
24
|
+
#=> { id: 100, fullName: "mksava", type: "Foo", modelName: "Foo" }
|
217
25
|
```
|
218
26
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
```
|
229
|
-
|
230
|
-
```app/models/props/samples/staff.rb
|
231
|
-
class Props::Samples::Staff < Props::Base
|
232
|
-
self.union = :default
|
233
|
-
attributes :salary
|
234
|
-
end
|
235
|
-
```
|
27
|
+
* 詳細な使い方はテストファイルを参照ください。
|
28
|
+
* [一般的な使い方](../spec/choron_support/as_props/general_spec.rb)
|
29
|
+
* [エッジケース](../spec/choron_support/as_props/edge_spec.rb)
|
30
|
+
* [attribute DSLの使い方](../spec/choron_support/as_props/attribute_spec.rb)
|
31
|
+
* [inherit DSL の使い方](../spec/choron_support/as_props/inherit_spec.rb)
|
32
|
+
* [relation DSLの使い方](../spec/choron_support/as_props/relation_spec.rb)
|
33
|
+
* 実装は以下を参照ください
|
34
|
+
* [as_props.rb](../lib/choron_support/as_props.rb)
|
35
|
+
* [props/](../lib/choron_support/props/)
|
@@ -9,14 +9,14 @@ module ChoronSupport
|
|
9
9
|
# @param [Hash] params その他のパラメータ。camel: false を指定すると自動でキャメライズしない。
|
10
10
|
# @return [Hash]
|
11
11
|
def as_props(type_symbol = nil, **params)
|
12
|
-
|
12
|
+
pass_params = params.except(:camel)
|
13
13
|
|
14
|
+
serializer = self.__get_props_class(type_symbol, pass_params)
|
14
15
|
skip_camel = (params[:camel] == false)
|
15
|
-
pass_params = params.except(:camel)
|
16
16
|
if serializer.nil?
|
17
17
|
skip_camel ? self.as_json : self.as_json.as_camel
|
18
18
|
else
|
19
|
-
skip_camel ? serializer.as_props
|
19
|
+
skip_camel ? serializer.as_props : serializer.as_props.as_camel
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
@@ -31,14 +31,15 @@ module ChoronSupport
|
|
31
31
|
class_name = type_symbol.to_s.classify
|
32
32
|
# 例: Serialize::Users::FooBar
|
33
33
|
props_class_name = "#{namespace}::#{class_name}"
|
34
|
-
when
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
34
|
+
when Class
|
35
|
+
given_class = type_symbol
|
36
|
+
if given_class.ancestors.include?(ChoronSupport::Props::Base)
|
37
|
+
props_class_name = given_class.to_s
|
38
|
+
else
|
39
|
+
raise ArgumentError, "invalid class: #{given_class}"
|
40
|
+
end
|
40
41
|
else
|
41
|
-
raise ArgumentError
|
42
|
+
raise ArgumentError, "invalid type_symbol: #{type_symbol}"
|
42
43
|
end
|
43
44
|
|
44
45
|
begin
|
@@ -46,11 +47,6 @@ module ChoronSupport
|
|
46
47
|
|
47
48
|
props_class.new(self, params)
|
48
49
|
rescue *rescue_errors
|
49
|
-
# もしmodelを指定しているときはnilを返し、as_jsonを利用させる
|
50
|
-
if type_symbol == :model
|
51
|
-
return nil
|
52
|
-
end
|
53
|
-
|
54
50
|
if type_symbol.blank?
|
55
51
|
raise ChoronSupport::AsProps::NameError, "Props class not found: #{props_class_name}. Please create props class."
|
56
52
|
else
|
@@ -1,381 +1,215 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
# attributes :amount, :amount_with_unit, to: :self
|
8
|
-
# def initialize(amount)
|
9
|
-
# @amount = amount.to_i
|
10
|
-
# end
|
11
|
-
# def amount
|
12
|
-
# @amount
|
13
|
-
# end
|
14
|
-
# def amount_with_unit
|
15
|
-
# "#{amount}円"
|
16
|
-
# end
|
17
|
-
# end
|
18
|
-
# Choronではデフォルトで以下のようなBaseクラスが作成されて、読み込みがされています
|
19
|
-
# @example
|
20
|
-
# class Props::Base < ChoronSupport::Props::Base
|
21
|
-
# include ChoronSupport::Props::Attributes
|
22
|
-
# end
|
23
|
-
# [使い方]
|
24
|
-
# @example 最も簡単な例
|
25
|
-
# [app/models/props/user.rb]
|
26
|
-
# class Props::User < Props::Base
|
27
|
-
# # id, full_name, ageを出力させる
|
28
|
-
# # retult: { id: 1, fullName: "John Smith", age: 20 }
|
29
|
-
# attributes :id, :full_name, :age
|
30
|
-
# end
|
31
|
-
# @example メソッドのデリゲート先を指定する
|
32
|
-
# [app/models/props/user.rb]
|
33
|
-
# class Props::User < Props::Base
|
34
|
-
# # result: { fullName: "John Smith" }
|
35
|
-
# attributes :full_name, to: :self
|
36
|
-
# def full_name
|
37
|
-
# "#{model.first_name} #{model.last_name}"
|
38
|
-
# end
|
39
|
-
# end
|
40
|
-
# @example 関連先のModelのPropsを結合する
|
41
|
-
# class Props::User < Props::Base
|
42
|
-
# # result: { posts: posts.as_props } => { posts: [{ id: 1, title: "foo" }, { id: 2, title: "bar" }] }
|
43
|
-
# relation :posts
|
44
|
-
# end
|
45
|
-
#
|
46
|
-
# これらの各種DSLは複数同時に設定することもできます
|
47
|
-
# @example 複数設定
|
48
|
-
# [app/models/props/user.rb]
|
49
|
-
# class Props::User < Props::Base
|
50
|
-
# # id, full_name, ageを出力させる
|
51
|
-
# attributes :id, :age
|
52
|
-
# attributes :full_name, to: :self
|
53
|
-
# relation :posts
|
54
|
-
# def full_name
|
55
|
-
# "#{model.first_name} #{model.last_name}"
|
56
|
-
# end
|
57
|
-
# end
|
58
|
-
#
|
59
|
-
# default値の設定やifオプションを渡して出力有無を動的に変更もできます
|
60
|
-
# @example default, ifの設定
|
61
|
-
# [app/models/props/user.rb]
|
62
|
-
# class Props::User < Props::Base
|
63
|
-
# # age が nil のときは 0 を出力する
|
64
|
-
# # age が 20 以上のときのみ出力する
|
65
|
-
# attributes :age, default: 0, if: :show_age?
|
66
|
-
# def show_age?
|
67
|
-
# model.age >= 20
|
68
|
-
# end
|
69
|
-
# end
|
70
|
-
#
|
71
|
-
#
|
72
|
-
# 本DSLを利用するときは基本的には attributes を使って設定するのが良いと思います
|
73
|
-
# 細かな使い方モジュールの該当DSL(self.xxxx)の説明を参照してください
|
74
|
-
module ChoronSupport
|
75
|
-
module Props
|
76
|
-
module Attributes
|
77
|
-
FORMATS = {
|
78
|
-
# HTMLのinput type="date"で使える形式
|
79
|
-
date: "%Y-%m-%d",
|
80
|
-
datetime: "%Y-%m-%dT%H:%M",
|
81
|
-
}.freeze
|
82
|
-
# 型のキャスト指定があってもキャストはしないメソッド
|
83
|
-
CAST_IGNORE_METHODS = [
|
84
|
-
# id は数値のほうが良いため
|
85
|
-
:id,
|
86
|
-
].freeze
|
87
|
-
# デフォルト値を設定しない場合に使う値
|
88
|
-
NO_DEFAULT = Object.new.freeze
|
89
|
-
|
90
|
-
extend ActiveSupport::Concern
|
91
|
-
|
92
|
-
included do
|
93
|
-
# 本moduleはActiveSupportが使える環境でのみ動作します
|
94
|
-
unless defined?(ActiveSupport)
|
95
|
-
raise "ActiveSupport is not defined. Please require 'active_support/all' in your Gemfile"
|
96
|
-
end
|
97
|
-
|
98
|
-
# DSLで設定できる設定値たち ==========================
|
99
|
-
# Props作成時に設定されるキーと値を設定します。
|
100
|
-
# この値はDSLを経由して内部的に設定されていきます
|
101
|
-
class_attribute :settings_attributes
|
102
|
-
# Props作成時に自動で付与される元のクラスの文字列を設定しないときはtrueを設定してください
|
103
|
-
# @example
|
104
|
-
# class Props::Foo < Props::Base
|
105
|
-
# self.skip_meta_mark = true
|
106
|
-
# end
|
107
|
-
class_attribute :skip_meta_mark, default: false
|
108
|
-
# 他のPropsクラスの結果を結合するときに結合先のProps識別子を設定してください
|
109
|
-
# @example
|
110
|
-
# class Props::Foos::General < Props::Base
|
111
|
-
# # すべてのカラムを出す
|
112
|
-
# self.union = :model
|
113
|
-
# # as_props の結果を結合する
|
114
|
-
# self.union = :default
|
115
|
-
# # as_props(:secure) の結果を結合する
|
116
|
-
# self.union = :secure
|
117
|
-
# end
|
118
|
-
class_attribute :union, default: nil
|
119
|
-
# ====================================================
|
120
|
-
|
121
|
-
# Props作成時に設定されるキーと値を設定します。
|
122
|
-
# @param [Symbol] key Propsのキーを指定してください
|
123
|
-
# @param [Symbol] method 値を代入するためのメソッドを指定してください。指定がないときはkeyをメソッドとして扱います
|
124
|
-
# @param [Symbol] to メソッドのデリゲート先を指定してください。指定がないときはmodelをデリゲート先として扱います。:selfを指定すると自身をデリゲート先として扱います
|
125
|
-
# @param [Symbol] cast デリゲート先のメソッドの戻り値に対して、さらにメソッドを実行するときは指定してください。
|
126
|
-
# @param [Object] default 値がnilのときに設定されるデフォルト値を指定してください。指定がないときはnilになります。
|
127
|
-
# @param [Proc | Symbol] if その値を出すときの条件。Symbolだとselfに対してsendを実行します
|
128
|
-
def self.attribute(key, method: nil, to: :model, cast: nil, default: NO_DEFAULT, if: nil)
|
129
|
-
self.settings_attributes ||= []
|
130
|
-
self.settings_attributes << { key:, to:, method: (method || key), cast:, default:, if: }
|
131
|
-
end
|
132
|
-
|
133
|
-
# 一度にまとめて複数のattributeを設定します
|
134
|
-
# 基本的なパラメータの説明はattributeを参照してください
|
135
|
-
# @param [Array<Symbol>] methods 設定するキーと値のペアを指定してください
|
136
|
-
# @example
|
137
|
-
# model = User.new(id: 1, name: "John", email: "JOHN@EXAMPLE", age: nil)
|
138
|
-
# class Props::User::Foo < ChoronSupport::Props::Base
|
139
|
-
# attributes :id, :name, :email, :age
|
140
|
-
# #=> { id: 1, name: "John", email: "JOHN@EXAMPLE", age: nil }
|
141
|
-
# end
|
142
|
-
# @note to, cast, defaultなどのパラメータについて
|
143
|
-
# これらの値は全てのメソッド・キーに対して同じ値が設定されます
|
144
|
-
def self.attributes(*methods, to: :model, cast: nil, default: NO_DEFAULT, if: nil)
|
145
|
-
methods.each do |method|
|
146
|
-
attribute(method, method:, to:, cast:, default:, if:)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
# Modelに対して関連付けされた別ModelのPropsを結合するためのDSLです
|
151
|
-
# to, cast, default, if については attribute と同じです
|
152
|
-
# @param [Symbol] key Propsのキーを指定してください
|
153
|
-
# @param [Symbol] relation 結合するModelのメソッドを指定してください。指定がないときはkeyをメソッドとして扱います
|
154
|
-
# @example
|
155
|
-
# class Props::User < ChoronSupport::Props::Base
|
156
|
-
# relation :posts
|
157
|
-
# #=> { posts: user.posts.as_props } と同じ結果になる
|
158
|
-
# relation :posts, props: :foo_bar
|
159
|
-
# #=> { posts: user.posts.as_props(:foo_bar) } と同じ結果になる
|
160
|
-
# relation :user_posts, relation: :posts
|
161
|
-
# #=> { user_posts: user.posts.as_props } と同じ結果になる
|
162
|
-
# end
|
163
|
-
def self.relation(key, relation: nil, to: :model, cast: nil, default: NO_DEFAULT, props: nil, if: nil)
|
164
|
-
relation ||= key
|
165
|
-
method = lambda { |model|
|
166
|
-
records = model.send(relation)
|
167
|
-
if props
|
168
|
-
records&.as_props(props) || {}
|
169
|
-
else
|
170
|
-
records&.as_props || {}
|
171
|
-
end
|
172
|
-
}
|
173
|
-
self.attribute(
|
174
|
-
key,
|
175
|
-
method:,
|
176
|
-
to:,
|
177
|
-
cast:,
|
178
|
-
default:,
|
179
|
-
if:,
|
180
|
-
)
|
181
|
-
end
|
182
|
-
|
183
|
-
# self.relation の複数同時に設定ができるversionです
|
184
|
-
# 基本は上記と一緒ですが、relationとkeyは同じである必要があります
|
185
|
-
# @example
|
186
|
-
# class Props::User < ChoronSupport::Props::Base
|
187
|
-
# relations :posts, :comments
|
188
|
-
# #=> { posts: user.posts.as_props, comments: user.comments.as_props } と同じ結果になる
|
189
|
-
# end
|
190
|
-
def self.relations(*keys, to: :model, cast: nil, default: NO_DEFAULT, props: nil, if: nil)
|
191
|
-
keys.each do |key|
|
192
|
-
method = lambda { |model|
|
193
|
-
records = model.send(key)
|
194
|
-
if props
|
195
|
-
records&.as_props(props) || {}
|
196
|
-
else
|
197
|
-
records&.as_props || {}
|
198
|
-
end
|
199
|
-
}
|
200
|
-
self.attribute(
|
201
|
-
key,
|
202
|
-
method:,
|
203
|
-
to:,
|
204
|
-
cast:,
|
205
|
-
default:,
|
206
|
-
if:,
|
207
|
-
)
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
# Propsに設定されるキーと値のペアを返します
|
212
|
-
# @param 各種パラメータは self.attribute に合わせているためそちらを参照してください
|
213
|
-
# @return [Hash] 設定されるPropsのキーと値のペア
|
214
|
-
# @note memo
|
215
|
-
# if が予約語のため options として受け取っています。_ifも検討しましたが全体でキーワードの形を合わせたかったためoptionsの形にしています
|
216
|
-
def attribute(key, method: nil, to: :model, cast: nil, default: NO_DEFAULT, **options)
|
217
|
-
__build_props_attribute__(key, (method || key), to, cast, default, **options)
|
218
|
-
end
|
219
|
-
|
220
|
-
# 一度にまとめて複数のattributeを設定します
|
221
|
-
# パラメータは self.attributes に合わせているためそちらを参照してください
|
222
|
-
def attributes(*methods, to: :model, cast: nil, default: nil, **options)
|
223
|
-
_props = {}
|
224
|
-
methods.each do |method|
|
225
|
-
key = method.to_sym
|
226
|
-
unit_props = __build_props_attribute__(key, method, to, cast, default, **options)
|
1
|
+
require_relative "./private/type_builder"
|
2
|
+
require_relative "./private/setting"
|
3
|
+
module ChoronSupport::Props::Attributes
|
4
|
+
unless defined?(ActiveSupport)
|
5
|
+
raise "ActiveSupport is not defined. Please require 'active_support/all' in your Gemfile"
|
6
|
+
end
|
227
7
|
|
228
|
-
|
229
|
-
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
# DSLで設定できる設定値たち ==========================
|
12
|
+
# Props作成時に設定されるキーと値を設定します。
|
13
|
+
# この値はDSLを経由して内部的に設定されていきます
|
14
|
+
class_attribute :settings, instance_writer: false, default: nil
|
15
|
+
# Props作成時に自動で付与される元のクラスの文字列を設定しないときはtrueを設定してください
|
16
|
+
# @example
|
17
|
+
# class Props::Foos::Bar
|
18
|
+
# self.skip_meta_mark = true
|
19
|
+
# end
|
20
|
+
class_attribute :skip_meta_mark, default: false
|
21
|
+
# Propsから自動でtypescriptの型を生成しないときはfalseを設定してください
|
22
|
+
# @example
|
23
|
+
# class Props::Foos::Bar
|
24
|
+
# self.skip_typescript = true
|
25
|
+
# end
|
26
|
+
class_attribute :skip_typescript, default: false
|
27
|
+
# 他のPropsクラスの設定を継承するときに設定されます
|
28
|
+
class_attribute :inherit_props_class, default: nil
|
29
|
+
# ====================================================
|
30
|
+
|
31
|
+
# Propsとして出力する属性を設定するためのDSLです
|
32
|
+
# @param [Symbol] method モデル, もしくは to オプションで設定したオブジェクトに対して実行するメソッドを指定してください
|
33
|
+
# @param [Keyword] options その他のオプションを指定してください
|
34
|
+
# @option [Symbol] :to 指定したメソッドを実行するオブジェクトを指定できます
|
35
|
+
# @option [Symbol | lambda] :if 属性を出力するための条件を指定できます
|
36
|
+
# @option [Symbol] :cast 属性を出力する前に指定したメソッドを実行できます。
|
37
|
+
# @option [Boolean] :default 属性値がnilのときに代わりに出力する値を指定できます
|
38
|
+
# @option [Proc] &block ブロックを渡すとそのブロックの戻り値を属性値として出力します
|
39
|
+
def self.attribute(method, **options, &block)
|
40
|
+
setting_params = options.merge(method: method, block: block)
|
41
|
+
setting = ChoronSupport::Props::Private::Setting.new(setting_params)
|
42
|
+
|
43
|
+
self.settings ||= []
|
44
|
+
self.settings << setting
|
45
|
+
end
|
230
46
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
47
|
+
# 他のPropsクラスの設定を継承するためのDSLです
|
48
|
+
# @param [ChoronSupport::Props::Base] inherit_props_class 継承するPropsクラスを指定してください
|
49
|
+
# @example
|
50
|
+
# class Props::Users::General < ChoronSupport::Props::Base
|
51
|
+
# inherit Props::Users::Base
|
52
|
+
# end
|
53
|
+
def self.inherit(props_class)
|
54
|
+
# 継承するクラスはProps::Baseを継承している必要があります
|
55
|
+
unless props_class.ancestors.include?(ChoronSupport::Props::Base)
|
56
|
+
raise "inherit class must be ChoronSupport::Props::Base. got: #{props_class}"
|
57
|
+
end
|
235
58
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
59
|
+
# 既に継承先が設定されている場合はエラーにします
|
60
|
+
if self.inherit_props_class.present?
|
61
|
+
raise "inherit props inherit class already set: #{self.inherit_props_class}.(Only one class can be inherited)"
|
62
|
+
end
|
240
63
|
|
241
|
-
|
242
|
-
|
64
|
+
self.inherit_props_class = props_class
|
65
|
+
self.settings ||= []
|
66
|
+
self.inherit_props_class.settings.to_a.each do |setting|
|
67
|
+
self.settings << setting
|
68
|
+
end
|
69
|
+
end
|
243
70
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
71
|
+
# Modelに対して関連付けされた別ModelのPropsを結合するためのDSLです
|
72
|
+
# @param [Symbol] method to オプションで指定されたオブジェクトに実行されるメソッドを指定してください
|
73
|
+
# @param [ChoronSupport::Props::Base] props モデルをProps化するためのクラスを指定してください
|
74
|
+
# @param [Keyword] options その他のオプションを指定してください。詳細は attribute と同じです
|
75
|
+
# @example
|
76
|
+
# class Props::Users::General < ChoronSupport::Props::Base
|
77
|
+
# relation :posts, props: Props::Posts::General
|
78
|
+
# #=> { posts: user.posts.as_props(:general) } と同じ結果になる
|
79
|
+
# end
|
80
|
+
def self.relation(method, props_class, **options)
|
81
|
+
self.attribute(method, **options) do |model, params|
|
82
|
+
records = model.send(method)
|
83
|
+
records&.as_props(props_class, **params)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
255
87
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
_props.merge!(model.as_props)
|
260
|
-
else
|
261
|
-
_props.merge!(model.as_props(self.class.union))
|
262
|
-
end
|
263
|
-
end
|
88
|
+
# @return [Hash] props
|
89
|
+
def as_props
|
90
|
+
_props = {}
|
264
91
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
)
|
270
|
-
end
|
92
|
+
# DSLの設定を設定する
|
93
|
+
self.class.settings.to_a.each do |setting|
|
94
|
+
_props.merge!(__build_props_attribute__(setting))
|
95
|
+
end
|
271
96
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
# Propsがオーバーライドされていればその値で上書きする
|
277
|
-
_props.merge!(self.props)
|
97
|
+
# Classのマークをつける(テスト用)
|
98
|
+
_props.merge!(__build_props_class_mark__)
|
99
|
+
# Modelのマークをつける
|
100
|
+
_props.merge!(__build_props_meta_mark__)
|
278
101
|
|
279
|
-
|
280
|
-
|
102
|
+
_props
|
103
|
+
end
|
281
104
|
|
282
|
-
|
283
|
-
# @note
|
284
|
-
# オーバーライドして使うことを想定しています
|
285
|
-
def props
|
286
|
-
{}
|
287
|
-
end
|
288
|
-
end
|
105
|
+
private
|
289
106
|
|
290
|
-
|
107
|
+
def model
|
108
|
+
raise NotImplementedError, "model method is not implemented"
|
109
|
+
end
|
291
110
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
# @param [Block] blockを渡すと実行結果をブロック引数でわたし、その中の戻り値を結果として返します
|
296
|
-
# @param [Symbol] cast デリゲート先のメソッドの戻り値に対して、さらにメソッドを実行する
|
297
|
-
# 複雑性を増す代わりに集約をさせています
|
298
|
-
def __build_props_attribute__(key, method, to, cast, default, **options)
|
299
|
-
props = {}
|
111
|
+
def params
|
112
|
+
raise NotImplementedError, "params method is not implemented"
|
113
|
+
end
|
300
114
|
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
115
|
+
FORMATS = {
|
116
|
+
# HTMLのinput type="date"で使える形式
|
117
|
+
date: "%Y-%m-%d",
|
118
|
+
datetime: "%Y-%m-%dT%H:%M",
|
119
|
+
}.freeze
|
120
|
+
# 型のキャスト指定があってもキャストはしないメソッド
|
121
|
+
CAST_IGNORE_METHODS = [
|
122
|
+
# id は数値のほうが良いため
|
123
|
+
:id,
|
124
|
+
].freeze
|
125
|
+
# @param [Array<Symbol>] Setting
|
126
|
+
def __build_props_attribute__(setting)
|
127
|
+
attribute = {}
|
128
|
+
|
129
|
+
_if = setting.if
|
130
|
+
if _if.present?
|
131
|
+
result = send(_if)
|
132
|
+
return {} unless result
|
133
|
+
end
|
306
134
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
135
|
+
# javascriptは?をキーとして使えないので削除しつつ、isXxx形式に変換する
|
136
|
+
key = setting.name
|
137
|
+
if key.to_s.end_with?("?")
|
138
|
+
key = key.to_s.gsub("?", "").to_sym
|
139
|
+
key = "is_#{key}".to_sym unless key.start_with?("is_")
|
140
|
+
end
|
312
141
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
142
|
+
# valはこの後の工程で書き換えの可能性があるため注意
|
143
|
+
val = nil
|
144
|
+
method = setting.method
|
145
|
+
to = setting.to
|
146
|
+
if setting.block.present?
|
147
|
+
val = setting.block.call(model, params)
|
148
|
+
else
|
149
|
+
if to == :self
|
150
|
+
if method.is_a?(Proc)
|
151
|
+
val = method.call(self)
|
320
152
|
else
|
321
|
-
|
322
|
-
val = method.call(send(to))
|
323
|
-
else
|
324
|
-
val = send(to)&.send(method)
|
325
|
-
end
|
153
|
+
val = send(method)
|
326
154
|
end
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
val = val.strftime(FORMATS[:date])
|
331
|
-
when ActiveSupport::TimeWithZone, Time
|
332
|
-
# 日付系であればjsで使えるようにhtmlに変換する
|
333
|
-
val = val.strftime(FORMATS[:datetime])
|
155
|
+
else
|
156
|
+
if method.is_a?(Proc)
|
157
|
+
val = method.call(send(to))
|
334
158
|
else
|
335
|
-
|
336
|
-
val = cast.to_s.split(".").inject(val) do |lval, cast_method|
|
337
|
-
lval.send(cast_method)
|
338
|
-
end
|
339
|
-
end
|
159
|
+
val = send(to)&.send(method)
|
340
160
|
end
|
161
|
+
end
|
162
|
+
end
|
341
163
|
|
342
|
-
|
343
|
-
|
164
|
+
case val
|
165
|
+
when Date
|
166
|
+
val = val.strftime(FORMATS[:date])
|
167
|
+
when ActiveSupport::TimeWithZone, Time
|
168
|
+
# 日付系であればjsで使えるようにhtmlに変換する
|
169
|
+
val = val.strftime(FORMATS[:datetime])
|
170
|
+
else
|
171
|
+
if setting.cast.present? && CAST_IGNORE_METHODS.exclude?(key)
|
172
|
+
val = setting.cast.to_s.split(".").inject(val) do |lval, cast_method|
|
173
|
+
lval.send(cast_method)
|
344
174
|
end
|
175
|
+
end
|
176
|
+
end
|
345
177
|
|
346
|
-
|
178
|
+
if val.nil? && setting.set_default?
|
179
|
+
val = setting.default
|
180
|
+
end
|
347
181
|
|
348
|
-
|
349
|
-
end
|
182
|
+
attribute[key] = val
|
350
183
|
|
351
|
-
|
352
|
-
|
353
|
-
mark = {}
|
354
|
-
if ENV["RAILS_ENV"] == "test"
|
355
|
-
mark[:props_class_name] = self.class.name
|
356
|
-
if self.class.union.present?
|
357
|
-
mark[:union_type_name] = self.class.union
|
358
|
-
end
|
359
|
-
end
|
184
|
+
attribute
|
185
|
+
end
|
360
186
|
|
361
|
-
|
187
|
+
# テストモードのときはどのPropsを実行したかを判定できるように属性をつけたします
|
188
|
+
def __build_props_class_mark__
|
189
|
+
mark = {}
|
190
|
+
if ENV["RAILS_ENV"] == "test"
|
191
|
+
mark[:props_class_name] = self.class.name
|
192
|
+
if self.class.inherit_props_class.present?
|
193
|
+
mark[:inherit_props_class_name] = self.class.inherit_props_class.try(:name) || self.class.inherit_props_class.to_s
|
362
194
|
end
|
195
|
+
end
|
363
196
|
|
364
|
-
|
365
|
-
|
366
|
-
return {} if self.class.skip_meta_mark
|
197
|
+
mark
|
198
|
+
end
|
367
199
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
self
|
372
|
-
end
|
200
|
+
# どのモデルのPropsかを判定できるように属性をつけたします
|
201
|
+
def __build_props_meta_mark__
|
202
|
+
return {} if self.class.skip_meta_mark
|
373
203
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
end
|
204
|
+
type_target = begin
|
205
|
+
model
|
206
|
+
rescue StandardError
|
207
|
+
self
|
379
208
|
end
|
209
|
+
|
210
|
+
{
|
211
|
+
type: type_target.class.try(:name).to_s,
|
212
|
+
model_name: type_target.class.try(:name).try(:demodulize).to_s,
|
213
|
+
}
|
380
214
|
end
|
381
215
|
end
|
@@ -1,24 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# @example
|
4
|
-
# [app/models/props/base.rb]
|
5
|
-
# class Props::Base < ChoronSupport::Props::Base
|
6
|
-
# include ChoronSupport::Props::Attributes
|
7
|
-
# end
|
8
|
-
# そして各種モデルのPropsは上記のBaseクラスを継承して作成されています
|
9
|
-
# @example
|
10
|
-
# [app/models/props/user.rb]
|
11
|
-
# class Props::User < Props::Base
|
12
|
-
# attributes :id, :name, :age
|
13
|
-
# end
|
14
|
-
# [app/models/props/users/secure.rb]
|
15
|
-
# class Props::Users::Secure < Props::Base
|
16
|
-
# # secure側はageは非表示
|
17
|
-
# attributes :id, :name
|
18
|
-
# end
|
1
|
+
require_relative "./attributes"
|
2
|
+
|
19
3
|
module ChoronSupport
|
20
4
|
module Props
|
21
5
|
class Base
|
6
|
+
include ChoronSupport::Props::Attributes
|
7
|
+
|
22
8
|
# @param [ActiveRecord::Base] model Props対象のモデルのインスタンス
|
23
9
|
# @param [Hash] params その他のパラメータ
|
24
10
|
def initialize(model, params = {})
|
@@ -26,15 +12,20 @@ module ChoronSupport
|
|
26
12
|
@params = params
|
27
13
|
end
|
28
14
|
|
29
|
-
# 継承先で実装されることを想定しています
|
30
|
-
# ChoronSupport::Props::Attributes を読み込んでいるときは、そちらでオーバーライドされています
|
31
|
-
def as_props
|
32
|
-
raise NotImplementedError
|
33
|
-
end
|
34
|
-
|
35
15
|
private
|
36
16
|
|
37
|
-
|
17
|
+
# @override
|
18
|
+
def model
|
19
|
+
@model
|
20
|
+
end
|
21
|
+
|
22
|
+
# @override
|
23
|
+
def params
|
24
|
+
@params
|
25
|
+
end
|
38
26
|
end
|
39
27
|
end
|
40
28
|
end
|
29
|
+
|
30
|
+
__END__
|
31
|
+
abcd
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class ChoronSupport::Props::Private::Setting
|
2
|
+
class Error < StandardError; end
|
3
|
+
# デフォルト値を設定しない場合に使う値
|
4
|
+
NO_DEFAULT = Object.new.freeze
|
5
|
+
private_constant :NO_DEFAULT
|
6
|
+
SETTING_ATTRIBUTES = %i[method name to cast default if block].freeze
|
7
|
+
private_constant :SETTING_ATTRIBUTES
|
8
|
+
|
9
|
+
SETTING_ATTRIBUTES.each {|atr_name| attr_reader atr_name }
|
10
|
+
|
11
|
+
def initialize(params)
|
12
|
+
# 不正なオプションがあれば例外を発生させる
|
13
|
+
if (params.keys - SETTING_ATTRIBUTES).present?
|
14
|
+
raise Error, "invalid params: #{(params.keys - SETTING_ATTRIBUTES).join(", ")}, valid params are #{SETTING_ATTRIBUTES.join(", ")}"
|
15
|
+
end
|
16
|
+
|
17
|
+
@method = params[:method]
|
18
|
+
@name = params[:name] || @method
|
19
|
+
@to = params[:to] || :model
|
20
|
+
@cast = params[:cast]
|
21
|
+
@default = params[:default] || NO_DEFAULT
|
22
|
+
@if = params[:if] || nil
|
23
|
+
@block = params[:block] || nil
|
24
|
+
|
25
|
+
check_params!
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_default?
|
29
|
+
self.default != NO_DEFAULT
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def check_params!
|
35
|
+
if name.blank?
|
36
|
+
raise Error, "name is required"
|
37
|
+
end
|
38
|
+
if method.blank?
|
39
|
+
raise Error, "method is required"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# @deprecated
|
2
|
+
class ChoronSupport::Props::Private::TypeBuilder
|
3
|
+
RESULT = Struct.new(:file_path, :body, :type_name, :attributes)
|
4
|
+
|
5
|
+
# @return [String]
|
6
|
+
# @memo
|
7
|
+
# 必要に応じてoverrideしてください
|
8
|
+
def self.output_dir
|
9
|
+
"app/javascript/types/props"
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [String]
|
13
|
+
# @memo
|
14
|
+
# 必要に応じてoverrideしてください
|
15
|
+
def self.file_path(props_class)
|
16
|
+
self.default_build_file_path(props_class)
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(props_class)
|
20
|
+
@body_buffer = []
|
21
|
+
@attributes_buffer = []
|
22
|
+
@props_class = props_class
|
23
|
+
end
|
24
|
+
|
25
|
+
# 設定値やクラス名からTypeScriptの型を生成する
|
26
|
+
# @return [RESULT]
|
27
|
+
# @example
|
28
|
+
# class Foo::Bars::Staff < Props::Base
|
29
|
+
# attribute :id, type: "number | null"
|
30
|
+
# attribute :name, type: "string"
|
31
|
+
# attribute :is_super, type: "boolean",
|
32
|
+
# attribute :license_names, type: "Array<string>"
|
33
|
+
# end
|
34
|
+
# builder = ChoronSupport::Props::Private::TypeBuilder.new(Foo::Bars::Staff)
|
35
|
+
# builder.build
|
36
|
+
# ####====#####
|
37
|
+
# type Foo_Bars_StaffProps = {
|
38
|
+
# id: number | null,
|
39
|
+
# name: string,
|
40
|
+
# is_super: boolean,
|
41
|
+
# license_names: Array<string>,
|
42
|
+
# type: "Foo::Bars::Staff"
|
43
|
+
# modelName: "Foo::Bar"
|
44
|
+
# }
|
45
|
+
# ####====#####
|
46
|
+
def build
|
47
|
+
set_type_buffer
|
48
|
+
|
49
|
+
file_path = self.class.file_path(props_class)
|
50
|
+
body = body_buffer.join("\n")
|
51
|
+
type_name = build_type_name(props_class)
|
52
|
+
attributes = attributes_buffer.join("\n")
|
53
|
+
|
54
|
+
RESULT.new(file_path, body, type_name, attributes)
|
55
|
+
end
|
56
|
+
|
57
|
+
# buildされたTypeScriptの型をファイルに出力する
|
58
|
+
# @return [RESULT]
|
59
|
+
def generate
|
60
|
+
result = self.build
|
61
|
+
|
62
|
+
# 出力用のディレクトリがなければ作成する
|
63
|
+
if !Dir.exist?(self.class.output_dir)
|
64
|
+
FileUtils.mkdir_p(self.class.output_dir)
|
65
|
+
end
|
66
|
+
|
67
|
+
# ファイルを作成する
|
68
|
+
File.open(result.file_path, "w") do |f|
|
69
|
+
f.puts(result.body)
|
70
|
+
end
|
71
|
+
|
72
|
+
result
|
73
|
+
end
|
74
|
+
|
75
|
+
def __body_buffer__
|
76
|
+
body_buffer
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
attr_reader :props_class, :body_buffer, :attributes_buffer
|
82
|
+
|
83
|
+
def self.default_build_file_path(props_class)
|
84
|
+
# 分かりやすいようにそのままtypenameをファイル名にする
|
85
|
+
file_name = props_class.name.gsub("::", "_") + ".d.ts"
|
86
|
+
|
87
|
+
if defined?(Rails) && Rails.root.present?
|
88
|
+
Rails.root.join(self.output_dir, file_name).to_s
|
89
|
+
else
|
90
|
+
File.join(self.output_dir, file_name)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def set_type_buffer
|
95
|
+
body_buffer << "type #{build_type_name(props_class)} = {"
|
96
|
+
attributes_buffer = "{"
|
97
|
+
|
98
|
+
build_attributes(props_class).each do |attr_val|
|
99
|
+
body_buffer << " #{attr_val}"
|
100
|
+
attributes_buffer << " #{attr_val}"
|
101
|
+
end
|
102
|
+
|
103
|
+
body_buffer << "}"
|
104
|
+
attributes_buffer << "}"
|
105
|
+
end
|
106
|
+
|
107
|
+
def build_attributes(props_class)
|
108
|
+
attributes = []
|
109
|
+
props_class.settings.each do |setting|
|
110
|
+
attributes << build_attribute(setting)
|
111
|
+
end
|
112
|
+
|
113
|
+
attributes
|
114
|
+
end
|
115
|
+
|
116
|
+
def build_attribute(setting)
|
117
|
+
name = setting.name
|
118
|
+
_if = setting.if
|
119
|
+
name_val = "#{name}#{_if ? "?" : ""}"
|
120
|
+
|
121
|
+
type = setting.type
|
122
|
+
|
123
|
+
"#{name_val}: #{type}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def build_type_name(props_class)
|
127
|
+
props_class.name.gsub("::", "_")
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# @deprecated
|
2
|
+
class ChoronSupport::Props::Private::TypeGenerator
|
3
|
+
def run
|
4
|
+
start_output
|
5
|
+
|
6
|
+
results = []
|
7
|
+
targer_props.each do |props_class|
|
8
|
+
result = builder_class.new(props_class).generate
|
9
|
+
results << result
|
10
|
+
end
|
11
|
+
|
12
|
+
output_results(results)
|
13
|
+
|
14
|
+
results
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def target_props_class?(props_class)
|
20
|
+
props_class.respond_to?(:skip_typescript) && props_class.respond_to?(:settings)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.props_base
|
24
|
+
Props::Base
|
25
|
+
end
|
26
|
+
|
27
|
+
def targer_props
|
28
|
+
props_list = []
|
29
|
+
self.class.props_base.descendants.each do |props_class|
|
30
|
+
next unless target_props_class?(props_class)
|
31
|
+
next if props_class.skip_typescript
|
32
|
+
|
33
|
+
props_list << props_class
|
34
|
+
end
|
35
|
+
|
36
|
+
props_list
|
37
|
+
end
|
38
|
+
|
39
|
+
def builder_class
|
40
|
+
ChoronSupport::Props::Private::TypeBuilder
|
41
|
+
end
|
42
|
+
|
43
|
+
def start_output
|
44
|
+
log("Start generating TypeScript Props...: #{targer_props.size}")
|
45
|
+
end
|
46
|
+
|
47
|
+
def output_results(results)
|
48
|
+
log("Generated TypeScript Props: #{results.size}")
|
49
|
+
log("for...")
|
50
|
+
results.each do |result|
|
51
|
+
log(" #{result.file_path}")
|
52
|
+
end
|
53
|
+
|
54
|
+
log("Done.")
|
55
|
+
end
|
56
|
+
|
57
|
+
def log(str)
|
58
|
+
@logger_method ||= defined?(Rails) ? Rails.logger.method(:info) : method(:puts)
|
59
|
+
|
60
|
+
@logger_method.call(str)
|
61
|
+
end
|
62
|
+
end
|
data/lib/choron_support.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ChoronSupport
|
4
|
+
module Domains; end
|
5
|
+
module Forms; end
|
6
|
+
module Props; end
|
7
|
+
module Props::Private; end
|
8
|
+
module Queries; end
|
9
|
+
|
4
10
|
SUPPORT_FILES = {
|
5
11
|
domains: "choron_support/domain_delegate",
|
6
12
|
queries: "choron_support/scope_query",
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: choron_support
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- mksava
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -212,6 +212,9 @@ files:
|
|
212
212
|
- lib/choron_support/props/base.rb
|
213
213
|
- lib/choron_support/props/ext/hash.rb
|
214
214
|
- lib/choron_support/props/ext/relation.rb
|
215
|
+
- lib/choron_support/props/private/setting.rb
|
216
|
+
- lib/choron_support/props/private/type_builder.rb
|
217
|
+
- lib/choron_support/props/private/type_generator.rb
|
215
218
|
- lib/choron_support/queries/base.rb
|
216
219
|
- lib/choron_support/scope_query.rb
|
217
220
|
- lib/choron_support/set_mask_for.rb
|