choron_support 0.1.8 → 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 +1 -1
- data/Gemfile.lock +1 -1
- data/Makefile +4 -1
- data/README.md +34 -75
- data/docs/idea.md +24 -0
- data/docs/props.md +35 -0
- data/lib/choron_support/as_props.rb +14 -17
- data/lib/choron_support/props/attributes.rb +215 -0
- data/lib/choron_support/props/base.rb +21 -7
- 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 +8 -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
CHANGED
data/Gemfile.lock
CHANGED
data/Makefile
CHANGED
@@ -12,7 +12,7 @@ run:
|
|
12
12
|
docker-compose up
|
13
13
|
|
14
14
|
web:
|
15
|
-
eval "docker exec -it `docker ps | grep
|
15
|
+
eval "docker exec -it `docker ps | grep choron_support-web- | cut -d' ' -f1` /bin/bash"
|
16
16
|
|
17
17
|
clean:
|
18
18
|
docker system prune
|
@@ -30,5 +30,8 @@ d-build-no-cache:
|
|
30
30
|
install:
|
31
31
|
docker-compose run web bash -c "gem uninstall bundler && gem install bundler -v 2.3.13 && bundle install && yarn install"
|
32
32
|
|
33
|
+
spec-db-create:
|
34
|
+
DB_CREATE=true bundle exec rspec spec/*
|
35
|
+
|
33
36
|
spec-table-create:
|
34
37
|
bundle exec ridgepole --config ./spec/rails/config/database.yml --file ./spec/rails/config/schemafile --apply
|
data/README.md
CHANGED
@@ -25,52 +25,9 @@ ChoronSupport.using :all
|
|
25
25
|
|
26
26
|
* 必要に応じて各種モジュールをincludeすることで利用できます
|
27
27
|
|
28
|
-
###
|
28
|
+
### Props
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
名前の `props` の由来は `React` からきています。
|
33
|
-
由来の通り、RailsからJS側へ値を渡す際にオブジェクトをJSON化するために作られました。
|
34
|
-
|
35
|
-
#### 使い方
|
36
|
-
|
37
|
-
* ActiveRecord
|
38
|
-
|
39
|
-
```ruby
|
40
|
-
class User < ApplicationRecord
|
41
|
-
include ChoronSupport::AsProps
|
42
|
-
# id: bigint
|
43
|
-
# name: string
|
44
|
-
end
|
45
|
-
|
46
|
-
# ActiveRecordから利用
|
47
|
-
User.new.as_props
|
48
|
-
#=> { id: nil, name: nil }
|
49
|
-
|
50
|
-
User.create(id: 1, name: "tarou")
|
51
|
-
|
52
|
-
User.find(1).as_props
|
53
|
-
#=> { id: 1, name: "tarou" }
|
54
|
-
|
55
|
-
# ActiveRecord::Relationからでも利用できます
|
56
|
-
users = User.all.as_props
|
57
|
-
#=> [
|
58
|
-
# { id: 1, name: "tarou" },
|
59
|
-
# ]
|
60
|
-
|
61
|
-
class Props::User < ChoronSupport::Props::Base
|
62
|
-
def as_props
|
63
|
-
model
|
64
|
-
.as_json
|
65
|
-
.merge(
|
66
|
-
name: "tanaka #{model.name}"
|
67
|
-
)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
user = User.find(1).as_props
|
72
|
-
#=> { id: 1, name: "tanaka tarou" }
|
73
|
-
```
|
30
|
+
* [Props ドキュメント](./docs/props.md)を参照ください
|
74
31
|
|
75
32
|
### Mask
|
76
33
|
|
@@ -81,15 +38,27 @@ user = User.find(1).as_props
|
|
81
38
|
|
82
39
|
### Domain
|
83
40
|
|
84
|
-
|
41
|
+
モデルの処理をメソッド単位で別クラスに委譲するための仕組みです。
|
42
|
+
クラスメソッド、インスタンスメソッドの両方で利用できます。
|
43
|
+
|
44
|
+
* 詳細な実装と使い方は [こちら](./lib/choron_support/domain_delegate.rb) を確認してください。
|
85
45
|
|
86
46
|
### Forms
|
87
47
|
|
88
|
-
|
48
|
+
ControllerでFormクラスのインスタンスを簡単に生成するための仕組みです。
|
49
|
+
|
50
|
+
* 詳細な実装と使い方はいかを参照してください。
|
51
|
+
* [build_form](./lib/choron_support/build_form.rb)
|
52
|
+
* ControllerからFormクラスのインスタンスを簡単に生成するメソッドです
|
53
|
+
* [ChoronSupport::Forms::Base](lib/choron_support/forms/base.rb)
|
54
|
+
* Formクラスのベースとなるクラスです
|
89
55
|
|
90
56
|
### Query
|
91
57
|
|
92
|
-
|
58
|
+
モデルのscope処理を別クラスに異常するための仕組みです。
|
59
|
+
もともと存在する `queryパターン` を簡単に使えるようにしたものです。
|
60
|
+
|
61
|
+
* 詳細な実装と使い方は [こちら](./lib/choron_support/scope_query.rb) を確認してください。
|
93
62
|
|
94
63
|
## Develop
|
95
64
|
|
@@ -101,46 +70,36 @@ Dockerを起動することで開発環境が整います
|
|
101
70
|
make d-build
|
102
71
|
```
|
103
72
|
|
104
|
-
* Docker
|
73
|
+
* Dockerコンテナの起動
|
105
74
|
|
106
75
|
```bash
|
107
76
|
make run
|
108
77
|
```
|
109
78
|
|
110
|
-
*
|
111
|
-
* spec/spec_helper.rb を開いて下記にあるDBの作成/Tableの作成のフラグを true に書き換えてから、テストを実行してください
|
112
|
-
* `bin/rspec spec`
|
79
|
+
* コンテナ内部に入る
|
113
80
|
|
81
|
+
```bash
|
82
|
+
make web
|
83
|
+
```
|
114
84
|
|
115
|
-
|
116
|
-
|
117
|
-
Railsにはこれまで多くのリファクタリング手法が、多くの人々から提案されてきました。
|
118
|
-
その中で本Gemは以下の思想をサポートするようにしています
|
119
|
-
|
120
|
-
* レイヤーを多く作らずにModelへ処理を凝集する
|
121
|
-
* Railsがデフォルトで用意してくれている `controllers`, `models`, `views` といったレイヤーのみをできるだけ使い、独自のレイヤーを**あまり**追加しない
|
122
|
-
* Modelの見通しをよくするためにファイル内の処理を委譲させる
|
123
|
-
* 委譲先のクラスはModel以外からは直接呼び出さない(必ずModelにpublicなメソッドを経由させる)
|
124
|
-
|
125
|
-
これによりドメインの知識をModelレイヤーに集めつつ、
|
126
|
-
中規模以上のシステムになってきた際のファットモデルによる問題を解消する取り組みを行います
|
127
|
-
|
128
|
-
### 具体的な取り組み
|
129
|
-
|
130
|
-
Modelの中で行われる処理の中でも、本Gemは以下の処理を簡易に別クラスへ委譲させます
|
131
|
-
|
132
|
-
* ビジネスロジック・ドメインロジック
|
133
|
-
* DBへのアクセス・取得
|
134
|
-
* データを表示するための加工(json化)
|
85
|
+
* テスト用のDBおよびテーブルの作成
|
135
86
|
|
87
|
+
※Dockerコンテナ内部で実行してくださ
|
136
88
|
|
137
|
-
|
89
|
+
```bash
|
90
|
+
make spec-db-create
|
91
|
+
make spec-table-create
|
92
|
+
```
|
138
93
|
|
139
|
-
|
94
|
+
* RSpecの実行
|
140
95
|
|
141
|
-
|
96
|
+
```bash
|
97
|
+
bin/rspec spec
|
98
|
+
```
|
142
99
|
|
100
|
+
## 本Gemの思想
|
143
101
|
|
102
|
+
* [こちら](docs/idea.md)を参照ください
|
144
103
|
|
145
104
|
## License
|
146
105
|
|
data/docs/idea.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# 本Gemの思想
|
2
|
+
|
3
|
+
(記載中...)
|
4
|
+
|
5
|
+
## 概要
|
6
|
+
|
7
|
+
Railsにはこれまで多くのリファクタリング手法が、多くの人々から提案されてきました。
|
8
|
+
その中で本Gemは以下の思想をサポートするようにしています
|
9
|
+
|
10
|
+
* レイヤーを多く作らずにModelへ処理を凝集する
|
11
|
+
* Railsがデフォルトで用意してくれている `controllers`, `models`, `views` といったレイヤーのみをできるだけ使い、独自のレイヤーを**あまり**追加しない
|
12
|
+
* Modelの見通しをよくするためにファイル内の処理を委譲させる
|
13
|
+
* 委譲先のクラスはModel以外からは直接呼び出さない(必ずModelにpublicなメソッドを経由させる)
|
14
|
+
|
15
|
+
これによりドメインの知識をModelレイヤーに集めつつ、
|
16
|
+
中規模以上のシステムになってきた際のファットモデルによる問題を解消する取り組みを行います
|
17
|
+
|
18
|
+
### 具体的な取り組み
|
19
|
+
|
20
|
+
Modelの中で行われる処理の中でも、本Gemは以下の処理を簡易に別クラスへ委譲させます
|
21
|
+
|
22
|
+
* ビジネスロジック・ドメインロジック
|
23
|
+
* DBへのアクセス・取得
|
24
|
+
* データを表示するための加工(json化)
|
data/docs/props.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Propsについて
|
2
|
+
|
3
|
+
## 概要
|
4
|
+
Propsは一言で言うとローキャメルケースのキーを持つJSON変換用のHashです。
|
5
|
+
|
6
|
+
Choronでは画面側の処理をReact + Typescrip の組み合わせで実現しています。
|
7
|
+
このときに、React側のProps(このpropsはReactの世界のpropsです)に、モデルの値や値クラスの値をJSON形式で渡す必要があります。
|
8
|
+
|
9
|
+
本モジュールのPropsは上記のReactへの値の受け渡しをサポートします。
|
10
|
+
|
11
|
+
一般的なJSON化ツールの違いとしては、Javascript側の記法・慣習を優先するため、
|
12
|
+
JSONのキーをローキャメルケース(fullName, isAdult,など)に自動変換します
|
13
|
+
|
14
|
+
## 使い方
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
class Props::Samples::Foo < Props::Base
|
18
|
+
attribute :id
|
19
|
+
attribute :full_name
|
20
|
+
end
|
21
|
+
|
22
|
+
sample = Sample.new(id: 100, full_name: "mksava")
|
23
|
+
sample.as_props(:foo)
|
24
|
+
#=> { id: 100, fullName: "mksava", type: "Foo", modelName: "Foo" }
|
25
|
+
```
|
26
|
+
|
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/)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative "props/base"
|
2
|
+
require_relative "props/attributes"
|
2
3
|
require_relative "props/ext/relation"
|
3
4
|
require_relative "props/ext/hash"
|
4
5
|
module ChoronSupport
|
@@ -8,20 +9,20 @@ module ChoronSupport
|
|
8
9
|
# @param [Hash] params その他のパラメータ。camel: false を指定すると自動でキャメライズしない。
|
9
10
|
# @return [Hash]
|
10
11
|
def as_props(type_symbol = nil, **params)
|
11
|
-
|
12
|
+
pass_params = params.except(:camel)
|
12
13
|
|
14
|
+
serializer = self.__get_props_class(type_symbol, pass_params)
|
13
15
|
skip_camel = (params[:camel] == false)
|
14
|
-
pass_params = params.except(:camel)
|
15
16
|
if serializer.nil?
|
16
17
|
skip_camel ? self.as_json : self.as_json.as_camel
|
17
18
|
else
|
18
|
-
skip_camel ? serializer.as_props
|
19
|
+
skip_camel ? serializer.as_props : serializer.as_props.as_camel
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
22
23
|
private
|
23
24
|
|
24
|
-
def __get_props_class(type_symbol,
|
25
|
+
def __get_props_class(type_symbol, params)
|
25
26
|
case type_symbol
|
26
27
|
when Symbol, String
|
27
28
|
# 名前空間の例: Serialize::Users
|
@@ -30,26 +31,22 @@ module ChoronSupport
|
|
30
31
|
class_name = type_symbol.to_s.classify
|
31
32
|
# 例: Serialize::Users::FooBar
|
32
33
|
props_class_name = "#{namespace}::#{class_name}"
|
33
|
-
when
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
39
41
|
else
|
40
|
-
raise ArgumentError
|
42
|
+
raise ArgumentError, "invalid type_symbol: #{type_symbol}"
|
41
43
|
end
|
42
44
|
|
43
45
|
begin
|
44
46
|
props_class = props_class_name.constantize
|
45
47
|
|
46
|
-
props_class.new(self)
|
48
|
+
props_class.new(self, params)
|
47
49
|
rescue *rescue_errors
|
48
|
-
# もしmodelを指定しているときはnilを返し、as_jsonを利用させる
|
49
|
-
if type_symbol == :model
|
50
|
-
return nil
|
51
|
-
end
|
52
|
-
|
53
50
|
if type_symbol.blank?
|
54
51
|
raise ChoronSupport::AsProps::NameError, "Props class not found: #{props_class_name}. Please create props class."
|
55
52
|
else
|
@@ -0,0 +1,215 @@
|
|
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
|
7
|
+
|
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
|
46
|
+
|
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
|
58
|
+
|
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
|
63
|
+
|
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
|
70
|
+
|
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
|
87
|
+
|
88
|
+
# @return [Hash] props
|
89
|
+
def as_props
|
90
|
+
_props = {}
|
91
|
+
|
92
|
+
# DSLの設定を設定する
|
93
|
+
self.class.settings.to_a.each do |setting|
|
94
|
+
_props.merge!(__build_props_attribute__(setting))
|
95
|
+
end
|
96
|
+
|
97
|
+
# Classのマークをつける(テスト用)
|
98
|
+
_props.merge!(__build_props_class_mark__)
|
99
|
+
# Modelのマークをつける
|
100
|
+
_props.merge!(__build_props_meta_mark__)
|
101
|
+
|
102
|
+
_props
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def model
|
108
|
+
raise NotImplementedError, "model method is not implemented"
|
109
|
+
end
|
110
|
+
|
111
|
+
def params
|
112
|
+
raise NotImplementedError, "params method is not implemented"
|
113
|
+
end
|
114
|
+
|
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
|
134
|
+
|
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
|
141
|
+
|
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)
|
152
|
+
else
|
153
|
+
val = send(method)
|
154
|
+
end
|
155
|
+
else
|
156
|
+
if method.is_a?(Proc)
|
157
|
+
val = method.call(send(to))
|
158
|
+
else
|
159
|
+
val = send(to)&.send(method)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
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)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
if val.nil? && setting.set_default?
|
179
|
+
val = setting.default
|
180
|
+
end
|
181
|
+
|
182
|
+
attribute[key] = val
|
183
|
+
|
184
|
+
attribute
|
185
|
+
end
|
186
|
+
|
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
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
mark
|
198
|
+
end
|
199
|
+
|
200
|
+
# どのモデルのPropsかを判定できるように属性をつけたします
|
201
|
+
def __build_props_meta_mark__
|
202
|
+
return {} if self.class.skip_meta_mark
|
203
|
+
|
204
|
+
type_target = begin
|
205
|
+
model
|
206
|
+
rescue StandardError
|
207
|
+
self
|
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
|
+
}
|
214
|
+
end
|
215
|
+
end
|
@@ -1,17 +1,31 @@
|
|
1
|
+
require_relative "./attributes"
|
2
|
+
|
1
3
|
module ChoronSupport
|
2
4
|
module Props
|
3
5
|
class Base
|
4
|
-
|
5
|
-
@model = model
|
6
|
-
end
|
6
|
+
include ChoronSupport::Props::Attributes
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
# @param [ActiveRecord::Base] model Props対象のモデルのインスタンス
|
9
|
+
# @param [Hash] params その他のパラメータ
|
10
|
+
def initialize(model, params = {})
|
11
|
+
@model = model
|
12
|
+
@params = params
|
10
13
|
end
|
11
14
|
|
12
15
|
private
|
13
16
|
|
14
|
-
|
17
|
+
# @override
|
18
|
+
def model
|
19
|
+
@model
|
20
|
+
end
|
21
|
+
|
22
|
+
# @override
|
23
|
+
def params
|
24
|
+
@params
|
25
|
+
end
|
15
26
|
end
|
16
27
|
end
|
17
|
-
end
|
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
|
@@ -199,6 +199,8 @@ files:
|
|
199
199
|
- Rakefile
|
200
200
|
- choron_support.gemspec
|
201
201
|
- docker-compose.yml
|
202
|
+
- docs/idea.md
|
203
|
+
- docs/props.md
|
202
204
|
- lib/choron_support.rb
|
203
205
|
- lib/choron_support/as_props.rb
|
204
206
|
- lib/choron_support/build_form.rb
|
@@ -206,9 +208,13 @@ files:
|
|
206
208
|
- lib/choron_support/domains/base.rb
|
207
209
|
- lib/choron_support/forms/base.rb
|
208
210
|
- lib/choron_support/helper.rb
|
211
|
+
- lib/choron_support/props/attributes.rb
|
209
212
|
- lib/choron_support/props/base.rb
|
210
213
|
- lib/choron_support/props/ext/hash.rb
|
211
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
|
212
218
|
- lib/choron_support/queries/base.rb
|
213
219
|
- lib/choron_support/scope_query.rb
|
214
220
|
- lib/choron_support/set_mask_for.rb
|