Have you ever wanted to just create an active record example to someone in your team without thinking in the database setup?
Or maybe send two different models designs with some examples to see the difference, but the setup what just to difficult?
Or like me, create a bunch of examples to teach an Active Record concept?
If you have had one of this problems, maybe this tool can help you.
I want to introduce a new tool that I am working on.
It will help you play with Active Record and Postgres, without the thinking in the database setup.
You will be able to declare the schema, models, seeds and examples in just one file, like this:
schema do
create_table :posts do |t|
t.string :title
t.text :body
end
create_table :comments do |t|
t.text :body
t.integer :post_id
end
add_index :comments, :post_id
end
models do
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
end
seeds do
posts = create_list(Post, count: 10) do
{
title: FFaker::CheesyLingo.title,
body: FFaker::CheesyLingo.paragraph
}
end
create_list_for_each_record(Comment, records: posts, count: 20) do |post|
{
post_id: post.id,
body: FFaker::CheesyLingo.sentence
}
end
end
example "first" do
Post.includes(:comments).limit(5).map do |post|
puts post.comments.count
end
end
example "second" do
Post.includes(:comments).limit(5).map do |post|
puts post.comments.size
end
end
And the run it with:
bundle exec run_playground file_name.rb
And it will:
And print an output like:
Setup
-----
-- create_table(:posts)
-> 0.3001s
-- create_table(:comments)
-> 0.0092s
-- add_index(:comments, :post_id)
-> 0.0041s
Example: first
--------------
D, [2022-08-08T18:13:46.118881 #89680] DEBUG -- : Post Load (4.5ms) SELECT "posts".* FROM "posts" LIMIT $1 [["LIMIT", 5]]
D, [2022-08-08T18:13:46.143351 #89680] DEBUG -- : Comment Load (1.0ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN ($1, $2, $3, $4, $5) [["post_id", 1], ["post_id", 2], ["post_id", 3], ["post_id", 4], ["post_id", 5]]
D, [2022-08-08T18:13:46.156379 #89680] DEBUG -- : Comment Count (3.8ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 1]]
20
D, [2022-08-08T18:13:46.158504 #89680] DEBUG -- : Comment Count (1.0ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 2]]
20
D, [2022-08-08T18:13:46.161012 #89680] DEBUG -- : Comment Count (1.3ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 3]]
20
D, [2022-08-08T18:13:46.163467 #89680] DEBUG -- : Comment Count (0.5ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 4]]
20
D, [2022-08-08T18:13:46.165299 #89680] DEBUG -- : Comment Count (1.0ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 5]]
20
Example: second
---------------
D, [2022-08-08T18:13:46.166206 #89680] DEBUG -- : Post Load (0.3ms) SELECT "posts".* FROM "posts" LIMIT $1 [["LIMIT", 5]]
D, [2022-08-08T18:13:46.167795 #89680] DEBUG -- : Comment Load (0.7ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN ($1, $2, $3, $4, $5) [["post_id", 1], ["post_id", 2], ["post_id", 3], ["post_id", 4], ["post_id", 5]]
20
20
20
20
20
I prepared a lot of examples for the ebook Avoid n+1 queries on rails using the first version of my Active Record Playground. A template to help in the setup of an environment with Active Record.
With the old template it was is hard to write a lot of examples with the same database schema, and it was hard to setup a project for each schema.
With this tool is much more easier to create examples with different schemas because you don’t need to setup a whole project directory for each schema.
It makes it easier to create more and more diverse examples. And also helping the people that see the examples to focus just on the example code instead of the project setup.
You can find the code on: https://github.com/bhserna/active_record_playground_runner
For now, the gem is only on github, and if you want to install it you will need to add this to your Gemfile:
gem 'active_record_playground_runner', "~> 0.1.0", github: "bhserna/active_record_playground_runner", branch: "main"
In the schema you will be able to define a schema using the methods that rails uses to define the schema in a regular rails application.
Here you can define the records or plain old ruby objects that you will need on the seeds.
You can use your models to create the state you need for your examples.
You will be able to use the FFaker gem. It will be already required.
In the seeds from the example, there are two methods that are part of the gem, create_list
and create_list_for_each_record
.
seeds do
posts = create_list(Post, count: 10) do
{
title: FFaker::CheesyLingo.title,
body: FFaker::CheesyLingo.paragraph
}
end
create_list_for_each_record(Comment, records: posts, count: 20) do |post|
{
post_id: post.id,
body: FFaker::CheesyLingo.sentence
}
end
end
create_list
receives a record class, a count
keyword and a block. The block will be executed to create each record the number of times specified by count
.
create_list_for_each_record
also expects a list of records
and for each record it will run the block count
times.
Both methods will first build data as an array of hashes, and then use insert_all
to create the records. Be aware that if you use this methods the callbacks will not be fired.
You can define as many examples as you want, you can pass a name if you need it:
example do
Post.includes(:comments).limit(5).map do |post|
puts post.comments.count
end
end
example "My example" do
Post.includes(:comments).limit(5).map do |post|
puts post.comments.size
end
end
If you need to define changes to the model classes, you will be able to do it inside the example:
example "third" do
class Post < ActiveRecord::Base
has_one :latest_comment, -> { order(id: :desc) }, class_name: "Comment"
end
Post.preload(:latest_comment).limit(5).map do |post|
puts post.latest_comment
end
end
If you want to run a playground without the need to install the gem apart or with a Gemfile, you can use bundler/inline and run the playground like this:
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'active_record_playground_runner', "~> 0.1.0", github: "bhserna/active_record_playground_runner", branch: "main"
end
require "active_record_playground_runner"
ActiveRecordPlaygroundRunner::Playground.new("My playground name") do
schema do
create_table :posts do |t|
t.string :title
t.text :body
end
create_table :comments do |t|
t.text :body
t.integer :post_id
end
add_index :comments, :post_id
end
models do
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
end
seeds do
posts = create_list(Post, count: 10) do
{
title: FFaker::CheesyLingo.title,
body: FFaker::CheesyLingo.paragraph
}
end
create_list_for_each_record(Comment, records: posts, count: 20) do |post|
{
post_id: post.id,
body: FFaker::CheesyLingo.sentence
}
end
end
example "first" do
Post.includes(:comments).limit(5).map do |post|
puts post.comments.count
end
end
example "second" do
Post.includes(:comments).limit(5).map do |post|
puts post.comments.size
end
end
end.setup.run.destroy
And then run the file like any other ruby file:
ruby my_playground.rb
Learn just enough fundamentals to be fluent preloading associations with ActiveRecord, and start helping your team to avoid n+1 queries on production.