aglushkov/serega

View on GitHub
examples/preloads.rb

Summary

Maintainability
A
50 mins
Test Coverage
# frozen_string_literal: true

require "bundler/inline"

gemfile(true, quiet: true) do
  source "https://rubygems.org"
  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem "serega", path: File.join(File.dirname(__FILE__), "..")
  gem "sqlite3"
  gem "activerecord"
end

class AppSerializer < Serega
end

require "active_record"
require "logger"

logger = Logger.new($stdout)
logger.level = Logger::INFO
logger.formatter = proc { |severity, datetime, progname, msg| msg << "\n" }
ActiveRecord::Base.logger = logger
ActiveSupport::LogSubscriber.colorize_logging = false
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")

# Schema (user has many posts, post has many comments)
ActiveRecord::Migration.verbose = false
ActiveRecord::Schema.define do
  create_table :users, force: true do |t|
    t.string :first_name
    t.string :last_name
  end

  create_table :posts, force: true do |t|
    t.integer :user_id
    t.string :text
  end

  create_table :comments, force: true do |t|
    t.integer :post_id
    t.integer :user_id
    t.string :text
  end
end

# Models
class User < ActiveRecord::Base
  has_many :posts
end

class Post < ActiveRecord::Base
  belongs_to :user, optional: true
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post, optional: true
  belongs_to :user, optional: true
end

# Data
user1 = User.create!(first_name: "Bruce", last_name: "Wayne")
user2 = User.create!(first_name: "Clark", last_name: "Kent")
user3 = User.create!(first_name: "Jane", last_name: "Doe")

post1 = Post.create!(user: user1, text: "post1")
post2 = Post.create!(user: user1, text: "post2")

Comment.create!(user: user1, post: post1, text: "comment1")
Comment.create!(user: user2, post: post1, text: "comment2")
Comment.create!(user: user3, post: post2, text: "comment3")
Comment.create!(user: user1, post: post2, text: "comment4")

# Serializers
class AppSerializer < Serega
  plugin :preloads,
    auto_preload_attributes_with_serializer: true,
    auto_preload_attributes_with_delegate: true,
    auto_hide_attributes_with_preload: false

  plugin :activerecord_preloads
end

class UserSerializer < AppSerializer
  attribute :first_name
  attribute :last_name
  attribute :posts, serializer: -> { PostSerializer }
end

class PostSerializer < AppSerializer
  attribute :text
  attribute :comments, serializer: -> { CommentSerializer }
end

class CommentSerializer < AppSerializer
  attribute :text
  # attribute :user, serializer: -> { UserSerializer }
end

def example(message, expected_queries_count:)
  sqls = Storage.sqls
  sqls.clear

  yield

  if sqls.count != expected_queries_count
    raise "#{message}: expected #{expected_queries_count}, but there were #{sqls.count} requests: \n - #{sqls.join("\n - ")}"
  end
end

class Storage
  @sqls = []

  class << self
    attr_accessor :sqls
  end
end

ActiveSupport::Notifications.subscribe "sql.active_record" do |_name, _started, _finished, _id, payload|
  Storage.sqls << payload[:sql]
end

user1.reload
example("Single object", expected_queries_count: 2) do # loads posts, comments
  UserSerializer.new.call(user1)
end

user1.reload
user1.posts.create!(text: "post3")
example("Single object with created post", expected_queries_count: 2) do # loads posts, comments
  UserSerializer.new.to_h(user1)
end

users = [user1.reload, user2.reload, user3.reload]
example("Array", expected_queries_count: 2) do # loads posts, comments
  UserSerializer.new.to_h(users)
end

users = [user1.reload, user2.reload, user3.reload]
user1.posts.create!(text: "post4")
example("Array with created posts in some user", expected_queries_count: 2) do # loads posts, comments
  UserSerializer.new.to_h(users)
end

users = User.all
example("Relation", expected_queries_count: 3) do # loads users, posts, comments
  UserSerializer.new.to_h(users)
end

users = User.all.load
example("Loaded Relation", expected_queries_count: 2) do # loads posts, comments
  UserSerializer.new.to_h(users)
end

users = User.all.includes(:posts)
example("Relation included posts", expected_queries_count: 3) do # loads users, posts, comments
  UserSerializer.new.to_h(users)
end

users = User.all.includes(:posts).load
example("Loaded attribute included posts", expected_queries_count: 1) do # loads only comments
  UserSerializer.new.to_h(users)
end

comments = Comment.all
example("No preloads", expected_queries_count: 1) do # loads only comments
  CommentSerializer.to_h(comments)
end