<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title>Lexin Gong</title>
  <link href="https://gonglexin.com/atom.xml" rel="self"/>
  <link href="https://gonglexin.com/"/>
  <updated>2026-05-05T14:34:34+00:00</updated>
  <id>https://gonglexin.com/</id>
  <author>
    <name>gonglexin</name>
    <email>gonglexin@gmail.com</email>
  </author>

  
  <entry>
    <title>Creating Custom Stickers/Avatars from Your Webcam - Part 1</title>
    <link href="https://gonglexin.com/posts/poker-face-part-1"/>
    <updated>2024-03-04T00:00:00+00:00</updated>
    <id>https://gonglexin.com/posts/poker-face-part-1</id>
    <content type="html">&lt;p&gt;I’ve just made my new project,
&lt;a href=&quot;https://poker-face.gonglexin.com&quot;&gt;Poker Face&lt;/a&gt;,
available as &lt;a href=&quot;https://github.com/gonglexin/poker_face&quot;&gt;open-source&lt;/a&gt;.
This project is crafted with Elixir and Phoenix LiveView.
Its core features allow you to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Transform your webcam feed into stickers or avatars.&lt;/li&gt;
  &lt;li&gt;Pose questions about the camera images to GPT.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out the introduction video here: &lt;a href=&quot;https://www.youtube.com/watch?v=EFUJFZRQNFk&quot;&gt;&lt;img src=&quot;https://img.youtube.com/vi/EFUJFZRQNFk/0.jpg&quot; alt=&quot;Poker Face intro video&quot; /&gt;&lt;/a&gt;
In this blog series, I’ll guide you through every detail of the implementation.
Welcome to Part 1.&lt;/p&gt;

&lt;p&gt;In this very first part, we’ll tackle a couple of key steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Opening the camera and capturing the video frame.&lt;/li&gt;
  &lt;li&gt;Sending the frame to a LiveView process and interfacing it with Gemini or OpenAI.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;initial-project&quot;&gt;Initial Project&lt;/h3&gt;

&lt;p&gt;Let’s kick things off with setting up the project.
Navigate to your desired directory and create a new phoenix project:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;mix phx.new poker_face
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;From the outset, my goal was to build this entire project in LiveView.
I proceeded by adding &lt;code&gt;face_live.ex&lt;/code&gt; and &lt;code&gt;face_live.html.heex&lt;/code&gt;,
then configuring the router to point to this LiveView component.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-elixir&quot;&gt;defmodule PokerFaceWeb.FaceLive do
  use PokerFaceWeb, :live_view

  @impl true
  def mount(_prams, _session, socket) do
    {:ok, assign(socket, :photo_info, nil)}
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&quot;language-elixir&quot;&gt;&amp;lt;div&amp;gt;
  &amp;lt;.header&amp;gt;
    Poker Face
    &amp;lt;:subtitle&amp;gt;&amp;lt;/:subtitle&amp;gt;
  &amp;lt;/.header&amp;gt;
  &amp;lt;div class=&quot;grid grid-cols-2 gap-x-2&quot;&amp;gt;
    &amp;lt;div id=&quot;user_photo&quot; class=&quot;rounded shadow-lg p-2 flex flex-col gap-y-2&quot; phx-hook=&quot;Camera&quot;&amp;gt;
      &amp;lt;button
        id=&quot;startCamera&quot;
        class=&quot;p-4 w-full h-full disabled:cursor-not-allowed&quot;
      &amp;gt;
        &amp;lt;p class=&quot;select-none flex items-center gap-2&quot;&amp;gt;
          &amp;lt;.icon name=&quot;hero-camera&quot; /&amp;gt;Take a photo with your webcam
        &amp;lt;/p&amp;gt;
      &amp;lt;/button&amp;gt;
      &amp;lt;div class=&quot;flex flex-col gap-y-2&quot;&amp;gt;
        &amp;lt;video id=&quot;video&quot; class=&quot;hidden&quot; playsinline autoplay&amp;gt;&amp;lt;/video&amp;gt;
        &amp;lt;img id=&quot;photo&quot; /&amp;gt;

        &amp;lt;div id=&quot;buttonGroup&quot; class=&quot;hidden flex flex-col gap-y-2 justify-center&quot;&amp;gt;
          &amp;lt;button
            id=&quot;takePhoto&quot;
            class=&quot;rounded-full py-2 border border-black hover:bg-black hover:text-white&quot;
          &amp;gt;
            Take Photo
          &amp;lt;/button&amp;gt;
          &amp;lt;button
            id=&quot;stopCamera&quot;
            class=&quot;rounded-full py-2 border border-black hover:bg-black hover:text-white&quot;
          &amp;gt;
            Cancel
          &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;canvas id=&quot;canvas&quot; class=&quot;hidden&quot;&amp;gt;&amp;lt;/canvas&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div id=&quot;poker_photo&quot; class=&quot;rounded shadow-lg p-2&quot;&amp;gt;
      &amp;lt;p class=&quot;text-center text-lg&quot;&amp;gt;&amp;lt;%= @photo_info %&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&quot;language-elixir&quot;&gt;defmodule PokerFaceWeb.Router do
  use PokerFaceWeb, :router

  ...

  scope &quot;/&quot;, PokerFaceWeb do
    pipe_through :browser

    live &quot;/&quot;, FaceLive
  end

  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once you run &lt;code&gt;mix phx.server&lt;/code&gt;, your page should like this: &lt;img src=&quot;/assets/images/poker-face/1.png&quot; alt=&quot;inital-page&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The variable &lt;code&gt;@photo_info&lt;/code&gt; is designated to display the responses
from GPT/Gemini once we’ve sent an video frame to be analyzed.&lt;/p&gt;

&lt;h3 id=&quot;webcam&quot;&gt;Webcam&lt;/h3&gt;

&lt;p&gt;Now, let’s delve into the webcam functionality.
To be honest, webcams were somewhat foreign to me.
So, I turned to GPT for assistance on how to implement it,
and it provided a solid, functional code example.
All I had to do was integrate the JavaScript into a Phoenix Hook.
&lt;img src=&quot;/assets/images/poker-face/2.png&quot; alt=&quot;ask_gpt&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Add this code to &lt;code&gt;app.js&lt;/code&gt; with any necessary modifications:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let Hooks = {}

Hooks.Camera = {
  mounted() {
    const startCamera = document.getElementById(&apos;startCamera&apos;);
    const video = document.getElementById(&apos;video&apos;);
    const takePhoto = document.getElementById(&apos;takePhoto&apos;);
    const stopCamera = document.getElementById(&apos;stopCamera&apos;);
    const canvas = document.getElementById(&apos;canvas&apos;);
    const buttonGroup = document.getElementById(&apos;buttonGroup&apos;);
    const photo = document.getElementById(&quot;photo&quot;);
    let stream = null;

    // Start the camera when button is clicked
    startCamera.addEventListener(&apos;click&apos;, async () =&amp;gt; {
      stream = await navigator.mediaDevices.getUserMedia({ video: true });
      video.srcObject = stream;
      video.classList.remove(&apos;hidden&apos;);
      buttonGroup.classList.remove(&apos;hidden&apos;);
      startCamera.classList.add(&apos;hidden&apos;);
      photo.src = &quot;&quot;;
    });

    // Take a photo
    takePhoto.addEventListener(&apos;click&apos;, () =&amp;gt; {
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      canvas.getContext(&apos;2d&apos;).drawImage(video, 0, 0);
      const imageDataURL = canvas.toDataURL(&apos;image/png&apos;);
      photo.src = imageDataURL;

      // push to the back-end and interact with Gemini/GPT
      this.pushEvent(&quot;new_photo&quot;, { photo: imageDataURL })

      // buttonGroup.classList.add(&apos;hidden&apos;);
      // startCamera.classList.remove(&apos;hidden&apos;);
      // video.classList.add(&apos;hidden&apos;);
      // video.srcObject = null;
    });

    // Stop the camera
    stopCamera.addEventListener(&apos;click&apos;, () =&amp;gt; {
      if (stream) {
        stream.getTracks().forEach(track =&amp;gt; track.stop());
      }
      buttonGroup.classList.add(&apos;hidden&apos;);
      startCamera.classList.remove(&apos;hidden&apos;);
      video.classList.add(&apos;hidden&apos;);
      video.srcObject = null;
      photo.src = &quot;&quot;;
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;By clicking the camera button, the camera will activate: &lt;img src=&quot;/assets/images/poker-face/3.png&quot; alt=&quot;camera&quot; /&gt;
Cool, it works. GPT is great!&lt;/p&gt;

&lt;p&gt;Click the ‘Take Photo’ button though, and you’ll bump into an error.
Currently, when this button is clicked,
an event named &lt;code&gt;new_photo&lt;/code&gt; is dispatched to the backend,
for which the handling is not yet implemented.
Let’s proceed with that.&lt;/p&gt;

&lt;h3 id=&quot;liveview-process&quot;&gt;LiveView process&lt;/h3&gt;

&lt;p&gt;Edit &lt;code&gt;face_live.ex&lt;/code&gt; and add these functions:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-elixir&quot;&gt;...

  @impl true
  def handle_event(&quot;new_photo&quot;, %{&quot;photo&quot; =&amp;gt; photo}, socket) do
    send(self(), {:anaylse, photo})
    {:noreply, socket}
  end

  @impl true
  def handle_info({:anaylse, photo}, socket) do
    {:ok, text} = Gemini.analyze_image(photo)

    socket =
      socket
      |&amp;gt; assign(:photo_info, text)

    {:noreply, socket}
  end

...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Instead of OpenAI gpt, We implement Gemini API first, Let’s add a &lt;code&gt;gemini.ex&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-elixir&quot;&gt;defmodule PokerFace.Gemini do
  @vision_uri &quot;https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent&quot;

  def analyze_image(&quot;data:image/png;base64,&quot; &amp;lt;&amp;gt; image_data) do
    prompt = &quot;&quot;&quot;
      What&apos;s is he/she/it doing?
    &quot;&quot;&quot;

    body = %{
      contents: [
        %{
          parts: [
            %{text: prompt},
            %{inlineData: %{mimeType: &quot;image/png&quot;, data: image_data}}
          ]
        }
      ]
    }

    resp =
      Req.post!(@vision_uri &amp;lt;&amp;gt; &quot;?key=#{System.get_env(&quot;GOOGLE_AI_API_KEY&quot;)}&quot;,
        json: body,
        receive_timeout: 60_000,
        connect_options: [protocols: [:http1]]
      )

    text =
      resp.body[&quot;candidates&quot;]
      |&amp;gt; List.first()
      |&amp;gt; Map.get(&quot;content&quot;)
      |&amp;gt; Map.get(&quot;parts&quot;)
      |&amp;gt; List.first()
      |&amp;gt; Map.get(&quot;text&quot;)

    {:ok, text}
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In order to utilize Gemini, you must obtain your personal API key from &lt;a href=&quot;https://ai.google.dev/&quot;&gt;Google AI&lt;/a&gt;.
Once acquired and configured as an environment variable,
you’ll possess a functional AI-powered image analyzer.
&lt;img src=&quot;/assets/images/poker-face/4.png&quot; alt=&quot;gemini response&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Although Gemini responds correctly,
the photo captured becomes non-persistent on the current page.
So, we need to make some fix and an OpenAI version could also be integrated.
I’ll not show the code here.
But you can check all the
code &lt;strong&gt;&lt;a href=&quot;https://github.com/gonglexin/poker_face/compare/0db4a57ee0d31d4d34da32acb181a121655379b9...1b60c1ded174555449a1ab89eb8d5eb16e881cde&quot;&gt;changes&lt;/a&gt;&lt;/strong&gt;
We should make today.&lt;/p&gt;

&lt;p&gt;At the end of these modifications,
we are equipped with a fully operational camera AI image analyzer!
&lt;img src=&quot;/assets/images/poker-face/5.png&quot; alt=&quot;5.png&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Managing Elixir's main branch with asdf</title>
    <link href="https://gonglexin.com/posts/managing-elixir-main-branch-with-asdf"/>
    <updated>2024-01-10T00:00:00+00:00</updated>
    <id>https://gonglexin.com/posts/managing-elixir-main-branch-with-asdf</id>
    <content type="html">&lt;p&gt;I’ve always used &lt;a href=&quot;https://github.com/asdf-vm/asdf&quot;&gt;asdf&lt;/a&gt; to manage Elixir versions, and I’ve also had a persistent need to run projects or experiments with the latest code from the Elixir repo (main branch). Installing the main branch is quite simple:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Clone the Elixir code and build it locally:
    &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/elixir-lang/elixir.git /path/to/elixir
cd /path/to/elixir &amp;amp;&amp;amp; make
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;Create a symlink in the asdf installs directory and switch to the main version:
    &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ln -s /path/to/elixir ~/.asdf/installs/elixir/main
asdf reshim elixir main
asdf shell elixir main | asdf global elixir main
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;Verify:
    &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ asdf list elixir
  1.15.7-otp-26
  1.16.0-otp-26
 *main
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;Daily update:
    &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /path/to/elixir &amp;amp;&amp;amp; git pull &amp;amp;&amp;amp; make
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;You can write the above  update commands to a script and run it periodically to ensure you’re always using the latest version of the main branch.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;If running make directly throws compilation errors, you need to delete the previous build artifacts before rebuilding:
    &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /path/to/elixir
make clean &amp;amp;&amp;amp; make
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;Using the main branch can be somewhat unstable as it’s still under development.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>在安卓手机上安装 Elixir</title>
    <link href="https://gonglexin.com/posts/install-elixir-on-android"/>
    <updated>2019-07-25T00:00:00+00:00</updated>
    <id>https://gonglexin.com/posts/install-elixir-on-android</id>
    <content type="html">&lt;p&gt;记录一下在安卓手机上安装 &lt;a href=&quot;https://elixir-lang.org/&quot;&gt;Elixir&lt;/a&gt; 的过程。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;在手机上安装 &lt;a href=&quot;https://termux.com/&quot;&gt;Termux&lt;/a&gt; 应用&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;开启 SSH 服务（&lt;strong&gt;非必须&lt;/strong&gt;, 开 &lt;code&gt;ssh&lt;/code&gt; 只是为了便于 PC 端访问操作手机，比在手机上打字方便）
    &lt;ul&gt;
      &lt;li&gt;安装相关包
        &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  apt update
  apt upgrade
  pkg install openssh
  pkg install net-tools
&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
      &lt;li&gt;设置密码并开启 ssh
        &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  # 设置一个密码
  passwd
  # 开启服务
  sshd
&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
      &lt;li&gt;查看手机 ip
        &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  ifconfig
&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;安装 Elixir
    &lt;ul&gt;
      &lt;li&gt;从 PC 端登录手机
        &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  # 确保手机和 PC 处在同一网络，用上一步获取的安卓手机 ip 登录
  # 拷贝公钥到安卓手机(需输入前面设置的密码)
  ssh-copy-id -p 8022 -i your_public_key_file andriod_ip
  # 登录
  ssh -p 8022 android_ip
&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
      &lt;li&gt;安装相关包 (PC 端登录之后，就可以开始正式的安装步骤了)
        &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  pkg install unzip
  pkg install erlang
&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
      &lt;li&gt;安装 Elixir, 参考 &lt;a href=&quot;https://github.com/hexpm/bob#elixir-builds&quot;&gt;https://github.com/hexpm/bob#elixir-builds&lt;/a&gt; 确定好 elixir 和 erlang otp 版本, 这里以 elixir 1.9.1 otp 22 为例:
        &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  mkdir elixir
  cd elixir
  # wget https://repo.hex.pm/builds/elixir/{REF}-otp-{OTP_MAJOR_VERSION}.zip
  wget https://repo.hex.pm/builds/elixir/v1.9.1-otp-22.zip
  unzip v1.9.1-otp-22.zip
  rm v1.9.1-otp-22.zip
&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
      &lt;li&gt;设置 PATH, 保存到 bashrc
        &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  vi .bashrc
  export PATH=$PATH:$HOME/elixir/bin
&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;验证
    &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;elixir -v
#&amp;gt; Erlang/OTP 22 [erts-10.4.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1]
#&amp;gt;
#&amp;gt; Elixir 1.9.1 (compiled with Erlang/OTP 22)
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>Mac 下临时显示隐藏的文件和文件夹</title>
    <link href="https://gonglexin.com/posts/how-to-temporarily-display-hidden-files-and-folders-in-mac-os-x"/>
    <updated>2013-12-21T00:00:00+00:00</updated>
    <id>https://gonglexin.com/posts/how-to-temporarily-display-hidden-files-and-folders-in-mac-os-x</id>
    <content type="html">&lt;p&gt;今天遇到一个问题, 在使用 Github 的 Mac 客户端的时候, 想直接导入已经 clone 到本地的 repo, 但是这个 repo 目录是一个以 . 为开头的格式命名的隐藏目录, 在打开的文件选择窗口里面是无法看到的。&lt;/p&gt;

&lt;p&gt;以前我一般是打开终端输入如下命令:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;defaults write com.apple.finder AppleShowAllFiles TRUE; killall Finder
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这条命令有一个不便利的地方就是执行之后系统就会一直显示那些隐藏文件，往往等你操作完之后，你又需要把值重新设置回去:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;defaults write com.apple.finder AppleShowAllFiles FALSE; killall Finder
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;今天才发现，原来在选择文件的时候, 只需要按下 &lt;strong&gt;&lt;code&gt;Command + Shift + .&lt;/code&gt;&lt;/strong&gt; 组合键就可以临时地显示隐藏文件了。当然这不仅仅适用于这一个软件，Mac 下所有软件的文件选择窗口都是可以这么做的。&lt;/p&gt;

&lt;p&gt;参考: &lt;a href=&quot;http://www.tekrevue.com/tip/how-to-temporarily-see-hidden-files-folders-in-mac-os-x/&quot;&gt;http://www.tekrevue.com/tip/how-to-temporarily-see-hidden-files-folders-in-mac-os-x/&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Rails 使用 mongoDB 和 Slim</title>
    <link href="https://gonglexin.com/posts/rails-project-setup-for-mongodb-and-slim"/>
    <updated>2012-11-24T00:00:00+00:00</updated>
    <id>https://gonglexin.com/posts/rails-project-setup-for-mongodb-and-slim</id>
    <content type="html">&lt;p&gt;预言中的世界末日都要来了，期待的 &lt;code&gt;Ruby 2.0&lt;/code&gt; 和 &lt;code&gt;Rails 4.0&lt;/code&gt; 都还没发布。那我就来看看怎么集成 &lt;a href=&quot;http://www.mongodb.org/&quot;&gt;mongoDB&lt;/a&gt; 和 &lt;a href=&quot;http://slim-lang.com/&quot;&gt;Slim&lt;/a&gt; 到一个 Rails 工程。&lt;/p&gt;

&lt;h2 id=&quot;安装-mongodb-数据库&quot;&gt;安装 mongoDB 数据库&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;brew install mongodb&lt;/code&gt;
安装好之后，可以把 mongo 的服务设为开机启动，或者在需要的时候手动 &lt;code&gt;mongod&lt;/code&gt; 开启。&lt;/p&gt;

&lt;h2 id=&quot;新建-rails-工程&quot;&gt;新建 Rails 工程&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;rails new projectname --skip-test-unit --skip-active-record --skip-bundle
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因为使用 mongoDB 并打算使用 &lt;a href=&quot;https://github.com/seattlerb/minitest&quot;&gt;minitest&lt;/a&gt; 所以会在创建工程的时候 skip 掉不必要的东西。&lt;/p&gt;

&lt;h2 id=&quot;修改-gemfile&quot;&gt;修改 Gemfile&lt;/h2&gt;

&lt;p&gt;在 Gemfile 里面添加以下几句之后执行 &lt;code&gt;bundle install&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;gem &apos;slim-rails&apos;
gem &apos;mongoid&apos;
group :test, :development do
  gem &apos;minitest-rails&apos;
end
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;生成数据库配置和-minitest-helper-文件&quot;&gt;生成数据库配置和 minitest helper 文件&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;rails generate mongoid:config
rails generate mini_test:install
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;slim-页面&quot;&gt;Slim 页面&lt;/h2&gt;

&lt;p&gt;把 application.html.erb 转换成 slim 格式，可以使用 &lt;a href=&quot;http://html2slim.herokuapp.com/&quot;&gt;html2slim&lt;/a&gt; 或者自己手动修改。参见我改好的&lt;a href=&quot;https://gist.github.com/4139805&quot;&gt;版本&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;使用&quot;&gt;使用&lt;/h2&gt;

&lt;p&gt;现在就可以使用 &lt;code&gt;rails g model | controller&lt;/code&gt; 等等命令了，默认生成的 view 文件都会是 slim 格式，model 都会自动 include &lt;a href=&quot;https://github.com/mongoid/mongoid&quot;&gt;mongoid&lt;/a&gt; 的 class。enjoy it!&lt;/p&gt;
</content>
  </entry>
  

</feed>
