Rails をInnteliJ IDEAでデバッグする

はじめに

Railsアプリケーションを開発にIntelliJ IDEAを利用し、デバッグ実行を行えるようにする。

色々試してみたが… 結論 remote-debug

IntelliJ IDEAにruby sdkを認識させ、gemsetを設定してみるが、うまくデバッグできることもあったが、再現性に乏しい。rbenv + gemsetでデバッグは正式にサポートされていないのか??
結局、リモートデバッグを利用することにした。リモートデバッグといっても、IDEAの設定をリモートデバッグにして、ローカルのターミナルでrailsを起動するので、新しい環境(サーバ)などは不要。

ruby/rails環境

rubyはrbenvを利用し、gemはrbenv gemsetを利用して管理する。

$ rbenv versions
  system
* 2.4.1 (set by /Users/xxxxx/Develop/Repos/gemset_name/src/.ruby-version)
$ rbenv gemset active
gemset_name global

デバッグツール(gem)のインストール

$ rbenv gemset active
gemset_name
$ gem install ruby-debug-ide
$ gem install debase

IDEA側設定

Project Settings > Project

一応SDKを設定する。
Project SDKにrbenv2.4.1 を選択する。

Run/Debug Configurations

+ボタンからruby remote debugを選択し、以下のように設定を行う。

項目 設定
Server Command rdebug-ide –host 0.0.0.0 –port 1234 –dispatcher-port 26162 – $COMMAND$
Remote host localhost
Remote port 1234
Remote root folder railsアプリケーションのルートフォルダ
Local port 26162
Local root folder railsアプリケーションのルートフォルダ

※ 今回はRemote と Localが同じマシンなので、root folderはいずれも同じになる。

デバッグの実行

リモートでrailsを起動

rdebug-ideでホスト、ポートなどを指定して、railsを起動する。

$ rdebug-ide --host 0.0.0.0 --port 1234 --dispatcher-port 26162 -- ./bin/rails server
Fast Debugger (ruby-debug-ide 0.6.0, debase 0.2.1, file filtering is supported) listens on 0.0.0.0:1234

debug実行

リモートでrailsを起動した状態で、IDEAのデバッグを実行する。
デバッグを開始し接続ができると、リモート側のコンソールに以下のようにコンソール出力がされる。

=> Booting Puma
=> Rails 5.1.1 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.9.1 (ruby 2.4.1-p111), codename: Private Caller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

ブレークポイントの設定

あとは通常通りブレークポイントを設定し、ブラウザなどでrailsのページを開くと、ブレークポイントでデバッガが停止する。

LocalDate型の変数をJSPで任意のフォーマットで表示する

サーブレット側で設定した日付(LocalDate)を保持した変数をjspに表示する。
こんな単純なことなのに、案外はまってしまった。

  • コントローラ
@Service
public class TestService {
    public Model getTest( Model model) throws ApplicationException {
        model.addAttribute("today", LocalDate.of(2017, 5, 1));
            return model;
    }
}
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
...
<fmt:parseDate value="${today}" pattern="yyyy-MM-dd" var="parsedToday" type="date" />
<fmt:formatDate value="${parsedToday}" var="newParsedToday" type="date" pattern="yyyy/MM/dd" />
<p>${newParsedToday}</p>

parseDateを利用して一度、var=“parsedToday” とした後、 formatDateを利用し、表示したい形式にする。

Intellij Ideaのgradleタスク実行でJavaHomeが違う。というエラー

トラブル事象

Intellij Idea(以降Idea)でgradelのRun設定を行い、タスクを実行しようとしたところ、以下のようなエラーとなった。

10:13:46: Executing external task 'gen'...
The newly created daemon process has a different context than expected.
It won't be possible to reconnect to this daemon. Context mismatch: 
Java home is different.
Wanted: DefaultDaemonContext[uid=null,javaHome=/Applications/IntelliJ IDEA.app/Contents/jdk/Contents/Home,daemonRegistryDir=/Users/xxxxx/.gradle/daemon,pid=9836,idleTimeout=null,daemonOpts=-XX:MaxPermSize=256m,-XX:+HeapDumpOnOutOfMemoryError,-Xmx1024m,-Dfile.encoding=UTF-8,-Duser.country=JP,-Duser.language=ja,-Duser.variant]
Actual: DefaultDaemonContext[uid=b46ae433-167f-4edf-83aa-8fcc74371dc1,javaHome=/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home,daemonRegistryDir=/Users/xxxxx/.gradle/daemon,pid=15833,idleTimeout=60000,daemonOpts=-XX:MaxPermSize=256m,-XX:+HeapDumpOnOutOfMemoryError,-Xmx1024m,-Dfile.encoding=UTF-8,-Duser.country=JP,-Duser.language=ja,-Duser.variant]

10:13:47: External task execution finished 'gen'.

解決策

gradle.propertiesファイルに以下のようにjava_homeを設定する。

org.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home
10:24:33: Executing external task 'gen'...
:gen

BUILD SUCCESSFUL

Total time: 3.411 secs
10:24:36: External task execution finished 'gen'.

SpringFramework Autowiredを設定したプロパティがNullになる問題

はじめに

Spring Framework ではAutowiredアノテーションを利用してクラスのインジェクションができるが、
ServiceクラスでAutowiredを設定したプロパティがNullになってしまう事象が発生した。

実現したかったこと

PropertiesFactoryBeanを利用して、Propertiesファイルを読み込み、プロパティを利用したいクラスで Autowiredを利用してプロパティインスタンスを読み込む。

    <bean id="applicationProperties"
          class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="locations">
            <list>
                <value>classpath:application.properties</value>
            </list>
        </property>
    </bean>

AutowiredしたプロパティがNull

Serviceクラスで以下のようにapplicationPropertiesをAutowiredしたが、Null。

@Service
public class SalesService {

    static Logger log = Logger.getLogger(SalesService.class);

    @Autowired
    private Properties applicationProperties;       → Null
    ...
}

原因

Autowiredを利用するには、サービスを呼び出すコントローラクラスでもAutowiredを利用して、サービスクラスをDIしておく必要があった。

  • 失敗(Nullになる) サービスクラスをnewして利用すると、サービスクラスのAutowiredが有効にならない。
@Controller
public class SalesController {
...

    /**
     * 売上レポート 画面表示
     * @return
     */
    @RequestMapping(value = "/sales/report", method = GET)
    public Object reportView(Model model)
            throws ApplicationException, IllegalAccessException, NoSuchMethodException, IOException {

        SalesService salesService = new SalesService();

        return "sales/report";
    }

}
  • 成功 コントローラクラスで、サービスクラスをAutowiredでDIしておく。
@Controller
public class SalesController {

    @Autowired
    private SalesService salesService;

...
}
@Service
public class SalesService {

    @Autowired
    private Properties applicationProperties;       → Nullにならずにインスタンス化されている
...
}

sudoでコマンドを実行する際に補完を利用する

sudo でコマンドを実行する際に、コマンドの補完が使えないことがある。
例えばCentOS7 ではsudo systemまで入力してTabキーを叩いても、候補が表示されない。
以下の設定をすることで、sudo で実行するコマンドでも補完がされるようになる。

設定

~/.bashrc

タブ補完が利用できるようにするには、.bashrcにcomplete -cf sudoを設定する。

$ sudo vim ~/.bashrc 
...
complete -cf sudo

~/.bashrcを読み込み

$ source ~/.bashrc

Debian8でVNC Serverを設定する

環境

OS : Debian8

インストール

$ apt iinstall tightvncserver

vncserverの起動

vncserverコマンドを実行し、パスワードを設定する。

$ vncserver 

You will require a password to access your desktops.

Password: 
Verify:   
Would you like to enter a view-only password (y/n)? n
...
New 'X' desktop is ie-server:2

Creating default startup script /home/gyamin/.vnc/xstartup
Starting applications specified in /home/gyamin/.vnc/xstartup
Log file is /home/gyamin/.vnc/ie-server:2.log

上記の例では、ディスプレイ番号2でvncserverが実行された。(:2)
vncserverを終了する場合は、-kill :ディスプレイ番号で、プロセスを終了する。

$ vncserver -kill :2
Killing Xtightvnc process ID 2097

vncserverへの接続

vncクライアントからサーバへ接続をする。Macの場合、Finderの 移動 > サーバへ接続 からvnc://接続先サーバ:ポート番号で接続ができる。
今回はディスプレイ番号が2なので、5902ポートに接続する。vnc://192.168.10.5:5902
vncserver起動時に設定したバスワードで認証する。

xinetdによるvncserverの起動

クライアントからの接続時に自動的にvncserverを実行するように、xinetdを設定する。

$ sudo vim /etc/services 
...
xvnc            5910/tcp                        # VNC Server    ←追加
...

xinetdの設定ファイルを作成する。

$ sudo vim /etc/xinetd.d/xvnc
service xvnc
{
    disable = no
    socket_type = stream
    wait = no
    user = nobody
    group = tty
    server = /usr/bin/Xvnc
    server_args = -inetd -geometry 1024x768 -depth 24 -query localhost -once -rfbport 5910 -rfbauth /etc/xvnc_passwd
}

今回はまったのが、このファイルのserver_args。ネット上の情報を参考に以下のような設定をしていた。

server_args = -inetd -geometry 1024x768 -depth 24 -query localhost -once PasswordFile=/etc/xvnc_passwd

しかしこれではvncクライアントから接続ができず、どうしたらいいか??だった。     /usr/bin/Xvncコマンドの引数の設定なので、manでXvncを調べてみたところ、パスワードファイルの指定は-rfbauthで指定するとの記述が。     この設定に変更することで、クライアントから接続ができるようになった。

vncpasswdコマンドを利用し、xvnc設定で指定したパスワードファイルを作成する。

$ sudo vncpasswd /etc/xvnc_passwd
Password: 
Verify:   
Would you like to enter a view-only password (y/n)? n

パスワードファイルに権限を設定する。

$ sudo chown nobody:tty /etc/xvnc_passwd
$ ls -l /etc/xvnc_passwd 
-rw------- 1 nobody tty 8 Dec 22 18:36 /etc/xvnc_passwd

xinetdを再起動する。

$ sudo systemctl restart xinetd

vncクライアントからサーバへ接続を行う。正しく設定ができていれば、VNCサーバのパスワード入力が促され、パスワードを入力すると、接続ができる。
※ただし、この段階では別途接続したsshクライアントなどで、X Windowを利用するアプリを起動した場合、その出力が表示されるだけで、サーバの画面出力と同等の内容が出力されるわけではない。

lightdmの設定

vncサーバへの接続時に、ホストで出力される画面と同等の画面を表示できるよう、 /etc/lightdm/lightdm.conf に設定を行う。

# vim /etc/lightdm/lightdm.conf 

[XDMCPServer]
enabled=true
port=177


[VNCServer]
enabled=true
command=Xvnc
port=5900

サービスを再起動する。

# /etc/init.d/lightdm restart
[ ok ] Restarting lightdm (via systemctl): lightdm.service.

lightdmの設定後、vncクライアントから接続を行うと、GUIでログイン画面が表示される。

USBメモリにDebian Installerを作成する

はじめに

Debian8をインストールするため、USBメモリインストーラの作成を行う。Mac OS X上で行う方法を記載する。

手順

isoファイルをダウンロード

Debianのダウンロードサイトからisoファイルをダウンロードする。 今回はネットワークインストールのisoファイルを利用した。

isoファイルをimgファイルに変換

hdiutilコマンドを利用してimgファイルを作成

$ hdiutil convert -format UDRW -o debian-stretch-DI-alpha8-amd64-netinst.img debian-stretch-DI-alpha8-amd64-netinst.iso 
Driver Descriptor Map(DDM:0) を読み込み中...
Debian stretch-DI-a8 amd64 1    (Apple_ISO:1) を読み込み中...
Apple(Apple_partition_map:2) を読み込み中...
Debian stretch-DI-a8 amd64 1    (Apple_ISO:3) を読み込み中...
EFI(Apple_HFS:4) を読み込み中...
Debian stretch-DI-a8 amd64 1    (Apple_ISO:5) を読み込み中...
..................................................................................................
経過時間: 1.217s
速度:235.8M バイト/秒
節約率:0.0%
created: /Users/xxx/Downloads/debian-stretch-DI-alpha8-amd64-netinst.img.dmg

.dmg拡張子を消す

$ mv debian-stretch-DI-alpha8-amd64-netinst.img.dmg debian-stretch-DI-alpha8-amd64-netinst.img

USBメモリをアンマウント

diskutil listで接続されているUSBメモリのデバイスを確認する

$ diskutil list
...
/dev/disk3
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     Apple_partition_scheme                        *16.0 GB    disk3

アンマウントする

$ diskutil unMountDisk /dev/disk3
Unmount of all volumes on disk3 was successful

ddコマンドでimgファイルをUSBメモリに書き込む

ddコマンドでusbメモリバイスにファイルをコピーする。少し時間がかかる。

$ sudo dd if=debian-stretch-DI-alpha8-amd64-netinst.img of=/dev/disk3 bs=1m
Password:

287+0 records in
287+0 records out
300941312 bytes transferred in 379.428644 secs (793143 bytes/sec)