This log covers few helpful patterns (3) and two tips and tricks for building Rails application.

Models

#1 Non ActiveReocrd Models are classes which encapsulate unique business logic out of the ActiveReocrd models.

Most logic should go to model to avoid fat controller because,

  • Hard to understand
  • Expose business logic
  • More conflicts in code
  • Hard to implement new feature

In OOP, developer should NEVER access object’s status but tell object what to do.

Callback Should Only Be Used To Alter Object’s Internal State

class User < ActiveReocrd::Base
  before_create :set_token
  protected
    def set_token
      self.token = TokenGenerator.create(self)  # Bad Code
    end
end

Developer must avoid calling other domain objects from callbacks which introduces tight coupling affects the object’s database lifecycle.

class User < ActiveReocrd::Base
  def register
    self.token = TokenGenerator.create(self)
    save
  end
end

Callbacks should only be used to alter the object’s internal state (NO external dependencies).

Not All Models Need To Be ActiveReocrd

An object that controls too many other objects in the system is an anti-pattern known as a God Object.

Scopes

class UsersController < ApplicationController
  def index
    @usres = User.where('created_at > ?', 3.days.ago)  # Bad code
  end
end

It is bad because it exposes implementation details, produce unnecessary duplication and complicates writing tests.

class User < ActiveReocrd
  def self.recent
    where('created_at > ?', 3.days.ago)
  end
end

# Alternatively
class User < ActiveReocrd
  scope :recent, -> { where('created_at > ?', 3.days.ago) }
end

Concerns

#2 Concerns help extract duplicate code into reusable modules that can be mixed into multiple controller or models.

# Bade Code
class Post < ActiveReocrd::Base
  has_many :comments, as: :commentable
  def comments_by_user(id)
    comments.where(user_id: id)
  end
end

class Image < ActiveReocrd::Base
  has_many :comments, as: :commentable
  def comments_by_user(id)
    comments.where(user_id: id)
  end
end

class Comment < ActiveReocrd::Base
  belongs_to :commentable, polymorphic: true
end
module Commentable
  def self.included(base)
    base.class_eval do
      has_many :comments, as: :commentable
    end

    def comments_by_user(id)
      comments.where(user_id: id)
    end
  end
end

class Post < ActiveReocrd::Base
  include Commentable
end

class Image < ActiveReocrd::Base
  include Commentable
end

Decorators

#3 Decorators helps extarct presentation logic out of the models.

# Bad Code
class Post < ActiveReocrd::Base
  def is_front_page?
    publish_at > 3.days.ago  # view-related business logic
  end
end

Decorators attach presentation logic to an object dynamically,

  • Transparent to clients
  • Wrap another object
  • Delegate methods to the wrapped object
  • Provide one or two methods of their own
# app/decorators/post_decorator.rb
class PostDecorator
  attr_reader :post
  def initialize(post)
    @post = post
  end

  def is_front_page?
    post.publish_at > 3.days.ago
  end

  # Forward any undefined method to the wrapped object
  def method_missing(method_name, *args, &block)
    post.send(method_name, *args, &block)
  end

  def respond_to_missing?(method_name, include_private = false)
    post.respond_to?(method_name, include_private) || super
  end
end

Problem with Rails view helpers,

  • Pollute the global namespaces with methods specific to a model
  • Forces a function approach within an object oriented domain model

Always define respond_to_missing when overriding method_missing.

Other Optimizations

SELECT

.select() to reduce the number of columns loaded from the database.

users = User.select(:id).where('created_at < ?', 3.days.from_now)

PLUCK

.pluck() to return an array of values instead of ActiveReocrd object.

ids = User.where('created_at < ?', 3.days.from_now).pluck(:id)

Filter Sensitive Parameters

Add additional fields that need to be filtered from logs,

config.filter_parameters += [:password, :ssn]

PROCFILE

Multiple processes required to run the application. For example, running more than one elements (rails server; bundle exec rake worker; bundle exec rake scheduler).

Procfile,

web: bundle exec rails s -p $PORT
worker: bundle exec rake worker
urgentworker: bundle exec rake urgent_worker
schedueler: bundle exec rake scheduler

Format for Procfile,

<process-type>: <command>

Use foreman to run procfile, which is a command-line tool for running Procfile-backed apps.

# Install Foreman
gem install foreman

# Running Procfile
foreman start