rider-kick 0.0.13 → 0.0.15

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 (91) 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 +236 -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/init_generator.rb +15 -0
  13. data/lib/generators/rider_kick/repositories_contract_spec.rb +95 -22
  14. data/lib/generators/rider_kick/scaffold_generator.rb +381 -64
  15. data/lib/generators/rider_kick/scaffold_generator_builder_uploaders_spec.rb +119 -14
  16. data/lib/generators/rider_kick/scaffold_generator_conditional_filtering_spec.rb +820 -0
  17. data/lib/generators/rider_kick/scaffold_generator_contracts_spec.rb +37 -10
  18. data/lib/generators/rider_kick/scaffold_generator_contracts_with_scope_spec.rb +42 -14
  19. data/lib/generators/rider_kick/scaffold_generator_engine_spec.rb +221 -0
  20. data/lib/generators/rider_kick/scaffold_generator_idempotent_spec.rb +38 -13
  21. data/lib/generators/rider_kick/scaffold_generator_list_spec_format_spec.rb +153 -0
  22. data/lib/generators/rider_kick/scaffold_generator_rspec_spec.rb +347 -0
  23. data/lib/generators/rider_kick/scaffold_generator_success_spec.rb +31 -12
  24. data/lib/generators/rider_kick/scaffold_generator_with_scope_spec.rb +33 -12
  25. data/lib/generators/rider_kick/structure_generator.rb +156 -43
  26. data/lib/generators/rider_kick/structure_generator_comprehensive_spec.rb +598 -0
  27. data/lib/generators/rider_kick/structure_generator_engine_spec.rb +279 -0
  28. data/lib/generators/rider_kick/structure_generator_spec.rb +3 -3
  29. data/lib/generators/rider_kick/structure_generator_success_spec.rb +33 -5
  30. data/lib/generators/rider_kick/structure_generator_unit_spec.rb +2202 -0
  31. data/lib/generators/rider_kick/templates/.rubocop.yml +5 -4
  32. data/lib/generators/rider_kick/templates/config/initializers/rider_kick.rb.tt +29 -0
  33. data/lib/generators/rider_kick/templates/config/initializers/version.rb.tt +1 -1
  34. data/lib/generators/rider_kick/templates/db/migrate/20220613145533_init_database.rb +1 -1
  35. data/lib/generators/rider_kick/templates/db/structures/example.yaml.tt +142 -66
  36. data/lib/generators/rider_kick/templates/domains/core/builders/builder.rb.tt +36 -10
  37. data/lib/generators/rider_kick/templates/domains/core/builders/builder_spec.rb.tt +219 -0
  38. data/lib/generators/rider_kick/templates/domains/core/builders/error.rb.tt +2 -2
  39. data/lib/generators/rider_kick/templates/domains/core/builders/pagination.rb.tt +2 -2
  40. data/lib/generators/rider_kick/templates/domains/core/entities/entity.rb.tt +32 -14
  41. data/lib/generators/rider_kick/templates/domains/core/entities/error.rb.tt +1 -1
  42. data/lib/generators/rider_kick/templates/domains/core/entities/pagination.rb.tt +1 -1
  43. data/lib/generators/rider_kick/templates/domains/core/repositories/abstract_repository.rb.tt +4 -4
  44. data/lib/generators/rider_kick/templates/domains/core/repositories/create.rb.tt +2 -2
  45. data/lib/generators/rider_kick/templates/domains/core/repositories/create_spec.rb.tt +78 -0
  46. data/lib/generators/rider_kick/templates/domains/core/repositories/destroy.rb.tt +2 -2
  47. data/lib/generators/rider_kick/templates/domains/core/repositories/destroy_spec.rb.tt +88 -0
  48. data/lib/generators/rider_kick/templates/domains/core/repositories/fetch_by_id.rb.tt +3 -3
  49. data/lib/generators/rider_kick/templates/domains/core/repositories/fetch_by_id_spec.rb.tt +62 -0
  50. data/lib/generators/rider_kick/templates/domains/core/repositories/list.rb.tt +13 -8
  51. data/lib/generators/rider_kick/templates/domains/core/repositories/list_spec.rb.tt +190 -0
  52. data/lib/generators/rider_kick/templates/domains/core/repositories/update.rb.tt +4 -4
  53. data/lib/generators/rider_kick/templates/domains/core/repositories/update_spec.rb.tt +119 -0
  54. data/lib/generators/rider_kick/templates/domains/core/use_cases/contract/default.rb.tt +1 -1
  55. data/lib/generators/rider_kick/templates/domains/core/use_cases/contract/pagination.rb.tt +1 -1
  56. data/lib/generators/rider_kick/templates/domains/core/use_cases/create.rb.tt +3 -7
  57. data/lib/generators/rider_kick/templates/domains/core/use_cases/create_spec.rb.tt +71 -0
  58. data/lib/generators/rider_kick/templates/domains/core/use_cases/destroy.rb.tt +3 -7
  59. data/lib/generators/rider_kick/templates/domains/core/use_cases/destroy_spec.rb.tt +62 -0
  60. data/lib/generators/rider_kick/templates/domains/core/use_cases/fetch_by_id.rb.tt +3 -7
  61. data/lib/generators/rider_kick/templates/domains/core/use_cases/fetch_by_id_spec.rb.tt +62 -0
  62. data/lib/generators/rider_kick/templates/domains/core/use_cases/get_version.rb.tt +2 -2
  63. data/lib/generators/rider_kick/templates/domains/core/use_cases/list.rb.tt +3 -7
  64. data/lib/generators/rider_kick/templates/domains/core/use_cases/list_spec.rb.tt +64 -0
  65. data/lib/generators/rider_kick/templates/domains/core/use_cases/update.rb.tt +3 -7
  66. data/lib/generators/rider_kick/templates/domains/core/use_cases/update_spec.rb.tt +73 -0
  67. data/lib/generators/rider_kick/templates/domains/core/utils/abstract_utils.rb.tt +3 -3
  68. data/lib/generators/rider_kick/templates/domains/core/utils/request_methods.rb.tt +1 -1
  69. data/lib/generators/rider_kick/templates/env.development +1 -1
  70. data/lib/generators/rider_kick/templates/env.production +1 -1
  71. data/lib/generators/rider_kick/templates/env.test +1 -1
  72. data/lib/generators/rider_kick/templates/models/{application_record.rb → application_record.rb.tt} +3 -1
  73. data/lib/generators/rider_kick/templates/models/model_spec.rb.tt +68 -0
  74. data/lib/generators/rider_kick/templates/spec/factories/.gitkeep +19 -0
  75. data/lib/generators/rider_kick/templates/spec/factories/factory.rb.tt +8 -0
  76. data/lib/generators/rider_kick/templates/spec/rails_helper.rb +3 -0
  77. data/lib/generators/rider_kick/templates/spec/support/class_stubber.rb +148 -0
  78. data/lib/generators/rider_kick/templates/spec/support/factory_bot.rb +34 -0
  79. data/lib/generators/rider_kick/templates/spec/support/faker.rb +61 -0
  80. data/lib/generators/rider_kick/templates/spec/support/use_case_stubber.rb +14 -0
  81. data/lib/rider-kick.rb +8 -6
  82. data/lib/rider_kick/builders/abstract_active_record_entity_builder_spec.rb +644 -0
  83. data/lib/rider_kick/configuration.rb +258 -0
  84. data/lib/rider_kick/configuration_engine_spec.rb +377 -0
  85. data/lib/rider_kick/entities/failure_details.rb +1 -1
  86. data/lib/rider_kick/entities/failure_details_spec.rb +1 -1
  87. data/lib/rider_kick/matchers/use_case_result.rb +1 -1
  88. data/lib/rider_kick/use_cases/abstract_use_case.rb +1 -1
  89. data/lib/rider_kick/version.rb +1 -1
  90. metadata +132 -8
  91. data/CHANGELOG.md +0 -5
@@ -1,11 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'active_support/inflector'
5
+ require 'active_support/core_ext/object/blank'
6
+ require 'active_support/core_ext/enumerable'
7
+ require 'hashie'
8
+ require_relative 'base_generator'
9
+ require_relative '../../rider-kick'
10
+
1
11
  module RiderKick
2
- class Structure < Rails::Generators::Base
12
+ class Structure < BaseGenerator
3
13
  source_root File.expand_path('templates', __dir__)
4
14
 
5
15
  argument :arg_model_name, type: :string, default: '', banner: 'Models::Name'
6
- argument :arg_settings, type: :hash, default: '', banner: 'route_scope:dashboard actor:user uploaders:assets,images,picture,document'
16
+ argument :arg_settings, type: :hash, default: {}, banner: 'route_scope:dashboard actor:user uploaders:assets,images,picture,document'
17
+ class_option :engine, type: :string, default: nil, desc: 'Specify engine name (e.g., Core, Admin)'
18
+ class_option :domain, type: :string, default: '', desc: 'Specify domain scope (e.g., core/, admin/, api/v1/)'
7
19
 
8
20
  def generate_use_case
21
+ configure_engine
9
22
  validation!
10
23
  setup_variables
11
24
  generate_files(@scope_path)
@@ -14,49 +27,118 @@ module RiderKick
14
27
  private
15
28
 
16
29
  def validation!
17
- unless Dir.exist?('app/domains')
18
- say 'Error must create clean arch structure first!'
19
- raise Thor::Error, 'run: bin/rails generate rider_kick:clean_arch --setup'
30
+ validate_domains_path!
31
+
32
+ # Pastikan parameter wajib tersedia
33
+ actor = arg_settings['actor'].to_s.strip
34
+ resource_owner = arg_settings['resource_owner'].to_s.strip
35
+ resource_owner_id = arg_settings['resource_owner_id'].to_s.strip
36
+
37
+ if actor.blank?
38
+ raise ValidationError.new(
39
+ "Missing required setting: actor. Contoh: 'actor:user'",
40
+ setting: 'actor',
41
+ provided_settings: arg_settings.keys
42
+ )
43
+ end
44
+
45
+ if resource_owner.blank?
46
+ raise ValidationError.new(
47
+ "Missing required setting: resource_owner. Contoh: 'resource_owner:account'",
48
+ setting: 'resource_owner',
49
+ provided_settings: arg_settings.keys
50
+ )
51
+ end
52
+
53
+ if resource_owner_id.blank?
54
+ raise ValidationError.new(
55
+ "Missing required setting: resource_owner_id. Contoh: 'resource_owner_id:account_id'",
56
+ setting: 'resource_owner_id',
57
+ provided_settings: arg_settings.keys
58
+ )
20
59
  end
21
60
  end
22
61
 
23
62
  def setup_variables
24
- @variable_subject = arg_model_name.split('::').last.underscore.downcase
63
+ @variable_subject = arg_model_name.split('::').last.underscore.downcase
64
+ validate_model_exists!(arg_model_name.camelize)
25
65
  @model_class = arg_model_name.camelize.constantize
26
66
  @subject_class = arg_model_name.split('::').last
27
67
  @scope_path = @subject_class.pluralize.underscore.downcase
28
68
  @scope_class = @scope_path.camelize
29
69
  @fields = contract_fields
30
- @uploaders = uploaders
31
- @actor = arg_settings['actor'].downcase
70
+ @uploaders = uploaders # Ini array string dari argumen
71
+ @actor = arg_settings['actor'].to_s.downcase
72
+ @actor_id = (@actor.present? ? arg_settings['actor'].to_s.downcase + '_id' : '')
32
73
 
74
+ @resource_owner = arg_settings['resource_owner'].to_s.presence
33
75
  @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
-
37
- @type_mapping = {
38
- 'uuid' => ':string',
39
- 'string' => ':string',
40
- 'text' => ':string',
41
- 'integer' => ':integer',
42
- 'boolean' => ':bool',
43
- 'float' => ':float',
44
- 'decimal' => ':float',
45
- 'date' => ':date',
46
- 'upload' => 'Types::File',
47
- 'datetime' => ':string'
48
- }
49
- @entity_type_mapping = {
50
- 'uuid' => 'Types::Strict::String',
51
- 'string' => 'Types::Strict::String',
52
- 'text' => 'Types::Strict::String',
53
- 'integer' => 'Types::Strict::Integer',
54
- 'boolean' => 'Types::Strict::Bool',
55
- 'float' => 'Types::Strict::Float',
56
- 'decimal' => 'Types::Strict::Decimal',
57
- 'date' => 'Types::Strict::Date',
58
- 'datetime' => 'Types::Strict::Time'
59
- }
76
+ @columns = columns_meta
77
+
78
+ @type_mapping = RiderKick.configuration.type_mapping
79
+ @entity_type_mapping = RiderKick.configuration.entity_type_mapping
80
+
81
+ @columns_meta_hash = @columns.index_by { |c| c[:name] }
82
+
83
+ @contract_lines_for_create = @fields.map do |field_name|
84
+ column_meta = @columns_meta_hash[field_name]
85
+ next nil if column_meta.nil?
86
+ next nil if @resource_owner_id.to_s.eql?(field_name.to_s)
87
+
88
+ predicate = column_meta[:null] ? 'optional' : 'required'
89
+ db_type = get_column_type(field_name).to_s
90
+ validation_type = @type_mapping[db_type]
91
+
92
+ if db_type == 'upload'
93
+ validation_type = 'Types::File'
94
+ elsif validation_type.nil?
95
+ validation_type = ':string'
96
+ end
97
+
98
+ if predicate == 'required'
99
+ "\"required(:#{field_name}).filled(#{validation_type})\""
100
+ else
101
+ "\"optional(:#{field_name}).maybe(#{validation_type})\""
102
+ end
103
+ end.compact
104
+
105
+ @contract_lines_for_update = @fields.map do |field_name|
106
+ column_meta = @columns_meta_hash[field_name]
107
+ next nil if column_meta.nil?
108
+ next nil if @resource_owner_id.to_s.eql?(field_name.to_s)
109
+
110
+ db_type = get_column_type(field_name).to_s
111
+ validation_type = @type_mapping[db_type]
112
+
113
+ if db_type == 'upload'
114
+ validation_type = 'Types::File'
115
+ elsif validation_type.nil?
116
+ validation_type = ':string'
117
+ end
118
+
119
+ "\"optional(:#{field_name}).maybe(#{validation_type})\""
120
+ end.compact
121
+
122
+ search_able_fields = arg_settings['search_able'].to_s.split(',').map(&:strip).reject(&:blank?)
123
+
124
+ @repository_list_filters = search_able_fields.map do |field|
125
+ "{ field: '#{field}', type: 'search' }"
126
+ end
127
+
128
+ @contract_lines_for_list = @repository_list_filters.map do |filter_hash|
129
+ field_name = filter_hash.match(/field: '([^']+)'/)[1]
130
+ "\"optional(:#{field_name}).maybe(:string)\""
131
+ end
132
+
133
+ @entity_db_fields = @fields
134
+
135
+ # 1. Tentukan definisi uploader yang kaya (array of hashes)
136
+ # @uploaders di sini masih array string dari argumen
137
+ @entity_uploader_definitions = @uploaders.map do |uploader_name|
138
+ type = is_singular?(uploader_name) ? 'single' : 'multiple'
139
+ # PERBAIKAN: Ini harus menjadi Hash, BUKAN String
140
+ { name: uploader_name, type: type }
141
+ end
60
142
  end
61
143
 
62
144
  def columns_meta
@@ -74,37 +156,68 @@ module RiderKick
74
156
  end
75
157
  end
76
158
 
77
- # optional: isi jika nanti kamu introspeksi foreign keys
78
159
  def fkeys_meta = []
79
160
 
80
- # optional: isi jika nanti kamu introspeksi index
81
161
  def indexes_meta = []
82
162
 
83
- # optional: isi jika pakai AR enum
84
163
  def enums_meta = {}
85
164
 
86
- # Kontrak opsional untuk baris dinamis; biarkan kosong dulu agar template aman
87
- def contract_lines_for_create = []
165
+ def contract_lines_for_create
166
+ @contract_lines_for_create || []
167
+ end
168
+
169
+ def contract_lines_for_update
170
+ @contract_lines_for_update || []
171
+ end
172
+
173
+ def contract_lines_for_list
174
+ @contract_lines_for_list || []
175
+ end
88
176
 
89
- def contract_lines_for_update = []
177
+ def repository_list_filters
178
+ @repository_list_filters || []
179
+ end
180
+
181
+ def entity_db_fields
182
+ @entity_db_fields || []
183
+ end
184
+
185
+ def entity_uploader_definitions
186
+ @entity_uploader_definitions || []
187
+ end
90
188
 
91
189
  def is_singular?(str)
92
190
  str.singularize == str
93
191
  end
94
192
 
95
193
  def generate_files(action)
96
- template 'db/structures/example.yaml.tt', File.join("db/structures/#{action}_structure.yaml")
194
+ structure_path = if RiderKick.configuration.engine_name.present?
195
+ # For engines, generate structure file in engine's db/structures directory
196
+ engine_name = RiderKick.configuration.engine_name.downcase
197
+ "engines/#{engine_name}/db/structures/#{action}_structure.yaml"
198
+ else
199
+ # For main app, generate in host's db/structures directory
200
+ "db/structures/#{action}_structure.yaml"
201
+ end
202
+
203
+ template 'db/structures/example.yaml.tt', structure_path
97
204
  end
98
205
 
99
206
  def contract_fields
100
- @model_class.columns.reject { |column| ['id', 'created_at', 'updated_at', 'type'].include?(column.name.to_s) }.map(&:name).map(&:to_s)
207
+ # Metode ini sekarang hanya digunakan oleh setup_variables untuk @fields
208
+ @model_class.columns.reject { |column|
209
+ (uploaders + ['id', 'created_at', 'updated_at', 'type']).include?(column.name.to_s)
210
+ }.map(&:name).map(&:to_s)
101
211
  end
102
212
 
103
213
  def get_column_type(field)
104
- uploaders.include?(field) ? 'upload' : @model_class.columns_hash[field].type
214
+ # uploaders di sini masih array string
215
+ uploaders.include?(field) ?
216
+ 'upload' : @model_class.columns_hash[field].type
105
217
  end
106
218
 
107
219
  def uploaders
220
+ # Ini adalah array string mentah dari argumen
108
221
  return [] unless arg_settings['uploaders'].present?
109
222
  arg_settings['uploaders'].split(',').map(&:strip)
110
223
  end