rage_arch 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +190 -0
  4. data/lib/generators/rage_arch/ar_dep_generator.rb +74 -0
  5. data/lib/generators/rage_arch/dep_generator.rb +120 -0
  6. data/lib/generators/rage_arch/dep_switch_generator.rb +224 -0
  7. data/lib/generators/rage_arch/install_generator.rb +64 -0
  8. data/lib/generators/rage_arch/scaffold_generator.rb +133 -0
  9. data/lib/generators/rage_arch/templates/ar_dep.rb.tt +46 -0
  10. data/lib/generators/rage_arch/templates/dep.rb.tt +16 -0
  11. data/lib/generators/rage_arch/templates/rage_arch.rb.tt +15 -0
  12. data/lib/generators/rage_arch/templates/scaffold/api_controller.rb.tt +39 -0
  13. data/lib/generators/rage_arch/templates/scaffold/controller.rb.tt +56 -0
  14. data/lib/generators/rage_arch/templates/scaffold/create.rb.tt +14 -0
  15. data/lib/generators/rage_arch/templates/scaffold/destroy.rb.tt +15 -0
  16. data/lib/generators/rage_arch/templates/scaffold/list.rb.tt +13 -0
  17. data/lib/generators/rage_arch/templates/scaffold/new.rb.tt +13 -0
  18. data/lib/generators/rage_arch/templates/scaffold/post_repo.rb.tt +35 -0
  19. data/lib/generators/rage_arch/templates/scaffold/show.rb.tt +14 -0
  20. data/lib/generators/rage_arch/templates/scaffold/update.rb.tt +15 -0
  21. data/lib/generators/rage_arch/templates/use_case.rb.tt +18 -0
  22. data/lib/generators/rage_arch/use_case_generator.rb +33 -0
  23. data/lib/rage_arch/container.rb +38 -0
  24. data/lib/rage_arch/controller.rb +22 -0
  25. data/lib/rage_arch/dep.rb +9 -0
  26. data/lib/rage_arch/dep_scanner.rb +95 -0
  27. data/lib/rage_arch/deps/active_record.rb +45 -0
  28. data/lib/rage_arch/event_publisher.rb +59 -0
  29. data/lib/rage_arch/fake_event_publisher.rb +37 -0
  30. data/lib/rage_arch/railtie.rb +23 -0
  31. data/lib/rage_arch/result.rb +31 -0
  32. data/lib/rage_arch/rspec_matchers.rb +94 -0
  33. data/lib/rage_arch/use_case.rb +252 -0
  34. data/lib/rage_arch/version.rb +5 -0
  35. data/lib/rage_arch.rb +97 -0
  36. metadata +133 -0
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module RageArch
6
+ module Generators
7
+ class InstallGenerator < ::Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Create config/initializers/rage_arch.rb, app/use_cases, app/deps, and include RageArch::Controller in ApplicationController"
11
+ def install
12
+ create_initializer
13
+ create_directories
14
+ inject_controller
15
+ end
16
+
17
+ private
18
+
19
+ def create_initializer
20
+ path = "config/initializers/rage_arch.rb"
21
+ full_path = File.join(destination_root, path)
22
+ if File.exist?(full_path)
23
+ say_status :skip, path, :yellow
24
+ return
25
+ end
26
+ template "rage.rb.tt", path
27
+ end
28
+
29
+ def create_directories
30
+ %w[app/use_cases app/deps].each do |dir|
31
+ full_path = File.join(destination_root, dir)
32
+ if File.directory?(full_path)
33
+ say_status :skip, dir, :yellow
34
+ next
35
+ end
36
+ empty_directory dir
37
+ create_file File.join(dir, ".keep"), ""
38
+ end
39
+ end
40
+
41
+ def inject_controller
42
+ path = "app/controllers/application_controller.rb"
43
+ full_path = File.join(destination_root, path)
44
+ unless File.exist?(full_path)
45
+ say_status :skip, path, :yellow
46
+ return
47
+ end
48
+ content = File.read(full_path)
49
+ if content.include?("RageArch::Controller")
50
+ say_status :skip, path, :yellow
51
+ return
52
+ end
53
+ injection = " include RageArch::Controller\n"
54
+ if content =~ /(class\s+ApplicationController\s*<\s*\S+)/
55
+ content.sub!(Regexp.last_match(0), "#{Regexp.last_match(0)}\n#{injection.chomp}")
56
+ File.write(full_path, content)
57
+ say_status :inject, path, :green
58
+ else
59
+ say_status :skip, path, :yellow
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+ require "rails/generators/active_record"
5
+
6
+ module RageArch
7
+ module Generators
8
+ class ScaffoldGenerator < ::Rails::Generators::NamedBase
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ argument :attributes, type: :array, default: [], banner: "field:type field:type"
12
+ class_option :skip_model, type: :boolean, default: false, desc: "Skip model and migration (use when model already exists)"
13
+ class_option :api, type: :boolean, default: false, desc: "Generate API-only controller (JSON responses, no views)"
14
+
15
+ desc "Generate a full Rage CRUD: model, migration, use cases (list/show/create/update/destroy), dep (AR), controller, and routes. With --api: JSON responses only, no views."
16
+ def create_all
17
+ create_model_and_migration
18
+ create_use_cases
19
+ create_dep
20
+ invoke_rails_scaffold_views
21
+ create_controller
22
+ add_route
23
+ inject_register_ar
24
+ end
25
+
26
+ private
27
+
28
+ def create_model_and_migration
29
+ return if options[:skip_model]
30
+ args = [name] + attributes.map(&:to_s)
31
+ invoke "active_record:model", args
32
+ end
33
+
34
+ def create_use_cases
35
+ dir = File.join("app/use_cases", plural_name)
36
+ empty_directory dir
37
+ template "scaffold/list.rb.tt", File.join(dir, "list.rb")
38
+ template "scaffold/show.rb.tt", File.join(dir, "show.rb")
39
+ template "scaffold/new.rb.tt", File.join(dir, "new.rb")
40
+ template "scaffold/create.rb.tt", File.join(dir, "create.rb")
41
+ template "scaffold/update.rb.tt", File.join(dir, "update.rb")
42
+ template "scaffold/destroy.rb.tt", File.join(dir, "destroy.rb")
43
+ end
44
+
45
+ def create_dep
46
+ dep_dir = File.join("app/deps", plural_name)
47
+ empty_directory dep_dir
48
+ template "scaffold/post_repo.rb.tt", File.join(dep_dir, "#{singular_name}_repo.rb")
49
+ end
50
+
51
+ def create_controller
52
+ template_name = options[:api] ? "scaffold/api_controller.rb.tt" : "scaffold/controller.rb.tt"
53
+ template template_name, File.join("app/controllers", "#{plural_name}_controller.rb"), force: true
54
+ end
55
+
56
+ # Reuse Rails' scaffold_controller: with --api it generates API controller (no views); otherwise controller + views.
57
+ def invoke_rails_scaffold_views
58
+ args = [name] + attributes.map(&:to_s)
59
+ opts = { skip_routes: true }
60
+ opts[:api] = true if options[:api]
61
+ invoke "scaffold_controller", args, opts
62
+ end
63
+
64
+ def add_route
65
+ route "resources :#{plural_name}"
66
+ end
67
+
68
+ def inject_register_ar
69
+ initializer_path = File.join(destination_root, "config/initializers/rage_arch.rb")
70
+ return unless File.exist?(initializer_path)
71
+ content = File.read(initializer_path)
72
+ return if content.include?("register_ar(:#{repo_symbol})")
73
+ inject_line = " Rage.register_ar(:#{repo_symbol}, #{model_class_name})\n"
74
+ content.sub!(/(Rails\.application\.config\.after_initialize do\s*\n)/m, "\\1#{inject_line}")
75
+ File.write(initializer_path, content)
76
+ say_status :inject, "config/initializers/rage_arch.rb (register_ar :#{repo_symbol})", :green
77
+ end
78
+
79
+ def plural_name
80
+ name.underscore.pluralize
81
+ end
82
+
83
+ def singular_name
84
+ name.underscore
85
+ end
86
+
87
+ def model_class_name
88
+ name.camelize
89
+ end
90
+
91
+ def module_name
92
+ plural_name.camelize
93
+ end
94
+
95
+ def repo_symbol
96
+ "#{singular_name}_repo"
97
+ end
98
+
99
+ def repo_class_name
100
+ "#{module_name}::#{singular_name.camelize}Repo"
101
+ end
102
+
103
+ def list_symbol
104
+ "#{plural_name}_list"
105
+ end
106
+
107
+ def show_symbol
108
+ "#{plural_name}_show"
109
+ end
110
+
111
+ def create_symbol
112
+ "#{plural_name}_create"
113
+ end
114
+
115
+ def update_symbol
116
+ "#{plural_name}_update"
117
+ end
118
+
119
+ def destroy_symbol
120
+ "#{plural_name}_destroy"
121
+ end
122
+
123
+ def new_symbol
124
+ "#{plural_name}_new"
125
+ end
126
+
127
+ def attribute_names_for_permit
128
+ return [] if attributes.blank?
129
+ attributes.map { |a| a.to_s.split(":").first.to_sym }
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Dep for :<%= symbol_name %>. Wraps the <%= model_name %> Active Record model.
4
+ # Register in config/initializers/rage_arch.rb: Rage.register(:<%= symbol_name %>, <%= full_class_name %>.new)
5
+ module <%= module_name %>
6
+ class <%= class_name %>
7
+ def initialize
8
+ @adapter = RageArch::Deps::ActiveRecord.for(<%= model_name %>)
9
+ end
10
+
11
+ def build(attrs = {})
12
+ @adapter.build(attrs)
13
+ end
14
+
15
+ def find(id)
16
+ @adapter.find(id)
17
+ end
18
+
19
+ def save(record)
20
+ @adapter.save(record)
21
+ end
22
+
23
+ def update(record, attrs)
24
+ @adapter.update(record, attrs)
25
+ end
26
+
27
+ def destroy(record)
28
+ @adapter.destroy(record)
29
+ end
30
+
31
+ def list(filters: {})
32
+ @adapter.list(filters: filters)
33
+ end
34
+ <% if @extra_methods.any? -%>
35
+
36
+ # Extra methods detected from use cases — implement as needed
37
+ <% @extra_methods.each do |method_name| -%>
38
+
39
+ def <%= method_name %>(*args, **kwargs)
40
+ # TODO: implement (e.g. delegate to @adapter or custom logic)
41
+ raise NotImplementedError, "<%= full_class_name %>#<%= method_name %>"
42
+ end
43
+ <% end -%>
44
+ <% end -%>
45
+ end
46
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Dep for :<%= symbol_name %>.
4
+ # Methods detected from use cases: <%= @methods.join(', ') %>.
5
+ # Register in config/initializers/rage_arch.rb: Rage.register(:<%= symbol_name %>, <%= full_class_name %>.new)
6
+ module <%= module_name %>
7
+ class <%= class_name %>
8
+ <% @methods.each do |method_name| -%>
9
+
10
+ def <%= method_name %>(*args, **kwargs)
11
+ # TODO: implement
12
+ raise NotImplementedError, "<%= full_class_name %>#<%= method_name %>"
13
+ end
14
+ <% end -%>
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Register your app's deps here. Deps are grouped by module (e.g. app/deps/posts/post_repo.rb → Posts::PostRepo).
4
+ # Use Rage.register(:symbol, ClassName.new) or Rage.register_ar(:symbol, Model) for AR-backed deps.
5
+
6
+ Rails.application.config.after_initialize do
7
+ # Deps
8
+ # Rage.register(:post_repo, Posts::PostRepo.new)
9
+ # Rage.register_ar(:user_repo, User)
10
+
11
+ # Event publisher: use cases that declare subscribe :event_name are wired here.
12
+ publisher = RageArch::EventPublisher.new
13
+ RageArch::UseCase::Base.wire_subscriptions_to(publisher)
14
+ Rage.register(:event_publisher, publisher)
15
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= module_name %>Controller < ApplicationController
4
+ def index
5
+ run :<%= list_symbol %>, {},
6
+ success: ->(r) { render json: r.value[:<%= singular_name %>s] },
7
+ failure: ->(r) { render json: { errors: r.errors }, status: :unprocessable_entity }
8
+ end
9
+
10
+ def show
11
+ run :<%= show_symbol %>, { id: params[:id] },
12
+ success: ->(r) { render json: r.value[:<%= singular_name %>] },
13
+ failure: ->(r) { render json: { errors: r.errors }, status: :not_found }
14
+ end
15
+
16
+ def create
17
+ run :<%= create_symbol %>, <%= singular_name %>_params,
18
+ success: ->(r) { render json: r.value[:<%= singular_name %>], status: :created },
19
+ failure: ->(r) { render json: { errors: r.errors }, status: :unprocessable_entity }
20
+ end
21
+
22
+ def update
23
+ run :<%= update_symbol %>, <%= singular_name %>_params.merge(id: params[:id]),
24
+ success: ->(r) { render json: r.value[:<%= singular_name %>] },
25
+ failure: ->(r) { render json: { errors: r.errors }, status: :unprocessable_entity }
26
+ end
27
+
28
+ def destroy
29
+ run :<%= destroy_symbol %>, { id: params[:id] },
30
+ success: ->(_r) { head :no_content },
31
+ failure: ->(r) { render json: { errors: r.errors }, status: :not_found }
32
+ end
33
+
34
+ private
35
+
36
+ def <%= singular_name %>_params
37
+ params.fetch(:<%= singular_name %>, params).permit(<%= attribute_names_for_permit.map { |x| ":#{x}" }.join(", ") %>)
38
+ end
39
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= module_name %>Controller < ApplicationController
4
+ def index
5
+ run :<%= list_symbol %>, {},
6
+ success: ->(r) { @<%= plural_name %> = r.value[:<%= singular_name %>s]; render :index },
7
+ failure: ->(r) { redirect_to root_path, alert: r.errors.join(", ") }
8
+ end
9
+
10
+ def show
11
+ run :<%= show_symbol %>, { id: params[:id] },
12
+ success: ->(r) { @<%= singular_name %> = r.value[:<%= singular_name %>]; render :show },
13
+ failure: ->(r) { redirect_to <%= plural_name %>_path, alert: r.errors.join(", ") }
14
+ end
15
+
16
+ def new
17
+ run :<%= new_symbol %>, {},
18
+ success: ->(r) { @<%= singular_name %> = r.value[:<%= singular_name %>]; render :new },
19
+ failure: ->(r) { redirect_to <%= plural_name %>_path, alert: r.errors.join(", ") }
20
+ end
21
+
22
+ def create
23
+ run :<%= create_symbol %>, <%= singular_name %>_params,
24
+ success: ->(r) { redirect_to <%= singular_name %>_path(r.value[:<%= singular_name %>].id), notice: "<%= model_class_name %> was successfully created." },
25
+ failure: ->(r) { @<%= singular_name %> = run_result(:<%= new_symbol %>, {}).value[:<%= singular_name %>]; flash_errors(r); render :new, status: :unprocessable_entity }
26
+ end
27
+
28
+ def edit
29
+ run :<%= show_symbol %>, { id: params[:id] },
30
+ success: ->(r) { @<%= singular_name %> = r.value[:<%= singular_name %>]; render :edit },
31
+ failure: ->(r) { redirect_to <%= plural_name %>_path, alert: r.errors.join(", ") }
32
+ end
33
+
34
+ def update
35
+ run :<%= update_symbol %>, <%= singular_name %>_params.merge(id: params[:id]),
36
+ success: ->(r) { redirect_to <%= singular_name %>_path(params[:id]), notice: "<%= model_class_name %> was successfully updated." },
37
+ failure: ->(r) {
38
+ res = run_result(:<%= show_symbol %>, id: params[:id])
39
+ @<%= singular_name %> = res.success? ? res.value[:<%= singular_name %>] : nil
40
+ flash_errors(r)
41
+ render :edit, status: :unprocessable_entity
42
+ }
43
+ end
44
+
45
+ def destroy
46
+ run :<%= destroy_symbol %>, { id: params[:id] },
47
+ success: ->(_r) { redirect_to <%= plural_name %>_path, notice: "<%= model_class_name %> was successfully destroyed." },
48
+ failure: ->(r) { redirect_to <%= plural_name %>_path, alert: r.errors.join(", ") }
49
+ end
50
+
51
+ private
52
+
53
+ def <%= singular_name %>_params
54
+ params.fetch(:<%= singular_name %>, params).permit(<%= attribute_names_for_permit.map { |x| ":#{x}" }.join(", ") %>)
55
+ end
56
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= module_name %>
4
+ class Create < RageArch::UseCase::Base
5
+ use_case_symbol :<%= create_symbol %>
6
+ deps :<%= repo_symbol %>
7
+
8
+ def call(params = {})
9
+ item = <%= repo_symbol %>.build(params)
10
+ return failure(item.errors.full_messages) unless <%= repo_symbol %>.save(item)
11
+ success(<%= singular_name %>: item)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= module_name %>
4
+ class Destroy < RageArch::UseCase::Base
5
+ use_case_symbol :<%= destroy_symbol %>
6
+ deps :<%= repo_symbol %>
7
+
8
+ def call(params = {})
9
+ item = <%= repo_symbol %>.find(params[:id])
10
+ return failure(["<%= model_class_name %> not found"]) unless item
11
+ <%= repo_symbol %>.destroy(item)
12
+ success(<%= singular_name %>: item)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= module_name %>
4
+ class List < RageArch::UseCase::Base
5
+ use_case_symbol :<%= list_symbol %>
6
+ deps :<%= repo_symbol %>
7
+
8
+ def call(_params = {})
9
+ items = <%= repo_symbol %>.list
10
+ success(<%= singular_name %>s: items)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= module_name %>
4
+ class New < RageArch::UseCase::Base
5
+ use_case_symbol :<%= new_symbol %>
6
+ deps :<%= repo_symbol %>
7
+
8
+ def call(_params = {})
9
+ item = <%= repo_symbol %>.build({})
10
+ success(<%= singular_name %>: item)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Dep for :<%= repo_symbol %>. Wraps the <%= model_class_name %> Active Record model.
4
+ # Registered by rage:scaffold in config/initializers/rage_arch.rb
5
+ module <%= module_name %>
6
+ class <%= singular_name.camelize %>Repo
7
+ def initialize
8
+ @adapter = RageArch::Deps::ActiveRecord.for(<%= model_class_name %>)
9
+ end
10
+
11
+ def build(attrs = {})
12
+ @adapter.build(attrs)
13
+ end
14
+
15
+ def find(id)
16
+ @adapter.find(id)
17
+ end
18
+
19
+ def save(record)
20
+ @adapter.save(record)
21
+ end
22
+
23
+ def update(record, attrs)
24
+ @adapter.update(record, attrs)
25
+ end
26
+
27
+ def destroy(record)
28
+ @adapter.destroy(record)
29
+ end
30
+
31
+ def list(filters: {})
32
+ @adapter.list(filters: filters)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= module_name %>
4
+ class Show < RageArch::UseCase::Base
5
+ use_case_symbol :<%= show_symbol %>
6
+ deps :<%= repo_symbol %>
7
+
8
+ def call(params = {})
9
+ item = <%= repo_symbol %>.find(params[:id])
10
+ return failure(["<%= model_class_name %> not found"]) unless item
11
+ success(<%= singular_name %>: item)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= module_name %>
4
+ class Update < RageArch::UseCase::Base
5
+ use_case_symbol :<%= update_symbol %>
6
+ deps :<%= repo_symbol %>
7
+
8
+ def call(params = {})
9
+ item = <%= repo_symbol %>.find(params[:id])
10
+ return failure(["<%= model_class_name %> not found"]) unless item
11
+ return failure(item.errors.full_messages) unless <%= repo_symbol %>.update(item, params.except(:id))
12
+ success(<%= singular_name %>: item)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ <%- if class_path.any? -%>
4
+ module <%= class_path.map(&:camelize).join('::') %>
5
+ <%- end -%>
6
+ class <%= file_name.camelize %> < RageArch::UseCase::Base
7
+ use_case_symbol :<%= file_name %>
8
+
9
+ deps # e.g. :order_store, :notifications
10
+
11
+ def call(params = {})
12
+ # TODO: implement use case logic
13
+ RageArch::Result.success(params)
14
+ end
15
+ end
16
+ <%- class_path.each do -%>
17
+ end
18
+ <%- end -%>
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/named_base"
4
+
5
+ module RageArch
6
+ module Generators
7
+ class UseCaseGenerator < ::Rails::Generators::NamedBase
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Create a use case in app/use_cases/."
11
+ def create_use_case
12
+ template "use_case.rb.tt", File.join("app/use_cases", *class_path, "#{file_name}.rb")
13
+ end
14
+
15
+ def class_name_without_module
16
+ file_name.camelize
17
+ end
18
+
19
+ def symbol_name
20
+ file_name
21
+ end
22
+
23
+ def use_case_symbol
24
+ ":#{symbol_name}"
25
+ end
26
+
27
+ def module_namespace
28
+ return "" if class_path.empty?
29
+ class_path.map(&:camelize).join("::") + "::"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RageArch
4
+ # Container to register and resolve dependencies by symbol.
5
+ # Usage: Rage.register(:order_store, MyAdapter.new); Rage.resolve(:order_store)
6
+ class Container
7
+ class << self
8
+ def register(symbol, implementation = nil, &block)
9
+ registry[symbol] = block || implementation
10
+ end
11
+
12
+ def resolve(symbol)
13
+ entry = registry[symbol]
14
+ raise KeyError, "Dep not registered: #{symbol.inspect}" unless entry
15
+
16
+ if entry.respond_to?(:call) && entry.is_a?(Proc)
17
+ entry.call
18
+ elsif entry.is_a?(Class)
19
+ entry.new
20
+ else
21
+ entry
22
+ end
23
+ end
24
+
25
+ def registered?(symbol)
26
+ registry.key?(symbol)
27
+ end
28
+
29
+ def registry
30
+ @registry ||= {}
31
+ end
32
+
33
+ def reset!
34
+ @registry = {}
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RageArch
4
+ # Include in ApplicationController to get run(symbol, params, success:, failure:) and
5
+ # flash_errors(result). Keeps controllers thin: symbol, params, and HTTP responses only.
6
+ module Controller
7
+ private
8
+
9
+ def run(symbol, params = {}, success:, failure:)
10
+ result = run_result(symbol, params)
11
+ (result.success? ? success : failure).call(result)
12
+ end
13
+
14
+ def run_result(symbol, params = {})
15
+ RageArch::UseCase::Base.build(symbol).call(params)
16
+ end
17
+
18
+ def flash_errors(result)
19
+ flash.now[:alert] = result.errors.join(", ")
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RageArch
4
+ # Convention: a "dep" is any injectable external dependency (persistence, mailer, API).
5
+ # Register it in RageArch::Container by symbol and resolve it in the use case with dep(:symbol).
6
+ # No base class required; any object can be a dep.
7
+ module Dep
8
+ end
9
+ end