Tuesday, May 21, 2024
HomeRuby On RailsRuby on Rails Mannequin Anti-patterns and Patterns

Ruby on Rails Mannequin Anti-patterns and Patterns


Welcome again to the second publish within the Ruby on Rails Patterns and Anti-patterns sequence. Within the final weblog publish, we went over what patterns and anti-patterns are normally. We additionally talked about a few of the most well-known patterns and anti-patterns within the Rails world. On this weblog publish, we’ll undergo a few Rails mannequin anti-patterns and patterns.

When you’re fighting fashions, this weblog publish is for you. We’ll shortly undergo the method of placing your fashions on a eating regimen and end strongly with some issues to keep away from when writing migrations. Let’s bounce proper in.

Fats Chubby fashions

When growing a Rails software, whether or not it’s a full-blown Rails web site or an API, folks are inclined to retailer many of the logic within the mannequin. Within the final weblog publish, we had an instance of a Tune class that did many issues. Holding loads of issues within the mannequin breaks the Single Accountability Precept (SRP).

Let’s take a look.

class Tune < ApplicationRecord
  belongs_to :album
  belongs_to :artist
  belongs_to :writer

  has_one :textual content
  has_many :downloads

  validates :artist_id, presence: true
  validates :publisher_id, presence: true

  after_update :alert_artist_followers
  after_update :alert_publisher

  def alert_artist_followers
    return if unreleased?

    artist.followers.every follower
  finish

  def alert_publisher
    PublisherMailer.song_email(writer, self).deliver_now
  finish

  def includes_profanities?
    textual content.scan_for_profanities.any?
  finish

  def user_downloaded?(person)
    person.library.has_song?(self)
  finish

  def find_published_from_artist_with_albums
    ...
  finish

  def find_published_with_albums
    ...
  finish

  def to_wav
    ...
  finish

  def to_mp3
    ...
  finish

  def to_flac
    ...
  finish
finish

The issue with fashions like these is that they grow to be a dumping floor for the totally different logic associated to a tune. Strategies begin piling up as they get added slowly, one after the other over time.

I instructed splitting the code contained in the mannequin into smaller modules. However by doing that, you’re merely shifting code from one place to a different. Nonetheless, shifting code round means that you can manage code higher and keep away from overweight fashions with diminished readability.

Some folks even resort to utilizing Rails issues and discover that the logic could be reused throughout fashions. I beforehand wrote about it and a few folks cherished it, others didn’t. Anyway, the story with issues is just like modules. You ought to be conscious that you’re simply shifting code to a module that may be included wherever.

One other different is to create small courses after which name them at any time when wanted. For instance, we are able to extract the tune changing code to a separate class.

class SongConverter
  attr_reader :tune

  def initialize(tune)
    @tune = tune
  finish

  def to_wav
    ...
  finish

  def to_mp3
    ...
  finish

  def to_flac
    ...
  finish
finish

class Tune
  ...

  def converter
    SongConverter.new(self)
  finish

  ...
finish

Now now we have the SongConverter that has the aim of changing songs to a
totally different format. It could have its personal checks and future logic about changing.
And, if we need to convert a tune to MP3, we are able to do the next:

To me, this seems a bit clearer than utilizing a module or a priority. Perhaps as a result of I favor to make use of composition over inheritance. I contemplate it extra intuitive and readable. I recommend you evaluate each circumstances earlier than deciding which strategy to go. Or you possibly can select each in order for you, no person is stopping you.

SQL Pasta Parmesan

Who doesn’t love some good pasta in actual life? Then again, relating to code pasta, virtually nobody is a fan. And for good causes. In Rails fashions, you possibly can shortly flip your Lively Report utilization into spaghetti, swirling round everywhere in the codebase. How do you keep away from this?

There are a number of concepts on the market that appear to maintain these lengthy queries from turning into strains of spaghetti. Let’s first see how database-related code could be in all places. Let’s return to our Tune mannequin. Particularly, to after we attempt to fetch one thing from it.

class SongReportService
  def gather_songs_from_artist(artist_id)
    songs = Tune.the place(standing: :revealed)
                .the place(artist_id: artist_id)
                .order(:title)

    ...
  finish
finish

class SongController < ApplicationController
  def index
    @songs = Tune.the place(standing: :revealed)
                 .order(:release_date)

    ...
  finish
finish

class SongRefreshJob < ApplicationJob
  def carry out
    songs = Tune.the place(standing: :revealed)

    ...
  finish
finish

Within the instance above, now we have three use-cases the place the Tune mannequin is being queried. Within the SongReporterService that’s used for reporting knowledge about songs, we attempt to get revealed songs from a concrete artist. Then, within the SongController, we get revealed songs and organize them by the discharge date. And eventually, within the SongRefreshJob we get solely revealed songs and do one thing with them.

That is all high quality, however what if we all of a sudden resolve to vary the standing title to launched or make another modifications to the way in which we fetch songs? We must go and edit all occurrences individually. Additionally, the code above will not be DRY. It repeats itself throughout the appliance. Don’t let this get you down. Fortunately, there are answers to this downside.

We are able to use Rails scopes to DRY this code out. Scoping means that you can outline commonly-used queries, which could be known as on associations and objects. This makes our code readable and simpler to vary. However, perhaps an important factor is that scopes enable us to chain different Lively Report strategies similar to joins, the place, and so on. Let’s see how our code seems with scopes.

class Tune < ApplicationRecord
  ...

  scope :revealed, ->            { the place(revealed: true) }
  scope :by_artist, ->(artist_id) { the place(artist_id: artist_id) }
  scope :sorted_by_title,         { order(:title) }
  scope :sorted_by_release_date,  { order(:release_date) }

  ...
finish

class SongReportService
  def gather_songs_from_artist(artist_id)
    songs = Tune.revealed.by_artist(artist_id).sorted_by_title

    ...
  finish
finish

class SongController < ApplicationController
  def index
    @songs = Tune.revealed.sorted_by_release_date

    ...
  finish
finish

class SongRefreshJob < ApplicationJob
  def carry out
    songs = Tune.revealed

    ...
  finish
finish

There you go. We managed to chop the repeating code and put it within the mannequin. However this doesn’t at all times work out for the perfect, particularly if you’re recognized with the case of a fats mannequin or a God Object. Including an increasing number of strategies and duties to the mannequin won’t be such an amazing concept.

My recommendation right here is to maintain scope utilization to a minimal and solely extract the frequent queries there. In our case, perhaps the the place(revealed: true) can be an ideal scope since it’s used in all places. For different SQL associated code, you would use one thing known as a Repository sample. Let’s discover out what it’s.

Repository sample

What we’re about to point out will not be a 1:1 Repository sample as outlined within the Area-Pushed Design e-book. The concept behind ours and the Rails Repository sample is to separate database logic from enterprise logic. We might additionally go full-on and create a repository class that does the uncooked SQL requires us as a substitute of Lively Report, however I wouldn’t advocate such issues until you really want it.

What we are able to do is create a SongRepository and put the database logic in there.

class SongRepository
  class << self
    def discover(id)
      Tune.discover(id)
    rescue ActiveRecord::RecordNotFound => e
      elevate RecordNotFoundError, e
    finish

    def destroy(id)
      discover(id).destroy
    finish

    def recently_published_by_artist(artist_id)
      Tune.the place(revealed: true)
          .the place(artist_id: artist_id)
          .order(:release_date)
    finish
  finish
finish

class SongReportService
  def gather_songs_from_artist(artist_id)
    songs = SongRepository.recently_published_by_artist(artist_id)

    ...
  finish
finish

class SongController < ApplicationController
  def destroy
    ...

    SongRepository.destroy(params[:id])

    ...
  finish
finish

What we did right here is we remoted the querying logic right into a testable class. Additionally, the mannequin is not involved with scopes and logic. The controller and fashions are skinny, and everybody’s blissful. Proper? Properly, there may be nonetheless Lively Report doing all of the heavy pulling there. In our state of affairs, we use discover, which generates the next:

SELECT "songs".* FROM "songs" WHERE "songs"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]

The “proper” method can be to have all this outlined contained in the SongRepository. As I mentioned, I might not advocate that. You don’t want it and also you need to have full management. A use case for going away from Lively Report can be that you simply want some complicated tips inside SQL that aren’t simply supported by Lively Report.

Speaking about uncooked SQL and Lively Report, I additionally must deliver up one matter. The subject of migrations and find out how to do them correctly. Let’s dive in.

Migrations – who cares?

I typically hear an argument when writing migrations that the code there shouldn’t be nearly as good as it’s in the remainder of the appliance. And that argument doesn’t sit properly with me. Individuals have a tendency to make use of this excuse to arrange smelly code within the migrations since it is going to solely be run as soon as and forgotten. Perhaps that is true if you’re working with a few folks and everyone seems to be in fixed sync on a regular basis.

The fact is commonly totally different. The appliance can be utilized by a bigger variety of folks not figuring out what occurs with totally different software components. And when you put some questionable one-off code there, you would possibly break somebody’s growth setting for a few hours due to the corrupted database state or only a bizarre migration. Unsure if that is an anti-pattern, however you ought to be conscious of it.

make migrations extra handy for different folks? Let’s undergo a listing that can make migrations simpler for everybody on the mission.

Be sure that at all times to offer a down technique.

You by no means know when one thing goes to be rolled again. In case your migration will not be reversible, ensure to boost ActiveRecord::IrreversibleMigration exception like so:

def down
  elevate ActiveRecord::IrreversibleMigration
finish

Attempt to keep away from Lively Report in migrations

The concept right here is to attenuate exterior dependencies apart from the state of the database on the time when the migration must be executed. So there can be no Lively Report validations to damage (or perhaps save) your day. You might be left with plain SQL. For instance, let’s write a migration that can publish all songs from a sure artist.

class UpdateArtistsSongsToPublished < ActiveRecord::Migration[6.0]
  def up
    execute <<-SQL
      UPDATE songs
      SET revealed = true
      WHERE artist_id = 46
    SQL
  finish

  def down
    execute <<-SQL
      UPDATE songs
      SET revealed = false
      WHERE artist_id = 46
    SQL
  finish
finish

You probably have an amazing want for the Tune mannequin, a suggestion can be to outline it contained in the migration. That method, you possibly can bulletproof your migration from any potential modifications within the precise Lively Report mannequin contained in the app/fashions. However, is that this all high quality and dandy? Let’s go to our subsequent level.

Separate schema migrations from knowledge migrations

Going by way of the Rails Guides on migrations, you’ll learn the next:

Migrations are a function of Lively Report that means that you can evolve your database schema over time. Somewhat than write schema modifications in pure SQL, migrations will let you use a Ruby DSL to explain modifications to your tables.

Within the abstract of the information, there isn’t any point out of enhancing the precise knowledge of
the database desk, solely the construction. So, the truth that we used the common migration
to replace songs within the second level will not be utterly proper.

If you must do one thing related in your mission typically, think about using
the data_migrate gem. It’s a good method
of separating knowledge migrations from schema migrations. We are able to simply rewrite our earlier instance with it. To generate the info migration, we are able to do the next:

bin/rails generate data_migration update_artists_songs_to_published

After which add the migration logic there:

class UpdateArtistsSongsToPublished < ActiveRecord::Migration[6.0]
  def up
    execute <<-SQL
      UPDATE songs
      SET revealed = true
      WHERE artist_id = 46
    SQL
  finish

  def down
    execute <<-SQL
      UPDATE songs
      SET revealed = false
      WHERE artist_id = 46
    SQL
  finish
finish

This fashion, you’re maintaining all of your schema migrations contained in the db/migrate
listing and all of the migrations that take care of the info contained in the db/knowledge
listing.

Remaining ideas

Coping with fashions and maintaining them readably in Rails is a continuing battle. Hopefully, on this weblog publish, you bought to see the attainable pitfalls and options to frequent issues. The checklist of mannequin anti-patterns and patterns is way from full on this publish, however these are essentially the most notable ones I discovered lately.

In case you are taken with extra Rails patterns and anti-patterns, keep tuned for the subsequent installment within the sequence. In upcoming posts, we’ll undergo frequent issues and options to the view and controller aspect of the Rails MVC.

Till subsequent time, cheers!

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments