我继续在我的实验中取得缓慢但稳定的进展,在运行Ubuntu的Vagrant虚拟机上使用nix-shell设置Rails开发环境。现在我已经有了四个Rails应用程序,使用的是Ruby版本、Rails版本、PostgreSQL版本和MySQL版本的组合,我对此感到非常高兴。
- Ruby v2.5, Rails v5.2.4.4, PostgreSQL v10
- Ruby v2.5, Rails v5.2.4.4, MySQL v5.7
- Ruby v2.6, Rails v6.0.3.4, PostgreSQL v11
- Ruby v2.6, Rails v6.0.3.4, MySQL v8.0

我继续使用bash脚本作为Vagrant的供应者,以可重复的方式完成这些工作,尽管目前的代码比我想的要混乱一些。
创建Rails应用程序
我改进了rails new 的运行方式,以便它能在不同版本的 Ruby 中正常工作。新的方法是基于我在NixOS论坛上提出的一个问题的答案。
围绕着为特定版本的Ruby和Rails提供一个合适的环境来运行rails new ,其中的一些复杂情况提醒我,在我目前的非Nix MacOS设置中,我没有一个特别好的解决方案。
事实上,我倾向于做一些类似于我在Nix中所做的事情,也就是说,我使用rbenv 来切换到相关的Ruby版本,创建一个Gemfile ,其中只包含对我想要的Rails gem版本的引用,运行bundle install ,然后是rails new 。如果有人有更好,更简单的方法,我很想知道。
在这一点上,向你展示四个Rails应用程序中的一个的相关文件可能会有启发。一个bundler.nix ,只用来运行bundle lock ,生成Gemfile.lock 。之前我一直使用bundix 本身来生成Gemfile.lock ,但我无法解决如何为不同版本的Ruby做这件事。
# Gemfile
source 'https://rubygems.org'
gem 'rails', '= 6.0.3.4'
# bundler.nix
with (import <nixpkgs> {});
let
myBundler = bundler.override { ruby = ruby_2_6; };
in
mkShell {
name = "bundler-shell";
buildInputs = [ myBundler ];
}
一个shell.nix ,用来运行bundix ,生成gemset.nix ,并使用这个gemset运行rails new 。
# shell.nix
with (import <nixpkgs> {});
let
env = bundlerEnv {
name = "ruby2.6-rails6.0.3.4-mysql8.0";
ruby = ruby_2_6;
gemdir = ./.;
};
in mkShell { buildInputs = [ env env.wrappedRuby ]; }
在rails应用程序目录中,还有一个bundler.nix (和上面那个完全一样),它同样只用来运行bundle lock ,还有一个shell.nix ,它既用来运行bundix ,又用来提供实际的开发环境,包括所有相关的依赖。
# shell.nix
with (import <nixpkgs> {});
let
env = bundlerEnv {
name = "ruby2.6-rails6.0.3.4-mysql8.0";
ruby = ruby_2_6;
gemdir = ./.;
};
in mkShell {
buildInputs = [ env env.wrappedRuby nodejs yarn mysql80 ];
}
为了处理Rails v5,我还必须处理一件事,就是在最初设置应用程序时,运行rails yarn:install ,而不是Rails v6的rails webpacker:install 。
设置数据库
我在开发环境shell.nix ,为每个Rails应用配置和运行一个数据库服务器实例,加入了一个shellHook。我现在不太确定在进入nix-shell时配置和运行一个数据库是否非常明智。我怀疑用一个单独的脚本来做这件事可能更有意义。
这一次我做了一些改变,以提高应用程序之间的隔离程度。首先,我将每个数据库配置为将其数据存储在Rails应用程序的子目录中,而不是在一个全局位置。其次,我将每个数据库配置为只接受通过unix域套接字的连接,该套接字也存储在Rails应用程序的子目录中。
我通过将Rails应用程序移到Vagrant用户的主目录下,实现了前者。这就避免了我之前在VirtualBox共享目录下的硬链接问题。虽然这意味着Rails应用程序的源代码无法从客户操作系统中获得,但这似乎只是暂时的不便,因为我只是用Vagrant虚拟机来模拟一台新机器。最终我的目标是在本地运行Nix,而完全不使用Vagrant。
我对unix域套接字解决方案特别满意,因为它意味着不需要为每个Rails应用确定一个未使用的端口来通过TCP/IP连接。下面是PostgreSQL和MySQL数据库的shellHook代码。
PostgreSQL
export PGHOST=/home/vagrant/ruby2.6-rails6.0.3.4-postgres11/tmp/postgres
export PGDATA=$PGHOST/data
export PGDATABASE=postgres
export PGLOG=$PGHOST/postgres.log
mkdir -p $PGHOST
if [ ! -d $PGDATA ]; then
initdb --auth=trust --no-locale --encoding=UTF8
fi
if ! pg_ctl status
then
pg_ctl start -l $PGLOG -o "--unix_socket_directories='$PGHOST' --listen_addresses='''"
fi
MySQL
MYSQL_HOME=/home/vagrant/ruby2.6-rails6.0.3.4-mysql8.0/tmp/mysql
MYSQL_DATA=$MYSQL_HOME/data
export MYSQL_UNIX_PORT=$MYSQL_HOME/mysql.sock
mkdir -p $MYSQL_HOME
if [ ! -d $MYSQL_DATA ]; then
mysqld --initialize-insecure --datadir=$MYSQL_DATA
fi
if ! mysqladmin status --user=root
then
mysqld_safe --datadir=$MYSQL_DATA --skip-networking &
while ! mysqladmin status --user=root; do
sleep 1
done
fi
我绝对不是设置数据库的专家,所以如果你能提出任何改进建议,我很想听到你的意见
总结
我对我的工作进展相当满意。我开始觉得自己有了一个坚实的基础,可以使用Nix在同一台机器上使用不同版本的Ruby、Rails、PostgreSQL和MySQL来创建体面的孤立的开发环境。
一个很好的副作用是,操作系统包的依赖性变得更加明确。我不认为需要更多的工作来获得可重复的配置,以便与其他开发者分享,或用于持续集成和/或部署。
像往常一样,源代码可以在GitHub仓库中找到,在README中也有关于如何自己运行它的说明。
接下来的步骤
-
想出一个更好的方法来管理每个数据库实例,即不在shellHook中,可以使用一个单独的脚本(或者可能使用systemd?)
-
使用特定的Ruby补丁版本或当前nix软件包中没有的Ruby次要版本。我很确信这可以通过钉住nix包的版本来实现,或者值得研究一下nix的碎片。
-
使用特定版本的Bundler。我还没有真正研究过这个问题,因为我不确定这是否是一个破绽。
-
调查在这些Rails应用程序中升级一个gem有多难,即重新生成
Gemfile.lock和gemset.nix文件。 -
研究将direnv与nix或lorri 结合使用,在不同的Rails应用目录之间无缝移动,而不必明确进入/退出相关的nix-shell。
-
研究使用Nix以某种方式使Node包在环境中可用,而不是直接使用Yarn,即也自动安装任何操作系统包的依赖。
-
研究使用Nix home-manager或自定义脚本,使其很容易能够为Ruby和Rails的指定版本运行
rails new。
进一步阅读
-
这是一个系列的第三篇文章。
-
Robin Schroer的《开发者的Nix闪电介绍》。
-
Mattia Gheda的nix-shell介绍。
-
Tom Feron的《使用Nix和direnv的简易可重复开发环境》。
-
Marcelo Lazaroni的Nix软件包版本。查找在某个频道中可用的软件包的所有版本,以及你可以下载它的修订版。