Refactoring the Rails Model

It feels like the Rails Model violates the Single Responsibility Principle right out of the box. Wouldn’t this be an easier way to write a non-trivial Rails application?

# lib/
#   models/
#     item/
#       creator.rb
#       query.rb
#       record.rb
#       validator.rb

module Item
  class Creator < ActiveRecord::Creator
    def call!
      super
      ElasticSearch.item_created!(item)
      # ... other side-effects
    end
  end
end

module Item
  class Query < ActiveRecord::Query
    belongs_to :user

    def available
      where(purchased_at: nil)
    end

    # ... more scopes
  end
end

module Item
  class Record < ActiveRecord::Record
    def photos
      0.upto(3).map { |i| send("photo_#{i}") }.compact
    end

    def photos=(arr)
      0.upto(3).each { |i| send("photo_#{i}=", arr[i] ) }
    end

    # ... other data-accessor methods
  end
end

module Item
  class Validator < ActiveRecord::Validator
    valid_attrs %i( title description photos user_id )
    validates :title, length: { in: 4..30 }
    validates :description, presence: true
    validates :user_id, presence: true
  end
end

class ItemsController
  def index
    render json: paginate(Item::Query.available)
  end

  def create
    creator = Item::Creator.new(params[:item])
    creator.call!
    render json: creator.item
  rescue ActiveRecord::Validator::Invalid => e
    render status: 400, json: { errors: e.error_messages }
  end
end

Leave a Reply

Your email address will not be published. Required fields are marked *