A medida que una aplicación crece en popularidad y uso, necesitarás escalar la aplicación para apoyar a tus nuevos usuarios y sus datos. Una forma en la que tu aplicación puede necesitar escalar es a nivel de base de datos. Rails admite el uso de múltiples bases de datos, por lo que no tienes que almacenar todos tus datos en un solo lugar.
En este momento se admiten las siguientes características:
- Múltiples bases de datos de escritura y una réplica para cada una
- Cambio automático de conexión para el modelo con el que estás trabajando
- Cambio automático entre la base de datos de escritura y la réplica dependiendo del verbo HTTP y escrituras recientes
- Tareas de Rails para crear, eliminar, migrar e interactuar con las múltiples bases de datos
Las siguientes características no están (todavía) soportadas:
- Balanceo de carga de réplicas
1 Configurando tu Aplicación
Aunque Rails intenta hacer la mayor parte del trabajo por ti, todavía hay algunos pasos que deberás seguir para preparar tu aplicación para múltiples bases de datos.
Supongamos que tenemos una aplicación con una sola base de datos de escritura, y necesitamos agregar una nueva base de datos para algunas tablas nuevas que estamos agregando. El nombre de la nueva base de datos será "animals".
config/database.yml
se ve así:
production:
database: my_primary_database
adapter: mysql2
username: root
password: <%= ENV['ROOT_PASSWORD'] %>
Agreguemos una segunda base de datos llamada "animals" y réplicas para ambas bases de datos también. Para hacer esto, necesitamos cambiar nuestro config/database.yml
de una configuración de 2 niveles a una de 3 niveles.
Si se proporciona una clave de configuración primary
, se usará como la configuración "predeterminada". Si no hay una configuración llamada primary
, Rails usará la primera configuración como predeterminada para cada entorno. Las configuraciones predeterminadas usarán los nombres de archivo predeterminados de Rails. Por ejemplo, las configuraciones primarias usarán db/schema.rb
para el archivo de esquema, mientras que todas las otras entradas usarán db/[CONFIGURATION_NAMESPACE]_schema.rb
para el nombre del archivo.
production:
primary:
database: my_primary_database
username: root
password: <%= ENV['ROOT_PASSWORD'] %>
adapter: mysql2
primary_replica:
database: my_primary_database
username: root_readonly
password: <%= ENV['ROOT_READONLY_PASSWORD'] %>
adapter: mysql2
replica: true
animals:
database: my_animals_database
username: animals_root
password: <%= ENV['ANIMALS_ROOT_PASSWORD'] %>
adapter: mysql2
migrations_paths: db/animals_migrate
animals_replica:
database: my_animals_database
username: animals_readonly
password: <%= ENV['ANIMALS_READONLY_PASSWORD'] %>
adapter: mysql2
replica: true
Cuando se usan múltiples bases de datos, hay algunas configuraciones importantes.
Primero, el nombre de la base de datos para primary
y primary_replica
debe ser el mismo porque contienen los mismos datos. Esto también es válido para animals
y animals_replica
.
Segundo, el nombre de usuario para los escritores y las réplicas debe ser diferente, y los permisos de la base de datos del usuario de la réplica deben estar configurados para solo lectura y no escritura.
Cuando se usa una base de datos réplica, necesitas agregar una entrada replica: true
a la réplica en config/database.yml
. Esto se debe a que Rails de otro modo no tiene forma de saber cuál es una réplica y cuál es el escritor. Rails no ejecutará ciertas tareas, como migraciones, contra réplicas.
Finalmente, para nuevas bases de datos de escritura, necesitas establecer la clave migrations_paths
en el directorio donde almacenarás las migraciones para esa base de datos. Veremos más sobre migrations_paths
más adelante en esta guía.
También puedes configurar el archivo de volcado de esquema configurando schema_dump
a un nombre de archivo de esquema personalizado o omitiendo completamente el volcado de esquema configurando schema_dump: false
.
Ahora que tenemos una nueva base de datos, configuremos el modelo de conexión.
La réplica de la base de datos primaria puede configurarse en ApplicationRecord
de esta manera:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :primary, reading: :primary_replica }
end
Si usas una clase con un nombre diferente para tu registro de aplicación, necesitas establecer primary_abstract_class
en su lugar, para que Rails sepa con qué clase ActiveRecord::Base
debe compartir una conexión.
class PrimaryApplicationRecord < ActiveRecord::Base
primary_abstract_class
connects_to database: { writing: :primary, reading: :primary_replica }
end
En ese caso, las clases que se conectan a primary
/primary_replica
pueden heredar de tu clase abstracta primaria como lo hacen las aplicaciones estándar de Rails con ApplicationRecord
:
class Person < PrimaryApplicationRecord
end
Por otro lado, necesitamos configurar nuestros modelos persistidos en la base de datos "animals":
class AnimalsRecord < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :animals, reading: :animals_replica }
end
Esos modelos deben heredar de esa clase abstracta común:
class Car < AnimalsRecord
# Habla automáticamente con la base de datos animals.
end
Por defecto, Rails espera que los roles de la base de datos sean writing
y reading
para la primaria y la réplica respectivamente. Si tienes un sistema heredado, es posible que ya tengas roles configurados que no quieras cambiar. En ese caso, puedes establecer un nuevo nombre de rol en la configuración de tu aplicación.
config.active_record.writing_role = :default
config.active_record.reading_role = :readonly
Es importante conectarse a tu base de datos en un solo modelo y luego heredar de ese modelo para las tablas en lugar de conectar múltiples modelos individuales a la misma base de datos. Los clientes de bases de datos tienen un límite en el número de conexiones abiertas que puede haber, y si haces esto, multiplicará el número de conexiones que tienes ya que Rails usa el nombre de la clase del modelo para el nombre de especificación de la conexión.
Ahora que tenemos el config/database.yml
y el nuevo modelo configurado, es hora de crear las bases de datos. Rails viene con todos los comandos que necesitas para usar múltiples bases de datos.
Puedes ejecutar bin/rails --help
para ver todos los comandos que puedes ejecutar. Deberías ver lo siguiente:
$ bin/rails --help
...
db:create # Crea la base de datos desde DATABASE_URL o config/database.yml para el ...
db:create:animals # Crea la base de datos animals para el entorno actual
db:create:primary # Crea la base de datos primary para el entorno actual
db:drop # Elimina la base de datos desde DATABASE_URL o config/database.yml para el cu...
db:drop:animals # Elimina la base de datos animals para el entorno actual
db:drop:primary # Elimina la base de datos primary para el entorno actual
db:migrate # Migra la base de datos (opciones: VERSION=x, VERBOSE=false, SCOPE=blog)
db:migrate:animals # Migra la base de datos animals para el entorno actual
db:migrate:primary # Migra la base de datos primary para el entorno actual
db:migrate:status # Muestra el estado de las migraciones
db:migrate:status:animals # Muestra el estado de las migraciones para la base de datos animals
db:migrate:status:primary # Muestra el estado de las migraciones para la base de datos primary
db:reset # Elimina y recrea todas las bases de datos desde su esquema para el entorno actual y carga las semillas
db:reset:animals # Elimina y recrea la base de datos animals desde su esquema para el entorno actual y carga las semillas
db:reset:primary # Elimina y recrea la base de datos primary desde su esquema para el entorno actual y carga las semillas
db:rollback # Retrocede el esquema a la versión anterior (especifica pasos con STEP=n)
db:rollback:animals # Retrocede la base de datos animals para el entorno actual (especifica pasos con STEP=n)
db:rollback:primary # Retrocede la base de datos primary para el entorno actual (especifica pasos con STEP=n)
db:schema:dump # Crea un archivo de esquema de base de datos (ya sea db/schema.rb o db/structure.sql ...
db:schema:dump:animals # Crea un archivo de esquema de base de datos (ya sea db/schema.rb o db/structure.sql ...
db:schema:dump:primary # Crea un archivo db/schema.rb que es portátil contra cualquier BD admitida ...
db:schema:load # Carga un archivo de esquema de base de datos (ya sea db/schema.rb o db/structure.sql ...
db:schema:load:animals # Carga un archivo de esquema de base de datos (ya sea db/schema.rb o db/structure.sql ...
db:schema:load:primary # Carga un archivo de esquema de base de datos (ya sea db/schema.rb o db/structure.sql ...
db:setup # Crea todas las bases de datos, carga todos los esquemas e inicializa con los datos de semillas (usa db:reset para también eliminar todas las bases de datos primero)
db:setup:animals # Crea la base de datos animals, carga el esquema e inicializa con los datos de semillas (usa db:reset:animals para también eliminar la base de datos primero)
db:setup:primary # Crea la base de datos primary, carga el esquema e inicializa con los datos de semillas (usa db:reset:primary para también eliminar la base de datos primero)
...
Ejecutar un comando como bin/rails db:create
creará tanto las bases de datos primary como animals. Ten en cuenta que no hay un comando para crear los usuarios de la base de datos, y deberás hacer eso manualmente para apoyar a los usuarios de solo lectura para tus réplicas. Si deseas crear solo la base de datos animals, puedes ejecutar bin/rails db:create:animals
.
2 Conectarse a Bases de Datos sin Gestionar Esquemas y Migraciones
Si deseas conectarte a una base de datos externa sin ninguna tarea de gestión de bases de datos, como gestión de esquemas, migraciones, semillas, etc., puedes configurar la opción de configuración por base de datos database_tasks: false
. Por defecto está configurado en true.
production:
primary:
database: my_database
adapter: mysql2
animals:
database: my_animals_database
adapter: mysql2
database_tasks: false
3 Generadores y Migraciones
Las migraciones para múltiples bases de datos deben vivir en sus propias carpetas con el prefijo del nombre de la clave de la base de datos en la configuración.
También necesitas establecer migrations_paths
en las configuraciones de la base de datos para decirle a Rails dónde encontrar las migraciones.
Por ejemplo, la base de datos animals
buscaría migraciones en el directorio db/animals_migrate
y primary
buscaría en db/migrate
. Los generadores de Rails ahora toman una opción --database
para que el archivo se genere en el directorio correcto. El comando se puede ejecutar de la siguiente manera:
$ bin/rails generate migration CreateDogs name:string --database animals
Si estás usando generadores de Rails, los generadores de scaffold y modelo crearán la clase abstracta por ti. Simplemente pasa la clave de la base de datos a la línea de comando.
$ bin/rails generate scaffold Dog name:string --database animals
Se creará una clase con el nombre camelizado de la base de datos y Record
. En este ejemplo, la base de datos es "animals", por lo que terminamos con AnimalsRecord
:
class AnimalsRecord < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :animals }
end
El modelo generado heredará automáticamente de AnimalsRecord
.
class Dog < AnimalsRecord
end
NOTA: Dado que Rails no sabe qué base de datos es la réplica de tu escritor, necesitarás agregar esto a la clase abstracta después de que hayas terminado.
Rails solo generará AnimalsRecord
una vez. No será sobrescrito por nuevos scaffolds ni eliminado si el scaffold es eliminado.
Si ya tienes una clase abstracta y su nombre difiere de AnimalsRecord
, puedes pasar la opción --parent
para indicar que deseas una clase abstracta diferente:
$ bin/rails generate scaffold Dog name:string --database animals --parent Animals::Record
Esto omitirá la generación de AnimalsRecord
ya que has indicado a Rails que deseas usar una clase padre diferente.
4 Activando el Cambio Automático de Roles
Finalmente, para usar la réplica de solo lectura en tu aplicación, necesitarás activar el middleware para el cambio automático.
El cambio automático permite que la aplicación cambie del escritor a la réplica o de la réplica al escritor según el verbo HTTP y si hubo una escritura reciente por parte del usuario que realiza la solicitud.
Si la aplicación recibe una solicitud POST, PUT, DELETE o PATCH, la aplicación escribirá automáticamente en la base de datos del escritor. Si la solicitud no es uno de esos métodos, pero la aplicación realizó recientemente una escritura, también se usará la base de datos del escritor. Todas las demás solicitudes usarán la base de datos de la réplica.
Para activar el middleware de cambio automático de conexión, puedes ejecutar el generador de intercambio automático:
$ bin/rails g active_record:multi_db
Y luego descomentar las siguientes líneas:
Rails.application.configure do
config.active_record.database_selector = { delay: 2.seconds }
config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
end
Rails garantiza "leer tu propia escritura" y enviará tu solicitud GET o HEAD al escritor si está dentro de la ventana de delay
. Por defecto, el retraso se establece en 2 segundos. Deberías cambiar esto según tu infraestructura de base de datos. Rails no garantiza "leer una escritura reciente" para otros usuarios dentro de la ventana de retraso y enviará solicitudes GET y HEAD a las réplicas a menos que hayan escrito recientemente.
El cambio automático de conexión en Rails es relativamente primitivo y deliberadamente no hace mucho. El objetivo es un sistema que demuestre cómo hacer el cambio automático de conexión que sea lo suficientemente flexible como para ser personalizable por los desarrolladores de aplicaciones.
La configuración en Rails te permite cambiar fácilmente cómo se realiza el cambio y en qué parámetros se basa. Supongamos que deseas usar una cookie en lugar de una sesión para decidir cuándo cambiar las conexiones. Puedes escribir tu propia clase:
class MyCookieResolver < ActiveRecord::Middleware::DatabaseSelector::Resolver
def self.call(request)
new(request.cookies)
end
def initialize(cookies)
@cookies = cookies
end
attr_reader :cookies
def last_write_timestamp
self.class.convert_timestamp_to_time(cookies[:last_write])
end
def update_last_write_timestamp
cookies[:last_write] = self.class.convert_time_to_timestamp(Time.now)
end
def save(response)
end
end
Y luego pasarla al middleware:
config.active_record.database_selector = { delay: 2.seconds }
config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
config.active_record.database_resolver_context = MyCookieResolver
5 Usando el Cambio Manual de Conexión
Hay algunos casos en los que es posible que desees que tu aplicación se conecte a un escritor o a una réplica y el cambio automático de conexión no sea adecuado. Por ejemplo, puedes saber que para una solicitud en particular siempre deseas enviar la solicitud a una réplica, incluso cuando estás en una ruta de solicitud POST.
Para hacer esto, Rails proporciona un método connected_to
que cambiará a la conexión que necesitas.
ActiveRecord::Base.connected_to(role: :reading) do
# Todo el código en este bloque estará conectado al rol de lectura.
end
El "rol" en la llamada connected_to
busca las conexiones que están conectadas en ese controlador de conexión (o rol). El controlador de conexión reading
mantendrá todas las conexiones que se conectaron a través de connects_to
con el nombre de rol de reading
.
Ten en cuenta que connected_to
con un rol buscará una conexión existente y cambiará usando el nombre de especificación de conexión. Esto significa que si pasas un rol desconocido como connected_to(role: :nonexistent)
, obtendrás un error que dice ActiveRecord::ConnectionNotEstablished (No connection pool for 'ActiveRecord::Base' found for the 'nonexistent' role.)
Si deseas que Rails garantice que cualquier consulta realizada sea de solo lectura, pasa prevent_writes: true
. Esto solo previene que las consultas que parecen escrituras se envíen a la base de datos. También debes configurar tu base de datos réplica para que funcione en modo de solo lectura.
ActiveRecord::Base.connected_to(role: :reading, prevent_writes: true) do
# Rails verificará cada consulta para asegurarse de que sea una consulta de lectura.
end
6 Fragmentación Horizontal
La fragmentación horizontal es cuando divides tu base de datos para reducir el número de filas en cada servidor de base de datos, pero mantienes el mismo esquema en "fragmentos". Esto se llama comúnmente fragmentación "multi-tenant".
La API para soportar la fragmentación horizontal en Rails es similar a la API de múltiples bases de datos / fragmentación vertical que ha existido desde Rails 6.0.
Los fragmentos se declaran en la configuración de tres niveles de esta manera:
production:
primary:
database: my_primary_database
adapter: mysql2
primary_replica:
database: my_primary_database
adapter: mysql2
replica: true
primary_shard_one:
database: my_primary_shard_one
adapter: mysql2
migrations_paths: db/migrate_shards
primary_shard_one_replica:
database: my_primary_shard_one
adapter: mysql2
replica: true
primary_shard_two:
database: my_primary_shard_two
adapter: mysql2
migrations_paths: db/migrate_shards
primary_shard_two_replica:
database: my_primary_shard_two
adapter: mysql2
replica: true
Los modelos se conectan con la API connects_to
a través de la clave shards
:
class ApplicationRecord < ActiveRecord::Base
primary_abstract_class
connects_to database: { writing: :primary, reading: :primary_replica }
end
class ShardRecord < ApplicationRecord
self.abstract_class = true
connects_to shards: {
shard_one: { writing: :primary_shard_one, reading: :primary_shard_one_replica },
shard_two: { writing: :primary_shard_two, reading: :primary_shard_two_replica }
}
end
Si estás usando fragmentos, asegúrate de que tanto migrations_paths
como schema_dump
permanezcan sin cambios para todos los fragmentos. Al generar una migración, puedes pasar la opción --database
y usar uno de los nombres de fragmento. Dado que todos establecen la misma ruta, no importa cuál elijas.
$ bin/rails g scaffold Dog name:string --database primary_shard_one
Luego, los modelos pueden cambiar de fragmento manualmente a través de la API connected_to
. Si usas fragmentación, se deben pasar tanto un role
como un shard
:
ActiveRecord::Base.connected_to(role: :writing, shard: :default) do
@id = Person.create! # Crea un registro en el fragmento llamado ":default"
end
ActiveRecord::Base.connected_to(role: :writing, shard: :shard_one) do
Person.find(@id) # No puede encontrar el registro, no existe porque fue creado
# en el fragmento llamado ":default".
end
La API de fragmentación horizontal también soporta réplicas de lectura. Puedes cambiar el rol y el fragmento con la API connected_to
.
ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one) do
Person.first # Busca el registro desde la réplica de lectura del fragmento uno.
end
7 Activando el Cambio Automático de Fragmentos
Las aplicaciones pueden cambiar automáticamente de fragmento por solicitud usando el middleware proporcionado.
El middleware ShardSelector
proporciona un marco para cambiar automáticamente de fragmento. Rails proporciona un marco básico para determinar a qué fragmento cambiar y permite que las aplicaciones escriban estrategias personalizadas para cambiar si es necesario.
ShardSelector
toma un conjunto de opciones (actualmente solo se admite lock
) que pueden ser usadas por el middleware para alterar el comportamiento. lock
es true por defecto y prohibirá que la solicitud cambie de fragmento una vez dentro del bloque. Si lock
es false, se permitirá el cambio de fragmento. Para la fragmentación basada en inquilinos, lock
siempre debe ser true para evitar que el código de la aplicación cambie erróneamente entre inquilinos.
El mismo generador que el selector de bases de datos puede usarse para generar el archivo para el cambio automático de fragmento:
$ bin/rails g active_record:multi_db
Luego, en el config/initializers/multi_db.rb
generado, descomenta lo siguiente:
Rails.application.configure do
config.active_record.shard_selector = { lock: true }
config.active_record.shard_resolver = ->(request) { Tenant.find_by!(host: request.host).shard }
end
Las aplicaciones deben proporcionar el código para el resolver ya que depende de modelos específicos de la aplicación. Un ejemplo de resolver se vería así:
config.active_record.shard_resolver = ->(request) {
subdomain = request.subdomain
tenant = Tenant.find_by_subdomain!(subdomain)
tenant.shard
}
8 Cambio Granular de Conexión a la Base de Datos
A partir de Rails 6.1, es posible cambiar conexiones para una base de datos en lugar de todas las bases de datos globalmente.
Con el cambio granular de conexión a la base de datos, cualquier clase de conexión abstracta podrá cambiar conexiones sin afectar a otras conexiones. Esto es útil para cambiar tus consultas de AnimalsRecord
para leer desde la réplica mientras aseguras que tus consultas de ApplicationRecord
vayan a la primaria.
AnimalsRecord.connected_to(role: :reading) do
Dog.first # Lee desde animals_replica.
Person.first # Lee desde primary.
end
También es posible cambiar conexiones granularmente para fragmentos.
AnimalsRecord.connected_to(role: :reading, shard: :shard_one) do
# Leerá desde shard_one_replica. Si no existe una conexión para shard_one_replica,
# se levantará un error de ConnectionNotEstablished.
Dog.first
# Leerá desde el escritor primary.
Person.first
end
Para cambiar solo el clúster de base de datos primario usa ApplicationRecord
:
ApplicationRecord.connected_to(role: :reading, shard: :shard_one) do
Person.first # Lee desde primary_shard_one_replica.
Dog.first # Lee desde animals_primary.
end
ActiveRecord::Base.connected_to
mantiene la capacidad de cambiar conexiones globalmente.
8.1 Manejo de Asociaciones con Uniones entre Bases de Datos
A partir de Rails 7.0+, Active Record tiene una opción para manejar asociaciones que realizarían una unión entre múltiples bases de datos. Si tienes una asociación has many through o has one through que deseas deshabilitar la unión y realizar 2 o más consultas, pasa la opción disable_joins: true
.
Por ejemplo:
class Dog < AnimalsRecord
has_many :treats, through: :humans, disable_joins: true
has_many :humans
has_one :home
has_one :yard, through: :home, disable_joins: true
end
class Home
belongs_to :dog
has_one :yard
end
class Yard
belongs_to :home
end
Anteriormente, llamar a @dog.treats
sin disable_joins
o @dog.yard
sin disable_joins
levantaría un error porque las bases de datos no pueden manejar uniones entre clústeres. Con la opción disable_joins
, Rails generará múltiples consultas select para evitar intentar unir entre clústeres. Para la asociación anterior, @dog.treats
generaría el siguiente SQL:
SELECT "humans"."id" FROM "humans" WHERE "humans"."dog_id" = ? [["dog_id", 1]]
SELECT "treats".* FROM "treats" WHERE "treats"."human_id" IN (?, ?, ?) [["human_id", 1], ["human_id", 2], ["human_id", 3]]
Mientras que @dog.yard
generaría el siguiente SQL:
SELECT "home"."id" FROM "homes" WHERE "homes"."dog_id" = ? [["dog_id", 1]]
SELECT "yards".* FROM "yards" WHERE "yards"."home_id" = ? [["home_id", 1]]
Hay algunas cosas importantes a tener en cuenta con esta opción:
- Puede haber implicaciones de rendimiento ya que ahora se realizarán dos o más consultas (dependiendo de la asociación) en lugar de una unión. Si el select para
humans
devolviera un alto número de IDs, el select paratreats
podría enviar demasiados IDs. - Dado que ya no estamos realizando uniones, una consulta con un orden o límite ahora se ordena en memoria ya que el orden de una tabla no puede aplicarse a otra tabla.
- Esta configuración debe agregarse a todas las asociaciones donde desees que se deshabiliten las uniones. Rails no puede adivinar esto por ti porque la carga de asociaciones es perezosa, para cargar
treats
en@dog.treats
, Rails ya necesita saber qué SQL debe generarse.
8.2 Caché de Esquema
Si deseas cargar una caché de esquema para cada base de datos, debes establecer schema_cache_path
en cada configuración de base de datos y establecer config.active_record.lazily_load_schema_cache = true
en tu configuración de aplicación. Ten en cuenta que esto cargará la caché de manera perezosa cuando se establezcan las conexiones de la base de datos.
9 Advertencias
9.1 Balanceo de Carga de Réplicas
Rails no soporta el balanceo de carga automático de réplicas. Esto es muy dependiente de tu infraestructura. Podríamos implementar un balanceo de carga básico y primitivo en el futuro, pero para una aplicación a escala, esto debería ser algo que tu aplicación maneje fuera de Rails.
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor contribuye si ves algún error tipográfico o errores fácticos. Para comenzar, puedes leer nuestra sección de contribuciones a la documentación.
También puedes encontrar contenido incompleto o cosas que no están actualizadas. Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar Guías Edge primero para verificar si los problemas ya están resueltos o no en la rama principal. Revisa las Guías de Ruby on Rails para estilo y convenciones.
Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor abre un issue.
Y por último, pero no menos importante, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.