choron_support 0.1.8 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e930829f733348afe1938ef857477f85bcc15695e67a31a491e50cd65cb3cc42
4
- data.tar.gz: 41287fc1cdd25345988f03b2ba780e777e8d478413e7f78a907b7931ad362535
3
+ metadata.gz: 5f5e95a92c449d959b9e9522a05e244461e98fb078b5405c93ceb873fdc5c49b
4
+ data.tar.gz: cec6ee644f7813d21e24039aadd30a6047e3a708a48cced1412f593c68be626a
5
5
  SHA512:
6
- metadata.gz: 426090be576fe116a04f6c939c89741868dd5b0b38e495d0fd6b1d613f02af280b8ac6046b0123504323fe2cbb6f7b938e6742863d7dc594626eb4436ccf38a4
7
- data.tar.gz: fb03ba53c19263bffcb80b3aad9853c87cffd53410a8c3246b512625ca549fa7af15ec618ce2fca50d32a055a5fd92572b61fd8cc5e48008a51542b2ad71f476
6
+ metadata.gz: a77b9097a63624fc09fdea17886eb85861c37272058938739692b812fd728d658602020c1b56f0ac3d2ed7f08e51f3b9b234dac28d23e9590620b81ac6794e08
7
+ data.tar.gz: 5ceff8def9787ebf4bdadf656173669f5ef1271cf27e08b41ec22b122b134f1f3e19ef70f59674a3a714eb4061e617fd381ea0a84a8c542b2bb6f9803fc02cba
data/Gemfile CHANGED
@@ -9,4 +9,4 @@ gem "rake", "~> 13.0"
9
9
 
10
10
  gem "rspec", "~> 3.0"
11
11
 
12
- gem "rubocop", "~> 1.21"
12
+ gem "rubocop", "~> 1.21"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- choron_support (0.1.8)
4
+ choron_support (0.1.10)
5
5
  activesupport
6
6
 
7
7
  GEM
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 _web_ | cut -d' ' -f1` /bin/bash"
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
- ### AsProps
28
+ ### Props
29
29
 
30
- `#as_props` はオブジェクトやモデルをhashに変換するものです。
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
- * TODO
41
+ モデルの処理をメソッド単位で別クラスに委譲するための仕組みです。
42
+ クラスメソッド、インスタンスメソッドの両方で利用できます。
43
+
44
+ * 詳細な実装と使い方は [こちら](./lib/choron_support/domain_delegate.rb) を確認してください。
85
45
 
86
46
  ### Forms
87
47
 
88
- * TODO
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
- * TODO
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
- * テスト用のDBおよびテーブルの作成 & RSpecの実行
111
- * spec/spec_helper.rb を開いて下記にあるDBの作成/Tableの作成のフラグを true に書き換えてから、テストを実行してください
112
- * `bin/rspec spec`
79
+ * コンテナ内部に入る
113
80
 
81
+ ```bash
82
+ make web
83
+ ```
114
84
 
115
- ## 本Gemの思想
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
- 以下、TODO
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
- serializer = self.__get_props_class(type_symbol, **params)
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(**pass_params) : serializer.as_props(**pass_params).as_camel
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, **params)
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 nil
34
- namespace = "Props"
35
- # 例: User / Master::Plan
36
- class_name = self.class.to_s
37
- # 例: Props::User
38
- props_class_name = "#{namespace}::#{class_name}"
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
- def initialize(model)
5
- @model = model
6
- end
6
+ include ChoronSupport::Props::Attributes
7
7
 
8
- def as_props
9
- raise NotImplementedError
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
- attr_reader :model
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ChoronSupport
4
- VERSION = "0.1.8"
4
+ VERSION = "0.1.10"
5
5
  end
@@ -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.8
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: 2023-06-03 00:00:00.000000000 Z
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