railsforge 1.0.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 (73) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +528 -0
  4. data/bin/railsforge +8 -0
  5. data/lib/railsforge/analyzers/base_analyzer.rb +41 -0
  6. data/lib/railsforge/analyzers/controller_analyzer.rb +83 -0
  7. data/lib/railsforge/analyzers/database_analyzer.rb +55 -0
  8. data/lib/railsforge/analyzers/metrics_analyzer.rb +55 -0
  9. data/lib/railsforge/analyzers/model_analyzer.rb +74 -0
  10. data/lib/railsforge/analyzers/performance_analyzer.rb +161 -0
  11. data/lib/railsforge/analyzers/refactor_analyzer.rb +118 -0
  12. data/lib/railsforge/analyzers/security_analyzer.rb +169 -0
  13. data/lib/railsforge/analyzers/spec_analyzer.rb +58 -0
  14. data/lib/railsforge/api_generator.rb +397 -0
  15. data/lib/railsforge/audit.rb +289 -0
  16. data/lib/railsforge/cli.rb +671 -0
  17. data/lib/railsforge/config.rb +181 -0
  18. data/lib/railsforge/database_analyzer.rb +300 -0
  19. data/lib/railsforge/doctor.rb +250 -0
  20. data/lib/railsforge/feature_generator.rb +560 -0
  21. data/lib/railsforge/generator.rb +313 -0
  22. data/lib/railsforge/generators/base_generator.rb +70 -0
  23. data/lib/railsforge/generators/demo_generator.rb +307 -0
  24. data/lib/railsforge/generators/devops_generator.rb +287 -0
  25. data/lib/railsforge/generators/monitoring_generator.rb +134 -0
  26. data/lib/railsforge/generators/service_generator.rb +122 -0
  27. data/lib/railsforge/generators/stimulus_controller_generator.rb +129 -0
  28. data/lib/railsforge/generators/test_generator.rb +289 -0
  29. data/lib/railsforge/generators/view_component_generator.rb +169 -0
  30. data/lib/railsforge/graph.rb +270 -0
  31. data/lib/railsforge/loader.rb +56 -0
  32. data/lib/railsforge/mailer_generator.rb +191 -0
  33. data/lib/railsforge/plugins/plugin_loader.rb +60 -0
  34. data/lib/railsforge/plugins.rb +30 -0
  35. data/lib/railsforge/profiles/admin_app.yml +49 -0
  36. data/lib/railsforge/profiles/api_only.yml +47 -0
  37. data/lib/railsforge/profiles/blog.yml +47 -0
  38. data/lib/railsforge/profiles/standard.yml +44 -0
  39. data/lib/railsforge/profiles.rb +99 -0
  40. data/lib/railsforge/refactor_analyzer.rb +401 -0
  41. data/lib/railsforge/refactor_controller.rb +277 -0
  42. data/lib/railsforge/refactors/refactor_controller.rb +117 -0
  43. data/lib/railsforge/template_loader.rb +105 -0
  44. data/lib/railsforge/templates/v1/form/spec_template.rb +18 -0
  45. data/lib/railsforge/templates/v1/form/template.rb +28 -0
  46. data/lib/railsforge/templates/v1/job/spec_template.rb +17 -0
  47. data/lib/railsforge/templates/v1/job/template.rb +13 -0
  48. data/lib/railsforge/templates/v1/policy/spec_template.rb +41 -0
  49. data/lib/railsforge/templates/v1/policy/template.rb +57 -0
  50. data/lib/railsforge/templates/v1/presenter/spec_template.rb +12 -0
  51. data/lib/railsforge/templates/v1/presenter/template.rb +13 -0
  52. data/lib/railsforge/templates/v1/query/spec_template.rb +12 -0
  53. data/lib/railsforge/templates/v1/query/template.rb +16 -0
  54. data/lib/railsforge/templates/v1/serializer/spec_template.rb +13 -0
  55. data/lib/railsforge/templates/v1/serializer/template.rb +11 -0
  56. data/lib/railsforge/templates/v1/service/spec_template.rb +12 -0
  57. data/lib/railsforge/templates/v1/service/template.rb +25 -0
  58. data/lib/railsforge/templates/v1/stimulus_controller/template.rb +35 -0
  59. data/lib/railsforge/templates/v1/view_component/template.rb +24 -0
  60. data/lib/railsforge/templates/v2/job/template.rb +49 -0
  61. data/lib/railsforge/templates/v2/query/template.rb +66 -0
  62. data/lib/railsforge/templates/v2/service/spec_template.rb +33 -0
  63. data/lib/railsforge/templates/v2/service/template.rb +71 -0
  64. data/lib/railsforge/templates/v3/job/template.rb +72 -0
  65. data/lib/railsforge/templates/v3/query/spec_template.rb +54 -0
  66. data/lib/railsforge/templates/v3/query/template.rb +115 -0
  67. data/lib/railsforge/templates/v3/service/spec_template.rb +51 -0
  68. data/lib/railsforge/templates/v3/service/template.rb +84 -0
  69. data/lib/railsforge/version.rb +5 -0
  70. data/lib/railsforge/wizard.rb +265 -0
  71. data/lib/railsforge/wizard_tui.rb +286 -0
  72. data/lib/railsforge.rb +13 -0
  73. metadata +216 -0
@@ -0,0 +1,12 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe <%= name.camelize %>Query do
4
+ let(:scope) { <%= name.camelize %>.all }
5
+ subject { described_class.new(scope: scope) }
6
+
7
+ describe '#call' do
8
+ it 'returns scope' do
9
+ expect(subject.call).to eq(scope)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ # Query class for <%= name %>
2
+ # Encapsulates database queries
3
+ #
4
+ # Usage:
5
+ # result = <%= name.camelize %>Query.call
6
+ # result = <%= name.camelize %>Query.call(scope: User.active)
7
+ class <%= name.camelize %>Query
8
+ def initialize(scope: nil)
9
+ @scope = scope || <%= name.camelize %>.all
10
+ end
11
+
12
+ def call
13
+ # TODO: Implement query logic
14
+ @scope
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe <%= name.camelize %>Serializer do
4
+ let(:<%= name.underscore %>) { <%= name.camelize %>.new<% attributes.each do |attr| %>
5
+ <%= name.underscore %>.<%= attr %> = 'test'<% end %> }
6
+ subject { described_class.new(<%= name.underscore %>) }
7
+
8
+ describe 'serialization' do
9
+ it 'includes id' do
10
+ expect(subject.as_json[:id]).to eq(<%= name.underscore %>.id)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # Serializer class for <%= name %>
2
+ # Handles JSON serialization
3
+ #
4
+ # Usage:
5
+ # render json: <%= name.camelize %>Serializer.new(<%= name.underscore %>)
6
+ class <%= name.camelize %>Serializer < ApplicationSerializer
7
+ attributes :id<% attributes.each do |attr| %>
8
+ :<%= attr %><% end %>
9
+
10
+ # TODO: Add custom methods
11
+ end
@@ -0,0 +1,12 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe <%= name.camelize %>Service do
4
+ let(:params) { {} }
5
+ subject { described_class.new(params) }
6
+
7
+ describe '#call' do
8
+ it 'returns successful result' do
9
+ expect(subject.call).to be_truthy
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,25 @@
1
+ # Service class for <%= name %>
2
+ # Encapsulates business logic
3
+ #
4
+ # Usage:
5
+ # result = <%= name.camelize %>Service.call(params)
6
+ #
7
+ # @example Basic service
8
+ # class CreateUserService
9
+ # def initialize(user_params)
10
+ # @user_params = user_params
11
+ # end
12
+ #
13
+ # def call
14
+ # User.create!(@user_params)
15
+ # end
16
+ # end
17
+ class <%= name.camelize %>Service
18
+ def initialize(<%= name.underscore %>_params = {})
19
+ @params = <%= name.underscore %>_params
20
+ end
21
+
22
+ def call
23
+ # TODO: Implement service logic
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ // Stimulus controller: <%= class_name %>
2
+ import { Controller } from "@hotwired/stimulus"
3
+
4
+ export default class <%= class_name %> extends Controller {
5
+ // Define static targets
6
+ static targets = ["output", "input"]
7
+
8
+ // Connect lifecycle
9
+ connect() {
10
+ console.log("<%= class_name %> connected")
11
+ }
12
+
13
+ // Disconnect lifecycle
14
+ disconnect() {
15
+ console.log("<%= class_name %> disconnected")
16
+ }
17
+
18
+ // Example action
19
+ // Usage: data-action="<%= underscore_name %>#greet"
20
+ greet() {
21
+ console.log("Hello from <%= class_name %>!")
22
+ }
23
+
24
+ // Example: Handle input changes
25
+ // Usage: data-action="input-><%= underscore_name %>#handleInput"
26
+ handleInput(event) {
27
+ const value = event.target.value
28
+ console.log("Input value:", value)
29
+ }
30
+
31
+ // Example: Toggle visibility
32
+ toggle() {
33
+ this.outputTarget.classList.toggle("hidden")
34
+ }
35
+ }
@@ -0,0 +1,24 @@
1
+ # ViewComponent template for <%= class_name %>
2
+ class <%= class_name %> < ViewComponent::Base
3
+ # Define component properties
4
+ # renders_maybe :some_child
5
+
6
+ # Initialize with data
7
+ # @param title [String] Component title
8
+ def initialize(title: "", classes: "")
9
+ @title = title
10
+ @classes = classes
11
+ end
12
+
13
+ # Check if title is present
14
+ # @return [Boolean]
15
+ def title?
16
+ @title.present?
17
+ end
18
+
19
+ # CSS classes for the component
20
+ # @return [String]
21
+ def classes
22
+ "component #{@classes}"
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ # Job template v2
2
+ # Enhanced job with error handling and callbacks
3
+ class <%= class_name %> < ApplicationJob
4
+ # Configure queue
5
+ queue_as :default
6
+
7
+ # Retry configuration
8
+ retry_on StandardError, wait: :exponentially_longer, attempts: 5
9
+
10
+ # Discard configuration
11
+ discard_on ActiveJob::DeserializationError
12
+
13
+ # Perform the job
14
+ # @param args [Array] Job arguments
15
+ def perform(*args)
16
+ # TODO: Implement job logic
17
+ Rails.logger.info "Executing #{self.class.name}"
18
+
19
+ # Example:
20
+ # SomeService.call(*args)
21
+ end
22
+
23
+ # Called before job execution
24
+ # @return [void]
25
+ def before_perform
26
+ Rails.logger.info "Starting #{self.class.name}"
27
+ end
28
+
29
+ # Called after job execution
30
+ # @return [void]
31
+ def after_perform
32
+ Rails.logger.info "Completed #{self.class.name}"
33
+ end
34
+
35
+ # Called on job failure
36
+ # @param exception [StandardError] The exception that was raised
37
+ # @return [void]
38
+ def on_failure(exception)
39
+ Rails.logger.error "#{self.class.name} failed: #{exception.message}"
40
+ # Notify error tracking service (e.g., Sentry, Bugsnag)
41
+ end
42
+
43
+ # Called when job is retried
44
+ # @param exception [StandardError] The exception that caused the retry
45
+ # @return [void]
46
+ def on_retry(exception)
47
+ Rails.logger.warn "#{self.class.name} retrying: #{exception.message}"
48
+ end
49
+ end
@@ -0,0 +1,66 @@
1
+ # Query object template v2
2
+ # Enhanced query with chainable interface
3
+ class <%= class_name %>
4
+ attr_reader :relation
5
+
6
+ def initialize(relation = nil)
7
+ @relation = relation || default_relation
8
+ end
9
+
10
+ # Chainable scope methods
11
+ def filter_by(**conditions)
12
+ conditions.each do |attribute, value|
13
+ @relation = @relation.where(attribute => value)
14
+ end
15
+ self
16
+ end
17
+
18
+ def order_by(attribute, direction = :asc)
19
+ @relation = @relation.order(attribute => direction)
20
+ self
21
+ end
22
+
23
+ def limit(count)
24
+ @relation = @relation.limit(count)
25
+ self
26
+ end
27
+
28
+ def offset(count)
29
+ @relation = @relation.offset(count)
30
+ self
31
+ end
32
+
33
+ # Execute the query
34
+ def call
35
+ @relation
36
+ end
37
+
38
+ # Convenience methods
39
+ def first
40
+ @relation.first
41
+ end
42
+
43
+ def last
44
+ @relation.last
45
+ end
46
+
47
+ def all
48
+ @relation.to_a
49
+ end
50
+
51
+ def count
52
+ @relation.count
53
+ end
54
+
55
+ def exists?
56
+ @relation.exists?
57
+ end
58
+
59
+ private
60
+
61
+ def default_relation
62
+ # TODO: Replace with actual model
63
+ # Example: Model.where(active: true)
64
+ raise NotImplementedError, "Override #default_relation in subclass"
65
+ end
66
+ end
@@ -0,0 +1,33 @@
1
+ # RSpec tests for <%= class_name %>
2
+ # Enhanced v2 testing with better patterns
3
+ RSpec.describe <%= class_name %> do
4
+ let(:params) { {} }
5
+ let(:service) { described_class.new(params) }
6
+
7
+ describe "#call" do
8
+ context "with valid params" do
9
+ it "returns a successful result" do
10
+ result = service.call
11
+ expect(result.success?).to be true
12
+ expect(result.data).to be_a(Hash)
13
+ end
14
+ end
15
+
16
+ context "with invalid params" do
17
+ let(:params) { { invalid: true } }
18
+
19
+ it "returns a failure result" do
20
+ result = service.call
21
+ expect(result.failure?).to be true
22
+ expect(result.errors).to be_an(Array)
23
+ end
24
+ end
25
+ end
26
+
27
+ describe ".call" do
28
+ it "creates and calls the service" do
29
+ result = described_class.call({})
30
+ expect(result).to be_a(Result)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,71 @@
1
+ # Service class template v2
2
+ # Enhanced service with better practices
3
+ class <%= class_name %>
4
+ # Initialize with dependencies
5
+ # @param [Hash] params - Parameters for the service
6
+ def initialize(params = {})
7
+ @params = params
8
+ @errors = []
9
+ end
10
+
11
+ # Execute the service logic
12
+ # @return [Result] Result object with success/failure
13
+ def call
14
+ return failure(["Validation failed"]) unless validate?
15
+
16
+ success(execute_operation)
17
+ rescue StandardError => e
18
+ failure([e.message])
19
+ end
20
+
21
+ # Class-level call for convenience
22
+ # @param [Hash] params - Parameters for the service
23
+ # @return [Result] Result object
24
+ def self.call(params = {})
25
+ new(params).call
26
+ end
27
+
28
+ private
29
+
30
+ # Validate inputs
31
+ # @return [Boolean] True if valid
32
+ def validate?
33
+ true
34
+ end
35
+
36
+ # Execute the main operation
37
+ # @return [Object] Operation result
38
+ def execute_operation
39
+ # TODO: Implement service logic
40
+ {}
41
+ end
42
+
43
+ # Helper method for success result
44
+ # @param [Object] data - Result data
45
+ # @return [Result]
46
+ def success(data)
47
+ Result.new(success: true, data: data, errors: [])
48
+ end
49
+
50
+ # Helper method for failure result
51
+ # @param [Array<String>] errors - Error messages
52
+ # @return [Result]
53
+ def failure(errors)
54
+ Result.new(success: false, data: nil, errors: errors)
55
+ end
56
+ end
57
+
58
+ # Result class for service responses
59
+ class Result
60
+ attr_reader :success, :data, :errors
61
+
62
+ def initialize(success:, data:, errors:)
63
+ @success = success
64
+ @data = data
65
+ @errors = errors
66
+ end
67
+
68
+ def failure?
69
+ !success
70
+ end
71
+ end
@@ -0,0 +1,72 @@
1
+ # Job template v3
2
+ # Advanced job with retries, priority, and callbacks
3
+ class <%= class_name %>Job < ApplicationJob
4
+ # Queue configuration
5
+ queue_with_priority PRIORITIES[:default]
6
+
7
+ # Retry configuration
8
+ retry_on StandardError, wait: :exponential_backoff, attempts: 5
9
+
10
+ # Discard configuration for permanent failures
11
+ discard_on PermanentJobError do |job, error|
12
+ Rails.logger.error("Job #{job.job_id} permanently failed: #{error.message}")
13
+ end
14
+
15
+ # Callback hooks
16
+ around_perform :instrument_job
17
+ before_perform :log_start
18
+ after_perform :log_complete
19
+
20
+ # Perform the job
21
+ # @param [Hash] args - Job arguments
22
+ def perform(*args)
23
+ # TODO: Implement job logic
24
+ # Example:
25
+ # user = User.find(args[:user_id])
26
+ # user.send_welcome_email
27
+ Rails.logger.info("Executing #{self.class.name}")
28
+ end
29
+
30
+ # Schedule for later
31
+ # @param [Integer] delay - Delay in seconds
32
+ def self.perform_in(delay, *args)
33
+ set(wait: delay seconds).perform_later(*args)
34
+ end
35
+
36
+ # Schedule at specific time
37
+ # @param [DateTime] time - Scheduled time
38
+ def self.perform_at(time, *args)
39
+ set(wait_until: time).perform_later(*args)
40
+ end
41
+
42
+ # Unique job - prevent duplicates
43
+ def self.unique_by(*attributes)
44
+ sidekiq_options unique: true,
45
+ unique_args: ->(args) { args }
46
+ end
47
+
48
+ private
49
+
50
+ # Instrument the job for monitoring
51
+ def instrument_job
52
+ start_time = Time.now
53
+ Rails.logger.info("Starting #{self.class.name} at #{start_time}")
54
+
55
+ begin
56
+ yield
57
+ ensure
58
+ duration = Time.now - start_time
59
+ Rails.logger.info("Completed #{self.class.name} in #{duration.round(2)}s")
60
+ end
61
+ end
62
+
63
+ # Log job start
64
+ def log_start
65
+ Rails.logger.debug("Job #{job_id} starting")
66
+ end
67
+
68
+ # Log job completion
69
+ def log_complete
70
+ Rails.logger.debug("Job #{job_id} completed")
71
+ end
72
+ end
@@ -0,0 +1,54 @@
1
+ # Query spec template v3
2
+ require 'rails_helper'
3
+
4
+ RSpec.describe <%= class_name %> do
5
+ let(:query) { described_class.new(relation) }
6
+ let(:relation) { double('Relation') }
7
+
8
+ describe '#where' do
9
+ it 'adds conditions' do
10
+ expect(query.where(status: 'active')).to be_a(described_class)
11
+ end
12
+ end
13
+
14
+ describe '#includes' do
15
+ it 'adds includes' do
16
+ expect(query.includes(:user)).to be_a(described_class)
17
+ end
18
+ end
19
+
20
+ describe '#order' do
21
+ it 'adds order' do
22
+ expect(query.order(created_at: :desc)).to be_a(described_class)
23
+ end
24
+ end
25
+
26
+ describe '#paginate' do
27
+ it 'sets limit and offset' do
28
+ paginated = query.paginate(page: 2, per_page: 10)
29
+ expect(paginated).to be_a(described_class)
30
+ end
31
+ end
32
+
33
+ describe '#call' do
34
+ it 'executes the query' do
35
+ allow(relation).to receive_message_chain(:where, :includes, :order, :to_a).and_return([])
36
+ expect(query.call).to eq([])
37
+ end
38
+ end
39
+
40
+ describe '#count' do
41
+ it 'returns total count' do
42
+ allow(relation).to receive_message_chain(:where, :includes, :order, :count).and_return(10)
43
+ expect(query.count).to eq(10)
44
+ end
45
+ end
46
+
47
+ describe '#pagination_info' do
48
+ it 'returns pagination metadata' do
49
+ allow(relation).to receive_message_chain(:where, :includes, :order, :count).and_return(25)
50
+ info = query.pagination_info(page: 1, per_page: 10)
51
+ expect(info[:total_pages]).to eq(3)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,115 @@
1
+ # Query object template v3
2
+ # Advanced query with pagination and caching
3
+ class <%= class_name %>
4
+ # @return [ActiveRecord::Relation] relation
5
+ attr_reader :relation
6
+
7
+ def initialize(relation = default_relation)
8
+ @relation = relation
9
+ @conditions = []
10
+ @includes = []
11
+ @order = []
12
+ @limit_value = nil
13
+ @offset_value = nil
14
+ end
15
+
16
+ # Add conditions
17
+ # @param [Hash] conditions - Query conditions
18
+ def where(conditions)
19
+ @conditions << conditions
20
+ self
21
+ end
22
+
23
+ # Eager load associations
24
+ # @param [Array<Symbol>] associations - Associations to include
25
+ def includes(*associations)
26
+ @includes << associations
27
+ self
28
+ end
29
+
30
+ # Order results
31
+ # @param [Hash] order_spec - Order specification
32
+ def order(order_spec)
33
+ @order << order_spec
34
+ self
35
+ end
36
+
37
+ # Paginate results
38
+ # @param [Integer] page - Page number (1-indexed)
39
+ # @param [Integer] per_page - Items per page
40
+ def paginate(page: 1, per_page: 25)
41
+ @limit_value = per_page
42
+ @offset_value = (page - 1) * per_page
43
+ self
44
+ end
45
+
46
+ # Cache results
47
+ # @param [Integer] duration - Cache duration in seconds
48
+ def cached(duration: 5.minutes)
49
+ cache_key = generate_cache_key
50
+ Rails.cache.fetch(cache_key, expires_in: duration) do
51
+ to_a
52
+ end
53
+ end
54
+
55
+ # Execute query and return results
56
+ def call
57
+ build_relation.to_a
58
+ end
59
+
60
+ # Get total count
61
+ def count
62
+ build_relation.count
63
+ end
64
+
65
+ # Pagination metadata
66
+ def pagination_info(page: 1, per_page: 25)
67
+ total = count
68
+ {
69
+ current_page: page,
70
+ per_page: per_page,
71
+ total_count: total,
72
+ total_pages: (total.to_f / per_page).ceil,
73
+ has_next_page: (page * per_page) < total,
74
+ has_previous_page: page > 1
75
+ }
76
+ end
77
+
78
+ # Reset to default
79
+ def reset
80
+ @conditions = []
81
+ @includes = []
82
+ @order = []
83
+ @limit_value = nil
84
+ @offset_value = nil
85
+ self
86
+ end
87
+
88
+ private
89
+
90
+ def default_relation
91
+ raise NotImplementedError, "Override default_relation in subclass"
92
+ end
93
+
94
+ def build_relation
95
+ result = @relation
96
+
97
+ @conditions.each { |cond| result = result.where(cond) }
98
+ @includes.flatten.each { |inc| result = result.includes(inc) }
99
+ @order.each { |ord| result = result.order(ord) }
100
+ result = result.limit(@limit_value) if @limit_value
101
+ result = result.offset(@offset_value) if @offset_value
102
+
103
+ result
104
+ end
105
+
106
+ def generate_cache_key
107
+ {
108
+ conditions: @conditions,
109
+ includes: @includes,
110
+ order: @order,
111
+ limit: @limit_value,
112
+ offset: @offset_value
113
+ }
114
+ end
115
+ end
@@ -0,0 +1,51 @@
1
+ # Service spec template v3
2
+ require 'rails_helper'
3
+
4
+ RSpec.describe <%= class_name %> do
5
+ let(:service) { described_class.new(params) }
6
+ let(:params) { { name: 'Test' } }
7
+
8
+ describe '#call' do
9
+ context 'with valid params' do
10
+ it 'returns success' do
11
+ result = service.call
12
+ expect(result).to be_success
13
+ end
14
+
15
+ it 'contains data in result' do
16
+ result = service.call
17
+ expect(result.value!).to have_key(:data)
18
+ end
19
+
20
+ it 'includes metadata' do
21
+ result = service.call
22
+ expect(result.value!).to have_key(:metadata)
23
+ end
24
+ end
25
+
26
+ context 'with invalid params' do
27
+ let(:params) { {} }
28
+
29
+ it 'returns failure' do
30
+ result = service.call
31
+ expect(result).to be_failure
32
+ end
33
+ end
34
+ end
35
+
36
+ describe '.call' do
37
+ it 'works as class method' do
38
+ result = described_class.call(name: 'Test')
39
+ expect(result).to be_success
40
+ end
41
+ end
42
+
43
+ describe '#then' do
44
+ let(:next_service) { double('NextService', new: double(call: Success({}))) }
45
+
46
+ it 'chains to another service' do
47
+ result = service.then(next_service, extra: 'param')
48
+ expect(result).to be_a(Dry::Monads::Result)
49
+ end
50
+ end
51
+ end