dry-rails

repository·main·Indexed 42 days ago

https://github.com/dry-rb/dry-rails

A gem that integrates the dry-rb ecosystem into Ruby on Rails, enabling decoupling and dependency injection. It provides a container for component management, controller helpers for dependency resolution, and features like ApplicationContract for I18n-integrated validations and safe_params for schema-based parameter validation.

Tokens
2.4K
Snippets
11
Records
12
Agent score
55%

What's inside dry-rails

  1. How dry-rails handles code reloading in Rails

    main

    In development mode, dry-rails integrates with the Rails reloading mechanism via config.to_prepare. When Rails reloads code, Dry::Rails::Railtie#finalize! is called to ensure the dependency injection container and auto-injection constants are refreshed.

    To prevent stale constants and memory leaks during reloading, the process follows these steps:

    1. Stop Features: It calls stop_features, which triggers the stop lifecycle step for all currently started bootable components (features). This allows components to clear runtime constants they created.
    2. Re-initialize Container: A new container is created using the Rails root path and default provider directories (e.g., config/system/boot).
    3. Register Defaults: The :env plugin is enabled by default (inferring from Rails.env), and the railtie and inflector are registered.
    4. Constant Management: Existing Container and Deps (auto-inject) constants are removed from the application namespace to avoid name collisions or unused constants.
    5. Reload: The new container and injector are set into the application namespace.
  2. Configure dry-rails Container settings

    main

    The Dry::Rails::Container class (which inherits from Dry::System::Container) allows you to customize the behavior of your dependency container via class-level settings. You can enable specific features, change the constant name used for auto-injection, or change the container's constant name.

    Available configuration settings:

    • features: An array of symbols representing features to enable by default. Available values:
      • :application_contract
      • :safe_params
      • :controller_helpers
    • auto_inject_constant: A string defining the constant name used for auto-injection (defaults to "Deps").
    • container_constant: A string defining the custom container constant name (defaults to "Container").
    module MyApp
      class Container < Dry::Rails::Container
        configure do |config|
          config.features = [:application_contract]
          config.auto_inject_constant = "MyDeps"
          config.container_constant = "AppContainer"
        end
      end
    end
  3. Access validated parameters with `safe_params`

    main

    The safe_params method returns a Dry::Schema::Result object containing the parameters processed by the schema defined for the current action.

    This method is only populated if a schema was registered for the current action_name. It provides access to the coerced values and validation results (e.g., safe_params.success? or safe_params.errors).

    # Inside a controller action
    if safe_params.success?
      # Use safe_params.to_h to get the validated data
      params_data = safe_params.to_h
    else
      # Handle validation errors
      render json: safe_params.errors.to_h, status: :unprocessable_entity
    end
  4. Stop all running dry-rails features

    main

    The stop_features method stops all currently started bootable components (features) within the dry-rails container. This is a critical operation during development reloading to ensure that components can execute their stop lifecycle logic, which typically involves cleaning up constants or resources created during the boot process.

    Dry::Rails::Railtie.stop_features
  5. Use ApplicationContract as a base class for validations

    main

    When using the :application_contract feature in dry-rails, you should inherit from Dry::Rails::Features::ApplicationContract instead of Dry::Validation::Contract.

    This base class is pre-configured during the Rails boot process to integrate with your application's localization settings. Specifically, it:

    1. Sets the message namespace to :contracts.
    2. Uses the :i18n backend for messages.
    3. Automatically loads translation files from config/locales/*.yml into the contract's message configuration.

    This allows you to define validation error messages using standard Rails I18n YAML files.

    class UserContract < Dry::Rails::Features::ApplicationContract
      params do
        required(:email).filled(:string)
      end
    
      rule(:email) do
        key.failure(:invalid_format) if value.glib_check_logic
      end
    end
  6. Define parameter schemas for controller actions

    main

    Use the schema class method in your controller to define validation rules for specific actions using dry-schema syntax. You can pass one or more action names as arguments and provide a block that defines the schema.

    When a request hits an action with a defined schema, dry-rails automatically runs the validation before the action executes. The validated and coerced parameters are then available via the safe_params instance method.

    class UsersController < ApplicationController
      schema :create, :update do
        required(:user).hash do
          required(:name).filled(:string)
          required(:email).filled(:string)
        end
      end
    
      def create
        # Access validated params via safe_params
        user_params = safe_params[:user]
        # ...
      end
    end
  7. Access the dry-rails container

    main

    The container method allows you to retrieve the current Dry::Rails::Container instance from the application's namespace. It dynamically looks up the constant defined by the container configuration.

    # Returns the Dry::Rails::Container instance
    container = Dry::Rails::Railtie.new.container
  8. Resolve dependencies in Rails controllers using `resolve`

    main

    The resolve method is a controller helper provided by dry-rails that allows you to fetch a component directly from the application container using its key. This is useful for injecting business logic or services into your controller actions.

    • Parameters: key (Symbol or String) representing the component key.
    • Returns: The component associated with the key.
    • Raises: Dry::Container::Error if the key is not found in the container.
    def index
      users = resolve("users.index").(safe_params[:query])
      render json: users
    end
  9. Configure the dry-rails container

    main

    Use Dry::Rails.container to define a configuration block that will be evaluated within the context of the container. This is typically used in a Rails initializer (e.g., config/initializer/system.rb) to set up auto-registration paths and namespaces for your components.

    Common tasks include:

    • Adding component directories via config.component_dirs.add.
    • Defining namespaces within those directories using dir.namespaces.add to map directory structures to specific container keys.
    # config/initializer/system.rb
    
    Dry::Rails.container do
      config.component_dirs.add "lib" do |dir|
        dir.namespaces.add "my_super_cool_app", key: nil
      end
    
      config.component_dirs.add "app/operations"
    end
  10. Finalize and reload the dry-rails container

    main

    You can manually trigger the finalization and setup process of the dry-rails container. This is useful if you need to ensure the container is fully booted and all providers are registered. The finalize! method performs the full setup, including registering default components and evaluating initializers. The reload method is an alias for finalize! and is intended for use during development when code changes require a fresh container state.

    # To finalize the container setup
    Dry::Rails::Railtie.finalize!
    
    # To reload the container (alias for finalize!)
    Dry::Rails::Railtie.reload!
  11. Configure available dry-rails features

    main

    When configuring the Dry::Rails::Container, you can specify which features are enabled by default using the features setting.

    Supported feature symbols:

    • application_contract
    • safe_params
    • controller_helpers
    # Example of enabling specific features
    config.features = [:application_contract, :safe_params]