Rails service objects for business logic

Maya Patel Jan 2026
2 tabs
module Posts
  class CreateService
    def initialize(author:, params:)
      @author = author
      @params = params
    end

    def call
      ActiveRecord::Base.transaction do
        create_post
        attach_images if @params[:images].present?
        notify_followers
        schedule_publication if @post.scheduled?

        Result.success(post: @post)
      end
    rescue ActiveRecord::RecordInvalid => e
      Result.failure(errors: e.record.errors.full_messages)
    rescue => e
      Rails.logger.error "Failed to create post: #{e.message}"
      Result.failure(errors: ['An unexpected error occurred'])
    end

    private

    def create_post
      @post = @author.posts.create!(
        title: @params[:title],
        body: @params[:body],
        status: @params[:status] || 'draft'
      )
    end

    def attach_images
      @params[:images].each do |blob_id|
        @post.images.attach(blob_id)
      end
    end

    def notify_followers
      return unless @post.published?

      NotifyFollowersJob.perform_later(@post.id)
    end

    def schedule_publication
      PublishPostJob.set(wait_until: @post.publish_at).perform_later(@post.id)
    end
  end

  class Result
    attr_reader :data, :errors

    def self.success(data = {})
      new(success: true, data: data)
    end

    def self.failure(errors:)
      new(success: false, errors: errors)
    end

    def initialize(success:, data: {}, errors: [])
      @success = success
      @data = data
      @errors = errors
    end

    def success?
      @success
    end

    def failure?
      !@success
    end
  end
end
2 files · ruby Explain with highlit

Service objects encapsulate complex business logic that doesn't belong in models or controllers. Each service performs one operation, like creating a post with side effects, processing a payment, or importing data. I create services in app/services with a single public call method. Services return result objects indicating success/failure with data or errors. This pattern keeps controllers thin—they orchestrate but don't implement business logic. Services are easily testable in isolation and reusable across controllers, jobs, and rake tasks. For multi-step operations, I chain services or use saga patterns. This architecture scales well as applications grow.