Skip to main content

ยท 2 min read

When building out your domain model it is tempting to annotate your entities with @Data.

@Data is a shortcut for @ToString, @EqualsAndHashCode, @Getter on all fields, @Setter on all non-final fields, and @RequiredArgsConstructor 1.

For example:

@Entity
@Data
class Author {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;

private String name;

@OneToMany
private Set<Book> books;

public Set<Book> getBooks() {
/* fetches books from database if books is null */
}
}

Now, lets say you fetch a list of authors from the database and log them. Your logger proceeds to log these authors by calling toString() This will in turn call getBooks() which will do further queries. You have just introduced N+1 queries while fetching author for data you will never use! For this reason, I would mark all associations with @ToString.Exclude

Similar problem arises when you use .equals(other), provided by @EqualsAndHashCode. With entities, equals may be done by comparing id (the primary key). Annotating your primary key attribute with @EqualsAndHashCode.Include, will ensure that only primary key is used for hash code calculation and equals.

caution

Your use-case may vary, write a test for this so others don't assume how .equals(other) is working.

With this advice our entity becomes:

@ToString
@EqualsAndHashCode
@Getter
@Setter
@RequiredArgsConstructor
@Entity
class Author {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@EqualsAndHashCode.Include
private Long id;

private String name;

@ToString.Exclude
@OneToMany
private Set<Book> books;

public Set<Book> getBooks() {
/* fetches books from database if books is null */
}
}

ยท One min read

A common mistake when beginning to use Rails is sprinkling all the models with default scopes specifying order.

class Foo < ActiveRecord::Base
default_scope { order(name: :asc) }
end

This will add order by's to query even when the order does not matter.

Foo.find(123)

Executes the following

SELECT  "foos".* FROM "foos" WHERE "foos"."id" = 123  ORDER BY "foos"."name" ASC LIMIT 1

The best use-case I've found with default scopes is scoping a model to the current tenant in a multi-tenant environment.

However, even that is questionable, as one could just do current_tenant.foos instead. In the controller, specify a scope allowing fetching to be scoped to tenant.

def foo_scope
current_tenant.foos
end