rider-kick 0.0.13 → 0.0.14

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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +629 -25
  3. data/lib/generators/rider_kick/USAGE +2 -0
  4. data/lib/generators/rider_kick/base_generator.rb +190 -0
  5. data/lib/generators/rider_kick/clean_arch_generator.rb +235 -45
  6. data/lib/generators/rider_kick/clean_arch_generator_engine_spec.rb +359 -0
  7. data/lib/generators/rider_kick/clean_arch_generator_factory_bot_spec.rb +131 -0
  8. data/lib/generators/rider_kick/entity_type_mapping_spec.rb +22 -13
  9. data/lib/generators/rider_kick/errors.rb +42 -0
  10. data/lib/generators/rider_kick/factory_generator.rb +238 -0
  11. data/lib/generators/rider_kick/factory_generator_spec.rb +175 -0
  12. data/lib/generators/rider_kick/repositories_contract_spec.rb +95 -22
  13. data/lib/generators/rider_kick/scaffold_generator.rb +377 -62
  14. data/lib/generators/rider_kick/scaffold_generator_builder_uploaders_spec.rb +119 -14
  15. data/lib/generators/rider_kick/scaffold_generator_conditional_filtering_spec.rb +820 -0
  16. data/lib/generators/rider_kick/scaffold_generator_contracts_spec.rb +37 -10
  17. data/lib/generators/rider_kick/scaffold_generator_contracts_with_scope_spec.rb +40 -11
  18. data/lib/generators/rider_kick/scaffold_generator_engine_spec.rb +221 -0
  19. data/lib/generators/rider_kick/scaffold_generator_idempotent_spec.rb +38 -13
  20. data/lib/generators/rider_kick/scaffold_generator_list_spec_format_spec.rb +153 -0
  21. data/lib/generators/rider_kick/scaffold_generator_rspec_spec.rb +347 -0
  22. data/lib/generators/rider_kick/scaffold_generator_success_spec.rb +31 -12
  23. data/lib/generators/rider_kick/scaffold_generator_with_scope_spec.rb +32 -11
  24. data/lib/generators/rider_kick/structure_generator.rb +154 -43
  25. data/lib/generators/rider_kick/structure_generator_comprehensive_spec.rb +598 -0
  26. data/lib/generators/rider_kick/structure_generator_engine_spec.rb +279 -0
  27. data/lib/generators/rider_kick/structure_generator_spec.rb +3 -3
  28. data/lib/generators/rider_kick/structure_generator_success_spec.rb +33 -5
  29. data/lib/generators/rider_kick/structure_generator_unit_spec.rb +2202 -0
  30. data/lib/generators/rider_kick/templates/.rubocop.yml +5 -4
  31. data/lib/generators/rider_kick/templates/config/initializers/version.rb.tt +1 -1
  32. data/lib/generators/rider_kick/templates/db/migrate/20220613145533_init_database.rb +1 -1
  33. data/lib/generators/rider_kick/templates/db/structures/example.yaml.tt +140 -66
  34. data/lib/generators/rider_kick/templates/domains/core/builders/builder.rb.tt +36 -10
  35. data/lib/generators/rider_kick/templates/domains/core/builders/builder_spec.rb.tt +219 -0
  36. data/lib/generators/rider_kick/templates/domains/core/builders/error.rb.tt +2 -2
  37. data/lib/generators/rider_kick/templates/domains/core/builders/pagination.rb.tt +2 -2
  38. data/lib/generators/rider_kick/templates/domains/core/entities/entity.rb.tt +32 -14
  39. data/lib/generators/rider_kick/templates/domains/core/entities/error.rb.tt +1 -1
  40. data/lib/generators/rider_kick/templates/domains/core/entities/pagination.rb.tt +1 -1
  41. data/lib/generators/rider_kick/templates/domains/core/repositories/abstract_repository.rb.tt +4 -4
  42. data/lib/generators/rider_kick/templates/domains/core/repositories/create.rb.tt +2 -2
  43. data/lib/generators/rider_kick/templates/domains/core/repositories/create_spec.rb.tt +78 -0
  44. data/lib/generators/rider_kick/templates/domains/core/repositories/destroy.rb.tt +2 -2
  45. data/lib/generators/rider_kick/templates/domains/core/repositories/destroy_spec.rb.tt +88 -0
  46. data/lib/generators/rider_kick/templates/domains/core/repositories/fetch_by_id.rb.tt +3 -3
  47. data/lib/generators/rider_kick/templates/domains/core/repositories/fetch_by_id_spec.rb.tt +62 -0
  48. data/lib/generators/rider_kick/templates/domains/core/repositories/list.rb.tt +13 -8
  49. data/lib/generators/rider_kick/templates/domains/core/repositories/list_spec.rb.tt +190 -0
  50. data/lib/generators/rider_kick/templates/domains/core/repositories/update.rb.tt +4 -4
  51. data/lib/generators/rider_kick/templates/domains/core/repositories/update_spec.rb.tt +119 -0
  52. data/lib/generators/rider_kick/templates/domains/core/use_cases/contract/default.rb.tt +1 -1
  53. data/lib/generators/rider_kick/templates/domains/core/use_cases/contract/pagination.rb.tt +1 -1
  54. data/lib/generators/rider_kick/templates/domains/core/use_cases/create.rb.tt +3 -7
  55. data/lib/generators/rider_kick/templates/domains/core/use_cases/create_spec.rb.tt +71 -0
  56. data/lib/generators/rider_kick/templates/domains/core/use_cases/destroy.rb.tt +3 -7
  57. data/lib/generators/rider_kick/templates/domains/core/use_cases/destroy_spec.rb.tt +62 -0
  58. data/lib/generators/rider_kick/templates/domains/core/use_cases/fetch_by_id.rb.tt +3 -7
  59. data/lib/generators/rider_kick/templates/domains/core/use_cases/fetch_by_id_spec.rb.tt +62 -0
  60. data/lib/generators/rider_kick/templates/domains/core/use_cases/get_version.rb.tt +2 -2
  61. data/lib/generators/rider_kick/templates/domains/core/use_cases/list.rb.tt +3 -7
  62. data/lib/generators/rider_kick/templates/domains/core/use_cases/list_spec.rb.tt +64 -0
  63. data/lib/generators/rider_kick/templates/domains/core/use_cases/update.rb.tt +3 -7
  64. data/lib/generators/rider_kick/templates/domains/core/use_cases/update_spec.rb.tt +73 -0
  65. data/lib/generators/rider_kick/templates/domains/core/utils/abstract_utils.rb.tt +3 -3
  66. data/lib/generators/rider_kick/templates/domains/core/utils/request_methods.rb.tt +1 -1
  67. data/lib/generators/rider_kick/templates/env.development +1 -1
  68. data/lib/generators/rider_kick/templates/env.production +1 -1
  69. data/lib/generators/rider_kick/templates/env.test +1 -1
  70. data/lib/generators/rider_kick/templates/models/{application_record.rb → application_record.rb.tt} +3 -1
  71. data/lib/generators/rider_kick/templates/models/model_spec.rb.tt +68 -0
  72. data/lib/generators/rider_kick/templates/spec/factories/.gitkeep +19 -0
  73. data/lib/generators/rider_kick/templates/spec/factories/factory.rb.tt +8 -0
  74. data/lib/generators/rider_kick/templates/spec/rails_helper.rb +2 -0
  75. data/lib/generators/rider_kick/templates/spec/support/class_stubber.rb +148 -0
  76. data/lib/generators/rider_kick/templates/spec/support/factory_bot.rb +34 -0
  77. data/lib/generators/rider_kick/templates/spec/support/faker.rb +61 -0
  78. data/lib/rider-kick.rb +8 -6
  79. data/lib/rider_kick/builders/abstract_active_record_entity_builder_spec.rb +644 -0
  80. data/lib/rider_kick/configuration.rb +238 -0
  81. data/lib/rider_kick/configuration_engine_spec.rb +377 -0
  82. data/lib/rider_kick/entities/failure_details.rb +1 -1
  83. data/lib/rider_kick/entities/failure_details_spec.rb +1 -1
  84. data/lib/rider_kick/matchers/use_case_result.rb +1 -1
  85. data/lib/rider_kick/use_cases/abstract_use_case.rb +1 -1
  86. data/lib/rider_kick/version.rb +1 -1
  87. metadata +129 -8
  88. data/CHANGELOG.md +0 -5
@@ -0,0 +1,279 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'tmpdir'
5
+ require 'fileutils'
6
+ require 'generators/rider_kick/structure_generator'
7
+
8
+ RSpec.describe 'rider_kick:structure generator with engine and domain options' do
9
+ let(:klass) { RiderKick::Structure }
10
+
11
+ context 'with --domain option' do
12
+ it 'uses domain scope for configuration' do
13
+ Dir.mktmpdir do |dir|
14
+ Dir.chdir(dir) do
15
+ # Set domain scope
16
+ RiderKick.configuration.domain_scope = 'admin/'
17
+
18
+ FileUtils.mkdir_p(RiderKick.configuration.domains_path)
19
+ FileUtils.mkdir_p('app/models/models')
20
+
21
+ # Stub model classes
22
+ Object.send(:remove_const, :Models) if Object.const_defined?(:Models)
23
+ Object.send(:remove_const, :Column) if Object.const_defined?(:Column)
24
+ module Models; end
25
+
26
+ Column = Struct.new(:name, :type, :sql_type, :null, :default, :precision, :scale, :limit)
27
+ class Models::User
28
+ def self.columns
29
+ [
30
+ Column.new('id', :uuid),
31
+ Column.new('name', :string),
32
+ Column.new('email', :string),
33
+ Column.new('created_at', :datetime),
34
+ Column.new('updated_at', :datetime)
35
+ ]
36
+ end
37
+
38
+ def self.columns_hash
39
+ columns.to_h { |c| [c.name.to_s, Struct.new(:type).new(c.type)] }
40
+ end
41
+
42
+ def self.column_names
43
+ columns.map { |c| c.name.to_s }
44
+ end
45
+ end
46
+
47
+ # Test dengan domain admin
48
+ instance = klass.new(['Models::User', 'actor:admin', 'resource_owner:account', 'resource_owner_id:account_id'])
49
+ allow(instance).to receive(:options).and_return({ domain: 'admin/' })
50
+ instance.generate_use_case
51
+
52
+ # Verifikasi domain scope sudah di-set
53
+ expect(RiderKick.configuration.domain_scope).to eq('admin/')
54
+ expect(RiderKick.configuration.domains_path).to eq('app/domains/admin/')
55
+ expect(File).to exist('db/structures/users_structure.yaml')
56
+ end
57
+ end
58
+ end
59
+
60
+ it 'generates structure file with domain scope api/v1/' do
61
+ Dir.mktmpdir do |dir|
62
+ Dir.chdir(dir) do
63
+ # Set domain scope
64
+ RiderKick.configuration.domain_scope = 'api/v1/'
65
+
66
+ FileUtils.mkdir_p(RiderKick.configuration.domains_path)
67
+ FileUtils.mkdir_p('app/models/models')
68
+
69
+ # Stub model classes
70
+ Object.send(:remove_const, :Models) if Object.const_defined?(:Models)
71
+ Object.send(:remove_const, :Column) if Object.const_defined?(:Column)
72
+ module Models; end
73
+
74
+ Column = Struct.new(:name, :type, :sql_type, :null, :default, :precision, :scale, :limit)
75
+ class Models::Article
76
+ def self.columns
77
+ [
78
+ Column.new('id', :uuid),
79
+ Column.new('title', :string),
80
+ Column.new('content', :text),
81
+ Column.new('published', :boolean),
82
+ Column.new('created_at', :datetime),
83
+ Column.new('updated_at', :datetime)
84
+ ]
85
+ end
86
+
87
+ def self.columns_hash
88
+ columns.to_h { |c| [c.name.to_s, Struct.new(:type).new(c.type)] }
89
+ end
90
+
91
+ def self.column_names
92
+ columns.map { |c| c.name.to_s }
93
+ end
94
+ end
95
+
96
+ # Test dengan domain api/v1
97
+ instance = klass.new(['Models::Article', 'actor:user', 'resource_owner:account', 'resource_owner_id:account_id'])
98
+ allow(instance).to receive(:options).and_return({ domain: 'api/v1/' })
99
+ instance.generate_use_case
100
+
101
+ # Verifikasi domain scope sudah di-set
102
+ expect(RiderKick.configuration.domain_scope).to eq('api/v1/')
103
+ expect(RiderKick.configuration.domains_path).to eq('app/domains/api/v1/')
104
+ expect(File).to exist('db/structures/articles_structure.yaml')
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ context 'without --engine option (main app)' do
111
+ it 'uses main app models path when no engine specified' do
112
+ Dir.mktmpdir do |dir|
113
+ Dir.chdir(dir) do
114
+ FileUtils.mkdir_p(RiderKick.configuration.domains_path)
115
+ FileUtils.mkdir_p('app/models/models')
116
+
117
+ # Stub model classes
118
+ Object.send(:remove_const, :Models) if Object.const_defined?(:Models)
119
+ Object.send(:remove_const, :Column) if Object.const_defined?(:Column)
120
+ module Models; end
121
+
122
+ Column = Struct.new(:name, :type, :sql_type, :null, :default, :precision, :scale, :limit)
123
+ class Models::User
124
+ def self.columns
125
+ [
126
+ Column.new('id', :uuid),
127
+ Column.new('name', :string),
128
+ Column.new('price', :decimal),
129
+ Column.new('created_at', :datetime),
130
+ Column.new('updated_at', :datetime)
131
+ ]
132
+ end
133
+
134
+ def self.columns_hash
135
+ columns.to_h { |c| [c.name.to_s, Struct.new(:type).new(c.type)] }
136
+ end
137
+
138
+ def self.column_names
139
+ columns.map { |c| c.name.to_s }
140
+ end
141
+ end
142
+
143
+ # Reset configuration untuk test
144
+ RiderKick.configuration.engine_name = nil
145
+
146
+ instance = klass.new(['Models::User', 'actor:owner', 'resource_owner:account', 'resource_owner_id:account_id'])
147
+ allow(instance).to receive(:options).and_return({ engine: nil })
148
+ instance.generate_use_case
149
+
150
+ # Verifikasi models_path menggunakan main app
151
+ expect(RiderKick.configuration.models_path).to eq('app/models/models')
152
+ expect(RiderKick.configuration.engine_name).to be_nil
153
+ expect(File).to exist('db/structures/users_structure.yaml')
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ context 'with --engine option' do
160
+ it 'uses engine models path when engine specified' do
161
+ Dir.mktmpdir do |dir|
162
+ Dir.chdir(dir) do
163
+ # Set configuration for test first
164
+ RiderKick.configuration.engine_name = 'Core'
165
+ RiderKick.configuration.domain_scope = 'core/'
166
+
167
+ FileUtils.mkdir_p(RiderKick.configuration.domains_path)
168
+ FileUtils.mkdir_p(RiderKick.configuration.models_path)
169
+ engine_structures_path = "engines/#{RiderKick.configuration.engine_name.downcase}/db/structures"
170
+ FileUtils.mkdir_p(engine_structures_path)
171
+
172
+ # Stub model classes
173
+ Object.send(:remove_const, :Models) if Object.const_defined?(:Models)
174
+ Object.send(:remove_const, :Column) if Object.const_defined?(:Column)
175
+ module Models; end
176
+
177
+ module Models::Core; end
178
+
179
+ Column = Struct.new(:name, :type, :sql_type, :null, :default, :precision, :scale, :limit)
180
+ class Models::Core::User
181
+ def self.columns
182
+ [
183
+ Column.new('id', :uuid),
184
+ Column.new('name', :string),
185
+ Column.new('price', :decimal),
186
+ Column.new('created_at', :datetime),
187
+ Column.new('updated_at', :datetime)
188
+ ]
189
+ end
190
+
191
+ def self.columns_hash
192
+ columns.to_h { |c| [c.name.to_s, Struct.new(:type).new(c.type)] }
193
+ end
194
+
195
+ def self.column_names
196
+ columns.map { |c| c.name.to_s }
197
+ end
198
+ end
199
+
200
+ instance = klass.new(['Models::Core::User', 'actor:owner', 'resource_owner:account', 'resource_owner_id:account_id'])
201
+ instance.generate_use_case
202
+
203
+ # Verifikasi models_path menggunakan engine
204
+ expect(RiderKick.configuration.models_path).to eq('engines/core/app/models/core/models')
205
+ expect(RiderKick.configuration.engine_name).to eq('Core')
206
+ expect(File).to exist('engines/core/db/structures/users_structure.yaml')
207
+ end
208
+ end
209
+ end
210
+
211
+ it 'generates structure file with correct engine configuration' do
212
+ Dir.mktmpdir do |dir|
213
+ Dir.chdir(dir) do
214
+ # Set configuration for test first
215
+ RiderKick.configuration.engine_name = 'Admin'
216
+ RiderKick.configuration.domain_scope = 'core/'
217
+
218
+ FileUtils.mkdir_p(RiderKick.configuration.domains_path)
219
+ FileUtils.mkdir_p(RiderKick.configuration.models_path)
220
+ engine_structures_path = "engines/#{RiderKick.configuration.engine_name.downcase}/db/structures"
221
+ FileUtils.mkdir_p(engine_structures_path)
222
+
223
+ # Stub model classes
224
+ Object.send(:remove_const, :Models) if Object.const_defined?(:Models)
225
+ Object.send(:remove_const, :Column) if Object.const_defined?(:Column)
226
+ module Models; end
227
+
228
+ module Models::Admin; end
229
+
230
+ Column = Struct.new(:name, :type, :sql_type, :null, :default, :precision, :scale, :limit)
231
+ class Models::Admin::Product
232
+ def self.columns
233
+ [
234
+ Column.new('id', :uuid),
235
+ Column.new('name', :string),
236
+ Column.new('price', :decimal),
237
+ Column.new('created_at', :datetime),
238
+ Column.new('updated_at', :datetime)
239
+ ]
240
+ end
241
+
242
+ def self.columns_hash
243
+ columns.to_h { |c| [c.name.to_s, Struct.new(:type).new(c.type)] }
244
+ end
245
+
246
+ def self.column_names
247
+ columns.map { |c| c.name.to_s }
248
+ end
249
+ end
250
+
251
+ instance = klass.new(['Models::Admin::Product', 'actor:admin', 'resource_owner:account', 'resource_owner_id:account_id'])
252
+ instance.generate_use_case
253
+
254
+ # Verifikasi engine name sudah di-set
255
+ expect(RiderKick.configuration.engine_name).to eq('Admin')
256
+ expect(RiderKick.configuration.models_path).to eq('engines/admin/app/models/admin/models')
257
+
258
+ # Verifikasi file structure ter-generate
259
+ expect(File).to exist('engines/admin/db/structures/products_structure.yaml')
260
+ yaml = File.read('engines/admin/db/structures/products_structure.yaml')
261
+ expect(yaml).to include('model: Models::Admin::Product')
262
+ end
263
+ end
264
+ end
265
+
266
+ it 'mengangkat Thor::Error jika engine domains belum ada' do
267
+ Dir.mktmpdir do |dir|
268
+ Dir.chdir(dir) do
269
+ instance = klass.new(['Models::OrderEngine::Order', 'actor:user', 'resource_owner:account', 'resource_owner_id:account_id'])
270
+ allow(instance).to receive(:options).and_return({ engine: 'OrderEngine', domain: 'fulfillment/' })
271
+
272
+ expect {
273
+ instance.generate_use_case
274
+ }.to raise_error(RiderKick::ValidationError, /clean_arch.*--setup/i)
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end
@@ -7,13 +7,13 @@ require 'generators/rider_kick/structure_generator'
7
7
  RSpec.describe 'rider_kick:structure generator' do
8
8
  let(:klass) { RiderKick::Structure }
9
9
 
10
- it 'mengangkat Thor::Error jika app/domains belum ada' do
10
+ it 'mengangkat ValidationError jika app/domains belum ada' do
11
11
  Dir.mktmpdir do |dir|
12
12
  Dir.chdir(dir) do
13
- expect(Dir.exist?('app/domains')).to be false
13
+ expect(Dir.exist?(RiderKick.configuration.domains_path)).to be false
14
14
  instance = klass.new(['Models::User']) # ← instansiasi dengan argumen
15
15
  expect { instance.generate_use_case } # ← panggil task langsung
16
- .to raise_error(Thor::Error, /clean_arch.*--setup/i)
16
+ .to raise_error(RiderKick::ValidationError, /clean_arch.*--setup/i)
17
17
  end
18
18
  end
19
19
  end
@@ -4,7 +4,6 @@ require 'rails/generators'
4
4
  require 'tmpdir'
5
5
  require 'active_support/inflector'
6
6
  require 'generators/rider_kick/structure_generator'
7
- require 'ostruct'
8
7
 
9
8
  RSpec.describe 'rider_kick:structure generator (success)' do
10
9
  let(:klass) { RiderKick::Structure }
@@ -13,21 +12,50 @@ RSpec.describe 'rider_kick:structure generator (success)' do
13
12
  Dir.mktmpdir do |dir|
14
13
  Dir.chdir(dir) do
15
14
  # 1) siapkan struktur minimal Clean Arch
16
- FileUtils.mkdir_p('app/domains/core/use_cases')
15
+ RiderKick.configuration.engine_name = 'OrderEngine'
16
+ RiderKick.configuration.domain_scope = 'order_engine/fulfillment/'
17
+ FileUtils.mkdir_p(RiderKick.configuration.domains_path + '/use_cases')
17
18
  FileUtils.mkdir_p('app/models/models')
19
+ FileUtils.mkdir_p('engines/orderengine/db/structures')
18
20
 
19
21
  # 2) stub namespace & model + metadata kolom
22
+ Object.send(:remove_const, :Models) if Object.const_defined?(:Models)
23
+ Object.send(:remove_const, :Column) if Object.const_defined?(:Column)
24
+ module Models; end
25
+
26
+ Column = Struct.new(:name, :type, :sql_type, :null, :default, :precision, :scale, :limit)
27
+ class Models::User
28
+ def self.columns
29
+ [
30
+ Column.new('id', :uuid),
31
+ Column.new('name', :string),
32
+ Column.new('price', :decimal),
33
+ Column.new('created_at', :datetime),
34
+ Column.new('updated_at', :datetime)
35
+ ]
36
+ end
37
+
38
+ def self.columns_hash
39
+ columns.to_h { |c| [c.name.to_s, Struct.new(:type).new(c.type)] }
40
+ end
41
+
42
+ def self.column_names
43
+ columns.map { |c| c.name.to_s }
44
+ end
45
+ end
20
46
 
21
47
  # 3) jalankan generator
22
- instance = klass.new(['Models::User', 'actor:owner']) # ← pakai token
48
+ instance = klass.new(['Models::User', 'actor:owner', 'resource_owner:account', 'resource_owner_id:account_id']) # ← pakai token
23
49
  instance.generate_use_case
24
50
 
25
51
  # 4) verifikasi file output
26
- expect(File).to exist('db/structures/users_structure.yaml')
27
- yaml = File.read('db/structures/users_structure.yaml')
52
+ expect(File).to exist('engines/orderengine/db/structures/users_structure.yaml')
53
+ yaml = File.read('engines/orderengine/db/structures/users_structure.yaml')
28
54
  expect(yaml).to include('model: Models::User')
29
55
  expect(yaml).to include('resource_name: users')
30
56
  expect(yaml).to include('actor: owner')
57
+ expect(yaml).to include('resource_owner: account')
58
+ expect(yaml).to include('resource_owner_id: account_id')
31
59
  expect(yaml).to include('- name') # field dari kolom
32
60
  expect(yaml).to include('- price')
33
61
  end