In Defence of ORMs

I’ve seen a strange amount of bickering about this particular pattern, and it honestly surprises me that there are two sides of this. Let me explain why one side’s opinion is so very wrong.

What Are ORMs For (and why do people use them)?

public function comments() {
return $this->hasMany(Comment::class);
}
$posts = Post::with('comments')->get();
Post->findOrFail($id)->addComment(new Comment())
(await Post.findByPk(id)).addComment({whateverData})
const post = Post.getById(12);
post.title = 'ORMs are cool'
await post.save();
UPDATE posts SET title="ORMs are cool" WHERE id = 12;
$post = Post.findOrFail(12);
$post->update(request->body);
$post.save();
//PDO example$query = "UPDATE posts SET title = :title, content = :content, summary = :summary, category_id = :category_id WHERE id = :id";
$statement = $database->prepare($query);
$statement->execute($data_array);
$query = "UPDATE posts SET "
if($data_array['title']) $query .= "title = :title, ";
if($data_array['content']) $query .= "content = :content, ";
if($data_array['summary']) $query .= "summary = :summary, ";
if($data_array['category_id']) $query .= "category_id = : category_id ";
$query .= " WHERE id = :id";
Route::get('comments/{comment}', function (Comment $comment) {
return $comment;
});

What is wrong with ORMs

They make inefficient code.

You’re learning the ORM not SQL, and it’s not transferable

An ORM Can’t Do Everything

SELECT *
FROM item
WHERE
id NOT IN
(SELECT item_id FROM ingredient WHERE dish_id = 2)
AND id IN
(SELECT item_id FROM ingredient WHERE dish_id = 1);
$missing_ingredients = Dish->find(1)->ingredients->all()
->diff(Dish->find(2)->ingredients)->all();
$ingredients = Ingredient::where('dish_id', 1)->get();
$ingredients2 = Ingredient::where('dish_id', 2)->get();
$missing_ingredients = $ingredients->diff($ingredients2)->all();
//  all the dishes
Dish::all();
// the dish with all the ingredients
Dish::find(15)->with('ingredients')->get()
// all the recipes this ingredient can make
Ingredient::find(123)->recipes()->get()
$posts_query = Post::with('comments')->where(id, 123);
$posts_query2 = DB::table('post')
->join('comments', 'comment.post_id', '=', 'post.id')
->where('id', 123);
$dish1_ids = Ingredient::where('dish_id', 1)
->pluck('item_id')->get();
$dish2_ids =Ingredient::where('dish_id', 2)
->pluck('item_id')->get();
$users = DB::table('item')
->whereIn('id', $dish1_ids)
->whereNotIn('id', $dish2_ids)
->get();

The Other Article’s Points

SELECT persons.id as parent_id, pets.name as pet_name, children.*
FROM persons
LEFT JOIN children_pivot ON children_pivot.parent_id = persons.id
LEFT JOIN persons as children ON children_pivot.child_id = children.id
LEFT JOIN pets ON pets.owner_id = persons.id
WHERE persons.first_name = 'Arnold'
AND pets.name = 'Fluffy'
Person
.query()
.select('persons.id as parent_id', 'pets.name as pet_name', 'children.*')
.loadEager({
children: true,
pets: true
})
.where('first_name', 'Arnold')
.andWhere('pets.name', 'Fluffy')
$fluffyOwningArnolds = Person::with('children', 'pets')
->where([
['first_name', 'Arnold'],
['pets.name', 'Fluffy']
])->get();

Conceding a Point

await queryInterface.createTable(
"users",
{
id: {
allowNull: false,
primaryKey: true,
},
email: {
allowNull: false,
type: Sequelize.STRING,
},
name: {
allowNull: false,
type: Sequelize.STRING,
},
password: {
allowNull: false,
type: Sequelize.STRING,
},
salt: {
allowNull: true,
type: Sequelize.STRING,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
deletedAt: {
allowNull: true,
type: Sequelize.DATE,
},
},
{
uniqueKeys: {
unique_tag: {
customIndex: true,
fields: ["email"],
},
},
}
);
class User extends Model {
public id!: string;
public name!: string;
public email!: string;
public password!: string;
public salt!: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
public readonly deletedAt!: Date;
}
User.init(
{
name: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: new DataTypes.STRING(),
allowNull: false,
},
password: {
type: new DataTypes.STRING(),
allowNull: false,
},
salt: {
type: new DataTypes.STRING(),
allowNull: false,
},
createdAt: {
type: new DataTypes.DATE(),
allowNull: false,
defaultValue: NOW,
},
updatedAt: {
type: new DataTypes.DATE(),
allowNull: false,
defaultValue: NOW,
},
deletedAt: {
type: new DataTypes.DATE(),
allowNull: true,
defaultValue: null,
},
},
{
tableName: "users",
sequelize,
paranoid: true
}
);
class User extends Model
{
use SoftDeletes;
}
$adult_users = User->isAdult()->get();
const adultUsers = await User.scope('adult').findAll({});
$adult_users = User->agedOver(18)->get();
const adultUsers = await User.scope({method: ['agedOver', 18]}).findAll({});
// Eloquent
$posts = Post->with('comments')->get();
// Sequelize
const posts = await Post.findAll({include: "comments"});
// Eloquent
$posts = Post->with('category', 'comments', 'comments.author')->get();
const posts = await Post.findAll({include: [
{ model: Category, as: "category" },
{
model: Comment,
as: "comments",
include: [{
model: Writer,
as: "author"
}]
}
]});
const posts = await Post.findAll({include: ["category", {
model: Comment,
as: "comments",
include: "author"
}]});

Senior Web Developer based in Bangkok, Thailand. Javascript, Web and Blockchain Developer.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store