### Access before hooks in Ruby Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/03-hooks.md The before_hooks method signature and usage example. ```ruby def self.before_hooks @before_hooks ||= [] end ``` ```ruby class MyInteractor include Interactor before :setup before { context.initialized = true } end MyInteractor.before_hooks # => [:setup, #] ``` -------------------------------- ### Controller Authentication Example Source: https://github.com/collectiveidea/interactor/blob/master/README.md A simplified controller implementation using an Interactor for authentication. ```ruby class SessionsController < ApplicationController def create result = AuthenticateUser.call(session_params) if result.success? session[:user_token] = result.token redirect_to result.user else flash.now[:message] = t(result.message) render :new end end private def session_params params.require(:session).permit(:email, :password) end end ``` -------------------------------- ### Run Interactor Example Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/01-interactor.md Executes an interactor and checks for success. ```ruby interactor = AuthenticateUser.new(email: "john@example.com", password: "secret") interactor.run puts interactor.context.success? # => true or false depending on execution ``` -------------------------------- ### Example Interactor Implementation Source: https://github.com/collectiveidea/interactor/blob/master/README.md A complete example of an interactor class for user authentication. ```ruby class AuthenticateUser include Interactor def call if user = User.authenticate(context.email, context.password) context.user = user context.token = user.secret_token else context.fail!(message: "authenticate_user.failure") end end end ``` -------------------------------- ### Implement before hooks in an Interactor Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/03-hooks.md Example demonstrating the use of symbol-based and block-based before hooks. ```ruby class CreateUser include Interactor before :validate_input before do context.created_at = Time.now end def call context.user = User.create(context.user_params) end private def validate_input context.fail!(error: "Email required") if context.email.blank? end end ``` -------------------------------- ### Initialize and Run Interactor Examples Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/01-interactor.md Demonstrates initializing an Interactor with a Hash or an existing context, followed by execution. ```ruby # Using a Hash interactor = AuthenticateUser.new(email: "john@example.com", password: "secret") interactor.run context = interactor.context # Using an existing context existing_context = Interactor::Context.new(email: "john@example.com") interactor = AuthenticateUser.new(existing_context) interactor.run ``` -------------------------------- ### Access after hooks in Ruby Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/03-hooks.md The after_hooks method signature and usage example. ```ruby def self.after_hooks @after_hooks ||= [] end ``` ```ruby class MyInteractor include Interactor after :cleanup after { context.finalized = true } end MyInteractor.after_hooks # => [#, :cleanup] # Note: reverse order ``` -------------------------------- ### Setup and Teardown Hooks Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/03-hooks.md Use before and after hooks to manage resource lifecycle, such as creating and cleaning up temporary files. ```ruby class InteractorWithSetupTeardown include Interactor before :setup after :teardown def call # Business logic with setup/teardown end private def setup context.temp_file = Tempfile.new end def teardown context.temp_file.unlink end end ``` -------------------------------- ### Call Method Implementation Example Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/01-interactor.md Shows how to implement business logic within an Interactor subclass. ```ruby class UpdateUser include Interactor def call user = User.find(context.user_id) user.update(context.user_params) context.updated_user = user end end ``` -------------------------------- ### Run! Interactor Example Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/01-interactor.md Executes an interactor and handles potential failures explicitly. ```ruby interactor = AuthenticateUser.new(email: "john@example.com", password: "secret") begin interactor.run! rescue Interactor::Failure => e puts "Failed: #{e.context.message}" end ``` -------------------------------- ### Access Interactor Context Example Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/01-interactor.md Shows how to retrieve data from the interactor's context. ```ruby interactor = AuthenticateUser.new(email: "john@example.com") puts interactor.context.email # => "john@example.com" ``` -------------------------------- ### Build Interactor::Context usage examples Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/02-context.md Demonstrates creating a context from a hash and preserving an existing context instance. ```ruby # Create from Hash context = Interactor::Context.build(email: "john@example.com", password: "secret") # => # # Preserve existing Context existing = Interactor::Context.new(user_id: 42) context = Interactor::Context.build(existing) context.object_id == existing.object_id # => true ``` -------------------------------- ### Define an Organizer Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/04-organizer.md Basic setup for an organizer class using the Interactor::Organizer module. ```ruby class PlaceOrder include Interactor::Organizer organize ValidateOrder, CreateOrderRecord, ProcessPayment, SendConfirmation end result = PlaceOrder.call( order_params: {...}, payment_method: "credit_card" ) ``` -------------------------------- ### Implement after hooks in an Interactor Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/03-hooks.md Example demonstrating the use of symbol-based and block-based after hooks, which execute in LIFO order. ```ruby class UpdateUser include Interactor after :send_confirmation_email after do context.audit_logged = true end def call context.user = User.find(context.user_id) context.user.update(context.user_params) end private def send_confirmation_email # This runs second (after hooks are LIFO) UserMailer.updated(context.user).deliver_later end end ``` -------------------------------- ### Implement Rollback Mechanism Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/04-organizer.md Example showing how to define rollback logic in individual interactors and trigger it via an organizer. ```ruby class CreateOrder include Interactor def call order = Order.create(context.order_params) if order.persisted? context.order = order else context.fail! end end def rollback context.order.destroy if context.order end end class ChargeCard include Interactor def call charge = process_payment(context.card_token, context.amount) if charge.successful? context.charge_id = charge.id else context.fail!(error: "Payment failed") # This triggers rollback end end def rollback # Refund the charge if it was processed refund_charge(context.charge_id) if context.charge_id end end class PlaceOrder include Interactor::Organizer organize CreateOrder, ChargeCard end # If ChargeCard fails: # 1. CreateOrder.rollback is called (destroys the order) # 2. ChargeCard is NOT rolled back (it failed, so has nothing to undo) # 3. SendConfirmation is never called result = PlaceOrder.call(order_params: {...}, card_token: "invalid") result.failure? # => true # Order is destroyed by rollback ``` -------------------------------- ### Rollback Method Implementation Example Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/01-interactor.md Shows how to implement a rollback mechanism to undo changes made in call. ```ruby class CreateOrder include Interactor def call order = Order.create(context.order_params) context.order = order if order.persisted? end def rollback context.order.destroy if context.order end end ``` -------------------------------- ### Implement around hooks in an Interactor Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/03-hooks.md Example showing how to use both symbol-based and block-based around hooks. ```ruby class ProcessPayment include Interactor around :track_timing around do |interactor| puts "Starting payment processing" interactor.call puts "Completed payment processing" end def call context.charge = charge_card end private def track_timing(interactor) context.start_time = Time.now interactor.call context.duration = Time.now - context.start_time end end ``` -------------------------------- ### Access around hooks in Ruby Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/03-hooks.md The around_hooks method signature and usage example. ```ruby def self.around_hooks @around_hooks ||= [] end ``` ```ruby class MyInteractor include Interactor around :measure_time around { |i| i.call } end MyInteractor.around_hooks # => [:measure_time, #] ``` -------------------------------- ### Dynamic Attribute Access in Context Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/02-context.md Shows how to set, get, check existence, and access attributes using OpenStruct-inherited methods and hash-like syntax. ```ruby context = Interactor::Context.new # Setting attributes context.user = User.new context.message = "Success" context.count = 42 # Getting attributes context.user # => # context.message # => "Success" context.count # => 42 # Check for attribute existence context.respond_to?(:user) # => true context.respond_to?(:nonexistent) # => false # Access via hash-like syntax (inherited from OpenStruct) context[:user] # => # context["user"] # => # ``` -------------------------------- ### Logging and Instrumentation Hook Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/03-hooks.md Use around hooks to log the start and completion of an interactor. ```ruby class LoggingInteractor include Interactor around do |interactor| Rails.logger.info("Starting #{self.class.name}") interactor.call Rails.logger.info("Completed #{self.class.name}") end def call # Business logic end end ``` -------------------------------- ### Add Interactor to Gemfile Source: https://github.com/collectiveidea/interactor/blob/master/README.md Include this line in your Gemfile to install the Interactor gem. ```ruby gem "interactor", "~> 3.0" ``` -------------------------------- ### Logging Pattern Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/09-quick-reference.md Uses an around hook to log the start and completion of an interactor. ```ruby class LoggedAction include Interactor around do |interactor| Rails.logger.info("Starting") interactor.call Rails.logger.info("Completed") end def call # Business logic end end ``` -------------------------------- ### Interactor Directory Structure Source: https://github.com/collectiveidea/interactor/blob/master/README.md Example of organizing business logic interactors within the Rails application directory structure. ```text ▾ app/ ▸ controllers/ ▸ helpers/ ▾ interactors/ authenticate_user.rb cancel_account.rb publish_post.rb register_user.rb remove_post.rb ▸ mailers/ ▸ models/ ▸ views/ ``` -------------------------------- ### Implement Nested Organizers Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/09-quick-reference.md Demonstrates how to nest organizers to create hierarchical workflows. ```ruby class OuterOrganizer include Interactor::Organizer organize InnerOrganizer, FinalStep end class InnerOrganizer include Interactor::Organizer organize Step1, Step2 end # Execution: Step1 -> Step2 -> FinalStep result = OuterOrganizer.call(input: "value") ``` -------------------------------- ### Hook Execution Sequence Source: https://github.com/collectiveidea/interactor/blob/master/README.md Demonstration of the order in which hooks are invoked. ```ruby around do |interactor| puts "around before 1" interactor.call puts "around after 1" end around do |interactor| puts "around before 2" interactor.call puts "around after 2" end before do puts "before 1" end before do puts "before 2" end after do puts "after 1" end after do puts "after 2" end ``` ```text around before 1 around before 2 before 1 before 2 after 2 after 1 around after 2 around after 1 ``` -------------------------------- ### Testing Interactors Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/09-quick-reference.md Example RSpec tests for verifying success and failure states of an interactor. ```ruby describe MyInteractor do it "succeeds with valid input" do result = MyInteractor.call(input: "valid") expect(result).to be_a_success end it "fails with invalid input" do result = MyInteractor.call(input: "") expect(result).to be_a_failure end end ``` -------------------------------- ### Handle Organizer Errors Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/04-organizer.md Demonstrates how to handle Interactor::Failure when using .call! versus .call. ```ruby class PlaceOrder include Interactor::Organizer organize ValidateOrder, CreateOrder, ChargeCard, SendConfirmation end # When calling via Interactor.call (recommended) result = PlaceOrder.call(order_params: {...}) if result.success? puts "Order placed: #{result.order.id}" else puts "Order failed: #{result.error}" end # When calling via Interactor.call! (propagates Failure) begin result = PlaceOrder.call!(order_params: {...}) rescue Interactor::Failure => e puts "Order failed: #{e.context.error}" # Rollback has already happened at this point end ``` -------------------------------- ### Collect Metrics for Organizer Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/04-organizer.md Wrap the organizer execution in a timing block to track performance metrics. ```ruby class CreateUserWithMetrics include Interactor::Organizer organize ValidateUser, CreateUserRecord, SendWelcomeEmail around do |organizer| StatsD.time('create_user') do organizer.call end end end ``` -------------------------------- ### Anti-pattern: Tight Coupling in Interactor Source: https://github.com/collectiveidea/interactor/blob/master/README.md An example of an interactor that violates separation of concerns by handling model-specific logic directly. ```ruby class AuthenticateUser include Interactor def call user = User.where(email: context.email).first # Yuck! if user && BCrypt::Password.new(user.password_digest) == context.password context.user = user else context.fail!(message: "authenticate_user.failure") end end end ``` -------------------------------- ### Implement Service Facade Pattern Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/08-patterns-and-best-practices.md Wraps organizers in service classes to provide a clean interface for external callers. ```ruby class OrderService def place_order(user_id, order_params) PlaceOrder.call( user_id: user_id, order_params: order_params ) end def cancel_order(order_id) CancelOrder.call(order_id: order_id) end end # Usage service = OrderService.new result = service.place_order(user_id, order_params) ``` -------------------------------- ### Display Project Structure Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/COMPLETION_CHECKLIST.md Visual representation of the documentation directory structure. ```text /workspace/home/output/ ├── 00-index.md (Navigation and overview) ├── 01-interactor.md (Core module) ├── 02-context.md (Context class) ├── 03-hooks.md (Hooks system) ├── 04-organizer.md (Organizer module) ├── 05-failure.md (Failure exception) ├── 06-types-and-interfaces.md (Type system) ├── 07-usage-examples.md (Practical examples) ├── 08-patterns-and-best-practices.md (Best practices) ├── 09-quick-reference.md (Quick reference) ├── README.txt (About this documentation) └── COMPLETION_CHECKLIST.md (This file) ``` -------------------------------- ### Compare Manual and Declarative Orchestration Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/04-organizer.md Contrasts manual interactor invocation with the declarative approach provided by organizers. ```ruby # Manual orchestration (not recommended) context = Interactor::Context.new(order_params: {...}, card_token: "...") begin ValidateOrder.call!(context) CreateOrderRecord.call!(context) ProcessPayment.call!(context) SendConfirmation.call!(context) rescue Interactor::Failure # Rollback manually or let context track it context.rollback! end ``` ```ruby # Declarative orchestration (recommended) class PlaceOrder include Interactor::Organizer organize ValidateOrder, CreateOrderRecord, ProcessPayment, SendConfirmation end result = PlaceOrder.call(order_params: {...}, card_token: "...") ``` -------------------------------- ### Demonstrate Interactor Hook Execution Order Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/03-hooks.md Shows the nested execution sequence of around, before, and after hooks in an Interactor. ```ruby class OrderProcessor include Interactor around do |interactor| puts "Around 1 - before" interactor.call puts "Around 1 - after" end around do |interactor| puts "Around 2 - before" interactor.call puts "Around 2 - after" end before do puts "Before 1" end before do puts "Before 2" end after do puts "After 1" end after do puts "After 2" end def call puts "Call" end end OrderProcessor.call # Output: # Around 1 - before # Around 2 - before # Before 1 # Before 2 # Call # After 2 # After 1 # Around 2 - after # Around 1 - after ``` -------------------------------- ### Handle Exceptions with Try-Catch Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/07-usage-examples.md Wrap external service calls in begin-rescue blocks to catch specific errors and trigger context failure. ```ruby class IntegrateWithExternalAPI include Interactor def call begin response = ExternalAPI.fetch_data(context.query) context.data = response.parsed_body rescue ExternalAPI::TimeoutError context.fail!( error: "API timeout", retry_later: true ) rescue ExternalAPI::NotFoundError context.fail!( error: "Resource not found", retry_later: false ) rescue StandardError => e context.fail!( error: e.message, error_class: e.class.name ) end end end # Usage result = IntegrateWithExternalAPI.call(query: "search term") if result.failure? if result.retry_later # Schedule retry else # Show error to user end end ``` -------------------------------- ### Passing Context Through Organizers Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/02-context.md Demonstrates how an organizer passes a shared context object through a sequence of interactors, allowing each to read or modify it. ```ruby class PlaceOrder include Interactor::Organizer organize ValidateOrder, CreateOrderRecord, ProcessPayment, SendConfirmation end result = PlaceOrder.call( order_params: {...}, payment_method: "credit_card" ) # Each interactor receives and can modify the same context result.order # Set by CreateOrderRecord result.charge_id # Set by ProcessPayment result.confirmation_sent # Set by SendConfirmation ``` -------------------------------- ### Query Context Success and Failure States Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/06-types-and-interfaces.md Use success? and failure? methods to check the current state of the context. ```ruby context = Interactor::Context.new # Success state (default) context.success? # => true context.failure? # => false # After failure context.fail! rescue nil context.success? # => false context.failure? # => true ``` -------------------------------- ### Managing Failures in Organizers Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/05-failure.md Demonstrates how organizers propagate failures from organized interactors. ```ruby class PlaceOrder include Interactor::Organizer organize CreateOrder, ProcessPayment, SendConfirmation end # Using .call on the organizer catches the Failure result = PlaceOrder.call(order_params: {...}) if result.failure? puts "Order failed: #{result.error}" # Rollback has already occurred end # Using .call! on the organizer propagates the Failure begin result = PlaceOrder.call!(order_params: {...}) rescue Interactor::Failure => e puts "Order failed: #{e.context.error}" end ``` -------------------------------- ### Organizer Method Signatures Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/04-organizer.md Internal implementation signatures for the organizer class methods. ```ruby def self.organize(*interactors) @organized = interactors.flatten end ``` ```ruby def self.organized @organized ||= [] end ``` -------------------------------- ### Build Interactor Context from Hash Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/06-types-and-interfaces.md Initialize a context from a hash using Interactor::Context.build or Interactor::Context.new. ```ruby hash = { name: "John", email: "john@example.com" } context = Interactor::Context.build(hash) # Equivalent to: context = Interactor::Context.new(hash) ``` -------------------------------- ### Define a Multi-Step Workflow with Organizer Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/09-quick-reference.md Uses Interactor::Organizer to chain multiple interactors and wraps execution in a database transaction. ```ruby class PlaceOrder include Interactor::Organizer organize( ValidateOrder, CreateOrder, ProcessPayment, SendConfirmation ) around do |organizer| ActiveRecord::Base.transaction { organizer.call } end end result = PlaceOrder.call(order_params: {...}) if result.success? puts "Order: #{result.order.id}" else puts "Error: #{result.error}" end ``` -------------------------------- ### Handling External Service Failures Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/05-failure.md Shows how to map external service responses to interactor failures. ```ruby class ChargeCard include Interactor def call response = payment_gateway.charge( amount: context.amount, card_token: context.card_token ) if response.success? context.charge_id = response.charge_id else context.fail!( error: response.error_message, error_code: response.error_code, retry_possible: response.retry_possible? ) end end end result = ChargeCard.call(amount: 100, card_token: "tok_...") if result.failure? if result.retry_possible? # Retry later else # Show error to user end end ``` -------------------------------- ### Handling Failures with .call Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/05-failure.md Recommended pattern using .call to return the context instead of raising exceptions. ```ruby class AuthenticateUser include Interactor def call user = User.find_by(email: context.email) if user && user.authenticate(context.password) context.user = user else context.fail!(error: "Invalid credentials") end end end # Using .call (recommended) result = AuthenticateUser.call(email: "john@example.com", password: "wrong") # No exception is raised; check result instead if result.success? puts "Authenticated" else puts "Error: #{result.error}" end ``` -------------------------------- ### Interactor Calling Patterns Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/09-quick-reference.md Demonstrates different ways to execute an interactor, including handling failures and manual instance execution. ```ruby # Use .call for normal operation (catches Failure) result = MyInteractor.call(input: "value") # Use .call! to propagate Failure begin result = MyInteractor.call!(input: "value") rescue Interactor::Failure => e puts e.context.error end # Call with instance interactor = MyInteractor.new(input: "value") interactor.run result = interactor.context ``` -------------------------------- ### Handle Interactor::Failure Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/06-types-and-interfaces.md Shows how to catch failures raised by context.fail! and how to manually instantiate the failure exception. ```ruby # Raised by context.fail! begin context.fail!(error: "Validation failed") rescue Interactor::Failure => e failed_context = e.context error_message = e.context.error end # Direct instantiation (unusual) context = Interactor::Context.new(error: "Failed") failure = Interactor::Failure.new(context) raise failure ``` -------------------------------- ### Create a Service Facade Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/09-quick-reference.md Wraps interactor calls within a service class to provide a clean interface for external components. ```ruby class UserService def authenticate(email, password) AuthenticateUser.call(email: email, password: password) end def create(user_params) CreateUser.call(user_params: user_params) end end service = UserService.new result = service.authenticate("john@example.com", "password") ``` -------------------------------- ### Manage Cross-Cutting Concerns with Hooks Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/08-patterns-and-best-practices.md Demonstrates the correct use of an around hook for database transactions, keeping business logic separate from infrastructure concerns. ```ruby class ProcessOrder include Interactor around :wrap_in_transaction def call # Business logic here order = create_order context.order = order end private def wrap_in_transaction(interactor) ActiveRecord::Base.transaction { interactor.call } end end ``` ```ruby # Bad: Business logic in hook class ProcessOrder include Interactor before do # This is business logic, not a cross-cutting concern context.discount = calculate_discount(context.order_total) end def call # ... end end ``` -------------------------------- ### Initialize an Interactor Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/01-interactor.md Defines the constructor signature for creating an Interactor instance. ```ruby def initialize(context = {}) @context = Context.build(context) end ``` -------------------------------- ### Implement Result Object Pattern Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/06-types-and-interfaces.md Shows how to use context failure and success states to return results from an interactor. ```ruby class CreateUser include Interactor def call user = User.create(context.user_params) if user.persisted? context.user = user else context.fail!(errors: user.errors) end end end result = CreateUser.call(user_params: {...}) result.user # or result.errors depending on success ``` -------------------------------- ### Pattern Matching and Destructuring with Context Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/02-context.md Demonstrates using the 'in' keyword for pattern matching and the '=>' operator for destructuring context results. ```ruby result = AuthenticateUser.call(email: "john@example.com", password: "secret") # Pattern matching case result in success: true, user: puts "Authenticated: #{user}" in failure: true, error: puts "Failed: #{error}" end # Destructuring result => { user:, token: } ``` -------------------------------- ### Define and use a before hook Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/07-usage-examples.md Use the before hook to execute logic before the call method, such as authorization checks. ```ruby class UpdateUserProfile include Interactor before :authorize_user def call user = User.find(context.user_id) user.update(context.user_params) context.user = user end private def authorize_user unless context.current_user.id == context.user_id context.fail!(error: "Unauthorized") end end end # Usage result = UpdateUserProfile.call( current_user: current_user, user_id: 123, user_params: { name: "New Name" } ) ``` -------------------------------- ### Defining Before Hooks Source: https://github.com/collectiveidea/interactor/blob/master/README.md Prepare the context before the interactor runs using blocks or method symbols. ```ruby before do context.emails_sent = 0 end ``` ```ruby before :zero_emails_sent def zero_emails_sent context.emails_sent = 0 end ``` -------------------------------- ### Define .before hook signature Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/03-hooks.md Internal implementation signature for the .before class method. ```ruby def self.before(*hooks, &block) hooks << block if block hooks.each { |hook| before_hooks.push(hook) } end ``` -------------------------------- ### Organize Interactors into Workflows Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/08-patterns-and-best-practices.md Groups related interactors into a single organizer to represent a cohesive business process. ```ruby class RegisterUser include Interactor::Organizer organize( ValidateUserData, CreateUserRecord, SendConfirmationEmail, LogUserSignup ) end ``` ```ruby # Bad: No clear workflow class MixedOperations include Interactor::Organizer organize( CreateUser, SendEmail, UpdateAnalytics, ProcessPayment, GenerateReport ) end ``` -------------------------------- ### Build Interactor::Context method Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/02-context.md Defines the logic for initializing a new context or returning an existing one. ```ruby def self.build(context = {}) if self === context context else new(context) end end ``` -------------------------------- ### Define Organizer with .organize Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/04-organizer.md Declares the sequence of interactors to be executed. Supports individual arguments, arrays, or nested arrays. ```ruby class PlaceOrder include Interactor::Organizer organize ValidateOrder, CreateOrderRecord, ProcessPayment, SendConfirmation end # Equivalent using an array class PlaceOrderAlt include Interactor::Organizer organize [ValidateOrder, CreateOrderRecord, ProcessPayment, SendConfirmation] end # Equivalent with nested arrays (both work identically) class PlaceOrderWithArray include Interactor::Organizer organize ValidateOrder, [CreateOrderRecord, ProcessPayment], SendConfirmation end ``` -------------------------------- ### Define hooks on an Organizer Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/04-organizer.md Use before, after, and around hooks to manage state or wrap execution in transactions within an organizer. ```ruby class PlaceOrder include Interactor::Organizer organize CreateOrder, ChargeCard, SendConfirmation before do context.start_time = Time.now end after do context.total_time = Time.now - context.start_time end around do |organizer| ActiveRecord::Base.transaction do organizer.call end end end ``` -------------------------------- ### .organized Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/04-organizer.md Retrieves the list of interactor classes currently configured for the organizer. ```APIDOC ## .organized ### Description Returns the array of declared interactor classes that will be invoked by the organizer. ### Signature `def self.organized` ### Returns - **Array** - An array of Interactor classes in the order they will be executed. ### Example ```ruby PlaceOrder.organized # => [ValidateOrder, CreateOrderRecord, ProcessPayment] ``` ``` -------------------------------- ### Execute an Organizer in a Controller Source: https://github.com/collectiveidea/interactor/blob/master/README.md Invoke an organizer using the call method and handle the result success or failure. ```ruby class OrdersController < ApplicationController def create result = PlaceOrder.call(order_params: order_params) if result.success? redirect_to result.order else @order = result.order render :new end end private def order_params params.require(:order).permit! end end ``` -------------------------------- ### Test Success and Failure Paths Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/08-patterns-and-best-practices.md Ensure comprehensive coverage by testing both successful and failed execution paths. ```ruby describe AuthenticateUser do context "with valid credentials" do it "succeeds" do allow(User).to receive(:authenticate).and_return(user) result = AuthenticateUser.call(email: "john@example.com", password: "secret") expect(result).to be_a_success end end context "with invalid credentials" do it "fails" do allow(User).to receive(:authenticate).and_return(nil) result = AuthenticateUser.call(email: "john@example.com", password: "wrong") expect(result).to be_a_failure end end end ``` -------------------------------- ### Handle Before Hook Failure Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/03-hooks.md Demonstrates that calling context.fail! in a before hook halts all subsequent execution. ```ruby class FailingBeforeHook include Interactor before { context.fail!(error: "Setup failed") } before { puts "This won't execute" } after { puts "This won't execute" } def call puts "This won't execute" end end result = FailingBeforeHook.call result.failure? # => true ``` -------------------------------- ### Coordinate multi-step workflows with Organizers Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/07-usage-examples.md Groups multiple interactors into a single workflow using Interactor::Organizer. ```ruby class PlaceOrder include Interactor::Organizer organize( ValidateOrder, CreateOrder, ProcessPayment, SendConfirmation ) end # Usage result = PlaceOrder.call( order_params: { items: [ { product_id: 1, quantity: 2 }, { product_id: 3, quantity: 1 } ] }, payment_method: "credit_card", card_token: "tok_visa" ) if result.success? puts "Order placed: #{result.order.id}" puts "Confirmation sent to: #{result.confirmation_email}" else puts "Order failed: #{result.error}" end ``` -------------------------------- ### Handle Nil Attributes in Context Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/06-types-and-interfaces.md Accessing undefined attributes returns nil, while explicitly set nil values are tracked by respond_to?. ```ruby context = Interactor::Context.new context.undefined_attr # => nil context.respond_to?(:undefined_attr) # => false # Setting to nil explicitly context.value = nil context.value # => nil context.respond_to?(:value) # => true ``` -------------------------------- ### Handling Business Logic Failures Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/05-failure.md Illustrates failing an interactor based on domain-specific business rules. ```ruby class CreateOrder include Interactor def call if out_of_stock? context.fail!( error: "Item out of stock", available_quantity: 0 ) else context.order = create_order_record end end end result = CreateOrder.call(item_id: 123, quantity: 5) if result.failure? puts "Cannot create order: #{result.error}" puts "Available: #{result.available_quantity}" end ``` -------------------------------- ### Retrieve Organized Interactors with .organized Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/04-organizer.md Returns the list of interactors currently configured for the organizer in their execution order. ```ruby class PlaceOrder include Interactor::Organizer organize ValidateOrder, CreateOrderRecord, ProcessPayment end PlaceOrder.organized # => [ValidateOrder, CreateOrderRecord, ProcessPayment] ``` -------------------------------- ### Dynamic Attribute Handling in Interactor::Context Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/06-types-and-interfaces.md Shows how Interactor::Context allows arbitrary attribute assignment and retrieval due to its inheritance from OpenStruct. ```ruby context = Interactor::Context.new # Any attribute can be set context.user = @user_object context.count = 42 context.valid = true context.items = [] context.metadata = { key: "value" } # Any attribute can be read context.user # Returns the user object context.count # Returns 42 context.nonexistent # Returns nil (no error) # Type introspection context.respond_to?(:user) # => true context.respond_to?(:nonexistent) # => false context.to_h # Returns hash of all attributes ``` -------------------------------- ### success? Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/02-context.md Checks if the context represents a successful invocation. ```APIDOC ## success? ### Description Returns true if the context has not been failed, false otherwise. Contexts are successful by default. ### Signature `def success?` ### Returns - **Boolean** - true if successful, false if failed. ``` -------------------------------- ### Handling Failure Propagation Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/02-context.md Shows how to trigger a failure within an interactor and the difference between using .call and .call! for handling failure states. ```ruby class ValidateUser include Interactor def call if context.email.blank? context.fail!(error: "Email is required") end end end # Using .call (catches Failure) result = ValidateUser.call(email: "") result.failure? # => true result.error # => "Email is required" # Using .call! (propagates Failure) begin ValidateUser.call!(email: "") rescue Interactor::Failure => e puts "Failed: #{e.context.error}" end ``` -------------------------------- ### Context Attribute Naming Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/08-patterns-and-best-practices.md Use descriptive, concise names for context attributes while avoiding unnecessary articles or excessive verbosity. ```ruby context.user context.order_id context.payment_token context.validation_errors ``` ```ruby context.u # Too abbreviated context.the_user # Articles unnecessary context.error_messages_for_validation # Too verbose ``` -------------------------------- ### Define and execute nested organizers in Ruby Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/07-usage-examples.md Defines a main organizer that delegates tasks to other interactors and nested organizers, including rollback logic for data consistency. ```ruby class ImportProductsFromFile include Interactor::Organizer organize( ValidateFile, ParseFile, ProcessProducts, GenerateReport ) end class ValidateFile include Interactor def call unless File.exist?(context.file_path) context.fail!(error: "File not found") end end end class ParseFile include Interactor def call context.products = CSV.read(context.file_path, headers: true) .map(&:to_h) end end class ProcessProducts include Interactor::Organizer organize( CreateProductBatch, ValidateProductData, SaveToDatabase ) end class CreateProductBatch include Interactor def call context.batch = ProductBatch.create! end def rollback context.batch.destroy end end class ValidateProductData include Interactor def call invalid_products = context.products.reject { |p| valid?(p) } if invalid_products.any? context.fail!( error: "Invalid products found", invalid_count: invalid_products.count ) end end private def valid?(product) product["name"].present? && product["price"].to_f > 0 end end class SaveToDatabase include Interactor def call context.products.each do |product_data| Product.create!( batch_id: context.batch.id, **product_data ) end end def rollback context.batch.products.delete_all end end class GenerateReport include Interactor def call context.report = { batch_id: context.batch.id, products_imported: context.products.count, timestamp: Time.current } end end # Usage result = ImportProductsFromFile.call(file_path: "products.csv") if result.success? puts "Imported #{result.products.count} products" puts "Report: #{result.report}" else puts "Import failed: #{result.error}" end ``` -------------------------------- ### Define and use an after hook Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/07-usage-examples.md Use the after hook to execute logic after the call method, such as clearing caches. ```ruby class DeletePost include Interactor after :clear_cache def call post = Post.find(context.post_id) context.post_deleted = post.destroy end private def clear_cache Rails.cache.delete("posts") end end # Usage result = DeletePost.call(post_id: 42) puts result.post_deleted # => true ``` -------------------------------- ### Interactor::Context.build(context = {}) Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/02-context.md Builds a new Interactor::Context instance from a hash or returns the existing context if one is already provided. ```APIDOC ## .build(context = {}) ### Description Builds an `Interactor::Context` or preserves an existing one. This method ensures that the returned object is an instance of `Interactor::Context`. ### Parameters - **context** (Hash or Interactor::Context) - Optional - A hash of key/value pairs or an existing `Interactor::Context` object. ### Returns - **Interactor::Context** - Either the input context (if already a Context) or a new Context initialized from the provided Hash. ### Example ```ruby # Create from Hash context = Interactor::Context.build(email: "john@example.com", password: "secret") # Preserve existing Context existing = Interactor::Context.new(user_id: 42) context = Interactor::Context.build(existing) ``` ``` -------------------------------- ### Implementing Interactors in Rails Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/07-usage-examples.md Shows a controller action invoking an organizer and the corresponding definitions for the organizer and its constituent interactors. ```ruby class OrdersController < ApplicationController def create result = CreateOrder.call( current_user_id: current_user.id, order_params: order_params, card_token: params[:card_token] ) if result.success? redirect_to order_path(result.order), notice: "Order created successfully" else @order = result.order @errors = result.errors render :new, status: :unprocessable_entity end end private def order_params params.require(:order).permit(:items, :shipping_address) end end class CreateOrder include Interactor::Organizer organize ValidateOrderData, CreateOrderRecord, ProcessPayment end class ValidateOrderData include Interactor def call unless context.order_params[:items].present? context.fail!(errors: { items: ["cannot be empty"] }) end end end class CreateOrderRecord include Interactor def call order = Order.create( user_id: context.current_user_id, **context.order_params ) if order.persisted? context.order = order else context.fail!(errors: order.errors) end end def rollback context.order.destroy end end class ProcessPayment include Interactor def call charge = PaymentProcessor.charge( amount: context.order.total, token: context.card_token ) context.order.update(charge_id: charge.id) rescue PaymentProcessor::Error => e context.fail!(error: e.message) end def rollback PaymentProcessor.refund(context.order.charge_id) end end ``` -------------------------------- ### Implement hooks in an Interactor Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/06-types-and-interfaces.md Use before, after, and around hooks to execute logic during the interactor lifecycle. ```ruby class HookedInteractor include Interactor # Hooks are automatically available before :setup after :cleanup around do |interactor| log("Starting") interactor.call log("Finished") end end ``` -------------------------------- ### Refactor Controller to Use Interactor Source: https://github.com/collectiveidea/interactor/blob/master/README.md Demonstrates the transition from standard controller logic to using an Interactor's call method for authentication. ```ruby class SessionsController < ApplicationController def create if user = User.authenticate(session_params[:email], session_params[:password]) session[:user_token] = user.secret_token redirect_to user else flash.now[:message] = "Please try again." render :new, status: :unprocessable_entity end end private def session_params params.require(:session).permit(:email, :password) end end ``` ```ruby class SessionsController < ApplicationController def create result = AuthenticateUser.call(session_params) if result.success? session[:user_token] = result.token redirect_to result.user else flash.now[:message] = t(result.message) render :new, status: :unprocessable_entity end end private def session_params params.require(:session).permit(:email, :password) end end ``` -------------------------------- ### Define and invoke an Interactor Organizer Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/06-types-and-interfaces.md Use Interactor::Organizer to sequence multiple interactors with shared context and automatic rollback. ```ruby class MyOrganizer include Interactor::Organizer organize InteractorOne, InteractorTwo, InteractorThree end result = MyOrganizer.call(input: "value") ``` -------------------------------- ### Handling Authorization Failures Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/05-failure.md Use this pattern to halt execution when a user lacks the required permissions. ```ruby class AuthorizeAction include Interactor def call unless context.current_user.can_perform?(context.action) context.fail!( error: "Unauthorized", error_code: "UNAUTHORIZED" ) end end end result = AuthorizeAction.call(current_user: user, action: "admin_delete") if result.failure? # Handle authorization failure end ``` -------------------------------- ### Testing Interactor Failures with .call! Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/05-failure.md Demonstrates how to test for raised Interactor::Failure exceptions and inspect the context within a rescue block. ```ruby describe AuthenticateUser do context "with invalid credentials" do it "raises Interactor::Failure" do expect { AuthenticateUser.call!(email: "john@example.com", password: "wrong") }.to raise_error(Interactor::Failure) end it "fails with correct context" do begin AuthenticateUser.call!(email: "john@example.com", password: "wrong") rescue Interactor::Failure => e expect(e.context).to be_a_failure end end end end ``` -------------------------------- ### Compose Interactors with .call! Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/08-patterns-and-best-practices.md Use .call! for manual composition to raise Interactor::Failure exceptions when steps fail. ```ruby # Recommended for manual composition begin step1 = Step1.call!(context) step2 = Step2.call!(context) rescue Interactor::Failure => e handle_error(e.context) end ``` -------------------------------- ### Authorization Pattern Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/09-quick-reference.md Uses a before hook to perform authorization checks before executing the main logic. ```ruby class ProtectedAction include Interactor before :authorize def call # Business logic end private def authorize context.fail!(error: "Unauthorized") unless authorized? end end ``` -------------------------------- ### Authorization Hook Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/03-hooks.md Implement a before hook to validate user permissions before executing business logic. ```ruby class AuthorizedInteractor include Interactor before :check_authorization def call # Authorized business logic end private def check_authorization context.fail!(error: "Unauthorized") unless current_user.admin? end end ``` -------------------------------- ### .organize(*interactors) Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/04-organizer.md Declares the list of interactor classes to be invoked in sequence when the organizer is called. ```APIDOC ## .organize(*interactors) ### Description Declares the interactors to be invoked as part of the organizer's execution. Interactors are called in the order they are declared. ### Signature `def self.organize(*interactors)` ### Parameters - **interactors** (Class or Array) - Required - One or more Interactor classes, or an array of Interactor classes. ### Example ```ruby class PlaceOrder include Interactor::Organizer organize ValidateOrder, CreateOrderRecord, ProcessPayment, SendConfirmation end ``` ``` -------------------------------- ### Avoid Overriding Organizer Call Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/08-patterns-and-best-practices.md Use hooks like 'before' instead of overriding the 'call' method in an organizer to maintain proper execution flow. ```ruby class MyOrganizer include Interactor::Organizer organize Step1, Step2 def call # Don't do this - you break the organizer puts "Custom call" super end end ``` ```ruby class MyOrganizer include Interactor::Organizer organize Step1, Step2 before do # Do custom setup here puts "Starting" end end ``` -------------------------------- ### Define .after hook signature Source: https://github.com/collectiveidea/interactor/blob/master/_autodocs/03-hooks.md Internal implementation signature for the .after class method. ```ruby def self.after(*hooks, &block) hooks << block if block hooks.each { |hook| after_hooks.unshift(hook) } end ```