使用nix-shell的多个Rails开发环境的详细步骤

327 阅读6分钟

我继续在我的实验中取得缓慢但稳定的进展,在运行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

Four Rails apps

我继续使用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.lockgemset.nix 文件。

  • 研究将direnv与nixlorri 结合使用,在不同的Rails应用目录之间无缝移动,而不必明确进入/退出相关的nix-shell。

  • 研究使用Nix以某种方式使Node包在环境中可用,而不是直接使用Yarn,即也自动安装任何操作系统包的依赖。

  • 研究使用Nix home-manager或自定义脚本,使其很容易能够为Ruby和Rails的指定版本运行rails new

进一步阅读