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.
- checksums.yaml +4 -4
- data/README.md +45 -8
- data/lib/generators/rider_kick/clean_arch_generator.rb +1 -0
- data/lib/generators/rider_kick/entity_type_mapping_spec.rb +52 -0
- data/lib/generators/rider_kick/repositories_contract_spec.rb +62 -0
- data/lib/generators/rider_kick/scaffold_generator_builder_uploaders_spec.rb +54 -0
- data/lib/generators/rider_kick/scaffold_generator_contracts_spec.rb +69 -0
- data/lib/generators/rider_kick/scaffold_generator_contracts_with_scope_spec.rb +54 -0
- data/lib/generators/rider_kick/scaffold_generator_idempotent_spec.rb +59 -0
- data/lib/generators/rider_kick/scaffold_generator_success_spec.rb +82 -0
- data/lib/generators/rider_kick/scaffold_generator_with_scope_spec.rb +55 -0
- data/lib/generators/rider_kick/structure_generator.rb +33 -0
- data/lib/generators/rider_kick/structure_generator_spec.rb +20 -0
- data/lib/generators/rider_kick/structure_generator_success_spec.rb +36 -0
- data/lib/generators/rider_kick/templates/db/structures/example.yaml.tt +114 -82
- data/lib/generators/rider_kick/templates/domains/core/repositories/abstract_repository.rb.tt +0 -12
- data/lib/generators/rider_kick/templates/domains/core/repositories/list.rb.tt +2 -2
- data/lib/generators/rider_kick/templates/domains/core/utils/abstract_utils.rb.tt +29 -0
- data/lib/generators/rider_kick/templates/domains/core/utils/request_methods.rb.tt +2 -1
- data/lib/rider-kick.rb +1 -0
- data/lib/rider_kick/entities/failure_details.rb +22 -14
- data/lib/rider_kick/entities/failure_details_spec.rb +22 -0
- data/lib/rider_kick/matchers/use_case_result_edge_spec.rb +28 -0
- data/lib/rider_kick/use_cases/abstract_use_case_spec.rb +57 -0
- data/lib/rider_kick/version.rb +1 -1
- metadata +223 -51
- data/.rspec +0 -3
- data/.rubocop.yml +0 -1141
- data/Rakefile +0 -12
- data/lib/rider_kick/builders/abstract_active_record_entity_builder_spec.rb +0 -116
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c2bc811b644701a52a4afcafd8fb7aa102cd8764cf164da4d1344d135ebd9539
|
4
|
+
data.tar.gz: dfd843914eea99a1df726b882659fed07ffddfd7c13655d5caf12c071252b4ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
11
|
-
|
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
|
-
|
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
|
-
|
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
|