rider-kick 0.0.12 → 0.0.13

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -8
  3. data/lib/generators/rider_kick/clean_arch_generator.rb +1 -0
  4. data/lib/generators/rider_kick/entity_type_mapping_spec.rb +52 -0
  5. data/lib/generators/rider_kick/repositories_contract_spec.rb +62 -0
  6. data/lib/generators/rider_kick/scaffold_generator_builder_uploaders_spec.rb +54 -0
  7. data/lib/generators/rider_kick/scaffold_generator_contracts_spec.rb +69 -0
  8. data/lib/generators/rider_kick/scaffold_generator_contracts_with_scope_spec.rb +54 -0
  9. data/lib/generators/rider_kick/scaffold_generator_idempotent_spec.rb +59 -0
  10. data/lib/generators/rider_kick/scaffold_generator_success_spec.rb +82 -0
  11. data/lib/generators/rider_kick/scaffold_generator_with_scope_spec.rb +55 -0
  12. data/lib/generators/rider_kick/structure_generator.rb +33 -0
  13. data/lib/generators/rider_kick/structure_generator_spec.rb +20 -0
  14. data/lib/generators/rider_kick/structure_generator_success_spec.rb +36 -0
  15. data/lib/generators/rider_kick/templates/db/structures/example.yaml.tt +114 -82
  16. data/lib/generators/rider_kick/templates/domains/core/repositories/abstract_repository.rb.tt +0 -12
  17. data/lib/generators/rider_kick/templates/domains/core/repositories/list.rb.tt +2 -2
  18. data/lib/generators/rider_kick/templates/domains/core/utils/abstract_utils.rb.tt +29 -0
  19. data/lib/generators/rider_kick/templates/domains/core/utils/request_methods.rb.tt +2 -1
  20. data/lib/rider-kick.rb +1 -0
  21. data/lib/rider_kick/entities/failure_details.rb +22 -14
  22. data/lib/rider_kick/entities/failure_details_spec.rb +22 -0
  23. data/lib/rider_kick/matchers/use_case_result_edge_spec.rb +28 -0
  24. data/lib/rider_kick/use_cases/abstract_use_case_spec.rb +57 -0
  25. data/lib/rider_kick/version.rb +1 -1
  26. metadata +223 -51
  27. data/.rspec +0 -3
  28. data/.rubocop.yml +0 -1141
  29. data/Rakefile +0 -12
  30. data/lib/rider_kick/builders/abstract_active_record_entity_builder_spec.rb +0 -116
  31. data/lib/rider_kick/matchers/use_case_result_spec.rb +0 -64
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 317fad4c5138d788b349d4d58f36a206c4fc66236c2039a64d9cc95df563abde
4
- data.tar.gz: 33e079ae9fd531b123ec5d9960e4fa83ff354a8282299e739c2d4f542a27a0c0
3
+ metadata.gz: c2bc811b644701a52a4afcafd8fb7aa102cd8764cf164da4d1344d135ebd9539
4
+ data.tar.gz: dfd843914eea99a1df726b882659fed07ffddfd7c13655d5caf12c071252b4ef
5
5
  SHA512:
6
- metadata.gz: 6f5bff26f5e8fa54319c35237c3f3eaa3105e113068bc661f253218ed574d7f76e7df019e33bd84517e676f7b60857821d506e1e8b15bfffe700e82a68b924e4
7
- data.tar.gz: b613c0a8c12fb43ca520a35f4fce2dd3a8fcdbef603f6dd67a8050844ad67a8a101b4658b054b97be8df586b51648e9b322f0f186af09347819bba16f67d8c65
6
+ metadata.gz: ddf08bea045677b5faae9a9fb8e15efa5f650848e93ec6e97af17cdaa797a50a08edf72e38e88616a28925030cb67688b2e750f7afd488d63c14d4dc9fd350c7
7
+ data.tar.gz: 6bbeab1d7d099ce51bbf07fabd272e63e31406915d2faa3f744f461ab9238cefc50d707068e3e9c0997ddee8445c088d34133a60ccb10021a53238016ffa1543
data/README.md CHANGED
@@ -1,15 +1,27 @@
1
1
  # RiderKick
2
+ Rails generators for **Clean Architecture** on the backend: use cases, entities, repositories, builders, and utilities — organized for clarity and designed for speed.
3
+
2
4
  This gem provides helper interfaces and classes to assist in the construction of application with
3
5
  Clean Architecture, as described in [Robert Martin's seminal book](https://www.amazon.com/gp/product/0134494164).
6
+ ---
7
+ ## Features
4
8
 
9
+ - **Clean Architecture scaffolding**
10
+ Creates `app/domains` with `entities/`, `use_cases/`, `repositories/`, `builders/`, and `utils/`.
11
+ - **Use-case-first “screaming architecture”**
12
+ Encourages file names like `[role]_[action]_[subject].rb` for immediate intent (e.g., `admin_update_stock.rb`).
13
+ - **Rails-native generators**
14
+ Pragmatic commands for bootstrapping domain structure and scaffolding.
15
+ - **Idempotent, minimal friction**
16
+ Safe to run more than once; prefers appending or no-ops over destructive changes.
17
+ ---
18
+ ## ✅ Compatibility
5
19
 
6
- ## Installation
7
-
8
- Add this line to your application's Gemfile:
20
+ - **Ruby:** ≥ 3.2
21
+ - **Rails:** 7.1, 7.2, 8.0 (up to < 8.1)
9
22
 
10
- ```ruby
11
- gem 'rider-kick'
12
- ```
23
+ ---
24
+ ## Installation
13
25
 
14
26
  And then execute:
15
27
  ```bash
@@ -26,6 +38,7 @@ And then execute:
26
38
  ```bash
27
39
  $ bundle add sun-sword
28
40
  ```
41
+ ---
29
42
  ## Usage
30
43
  ```bash
31
44
  Description:
@@ -39,13 +52,27 @@ Example:
39
52
  bin/rails generate rider_kick:scaffold users scope:dashboard
40
53
 
41
54
  ```
55
+ ---
56
+ ## Generated Structure
57
+
58
+ ```text
59
+ app/
60
+ domains/
61
+ core/
62
+ entities/
63
+ builders/
64
+ repositories/
65
+ use_cases/
66
+ utils/
67
+ ```
42
68
 
69
+ ---
43
70
  ## Philosophy
44
71
 
45
72
  The intention of this gem is to help you build applications that are built from the use case down,
46
73
  and decisions about I/O can be deferred until the last possible moment.
47
74
 
48
- ## Clean Architecture
75
+ ### Clean Architecture
49
76
  This structure provides helper interfaces and classes to assist in the construction of application with Clean Architecture, as described in Robert Martin's seminal book.
50
77
 
51
78
  ```
@@ -62,7 +89,7 @@ This structure provides helper interfaces and classes to assist in the construct
62
89
  - use_cases (Just Usecase)
63
90
  - utils (Class Reusable)
64
91
  ```
65
- ## Screaming architecture - use cases as an organisational principle
92
+ ### Screaming architecture - use cases as an organisational principle
66
93
  Uncle Bob suggests that your source code organisation should allow developers to easily find a listing of all use cases your application provides. Here's an example of how this might look in a this application.
67
94
  ```
68
95
  - app
@@ -77,6 +104,7 @@ Uncle Bob suggests that your source code organisation should allow developers to
77
104
  - retail_customer_makes_deposit.rb
78
105
  - ...
79
106
  ```
107
+
80
108
  Note that the use case name contains:
81
109
 
82
110
  - the user role
@@ -88,4 +116,13 @@ Note that the use case name contains:
88
116
  # admin_fetch_info.rb [specific usecase]
89
117
  # fetch_info.rb [generic usecase] every role can access it
90
118
  ```
119
+ ---
120
+ ## 🤝 Contributing
121
+
122
+ - Fork the repo & bundle install
123
+ - Create a feature branch: git checkout -b feat/your-feature
124
+ - Add tests where it makes sense
125
+ - ``` bundle exec rspec```
126
+ - Open a Pull Request 🎉
91
127
 
128
+ See CONTRIBUTING.md for details.
@@ -57,6 +57,7 @@ module RiderKick
57
57
  template 'domains/core/entities/pagination.rb.tt', File.join("#{path_app}/domains/core/entities", 'pagination.rb')
58
58
 
59
59
  template 'domains/core/repositories/abstract_repository.rb.tt', File.join("#{path_app}/domains/core/repositories", 'abstract_repository.rb')
60
+ template 'domains/core/utils/abstract_utils.rb.tt', File.join("#{path_app}/domains/core/utils", 'abstract_utils.rb')
60
61
  template 'domains/core/utils/request_methods.rb.tt', File.join("#{path_app}/domains/core/utils", 'request_methods.rb')
61
62
  end
62
63
 
@@ -0,0 +1,52 @@
1
+ # lib/generators/rider_kick/entity_type_mapping_spec.rb
2
+ # frozen_string_literal: true
3
+
4
+ require 'rails/generators'
5
+ require 'tmpdir'
6
+ require 'fileutils'
7
+ require 'ostruct'
8
+ require 'generators/rider_kick/scaffold_generator'
9
+
10
+ RSpec.describe 'entity type mapping' do
11
+ let(:klass) { RiderKick::ScaffoldGenerator }
12
+
13
+ it 'memetakan kolom ke Types::Strict yang benar' do
14
+ Dir.mktmpdir do |dir|
15
+ Dir.chdir(dir) do
16
+ FileUtils.mkdir_p %w[
17
+ app/domains/core/use_cases
18
+ app/domains/core/repositories
19
+ app/domains/core/builders
20
+ app/domains/core/entities
21
+ app/models/models
22
+ db/structures
23
+ ]
24
+
25
+ # pakai stub shared Models::User dari spec/support (sudah auto-require)
26
+ File.write('app/models/models/user.rb', "class Models::User < ApplicationRecord; end\n")
27
+ File.write('db/structures/users_structure.yaml', <<~YAML)
28
+ model: Models::User
29
+ resource_name: users
30
+ actor: owner
31
+ uploaders: []
32
+ search_able: []
33
+ domains:
34
+ action_list: { use_case: { contract: [] } }
35
+ action_fetch_by_id: { use_case: { contract: [] } }
36
+ action_create: { use_case: { contract: [] } }
37
+ action_update: { use_case: { contract: [] } }
38
+ action_destroy: { use_case: { contract: [] } }
39
+ entity: { skipped_fields: [id, created_at, updated_at] }
40
+ YAML
41
+
42
+ klass.new(['users']).generate_use_case
43
+
44
+ entity = File.read('app/domains/core/entities/user.rb')
45
+ # harapannya: string, decimal, datetime dipetakan ke Strict
46
+ expect(entity).to match(/Types::Strict::String/)
47
+ expect(entity).to match(/Types::Strict::Decimal|Types::Coercible::Decimal/)
48
+ expect(entity).to match(/Types::Strict::Time|Types::Strict::Date|Types::Strict::DateTime/)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,62 @@
1
+ # lib/generators/rider_kick/repositories_contract_spec.rb
2
+ # frozen_string_literal: true
3
+
4
+ require 'rails/generators'
5
+ require 'tmpdir'
6
+ require 'fileutils'
7
+ require 'ostruct'
8
+ require 'generators/rider_kick/scaffold_generator'
9
+
10
+ RSpec.describe 'repositories scaffolded content' do
11
+ let(:klass) { RiderKick::ScaffoldGenerator }
12
+
13
+ it 'memuat filter resource_owner + pagination + (opsional) search_able' do
14
+ Dir.mktmpdir do |dir|
15
+ Dir.chdir(dir) do
16
+ FileUtils.mkdir_p %w[
17
+ app/domains/core/use_cases
18
+ app/domains/core/repositories
19
+ app/domains/core/builders
20
+ app/domains/core/entities
21
+ app/models/models
22
+ db/structures
23
+ ]
24
+ File.write('app/models/models/user.rb', "class Models::User < ApplicationRecord; end\n")
25
+ File.write('db/structures/users_structure.yaml', <<~YAML)
26
+ model: Models::User
27
+ resource_name: users
28
+ actor: owner
29
+ resource_owner_id: owner_id
30
+ uploaders: []
31
+ search_able: [name]
32
+ domains:
33
+ action_list: { use_case: { contract: [] } }
34
+ action_fetch_by_id: { use_case: { contract: [] } }
35
+ action_create: { use_case: { contract: [] } }
36
+ action_update: { use_case: { contract: [] } }
37
+ action_destroy: { use_case: { contract: [] } }
38
+ entity: { skipped_fields: [id, created_at, updated_at] }
39
+ YAML
40
+
41
+ klass.new(['users']).generate_use_case
42
+
43
+ list_repo = File.read('app/domains/core/repositories/users/list_user.rb')
44
+ expect(list_repo).to match(/resource_owner_id|owner_id|account_id/i) # salah satu pola filter kepemilikan
45
+ expect(list_repo).to match(/paginate|per_page|page/i) # pagination hook
46
+ expect(list_repo).to match(/name|search/i) # search_able minimal
47
+
48
+ fetch_repo = File.read('app/domains/core/repositories/users/fetch_user_by_id.rb')
49
+ expect(fetch_repo).to match(/find_by|where\(.+id:/i)
50
+
51
+ create_repo = File.read('app/domains/core/repositories/users/create_user.rb')
52
+ update_repo = File.read('app/domains/core/repositories/users/update_user.rb')
53
+ destroy_repo = File.read('app/domains/core/repositories/users/destroy_user.rb')
54
+
55
+ # repos utama terbentuk & memanggil ActiveRecord target
56
+ [create_repo, update_repo, destroy_repo].each do |src|
57
+ expect(src).to include('Models::User')
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'tmpdir'
5
+ require 'active_support/inflector'
6
+ require 'ostruct'
7
+ require 'fileutils'
8
+ require 'generators/rider_kick/scaffold_generator'
9
+
10
+ RSpec.describe 'rider_kick:scaffold builder (uploaders)' do
11
+ let(:klass) { RiderKick::ScaffoldGenerator }
12
+
13
+ it 'menulis mapping uploader: single (has_one) & multiple (has_many) ke builder' do
14
+ Dir.mktmpdir do |dir|
15
+ Dir.chdir(dir) do
16
+ FileUtils.mkdir_p(%w[
17
+ app/domains/core/use_cases
18
+ app/domains/core/repositories
19
+ app/domains/core/builders
20
+ app/domains/core/entities
21
+ app/models/models
22
+ db/structures
23
+ ])
24
+
25
+ File.write('app/models/models/user.rb', "class Models::User < ApplicationRecord; end\n")
26
+
27
+ # avatar (singular) + images (plural)
28
+ File.write('db/structures/users_structure.yaml', <<~YAML)
29
+ model: Models::User
30
+ resource_name: users
31
+ actor: owner
32
+ uploaders: [avatar, images]
33
+ search_able: []
34
+ domains:
35
+ action_list: { use_case: { contract: [] } }
36
+ action_fetch_by_id: { use_case: { contract: [] } }
37
+ action_create: { use_case: { contract: [] } }
38
+ action_update: { use_case: { contract: [] } }
39
+ action_destroy: { use_case: { contract: [] } }
40
+ entity: { skipped_fields: [id, created_at, updated_at] }
41
+ YAML
42
+
43
+ klass.new(['users']).generate_use_case
44
+
45
+ builder = File.read('app/domains/core/builders/user.rb')
46
+ # singular -> satu URL string
47
+ expect(builder).to include('avatar: (Rails.application.routes.url_helpers.polymorphic_url(params.avatar)')
48
+ # plural -> array dengan helper build_assets
49
+ expect(builder).to include('images: build_assets(params.images)')
50
+ expect(builder).to include('def build_assets(assets)')
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'tmpdir'
5
+ require 'active_support/inflector'
6
+ require 'ostruct'
7
+ require 'fileutils'
8
+ require 'generators/rider_kick/scaffold_generator'
9
+
10
+ RSpec.describe 'rider_kick:scaffold contracts' do
11
+ let(:klass) { RiderKick::ScaffoldGenerator }
12
+
13
+ it 'menulis kontrak schema yang tepat di use_cases (list/fetch_by_id/create/update/destroy)' do
14
+ Dir.mktmpdir do |dir|
15
+ Dir.chdir(dir) do
16
+ FileUtils.mkdir_p(%w[
17
+ app/domains/core/use_cases
18
+ app/domains/core/repositories
19
+ app/domains/core/builders
20
+ app/domains/core/entities
21
+ app/models/models
22
+ db/structures
23
+ ])
24
+
25
+ # Stub model & kolom
26
+
27
+ File.write('app/models/models/user.rb', "class Models::User < ApplicationRecord; end\n")
28
+
29
+ # YAML struktur minimal
30
+ File.write('db/structures/users_structure.yaml', <<~YAML)
31
+ model: Models::User
32
+ resource_name: users
33
+ actor: owner
34
+ uploaders: []
35
+ search_able: []
36
+ domains:
37
+ action_list: { use_case: { contract: [] } }
38
+ action_fetch_by_id: { use_case: { contract: [] } }
39
+ action_create: { use_case: { contract: [] } }
40
+ action_update: { use_case: { contract: [] } }
41
+ action_destroy: { use_case: { contract: [] } }
42
+ entity: { skipped_fields: [id, created_at, updated_at] }
43
+ YAML
44
+
45
+ # Generate
46
+ klass.new(['users']).generate_use_case
47
+
48
+ # Cek isi kontrak
49
+ readf = ->(path) { File.read(File.join(path)) }
50
+ base = 'app/domains/core/use_cases/users'
51
+
52
+ expect(readf["#{base}/owner_list_user.rb"])
53
+ .to include('Core::UseCases::Contract::Default', 'Contract::Pagination')
54
+
55
+ expect(readf["#{base}/owner_fetch_user_by_id.rb"])
56
+ .to include('required(:id).filled(:string)')
57
+
58
+ expect(readf["#{base}/owner_create_user.rb"])
59
+ .to include('Core::Repositories::Users::CreateUser')
60
+
61
+ expect(readf["#{base}/owner_update_user.rb"])
62
+ .to include('required(:id).filled(:string)')
63
+
64
+ expect(readf["#{base}/owner_destroy_user.rb"])
65
+ .to include('required(:id).filled(:string)')
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'tmpdir'
5
+ require 'active_support/inflector'
6
+ require 'ostruct'
7
+ require 'fileutils'
8
+ require 'generators/rider_kick/scaffold_generator'
9
+
10
+ RSpec.describe 'rider_kick:scaffold contracts (with scope)' do
11
+ let(:klass) { RiderKick::ScaffoldGenerator }
12
+
13
+ it 'menaruh use_cases di folder scope yang benar & menulis kontrak sesuai' do
14
+ Dir.mktmpdir do |dir|
15
+ Dir.chdir(dir) do
16
+ FileUtils.mkdir_p(%w[
17
+ app/domains/core/use_cases
18
+ app/domains/core/repositories
19
+ app/domains/core/builders
20
+ app/domains/core/entities
21
+ app/models/models
22
+ db/structures
23
+ ])
24
+
25
+ File.write('app/models/models/user.rb', "class Models::User < ApplicationRecord; end\n")
26
+
27
+ File.write('db/structures/users_structure.yaml', <<~YAML)
28
+ model: Models::User
29
+ resource_name: users
30
+ actor: owner
31
+ uploaders: []
32
+ search_able: []
33
+ domains:
34
+ action_list: { use_case: { contract: [] } }
35
+ action_fetch_by_id: { use_case: { contract: [] } }
36
+ action_create: { use_case: { contract: [] } }
37
+ action_update: { use_case: { contract: [] } }
38
+ action_destroy: { use_case: { contract: [] } }
39
+ entity: { skipped_fields: [id, created_at, updated_at] }
40
+ YAML
41
+
42
+ klass.new(['users', 'scope:dashboard']).generate_use_case
43
+
44
+ base = 'app/domains/core/use_cases/dashboard/users'
45
+ %w[owner_list_user owner_fetch_user_by_id owner_create_user owner_update_user owner_destroy_user].each do |uc|
46
+ expect(File).to exist("#{base}/#{uc}.rb")
47
+ end
48
+
49
+ expect(File.read("#{base}/owner_list_user.rb"))
50
+ .to include('Core::UseCases::Dashboard::Users::OwnerListUser')
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,59 @@
1
+ # lib/generators/rider_kick/scaffold_generator_idempotent_spec.rb
2
+ # frozen_string_literal: true
3
+
4
+ require 'rails/generators'
5
+ require 'tmpdir'
6
+ require 'fileutils'
7
+ require 'ostruct'
8
+ require 'generators/rider_kick/scaffold_generator'
9
+
10
+ RSpec.describe 'rider_kick:scaffold generator (idempotent)' do
11
+ let(:klass) { RiderKick::ScaffoldGenerator }
12
+
13
+ it 'tidak menduplikasi konten ketika dijalankan ulang' do
14
+ Dir.mktmpdir do |dir|
15
+ Dir.chdir(dir) do
16
+ FileUtils.mkdir_p %w[
17
+ app/domains/core/use_cases
18
+ app/domains/core/repositories
19
+ app/domains/core/builders
20
+ app/domains/core/entities
21
+ app/models/models
22
+ db/structures
23
+ ]
24
+
25
+ # model fisik & YAML minimal
26
+ File.write('app/models/models/user.rb', "class Models::User < ApplicationRecord; end\n")
27
+ File.write('db/structures/users_structure.yaml', <<~YAML)
28
+ model: Models::User
29
+ resource_name: users
30
+ actor: owner
31
+ uploaders: []
32
+ search_able: []
33
+ domains:
34
+ action_list: { use_case: { contract: [] } }
35
+ action_fetch_by_id: { use_case: { contract: [] } }
36
+ action_create: { use_case: { contract: [] } }
37
+ action_update: { use_case: { contract: [] } }
38
+ action_destroy: { use_case: { contract: [] } }
39
+ entity: { skipped_fields: [id, created_at, updated_at] }
40
+ YAML
41
+
42
+ # run pertama
43
+ klass.new(['users']).generate_use_case
44
+ builder_v1 = File.read('app/domains/core/builders/user.rb')
45
+
46
+ # run kedua (seharusnya idempotent)
47
+ klass.new(['users']).generate_use_case
48
+ builder_v2 = File.read('app/domains/core/builders/user.rb')
49
+
50
+ # konten tidak berubah
51
+ expect(builder_v2).to eq(builder_v1)
52
+
53
+ # entity & repositori tetap 1 file masing-masing
54
+ expect(Dir['app/domains/core/entities/user.rb'].size).to eq(1)
55
+ expect(Dir['app/domains/core/repositories/users/*.rb'].size).to be >= 5
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'tmpdir'
5
+ require 'active_support/inflector'
6
+ require 'ostruct'
7
+ require 'fileutils'
8
+ require 'generators/rider_kick/scaffold_generator'
9
+
10
+ RSpec.describe 'rider_kick:scaffold generator (success)' do
11
+ let(:klass) { RiderKick::ScaffoldGenerator }
12
+
13
+ it 'menghasilkan use_cases, repositories, builder, dan entity dari YAML struktur' do
14
+ Dir.mktmpdir do |dir|
15
+ Dir.chdir(dir) do
16
+ # 1) siapkan kerangka clean-arch minimal
17
+ FileUtils.mkdir_p('app/domains/core/use_cases')
18
+ FileUtils.mkdir_p('app/domains/core/repositories')
19
+ FileUtils.mkdir_p('app/domains/core/builders')
20
+ FileUtils.mkdir_p('app/domains/core/entities')
21
+ FileUtils.mkdir_p('app/models/models')
22
+ FileUtils.mkdir_p('db/structures')
23
+
24
+ # 2) stub namespace & model + metadata kolom
25
+
26
+ # 3) file model untuk inject (hanya dipakai jika uploaders ada)
27
+ File.write('app/models/models/user.rb', <<~RUBY)
28
+ class Models::User < ApplicationRecord
29
+ end
30
+ RUBY
31
+
32
+ # 4) YAML struktur minimal (hasil dari generator :structure pada umumnya)
33
+ File.write('db/structures/users_structure.yaml', <<~YAML)
34
+ model: Models::User
35
+ resource_name: users
36
+ actor: owner
37
+ resource_owner_id:
38
+ uploaders: []
39
+ search_able: []
40
+ domains:
41
+ action_list:
42
+ use_case:
43
+ contract: []
44
+ action_fetch_by_id:
45
+ use_case:
46
+ contract: []
47
+ action_create:
48
+ use_case:
49
+ contract: []
50
+ action_update:
51
+ use_case:
52
+ contract: []
53
+ action_destroy:
54
+ use_case:
55
+ contract: []
56
+ entity:
57
+ skipped_fields:
58
+ - id
59
+ - created_at
60
+ - updated_at
61
+ YAML
62
+
63
+ # 5) jalankan generator
64
+ instance = klass.new(['users']) # arg_structure = "users"
65
+ instance.generate_use_case
66
+
67
+ # 6) verifikasi artefak
68
+ # use_cases (tanpa route scope): app/domains/core/use_cases/users/<use_case>.rb
69
+ %w[owner_create_user owner_update_user owner_list_user owner_destroy_user owner_fetch_user_by_id].each do |uc|
70
+ expect(File).to exist(File.join('app/domains/core/use_cases/users', "#{uc}.rb"))
71
+ end
72
+ # repositories: app/domains/core/repositories/users/<repo>.rb
73
+ %w[create_user update_user list_user destroy_user fetch_user_by_id].each do |repo|
74
+ expect(File).to exist(File.join('app/domains/core/repositories/users', "#{repo}.rb"))
75
+ end
76
+ # builder & entity
77
+ expect(File).to exist('app/domains/core/builders/user.rb')
78
+ expect(File).to exist('app/domains/core/entities/user.rb')
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'tmpdir'
5
+ require 'active_support/inflector'
6
+ require 'ostruct'
7
+ require 'fileutils'
8
+ require 'generators/rider_kick/scaffold_generator'
9
+
10
+ RSpec.describe 'rider_kick:scaffold generator (with scope)' do
11
+ let(:klass) { RiderKick::ScaffoldGenerator }
12
+
13
+ it 'meletakkan use_cases di folder route scope yang benar' do
14
+ Dir.mktmpdir do |dir|
15
+ Dir.chdir(dir) do
16
+ FileUtils.mkdir_p('app/domains/core/use_cases')
17
+ FileUtils.mkdir_p('app/domains/core/repositories')
18
+ FileUtils.mkdir_p('app/domains/core/builders')
19
+ FileUtils.mkdir_p('app/domains/core/entities')
20
+ FileUtils.mkdir_p('app/models/models')
21
+ FileUtils.mkdir_p('db/structures')
22
+
23
+ File.write('app/models/models/user.rb', "class Models::User < ApplicationRecord; end\n")
24
+
25
+ File.write('db/structures/users_structure.yaml', <<~YAML)
26
+ model: Models::User
27
+ resource_name: users
28
+ actor: owner
29
+ uploaders: []
30
+ search_able: []
31
+ domains: { action_list: { use_case: { contract: [] } },
32
+ action_fetch_by_id: { use_case: { contract: [] } },
33
+ action_create: { use_case: { contract: [] } },
34
+ action_update: { use_case: { contract: [] } },
35
+ action_destroy: { use_case: { contract: [] } } }
36
+ entity: { skipped_fields: [id, created_at, updated_at] }
37
+ YAML
38
+
39
+ # jalankan dengan token scope:dashboard (Thor hash-arg)
40
+ instance = klass.new(['users', 'scope:dashboard'])
41
+ instance.generate_use_case
42
+
43
+ # use_cases berada di app/domains/core/use_cases/dashboard/users/...
44
+ path = 'app/domains/core/use_cases/dashboard/users'
45
+ %w[owner_create_user owner_update_user owner_list_user owner_destroy_user owner_fetch_user_by_id].each do |uc|
46
+ expect(File).to exist(File.join(path, "#{uc}.rb"))
47
+ end
48
+ # repositories tetap di .../repositories/users
49
+ %w[create_user update_user list_user destroy_user fetch_user_by_id].each do |repo|
50
+ expect(File).to exist(File.join('app/domains/core/repositories/users', "#{repo}.rb"))
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -30,6 +30,10 @@ module RiderKick
30
30
  @uploaders = uploaders
31
31
  @actor = arg_settings['actor'].downcase
32
32
 
33
+ @resource_owner_id = arg_settings['resource_owner_id'].to_s.presence
34
+ @search_able = arg_settings['search_able'].to_s.split(',').map(&:strip).reject(&:blank?) # => []
35
+ @columns = columns_meta # materialize array meta kolom
36
+
33
37
  @type_mapping = {
34
38
  'uuid' => ':string',
35
39
  'string' => ':string',
@@ -55,6 +59,35 @@ module RiderKick
55
59
  }
56
60
  end
57
61
 
62
+ def columns_meta
63
+ @model_class.columns.map do |c|
64
+ {
65
+ name: c.name.to_s,
66
+ type: c.type,
67
+ sql_type: (c.respond_to?(:sql_type) ? c.sql_type : nil),
68
+ null: (c.respond_to?(:null) ? c.null : nil),
69
+ default: (c.respond_to?(:default) ? c.default : nil),
70
+ precision: (c.respond_to?(:precision) ? c.precision : nil),
71
+ scale: (c.respond_to?(:scale) ? c.scale : nil),
72
+ limit: (c.respond_to?(:limit) ? c.limit : nil)
73
+ }
74
+ end
75
+ end
76
+
77
+ # optional: isi jika nanti kamu introspeksi foreign keys
78
+ def fkeys_meta = []
79
+
80
+ # optional: isi jika nanti kamu introspeksi index
81
+ def indexes_meta = []
82
+
83
+ # optional: isi jika pakai AR enum
84
+ def enums_meta = {}
85
+
86
+ # Kontrak opsional untuk baris dinamis; biarkan kosong dulu agar template aman
87
+ def contract_lines_for_create = []
88
+
89
+ def contract_lines_for_update = []
90
+
58
91
  def is_singular?(str)
59
92
  str.singularize == str
60
93
  end