rider-kick 0.0.15 → 0.0.16

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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/rider_kick/clean_arch_generator.rb +1 -3
  3. data/lib/generators/rider_kick/scaffold_generator.rb +37 -37
  4. data/lib/generators/rider_kick/scaffold_generator_contracts_with_scope_spec.rb +1 -1
  5. data/lib/generators/rider_kick/scaffold_generator_list_spec_format_spec.rb +17 -11
  6. data/lib/generators/rider_kick/scaffold_generator_rspec_spec.rb +2 -11
  7. data/lib/generators/rider_kick/scaffold_generator_with_scope_spec.rb +1 -1
  8. data/lib/generators/rider_kick/templates/config/initializers/pagy.rb.tt +45 -10
  9. data/lib/generators/rider_kick/templates/db/structures/example.yaml.tt +1 -2
  10. data/lib/generators/rider_kick/templates/domains/core/builders/builder_spec.rb.tt +51 -38
  11. data/lib/generators/rider_kick/templates/domains/core/repositories/abstract_repository.rb.tt +7 -5
  12. data/lib/generators/rider_kick/templates/domains/core/repositories/create_spec.rb.tt +131 -40
  13. data/lib/generators/rider_kick/templates/domains/core/repositories/destroy.rb.tt +4 -5
  14. data/lib/generators/rider_kick/templates/domains/core/repositories/destroy_spec.rb.tt +102 -57
  15. data/lib/generators/rider_kick/templates/domains/core/repositories/fetch_by_id.rb.tt +1 -1
  16. data/lib/generators/rider_kick/templates/domains/core/repositories/fetch_by_id_spec.rb.tt +89 -37
  17. data/lib/generators/rider_kick/templates/domains/core/repositories/list.rb.tt +8 -3
  18. data/lib/generators/rider_kick/templates/domains/core/repositories/list_spec.rb.tt +141 -131
  19. data/lib/generators/rider_kick/templates/domains/core/repositories/update.rb.tt +2 -2
  20. data/lib/generators/rider_kick/templates/domains/core/repositories/update_spec.rb.tt +171 -80
  21. data/lib/generators/rider_kick/templates/domains/core/use_cases/create_spec.rb.tt +25 -5
  22. data/lib/generators/rider_kick/templates/domains/core/use_cases/destroy_spec.rb.tt +4 -4
  23. data/lib/generators/rider_kick/templates/domains/core/use_cases/fetch_by_id_spec.rb.tt +4 -4
  24. data/lib/generators/rider_kick/templates/domains/core/use_cases/list_spec.rb.tt +1 -1
  25. data/lib/generators/rider_kick/templates/domains/core/use_cases/update_spec.rb.tt +28 -8
  26. data/lib/rider_kick/matchers/use_case_result.rb +1 -1
  27. data/lib/rider_kick/use_cases/abstract_use_case_spec.rb +3 -3
  28. data/lib/rider_kick/version.rb +1 -1
  29. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5129eebe54dcc3056758fead3e72d4a98a788cd71845dac6f5d9611792a1704
4
- data.tar.gz: 56bfecfa53f00172d78a7e8b91db4908042fc71f2075da5d5f2d7adb0ec53a3c
3
+ metadata.gz: 3c933ba32d84ebd605729faf5dddd9ee0392e2954b160c0e2ef0a23570f5c636
4
+ data.tar.gz: fab65284c253086df7acb0b40e89f078f20d436f241b63c3a0352e36d96127f2
5
5
  SHA512:
6
- metadata.gz: ad11a0d3998b1f69ef33e70739a3e75785356d3e63960641f80d06047a8f0f9c3661c4ebece9a1c119d03370900b716e2afc65aa3ada2a47565fedb86e548713
7
- data.tar.gz: 67175aad3253eb90b31a8987d87bcda6ffe3abf69358719d7451f0d310ab76d16c16341635056b96acd179c3136466abcfd60a4bf24463c90bb13147a383089b
6
+ metadata.gz: e1dc64862b3707c35dd437dd9f2ce1af940d20fcf9f8c99f0160c65bc77c99f0aba9af9046a8ccc2ea8309ce56fff57cd6fada35afa46b996ff36f6462b645e5
7
+ data.tar.gz: 908f66d02665cac5897adb37cfc215a34cccee066bf009b111037a82f0a58ad905ac327d54e77bcbb0c67da65d8b3d75eae6aaa4479b65902e4581d6e1be61ff
@@ -221,6 +221,7 @@ module RiderKick
221
221
 
222
222
  def gem_dependencies
223
223
  <<~RUBY
224
+
224
225
  group :development, :test do
225
226
  gem "rspec-rails"
226
227
  gem "factory_bot_rails"
@@ -241,9 +242,6 @@ module RiderKick
241
242
 
242
243
  # pagination
243
244
  gem 'pagy'
244
-
245
- # models validation
246
- gem 'schema_validations'
247
245
  RUBY
248
246
  end
249
247
 
@@ -93,11 +93,11 @@ module RiderKick
93
93
 
94
94
  unless @model_class.column_names.include?(field_name)
95
95
  raise ValidationError.new(
96
- "Repository filter error: Field '#{field_name}' tidak ditemukan di model #{@model_class}",
97
- structure_file: "#{arg_structure}_structure.yaml",
98
- field_name: field_name,
99
- model_class: @model_class.to_s,
100
- available_columns: @model_class.column_names
96
+ "Repository filter error: Field '#{field_name}' tidak ditemukan di model #{@model_class}",
97
+ structure_file: "#{arg_structure}_structure.yaml",
98
+ field_name: field_name,
99
+ model_class: @model_class.to_s,
100
+ available_columns: @model_class.column_names
101
101
  )
102
102
  end
103
103
  end
@@ -110,12 +110,12 @@ module RiderKick
110
110
 
111
111
  if missing_fields.any?
112
112
  raise ValidationError.new(
113
- "Entity configuration error: Field(s) tidak ditemukan di model #{@model_class}",
114
- structure_file: "#{arg_structure}_structure.yaml",
115
- missing_fields: missing_fields,
116
- model_class: @model_class.to_s,
117
- available_columns: @model_class.column_names,
118
- suggestion: "Update section 'entity.db_attributes' di YAML file"
113
+ "Entity configuration error: Field(s) tidak ditemukan di model #{@model_class}",
114
+ structure_file: "#{arg_structure}_structure.yaml",
115
+ missing_fields: missing_fields,
116
+ model_class: @model_class.to_s,
117
+ available_columns: @model_class.column_names,
118
+ suggestion: "Update section 'entity.db_attributes' di YAML file"
119
119
  )
120
120
  end
121
121
  end
@@ -131,13 +131,13 @@ module RiderKick
131
131
  def setup_structure_config
132
132
  # Determine structure file path based on engine configuration
133
133
  structure_path = if RiderKick.configuration.engine_name.present?
134
- # For engines, read structure file from engine's db/structures directory
135
- engine_name = RiderKick.configuration.engine_name.downcase
136
- "engines/#{engine_name}/db/structures/#{arg_structure}_structure.yaml"
137
- else
138
- # For main app, read from host's db/structures directory
139
- "db/structures/#{arg_structure}_structure.yaml"
140
- end
134
+ # For engines, read structure file from engine's db/structures directory
135
+ engine_name = RiderKick.configuration.engine_name.downcase
136
+ "engines/#{engine_name}/db/structures/#{arg_structure}_structure.yaml"
137
+ else
138
+ # For main app, read from host's db/structures directory
139
+ "db/structures/#{arg_structure}_structure.yaml"
140
+ end
141
141
 
142
142
  validate_yaml_format!(structure_path)
143
143
  config = YAML.load_file(structure_path)
@@ -200,15 +200,15 @@ module RiderKick
200
200
 
201
201
  # Route scope
202
202
  # @route_scope_path = arg_scope.fetch('scope', '').to_s.downcase
203
- @route_scope_path = @structure.domain.to_s.downcase
203
+ @route_scope_path = @structure.scope.to_s.downcase
204
204
  @route_scope_class = @route_scope_path.camelize
205
205
 
206
206
  # Baca actor_id dari structure.yaml jika ada, jika tidak generate dari actor
207
207
  @actor_id = if @structure.actor_id.present?
208
- @structure.actor_id.to_s
209
- elsif @actor.present?
210
- "#{@actor.to_s.downcase}_id"
211
- end
208
+ @structure.actor_id.to_s
209
+ elsif @actor.present?
210
+ "#{@actor.to_s.downcase}_id"
211
+ end
212
212
 
213
213
  # Set flag untuk setiap action apakah resource_owner_id atau actor_id ada di contract
214
214
  # Ini digunakan di template repository untuk conditional logic
@@ -231,19 +231,19 @@ module RiderKick
231
231
 
232
232
  # Get contract untuk action tertentu
233
233
  contract = case action.to_s
234
- when 'list'
235
- @contract_list
236
- when 'fetch', 'fetch_by_id'
237
- @contract_fetch_by_id
238
- when 'create'
239
- @contract_create
240
- when 'update'
241
- @contract_update
242
- when 'destroy'
243
- @contract_destroy
244
- else
245
- []
246
- end
234
+ when 'list'
235
+ @contract_list
236
+ when 'fetch', 'fetch_by_id'
237
+ @contract_fetch_by_id
238
+ when 'create'
239
+ @contract_create
240
+ when 'update'
241
+ @contract_update
242
+ when 'destroy'
243
+ @contract_destroy
244
+ else
245
+ []
246
+ end
247
247
  contract ||= []
248
248
  # Check apakah contract string mengandung field name
249
249
  # Pattern: "required(:field_name)" atau "optional(:field_name)"
@@ -372,7 +372,7 @@ module RiderKick
372
372
 
373
373
  def contract_fields
374
374
  @model_class.columns.reject { |c| ['id', 'created_at', 'updated_at', 'type'].include?(c.name.to_s) }
375
- .map { |c| c.name.to_s }
375
+ .map { |c| c.name.to_s }
376
376
  end
377
377
 
378
378
  # --- AWAL BLOK MODIFIKASI: (PERBAIKAN KEGAGALAN #1) ---
@@ -70,7 +70,7 @@ RSpec.describe 'rider_kick:scaffold contracts (with scope)' do
70
70
 
71
71
  klass.new(['users', 'scope:dashboard']).generate_use_case
72
72
 
73
- base = RiderKick.configuration.domains_path + '/use_cases/dashboard/users'
73
+ RiderKick.configuration.domains_path
74
74
  ['owner_list_user', 'owner_fetch_user_by_id', 'owner_create_user', 'owner_update_user', 'owner_destroy_user'].each do |uc|
75
75
  # expect(File).to exist("#{base}/#{uc}.rb")
76
76
  end
@@ -47,10 +47,12 @@ RSpec.describe 'rider_kick:scaffold list spec generation' do
47
47
  resource_name: article
48
48
  actor: owner
49
49
  resource_owner_id: account_id
50
+ resource_owner: account
50
51
  domains:
51
52
  action_list:
52
53
  use_case:
53
- contract: []
54
+ contract:
55
+ - "required(:account_id).filled(:string)"
54
56
  repository:
55
57
  filters:
56
58
  - "{ field: 'title', type: 'search' }"
@@ -73,13 +75,15 @@ RSpec.describe 'rider_kick:scaffold list spec generation' do
73
75
  content = File.read(list_spec_file)
74
76
 
75
77
  # Verify format matches user's requirements
76
- expect(content).to include('let(:repository) { described_class }')
77
- expect(content).to include('repository.new(params: params).call')
78
- expect(content).to include('account_id: SecureRandom.uuid')
79
- expect(content).to include('create_list(:article, 3, account_id: params[:account_id])')
80
- expect(content).to include('context \'with search filters\'')
81
- expect(content).to include('context \'with resource owner filter\'')
82
- expect(content).to include('context \'with sorting\'')
78
+ expect(content).to include('require "rails_helper"')
79
+ expect(content).to include('subject(:result) { described_class.new(params: params).call }')
80
+ expect(content).to include('let(:account) { create(:account) }')
81
+ expect(content).to include('let(:account_id) { account.id }')
82
+ expect(content).to include('let!(:article_recent) { create(:article, account_id: account_id, created_at: 1.hour.ago) }')
83
+ expect(content).to include('let!(:other_account_article) { create(:article, account_id: other_account_id) }')
84
+ expect(content).to include('context "with valid params"')
85
+ expect(content).to include('context "with pagination"')
86
+ expect(content).to include('context "when account has no data"')
83
87
  end
84
88
  end
85
89
  end
@@ -142,9 +146,11 @@ RSpec.describe 'rider_kick:scaffold list spec generation' do
142
146
  content = File.read(list_spec_file)
143
147
 
144
148
  # Verify format without resource_owner_id
145
- expect(content).to include('let(:repository) { described_class }')
146
- expect(content).to include('repository.new(params: params).call')
147
- expect(content).to include('create_list(:product, 3)')
149
+ expect(content).to include('require "rails_helper"')
150
+ expect(content).to include('subject(:result) { described_class.new(params: params).call }')
151
+ expect(content).to include('let!(:product_recent) { create(:product, created_at: 1.hour.ago) }')
152
+ expect(content).to include('context "when no resources exist"')
153
+ expect(content).to include('Models::Product.destroy_all')
148
154
  expect(content).not_to include('account_id')
149
155
  expect(content).not_to include('user_id')
150
156
  end
@@ -131,23 +131,14 @@ RSpec.describe 'rider_kick:scaffold generator (RSpec generation)' do
131
131
 
132
132
  # Verifikasi konten spec file
133
133
  spec_content = File.read(spec_file)
134
- expect(spec_content).to include('require \'rails_helper\'')
134
+ expect(spec_content).to include('require "rails_helper"')
135
135
  expect(spec_content).to include('RSpec.describe')
136
- expect(spec_content).to include('describe \'#call\'')
136
+ expect(spec_content).to include('describe "#call"')
137
137
 
138
138
  # Only check Hashie::Mash for create, update, destroy, fetch_by_id (list uses simple hash)
139
139
  if ['create_product', 'update_product', 'destroy_product', 'fetch_product_by_id'].include?(repo)
140
140
  expect(spec_content).to include('Hashie::Mash.new')
141
141
  end
142
-
143
- # Verify error mocking for create, update, destroy (but not list or fetch_by_id)
144
- if ['create_product', 'update_product', 'destroy_product'].include?(repo)
145
- expect(spec_content).to include('let(:error_messages)')
146
- expect(spec_content).to include('let(:active_model_errors)')
147
- expect(spec_content).to include('allow(errors).to receive(:each)')
148
- expect(spec_content).to include('double(as_json:')
149
- expect(spec_content).to include("'options' => { 'message' => 'must be valid format' }")
150
- end
151
142
  end
152
143
 
153
144
  # Builder spec (covers entity validation too)
@@ -62,7 +62,7 @@ RSpec.describe 'rider_kick:scaffold generator (with scope)' do
62
62
  instance.generate_use_case
63
63
 
64
64
  # use_cases berada di app/domains/use_cases/dashboard/users/...
65
- path = RiderKick.configuration.domains_path + '/use_cases/dashboard/users'
65
+ RiderKick.configuration.domains_path
66
66
  ['owner_create_user', 'owner_update_user', 'owner_list_user', 'owner_destroy_user', 'owner_fetch_user_by_id'].each do |uc|
67
67
  # expect(File).to exist(File.join(path, "#{uc}.rb"))
68
68
  end
@@ -1,13 +1,48 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- # Optionally override some pagy default with your own in the pagy initializer
5
- Pagy::DEFAULT[:limit] = 30 # items per page
6
- Pagy::DEFAULT[:size] = 9 # nav bar links
7
- # Better user experience handled automatically
8
- require 'pagy/extras/overflow'
9
- Pagy::DEFAULT[:overflow] = :empty_page
10
- require 'pagy/extras/elasticsearch_rails'
11
- require 'pagy/extras/array'
3
+ # Pagy initializer file (43.2.4)
4
+ # See https://ddnexus.github.io/pagy/resources/initializer/
12
5
 
13
- include Pagy::Backend
6
+ ############ Global Options ################################################################
7
+ # See https://ddnexus.github.io/pagy/toolbox/options/ for details.
8
+ # Add your global options below. They will be applied globally.
9
+ # For example:
10
+ #
11
+ Pagy.options[:limit] = 10 # Limit the items per page
12
+ Pagy.options[:client_max_limit] = 50 # The client can request a limit up to 100
13
+ Pagy.options[:max_pages] = 200 # Allow only 200 pages
14
+ # Pagy.options[:jsonapi] = true # Use JSON:API compliant URLs
15
+
16
+
17
+ ############ JavaScript ####################################################################
18
+ # See https://ddnexus.github.io/pagy/resources/javascript/ for details.
19
+ # Examples for Rails:
20
+ # For apps with an assets pipeline
21
+ # Rails.application.config.assets.paths << Pagy::ROOT.join('javascripts')
22
+ #
23
+ # For apps with a javascript builder (e.g. esbuild, webpack, etc.)
24
+ # javascript_dir = Rails.root.join('app/javascript')
25
+ # Pagy.sync_javascript(javascript_dir, 'pagy.mjs') if Rails.env.development?
26
+
27
+
28
+ ############# Overriding Pagy::I18n Lookup #################################################
29
+ # Refer to https://ddnexus.github.io/pagy/resources/i18n/ for details.
30
+ # Override the I18n lookup by dropping your custom dictionary in some pagy dir.
31
+ # Example for Rails:
32
+ #
33
+ # Pagy::I18n.pathnames << Rails.root.join('config/locales/pagy')
34
+
35
+
36
+ ############# I18n Gem Translation #########################################################
37
+ # See https://ddnexus.github.io/pagy/resources/i18n/ for details.
38
+ #
39
+ # Pagy.translate_with_the_slower_i18n_gem!
40
+
41
+
42
+ ############# Calendar Localization for non-en locales ####################################
43
+ # See https://ddnexus.github.io/pagy/toolbox/paginators/calendar#localization for details.
44
+ # Add your desired locales to the list and uncomment the following line to enable them,
45
+ # regardless of whether you use the I18n gem for translations or not, whether with
46
+ # Rails or not.
47
+ #
48
+ # Pagy::Calendar.localize_with_rails_i18n_gem(*your_locales)
@@ -8,8 +8,7 @@ resource_owner_id: <%= @resource_owner_id %> # account_id
8
8
  resource_owner: <%= @resource_owner %> # account
9
9
  actor: <%= @actor %> # user
10
10
  actor_id: <%= @actor_id %> # user_id
11
- scope: '' #dashboard
12
- domain: '' # core
11
+ scope: '' # dashboard
13
12
 
14
13
  fields:
15
14
  <% @fields.each do |f| -%>
@@ -6,30 +6,41 @@ RSpec.describe <%= domain_class_name %>::Builders::<%= @subject_class %>, type:
6
6
  describe '#build' do
7
7
  let(:<%= @variable_subject %>) do
8
8
  ClassStubber::Model.new(
9
- 'id' => 'test-id-123',
10
- <%
9
+ 'id' => SecureRandom.uuid,
10
+ <%
11
11
  # Get uploader field names to exclude from entity_db_fields
12
12
  uploader_names = @uploaders.map { |u| u.name.to_s }
13
13
  -%>
14
14
  <% @entity_db_fields.each do |field| -%>
15
15
  <% next if uploader_names.include?(field.to_s) -%>
16
16
  <% column_meta = get_column_meta(field) -%>
17
- <% case column_meta[:type].to_s -%>
18
- <% when 'datetime', 'timestamp' -%>
19
- '<%= field %>' => Time.current,
20
- <% when 'date' -%>
21
- '<%= field %>' => Date.current,
22
- <% when 'time' -%>
23
- '<%= field %>' => Time.current,
24
- <% when 'boolean' -%>
25
- '<%= field %>' => true,
26
- <% when 'integer', 'bigint' -%>
27
- '<%= field %>' => 123,
28
- <% when 'decimal', 'float' -%>
29
- '<%= field %>' => 123.45,
30
- <% else -%>
31
- '<%= field %>' => '<%= field %>_value',
32
- <% end -%>
17
+ <%
18
+ faker_val = case field.to_s
19
+ when /email/ then "Faker::Internet.email"
20
+ when /name/ then "Faker::Name.name"
21
+ when /first_name/ then "Faker::Name.first_name"
22
+ when /last_name/ then "Faker::Name.last_name"
23
+ when /title/ then "Faker::Book.title"
24
+ when /description/ then "Faker::Lorem.paragraph"
25
+ when /address/ then "Faker::Address.full_address"
26
+ when /phone/ then "Faker::PhoneNumber.cell_phone"
27
+ when /url/, /website/ then "Faker::Internet.url"
28
+ when /price/ then "Faker::Commerce.price"
29
+ else
30
+ case column_meta[:type].to_s
31
+ when 'datetime', 'timestamp' then "Faker::Time.backward(days: 14)"
32
+ when 'date' then "Faker::Date.backward(days: 14)"
33
+ when 'time' then "Faker::Time.backward(days: 14)"
34
+ when 'boolean' then "Faker::Boolean.boolean"
35
+ when 'integer', 'bigint' then "Faker::Number.number(digits: 2)"
36
+ when 'float' then "Faker::Number.decimal(l_digits: 2, r_digits: 2).to_f"
37
+ when 'decimal' then "Faker::Number.decimal(l_digits: 2, r_digits: 2).to_d"
38
+ when 'uuid' then "SecureRandom.uuid"
39
+ else "Faker::Lorem.word"
40
+ end
41
+ end
42
+ -%>
43
+ '<%= field %>' => <%= faker_val %>,
33
44
  <% end -%>
34
45
  <% @uploaders.each do |uploader| -%>
35
46
  <% if uploader.type == 'single' -%>
@@ -52,7 +63,7 @@ uploader_names = @uploaders.map { |u| u.name.to_s }
52
63
  entity = builder.build
53
64
 
54
65
  expect(entity).to be_a(<%= domain_class_name %>::Entities::<%= @subject_class %>)
55
- expect(entity.id).to eq('test-id-123')
66
+ expect(entity.id).to be_a(String)
56
67
  <% @entity_db_fields.first(2).each do |field| -%>
57
68
  <% column_meta = get_column_meta(field) -%>
58
69
  <% case column_meta[:type].to_s -%>
@@ -61,20 +72,22 @@ uploader_names = @uploaders.map { |u| u.name.to_s }
61
72
  <% when 'date' -%>
62
73
  expect(entity.<%= field %>).to be_a(Date)
63
74
  <% when 'boolean' -%>
64
- expect(entity.<%= field %>).to eq(true)
75
+ expect([true, false]).to include(entity.<%= field %>)
65
76
  <% when 'integer', 'bigint' -%>
66
- expect(entity.<%= field %>).to eq(123)
77
+ expect(entity.<%= field %>).to be_a(Integer)
67
78
  <% when 'decimal', 'float' -%>
68
- expect(entity.<%= field %>).to eq(123.45)
79
+ expect(entity.<%= field %>).to be_a(Numeric)
80
+ <% when 'uuid' -%>
81
+ expect(entity.<%= field %>).to be_a(String)
69
82
  <% else -%>
70
- expect(entity.<%= field %>).to eq('<%= field %>_value')
83
+ expect(entity.<%= field %>).to be_a(String)
71
84
  <% end -%>
72
85
  <% end -%>
73
86
  end
74
87
 
75
88
  it 'includes all required entity attributes', :aggregate_failures do
76
89
  entity = builder.build
77
-
90
+
78
91
  # Entity must have these attributes (keys must exist, values can be nil for optional)
79
92
  expect(entity).to respond_to(:id)
80
93
  <% @entity_db_fields.each do |field| -%>
@@ -96,7 +109,7 @@ uploader_names = @uploaders.map { |u| u.name.to_s }
96
109
  describe '#attributes_for_entity' do
97
110
  it 'returns hash with uploader attributes', :aggregate_failures do
98
111
  attributes = builder.send(:attributes_for_entity)
99
-
112
+
100
113
  expect(attributes).to be_a(Hash)
101
114
  <% @uploaders.each do |uploader| -%>
102
115
  <% if uploader.type == 'single' -%>
@@ -109,7 +122,7 @@ uploader_names = @uploaders.map { |u| u.name.to_s }
109
122
 
110
123
  it 'generates correct URL attributes', :aggregate_failures do
111
124
  attributes = builder.send(:attributes_for_entity)
112
-
125
+
113
126
  <% @uploaders.each do |uploader| -%>
114
127
  <% if uploader.type == 'single' -%>
115
128
  expect(attributes[:<%= uploader.name %>_url]).to eq('http://example.com/<%= uploader.name %>.jpg')
@@ -125,14 +138,14 @@ uploader_names = @uploaders.map { |u| u.name.to_s }
125
138
  it 'ensures all entity attributes have correct keys and types', :aggregate_failures do
126
139
  entity = builder.build
127
140
  entity_hash = entity.to_h
128
-
141
+
129
142
  # All entity attributes must have keys in the hash (source of truth from entity)
130
143
  expect(entity_hash).to have_key(:id)
131
144
  expect(entity.id).to be_a(String)
132
145
  <% @entity_db_fields.each do |field| -%>
133
146
  <% next if uploader_names.include?(field.to_s) -%>
134
147
  <% column_meta = get_column_meta(field) -%>
135
-
148
+
136
149
  expect(entity_hash).to have_key(:<%= field %>)
137
150
  <% case column_meta[:type].to_s -%>
138
151
  <% when 'datetime', 'timestamp', 'time' -%>
@@ -151,19 +164,19 @@ uploader_names = @uploaders.map { |u| u.name.to_s }
151
164
  <% end -%>
152
165
  <% @uploaders.each do |uploader| -%>
153
166
  <% if uploader.type == 'single' -%>
154
-
167
+
155
168
  expect(entity_hash).to have_key(:<%= uploader.name %>_url)
156
169
  expect(entity.<%= uploader.name %>_url).to be_a(String).or be_nil
157
170
  <% else -%>
158
-
171
+
159
172
  expect(entity_hash).to have_key(:<%= uploader.name %>_urls)
160
173
  expect(entity.<%= uploader.name %>_urls).to be_a(Array)
161
174
  <% end -%>
162
175
  <% end -%>
163
-
176
+
164
177
  expect(entity_hash).to have_key(:created_at)
165
178
  expect(entity.created_at).to be_a(Time) if entity.created_at.present?
166
-
179
+
167
180
  expect(entity_hash).to have_key(:updated_at)
168
181
  expect(entity.updated_at).to be_a(Time) if entity.updated_at.present?
169
182
  end
@@ -171,7 +184,7 @@ uploader_names = @uploaders.map { |u| u.name.to_s }
171
184
  it 'validates entity type schema definitions', :aggregate_failures do
172
185
  # Verify entity has correct Dry::Types definitions
173
186
  schema = <%= domain_class_name %>::Entities::<%= @subject_class %>.schema
174
-
187
+
175
188
  # ID must be required (not optional)
176
189
  id_key = schema.key(:id)
177
190
  expect(id_key.required?).to be true
@@ -180,7 +193,7 @@ uploader_names = @uploaders.map { |u| u.name.to_s }
180
193
  <% @entity_db_fields.each do |field| -%>
181
194
  <% next if uploader_names.include?(field.to_s) -%>
182
195
  <% column_meta = get_column_meta(field) -%>
183
-
196
+
184
197
  # <%= field %>: <%= column_meta[:null] ? 'optional' : 'required' %>
185
198
  <% if column_meta[:null] -%>
186
199
  # Optional field - can be nil
@@ -195,22 +208,22 @@ uploader_names = @uploaders.map { |u| u.name.to_s }
195
208
  <% end -%>
196
209
  <% @uploaders.each do |uploader| -%>
197
210
  <% if uploader.type == 'single' -%>
198
-
211
+
199
212
  # <%= uploader.name %>_url: optional (uploader can be nil)
200
213
  <%= uploader.name %>_url_key = schema.key(:<%= uploader.name %>_url)
201
214
  expect(<%= uploader.name %>_url_key.required?).to be false
202
215
  <% else -%>
203
-
216
+
204
217
  # <%= uploader.name %>_urls: required (but can be empty array)
205
218
  <%= uploader.name %>_urls_key = schema.key(:<%= uploader.name %>_urls)
206
219
  expect(<%= uploader.name %>_urls_key.required?).to be true
207
220
  <% end -%>
208
221
  <% end -%>
209
-
222
+
210
223
  # Timestamps - check their actual definition
211
224
  created_at_key = schema.key(:created_at)
212
225
  updated_at_key = schema.key(:updated_at)
213
-
226
+
214
227
  # Verify timestamps exist (they might be optional in some entities)
215
228
  expect(schema.keys.map(&:name)).to include(:created_at, :updated_at)
216
229
  end
@@ -11,12 +11,14 @@ class <%= domain_class_name %>::Repositories::AbstractRepository
11
11
  record.errors.to_a.join(', ')
12
12
  end
13
13
 
14
+ def build_error(code, message, attribute: nil)
15
+ [{ code: code, message: message, attribute: attribute }]
16
+ end
17
+
14
18
  def build_errors(resource)
15
- errors = []
16
- resource.errors.each do |error|
17
- errors << <%= domain_class_name %>::Builders::Error.new(error.as_json).build
19
+ resource.errors.map do |error|
20
+ { code: error.type.to_s, message: error.message, attribute: error.attribute.to_s }
18
21
  end
19
- errors
20
22
  end
21
23
 
22
24
  def prepare!(params, sanitize: true)
@@ -33,7 +35,7 @@ class <%= domain_class_name %>::Repositories::AbstractRepository
33
35
 
34
36
  def build_pagination_variable!(params)
35
37
  @per_page = params[:per_page] || Pagy::DEFAULT[:limit]
36
- @page = params[:page] || Pagy::DEFAULT[:page]
38
+ @page = params[:page] || 1
37
39
  @search = params[:search]
38
40
  end
39
41
  end