The Beginning, Fat Controllers
When it all began most code was written in the controllers. Controllers grew in size to the point it was impossible to navigate the code. We started moving that logic into the models to keep the controllers smaller. 
Skinny Controllers, Fat Models
We moved on to the era of skinny controllers. This left us with fat models, the same problem occurred but worse, the code was no longer isolated to an individual requests like it was with fat controllers so we had to start being careful to manage side-effects correctly. A model callback might be desirable in one request but not in another, same for validations etc. Our models also began to bloat, full of methods only relevant to a subset of requests that model would interact with.
Service Objects
Somewhere along the road after the shift to fat models we started building service objects, PORO classes and modules to run abstracted logic. This helped us to reduce the size of our models to something more manageable but didn’t really solve the core issue. 
In hindsight, I believe if the community started using service objects sooner we would never have moved to fat models. At that point in time the community was trying it's best to stick to the controller, model, or view mentality, anything outside of that was slightly taboo.
A Better Future
There are solutions to architecture out there but are often much more complex than the traditional rails architecture. IMO the key problem was introduced when we moved away from fat controllers.
Fat controllers allowed us to have isolated logic specific to each request, the entry point into our applications. The end-user always starts from a controller action so why do we write our code as if they start in the model?
Why not start in the controller and build outwards, that’s what I’ve started doing.
Form Objects and Service Objects
Form objects have been around for as long as ActiveModel has been around. When people started touting the idea of form objects they were  primarily discussed as a case for virtual models, like contact forms or other non-persisted data sources.
class ContactForm
  include ActiveModel::Model
  attr_accessor :name, :email, :message
  validates :name, presence: true
  validates :email, presence: true
  validates :message, presence: true
  def send_mail
    return false unless valid?
    
    ContactFormMailer.incoming_message(
      name: name, email: email, message: message
    ).deliver_later
  end
end
We can use this same design to pass the params to models for persistence.
This allows us to do some interesting things that are otherwise much more complicated.
Perhaps you have a page that saves data to more than one model:
class UpdateProfileForm
  include ActiveModel::Model
  attr_accessor :user_id, :name, :contact_number
  def save
    user = User.find(user_id)
    user.update(name: name)
    user.device.update(contact_number: contact_number)
  end
end
More importantly we can move all of those validations and callbacks from the models into the correct places. The main reason I wanted to pursue a solution to this problem was actually due to the nature of modern app UX design, often signup isn’t a simple single page form but a bunch of steps. Validations don’t always apply to each of those steps.
For example let’s imagine a situation where we have a KYC process that has 50 fields. 
The first page has 10 fields, including an image uploader for an ID card and a selfie holding that ID card. This is fairly common in KYC (Know Your Customer) processes and in my experience they often are not allowed to be stored for a long period. In more than one application I’ve worked on those files should be validated during the first step of the process but will be sent off for processing before we validate the second step. With the traditional Rails MVC architecture this is a real mess. You end up with something like this
class User
  attr_accessor :kyc_step, :updating_profile
  validates :name, on: :create
  validates :id_image, if: -> { kyc_step == '1' }
  validates :document, if: -> { kyc_step == '2' }
  validates :avatar, if: -> { kyc_step == '3' || updating_profile }
end
With form objects each action has its own set of validations so there’s no need for these virtual attributes and complex conditionals. The code becomes much easier to read and understand. All of the logic for the controller action is contained inside a single class (except calls to abstracted service objects) and it’s isolated from the rest of the codebase. 
I’ve been fortunate enough to write a couple of apps from scratch using form objects and both times I feel that it benefited the app design and will prove to be a simpler system to maintain in the long run. I’ve also developed a small lib called ActionForm for this purpose which I’ll go into more details in another post. 
Models become ORM models again. They define associations and scopes, that's it. The logic is abstracted to the correct places. Each controller action has its own form, super simple to read and debug. You can even render them in views. 
The Result
class User < ApplicationRecord
  belongs_to :account
  has_many :devices
  
  scope :active, -> { where.not(:activated_at: nil) }
end
 
# app/forms/user/registration_form.rb
class User < ApplicationRecord
  class RegistrationForm
    # more about this in another post,
    # basically a collection of active support
    # modules and some helpers
    include ActiveForm::Form 
    
    attr_accessor :user
  
    attribute :email
    attribute :password
  
    with_options presence: true do
      validates :email, format: { with: Devise.EMAIL_REGEX }
      validates :password, length: { minimum: 8, maximum: 128 }
    end
 
    def save
      return false unless valid?
  
      self.user = User.create(email: email, password: password)
    end
  end
end
 
# app/controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
  def new
    @form = User::RegistrationForm.new
  end
 
  def create
    @form = User::RegistrationForm.new(params: params[:form])
 
    if @form.save
      redirect_to welcome_path
    else
      render :new
    end
  end
end
 
# app/views/registrations/new.erb.html
<%= simple_form_for @form, url: registrations_path, method: :post do |f| %>
  <%= f.input :email %>
  <%= f.input :password %>
  <%= f.submit %>
<% end %>