From 2b46d15f82b79e29204a7b18bb14afe36c6e68fb Mon Sep 17 00:00:00 2001 From: Jane-Terziev Date: Mon, 19 Feb 2024 10:11:09 +0100 Subject: [PATCH] Made gem configurable, added both bootstrap5 and beercss views based on configuration, added read service and specs --- lib/dry_module_generator.rb | 5 +- .../config/configuration.rb | 31 ++++ .../config/generator_configuration.rb | 13 ++ lib/dry_module_generator/install/installer.rb | 158 +++++++++--------- .../{ => app}/errors/constraint_error.rb.tt | 0 .../templates/app/models/aggregate_root.rb.tt | 14 ++ .../app/models/application_record.rb.tt | 11 ++ .../app/models/current_user_repository.rb.tt | 3 + .../app/services/application_service.rb.tt | 20 +++ .../app/services/async_event_handler.rb.tt | 7 + .../services/async_thread_event_handler.rb.tt | 3 + .../app/views/shared/_flash.html.erb.tt | 12 ++ .../initializers/active_job_serializers.rb.tt | 3 + .../config/initializers/container.rb.tt | 55 ++++++ .../initializers/dependency_injection.rb.tt | 2 + .../initializers/dry_module_generator.rb.tt | 5 + .../initializers/dry_struct_generator.rb.tt | 0 .../{ => config}/initializers/routes.rb.tt | 0 .../templates/initializers/container.rb.tt | 13 -- .../controllers/form_controller.js.tt | 14 -- .../lib/utils/application_contract.rb.tt | 19 +++ .../utils/application_read_struct.rb.tt | 0 .../{ => lib}/utils/application_struct.rb.tt | 0 .../templates/lib/utils/bean_proxy.rb.tt | 15 ++ .../lib/utils/command_serializer.rb.tt | 15 ++ .../concurrent/async_job_scheduler.rb.tt | 42 +++++ .../{ => lib}/utils/contract_validator.rb.tt | 3 +- .../events/active_job_event_scheduler.rb.tt | 5 + .../events/async_thread_event_scheduler.rb.tt | 25 +++ ...immediate_active_job_event_scheduler.rb.tt | 5 + .../events/immediate_event_scheduler.rb.tt | 16 ++ .../lib/utils/events/integration_event.rb.tt | 19 +++ .../events/metadata_event_publisher.rb.tt | 37 ++++ .../utils/events/no_op_event_repository.rb.tt | 42 +++++ .../utils/events/to_yaml_event_mapper.rb.tt | 13 ++ .../utils/injection/active_job_strategy.rb.tt | 48 ++++++ .../controller_resolve_strategy.rb.tt | 0 .../templates/lib/utils/list_query.rb.tt | 5 + .../templates/lib/utils/optional.rb.tt | 97 +++++++++++ .../templates/lib/utils/pagination_dto.rb.tt | 9 + .../lib/utils/pagination_list_dto.rb.tt | 6 + .../templates/{ => lib}/utils/types.rb.tt | 0 .../services/application_service.rb.tt | 5 - .../utils/application_contract.rb.tt | 32 ---- lib/dry_module_generator/module/generator.rb | 85 +++++++--- .../module/templates/app/details_dto.rb.tt | 10 -- .../module/templates/app/list_dto.rb.tt | 10 -- .../app/read_service/get_details_dto.rb.tt | 10 ++ .../app/read_service/get_list_dto.rb.tt | 10 ++ .../templates/app/read_service/service.rb.tt | 23 +++ .../module/templates/app/service.rb.tt | 31 ++-- .../domain/events/created_event.rb.tt | 16 ++ .../domain/events/deleted_event.rb.tt | 16 ++ .../domain/events/updated_event.rb.tt | 16 ++ .../module/templates/domain/model.rb.tt | 27 ++- .../templates/infra/config/application.rb.tt | 10 +- .../templates/infra/config/importmap.rb.tt | 5 + .../templates/infra/config/routes.rb.tt | 4 +- .../infra/db/migrate/migration.rb.tt | 5 +- .../infra/system/provider_source.rb.tt | 5 +- .../spec/app/read_service/service_spec.rb.tt | 47 ++++++ ...{service_test.rb.tt => service_spec.rb.tt} | 27 ++- .../templates/spec/domain/model_spec.rb.tt | 37 ++++ ...oller_test.rb.tt => controller_spec.rb.tt} | 0 ...ation_test.rb.tt => validation_spec.rb.tt} | 0 .../module/templates/ui/controller.rb.tt | 32 +++- .../templates/ui/create_validation.rb.tt | 9 + ...lidation.rb.tt => update_validation.rb.tt} | 6 +- .../ui/views/beercss/erb/_form.html.erb.tt | 10 ++ .../beercss/erb/_table_filter.html.erb.tt | 14 ++ .../ui/views/beercss/erb/edit.html.erb.tt | 5 + .../ui/views/beercss/erb/index.html.erb.tt | 48 ++++++ .../ui/views/beercss/erb/new.html.erb.tt | 5 + .../ui/views/beercss/erb/show.html.erb.tt | 16 ++ .../erb/_form.html.erb.tt} | 4 +- .../bootstrap5/erb/_table_filter.html.erb.tt | 12 ++ .../erb/edit.html.erb.tt} | 4 +- .../erb/index.html.erb.tt} | 8 +- .../ui/views/bootstrap5/erb/new.html.erb.tt | 4 + .../erb/show.html.erb.tt} | 4 +- .../module/templates/ui/views/new.rb.tt | 7 - .../uninstall/uninstaller.rb | 33 ---- 82 files changed, 1144 insertions(+), 298 deletions(-) create mode 100644 lib/dry_module_generator/config/configuration.rb create mode 100644 lib/dry_module_generator/config/generator_configuration.rb rename lib/dry_module_generator/install/templates/{ => app}/errors/constraint_error.rb.tt (100%) create mode 100644 lib/dry_module_generator/install/templates/app/models/aggregate_root.rb.tt create mode 100644 lib/dry_module_generator/install/templates/app/models/application_record.rb.tt create mode 100644 lib/dry_module_generator/install/templates/app/models/current_user_repository.rb.tt create mode 100644 lib/dry_module_generator/install/templates/app/services/application_service.rb.tt create mode 100644 lib/dry_module_generator/install/templates/app/services/async_event_handler.rb.tt create mode 100644 lib/dry_module_generator/install/templates/app/services/async_thread_event_handler.rb.tt create mode 100644 lib/dry_module_generator/install/templates/app/views/shared/_flash.html.erb.tt create mode 100644 lib/dry_module_generator/install/templates/config/initializers/active_job_serializers.rb.tt create mode 100644 lib/dry_module_generator/install/templates/config/initializers/container.rb.tt rename lib/dry_module_generator/install/templates/{ => config}/initializers/dependency_injection.rb.tt (52%) create mode 100644 lib/dry_module_generator/install/templates/config/initializers/dry_module_generator.rb.tt rename lib/dry_module_generator/install/templates/{ => config}/initializers/dry_struct_generator.rb.tt (100%) rename lib/dry_module_generator/install/templates/{ => config}/initializers/routes.rb.tt (100%) delete mode 100644 lib/dry_module_generator/install/templates/initializers/container.rb.tt delete mode 100644 lib/dry_module_generator/install/templates/javascript/controllers/form_controller.js.tt create mode 100644 lib/dry_module_generator/install/templates/lib/utils/application_contract.rb.tt rename lib/dry_module_generator/install/templates/{ => lib}/utils/application_read_struct.rb.tt (100%) rename lib/dry_module_generator/install/templates/{ => lib}/utils/application_struct.rb.tt (100%) create mode 100644 lib/dry_module_generator/install/templates/lib/utils/bean_proxy.rb.tt create mode 100644 lib/dry_module_generator/install/templates/lib/utils/command_serializer.rb.tt create mode 100644 lib/dry_module_generator/install/templates/lib/utils/concurrent/async_job_scheduler.rb.tt rename lib/dry_module_generator/install/templates/{ => lib}/utils/contract_validator.rb.tt (78%) create mode 100644 lib/dry_module_generator/install/templates/lib/utils/events/active_job_event_scheduler.rb.tt create mode 100644 lib/dry_module_generator/install/templates/lib/utils/events/async_thread_event_scheduler.rb.tt create mode 100644 lib/dry_module_generator/install/templates/lib/utils/events/immediate_active_job_event_scheduler.rb.tt create mode 100644 lib/dry_module_generator/install/templates/lib/utils/events/immediate_event_scheduler.rb.tt create mode 100644 lib/dry_module_generator/install/templates/lib/utils/events/integration_event.rb.tt create mode 100644 lib/dry_module_generator/install/templates/lib/utils/events/metadata_event_publisher.rb.tt create mode 100644 lib/dry_module_generator/install/templates/lib/utils/events/no_op_event_repository.rb.tt create mode 100644 lib/dry_module_generator/install/templates/lib/utils/events/to_yaml_event_mapper.rb.tt create mode 100644 lib/dry_module_generator/install/templates/lib/utils/injection/active_job_strategy.rb.tt rename lib/dry_module_generator/install/templates/{ => lib}/utils/injection/controller_resolve_strategy.rb.tt (100%) create mode 100644 lib/dry_module_generator/install/templates/lib/utils/list_query.rb.tt create mode 100644 lib/dry_module_generator/install/templates/lib/utils/optional.rb.tt create mode 100644 lib/dry_module_generator/install/templates/lib/utils/pagination_dto.rb.tt create mode 100644 lib/dry_module_generator/install/templates/lib/utils/pagination_list_dto.rb.tt rename lib/dry_module_generator/install/templates/{ => lib}/utils/types.rb.tt (100%) delete mode 100644 lib/dry_module_generator/install/templates/services/application_service.rb.tt delete mode 100644 lib/dry_module_generator/install/templates/utils/application_contract.rb.tt delete mode 100644 lib/dry_module_generator/module/templates/app/details_dto.rb.tt delete mode 100644 lib/dry_module_generator/module/templates/app/list_dto.rb.tt create mode 100644 lib/dry_module_generator/module/templates/app/read_service/get_details_dto.rb.tt create mode 100644 lib/dry_module_generator/module/templates/app/read_service/get_list_dto.rb.tt create mode 100644 lib/dry_module_generator/module/templates/app/read_service/service.rb.tt create mode 100644 lib/dry_module_generator/module/templates/domain/events/created_event.rb.tt create mode 100644 lib/dry_module_generator/module/templates/domain/events/deleted_event.rb.tt create mode 100644 lib/dry_module_generator/module/templates/domain/events/updated_event.rb.tt create mode 100644 lib/dry_module_generator/module/templates/infra/config/importmap.rb.tt create mode 100644 lib/dry_module_generator/module/templates/spec/app/read_service/service_spec.rb.tt rename lib/dry_module_generator/module/templates/spec/app/{service_test.rb.tt => service_spec.rb.tt} (74%) create mode 100644 lib/dry_module_generator/module/templates/spec/domain/model_spec.rb.tt rename lib/dry_module_generator/module/templates/spec/ui/{controller_test.rb.tt => controller_spec.rb.tt} (100%) rename lib/dry_module_generator/module/templates/spec/ui/{validation_test.rb.tt => validation_spec.rb.tt} (100%) create mode 100644 lib/dry_module_generator/module/templates/ui/create_validation.rb.tt rename lib/dry_module_generator/module/templates/ui/{validation.rb.tt => update_validation.rb.tt} (71%) create mode 100644 lib/dry_module_generator/module/templates/ui/views/beercss/erb/_form.html.erb.tt create mode 100644 lib/dry_module_generator/module/templates/ui/views/beercss/erb/_table_filter.html.erb.tt create mode 100644 lib/dry_module_generator/module/templates/ui/views/beercss/erb/edit.html.erb.tt create mode 100644 lib/dry_module_generator/module/templates/ui/views/beercss/erb/index.html.erb.tt create mode 100644 lib/dry_module_generator/module/templates/ui/views/beercss/erb/new.html.erb.tt create mode 100644 lib/dry_module_generator/module/templates/ui/views/beercss/erb/show.html.erb.tt rename lib/dry_module_generator/module/templates/ui/views/{form.rb.tt => bootstrap5/erb/_form.html.erb.tt} (94%) create mode 100644 lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/_table_filter.html.erb.tt rename lib/dry_module_generator/module/templates/ui/views/{edit.rb.tt => bootstrap5/erb/edit.html.erb.tt} (64%) rename lib/dry_module_generator/module/templates/ui/views/{index.rb.tt => bootstrap5/erb/index.html.erb.tt} (91%) create mode 100644 lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/new.html.erb.tt rename lib/dry_module_generator/module/templates/ui/views/{show.rb.tt => bootstrap5/erb/show.html.erb.tt} (93%) delete mode 100644 lib/dry_module_generator/module/templates/ui/views/new.rb.tt delete mode 100644 lib/dry_module_generator/uninstall/uninstaller.rb diff --git a/lib/dry_module_generator.rb b/lib/dry_module_generator.rb index fbec52a..52cda55 100644 --- a/lib/dry_module_generator.rb +++ b/lib/dry_module_generator.rb @@ -4,9 +4,10 @@ require "rails/generators" require_relative "dry_module_generator/install/installer" require_relative "dry_module_generator/module/generator" -require_relative "dry_module_generator/uninstall/uninstaller" +require_relative "dry_module_generator/config/configuration" +require_relative "dry_module_generator/config/generator_configuration" module DryModuleGenerator class Error < StandardError; end - # Your code goes here... + class ConfigurationError < StandardError; end end diff --git a/lib/dry_module_generator/config/configuration.rb b/lib/dry_module_generator/config/configuration.rb new file mode 100644 index 0000000..5cbbd83 --- /dev/null +++ b/lib/dry_module_generator/config/configuration.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module DryModuleGenerator + module Config + module Configuration + def configure + yield self + end + + def define_setting(name, default = nil) + class_variable_set("@@#{name}", default) + + define_class_method "#{name}=" do |value| + class_variable_set("@@#{name}", value) + end + + define_class_method name do + class_variable_get("@@#{name}") + end + end + + private + + def define_class_method(name, &block) + (class << self; self; end).instance_eval do + define_method name, &block + end + end + end + end +end diff --git a/lib/dry_module_generator/config/generator_configuration.rb b/lib/dry_module_generator/config/generator_configuration.rb new file mode 100644 index 0000000..243571e --- /dev/null +++ b/lib/dry_module_generator/config/generator_configuration.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module DryModuleGenerator + module Config + module GeneratorConfiguration + extend Configuration + + define_setting :include_views, true + define_setting :css_framework, "bootstrap5" + define_setting :html_style, "erb" + end + end +end \ No newline at end of file diff --git a/lib/dry_module_generator/install/installer.rb b/lib/dry_module_generator/install/installer.rb index 3009500..cfa3cac 100644 --- a/lib/dry_module_generator/install/installer.rb +++ b/lib/dry_module_generator/install/installer.rb @@ -3,71 +3,12 @@ module DryModuleGenerator class Installer < Rails::Generators::Base source_root File.expand_path("templates", __dir__) - namespace "dry_module:setup" + namespace "dry_module:install" - def update_application_record - file_path = "app/models/application_record.rb" - class_name = "ApplicationRecord" - - # Read the existing content of the file - file_content = File.read(file_path) - - # Define the code you want to add - additional_code = " - - def self.save(record) - record.tap(&:save) - end - - def self.save!(record) - record.tap(&:save!) - end - - def self.delete!(record) - record.tap(&:destroy!) - end" - - # Check if the class is present in the file - if file_content.include?("class #{class_name}") && !file_content.include?("def self.save!") - # If the class is present, find the end of the class definition and add the code there - class_definition_end = file_content.index("end", file_content.index("class #{class_name}")) - if class_definition_end - inject_code_position = class_definition_end - 1 - file_content.insert(inject_code_position, additional_code) - File.write(file_path, file_content) - else - puts "Error: Unable to find the end of the class definition in #{file_path}." - end - else - puts "Error: Unable to find the class definition for #{class_name} in #{file_path}." - end - end - - def create_utils - template("utils/contract_validator.rb", File.join("lib/utils/contract_validator.rb")) - template("utils/types.rb", File.join("lib/utils/types.rb")) - template("utils/application_contract.rb", File.join("lib/utils/application_contract.rb")) - template("utils/application_struct.rb", File.join("lib/utils/application_struct.rb")) - template("utils/application_read_struct.rb", File.join("lib/utils/application_read_struct.rb")) - template( - "utils/injection/controller_resolve_strategy.rb", - File.join("lib/utils/injection/controller_resolve_strategy.rb") - ) - end - - def create_application_service - template("services/application_service.rb", File.join("app/services/application_service.rb")) - end - - def create_constraint_error - template("errors/constraint_error.rb", File.join("app/errors/constraint_error.rb")) - end - - def create_initializers - template("initializers/container.rb", File.join("config/initializers/container.rb")) - template("initializers/dependency_injection.rb", File.join("config/initializers/dependency_injection.rb")) - template("initializers/dry_struct_generator.rb", File.join("config/initializers/dry_struct_generator.rb")) - template("initializers/routes.rb", File.join("config/initializers/routes.rb")) + def create_utility_files + full_file_paths = Dir.glob(File.join(self.class.source_root, '**', '**')).filter {|it| it.include?('.rb.tt') } + file_names = full_file_paths.map {|it| it.split('templates/').last.chop.chop.chop } + file_names.each {|name| template(name, File.join(name)) } end def update_application @@ -87,18 +28,7 @@ def update_application_controller return if file_content.include?("ConstraintError") inject_into_class file_path, "ApplicationController" do - " include Import.inject[validator: 'contract_validator'] - - rescue_from(ConstraintError) do |e| - @form = e.validator - if action_name == 'create' - render :new, status: :unprocessable_entity - elsif action_name == 'update' - render :edit, status: :unprocessable_entity - end - end - -" + " include Import.inject[validator: 'contract_validator']" end end @@ -106,10 +36,9 @@ def update_application_helper file_path = "app/helpers/application_helper.rb" file_content = File.read(file_path) - return if file_content.include?("def show_error") - - inject_into_module file_path, "ApplicationHelper" do - <<-"CODE" + unless file_content.include?("def show_error") + inject_into_module file_path, "ApplicationHelper" do + <<-"CODE" def show_error(validator, keys) return unless validator.errors keys = [keys] unless keys.is_a?(Array) @@ -134,12 +63,75 @@ def find_value(hash, keys) end result end - CODE + + def invalid?(validator, keys) + if show_error(validator, keys) + 'invalid' + else + '' + end + end + CODE + end + end + + unless file_content.include?("include Pagy::Frontend") + inject_into_module file_path, "ApplicationHelper" do + " include Pagy::Frontend + +" + end end end - def create_javascripts - template("javascript/controllers/form_controller.js", File.join("app/javascript/controllers/form_controller.js")) + def update_gemfile + file_path = "Gemfile" + file_content = File.read(file_path) + + append_to_file(file_path) do + " +gem 'sass-rails'" + end unless file_content.include?('sass-rails') + + append_to_file(file_path) do + " +gem 'dry-validation'" + end unless file_content.include?('dry-validation') + + append_to_file(file_path) do + " +gem 'dry-struct'" + end unless file_content.include?('dry-struct') + + append_to_file(file_path) do + " +gem 'dry-system', '~> 1'" + end unless file_content.include?('dry-system') + + append_to_file(file_path) do + " +gem 'dry_struct_generator'" + end unless file_content.include?('dry_struct_generator') + + append_to_file(file_path) do + " +gem 'dry_object_mapper'" + end unless file_content.include?('dry_object_mapper') + + append_to_file(file_path) do + " +gem 'pagy'" + end unless file_content.include?('pagy') + + append_to_file(file_path) do + " +gem 'ransack'" + end unless file_content.include?('ransack') + + append_to_file(file_path) do + " +gem 'rails_event_store'" + end unless file_content.include?('rails_event_store') end end end diff --git a/lib/dry_module_generator/install/templates/errors/constraint_error.rb.tt b/lib/dry_module_generator/install/templates/app/errors/constraint_error.rb.tt similarity index 100% rename from lib/dry_module_generator/install/templates/errors/constraint_error.rb.tt rename to lib/dry_module_generator/install/templates/app/errors/constraint_error.rb.tt diff --git a/lib/dry_module_generator/install/templates/app/models/aggregate_root.rb.tt b/lib/dry_module_generator/install/templates/app/models/aggregate_root.rb.tt new file mode 100644 index 0000000..3429c8d --- /dev/null +++ b/lib/dry_module_generator/install/templates/app/models/aggregate_root.rb.tt @@ -0,0 +1,14 @@ +class AggregateRoot < ApplicationRecord + self.abstract_class = true + + def domain_events + @domain_events ||= [] + end + + def apply_event(event) + domain_events << event + self.updated_at = DateTime.now + end + + attr_writer :domain_events +end diff --git a/lib/dry_module_generator/install/templates/app/models/application_record.rb.tt b/lib/dry_module_generator/install/templates/app/models/application_record.rb.tt new file mode 100644 index 0000000..c4e22d2 --- /dev/null +++ b/lib/dry_module_generator/install/templates/app/models/application_record.rb.tt @@ -0,0 +1,11 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class + + def self.save!(record) + record.tap(&:save!) + end + + def self.delete!(record) + record.tap(&:destroy!) + end +end diff --git a/lib/dry_module_generator/install/templates/app/models/current_user_repository.rb.tt b/lib/dry_module_generator/install/templates/app/models/current_user_repository.rb.tt new file mode 100644 index 0000000..9987653 --- /dev/null +++ b/lib/dry_module_generator/install/templates/app/models/current_user_repository.rb.tt @@ -0,0 +1,3 @@ +class CurrentUserRepository < ActiveSupport::CurrentAttributes + attribute :authenticated_identity +end \ No newline at end of file diff --git a/lib/dry_module_generator/install/templates/app/services/application_service.rb.tt b/lib/dry_module_generator/install/templates/app/services/application_service.rb.tt new file mode 100644 index 0000000..cbab8a9 --- /dev/null +++ b/lib/dry_module_generator/install/templates/app/services/application_service.rb.tt @@ -0,0 +1,20 @@ +class ApplicationService + include Import[event_publisher: 'events.publisher', current_user_repository: 'current_user_repository'] + include Pagy::Backend + + def paginate_collection(collection:, mapper:, page:, page_size:, filter:, size: 5, options: {}) + result = pagy(collection.ransack(filter).result, items: page_size, page: page, size: size) + + pagy_metadata = result[0] + paginated_data = result[1] + + PaginationListDto.new( + data: map_into(paginated_data, mapper, options), + pagination: map_into(pagy_metadata, PaginationDto) + ) + end + + def map_into(data, mapper, options = {}) + DryObjectMapper::Mapper.call(data, mapper, options) + end +end \ No newline at end of file diff --git a/lib/dry_module_generator/install/templates/app/services/async_event_handler.rb.tt b/lib/dry_module_generator/install/templates/app/services/async_event_handler.rb.tt new file mode 100644 index 0000000..957e6e1 --- /dev/null +++ b/lib/dry_module_generator/install/templates/app/services/async_event_handler.rb.tt @@ -0,0 +1,7 @@ +class AsyncEventHandler < ApplicationJob + def call(_event); end + + def perform(serialized_event) + call(App::Container.resolve('events.publisher').deserialize(serialized_event, YAML)) + end +end diff --git a/lib/dry_module_generator/install/templates/app/services/async_thread_event_handler.rb.tt b/lib/dry_module_generator/install/templates/app/services/async_thread_event_handler.rb.tt new file mode 100644 index 0000000..fea0ebf --- /dev/null +++ b/lib/dry_module_generator/install/templates/app/services/async_thread_event_handler.rb.tt @@ -0,0 +1,3 @@ +# Just a marker abstract class +class AsyncThreadEventHandler +end diff --git a/lib/dry_module_generator/install/templates/app/views/shared/_flash.html.erb.tt b/lib/dry_module_generator/install/templates/app/views/shared/_flash.html.erb.tt new file mode 100644 index 0000000..3dc88c7 --- /dev/null +++ b/lib/dry_module_generator/install/templates/app/views/shared/_flash.html.erb.tt @@ -0,0 +1,12 @@ +<%% + key = flash&.keys&.first&.to_s || "" + value = flash[key] || [] + value = [value] unless value.is_a?(Array) +%> + +
+
\ No newline at end of file diff --git a/lib/dry_module_generator/install/templates/config/initializers/active_job_serializers.rb.tt b/lib/dry_module_generator/install/templates/config/initializers/active_job_serializers.rb.tt new file mode 100644 index 0000000..0447daf --- /dev/null +++ b/lib/dry_module_generator/install/templates/config/initializers/active_job_serializers.rb.tt @@ -0,0 +1,3 @@ +require 'utils/command_serializer' + +Rails.application.config.active_job.custom_serializers << CommandSerializer diff --git a/lib/dry_module_generator/install/templates/config/initializers/container.rb.tt b/lib/dry_module_generator/install/templates/config/initializers/container.rb.tt new file mode 100644 index 0000000..87c43d4 --- /dev/null +++ b/lib/dry_module_generator/install/templates/config/initializers/container.rb.tt @@ -0,0 +1,55 @@ +Dir[File.join(Rails.root, 'lib', 'utils', 'events', '*.rb')].each do |file| + require File.join(File.dirname(file), File.basename(file, File.extname(file))) +end + +Dir[File.join(Rails.root, 'lib', 'utils', 'concurrent', '*.rb')].each do |file| + require File.join(File.dirname(file), File.basename(file, File.extname(file))) +end + +module App + class Container < Dry::System::Container + register('contract_validator') { ContractValidator.new } + register('current_user_repository') { CurrentUserRepository } + if Rails.env.test? + register('events.client', RailsEventStore::Client.new( + repository: NoOpEventRepository.new, + mapper: ToYAMLEventMapper.new, + dispatcher: RubyEventStore::ComposedDispatcher.new( + RailsEventStore::ImmediateAsyncDispatcher.new(scheduler: ImmediateActiveJobEventScheduler.new(serializer: YAML)), + RailsEventStore::ImmediateAsyncDispatcher.new(scheduler: ImmediateEventScheduler.new(event_deserializer: YAML)) + ) + )) + else + register('events.client', RailsEventStore::Client.new( + repository: NoOpEventRepository.new, + mapper: ToYAMLEventMapper.new, + dispatcher: RubyEventStore::ComposedDispatcher.new( + RailsEventStore::ImmediateAsyncDispatcher.new(scheduler: ActiveJobEventScheduler.new(serializer: YAML)), + RailsEventStore::ImmediateAsyncDispatcher.new( + scheduler: AsyncThreadEventScheduler.new( + job_scheduler: AsyncJobScheduler.new( + executor_service: Concurrent::ThreadPoolExecutor.new( + min_threads: 1, + max_threads: (ENV['BACKGROUND_THREADS'] || 5).to_i, + max_queue: ENV['BACKGROUND_TASK_POOL_SIZE'].to_i, + fallback_policy: :caller_runs + ) + ), + event_deserializer: YAML + ) + ) + ) + )) + end + + register('events.publisher', memoize: true) { MetadataEventPublisher.new } + end +end + +Import = App::Container.injector + +Rails.configuration.after_initialize do + Dir[File.join(Rails.root, '*', 'lib', '*', 'infra', 'system', 'provider_source.rb')].each do |file| + require File.join(File.dirname(file), File.basename(file, File.extname(file))) + end +end \ No newline at end of file diff --git a/lib/dry_module_generator/install/templates/initializers/dependency_injection.rb.tt b/lib/dry_module_generator/install/templates/config/initializers/dependency_injection.rb.tt similarity index 52% rename from lib/dry_module_generator/install/templates/initializers/dependency_injection.rb.tt rename to lib/dry_module_generator/install/templates/config/initializers/dependency_injection.rb.tt index a3feadd..2e6bcbf 100644 --- a/lib/dry_module_generator/install/templates/initializers/dependency_injection.rb.tt +++ b/lib/dry_module_generator/install/templates/config/initializers/dependency_injection.rb.tt @@ -1,3 +1,5 @@ +require 'utils/injection/active_job_strategy' require 'utils/injection/controller_resolve_strategy' +Dry::AutoInject::Strategies.register('active_job', Dry::AutoInject::Strategies::ActiveJobStrategy) Dry::AutoInject::Strategies.register('inject', Dry::AutoInject::Strategies::ControllerResolveStrategy) diff --git a/lib/dry_module_generator/install/templates/config/initializers/dry_module_generator.rb.tt b/lib/dry_module_generator/install/templates/config/initializers/dry_module_generator.rb.tt new file mode 100644 index 0000000..4d9a471 --- /dev/null +++ b/lib/dry_module_generator/install/templates/config/initializers/dry_module_generator.rb.tt @@ -0,0 +1,5 @@ +DryModuleGenerator::Config::GeneratorConfiguration.configure do |config| + config.include_views = true + config.css_framework = "bootstrap5" + config.html_style = "erb" +end \ No newline at end of file diff --git a/lib/dry_module_generator/install/templates/initializers/dry_struct_generator.rb.tt b/lib/dry_module_generator/install/templates/config/initializers/dry_struct_generator.rb.tt similarity index 100% rename from lib/dry_module_generator/install/templates/initializers/dry_struct_generator.rb.tt rename to lib/dry_module_generator/install/templates/config/initializers/dry_struct_generator.rb.tt diff --git a/lib/dry_module_generator/install/templates/initializers/routes.rb.tt b/lib/dry_module_generator/install/templates/config/initializers/routes.rb.tt similarity index 100% rename from lib/dry_module_generator/install/templates/initializers/routes.rb.tt rename to lib/dry_module_generator/install/templates/config/initializers/routes.rb.tt diff --git a/lib/dry_module_generator/install/templates/initializers/container.rb.tt b/lib/dry_module_generator/install/templates/initializers/container.rb.tt deleted file mode 100644 index e64df98..0000000 --- a/lib/dry_module_generator/install/templates/initializers/container.rb.tt +++ /dev/null @@ -1,13 +0,0 @@ -module App - class Container < Dry::System::Container - register('contract_validator') { ContractValidator.new } - end -end - -Import = App::Container.injector - -Rails.configuration.after_initialize do - Dir[File.join(Rails.root, '*', 'lib', '*', 'infra', 'system', 'provider_source.rb')].each do |file| - require File.join(File.dirname(file), File.basename(file, File.extname(file))) - end -end \ No newline at end of file diff --git a/lib/dry_module_generator/install/templates/javascript/controllers/form_controller.js.tt b/lib/dry_module_generator/install/templates/javascript/controllers/form_controller.js.tt deleted file mode 100644 index 0576046..0000000 --- a/lib/dry_module_generator/install/templates/javascript/controllers/form_controller.js.tt +++ /dev/null @@ -1,14 +0,0 @@ -import { Controller } from "@hotwired/stimulus" - -export default class extends Controller { - connect() { - const form = this.element; - form.addEventListener('submit', event => { - if (!form.checkValidity()) { - event.preventDefault() - event.stopPropagation() - } - form.classList.add('was-validated'); - }, false) - } -} diff --git a/lib/dry_module_generator/install/templates/lib/utils/application_contract.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/application_contract.rb.tt new file mode 100644 index 0000000..4e43dec --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/application_contract.rb.tt @@ -0,0 +1,19 @@ +class ApplicationContract < Dry::Validation::Contract + attr_accessor :params, :errors + + def initialize(errors: {}, params: {}) + super() + self.errors = errors + self.params = params + end + + def self.command + DryStructGenerator::StructGenerator.new.call(self) + end + + def to_h + self.class.schema.rules.keys.inject({}) do |hash, key| + hash.merge({ key => self.send(key) }) + end + end +end diff --git a/lib/dry_module_generator/install/templates/utils/application_read_struct.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/application_read_struct.rb.tt similarity index 100% rename from lib/dry_module_generator/install/templates/utils/application_read_struct.rb.tt rename to lib/dry_module_generator/install/templates/lib/utils/application_read_struct.rb.tt diff --git a/lib/dry_module_generator/install/templates/utils/application_struct.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/application_struct.rb.tt similarity index 100% rename from lib/dry_module_generator/install/templates/utils/application_struct.rb.tt rename to lib/dry_module_generator/install/templates/lib/utils/application_struct.rb.tt diff --git a/lib/dry_module_generator/install/templates/lib/utils/bean_proxy.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/bean_proxy.rb.tt new file mode 100644 index 0000000..770bd42 --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/bean_proxy.rb.tt @@ -0,0 +1,15 @@ +class BeanProxy + def initialize(bean_name:) + self.bean_name = bean_name + end + + def bean + App::Container.resolve(bean_name) + end + + delegate_missing_to :bean + + private + + attr_accessor :bean_name +end diff --git a/lib/dry_module_generator/install/templates/lib/utils/command_serializer.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/command_serializer.rb.tt new file mode 100644 index 0000000..f761509 --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/command_serializer.rb.tt @@ -0,0 +1,15 @@ +require_relative 'application_struct' + +class CommandSerializer < ActiveJob::Serializers::ObjectSerializer + def serialize(command) + YAML.dump(command) + end + + def deserialize(command) + YAML.load(command) + end + + def serialize?(command) + command.is_a? ApplicationStruct + end +end diff --git a/lib/dry_module_generator/install/templates/lib/utils/concurrent/async_job_scheduler.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/concurrent/async_job_scheduler.rb.tt new file mode 100644 index 0000000..c523d2b --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/concurrent/async_job_scheduler.rb.tt @@ -0,0 +1,42 @@ +class AsyncJobScheduler + def initialize(executor_service:) + self.executor_service = executor_service + end + + def schedule(callable = nil, delay: 0.seconds, &block) + unless !!callable ^ !!block + raise ArgumentError, 'Either pass in a proc, or a block, but not both or none' + end + + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + future = Concurrent::Promises.delay_on(executor_service) do + Rails.application.executor.wrap do + sleep(delay) + (callable || block).call + end + end + + future.on_rejection_using(executor_service) { |reason| raise StandardError.new(reason) }.touch + end + end + + def fulfilled_future(result = nil) + Concurrent::Promises.fulfilled_future(result, executor_service) + end + + def rejected_future(error) + Concurrent::Promises.rejected_future(error) + end + + def wrap + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + Rails.application.executor.wrap do + yield + end + end + end + + private + + attr_accessor :executor_service +end diff --git a/lib/dry_module_generator/install/templates/utils/contract_validator.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/contract_validator.rb.tt similarity index 78% rename from lib/dry_module_generator/install/templates/utils/contract_validator.rb.tt rename to lib/dry_module_generator/install/templates/lib/utils/contract_validator.rb.tt index 68aed8b..e1a7ebb 100644 --- a/lib/dry_module_generator/install/templates/utils/contract_validator.rb.tt +++ b/lib/dry_module_generator/install/templates/lib/utils/contract_validator.rb.tt @@ -1,8 +1,7 @@ class ContractValidator def validate(input_hash, validator, options = {}) + validator.params = input_hash result = validator.call(input_hash) - validator.result = result - validator.params = result.to_h.with_indifferent_access return validator.class.command.new(result.to_h.merge(options)) if result.success? diff --git a/lib/dry_module_generator/install/templates/lib/utils/events/active_job_event_scheduler.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/events/active_job_event_scheduler.rb.tt new file mode 100644 index 0000000..8a2310b --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/events/active_job_event_scheduler.rb.tt @@ -0,0 +1,5 @@ +class ActiveJobEventScheduler < RailsEventStore::ActiveJobScheduler + def call(klass, serialized_event) + klass.perform_later(serialized_event) + end +end diff --git a/lib/dry_module_generator/install/templates/lib/utils/events/async_thread_event_scheduler.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/events/async_thread_event_scheduler.rb.tt new file mode 100644 index 0000000..c2a008a --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/events/async_thread_event_scheduler.rb.tt @@ -0,0 +1,25 @@ +require 'utils/optional' + +class AsyncThreadEventScheduler + def initialize(job_scheduler: nil, event_deserializer:) + self.job_scheduler = job_scheduler + self.event_deserializer = event_deserializer + end + + def call(subscriber, event) + deserialized = event_deserializer.load(event) + sub = Optional.of(subscriber) + .filter { |it| Class === it } + .map(&:new) + .or_else(subscriber) + job_scheduler.schedule(-> { sub.call(deserialized) }) + end + + def verify(subscriber) + (Class === subscriber && subscriber < AsyncThreadEventHandler) || (Method === subscriber) + end + + private + + attr_accessor :job_scheduler, :event_deserializer +end diff --git a/lib/dry_module_generator/install/templates/lib/utils/events/immediate_active_job_event_scheduler.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/events/immediate_active_job_event_scheduler.rb.tt new file mode 100644 index 0000000..bbd83cf --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/events/immediate_active_job_event_scheduler.rb.tt @@ -0,0 +1,5 @@ +class ImmediateActiveJobEventScheduler < RailsEventStore::ActiveJobScheduler + def call(klass, serialized_event) + klass.perform_now(serialized_event) + end +end diff --git a/lib/dry_module_generator/install/templates/lib/utils/events/immediate_event_scheduler.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/events/immediate_event_scheduler.rb.tt new file mode 100644 index 0000000..9dd54f2 --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/events/immediate_event_scheduler.rb.tt @@ -0,0 +1,16 @@ +require_relative 'async_thread_event_scheduler' + +class ImmediateEventScheduler < AsyncThreadEventScheduler + def initialize(event_deserializer:) + self.event_deserializer = event_deserializer + end + + def call(subscriber, event) + deserialized = event_deserializer.load(event) + sub = Optional.of(subscriber) + .filter { |it| Class === it } + .map(&:new) + .or_else(subscriber) + sub.call(deserialized) + end +end diff --git a/lib/dry_module_generator/install/templates/lib/utils/events/integration_event.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/events/integration_event.rb.tt new file mode 100644 index 0000000..d130b8a --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/events/integration_event.rb.tt @@ -0,0 +1,19 @@ +class IntegrationEvent < RailsEventStore::Event + def current_user_id + metadata[:user_id] + end + + def registered_at + metadata[:registered_at] + end + + def ==(other) + data == other.data && self.class == other.class + end + + alias eql? == + + def hash + data.hash + end +end diff --git a/lib/dry_module_generator/install/templates/lib/utils/events/metadata_event_publisher.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/events/metadata_event_publisher.rb.tt new file mode 100644 index 0000000..cb22e5e --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/events/metadata_event_publisher.rb.tt @@ -0,0 +1,37 @@ +require 'utils/optional' + +class MetadataEventPublisher + attr_accessor :client, :current_user_repository + + def initialize( + client: App::Container.resolve('events.client'), + current_user_repository: App::Container.resolve('current_user_repository') + ) + self.client = client + self.current_user_repository = current_user_repository + end + + def publish(event) + user_id = Optional.of_nullable(current_user_repository.authenticated_identity).map(&:id).or_else(nil) + client.with_metadata({ user_id: user_id, registered_at: DateTime.now }) do + client.publish(event) + end + end + + def publish_all(events) + events.each { |event| publish(event) } + events.clear + end + + def subscribe(subscriber, to:) + client.subscribe(subscriber, to: to) + end + + def mapper + client.send(:mapper) + end + + def deserialize(serialized_event, serializer) + serializer.load(serialized_event) + end +end diff --git a/lib/dry_module_generator/install/templates/lib/utils/events/no_op_event_repository.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/events/no_op_event_repository.rb.tt new file mode 100644 index 0000000..e2fdc60 --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/events/no_op_event_repository.rb.tt @@ -0,0 +1,42 @@ +class NoOpEventRepository + def add_to_stream(*_args) + self + end + + def link_to_stream(*_args) + self + end + + def delete_stream(*_args) + self + end + + def has_event?(_event_id) + false + end + + def last_stream_event(_stream) + nil + end + + def read(_specification) + nil + end + + def count(_specification) + 0 + end + + def update_messages(_messages) + self + end + + def streams_of(_event_id) + [] + end + + def append_to_stream(*_args) + self + end +end + diff --git a/lib/dry_module_generator/install/templates/lib/utils/events/to_yaml_event_mapper.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/events/to_yaml_event_mapper.rb.tt new file mode 100644 index 0000000..1c7d5c3 --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/events/to_yaml_event_mapper.rb.tt @@ -0,0 +1,13 @@ +require 'ruby_event_store/mappers/pipeline_mapper' + +class ToYAMLEventMapper < RubyEventStore::Mappers::PipelineMapper + attr_reader :pipeline + + def initialize(pipeline: YAML) + self.pipeline = pipeline + end + + private + + attr_writer :pipeline +end diff --git a/lib/dry_module_generator/install/templates/lib/utils/injection/active_job_strategy.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/injection/active_job_strategy.rb.tt new file mode 100644 index 0000000..ec88bcf --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/injection/active_job_strategy.rb.tt @@ -0,0 +1,48 @@ +require 'utils/bean_proxy' + +module Dry + module AutoInject + class Strategies + class ActiveJobStrategy < Kwargs + private + + def define_new + class_mod.class_exec(container, dependency_map) do |_container, dependency_map| + map = dependency_map.to_h + + define_method :new do |*args, **kwargs| + map.each do |name, identifier| + kwargs[name] = BeanProxy.new(bean_name: identifier) unless kwargs.key?(name) + end + + super(*args, **kwargs) + end + end + end + + def define_initialize_with_splat(super_parameters) + assign_dependencies = method(:assign_dependencies) + slice_kwargs = method(:slice_kwargs) + + instance_mod.class_exec do + define_method :initialize do |*args, **kwargs| + assign_dependencies.(kwargs, self) + + if super_parameters.splat? + super(*args) + else + super_kwargs = slice_kwargs.(kwargs, super_parameters) + + if super_kwargs.any? + super(*args, super_kwargs) + else + super(*args) + end + end + end + end + end + end + end + end +end diff --git a/lib/dry_module_generator/install/templates/utils/injection/controller_resolve_strategy.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/injection/controller_resolve_strategy.rb.tt similarity index 100% rename from lib/dry_module_generator/install/templates/utils/injection/controller_resolve_strategy.rb.tt rename to lib/dry_module_generator/install/templates/lib/utils/injection/controller_resolve_strategy.rb.tt diff --git a/lib/dry_module_generator/install/templates/lib/utils/list_query.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/list_query.rb.tt new file mode 100644 index 0000000..9f8e3b0 --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/list_query.rb.tt @@ -0,0 +1,5 @@ +class ListQuery < ApplicationStruct + attribute? :q, Types::Any.default({}.freeze) + attribute? :page, Types::Coercible::Integer.default(1.freeze) + attribute? :page_size, Types::Coercible::Integer.default(Pagy::DEFAULT[:items].freeze) +end \ No newline at end of file diff --git a/lib/dry_module_generator/install/templates/lib/utils/optional.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/optional.rb.tt new file mode 100644 index 0000000..ed81154 --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/optional.rb.tt @@ -0,0 +1,97 @@ +class Optional + def self.of_nullable(value) + new(value) + end + + def self.of(value) + raise ArgumentError, 'value is nil' if value.nil? + new(value) + end + + def self.empty + new(nil) + end + + def initialize(value) + self.value = value + end + + def get + if value.nil? + raise ArgumentError, 'no value present' + else + value + end + end + + def present? + !value.nil? + end + + def if_present(&consumer) + consumer.call(value) if present? + end + + def and_then(&consumer) + return self unless present? + + consumer.call(value) + self + end + + def filter(&predicate) + return self unless present? + + predicate.call(value) ? self : Optional.empty + end + + def map(&mapper) + return self unless present? + + Optional.of_nullable(mapper.call(value)) + end + + def flat_map(&mapper) + return self unless present? + + mapper.call(value) + end + + def or(&supplier) + present? ? self : self.class.of_nullable(supplier.call) + end + + def or_else(other) + present? ? value : other + end + + def or_else_get(&supplier) + present? ? value : supplier.call + end + + def or_else_raise(&error_supplier) + if present? + value + else + raise error_supplier.call + end + end + + def as_json(_options = {}) + present? ? value.as_json : nil + end + + def ==(other) + other.filter { |it| it == value }.present? + end + + alias eql? == + + def hash + value.hash + end + + private + + attr_accessor :value +end diff --git a/lib/dry_module_generator/install/templates/lib/utils/pagination_dto.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/pagination_dto.rb.tt new file mode 100644 index 0000000..bb84fe3 --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/pagination_dto.rb.tt @@ -0,0 +1,9 @@ +require_relative 'application_read_struct' + +class PaginationDto < ApplicationReadStruct + attribute :count, Types::Integer + attribute :last, Types::Integer + attribute :next, Types::Integer + attribute :page, Types::Integer + attribute :pages, Types::Integer +end diff --git a/lib/dry_module_generator/install/templates/lib/utils/pagination_list_dto.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/pagination_list_dto.rb.tt new file mode 100644 index 0000000..021c1ac --- /dev/null +++ b/lib/dry_module_generator/install/templates/lib/utils/pagination_list_dto.rb.tt @@ -0,0 +1,6 @@ +require_relative 'application_read_struct' + +class PaginationListDto < ApplicationReadStruct + attribute :pagination, Types::Any + attribute :data, Types::Array +end diff --git a/lib/dry_module_generator/install/templates/utils/types.rb.tt b/lib/dry_module_generator/install/templates/lib/utils/types.rb.tt similarity index 100% rename from lib/dry_module_generator/install/templates/utils/types.rb.tt rename to lib/dry_module_generator/install/templates/lib/utils/types.rb.tt diff --git a/lib/dry_module_generator/install/templates/services/application_service.rb.tt b/lib/dry_module_generator/install/templates/services/application_service.rb.tt deleted file mode 100644 index 3a8510f..0000000 --- a/lib/dry_module_generator/install/templates/services/application_service.rb.tt +++ /dev/null @@ -1,5 +0,0 @@ -class ApplicationService - def map_into(data, mapper, options = {}) - DryObjectMapper::Mapper.call(data, mapper, options) - end -end diff --git a/lib/dry_module_generator/install/templates/utils/application_contract.rb.tt b/lib/dry_module_generator/install/templates/utils/application_contract.rb.tt deleted file mode 100644 index 0c454bc..0000000 --- a/lib/dry_module_generator/install/templates/utils/application_contract.rb.tt +++ /dev/null @@ -1,32 +0,0 @@ -class ApplicationContract < Dry::Validation::Contract - attr_accessor :errors, :params, :result - - def initialize(errors: {}, params: {}, result: nil) - super() - self.errors = errors - self.params = params - self.result = result - end - - def self.command - DryStructGenerator::StructGenerator.new.call(self) - end - - register_macro(:email_format) do - unless /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.match?(value) - key.failure('not a valid email') - end - end - - register_macro(:password_format) do - unless /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/i.match?(value) - key.failure('must contain 1 lower case letter, 1 upper case letter, 1 number, 1 special character and have a minimum length of 8') - end - end - - def to_h - self.class.schema.rules.keys.inject({}) do |hash, key| - hash.merge({ key => self.send(key) }) - end - end -end diff --git a/lib/dry_module_generator/module/generator.rb b/lib/dry_module_generator/module/generator.rb index 5e06b27..ef06fe3 100644 --- a/lib/dry_module_generator/module/generator.rb +++ b/lib/dry_module_generator/module/generator.rb @@ -30,19 +30,41 @@ class Generator < Rails::Generators::NamedBase 'boolean': "check_box_tag" }.freeze + attr_accessor :config + def initialize(args, *options) super self.module_name = args[0] + self.config = Config::GeneratorConfiguration end def create_app template("app/service.rb", File.join("#{module_path}/app/#{class_name.downcase}_service.rb")) - template("app/list_dto.rb", File.join("#{module_path}/app/get_#{class_name.pluralize.downcase}_list_dto.rb")) - template("app/details_dto.rb", File.join("#{module_path}/app/get_#{class_name.downcase}_details_dto.rb")) + template("app/read_service/service.rb", File.join("#{module_path}/app/read_service/#{class_name.downcase}_service.rb")) + template( + "app/read_service/get_list_dto.rb", + File.join("#{module_path}/app/read_service/get_#{class_name.pluralize.downcase}_list_dto.rb") + ) + template( + "app/read_service/get_details_dto.rb", + File.join("#{module_path}/app/read_service/get_#{class_name.downcase}_details_dto.rb") + ) end def create_domain template("domain/model.rb", File.join("#{module_path}/domain/#{class_name.downcase}.rb")) + template( + "domain/events/created_event.rb", + File.join("#{module_path}/domain/#{class_name.downcase}/created_event.rb") + ) + template( + "domain/events/updated_event.rb", + File.join("#{module_path}/domain/#{class_name.downcase}/updated_event.rb") + ) + template( + "domain/events/deleted_event.rb", + File.join("#{module_path}/domain/#{class_name.downcase}/deleted_event.rb") + ) end def create_infra @@ -56,43 +78,64 @@ def create_infra ) template("infra/config/application.rb", File.join("#{module_path}/infra/config/application.rb")) template("infra/config/routes.rb", File.join("#{module_path}/infra/config/routes.rb")) + template("infra/config/importmap.rb", File.join("#{module_path}/infra/config/importmap.rb")) end def create_ui - @action_type = "Create" - template("ui/validation.rb", File.join("#{module_path}/ui/create_#{class_name.downcase}_validator.rb")) - @action_type = "Update" - template("ui/validation.rb", File.join("#{module_path}/ui/update_#{class_name.downcase}_validator.rb")) + template("ui/create_validation.rb", File.join("#{module_path}/ui/create_#{class_name.downcase}_validator.rb")) + template("ui/update_validation.rb", File.join("#{module_path}/ui/update_#{class_name.downcase}_validator.rb")) template("ui/controller.rb", File.join("#{module_path}/ui/#{class_name.pluralize.downcase}_controller.rb")) + empty_directory("#{module_path}/ui/javascript/controllers") if Config::GeneratorConfiguration.include_views end def create_views - template("ui/views/form.rb", File.join("#{module_path}/ui/#{class_name.pluralize.downcase}/_form.html.erb")) - template("ui/views/index.rb", File.join("#{module_path}/ui/#{class_name.pluralize.downcase}/index.html.erb")) - template("ui/views/show.rb", File.join("#{module_path}/ui/#{class_name.pluralize.downcase}/show.html.erb")) - template("ui/views/new.rb", File.join("#{module_path}/ui/#{class_name.pluralize.downcase}/new.html.erb")) - template("ui/views/edit.rb", File.join("#{module_path}/ui/#{class_name.pluralize.downcase}/edit.html.erb")) + return unless Config::GeneratorConfiguration.include_views + template_path = "ui/views/#{config.css_framework}/#{config.html_style}" + template("#{template_path}/_form.html.erb", File.join("#{module_path}/ui/#{class_name.pluralize.downcase}/_form.html.erb")) + template("#{template_path}/_table_filter.html.erb", File.join("#{module_path}/ui/#{class_name.pluralize.downcase}/_table_filter.html.erb")) + template("#{template_path}/index.html.erb", File.join("#{module_path}/ui/#{class_name.pluralize.downcase}/index.html.erb")) + template("#{template_path}/show.html.erb", File.join("#{module_path}/ui/#{class_name.pluralize.downcase}/show.html.erb")) + template("#{template_path}/new.html.erb", File.join("#{module_path}/ui/#{class_name.pluralize.downcase}/new.html.erb")) + template("#{template_path}/edit.html.erb", File.join("#{module_path}/ui/#{class_name.pluralize.downcase}/edit.html.erb")) + end + + def add_importmap_configuration + append_to_file("app/assets/config/manifest.js") do + "//= link_tree ../../../#{module_name}/lib/#{module_name}/ui/javascript/controllers .js" + end + + append_to_file("app/javascript/controllers/index.js") do + "eagerLoadControllersFrom('#{module_name}', application)" + end end def create_tests - @action_type = "Create" template( - "spec/ui/validation_test.rb", - File.join("#{module_name}/spec/ui/create_#{class_name.downcase}_validator_spec.rb") + "spec/app/service_spec.rb", + File.join("#{module_name}/spec/app/#{class_name.downcase}_service_spec.rb") ) - @action_type = "Update" template( - "spec/ui/validation_test.rb", - File.join("#{module_name}/spec/ui/update_#{class_name.downcase}_validator_spec.rb") + "spec/app/read_service/service_spec.rb", + File.join("#{module_name}/spec/app/read_service/#{class_name.downcase}_service_spec.rb") ) template( - "spec/app/service_test.rb", - File.join("#{module_name}/spec/app/#{class_name.downcase}_service_spec.rb") + "spec/domain/model_spec.rb", + File.join("#{module_name}/spec/domain/#{class_name.downcase}_spec.rb") ) template( - "spec/ui/controller_test.rb", + "spec/ui/controller_spec.rb", File.join("#{module_name}/spec/ui/#{class_name.pluralize.downcase}_controller_spec.rb") ) + @action_type = "Create" + template( + "spec/ui/validation_spec.rb", + File.join("#{module_name}/spec/ui/create_#{class_name.downcase}_validator_spec.rb") + ) + @action_type = "Update" + template( + "spec/ui/validation_spec.rb", + File.join("#{module_name}/spec/ui/update_#{class_name.downcase}_validator_spec.rb") + ) end private @@ -178,4 +221,4 @@ def form_attributes d end end -end +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/app/details_dto.rb.tt b/lib/dry_module_generator/module/templates/app/details_dto.rb.tt deleted file mode 100644 index 8400ef0..0000000 --- a/lib/dry_module_generator/module/templates/app/details_dto.rb.tt +++ /dev/null @@ -1,10 +0,0 @@ -<% module_namespacing do -%> -module <%= module_name.capitalize %> - module App - class Get<%= class_name %>DetailsDto < ApplicationReadStruct - attribute :id, Types::String - <%= dto_definition.join("\n ") %> - end - end -end -<% end %> diff --git a/lib/dry_module_generator/module/templates/app/list_dto.rb.tt b/lib/dry_module_generator/module/templates/app/list_dto.rb.tt deleted file mode 100644 index 937b102..0000000 --- a/lib/dry_module_generator/module/templates/app/list_dto.rb.tt +++ /dev/null @@ -1,10 +0,0 @@ -<% module_namespacing do -%> -module <%= module_name.capitalize %> - module App - class Get<%= class_name.pluralize %>ListDto < ApplicationReadStruct - attribute :id, Types::String - <%= dto_definition.join("\n ") %> - end - end -end -<% end %> diff --git a/lib/dry_module_generator/module/templates/app/read_service/get_details_dto.rb.tt b/lib/dry_module_generator/module/templates/app/read_service/get_details_dto.rb.tt new file mode 100644 index 0000000..aa44be5 --- /dev/null +++ b/lib/dry_module_generator/module/templates/app/read_service/get_details_dto.rb.tt @@ -0,0 +1,10 @@ +module <%= module_name.capitalize %> + module App + module ReadService + class Get<%= class_name %>DetailsDto < ApplicationReadStruct + attribute :id, Types::String + <%= dto_definition.join("\n ") %> + end + end + end +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/app/read_service/get_list_dto.rb.tt b/lib/dry_module_generator/module/templates/app/read_service/get_list_dto.rb.tt new file mode 100644 index 0000000..f66bf36 --- /dev/null +++ b/lib/dry_module_generator/module/templates/app/read_service/get_list_dto.rb.tt @@ -0,0 +1,10 @@ +module <%= module_name.capitalize %> + module App + module ReadService + class Get<%= class_name.pluralize %>ListDto < ApplicationReadStruct + attribute :id, Types::String + <%= dto_definition.join("\n ") %> + end + end + end +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/app/read_service/service.rb.tt b/lib/dry_module_generator/module/templates/app/read_service/service.rb.tt new file mode 100644 index 0000000..a1ac804 --- /dev/null +++ b/lib/dry_module_generator/module/templates/app/read_service/service.rb.tt @@ -0,0 +1,23 @@ +module <%= module_name.capitalize %> + module App + module ReadService + class <%= class_name %>Service < ApplicationService + include Import[<%= class_name.downcase %>_repository: "<%= module_name %>.<%= class_name.downcase %>_repository"] + + def get_all_<%= class_name.pluralize.downcase %>(query) + paginate_collection( + collection: <%= class_name.downcase %>_repository.all, + mapper: Get<%= class_name.pluralize %>ListDto, + page: query.page, + page_size: query.page_size, + filter: query.q + ) + end + + def get_<%= class_name.downcase %>(id) + map_into(<%= class_name.downcase %>_repository.find(id), Get<%= class_name %>DetailsDto) + end + end + end + end +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/app/service.rb.tt b/lib/dry_module_generator/module/templates/app/service.rb.tt index 41c69f9..fd1241a 100644 --- a/lib/dry_module_generator/module/templates/app/service.rb.tt +++ b/lib/dry_module_generator/module/templates/app/service.rb.tt @@ -1,21 +1,29 @@ -<% module_namespacing do -%> module <%= module_name.capitalize %> module App class <%= class_name %>Service < ApplicationService include Import[<%= class_name.downcase %>_repository: "<%= module_name %>.<%= class_name.downcase %>_repository"] def create_<%= class_name.downcase %>(command) - result = ActiveRecord::Base.transaction do - <%= class_name.downcase %>_repository.create!(command.to_h) + <%= class_name.downcase %> = ActiveRecord::Base.transaction do + <%= class_name.downcase %> = <%= class_name.downcase %>_repository.create_new(<% options[:attributes].each do |field_name, _| %> + <%= field_name %>: command.<%= field_name %>,<% end %> + ) + <%= class_name.downcase %>_repository.save!(<%= class_name.downcase %>) end + + event_publisher.publish_all(<%= class_name.downcase %>.domain_events) end def update_<%= class_name.downcase %>(command) - result = ActiveRecord::Base.transaction do + <%= class_name.downcase %> = ActiveRecord::Base.transaction do <%= class_name.downcase %> = <%= class_name.downcase %>_repository.find(command.id) - <%= class_name.downcase %>.update!(command.to_h) - <%= class_name.downcase %> + <%= class_name.downcase %>.update_<%= class_name.downcase %>(<% options[:attributes].each do |field_name, _| %> + <%= field_name %>: command.<%= field_name %>,<% end %> + ) + <%= class_name.downcase %>_repository.save!(<%= class_name.downcase %>) end + + event_publisher.publish_all(<%= class_name.downcase %>.domain_events) end def delete_<%= class_name.downcase %>(id) @@ -23,15 +31,6 @@ module <%= module_name.capitalize %> <%= class_name.downcase %>_repository.delete!(<%= class_name.downcase %>_repository.find(id)) end end - - def get_all_<%= class_name.pluralize.downcase %> - map_into(<%= class_name.downcase %>_repository.all, Get<%= class_name.pluralize %>ListDto) - end - - def get_<%= class_name.downcase %>(id) - map_into(<%= class_name.downcase %>_repository.find(id), Get<%= class_name %>DetailsDto) - end end end -end -<% end -%> \ No newline at end of file +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/domain/events/created_event.rb.tt b/lib/dry_module_generator/module/templates/domain/events/created_event.rb.tt new file mode 100644 index 0000000..22724fb --- /dev/null +++ b/lib/dry_module_generator/module/templates/domain/events/created_event.rb.tt @@ -0,0 +1,16 @@ +module <%= module_name.capitalize %> + module Domain + class <%= class_name %> + class CreatedEvent < IntegrationEvent + def id + data[:id] + end + <% options[:attributes].each do |field_name, _| %> + def <%= field_name %> + data[:<%= field_name %>] + end + <% end %> + end + end + end +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/domain/events/deleted_event.rb.tt b/lib/dry_module_generator/module/templates/domain/events/deleted_event.rb.tt new file mode 100644 index 0000000..44d10a9 --- /dev/null +++ b/lib/dry_module_generator/module/templates/domain/events/deleted_event.rb.tt @@ -0,0 +1,16 @@ +module <%= module_name.capitalize %> + module Domain + class <%= class_name %> + class DeletedEvent < IntegrationEvent + def id + data[:id] + end + <% options[:attributes].each do |field_name, _| %> + def <%= field_name %> + data[:<%= field_name %>] + end + <% end %> + end + end + end +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/domain/events/updated_event.rb.tt b/lib/dry_module_generator/module/templates/domain/events/updated_event.rb.tt new file mode 100644 index 0000000..07abf81 --- /dev/null +++ b/lib/dry_module_generator/module/templates/domain/events/updated_event.rb.tt @@ -0,0 +1,16 @@ +module <%= module_name.capitalize %> + module Domain + class <%= class_name %> + class UpdatedEvent < IntegrationEvent + def id + data[:id] + end + <% options[:attributes].each do |field_name, _| %> + def <%= field_name %> + data[:<%= field_name %>] + end + <% end %> + end + end + end +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/domain/model.rb.tt b/lib/dry_module_generator/module/templates/domain/model.rb.tt index a53a85d..7f7a327 100644 --- a/lib/dry_module_generator/module/templates/domain/model.rb.tt +++ b/lib/dry_module_generator/module/templates/domain/model.rb.tt @@ -1,9 +1,28 @@ -<% module_namespacing do -%> module <%= module_name.capitalize %> module Domain - class <%= class_name %> < ApplicationRecord + class <%= class_name %> < AggregateRoot self.table_name = "<%= class_name.pluralize.downcase %>" + + def self.create_new(id: SecureRandom.uuid, <%= options[:attributes].map { |field_name, _| "#{field_name}:" }.join(', ') %>) + <%= class_name.downcase %> = new( + id: id, + <%= options[:attributes].map do |field_name, _| + "#{field_name}: #{field_name}," + end.join("\n ") %> + ) + + <%= class_name.downcase %> + end + + def update_<%= class_name.downcase %>(<%= options[:attributes].map { |field_name, _| "#{field_name}:" }.join(', ') %>) + assign_attributes( + <%= options[:attributes].map do |field_name, _| + "#{field_name}: #{field_name}," + end.join("\n ") %> + ) + + self + end end end -end -<% end %> \ No newline at end of file +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/infra/config/application.rb.tt b/lib/dry_module_generator/module/templates/infra/config/application.rb.tt index 76d3e5f..a6eac11 100644 --- a/lib/dry_module_generator/module/templates/infra/config/application.rb.tt +++ b/lib/dry_module_generator/module/templates/infra/config/application.rb.tt @@ -1,5 +1,5 @@ -<% module_namespacing do -%> -<%= Rails.application.class %>.config.paths.add '<%= module_name %>/lib', load_path: true, autoload: true -<%= Rails.application.class %>.config.paths['db/migrate'] << '<%= module_path %>/infra/db/migrate' -<%= Rails.application.class %>.config.paths['app/views'] << '<%= module_name %>/lib' -<% end %> \ No newline at end of file +Rails.application.config.paths.add '<%= module_name %>/lib', load_path: true, autoload: true +Rails.application.config.paths['db/migrate'] << '<%= module_path %>/infra/db/migrate' +Rails.application.config.paths['app/views'] << '<%= module_name %>/lib' +Rails.application.config.importmap.paths << Rails.root.join('<%= module_name %>/lib/<%= module_name %>/infra/config/importmap.rb') +Rails.application.config.assets.paths << Rails.root.join("<%= module_name %>/lib/<%= module_name %>/ui/javascript/controllers") \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/infra/config/importmap.rb.tt b/lib/dry_module_generator/module/templates/infra/config/importmap.rb.tt new file mode 100644 index 0000000..9b9bf43 --- /dev/null +++ b/lib/dry_module_generator/module/templates/infra/config/importmap.rb.tt @@ -0,0 +1,5 @@ +controllers_path = Rails.root.join("<%= module_path %>/ui/javascript/controllers") +controllers_path.glob("**/*_controller.js").each do |controller| + name = controller.relative_path_from(controllers_path).to_s.remove(/\.js$/) + pin "<%= module_name %>/#{name}", to: name +end diff --git a/lib/dry_module_generator/module/templates/infra/config/routes.rb.tt b/lib/dry_module_generator/module/templates/infra/config/routes.rb.tt index f65434d..6183dc2 100644 --- a/lib/dry_module_generator/module/templates/infra/config/routes.rb.tt +++ b/lib/dry_module_generator/module/templates/infra/config/routes.rb.tt @@ -1,5 +1,3 @@ -<% module_namespacing do -%> Rails.application.routes.draw do resources :<%= class_name.pluralize.downcase %>, controller: "<%= module_name %>/ui/<%= class_name.pluralize.downcase %>" -end -<% end %> \ No newline at end of file +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/infra/db/migrate/migration.rb.tt b/lib/dry_module_generator/module/templates/infra/db/migrate/migration.rb.tt index e914a72..972c46e 100644 --- a/lib/dry_module_generator/module/templates/infra/db/migrate/migration.rb.tt +++ b/lib/dry_module_generator/module/templates/infra/db/migrate/migration.rb.tt @@ -1,12 +1,11 @@ -<% module_namespacing do -%> class Create<%= class_name.pluralize %> < ActiveRecord::Migration[<%= ActiveRecord::VERSION::MAJOR %>.<%= ActiveRecord::VERSION::MINOR %>] def change create_table :<%= class_name.pluralize.downcase %>, id: false do |t| t.string :id, limit: 36, primary_key: true <%= migration_definition.join("\n ") %> + t.boolean t.timestamps end end -end -<% end %> \ No newline at end of file +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/infra/system/provider_source.rb.tt b/lib/dry_module_generator/module/templates/infra/system/provider_source.rb.tt index 4b4a8ac..fb4f352 100644 --- a/lib/dry_module_generator/module/templates/infra/system/provider_source.rb.tt +++ b/lib/dry_module_generator/module/templates/infra/system/provider_source.rb.tt @@ -1,11 +1,10 @@ -<% module_namespacing do -%> Dry::System.register_provider_source(:<%= module_name %>, group: :<%= module_name %>) do prepare do register("<%= module_name %>.<%= class_name.downcase %>_repository") { <%= module_name.capitalize %>::Domain::<%= class_name %> } register("<%= module_name %>.<%= class_name.downcase %>_service") { <%= module_name.capitalize %>::App::<%= class_name %>Service.new } + register("<%= module_name %>.<%= class_name.downcase %>_read_service") { <%= module_name.capitalize %>::App::ReadService::<%= class_name %>Service.new } end end App::Container.register_provider(:<%= module_name %>, from: :<%= module_name %>) -App::Container.start(:<%= module_name %>) -<% end %> +App::Container.start(:<%= module_name %>) \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/spec/app/read_service/service_spec.rb.tt b/lib/dry_module_generator/module/templates/spec/app/read_service/service_spec.rb.tt new file mode 100644 index 0000000..bf7048e --- /dev/null +++ b/lib/dry_module_generator/module/templates/spec/app/read_service/service_spec.rb.tt @@ -0,0 +1,47 @@ +require 'rails_helper' + +RSpec.describe <%= module_name.capitalize %>::App::ReadService::<%= class_name %>Service, type: :unit do + subject(:<%= class_name.downcase %>_service) { described_class.new } + + describe "#.get_all_<%= class_name.pluralize.downcase %>(query)" do + let(:query) { ListQuery.new(page: 1, page_size: 10, q: {}) } + + context "when <%= class_name.downcase %> exists" do + let!(:<%= class_name.downcase %>) do + <%= module_name.capitalize %>::Domain::<%= class_name %>.create_new(<% options[:attributes].each do |field_name, _| %> + <%= field_name %>: "<%= field_name %>", <% end %> + ).tap(&:save!) + end + + it "should return a PaginatedListDto as a result" do + result = <%= class_name.downcase %>_service.get_all_<%= class_name.pluralize.downcase %>(query) + expect(result.class).to eq(PaginationListDto) + expect(result.data.size).to eq(1)<% options[:attributes].each do |field_name, _| %> + expect(result.data[0].<%= field_name %>).to eq(<%= class_name.downcase %>.<%= field_name %>) <% end %> + end + end + end + + describe "#.get_<%= class_name.downcase %>(id)" do + context "when the <%= class_name.downcase %> is not found" do + it "should raise an 404 not found error" do + expect { <%= class_name.downcase %>_service.get_<%= class_name.downcase %>(SecureRandom.uuid) }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context "when the <%= class_name.downcase %> is found" do + let!(:<%= class_name.downcase %>) do + <%= module_name.capitalize %>::Domain::<%= class_name %>.create_new(<% options[:attributes].each do |field_name, _| %> + <%= field_name %>: "<%= field_name %>", <% end %> + ).tap(&:save!) + end + + it "should return a <%= class_name.downcase %> dto" do + result = <%= class_name.downcase %>_service.get_<%= class_name.downcase %>(<%= class_name.downcase %>.id) + expect(result.class).to eq(<%= module_name.capitalize %>::App::ReadService::Get<%= class_name %>DetailsDto) + expect(result.id).to eq(<%= class_name.downcase %>.id)<% options[:attributes].each do |field_name, _| %> + expect(result.<%= field_name %>).to eq(<%= class_name.downcase %>.<%= field_name %>)<% end %> + end + end + end +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/spec/app/service_test.rb.tt b/lib/dry_module_generator/module/templates/spec/app/service_spec.rb.tt similarity index 74% rename from lib/dry_module_generator/module/templates/spec/app/service_test.rb.tt rename to lib/dry_module_generator/module/templates/spec/app/service_spec.rb.tt index 8779334..ffd4f84 100644 --- a/lib/dry_module_generator/module/templates/spec/app/service_test.rb.tt +++ b/lib/dry_module_generator/module/templates/spec/app/service_spec.rb.tt @@ -2,23 +2,38 @@ require 'rails_helper' RSpec.describe <%= module_name.capitalize %>::App::<%= class_name %>Service, type: :unit do subject(:<%= class_name.downcase %>_service) do - described_class.new(<%= class_name.downcase %>_repository: <%= class_name.downcase %>_repository) + described_class.new( + <%= class_name.downcase %>_repository: <%= class_name.downcase %>_repository, + event_publisher: event_publisher + ) end let(:<%= class_name.downcase %>_repository) { spy(<%= module_name.capitalize %>::Domain::<%= class_name %>) } + let(:event_publisher) { spy(MetadataEventPublisher) } describe "#.create_<%= class_name.downcase %>(command)" do let(:command) do <%= module_name.capitalize %>::Ui::Create<%= class_name %>Validator.command.new(<% contract_test_params.each do |param| %> <%= param[:field_name] %>: <%= param[:value] %>,<% end %> - id: SecureRandom.uuid ) end context "when creating a new <%= class_name.downcase %>" do + let(:<%= class_name.downcase %>) { instance_double(<%= module_name.capitalize %>::Domain::<%= class_name %>, domain_events: []) } + + before do + allow(<%= class_name.downcase %>_repository).to receive(:create_new) { <%= class_name.downcase %> } + allow(<%= class_name.downcase %>_repository).to receive(:save!) { <%= class_name.downcase %> } + allow(event_publisher).to receive(:publish_all) + end + it 'should call all the required model methods' do expect { <%= class_name.downcase %>_service.create_<%= class_name.downcase %>(command) }.to_not raise_error - expect(<%= class_name.downcase %>_repository).to have_received(:create!).with(command.to_h) + expect(<%= class_name.downcase %>_repository).to have_received(:create_new).with(<% options[:attributes].each do |field_name, _| %> + <%= field_name %>: command.<%= field_name %>, <% end %> + ) + expect(<%= class_name.downcase %>_repository).to have_received(:save!).with(<%= class_name.downcase %>) + expect(event_publisher).to have_received(:publish_all).with(<%= class_name.downcase %>.domain_events) end end end @@ -46,13 +61,15 @@ RSpec.describe <%= module_name.capitalize %>::App::<%= class_name %>Service, typ before do allow(<%= class_name.downcase %>_repository).to receive(:find) { <%= class_name.downcase %> } - allow(<%= class_name.downcase %>).to receive(:update!) + allow(<%= class_name.downcase %>).to receive(:update_<%= class_name.downcase %>) end it 'should update the record' do expect { <%= class_name.downcase %>_service.update_<%= class_name.downcase %>(command) }.to_not raise_error expect(<%= class_name.downcase %>_repository).to have_received(:find).with(command.id) - expect(<%= class_name.downcase %>).to have_received(:update!).with(command.to_h) + expect(<%= class_name.downcase %>).to have_received(:update_<%= class_name.downcase %>).with(<% options[:attributes].each do |field_name, _| %> + <%= field_name %>: command.<%= field_name %>, <% end %> + ) end end end diff --git a/lib/dry_module_generator/module/templates/spec/domain/model_spec.rb.tt b/lib/dry_module_generator/module/templates/spec/domain/model_spec.rb.tt new file mode 100644 index 0000000..4edbca0 --- /dev/null +++ b/lib/dry_module_generator/module/templates/spec/domain/model_spec.rb.tt @@ -0,0 +1,37 @@ +require 'rails_helper' + +RSpec.describe <%= module_name.capitalize %>::Domain::<%= class_name %>, type: :unit do + describe "#self.create_new(id: SecureRandom.uuid, , <%= options[:attributes].map { |field_name, _| "#{field_name}:" }.join(', ') %>)" do + context "when creating a new <%= class_name.downcase %>" do + it "should save without error" do + id = SecureRandom.uuid <% options[:attributes].each do |field_name, _| %> + <%= field_name %> = "<%= field_name %>"<% end %> + + <%= class_name.downcase %> = described_class.create_new( + id: id, <% options[:attributes].each do |field_name, _| %> + <%= field_name %>: <%= field_name %>, <% end %> + ) + + expect { described_class.save!(<%= class_name.downcase %>) }.to_not raise_error + end + end + end + + describe "#.update_<%= class_name.downcase %>(<%= options[:attributes].map { |field_name, _| "#{field_name}:" }.join(', ') %>)" do + context "when updating an <%= class_name.downcase %>" do + it "should update the attributes" do + <%= class_name.downcase %> = described_class.create_new(<% options[:attributes].each do |field_name, _| %> + <%= field_name %>: "old_<%= field_name %>", <% end %> + ) + <% options[:attributes].each do |field_name, _| %> + <%= field_name %> = "new_<%= field_name %>"<% end %> + + <%= class_name.downcase %>.update_<%= class_name.downcase %>(<% options[:attributes].each do |field_name, _| %> + <%= field_name %>: <%= field_name %>, <% end %> + ) + <% options[:attributes].each do |field_name, _| %> + expect(<%= class_name.downcase %>.<%= field_name %>).to eq(<%= field_name %>)<% end %> + end + end + end +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/spec/ui/controller_test.rb.tt b/lib/dry_module_generator/module/templates/spec/ui/controller_spec.rb.tt similarity index 100% rename from lib/dry_module_generator/module/templates/spec/ui/controller_test.rb.tt rename to lib/dry_module_generator/module/templates/spec/ui/controller_spec.rb.tt diff --git a/lib/dry_module_generator/module/templates/spec/ui/validation_test.rb.tt b/lib/dry_module_generator/module/templates/spec/ui/validation_spec.rb.tt similarity index 100% rename from lib/dry_module_generator/module/templates/spec/ui/validation_test.rb.tt rename to lib/dry_module_generator/module/templates/spec/ui/validation_spec.rb.tt diff --git a/lib/dry_module_generator/module/templates/ui/controller.rb.tt b/lib/dry_module_generator/module/templates/ui/controller.rb.tt index abeeb35..d1d3a84 100644 --- a/lib/dry_module_generator/module/templates/ui/controller.rb.tt +++ b/lib/dry_module_generator/module/templates/ui/controller.rb.tt @@ -1,17 +1,26 @@ -<% module_namespacing do -%> <% controller_name = "#{class_name.pluralize}Controller" service_name = "#{class_name.singularize.downcase}_service" + read_service_name = "#{class_name.singularize.downcase}_read_service" pluralized_variable_name = class_name.pluralize.downcase singularize_variable_name = class_name.singularize.downcase view_path_name = class_name.pluralize.downcase %>module <%= module_name.capitalize %> module Ui class <%= controller_name %> < ApplicationController - include Import.inject[<%= service_name %>: "<%= module_name %>.<%= service_name %>"] + include Import.inject[ + <%= service_name %>: "<%= module_name %>.<%= service_name %>", + <%= read_service_name %>: "<%= module_name %>.<%= read_service_name %>" + ] def index - @<%= pluralized_variable_name %> = <%= service_name %>.get_all_<%= pluralized_variable_name %> + @paginated_result = <%= read_service_name %>.get_all_<%= pluralized_variable_name %>( + ::ListQuery.new( + page: params[:page] || 1, + page_size: params[:page_size] || 10, + q: params[:q] || {} + ) + ) end def new @@ -27,25 +36,31 @@ ) ) redirect_to <%= view_path_name %>_path, notice: "<%= class_name %> was successfully created!" + rescue ConstraintError => e + @form = e.validator + render :new, status: :unprocessable_entity end def show - @<%= singularize_variable_name %> = <%= service_name %>.get_<%= singularize_variable_name %>(params[:id]) + @<%= singularize_variable_name %> = <%= read_service_name %>.get_<%= singularize_variable_name %>(params[:id]) end def edit - @<%= singularize_variable_name %> = <%= service_name %>.get_<%= singularize_variable_name %>(params[:id]) + @<%= singularize_variable_name %> = <%= read_service_name %>.get_<%= singularize_variable_name %>(params[:id]) @form = Update<%= class_name.singularize %>Validator.new(params: { <% options[:attributes].each do |field_name, value| %><%= field_name %>: @<%= singularize_variable_name %>.<%= field_name %>, <% end %> }) end def update <%= service_name %>.update_<%= singularize_variable_name %>(validator.validate(<%= singularize_variable_name %>_params, Update<%= class_name.singularize %>Validator.new, { id: params[:id] })) - redirect_to <%= view_path_name %>_path, notice: "<%= class_name %> was successfully updated!" + redirect_to <%= view_path_name %>_path(page: params[:page], q: params[:q].as_json), notice: "<%= class_name %> was successfully updated!" + rescue ConstraintError => e + @form = e.validator + render :edit, status: :unprocessable_entity end def destroy <%= service_name %>.delete_<%= singularize_variable_name %>(params[:id]) - redirect_to <%= view_path_name %>_path, notice: "<%= class_name %> was successfully deleted!" + redirect_to <%= view_path_name %>_path(page: params[:page], q: params[:q].as_json), notice: "<%= class_name %> was successfully deleted!" end private @@ -55,5 +70,4 @@ end end end -end -<% end %> \ No newline at end of file +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/create_validation.rb.tt b/lib/dry_module_generator/module/templates/ui/create_validation.rb.tt new file mode 100644 index 0000000..692a60a --- /dev/null +++ b/lib/dry_module_generator/module/templates/ui/create_validation.rb.tt @@ -0,0 +1,9 @@ +module <%= module_name.capitalize %> + module Ui + class Create<%= class_name %>Validator < ApplicationContract + params do + <%= contract_definition.join("\n ") %> + end + end + end +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/validation.rb.tt b/lib/dry_module_generator/module/templates/ui/update_validation.rb.tt similarity index 71% rename from lib/dry_module_generator/module/templates/ui/validation.rb.tt rename to lib/dry_module_generator/module/templates/ui/update_validation.rb.tt index 2aa994a..ed9a983 100644 --- a/lib/dry_module_generator/module/templates/ui/validation.rb.tt +++ b/lib/dry_module_generator/module/templates/ui/update_validation.rb.tt @@ -1,7 +1,6 @@ -<% module_namespacing do -%> module <%= module_name.capitalize %> module Ui - class <%= @action_type %><%= class_name %>Validator < ApplicationContract + class Update<%= class_name %>Validator < ApplicationContract params do <%= contract_definition.join("\n ") %> end @@ -11,5 +10,4 @@ module <%= module_name.capitalize %> end end end -end -<% end %> \ No newline at end of file +end \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/views/beercss/erb/_form.html.erb.tt b/lib/dry_module_generator/module/templates/ui/views/beercss/erb/_form.html.erb.tt new file mode 100644 index 0000000..14e98b7 --- /dev/null +++ b/lib/dry_module_generator/module/templates/ui/views/beercss/erb/_form.html.erb.tt @@ -0,0 +1,10 @@ +<%%= form_tag(url, method: method, id: 'form', "data-controller": 'form') do %> + <% form_attributes.each do |attribute| %>
+ <%%= <%= attribute[:type] %> '<%= class_name.downcase %>[<%= attribute[:field_name] %>]', @form.params.dig(:<%= attribute[:field_name] %>) %> + <%%= label_tag '<%= class_name.downcase %>[<%= attribute[:field_name] %>]', '<%= attribute[:label].humanize %>' %> + <%%= show_error(@form, :<%= attribute[:field_name] %>) %> +
+ <% end %> + <%%= button_tag button_text %> + <%%= link_to 'Back', <%= class_name.pluralize.downcase %>_path(page: params[:page], q: params[:q].as_json), class: 'button border' %> +<%% end %> \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/views/beercss/erb/_table_filter.html.erb.tt b/lib/dry_module_generator/module/templates/ui/views/beercss/erb/_table_filter.html.erb.tt new file mode 100644 index 0000000..aa9046b --- /dev/null +++ b/lib/dry_module_generator/module/templates/ui/views/beercss/erb/_table_filter.html.erb.tt @@ -0,0 +1,14 @@ +<%%= search_form_for <%= module_name.capitalize %>::Domain::<%= class_name %>.ransack(params[:q]), url: <%= class_name.pluralize.downcase %>_path, data: { "data-controller": "form" } do |f| %> +
<% options[:attributes].each do |field_name, _| %> +
+
+ <%%= f.search_field :<%= field_name %>_eq, placeholder: ' ' %> + <%%= f.label :<%= field_name %>_cont, '<%= field_name.capitalize.humanize %>' %> +
+
<% end %> + +
+ <%%= button_tag 'Search', class: 'large no-margin responsive' %> +
+
+<%% end %> \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/views/beercss/erb/edit.html.erb.tt b/lib/dry_module_generator/module/templates/ui/views/beercss/erb/edit.html.erb.tt new file mode 100644 index 0000000..28f9a3b --- /dev/null +++ b/lib/dry_module_generator/module/templates/ui/views/beercss/erb/edit.html.erb.tt @@ -0,0 +1,5 @@ +
+

Edit <%= class_name %>

+
+ +<%%= render 'form', url: <%= class_name.downcase %>_path(id: params[:id], page: params[:page], q: params[:q].as_json), method: :patch, button_text: 'Update' %> \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/views/beercss/erb/index.html.erb.tt b/lib/dry_module_generator/module/templates/ui/views/beercss/erb/index.html.erb.tt new file mode 100644 index 0000000..c578def --- /dev/null +++ b/lib/dry_module_generator/module/templates/ui/views/beercss/erb/index.html.erb.tt @@ -0,0 +1,48 @@ +
+

<%= class_name.pluralize %>

+ <%%= link_to new_<%= class_name.downcase %>_path(page: params[:page], q: params[:q].as_json), class: 'button extend circle' do %> + add + Add <%= class_name.singularize %> + <%% end %> +
+ +<%%= render 'table_filter' %> +
+<%%= turbo_frame_tag '<%= class_name.pluralize.downcase %>', "data-turbo-action": 'advance' do %> +
+ + + <% options[:attributes].each do |field_name, _| %> + <% end %> + + + + + <%% @paginated_result.data.each do |<%= class_name.downcase %>| %> + <% options[:attributes].each do |field_name, _| %> + <% end %> + + + <%% end %> + +
<%= field_name.capitalize.humanize %>

<%%= <%= class_name.downcase %>.<%= field_name %> || '/' %>

+ +
+
+ + <%% if @paginated_result.pagination.pages > 1 %> + <%%= render 'shared/pagination' %> + <%% end %> +<%% end %> \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/views/beercss/erb/new.html.erb.tt b/lib/dry_module_generator/module/templates/ui/views/beercss/erb/new.html.erb.tt new file mode 100644 index 0000000..f60e00d --- /dev/null +++ b/lib/dry_module_generator/module/templates/ui/views/beercss/erb/new.html.erb.tt @@ -0,0 +1,5 @@ +
+

Add <%= class_name %>

+
+ +<%%= render 'form', url: <%= class_name.pluralize.downcase %>_path(page: params[:page], q: params[:q].as_json), method: :post, button_text: 'Save' %> \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/views/beercss/erb/show.html.erb.tt b/lib/dry_module_generator/module/templates/ui/views/beercss/erb/show.html.erb.tt new file mode 100644 index 0000000..e06a618 --- /dev/null +++ b/lib/dry_module_generator/module/templates/ui/views/beercss/erb/show.html.erb.tt @@ -0,0 +1,16 @@ +
+

<%= class_name %> Details

+
+ +<% options[:attributes].each do |field_name, _| %>
+
+
<%= field_name.capitalize.humanize %>
+
+
+

<%%= @<%= class_name.downcase %>.<%= field_name %> || '/' %>

+
+
+ +
<% end %> + +<%%= link_to "Back to <%= class_name.pluralize.downcase %>", <%= class_name.pluralize.downcase %>_path(page: params[:page], q: params[:q].as_json), class: "button border large-margin", style: 'margin-left: 0 !important;' %> \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/views/form.rb.tt b/lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/_form.html.erb.tt similarity index 94% rename from lib/dry_module_generator/module/templates/ui/views/form.rb.tt rename to lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/_form.html.erb.tt index dec2e57..3119dc0 100644 --- a/lib/dry_module_generator/module/templates/ui/views/form.rb.tt +++ b/lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/_form.html.erb.tt @@ -1,4 +1,3 @@ -<% module_namespacing do -%> <%%= form_tag(url, method: method, id: 'form', novalidate: true, "data-controller": 'form') do %> <% form_attributes.each do |attribute| %>
<%%= <%= attribute[:type] %> '<%= class_name.downcase %>[<%= attribute[:field_name] %>]', @form.params.dig(:<%= attribute[:field_name] %>), class: 'form-control', placeholder: '<%= attribute[:field_name].capitalize.humanize %>', required: <%= attribute[:required] %> %> @@ -8,5 +7,4 @@ <% end %> <%%= button_tag button_text, class: 'btn btn-primary' %> <%%= link_to "Back", <%= class_name.pluralize.downcase %>_path, class: "btn btn-danger" %> -<%% end %> -<% end %> +<%% end %> \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/_table_filter.html.erb.tt b/lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/_table_filter.html.erb.tt new file mode 100644 index 0000000..3cd3e48 --- /dev/null +++ b/lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/_table_filter.html.erb.tt @@ -0,0 +1,12 @@ +<%%= search_form_for <%= module_name.capitalize %>::Domain::<%= class_name %>.ransack(params[:q]), url: <%= class_name.pluralize.downcase %>_path, data: { "data-controller": "form" } do |f| %> +
<% options[:attributes].each do |field_name, _| %> +
+ <%%= f.label :<%= field_name %>_cont, '<%= field_name.capitalize.humanize %>', class: 'visually-hidden' %> + <%%= f.search_field :<%= field_name %>_eq, placeholder: '<%= field_name.capitalize %>', class: 'form-control' %> +
<% end %> + +
+ <%%= button_tag 'Search', class: 'btn btn-primary' %> +
+
+<%% end %> \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/views/edit.rb.tt b/lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/edit.html.erb.tt similarity index 64% rename from lib/dry_module_generator/module/templates/ui/views/edit.rb.tt rename to lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/edit.html.erb.tt index f3cd953..a1ebfa4 100644 --- a/lib/dry_module_generator/module/templates/ui/views/edit.rb.tt +++ b/lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/edit.html.erb.tt @@ -1,6 +1,4 @@ -<% module_namespacing do -%>
Edit <%= class_name %>
-<%%= render 'form', url: <%= class_name.downcase %>_path(id: params[:id]), method: :patch, button_text: 'Update' %> -<% end %> +<%%= render 'form', url: <%= class_name.downcase %>_path(id: params[:id]), method: :patch, button_text: 'Update' %> \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/views/index.rb.tt b/lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/index.html.erb.tt similarity index 91% rename from lib/dry_module_generator/module/templates/ui/views/index.rb.tt rename to lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/index.html.erb.tt index 8af22b3..a6da02f 100644 --- a/lib/dry_module_generator/module/templates/ui/views/index.rb.tt +++ b/lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/index.html.erb.tt @@ -1,4 +1,3 @@ -<% module_namespacing do -%>
<%= class_name.pluralize %> @@ -9,6 +8,7 @@
+
@@ -19,7 +19,7 @@ - <%% @<%= class_name.pluralize.downcase %>.each do |<%= class_name.downcase %>| %> + <%% @paginated_result.data.each do |<%= class_name.downcase %>| %> <% options[:attributes].each do |field_name, _| %> <% end %>
<%%= <%= class_name.downcase %>.<%= field_name %> || '/' %> @@ -31,6 +31,4 @@ <%% end %>
-
-<% end %> - +
\ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/new.html.erb.tt b/lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/new.html.erb.tt new file mode 100644 index 0000000..e418107 --- /dev/null +++ b/lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/new.html.erb.tt @@ -0,0 +1,4 @@ +
+ New <%= class_name %> +
+<%%= render 'form', url: <%= class_name.pluralize.downcase %>_path, method: :post, button_text: 'Save' %> \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/views/show.rb.tt b/lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/show.html.erb.tt similarity index 93% rename from lib/dry_module_generator/module/templates/ui/views/show.rb.tt rename to lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/show.html.erb.tt index 47735c9..a8f9997 100644 --- a/lib/dry_module_generator/module/templates/ui/views/show.rb.tt +++ b/lib/dry_module_generator/module/templates/ui/views/bootstrap5/erb/show.html.erb.tt @@ -1,4 +1,3 @@ -<% module_namespacing do -%>
<%= class_name %> Details
@@ -12,5 +11,4 @@
<% end %> -<%%= link_to "Back to <%= class_name.pluralize.downcase %>", <%= class_name.pluralize.downcase %>_path, class: "btn btn-danger" %> -<% end %> \ No newline at end of file +<%%= link_to "Back to <%= class_name.pluralize.downcase %>", <%= class_name.pluralize.downcase %>_path, class: "btn btn-danger" %> \ No newline at end of file diff --git a/lib/dry_module_generator/module/templates/ui/views/new.rb.tt b/lib/dry_module_generator/module/templates/ui/views/new.rb.tt deleted file mode 100644 index 1d5faec..0000000 --- a/lib/dry_module_generator/module/templates/ui/views/new.rb.tt +++ /dev/null @@ -1,7 +0,0 @@ -<% module_namespacing do -%> -
- Add <%= class_name %> -
-<%%= render 'form', url: <%= class_name.pluralize.downcase %>_path, method: :post, button_text: 'Save' %> -<% end %> - diff --git a/lib/dry_module_generator/uninstall/uninstaller.rb b/lib/dry_module_generator/uninstall/uninstaller.rb deleted file mode 100644 index 68a6623..0000000 --- a/lib/dry_module_generator/uninstall/uninstaller.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module DryModuleGenerator - class Uninstaller < Rails::Generators::Base - source_root File.expand_path("templates", __dir__) - namespace "dry_module:uninstall" - - # causing class errors - def remove_config_files - remove_file "config/initializers/container.rb" - remove_file "config/initializers/dependency_injection.rb" - remove_file "config/initializers/dry_struct_generator.rb" - remove_file "config/initializers/routes.rb" - end - - def remove_utility_files - remove_file "lib/utils/types.rb" - remove_file "lib/utils/application_struct.rb" - remove_file "lib/utils/application_read_struct.rb" - remove_file "lib/utils/application_contract.rb" - remove_file "lib/utils/contract_validator.rb" - FileUtils.remove_dir("lib/utils/injection", force: true) - end - - def remove_error_file - remove_file "app/errors/constraint_error.rb" - end - - def remove_service_file - remove_file "app/services/application_service.rb" - end - end -end