rage_arch 0.2.0 → 0.2.2

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: 827bf0c1b2891fa1d67035363a4b820a71038e7f9b56b4167a29aa7524fa56dc
4
+ data.tar.gz: 57e7d566e7b2cbf2ff23e0ee9b1958f7a3e96e9dd9bdf1a2bea22e69c32a5d1c
5
5
  SHA512:
6
- metadata.gz: 25f7ef50af078a7b61663d26fd8586c5d512709b116ca206266c8d04e1d364c19caf5bf131c0886d0061fc7b1db0e7de3c47f2a827360f2a03b03d911ee5336d
7
- data.tar.gz: 06fea4288ebf3eeb5a6e1c3a2baf2895c22514389fbb169a5741474ef500baaaa52b129d0711ee645025fc713a6bd58e2436be05979445c64ec1831206af1437
6
+ metadata.gz: 95f6572c4ce9124edd8570e31be5ef8a0aaebfd63a3e35ce0c72662910fdfe2daf7141f82d3a5c14f1a7df451340b7b3ac59df0a9805b65e957810359fbdb3a0
7
+ data.tar.gz: 137357b33fe38902cce2b6de71a3905d22b5b698422eabad04f852fc056ee6da763346dbac1cd842df21677df74677530ad759bd6bc47e9c8b00dbff484d8324
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
@@ -23,7 +23,7 @@ module RageArch
23
23
 
24
24
  # Ensure all subclasses have their symbol inferred and registered
25
25
  RageArch::UseCase::Base.descendants.each do |klass|
26
- next if klass.name.nil? # anonymous classes
26
+ next if safe_class_name(klass).nil?
27
27
  klass.use_case_symbol # triggers inference + registration if not already set
28
28
  end
29
29
  end
@@ -38,34 +38,57 @@ module RageArch
38
38
  require file
39
39
  end
40
40
 
41
- # Register any class defined under app/deps/ that isn't already registered
41
+ # Register any class defined under app/deps/ that isn't already registered.
42
+ # Some gems (e.g. Faker) redefine .name with required keyword args,
43
+ # so we use safe_class_name to avoid ArgumentError on iteration.
42
44
  ObjectSpace.each_object(Class).select do |klass|
43
- next unless klass.name
44
- next if klass.name.start_with?("RageArch::")
45
+ klass_name = safe_class_name(klass)
46
+ next unless klass_name
47
+ next if klass_name.start_with?("RageArch::")
45
48
 
46
49
  # Check if this class was loaded from app/deps/
47
50
  source_file = begin
48
- Object.const_source_location(klass.name)&.first
49
- rescue
51
+ Object.const_source_location(klass_name)&.first
52
+ rescue StandardError
50
53
  nil
51
54
  end
52
55
  next unless source_file && source_file.start_with?(deps_dir.to_s)
53
56
 
54
- sym = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(klass.name)).to_sym
57
+ sym = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(klass_name)).to_sym
55
58
  unless Container.registered?(sym)
56
- Container.register(sym, klass.new)
59
+ begin
60
+ Container.register(sym, klass.new)
61
+ rescue LoadError, StandardError
62
+ # Skip deps that fail to instantiate (e.g. missing gem dependencies).
63
+ end
57
64
  end
58
65
  end
59
66
  end
60
67
 
68
+ # Safely retrieve a class name. Some gems (e.g. Faker::Travel::Airport)
69
+ # redefine .name with required keyword arguments, which raises ArgumentError
70
+ # when called without them. Returns nil if the name cannot be retrieved.
71
+ def safe_class_name(klass)
72
+ klass.name
73
+ rescue ArgumentError, NoMethodError
74
+ nil
75
+ end
76
+
61
77
  def resolve_store_deps
62
- # For each use case, check declared deps ending in _store
78
+ # For each use case, check declared deps ending in _store or _repo
63
79
  RageArch::UseCase::Base.registry.each_value do |klass|
64
80
  klass.declared_deps.each do |dep_sym|
65
81
  next if Container.registered?(dep_sym)
66
- next unless dep_sym.to_s.end_with?("_store")
67
82
 
68
- model_name = dep_sym.to_s.sub(/_store\z/, "").camelize
83
+ dep_str = dep_sym.to_s
84
+ suffix = if dep_str.end_with?("_store")
85
+ "_store"
86
+ elsif dep_str.end_with?("_repo")
87
+ "_repo"
88
+ end
89
+ next unless suffix
90
+
91
+ model_name = dep_str.sub(/#{suffix}\z/, "").camelize
69
92
  model_class = begin
70
93
  model_name.constantize
71
94
  rescue NameError
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ruby 4.0+ freezes array literals by default. ActionPack's MiddlewareStack#build
4
+ # calls `middlewares.freeze` which permanently freezes the internal array,
5
+ # preventing later initializers (e.g. Propshaft) from inserting middleware.
6
+ #
7
+ # This patch makes `build` freeze a dup instead of the original array.
8
+ if RUBY_VERSION >= '4.0'
9
+ require 'action_dispatch/middleware/stack'
10
+
11
+ ActionDispatch::MiddlewareStack.prepend(Module.new do
12
+ def build(app = nil, &block)
13
+ duped = middlewares.dup
14
+ duped.freeze.reverse.inject(app || block) do |a, e|
15
+ e.build(a)
16
+ end
17
+ end
18
+ end)
19
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "rage_arch"
4
4
  require_relative "controller"
5
+ require_relative "patches/middleware_stack"
5
6
 
6
7
  module RageArch
7
8
  class Railtie < ::Rails::Railtie
@@ -21,7 +22,7 @@ module RageArch
21
22
  end
22
23
 
23
24
  # Auto-register use cases and deps by convention.
24
- RageArch::AutoRegistrar.run
25
+ RageArch::AutoRegistry.run
25
26
  end
26
27
  end
27
28
  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.2"
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.2
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
@@ -103,6 +111,7 @@ files:
103
111
  - lib/rage_arch/deps/active_record.rb
104
112
  - lib/rage_arch/event_publisher.rb
105
113
  - lib/rage_arch/fake_event_publisher.rb
114
+ - lib/rage_arch/patches/middleware_stack.rb
106
115
  - lib/rage_arch/railtie.rb
107
116
  - lib/rage_arch/result.rb
108
117
  - lib/rage_arch/rspec_helpers.rb