rider-kick 0.0.12 → 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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +668 -27
  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 -44
  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 +61 -0
  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 +135 -0
  13. data/lib/generators/rider_kick/scaffold_generator.rb +377 -62
  14. data/lib/generators/rider_kick/scaffold_generator_builder_uploaders_spec.rb +159 -0
  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 +96 -0
  17. data/lib/generators/rider_kick/scaffold_generator_contracts_with_scope_spec.rb +83 -0
  18. data/lib/generators/rider_kick/scaffold_generator_engine_spec.rb +221 -0
  19. data/lib/generators/rider_kick/scaffold_generator_idempotent_spec.rb +84 -0
  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 +101 -0
  23. data/lib/generators/rider_kick/scaffold_generator_with_scope_spec.rb +76 -0
  24. data/lib/generators/rider_kick/structure_generator.rb +179 -35
  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 +20 -0
  28. data/lib/generators/rider_kick/structure_generator_success_spec.rb +64 -0
  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 +157 -51
  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 -16
  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 +12 -7
  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 +29 -0
  66. data/lib/generators/rider_kick/templates/domains/core/utils/request_methods.rb.tt +3 -2
  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 +9 -6
  79. data/lib/rider_kick/builders/abstract_active_record_entity_builder_spec.rb +596 -68
  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 +23 -15
  83. data/lib/rider_kick/entities/failure_details_spec.rb +22 -0
  84. data/lib/rider_kick/matchers/use_case_result.rb +1 -1
  85. data/lib/rider_kick/matchers/use_case_result_edge_spec.rb +28 -0
  86. data/lib/rider_kick/use_cases/abstract_use_case.rb +1 -1
  87. data/lib/rider_kick/use_cases/abstract_use_case_spec.rb +57 -0
  88. data/lib/rider_kick/version.rb +1 -1
  89. metadata +345 -52
  90. data/.rspec +0 -3
  91. data/.rubocop.yml +0 -1141
  92. data/CHANGELOG.md +0 -5
  93. data/Rakefile +0 -12
  94. data/lib/rider_kick/matchers/use_case_result_spec.rb +0 -64
@@ -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,45 +27,161 @@ 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
32
-
33
- @type_mapping = {
34
- 'uuid' => ':string',
35
- 'string' => ':string',
36
- 'text' => ':string',
37
- 'integer' => ':integer',
38
- 'boolean' => ':bool',
39
- 'float' => ':float',
40
- 'decimal' => ':float',
41
- 'date' => ':date',
42
- 'upload' => 'Types::File',
43
- 'datetime' => ':string'
44
- }
45
- @entity_type_mapping = {
46
- 'uuid' => 'Types::Strict::String',
47
- 'string' => 'Types::Strict::String',
48
- 'text' => 'Types::Strict::String',
49
- 'integer' => 'Types::Strict::Integer',
50
- 'boolean' => 'Types::Strict::Bool',
51
- 'float' => 'Types::Strict::Float',
52
- 'decimal' => 'Types::Strict::Decimal',
53
- 'date' => 'Types::Strict::Date',
54
- 'datetime' => 'Types::Strict::Time'
55
- }
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' : '')
73
+
74
+ @resource_owner = arg_settings['resource_owner'].to_s.presence
75
+ @resource_owner_id = arg_settings['resource_owner_id'].to_s.presence
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
140
+ end
141
+
142
+ def columns_meta
143
+ @model_class.columns.map do |c|
144
+ {
145
+ name: c.name.to_s,
146
+ type: c.type,
147
+ sql_type: (c.respond_to?(:sql_type) ? c.sql_type : nil),
148
+ null: (c.respond_to?(:null) ? c.null : nil),
149
+ default: (c.respond_to?(:default) ? c.default : nil),
150
+ precision: (c.respond_to?(:precision) ? c.precision : nil),
151
+ scale: (c.respond_to?(:scale) ? c.scale : nil),
152
+ limit: (c.respond_to?(:limit) ? c.limit : nil)
153
+ }
154
+ end
155
+ end
156
+
157
+ def fkeys_meta = []
158
+
159
+ def indexes_meta = []
160
+
161
+ def enums_meta = {}
162
+
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
174
+
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 || []
56
185
  end
57
186
 
58
187
  def is_singular?(str)
@@ -60,18 +189,33 @@ module RiderKick
60
189
  end
61
190
 
62
191
  def generate_files(action)
63
- 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
64
202
  end
65
203
 
66
204
  def contract_fields
67
- @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)
68
209
  end
69
210
 
70
211
  def get_column_type(field)
71
- 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
72
215
  end
73
216
 
74
217
  def uploaders
218
+ # Ini adalah array string mentah dari argumen
75
219
  return [] unless arg_settings['uploaders'].present?
76
220
  arg_settings['uploaders'].split(',').map(&:strip)
77
221
  end