じぶんメモ

プログラミングのメモ、日常のメモとか。

railsのモデルに、特定の条件下で動くvalidationを追加

ifを使用した条件分岐

例えば、bという項目がtrueの場合のみaの値は必須にしたい、とかあると思う。
with_optionsメソッドを使用して以下のように実装することができる。

class Post < ActiveRecord::Base
  # ifオプションで条件に合致する場合のみvalidatesを実行(published?はメソッド名またはmodelのboolean項目)
  validates :name, presence: true if: published?

  # 複数valitatesをまとめたい場合はwith_optionsを使用
  with_options if: :published? do
    validates :name,  presence: true
    validates :category,  presence: true
  end

  # unlessで条件に合致しない場合のみvalidatesを実行
  # lambdaを使うことで複数条件を指定できる
  with_options unless: -> { :hoge? || :foo? } do
    validates :name,  presence: true
  end
end

ただし、この場合、with_optionsブロック内部で更にif: を使用してしまうと、
内部のif条件の場合にしかvalidatesが実行されなくなる。

  # hogeの時しかwith_options内部が実行されない
  with_options if: :published? do
    validates :name,  presence: true
    validates :category,  presence: true, if: hoge
  end

  # with_optionsで定義したものと逆のもの(この場合はunless)を使用すれば問題ない
  with_options if: :published? do
    validates :name,  presence: true
    validates :category,  presence: true, unless: 'hoge.blank?'
  end

ネストしたvalidationを記載するなら、メソッドを一つ作って指定した方が良さそう。

validates :name,  presence: true, if: :require_validation?

def require_validation?
  return true if aaa? && bbb?
  false
end

onを使用した条件分岐

例えば新規登録(create)の場合のみバリデーションをかけたい時は、onオプションを使用する。

class Post < ActiveRecord::Base
  # createの時のみ実行
  validates :name, presence: true on: :create

  # if同様with_optionsを使用できる
  with_options on: :create? do
    validates :name,  presence: true
    validates :category,  presence: true
  end

  # こうすることで、pattern_hogeコンテキストが渡された時のみvalidatesが実行される
  validates :hoge, presence: true, length: { maximum: 3000 }, on: :pattern_hoge
end
class PostsController < ApplicationController
  def hoge
    # pattern_hogeコンテキストを指定
    post.save(context: :hoge.save(context: :pattern_validation))
  end
end

railsでenumを使う

modelに記述する。

class Post < ActiveRecord::Base
  enum status: { created: 0, drafted: 1, canceled: 2 }
end

こうすることで、Post.statusesとして、各Enumにアクセスすることができる。

pry(main)> Post.statuses            # => {"created"=>0, "drafted"=>1, "canceled"=>2}
pry(main)> Post.statuses[:drafted]  # => 1
pry(main)> @post = Post.new
pry(main)> @post.status = Post.statuses[:drafted]

また、インスタンスに対して、enumの各要素をメソッドとして使用することができる。

pry(main)> @post = Post.new
pry(main)> @post.status = Post.statuses[:drafted]
pry(main)> @post.drafted?     # => true
pry(main)> @post.canceled!
pry(main)> @post.canceled?    # => true
pry(main)> @post.drafted?     # => false

modelとの関連付けはせず、Enumとしてだけ使用したい場合、
locales下のymlファイルに記述する方法もあり。

ja:
  Enum:
    status:
      created: 0
      drafted: 1
      canceled: 2
pry(main)> Enum.codes(:status)
=> ["created", "drafted", "canceled"]

選択肢が限られているセレクトボックスの検証とかで、inclusionと合わせて使えそう。

シェルでディレクトリ内のファイルの数を調べる

例えばシェルでディレクトリ内に何ファイル存在するか調べるには以下の方法でチェックをする。

ls -FU1 '対象のパス' | grep -v / | wc -l 

ざっと解説すると、lsの結果をgrepに渡し、grep -vで不要な情報を削除し、
wcコマンドにわたし、数をカウントしている。lsオプションは以下の通り。

  • -F:ディレクトリが存在する場合は/を付与して表示する。
  • -1:lsの結果を1行ずつ表示
  • -U:lsの結果をソートしない(速度向上)

上記の例だとディレクトリを省いてファイルの数を調べている。
また、wcコマンドは、渡されたファイルのバイト数も表示するため、
-lオプションは、渡されたファイルの個数のみを表示させている。

ブックマークレットでフォームに自動的に入力するスクリプトを作りたい

テストとかで画面に値を入力する場合、 繰り返しテストをしていると、
毎回フォームに値を入力しないといけないのがダルい。
ブックマークレットで1クリックだけでフォームに値をセットできるスクリプトを作りたい。
とりあえずフォームの要素に値をセットする方法。

javascript:(function(){ document.getElementById("srchtxt").value = "てすと";}())

ついでに画面内のID全て取得するブックマークレット

javascript:(function() {var elements = document.getElementsByTagName("input");var i = 0; var ids = "";for (i = 0 ; i < elements.length ; i++){ids += elements[i].id + "\r\n";}alert(ids)}());

ブックマークレットスクリプトで改行すると美味く解釈されないので、基本的には改行なし。

コマンドプロンプトでgrep

コマンドプロンプトgrepするならfindstr

Windowsコマンドプロンプトgrepと同じことをしたい。
findstrコマンドが良さげ。

findstr [オプション]  "検索対象文字列" "検索対象のファイル"

以下よく使いそうなオプション

デフォルトでは正規表現での検索をしてくれないので、/Rオプションを使用する。

/I 大文字小文字を区別しない
/S ディレクトリを再帰的に検索する
/R 正規表現を使用する
/C 空白を含む文字を検索する場合に指定する
/N 一致した行数を表示
/V 指定した文字を含まない行をすべて表示

使用例

カレントディレクトリ以下で、”This is〜"という文字が含まれている箇所をresult.txtに出力する。

findstr /R /S  /C "This is.*" * > result.txt

Oracleでテーブルのカラム名表示

  • 全ての詳細表示
DESC テーブル名;
  • テーブルの物理名の表示
SELECT USER_TAB_COMMENTS;
  • テーブルの項目の物理名表示
SELECT USER_COL_COMMENTS
WHERE TABLE_NAME = 'テーブル名';

システムテーブルも表示したい場合は、ALL_TAB_COMMENTS、ALL_COL_COMMENTSにすればOK。

JavaでのZipファイル作成

JavaでのZipファイル作成方法を調査した。 一例なので、他の方法もあるかも。
肝心なのは、ZipArchiveOutputStreamのflush()を使用して、1ファイルずつZipに書き込んでいるところ。
flushを使わず、全てのファイルをメモリに格納し、 Zipを生成すると、
OutOfMemoryExceptionが発生する可能性がある。

public String downloadZip() throws Exception {
    //対象年月のFileオブジェクト取得
    File downloadTargetDir = new File("/var/local/testDir/");

    ByteArrayOutputStream zipBAOS = new ByteArrayOutputStream();
    ZipArchiveOutputStream zipArchiveOS = new ZipArchiveOutputStream(zipBAOS);

    // ヘッダー情報設定
    try {
        this.response.setHeader("Content-Transfer-Encoding", "binary");
        this.response.setHeader("Content-Type", "application/zip;charset=UTF-8");
        this.response.setHeader("Content-Disposition", "attachment; filename=\"" + "test.zip" + "\"");
        this.response.setHeader("Cache-Control", "private");
        this.response.setHeader("Pragma", "private");

        // ディレクトリを圧縮
        this.recursiveArchive(outZip, downloadTargetDir);

    } catch ( Exception e ) {
        // ZIP圧縮失敗
        e.printStackTrace();
        throw new Exception(e);
        
    } finally {
        // ZIPエントリクローズ
        try { outZip.closeArchiveEntry(); } catch (Exception e) {}
        try { outZip.close(); } catch (Exception e) {}
        try { zipBAOS.close(); } catch (Exception e) {}
    }
}

/**
 * ディレクトリ圧縮のための再帰処理
 *
 * @param outZip ZipOutputStream
 * @param targetFile File 圧縮したいファイル
 * @param parentFilepath String
 */
private void recursiveArchive(ZipArchiveOutputStream outZip, File targetFile) {
    if ( targetFile.isDirectory() ) {
        File[] files = targetFile.listFiles();
        
        for (File file : files) {
            if ( file.isDirectory() ) {
                recursiveArchive(outZip, file, parentFilepath);
            } else {
                String entryName = file.getName();
                // 圧縮処理
                archive(outZip, file, entryName);
            }
        }
    }
}

/**
 * 圧縮処理
 * @param outZip ZipOutputStream
 * @param targetFile 圧縮したいファイル
 * @param entryName 保存ファイル名
 * @return
 */
private boolean archive(ZipArchiveOutputStream outZip, File targetFile, String entryName) {

    // 圧縮レベル設定
    outZip.setLevel(5);

    try {
        // ZIPエントリ作成
        outZip.putArchiveEntry(new ZipArchiveEntry(entryName));
        // 圧縮ファイル読み込みストリーム取得
        BufferedInputStream in = new BufferedInputStream(new FileInputStream(targetFile));

        // 圧縮ファイルをZIPファイルに出力
        int size = 0;
        //byte buffer[] = new byte[1024]; // 読み込みバッファ
        byte[] bff = new byte[1024];
        while ((size = in.read(bff, 0, bff.length)) != -1) {
            outZip.write(bff, 0, size);
        }
        in.close();
        outZip.closeArchiveEntry();

        try {
            outZip.flush();             
        } catch (Exception e) {
        }            
        
    } catch ( Exception e ) {
        // ZIP圧縮失敗
        return false;
    }
    return true;
}