rage_arch 0.2.0 → 0.2.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5977f7f0cda9c29591b5fada240de2a007facc8c3922e8f2a9c6a5ae08ed1f5b
4
- data.tar.gz: 05b8e5a3a44cb57099c64b33c9cffba6a0abf80dc6256c41eabe87e86d48378e
3
+ metadata.gz: 67361dabc2170be9ff033ec5cacbc0c271d01351bccde53e04804417043fc1ac
4
+ data.tar.gz: fede1669810366cc822f31ce1e14152c564365ed5934cab2b34eb0707e5245c6
5
5
  SHA512:
6
- metadata.gz: 25f7ef50af078a7b61663d26fd8586c5d512709b116ca206266c8d04e1d364c19caf5bf131c0886d0061fc7b1db0e7de3c47f2a827360f2a03b03d911ee5336d
7
- data.tar.gz: 06fea4288ebf3eeb5a6e1c3a2baf2895c22514389fbb169a5741474ef500baaaa52b129d0711ee645025fc713a6bd58e2436be05979445c64ec1831206af1437
6
+ metadata.gz: 725210b1791995e3dd9f7964676b2ca7a87016113eb05454dcb7e327efdde2f2715e3691b8689c47ac56292cc800244a32faa9f8d1374a1d0d3e434b58c69664
7
+ data.tar.gz: d846584108e497e4b2ab036a3965f7ef6d6466a6ad57c1fa33e63aab005223b618583153f3710380663ad0e3ae3b3837a2b069b7d1d90fc462b2500d7997a0ef
data/README.md CHANGED
@@ -56,10 +56,10 @@ RageArch.registered?(:order_store) # => true
56
56
 
57
57
  **Convention-based auto-registration:** Use cases from `app/use_cases/` and deps from `app/deps/` are auto-registered at boot. The initializer is only needed to override conventions or register external adapters.
58
58
 
59
- **AR model auto-resolution:** If a dep symbol ends in `_store` and no file exists in `app/deps/`, rage_arch looks for an ActiveRecord model automatically:
59
+ **AR model auto-resolution:** If a dep symbol ends in `_store` or `_repo` and no file exists in `app/deps/`, rage_arch looks for an ActiveRecord model automatically:
60
60
 
61
- - `:post_store` resolves to `Post`
62
- - `:appointment_store` resolves to `Appointment`
61
+ - `:post_store` or `:post_repo` resolves to `Post`
62
+ - `:appointment_store` or `:appointment_repo` resolves to `Appointment`
63
63
 
64
64
  Explicit `RageArch.register(...)` always takes priority.
65
65
 
@@ -288,11 +288,15 @@ end
288
288
  | `rails g rage_arch:scaffold Post title:string` | Full CRUD: model, use cases, dep, controller, views, routes |
289
289
  | `rails g rage_arch:scaffold Post title:string --api` | Same but API-only (JSON responses) |
290
290
  | `rails g rage_arch:scaffold Post title:string --skip-model` | Skip model/migration if it already exists |
291
+ | `rails g rage_arch:resource Post title:string` | Like scaffold but without views (API-style controller) |
292
+ | `rails g rage_arch:controller Pages home about` | Thin controller + use case per action + routes |
291
293
  | `rails g rage_arch:use_case CreateOrder` | Generates a base use case file |
292
294
  | `rails g rage_arch:use_case orders/create` | Generates a namespaced use case (`Orders::Create`) |
293
295
  | `rails g rage_arch:dep post_store` | Generates a dep class by scanning method calls in use cases |
294
296
  | `rails g rage_arch:dep_switch post_store` | Lists implementations and switches which one is registered |
295
297
  | `rails g rage_arch:dep_switch post_store PostgresPostStore` | Directly activates a specific implementation |
298
+ | `rails g rage_arch:mailer PostMailer post_created` | Rails mailer + dep wrapper (auto-registered) |
299
+ | `rails g rage_arch:job ProcessOrder orders_create` | ActiveJob that runs a use case by symbol |
296
300
 
297
301
  ---
298
302
 
@@ -360,7 +364,7 @@ config.rage_arch.async_subscribers = false
360
364
 
361
365
  At boot, `RageArch.verify_deps!` runs automatically and raises if it finds wiring problems. It checks:
362
366
 
363
- - Every dep declared with `deps :symbol` is registered in the container (or auto-resolved for `_store` deps)
367
+ - Every dep declared with `deps :symbol` is registered in the container (or auto-resolved for `_store`/`_repo` deps)
364
368
  - Every method called on a dep is implemented by the registered object (via static analysis)
365
369
  - Every use case declared with `use_cases :symbol` exists in the registry
366
370
  - Warns if `use_case_symbol` doesn't match the convention-inferred symbol
@@ -369,7 +373,7 @@ Example error output:
369
373
 
370
374
  ```
371
375
  RageArch boot verification failed:
372
- UseCase :posts_create (Posts::Create) declares dep :post_store — not registered in container and no AR model found
376
+ UseCase :posts_create (Posts::Create) declares dep :post_store — not registered in container and no AR model found for _store/_repo
373
377
  UseCase :posts_create (Posts::Create) calls dep :post_store#save — Posts::PostStore does not implement #save
374
378
  UseCase :posts_notify (Posts::Notify) declares use_cases :email_send — not registered in use case registry
375
379
  ```
@@ -394,6 +398,7 @@ end
394
398
  ## Documentation
395
399
 
396
400
  - [`doc/GETTING_STARTED.md`](doc/GETTING_STARTED.md) — Getting started guide with common tasks
401
+ - [`doc/TUTORIAL.md`](doc/TUTORIAL.md) — Side-by-side Rails vs Rails+RageArch comparison
397
402
  - [`doc/DOCUMENTATION.md`](doc/DOCUMENTATION.md) — Detailed behaviour (use cases, deps, events, config)
398
403
  - [`doc/REFERENCE.md`](doc/REFERENCE.md) — Quick-lookup API reference (classes, methods, options)
399
404
 
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module RageArch
6
+ module Generators
7
+ class ControllerGenerator < ::Rails::Generators::NamedBase
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ argument :actions, type: :array, default: [], banner: "action action"
11
+
12
+ desc "Generate a thin RageArch controller with use cases for each action."
13
+ def create_controller
14
+ template "controller/controller.rb.tt", File.join("app/controllers", "#{plural_file_name}_controller.rb")
15
+ end
16
+
17
+ def create_use_cases
18
+ actions.each do |action|
19
+ @current_action = action
20
+ template "controller/action_use_case.rb.tt", File.join("app/use_cases", plural_file_name, "#{action}.rb")
21
+ end
22
+ end
23
+
24
+ def add_routes
25
+ return if actions.empty?
26
+ lines = actions.map { |a| " get \"#{plural_file_name}/#{a}\", to: \"#{plural_file_name}##{a}\"" }
27
+ route lines.join("\n")
28
+ end
29
+
30
+ private
31
+
32
+ def plural_file_name
33
+ file_name.pluralize
34
+ end
35
+
36
+ def controller_class_name
37
+ plural_file_name.camelize
38
+ end
39
+
40
+ def module_name
41
+ plural_file_name.camelize
42
+ end
43
+
44
+ def current_action
45
+ @current_action
46
+ end
47
+
48
+ def use_case_symbol(action)
49
+ "#{plural_file_name}_#{action}"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module RageArch
6
+ module Generators
7
+ class JobGenerator < ::Rails::Generators::NamedBase
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ argument :use_case_symbol, type: :string, required: false, default: nil, banner: "use_case_symbol"
11
+
12
+ desc "Generate an ActiveJob that runs a RageArch use case by symbol."
13
+ def create_job
14
+ template "job/job.rb.tt", File.join("app/jobs", "#{file_name}_job.rb")
15
+ end
16
+
17
+ private
18
+
19
+ def job_class_name
20
+ file_name.camelize
21
+ end
22
+
23
+ def inferred_symbol
24
+ use_case_symbol || file_name
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module RageArch
6
+ module Generators
7
+ class MailerGenerator < ::Rails::Generators::NamedBase
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ argument :actions, type: :array, default: [], banner: "action action"
11
+
12
+ desc "Generate a Rails mailer and a RageArch dep wrapper (auto-registered from app/deps/)."
13
+ def create_rails_mailer
14
+ args = [name] + actions
15
+ invoke "mailer", args
16
+ end
17
+
18
+ def create_dep
19
+ dep_dir = File.join("app/deps")
20
+ template "mailer/mailer_dep.rb.tt", File.join(dep_dir, "#{dep_file_name}.rb")
21
+ end
22
+
23
+ private
24
+
25
+ def mailer_class_name
26
+ name.camelize
27
+ end
28
+
29
+ def dep_class_name
30
+ "#{mailer_class_name}Dep"
31
+ end
32
+
33
+ def dep_file_name
34
+ "#{file_name}_dep"
35
+ end
36
+
37
+ def dep_symbol
38
+ file_name.underscore
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,110 @@
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 ResourceGenerator < ::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
+
14
+ desc "Generate a RageArch resource: model, migration, CRUD use cases, dep, API-style controller, and routes (no views)."
15
+ def create_all
16
+ create_model_and_migration
17
+ create_use_cases
18
+ create_dep
19
+ create_controller
20
+ add_route
21
+ end
22
+
23
+ private
24
+
25
+ def create_model_and_migration
26
+ return if options[:skip_model]
27
+ args = [name] + attributes.map(&:to_s)
28
+ invoke "active_record:model", args
29
+ end
30
+
31
+ def create_use_cases
32
+ dir = File.join("app/use_cases", plural_name)
33
+ empty_directory dir
34
+ template "scaffold/list.rb.tt", File.join(dir, "list.rb")
35
+ template "scaffold/show.rb.tt", File.join(dir, "show.rb")
36
+ template "scaffold/new.rb.tt", File.join(dir, "new.rb")
37
+ template "scaffold/create.rb.tt", File.join(dir, "create.rb")
38
+ template "scaffold/update.rb.tt", File.join(dir, "update.rb")
39
+ template "scaffold/destroy.rb.tt", File.join(dir, "destroy.rb")
40
+ end
41
+
42
+ def create_dep
43
+ dep_dir = File.join("app/deps", plural_name)
44
+ empty_directory dep_dir
45
+ template "scaffold/post_repo.rb.tt", File.join(dep_dir, "#{singular_name}_repo.rb")
46
+ end
47
+
48
+ def create_controller
49
+ template "scaffold/api_controller.rb.tt", File.join("app/controllers", "#{plural_name}_controller.rb")
50
+ end
51
+
52
+ def add_route
53
+ route "resources :#{plural_name}"
54
+ end
55
+
56
+ def plural_name
57
+ name.underscore.pluralize
58
+ end
59
+
60
+ def singular_name
61
+ name.underscore
62
+ end
63
+
64
+ def model_class_name
65
+ name.camelize
66
+ end
67
+
68
+ def module_name
69
+ plural_name.camelize
70
+ end
71
+
72
+ def repo_symbol
73
+ "#{singular_name}_repo"
74
+ end
75
+
76
+ def repo_class_name
77
+ "#{module_name}::#{singular_name.camelize}Repo"
78
+ end
79
+
80
+ def list_symbol
81
+ "#{plural_name}_list"
82
+ end
83
+
84
+ def show_symbol
85
+ "#{plural_name}_show"
86
+ end
87
+
88
+ def create_symbol
89
+ "#{plural_name}_create"
90
+ end
91
+
92
+ def update_symbol
93
+ "#{plural_name}_update"
94
+ end
95
+
96
+ def destroy_symbol
97
+ "#{plural_name}_destroy"
98
+ end
99
+
100
+ def new_symbol
101
+ "#{plural_name}_new"
102
+ end
103
+
104
+ def attribute_names_for_permit
105
+ return [] if attributes.blank?
106
+ attributes.map { |a| a.to_s.split(":").first.to_sym }
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= module_name %>
4
+ class <%= current_action.camelize %> < RageArch::UseCase::Base
5
+ # deps :example_repo
6
+
7
+ def call(params = {})
8
+ # TODO: implement use case logic
9
+ success(params)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= controller_class_name %>Controller < ApplicationController
4
+ <%- actions.each do |action| -%>
5
+ def <%= action %>
6
+ run :<%= use_case_symbol(action) %>,
7
+ success: ->(r) { render :<%= action %> },
8
+ failure: ->(r) { flash_errors(r); redirect_to root_path }
9
+ end
10
+
11
+ <%- end -%>
12
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= job_class_name %>Job < ApplicationJob
4
+ queue_as :default
5
+
6
+ def perform(**params)
7
+ result = RageArch::UseCase::Base.build(:<%= inferred_symbol %>).call(params)
8
+
9
+ unless result.success?
10
+ Rails.logger.error "[<%= job_class_name %>Job] Use case :<%= inferred_symbol %> failed: #{result.errors.inspect}"
11
+ end
12
+
13
+ result
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Dep for :<%= dep_symbol %>. Wraps <%= mailer_class_name %>.
4
+ # Auto-registered by convention from app/deps/
5
+ class <%= dep_class_name %>
6
+ <%- actions.each do |action| -%>
7
+ def <%= action %>(*args)
8
+ <%= mailer_class_name %>.<%=action %>(*args).deliver_later
9
+ end
10
+
11
+ <%- end -%>
12
+ <%- if actions.empty? -%>
13
+ # def send_email(*args)
14
+ # <%= mailer_class_name %>.send_email(*args).deliver_later
15
+ # end
16
+ <%- end -%>
17
+ end
@@ -5,7 +5,7 @@ module RageArch
5
5
  # Use cases: subclasses of RageArch::UseCase::Base → registered by inferred symbol.
6
6
  # Deps: classes under app/deps/ → registered by inferred symbol.
7
7
  # AR auto-resolution: deps ending in _store with no file in app/deps/ → resolves AR model.
8
- class AutoRegistrar
8
+ class AutoRegistry
9
9
  class << self
10
10
  def run
11
11
  register_use_cases
@@ -53,19 +53,31 @@ module RageArch
53
53
 
54
54
  sym = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(klass.name)).to_sym
55
55
  unless Container.registered?(sym)
56
- Container.register(sym, klass.new)
56
+ begin
57
+ Container.register(sym, klass.new)
58
+ rescue LoadError, StandardError => e
59
+ # Skip deps that fail to instantiate (e.g. missing gem dependencies).
60
+ # These are typically alternative implementations not currently active.
61
+ end
57
62
  end
58
63
  end
59
64
  end
60
65
 
61
66
  def resolve_store_deps
62
- # For each use case, check declared deps ending in _store
67
+ # For each use case, check declared deps ending in _store or _repo
63
68
  RageArch::UseCase::Base.registry.each_value do |klass|
64
69
  klass.declared_deps.each do |dep_sym|
65
70
  next if Container.registered?(dep_sym)
66
- next unless dep_sym.to_s.end_with?("_store")
67
71
 
68
- model_name = dep_sym.to_s.sub(/_store\z/, "").camelize
72
+ dep_str = dep_sym.to_s
73
+ suffix = if dep_str.end_with?("_store")
74
+ "_store"
75
+ elsif dep_str.end_with?("_repo")
76
+ "_repo"
77
+ end
78
+ next unless suffix
79
+
80
+ model_name = dep_str.sub(/#{suffix}\z/, "").camelize
69
81
  model_class = begin
70
82
  model_name.constantize
71
83
  rescue NameError
@@ -21,7 +21,7 @@ module RageArch
21
21
  end
22
22
 
23
23
  # Auto-register use cases and deps by convention.
24
- RageArch::AutoRegistrar.run
24
+ RageArch::AutoRegistry.run
25
25
  end
26
26
  end
27
27
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RageArch
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/rage_arch.rb CHANGED
@@ -8,7 +8,7 @@ require_relative "rage_arch/event_publisher"
8
8
  require_relative "rage_arch/use_case"
9
9
  require_relative "rage_arch/deps/active_record"
10
10
  require_relative "rage_arch/dep_scanner"
11
- require_relative "rage_arch/auto_registrar"
11
+ require_relative "rage_arch/auto_registry"
12
12
 
13
13
  module RageArch
14
14
  class << self
@@ -68,7 +68,7 @@ module RageArch
68
68
  klass.declared_deps.uniq.each do |dep_sym|
69
69
  unless Container.registered?(dep_sym)
70
70
  # 6b. AR model not found for _store dep
71
- if dep_sym.to_s.end_with?("_store")
71
+ if dep_sym.to_s.end_with?("_store") || dep_sym.to_s.end_with?("_repo")
72
72
  errors << " UseCase :#{uc_symbol} (#{klass}) declares dep :#{dep_sym} — not registered in container and no AR model found"
73
73
  else
74
74
  errors << " UseCase :#{uc_symbol} (#{klass}) declares dep :#{dep_sym} — not registered in container"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rage_arch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rage Corp
@@ -77,11 +77,19 @@ extra_rdoc_files: []
77
77
  files:
78
78
  - LICENSE
79
79
  - README.md
80
+ - lib/generators/rage_arch/controller_generator.rb
80
81
  - lib/generators/rage_arch/dep_generator.rb
81
82
  - lib/generators/rage_arch/dep_switch_generator.rb
82
83
  - lib/generators/rage_arch/install_generator.rb
84
+ - lib/generators/rage_arch/job_generator.rb
85
+ - lib/generators/rage_arch/mailer_generator.rb
86
+ - lib/generators/rage_arch/resource_generator.rb
83
87
  - lib/generators/rage_arch/scaffold_generator.rb
88
+ - lib/generators/rage_arch/templates/controller/action_use_case.rb.tt
89
+ - lib/generators/rage_arch/templates/controller/controller.rb.tt
84
90
  - lib/generators/rage_arch/templates/dep.rb.tt
91
+ - lib/generators/rage_arch/templates/job/job.rb.tt
92
+ - lib/generators/rage_arch/templates/mailer/mailer_dep.rb.tt
85
93
  - lib/generators/rage_arch/templates/rage_arch.rb.tt
86
94
  - lib/generators/rage_arch/templates/scaffold/api_controller.rb.tt
87
95
  - lib/generators/rage_arch/templates/scaffold/controller.rb.tt
@@ -95,7 +103,7 @@ files:
95
103
  - lib/generators/rage_arch/templates/use_case.rb.tt
96
104
  - lib/generators/rage_arch/use_case_generator.rb
97
105
  - lib/rage_arch.rb
98
- - lib/rage_arch/auto_registrar.rb
106
+ - lib/rage_arch/auto_registry.rb
99
107
  - lib/rage_arch/container.rb
100
108
  - lib/rage_arch/controller.rb
101
109
  - lib/rage_arch/dep.rb