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
@@ -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,116 @@ 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
+
87
+ predicate = column_meta[:null] ? 'optional' : 'required'
88
+ db_type = get_column_type(field_name).to_s
89
+ validation_type = @type_mapping[db_type]
90
+
91
+ if db_type == 'upload'
92
+ validation_type = 'Types::File'
93
+ elsif validation_type.nil?
94
+ validation_type = ':string'
95
+ end
96
+
97
+ if predicate == 'required'
98
+ "\"required(:#{field_name}).filled(#{validation_type})\""
99
+ else
100
+ "\"optional(:#{field_name}).maybe(#{validation_type})\""
101
+ end
102
+ end.compact
103
+
104
+ @contract_lines_for_update = @fields.map do |field_name|
105
+ column_meta = @columns_meta_hash[field_name]
106
+ next nil if column_meta.nil?
107
+
108
+ db_type = get_column_type(field_name).to_s
109
+ validation_type = @type_mapping[db_type]
110
+
111
+ if db_type == 'upload'
112
+ validation_type = 'Types::File'
113
+ elsif validation_type.nil?
114
+ validation_type = ':string'
115
+ end
116
+
117
+ "\"optional(:#{field_name}).maybe(#{validation_type})\""
118
+ end.compact
119
+
120
+ search_able_fields = arg_settings['search_able'].to_s.split(',').map(&:strip).reject(&:blank?)
121
+
122
+ @repository_list_filters = search_able_fields.map do |field|
123
+ "{ field: '#{field}', type: 'search' }"
124
+ end
125
+
126
+ @contract_lines_for_list = @repository_list_filters.map do |filter_hash|
127
+ field_name = filter_hash.match(/field: '([^']+)'/)[1]
128
+ "\"optional(:#{field_name}).maybe(:string)\""
129
+ end
130
+
131
+ @entity_db_fields = @fields
132
+
133
+ # 1. Tentukan definisi uploader yang kaya (array of hashes)
134
+ # @uploaders di sini masih array string dari argumen
135
+ @entity_uploader_definitions = @uploaders.map do |uploader_name|
136
+ type = is_singular?(uploader_name) ? 'single' : 'multiple'
137
+ # PERBAIKAN: Ini harus menjadi Hash, BUKAN String
138
+ { name: uploader_name, type: type }
139
+ end
60
140
  end
61
141
 
62
142
  def columns_meta
@@ -74,37 +154,68 @@ module RiderKick
74
154
  end
75
155
  end
76
156
 
77
- # optional: isi jika nanti kamu introspeksi foreign keys
78
157
  def fkeys_meta = []
79
158
 
80
- # optional: isi jika nanti kamu introspeksi index
81
159
  def indexes_meta = []
82
160
 
83
- # optional: isi jika pakai AR enum
84
161
  def enums_meta = {}
85
162
 
86
- # Kontrak opsional untuk baris dinamis; biarkan kosong dulu agar template aman
87
- def contract_lines_for_create = []
163
+ def contract_lines_for_create
164
+ @contract_lines_for_create || []
165
+ end
166
+
167
+ def contract_lines_for_update
168
+ @contract_lines_for_update || []
169
+ end
170
+
171
+ def contract_lines_for_list
172
+ @contract_lines_for_list || []
173
+ end
88
174
 
89
- def contract_lines_for_update = []
175
+ def repository_list_filters
176
+ @repository_list_filters || []
177
+ end
178
+
179
+ def entity_db_fields
180
+ @entity_db_fields || []
181
+ end
182
+
183
+ def entity_uploader_definitions
184
+ @entity_uploader_definitions || []
185
+ end
90
186
 
91
187
  def is_singular?(str)
92
188
  str.singularize == str
93
189
  end
94
190
 
95
191
  def generate_files(action)
96
- template 'db/structures/example.yaml.tt', File.join("db/structures/#{action}_structure.yaml")
192
+ structure_path = if RiderKick.configuration.engine_name.present?
193
+ # For engines, generate structure file in engine's db/structures directory
194
+ engine_name = RiderKick.configuration.engine_name.downcase
195
+ "engines/#{engine_name}/db/structures/#{action}_structure.yaml"
196
+ else
197
+ # For main app, generate in host's db/structures directory
198
+ "db/structures/#{action}_structure.yaml"
199
+ end
200
+
201
+ template 'db/structures/example.yaml.tt', structure_path
97
202
  end
98
203
 
99
204
  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)
205
+ # Metode ini sekarang hanya digunakan oleh setup_variables untuk @fields
206
+ @model_class.columns.reject { |column|
207
+ (uploaders + ['id', 'created_at', 'updated_at', 'type']).include?(column.name.to_s)
208
+ }.map(&:name).map(&:to_s)
101
209
  end
102
210
 
103
211
  def get_column_type(field)
104
- uploaders.include?(field) ? 'upload' : @model_class.columns_hash[field].type
212
+ # uploaders di sini masih array string
213
+ uploaders.include?(field) ?
214
+ 'upload' : @model_class.columns_hash[field].type
105
215
  end
106
216
 
107
217
  def uploaders
218
+ # Ini adalah array string mentah dari argumen
108
219
  return [] unless arg_settings['uploaders'].present?
109
220
  arg_settings['uploaders'].split(',').map(&:strip)
110
221
  end