This chapter explains refactoring techniques and cautionary points in Model.
Initial value
initialize
If you want to set a default value to Model, you can write as follows. This is a function of Ruby itself. (super
is for overriding ActiveRecord.)
def initialize(args = {})
super
self.name = 'New user'
end
after_initialize
is also called when the registered record initialized, but I think this is little or no used. Similar method after_find
is little or no used too.
*
It doesn't mean you mustn't use it.
DB default
It can be used default
option in create_table
block to set default values to DB values as follows. This will be the initial value when Model.new
.
create_table :users do |t|
t.string :name, default: 'New user'
t.integer :status, default: 1
end
You can use this for the value that are unlikely to change in the future.
Be careful of Callbacks using
Callbacks before_save
and after_commit
etc, are automatically called at save
. Since Callbacks can easily become complicated and will be called at unintended times, it is necessary to consider whether it should really be used.
In major methods, callbacks are called at these times.
create
,create!
save
,save!
update
,update!
destroy
,destroy!
And basically, these methods should be used when INSERT
, UPDATE
and DETETE
. If other methods are used, Validations and Callbacks may not be called. Therefore, it is necessary to check if they are not needed.
For example, if you want to use update_all
, make sure to check if you really can skip Validations and Callbacks.
Checking input values should bring together to Validations.
Validations are used to ensure that invalid values are not INSERT/UPDATE-ed into the DB. Validations are automatically called by the methods introduced in the previous section, but can also be called manually by these methods.
valid?
,invalid?
Validations are a quite good standard feature and well documented.
Rails Guides - Active Record Validations
Not that not table column values can be checked by other methods (such as private methods in Service class).
Base Validations
class User < ApplicationRecord
validates :name, presence: true
end
Various value checks can be used, such as null check, numbers, in range, case, etc.
Automatic generation Validation code
Validations can be generated automatically by using gems such as schema_validations
and pretty_validation
.
This gems are a little old, but they are for development so you don't care too much about them. If you are concerned about them, you can remove them from Gemfile
after use.
Needless to say, you should check the generated code.
Custom Validations
Use custom validations for complex value checks that are not possible with normal validations.
class User < ApplicationRecord
validates :name, foo: true
end
This will call FooValidator
. This is a standard feature in Rails. It can be put in app/validators
and separate them from app/models
. If it is difficult to create a new directory in app
, you can create a directory app/models/{table name}
and put it there.
- To validate single column,
- Use
validates
in the Model - Create
Activemodel::EachValidator
class and definevalidate_each
method in it.
- Use
- To validate multiple columns or multiple child models,
- Use
validates_with
in the Model - Create
Activemodel::Validator
class and definevalidate
method in it.
- Use
- If you doun't separate file,
- Use
validate
and define the method in the Model.
- Use
Avoiding validations
save(validate: false)
can be used to skip Validations but, it should basically not be used except during development or special operations on the admin system.
SQL related tips
The first thing to keep in mind is that neither Rails nor MySQL are good at handling subqueries. So unless you have a reason to do so, do not use subqueries, but rather execute SQL multiple times to retrieve data. Of course, the records should be narrowed down as much as possible by index and WHERE
conditions.
all
Model.all
class is ActiveRecord_Relation
(so-called scope
), not Array
. It is not yet instantiated and no SQL has been executed.
ActiveRecord_Relation
It is convenient and Rails-like to use this class. It can be used Array
methods. SQL will be executed and instantiated when Array
method is used.
select
It tends not to be used very often except when using GROUP
. It tends not to be used much except when using GROUP
. In fact, it may be better to have it, but it is often omitted. For APIs, use a serializer to pick columns.
where
Basically, written in the following two ways.
- Placeholder
where("created_at > ?" , date)
- Hash
where(created_at: date)
Direct embedding of user-input strings as follows is not secure because it is not escaped.
where("created_at > #{params[:date]}")
group
Returns as a hash. It is not an intuitive(simple) method, so you can want to consider other ways.
*
It does not mean you must not use it.
order
Use order(id: :desc)
for descending order.
default_scope
It will include SQL such as where
and order
by default. It is also the default value for new
when it is used for where
.
If you are concerned about unintended behavior, you can use only the default order for convenience.
merge
It is a convenience method to cleanly merge scopes that has already been defined in joining Models.
Example.
class Post < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :post
scope :draft, -> { where(status: :draft) }
end
Post.joins(:comments).merge(Comment.draft).
Result SQL.
SELECT (omitted) WHERE comments.status = 'draft'
Checking SQL
It can be used to_sql
, but it does not execute SQL and just output a SQL string. So, I recommend to use first
or to_a
that will execute SQL, and check its logs.
For checking the effectiveness of index, it can be used .explain
.
How to use joins
, includes
, preload
, eager_load
To keep it simple,
- For INNER JOIN, use
joins
. - For LEFT JOIN, use
preload
.- If have a problem with it, use
eager_load
- If have a problem with it, use
To be precise, Preloads
are not execute LEFT JOIN
, but rather execute another SQL.
It is not prohibited to use includes
, but it requires a deeper understanding of the behavior and it may be a little difficult to use.
About the N+1 problem
The N+1 is a performance problem that can occur in Rails applications. There is no detailed explanation here, so if you don't know it, please Google it and learn it.
Since it is difficult to detect this problem perfectly by oneself, it is better to use the gem bullet
to detect them automatically.
arel_table
It will be able to create OR
and complex SQL using only ActiveRecord functions, but it tends not to be recommended in terms of code readability.
I have seen some unreadable code using this, so I don't think it is necessary to use it. I recommend using String, Heredoc or another SQL instead of arel_table
.
Some people may think "writing SQL in String is not a clean", but it is fine to recognize "writing SQL in String tends to make it not clean". Don't have to deny it strongly.
Enumeration
Use enum
to have numerical status values, such as a status
column. Also, String value can be used for DB values so that they can be easily recognized.
This feature since Rails 4.1, so older systems may have other implementation or use the gem enumerize
.
How to use
Define with _prefix: true
is recommended like the following.
class User < ApplicationRecord
enum status: { draft: 0, public: 1, private: 2 }, _prefix: true
end
In this case, user.status
to get 'draft'
but it is stored 0
as a number in DB.
Also, these methods will also be available.
user.status_draft?
check the status.user.status_draft!
update the status.User.statuses
get the statuses. (Class method)
Concerning
to organize code
Module#concerning
can be used to organize code and I recommend this for refactoring.
Ruby on Rails API - Module::Concerning
This is same as including the module. Writing within a file prevents excessive file splitting, and by making groups of functions into blocks, readability is increased.
It can be used anywhere, not only in Models. Note that it is not related to models/concerns
directly.
Namespacing for Model
In some cases, it is wanted to create a namespace to organize what the model files are related to.
When model name ≠ table name
, the following can be used.
table_name_prefix
for the Models under the namespace.table_name
for a specific Model.
Ruby on Rails API - table_name_prefix=
Using Namespace is a little difficult to use
So, I don't use it, but it does not mean that you must not use Namespace. Using well, it may be possible to organize the Models nicely. If you feel stuck to implement, you can implement without Namespace first, and then try to add it later.
Devises to manage Namespace
- Prefix child table names with the parent table name
Example.
If there are three tables related to the company,
- Companies
- Company's employees
- Employees' jobs
then
companies
-company_staffs
-company_staff_jobs
could be good. This is easier to understand in terms of relevance than
companies
-employees
-jobs
In some cases, it may be better not to use this rule because it makes it difficult to understand the entity (what the table manages), but basically, first consider possible to use this rule.
- Alias for associations
For the following definition in company.rb
,
has_many :company_staffs
and add the following.
has_many :staffs, class_name: :company_staffs
By doing so, the redundant description company.company_staffs
can be written as company.staffs
. Note that there may be difficult to read in some cases, so be careful not to just add aliases without thinking.
How to use concerns
directory in app/models
Putting all Model's modules into app/models/concerns/
will become unmanageable. So, it is needed to put only common functions into it.
For modules used only for a specific model, it is recommended to create the app/models/{table name}/
directory and place them there instead of concerns. For example, use app/models/users/
to seperate the functions from the User Model.
Some people don't want to use concerns
, but this rule is fine to use it.
Naming with able
suffix
Module name
Defining the module name with able
makes it easier to understand what kind of behavior is being added.
It is more intuitive include Taggable
than include TagModule
. This is not Ruby or Rails coding convention, but a convention. If you have more intuitive and understandable, you can arrange it.
Method and column names
Also, it can be used for method and column names.
It may be more intuitive taggable?
than can_tag?
or can_use_tag?
.
*
?
can't be used in table column name.
Introduction convenience some methods for checking Model status
model.new_record?
check if new record.model.persisted?
check if exists in the DB.model.changed?
check if record values changed.model.{column name}_changed?
check if a specific column changed.model.changes
get before and after values as a Hash.model.{column name}_was
get a value before the change.