ホーム ブログ ページ 44

[Rails4]Bootstrap導入とassets:precompileでglyphiconが表示されない不具合対応

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Rails4にBootstrapを導入するのは、とても簡単です。
だが、いざ本番環境にデプロイしたら、glyphiconが表示されないという不具合に見舞われました。
自分がベストだと思う導入方法と対処方法をメモします。
対象バージョンはbootstrap-sass-3.2.0.0です。バージョンアップで解消される事を期待したい。

●Bootstrapを追加

turbolinksと相性が悪いらしいので、turbolinksを外してbootstrap-sassを追加します。

編集:Gemfile

# gem 'turbolinks'

# Use Bootstrap
gem 'bootstrap-sass'

$ bundle install =========================================== Installing bootstrap-sass 3.1.1.1 =========================================== ※「Your bundle is complete!」と表示されればOK

●Bootstrapの適用順番を明示

JSとCSSは、デフォルトだと名前順に適用されるので、先に読まれるように明示します。
aとかで始まる名前のJS/CSSだとBootstrapの設定を上書けない為です。(CSSは後勝ちなので)

編集:app/assets/javascripts/application.js

//= require turbolinks
//= require bootstrap

編集:app/assets/stylesheets/application.css

 *= require bootstrap
 *= require_tree .
 *= require_self

●assets:precompileでglyphiconが表示されない不具合対応

本番環境ではassets:precompileして、public/assets以下をApacheから直接返すようにしていますが、gems/bootstrap-sass-3.2.0.0/assets/stylesheets/bootstrap/_glyphicons.scssで「url(」と定義されている為、precompile時にcssにハッシュ値が入らず、アイコンが表示できません。
SCSSで画像などを表示するには「image-url(」と定義して、Railsに変換してもらう必要があります。開発環境では「url(」でも表示されるので、開発段階では気付き難いので厄介です。

作成:app/assets/stylesheets/bootstrap_override.scss

//### assets:precompileでglyphiconが表示されない不具合対応 ###
@font-face{
  font-family:'Glyphicons Halflings';
  src: image-url("bootstrap/glyphicons-halflings-regular.eot");
  src: image-url("bootstrap/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"),
       image-url("bootstrap/glyphicons-halflings-regular.woff") format("woff"),
       image-url("bootstrap/glyphicons-halflings-regular.ttf") format("truetype"),
       image-url("bootstrap/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg")
}

編集:app/assets/stylesheets/application.css

 *= require bootstrap
 *= require bootstrap_override

論理削除Gem(Paranoia)を自分好みにカスタマイズ

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

DBのユニーク規制が、データの整合性を保つ最後の砦だと考えている。
実装に注意したり、DBを直接書き換える場合に、注意すれば良いのだけれど、やっぱりバグやヒューマンエラーは付き物。
バグやヒューマンエラーを考えると論理削除を使って、定期的にクリーニングがベストかな。
論理削除のGemを探しましたが、日付を使う物が多く、かつNULLが使われるので、複合キーでのユニーク規制が掛からない。
フラグを使う物もあったけど、0・1では一度削除したものと同じものを再度削除出来なくなる。
辿り着いたのは、初期値0で、削除時にIDと同じ値を設定するというDB設計。
ただ、これを満たすGemが見つからないので、良さそうなParanoiaの一部を修正する(モンキーパッチを充てる)事にしました。

また、JOINされるケースでSQLが「WHERE “.`deleted` = 0」となり、「Mysql2::Error: Column ‘deleted’ in where clause is ambiguous:」で落ちる問題も対応しました。
このバージョンの組み合わせだから動かない、という事なんだろうか?(謎)

前提:Rails 4、Paranoia 2.0.2

●Paranoia追加

編集:Gemfile

# Use Paranoia
gem 'paranoia', '2.0.2' # モンキーパッチ:config/initializers/extensions/paranoia.rb

$ bundle install
===========================================
Installing paranoia 2.0.2
===========================================
※「Your bundle is complete!」と表示されればOK

●Paranoiaカスタマイズ

作成:config/initializers/extensions/paranoia.rb

module Paranoia
  module Query
    def only_deleted
      # with_deleted.where.not(paranoia_column => nil)
      with_deleted.where.not(paranoia_column => 0)
    end
  end

  def restore!(opts = {})
    ActiveRecord::Base.transaction do
      run_callbacks(:restore) do
        # update_column paranoia_column, nil
        update_column paranoia_column, 0
        restore_associated_records if opts[:recursive]
      end
    end
  end

  private

  def touch_paranoia_column(with_transaction=false)
    if with_transaction
      # with_transaction_returning_status { touch(paranoia_column) }
      with_transaction_returning_status { update_attribute(paranoia_column, id) }
    else
      # touch(paranoia_column)
      update_attribute(paranoia_column, id)
    end
  end
end

class ActiveRecord::Base
  def self.acts_as_paranoid(options={})
    alias :really_destroy! :destroy
    alias :destroy! :destroy
    alias :delete! :delete
    include Paranoia
    class_attribute :paranoia_column

    # self.paranoia_column = options[:column] || :deleted_at
    self.paranoia_column = options[:column] || :deleted
    # default_scope { where(paranoia_column => nil) }
    def self.default_scope
      where(arel_table[paranoia_column].eq 0)
    end

    before_restore {
      self.class.notify_observers(:before_restore, self) if self.class.respond_to?(:notify_observers)
    }
    after_restore {
      self.class.notify_observers(:after_restore, self) if self.class.respond_to?(:notify_observers)
    }
  end
end

●ベースモデル作成

$ rails g model base
===========================================
invoke active_record
create db/migrate/20140707233955_create_bases.rb
create app/models/base.rb
invoke rspec
create spec/models/base_spec.rb
invoke factory_girl
create spec/factories/bases.rb
===========================================
※エラーが表示されなければOK
削除:db/migrate/20140707233955_create_bases.rb
削除:spec/factories/bases.rb
編集:app/models/base.rb

class Base < ActiveRecord::Base
  self.abstract_class = true
  acts_as_paranoid
end

●テストモデル作成

$ rails g model test
===========================================
invoke active_record
create db/migrate/20140707234352_create_tests.rb
create app/models/test.rb
invoke rspec
create spec/models/test_spec.rb
invoke factory_girl
create spec/factories/tests.rb
===========================================
※エラーが表示されなければOK
編集:db/migrate/20140707234352_create_tests.rb

class Tests < ActiveRecord::Migration
  def change
	create_table :tests do |t|
      t.string :name, null: false
      t.timestamps
      t.integer :deleted, null: false, default: 0
    end
    add_index :tests, [:name, :deleted], unique: true
  end
end

$ rake db:migrate
※エラーが表示されなければOK
編集:app/models/test.rb

class Test < ActiveRecord::Base
class Test < Base
end

○動作確認

$ rails cTest.create(name: ‘test’)
test = Test.find_by_name(‘test’)
test.blank?
=> false
test.destroy
=> UPDATE文
test = Test.find_by_name(‘test’)
test.blank?
=> true
exit

●テストモデル削除

削除:app/models/test.rb
削除:db/migrate/20140707234352_create_tests.rb
削除:spec/models/test_spec.rb
削除:spec/factories/tests.rb
$ rake db:migrate:reset
※エラーが表示されなければOK

jQuery flotr2 〜グラフ〜

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

rick No33です。
今回は、jQueryのプラグインflotr2を使用して簡単にきれいなグラフを作成します

環境

jQuery 2.1.1

インストール

http://humblesoftware.com/flotr2/よりダウンロード

手順

ダウンロードした中のflotr2.min.jsを読み込む

<!– 出力先 –>
<div id=”graph1″ style=”width:800px;height:300px;”></div>

<!– グラフ作成 –>
<script>
$(document).ready(function(){
var graph=[{data: [[1,10], [2,20], [3, 5]], label: “グラフ1”, yaxis: 1}, {data: [[1,30], [2,1], [3, 15]], label: “グラフ2”, yaxis: 1}];
var option={
title: “グラフ名”,
legend: {
position: “se” //ラベル表示位置(右下)
},
xaxis: {
title: “x軸名”,
ticks: [[1,”1個目”], [2, “2個目”], [3, “3個目”]]
}
}
Flotr.draw($(#graph1)[0], graph, options);
})
</script>

これだけです。 他にも色々グラフがあるので試してみてください。

[Ruby] 複数サーバの同じファイルをtail -fするプログラムを作ってみよう

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

複数サーバの同じファイルをtail -fするプログラムを作ってみよう

 この記事について

サーバでの作業にはかかせないログの監視。

tail -fは多くの方が利用されていると思います。

台数が少なければいいのですが、増えてくると一台ずつ

サーバにログインして→”tail -f <パス名>”とタイプして・・・

が面倒になってきます。

今回はこの手間を省くスクリプトを作成しようというのがテーマです。

 net/ssh/multi

今回使うrubygemsライブラリはnet-ssh-multiです。

複数のサーバにログインして同じ操作を行うことができるものです。

 プログラム作成

いきなりですが、以下のように作成しました。

#!/usr/bin/env ruby
#-*- coding: utf-8 -*-
require 'rubygems'
require "net/ssh/multi"
SSH_CONFIG = File.join(ENV['HOME'], ".ssh/config")

def main(hosts, paths)
  Net::SSH::Multi.start do |session|
    hosts.each do |host|
      connect_host(session, host)
    end
    tail_f(session, paths)
    session.loop
  end
rescue Interrupt
end

def connect_host(session, host)
  option = {}
  option[:config] = SSH_CONFIG if SSH_CONFIG && File.exists?(SSH_CONFIG)
  session.use host, option
end

def tail_f(session, paths)
  session.open_channel do |channel|
    channel.on_data do |ch, data|
      data.each_line do |line|
        puts ["[#{ch[:host]}]", line].join("\t")
      end
    end
    channel.exec "tail -f #{paths.join(" ")}"
  end
end    

if __FILE__ == $0
  raise "usage: mtailf <host ,host,...> <file file file...>" if ARGV.length < 2
  hosts = ARGV[0].split(/\s*,\s*/)
  paths = ARGV[1 .. -1]
  main(hosts, paths)
end

ポイントを説明していきます。

SSH_CONFIG = File.join(ENV['HOME'], ".ssh/config")

ssh_configの設定が使えるようにssh/configファイルの場所を指定しておきます。

def main(hosts, paths)
  Net::SSH::Multi.start do |session|
    hosts.each do |host|
      connect_host(session, host)
    end
    tail_f(session, paths)
    session.loop
  end
rescue Interrupt
end

mainメソッドです。

Net::SSH::Multiのブロック内でサーバへのコネクションをはり、

tailコマンドを実行しています。

tail -f はctrl+cを押すまで処理が続くので

session.loop

を実行しています。

def connect_host(session, host)
  option = {}
  option[:config] = SSH_CONFIG if SSH_CONFIG && File.exists?(SSH_CONFIG)
  session.use host, option
end

サーバへの接続です。

session.use で接続します。

def tail_f(session, paths)
  session.open_channel do |channel|
    channel.on_data do |ch, data|
      data.each_line do |line|
        puts ["[#{ch[:host]}]", line].join("\t")
      end
    end
    channel.exec "tail -f #{paths.join(" ")}"
  end
end

リモートホストに対してtail -fを実行している部分です。

channel.execでコマンドを実行して

ホストから応答があったら

channel.on_data

のブロックが実行されるかんじです。

JavaScriptっぽいですね。

ch変数にはホスト名も含まれているので

コンソールに出力する際には

puts [“[#{ch[:host]}]”, line].join(“\t”)

として、どのホストからの出力なのかがわかるようにしています。

 使い方

上記のプログラムを作成したら

ファイル名はmtailf とします。

そして

chmod 755 mtailf

と実行可能なようにパーミッションを設定します。

net-ssh-multiをインストールします。

gem install net-ssh-multi

ここまでが初期設定で、実際に使うときには

mtailf user@host01,user@host02 /path/to/file

のようにします。

引数は

第一引数→ホスト名をカンマ区切りで

第二引数以降→表示するファイルパス

以下は出力イメージです。

[host01]   ファイル行・・・・・・・・・・・
[host02]   ファイル行・・・・・・・・・・・
・
・
・
・

終了方法は

tail -fと同様に

ctrl+c です。

このコマンド打つのも面倒!となったら・・・・・・

aliasとか設定しましょう!

【LiveValidation】ブラウザ上でバリデーションを行う【RAILS/JS】

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

LiveValidation.jsを使用するとブラウザ上でバリデーションを行えます。

 動作環境

Ruby 1.8.7,1.9.3

Rails 2.3.15

prototype.jsがある

 1.「LiveValidation.js」をダウンロード

ここからダウンロードできます

 2.ダウンロードしたものをRailsのpublicディレクトリに配置(prototype.jsと同じ階層)

 3.Viewで使ってみる

JSを読み込み、テキストフィールド等のIDに対するscriptを書くだけ

	~~~~~~~~~~~
	~~~~~~~~~~~
	<%= javascript_include_tag "livevalidation_prototype" %>
	~~~~~~~~~~~
	~~~~~~~~~~~
	
	<%= :text_field, :product, :name, :id => '/product_name'/ %> 
	この下に↓を追加
	<script type="text/javascript">
	        var product_name = new LiveValidation('product_name', { validMessage: 'OKです', wait: 500 });
	        product_name.add(Validate.Presence, {failureMessage: "未入力です"});
	        //product_name.add(Validate.Length, { minimum: 3, maximum: 255,
	tooLowMessage: '3文字以上で', tooHighMessage: '255文字以下で' });
	        //product_name.add(Validate.Numericality, { minimum: 3, maximum: 255,
	tooLowMessage: '数字は3から', tooHighMessage: '数字は255まで' });
	        //product_name.add(Validate.Format, {pattern: /^会社名$/i, 
	failureMessage: "'会社名'と入力してください" } );
	</script>
	
	

Rails3系だとProduct.validatorsでバリデーションの設定がとれるのでペルパーとかを作成してもっとスマートにできそうです。

MyBatis Generator紹介

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

MyBatis Generator紹介

 MyBatis Generatorとは

データベースのスキーマを元にMyBatisが使用する各種ファイルを自動生成するためのツールです。

MyBatisを使う場合は基本的にSQLを手書きする必要がありますが、大量のXMLとJavaBeanファイルを手で書くのはミスの原因にもなりますし健康にも良くありません。

テーブルが多くて、カラム数が多い場合、MyBatis Generatorを使えば、効率はすごくアップしますし、ミスも少ないです。

 ダウンロード先:

https://code.google.com/p/mybatis/downloads/list?can=1&q=Product%3DGenerator

 使い方

●ファイル置き場:

D:\mybatis\generator
 ・src(保存先)
 ・mybatis-generator-core-1.3.2.jar(実行ファイル)
 ・generator.xml(設定ファイル)

●設定ファイル:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorconfiguration>
	-->
	<classpathentry location="D:\lib\ojdbc6.jar" />
	<context id="DB2Tables" targetruntime="MyBatis3">
		<commentgenerator>
			<property name="suppressAllComments" value="true" />
		</commentgenerator>
		-->
		<jdbcconnection driverclass="oracle.jdbc.driver.OracleDriver" connectionurl="jdbc:oracle:thin:@192.168.111.111:1521:XE" userid="xxxx" password="xxxx">
		</jdbcconnection>
		<javatyperesolver>
			<property name="forceBigDecimals" value="false" />
		</javatyperesolver>
		-->
		<javamodelgenerator targetpackage="com.test.webapp.model" targetproject="D:\mybatis\generator\src">
			<property name="enableSubPackages" value="true" />
			<property name="trimStrings" value="true" />
		</javamodelgenerator>
		-->
		<sqlmapgenerator targetpackage="com.test.webapp.mapping" targetproject="D:\mybatis\generator\src">
			<property name="enableSubPackages" value="true" />
		</sqlmapgenerator>
		-->
		<javaclientgenerator type="XMLMAPPER" targetpackage="com.test.webapp.persistence" targetproject="D:\mybatis\generator\src">
			<property name="enableSubPackages" value="true" />
		</javaclientgenerator>
		-->
		-->
			<generatedkey column="id" sqlstatement="SELECT TEST_TABELE_SEQ.nextval FROM dual" type="pre" />
		
	</context>
</generatorconfiguration>

●実行

コマンド:

java -jar D:\myibatis\generator\mybatis-generator-core-1.3.2.jar -configfile generator.xml -overwrite

●結果:

下記ファイルが作成されます。

TestTableMapper.xml

TestTable.java

TestTableMapper.java

RewriteRuleとWordPressのRedirectionで日本語URLを扱う方法

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

WordPressのページ構成変更に伴い日本語URLを日本語URLにリダイレクトしようした時にハマったのでメモしておきます。
最終的には、WordPressのRedirectionプラグインを使う事にしましたが、RewriteRuleでも成功しています。
書き方が違うので注意が必要です。

RewriteRuleを使う場合

Apacheの設定ファイルまたは.htaccessでmod_rewriteを有効にしてある前提です。
「http://example.com/ほげ」にアクセスされた場合に「http://example.com/ほげほげ」にリダイレクトする例です。
RewriteRule ^/\xe3\x81\xbb\xe3\x81\x92$ /\%e3\%81\%bb\%e3\%81\%92\%e3\%81\%bb\%e3\%81\%92 [NE,R=301,L]
「ほげ」をUTF-8でURLエンコードしたのはこれ「%e3%81%bb%e3%81%92」です。
転送元のURIは「%」を「\x」に、転送先のURIは「%」を「\%」とエスケープしてあげる必要があります。
また、最後に「NE」を追加して、エスケープしないようにしてあげる必要があります。
面倒ですね。

WordPressのRedirectionプラグインを使う場合

WordPressにRedirectionプラグインがインストール&有効化されている前提です。
RewriteRuleと同様に「http://example.com/ほげ」にアクセスされた場合に「http://example.com/ほげほげ」にリダイレクトする例です。
ソースURL:/%e3%81%bb%e3%81%92
ターゲットURL:/\%e3\%81\%bb\%e3\%81\%92\%e3\%81\%bb\%e3\%81\%92
「ほげ」をUTF-8でURLエンコードしたのはこれ「%e3%81%bb%e3%81%92」です。
転送元のURIはそのまま(RewriteRuleと異なる)で、転送先のURIは「%」を「\%」(RewriteRuleと同様)とエスケープしてあげる必要があります。
やはり面倒ですね。

MyBatisでwhere要素を使った時になぜか先頭の「AND」が取り除かれなかった時の話

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

仕事でMyBatisを使っているのですが、select文を書いてる際にWHERE句を編集したらSQLがエラーになりました。
ひょっとしたら同じ事でコケる人もいるかもしれないので原因を書いておきます。

MyBatisでSQLを定義するXMLのwhere要素には、中の文字列の先頭がAND|ORだったらそれを取り除く機能があるのですが、今回なぜかそれが機能しませんでした。

よくチェックすると、文字列のANDの後が半角スペースでなく、タブで入っていました。
タブを半角スペースに置き換えたところ、正常にSQLが流れるようになりました。

インデントにタブを使っていると同様の事態に引っかかるかもしれません。
どうしてもタブで書きたいという場合はwhere要素を使わずに
prefixOverrides=”AND★|OR★”(★をタブに置き換える)
とするとうまくいくかもしれません。

参考:
http://mybatis.github.io/mybatis-3/ja/dynamic-sql.html

システムのせいでタグがエスケープしても取り除かれてしまうので色々省略して書いてます。

続・たった1コマンドでネットショップ構築できちゃうelecoma-vagrant

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

通販サイトをたった1コマンドで簡単に構築できる「elecoma-vagrant」のしくみについて紹介します。

■ 概要

以前の記事」で紹介した「elecoma-vagrant」のに利用されている chef + vagrant の技術について、スライドにまとめました。

『オープンソースカンファレンス2014 Tokyo/Spring』のブースにてデモを交えて解説する予定ですので、是非お越し下さい!

■ 本編(スライド)

xinetd(ザイネットディー) 〜sshのIP制限とアクセスログの記録〜

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

rick No32です。
今回は、xinetdといわれるサービスを管理し、必用なときに起動させるデーモンです。
このxinetdでsshのIP制限とアクセスログの記録を行います。

インストール

yum -y install xinetd
> xinetd.x86_64 2:2.3.14-20.el5_10
ls /etc
> xinetd.confとxinetd.dフォルダが作成される

SSH設定

// 自動起動
chkconfig xinetd on
service sshd stop
chkconfig sshd off
cd /etc/xinetd.d/
vim ssh
————————
// 設定するサービス名を指定(/etc/servicesファイルに定義されたサービス名を指定)
service ssh
{
// サービスの停止指定
// no=サービスを起動する
disable =no
// サービスが使用するソケットタイプ
// stream=TCP
// dgram=UDP
// raw=IPダイレクトアクセス
// seqpacket=sequenced packet
socket_type =stream
// マルチスレッド
// yes=1つしかsshを受け付けない
// no=リミットまで受け付ける
wait =no
// サービスを実行するユーザ名
user =root
// 起動するサービスを絶対パスで指定
server =/usr/sbin/sshd
// サービスに渡す引数
server_args =-i
// 接続に成功した場合に記録するログの種類を指定(下記をスペース区切りで繋げられる)
// PID=サービスのプロセスID
// HOST=リモートホストのIPアドレス
// USERID=リモートホストの認証ユーザ名(マルチスレッド+TCPでのみ有効)
// EXIT=サービス終了シグナルを受けたのかを記録
// DURATION=セッション周期(秒)
log_on_success += DURATION HOST USERID
// 接続に失敗した場合に記録するログの種類を指定
// HOST=リモートホストのIPアドレス
// USERID=リモートホストの認証ユーザ名(マルチスレッド+TCPでのみ有効)
// ATTEMPT=起動に失敗した場合の記録
// RECORD=サービスが起動しなかった場合の諸事情を記録
//  設定するとエラー Bad log_on_failure flag: RECORD [file=/etc/xinetd.d/ssh] [line=10]
log_on_failure += HOST USERID
}
———————–
/etc/init.d/xinetd start

共通設定

vim /etc/xinetd.conf
defaults
{
// ログ出力方法を定義
log_type = SYSLOG daemon info
log_on_failure = HOST
log_on_success = PID HOST DURATION EXIT
// 毎秒ごとの可能な接続数上限の設定
// 第1引数=1秒間で処理可能な数
// 第2引数=上限を超えた後サービスが停止し再開するまでの時間
cps = 50 10
// 起動できるデーモンの数
instances = 50
// 同じクライアントからの接続数制限
per_source = 10
v6only = no
groups = yes
umask = 002
}

ログ出力

基本は
/var/log/messages
に出力される
別ファイルに出したい場合は
log_type = FILE ファイル名
を指定するとファイルにログが吐出される(log_typeは型も含めて2つで問題ない)

IP制限

個別指定に
lonly_from = 192.168.7.{23,91}
のようにすると23と91だけ許可になる

注意点

selinuxは停止しておかないと接続できない可能性があります。

[Ruby] コンソールアプリで表を出力しよう

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

コンソールアプリで表を出力しよう

 この記事について

メンテナンスや日々の確認のために

ちょっとしたスクリプトを書くことがあります。

データなどを確認したい場面では

タブ区切りでのテキストを出力していましたが

データ量が大きくなると視認性が悪くなります。

そこでコンソール上でも表を出力したい!

となります。

※記事内でソースコードを変更する箇所がありますが、利用する場合は自己責任でお願いします。

 formatador

rubygemsでインストールができる

formatador

を使うこととします。

簡単な使い方は以下の通りです。

#-*- coding: utf-8 -*-
require "rubygems"
require "formatador"

data = [
  {:name => "taro", :age => 30},
  {:name => "hanako", :age => 25}
]

Formatador.display_compact_table(data, [:name, :age])
#第2引数で列順を指定
  +--------+-----+
  | name   | age |
  +--------+-----+
  | taro   | 30  |
  | hanako | 25  |
  +--------+-----+

実行すると上記のように表が出力されます。

 日本語表示でずれる

スクリプト上のdataを以下のように変更して出力してみます。

data = [
  {:name => "太郎", :age => 30},
  {:name => "花子", :age => 25}
]
  +------+-----+
  | name | age |
  +------+-----+
  | 太郎   | 30  |
  | 花子   | 25  |
  +------+-----+

ずれました。

マルチバイト文字に対応させてみます。

formatador-0.2.4/lib/formatador/table.rb

を以下のように修正しました。

--- table.rb.org 
+++ table.rb 
@@ -1,3 +1,9 @@
+class String
+  def width
+    chars.inject(0){|s,c| s += c.bytesize > 1 ? 2 : 1}
+  end
+end
+
class Formatador
   def display_table(hashes, keys = nil, &block)
     new_hashes = hashes.inject([]) do |accum,item|
@@ -14,7 +20,7 @@
     # Calculate Widths
     if hashes.empty? && keys
       keys.each do |key|
-        widths[key] = key.to_s.length
+        widths[key] = key.to_s.width
       end
     else
       hashes.each do |hash|
@@ -52,7 +58,7 @@
     # Display data row
     columns = []
     headers.each do |header|
-      columns << "[bold]#{header}[/]#{' ' * (widths[header] - header.to_s.length)}"
+      columns << "[bold]#{header}[/]#{' ' * (widths[header] - header.to_s.width)}"
     end
     display_line("| #{columns.join(' | ')} |")
     display_line(split)
@@ -80,7 +86,7 @@
   private

   def length(value)
-    value.to_s.gsub(PARSE_REGEX, '').length
+    value.to_s.gsub(PARSE_REGEX, '').width
   end

   def calculate_datum(header, hash)

再度出力してみます。

  +------+-----+
  | name | age |
  +------+-----+
  | 太郎 | 30  |
  | 花子 | 25  |
  +------+-----+

ずれなくなりました!

環境

ruby 1.9.3p484

formatador 0.2.4

少人数チームにおけるプロジェクト管理のベストプラクティス

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Redmine+Git+GitLab+Jenkinsを使ったプロジェクト管理について、自らの経験して実際にかなり利便性が高いと思えた内容をフロー化してスライドにまとめてみました。

■ 概要

チケット管理ツール(Redmine), バージョン管理ツール(Git), Git管理ツール(GitLab), CIツール(Jenkins) を総合的に利用した少人数チームでのプロジェクト管理について、自らの経験して実際にかなり利便性が高いと思えた内容をフロー化してスライドにまとめてみました。

前置きが長くても仕方がないので早速下記スライドをご覧ください。

■ 本編(スライド)

■ 関連リンク

Comableのページ: hyoshida/comable

Ruby技術者認定試験(Gold)合格経験

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Ruby技術者認定試験(Gold)合格経験

 【使用教材】

Ruby リファレンスマニュアル(オンラインのドキュメント)

Ruby公式資格教科書 Ruby技術者認定試験Silver/Gold対応

メタプログラミングRuby

 【勉強方法】

Ruby公式資格教科書

教科書のとおりに、Gold範囲をしっかり読む。

ガイドの説明をしっかり読んだほうが効率的です。

3章、4章、5-9~5-12は重要です

2章 実行環境 2,3問ぐらい

* コマンドラインオプション

* 組み込み変数/定数

3章 文法 15問ぐらい

* 変数と定数

* 演算子

* ブロック

* 例外処理

* 大域脱出

4章 オブジェクト指向 (試験の肝の部分です)25問くらい。

* メソッドの詳細

* メソッドの可視性

* クラスの詳細

* クラスの継承

* モジュールの詳細

5-9~5-12 組み込みライブラリ 10問くらい。

* よく使用されるクラス(Object、Kernel、Module等)

* よく使用されるモジュール(Enumerable、Comparable等)

* 数値

* 正規表現

正規表現は基本的な使い方以外にも、 *? や +? などの最短マッチも出題されます。

6章 標準添付ライブラリ 2,3問ぐらい

* よく使用されるライブラリ(socket、rdoc等)

メタプログラミングRuby 

 オブジェクト指向について、教科書がない内容があります。

 自分はヨドバシで立って読みました。

irbで試す(重要)

自分が試した注意点を共有します。

—————————————————————————-

クラスメソッドはクラスしか呼び出さない。

class C

def self.m

p “2”

end

end

C.new.m # undefined method `m’ for #<c:0x1fd7a80> (NoMethodError)

C.m # 2

—————————————————————————-

モジュールをクラスに extend すれば、モジュールのインスタンスメソッドがクラスメソッドになる

module Foo

def foo

p “foo”

end

end

class Hoge

extend Foo

end

Hoge.new.foo #undefined method `foo’ for #<hoge:0x1f07868> (NoMethodError)

Hoge.foo # foo

—————————————————————————-

initialize を定義しない場合は親クラスの initialize が自動で呼ばれます

class M1

def initialize

p “m1”

end

end

class M2 < M1

end

M2.new  #m1

—————————————————————————-

super と super() は動作が異なります

super は現在のメソッドがオーバーライドしているメソッドを呼び出します。

括弧と引数が省略された場合には現在のメソッドの引数がそのまま引き 渡されます。

引数を渡さずにオーバーライドしたメソッドを呼び出すには super() と括弧を明示します。

例:

class Foo

def foo(arg=nil)

p arg

end

end

class Bar < Foo

def foo(arg)

super(5) # 5 を引数にして呼び出す

super(arg) # 5 を引数にして呼び出す

super # 5 を引数にして呼び出す super(arg) の略記法

arg = 1

super # 1 を引数にして呼び出す super(arg) の略記法

super() # 引数なしで呼び出す nil

end

end

Bar.new.foo 5

—————————————————————————-

インスタンス変数

class Sample

@value = “hello”

def value

@value

end

end

Sample.new.value #=> nil

Sample.class_eval { @value } #=> “hello”

クラス定義の中で初期化した、@valueは Sampleクラスオブジェクトのインスタンス変数で、

メソッド定義の中で、初期化した、@valueはSampleクラスオブジェクトのインスタンスのインスタンス変数になる。

インスタンス変数はクラス内で全メソッドで共通して使用することが出来ます。最初にどこかのメソッドで使用された時点でインスタンス変数は作成され、一度作成されたインスタンス変数は他のメソッドで値を取り出したり格納したりすることが出来るようになります。

class Foo

@v1=1

def self.read; @v1;end

def write; @v1=2;end

def read; @v1;end

end

=> nil

irb(main):017:0> obj=Foo.new

=> #<foo:0x000000174ce908>

irb(main):018:0> obj.write

=> 2

irb(main):019:0> obj.read

=> 2

irb(main):020:0> Foo.read

=> 1

—————————————————————————-

クラスとsuperクラス

irb(main):032:0* foo=Foo.new

=> #<foo:0x0000001d9caf78>

irb(main):033:0> foo.class

=> Foo

irb(main):034:0> foo.superclass

NoMethodError: undefined method `superclass’ for #<foo:0x0000001d9caf78>

from (irb):34

from /home/search/ruby/bin/irb:12:in `<main>’

irb(main):035:0> Foo.class

=> Class

irb(main):036:0> Foo.superclass

=> Object

irb(main):037:0> Class.class

=> Class

irb(main):038:0> Class.superclass

=> Module

irb(main):007:0> Module.superclass

=> Object

—————————————————————————-

クラス変数。トップレベル:object 全てのクラスの親クラス。

クラス変数は、親と子共有で使う

irb(main):001:0> @@v1=1

=> 1

irb(main):002:0> class Foo

irb(main):003:1> @@v1=2

irb(main):004:1> end

=> 2

irb(main):005:0> p @@v1

2

=> 2

—————————————————————————-

Object extend

irb(main):001:0> module M

irb(main):002:1> def m;”aa”;end

irb(main):003:1> end

=> nil

irb(main):004:0> obj=Object.new

=> #<object:0x00000011384670>

irb(main):005:0> obj.extend M

=> #<object:0x00000011384670>

irb(main):006:0> obj.m

=> “aa”

irb(main):007:0> class C

irb(main):008:1> extend M

irb(main):009:1> end

=> C

irb(main):010:0> C.m

=> “aa”

irb(main):011:0> c.new.m

NameError: undefined local variable or method `c’ for main:Object

from (irb):11

from /home/search/ruby/bin/irb:12:in `<main>’

—————————————————————————-

以下の2つのコード、include の位置を変えると表示される結果が異なります。

コード1

module M

@@x = 100

end

class A

@@x = 500

include M

end

module M

puts @@x # 100 と表示される

end

class A

puts @@x # 100 と表示される

end

コード2

module M

@@x = 100

end

class A

include M

@@x = 500

end

module M

puts @@x # 500 と表示される

end

class A

puts @@x # 500 と表示される

end

—————————————————————————-

モジュールで定義されたクラス変数(モジュール変数)は、そのモジュールをインクルードしたクラス間でも共有されます。

module Foo

@@foo = 1

end

class Bar

include Foo

p @@foo += 1 # => 2

end

class Baz

include Foo

p @@foo += 1 # => 3

end

—————————————————————————-

親クラスに、子クラスで既に定義されている同名のクラス変数を追加した場合には、 子クラスのクラス変数は子クラスで保存されます。

上書きされません。

class Foo

end

class Bar < Foo

@@v = :bar

end

class Foo

@@v = :foo

end

class Bar

p @@v #=> :bar

end

class Foo

p @@v #=> :foo

end

—————————————————————————-

トップレベルで定義すれば、Object中に定義され、親なので、子供も共有。下記は同じになる。

@@v=1

class Foo

end

class Bar < Foo

@@v = :bar

end

class Foo

@@v = :foo

end

class Bar

p @@v #=> :foo

end

class Foo

p @@v #=> :foo

end

p @@v #=> :foo

irb(main):002:0> @@v=1

=> 1

irb(main):003:0> class Foo

irb(main):004:1> @@v=2

irb(main):005:1> end

=> 2

irb(main):006:0> @@v

=> 2

irb(main):001:0> class Foo

irb(main):002:1> @@v=1

irb(main):003:1> end

=> 1

irb(main):004:0> p @@v

NameError: uninitialized class variable @@v in Object

from (irb):4

from /usr/bin/irb:12:in `<main>’

—————————————————————————-

irb(main):007:0> Module.class

=> Class

irb(main):008:0> Class.class

=> Class

irb(main):009:0> Class.superclass

=> Module

irb(main):005:0> Class.ancestors

=> [Class, Module, Object, Kernel]

irb(main):010:0> Module.superclass

=> Object

irb(main):001:0> module Foo

irb(main):002:1> @@foo = 1

irb(main):003:1> end

=> 1

irb(main):004:0> Foo.class

=> Module

irb(main):006:0> Foo.ancestors

=> [Foo]

irb(main):001:0> class Foo

irb(main):002:1> end

=> nil

irb(main):003:0> Foo.class

=> Class

irb(main):004:0> Foo.ancestors

=> [Foo, Object, Kernel]

—————————————————————————-

可変引数

可変長引数のデフォルト値は指定できません。

引数は配列として扱われる。

可変長引数は一つのメソッドは一つしか指定できません。

def foo(*args)

p args

end

def bar(*args)

foo(args)

end

def hoge(*args)

foo(*args)

end

実行結果は以下のとおり

bar(1, 2, 3, 4, 5) #=> [[1, 2, 3, 4, 5]]

hoge(1, 2, 3, 4, 5) #=> [1, 2, 3, 4, 5]

foo, bar = [1, 2] # foo = 1; bar = 2

foo, bar = 1, 2 # foo = 1; bar = 2

foo, bar = 1 # foo = 1; bar = nil

foo, bar, baz = 1, 2 # foo = 1; bar = 2; baz = nil

foo, bar = 1, 2, 3 # foo = 1; bar = 2

foo = 1, 2, 3 # foo = [1, 2, 3]

*foo = 1, 2, 3 # foo = [1, 2, 3]

foo,*bar = 1, 2, 3 # foo = 1; bar = [2, 3]

—————————————————————————-

合格者の合格経験を見る

http://rochefort.hatenablog.com/category/gold

 【感想】

Silverよりかなり難しいですが、試験内容は上記です。

ですので、重要な部分を理解して、irbで注意点などしっかり把握すれば、合格できます。

PHP5技術者認定初級試験を受験しました

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

所属のSaaS部の案件の多くは ruby や java といった言語を使用していますが、php の案件も増えています。昨年、アピリッツの資格支援の対象にPHP技術者認定試験が加えられたこともあり、先週2014年1月25日に PHP5技術者認定初級試験を受験しましたので報告します。

 試験対策について

業務で php を使っていれば初級試験の試験範囲はほぼ網羅できていると考えらますが、

試験ですので、試験特有の罠にかからないように試験用の学習はする必要があると考えましょう。要するに試験問題をひと通り解いて問題の作り方のパターンや引っ掛けの選択肢のパターンを把握しましょう。LPIC Level 1や Ruby Silver もそうですが初級の試験は回答の選択肢だけ見て問題文を見ずに正解を出すといった裏技が使えることがありま…。

 実務学習での盲点

・ セキュリティー対策やDB接続、出力バッファの制御の箇所は業務ではフレームワーク任せにしていたた為、知識が曖昧になっており失点しておりました。実害なく自分の弱点が把握できるのが試験の良いところです。セキュリティー対策として14種類の攻撃方法への対応方法を今学習しています。

・ PHPのオブジェクト指向の傾向についての出題があり、いくつか耳慣れない用語が登場しました。オブジェクト指向についての一般的な知識も整理しておくと良いです。

 試験概要・出題範囲

下記サイトで確認できます。書籍教材の紹介のリンクもあります。

http://www.phpexam.jp/summary/novice/

 学習サイト

下記を利用させて頂きました。

http://jibun.atmarkit.co.jp/scenter/ittrain/122_today_q.html

rubyの学習サイト「ミニツク」程ではありませんが初級試験対策の心強い味方です。

 書籍教材

『徹底攻略 PHP5技術者認定[初級]試験 公式問題集』が社内にありましたので利用させて頂きました。

上記サイトと重複する内容もありますが通信環境が不要なのが書籍の便利なところです。

 申し込み

PHP5技術者認定初級試験はプロメトリック社で受験を申し込みます。

http://it.prometric-jp.com/testlist/php/index.html

 注意点

試験は php-5.1がベースです。

現在 安定版は 5.5.8, 5.4.24, 5.3.28 となっており、記述方法の進化や関数の入れ替えがあったりします。詳細については下記にURLを記載しましたPHPマニュアルをご参照ください。

SQLも出題されます。

insert, update, delete 文や where, order by, limit 句 sum() 関数といった基本的なものは理解しておきます。

プロメトリック社の試験場での受験です。

3営業日以上前に申し込みが必要(以前、LPIC認定試験を受けたピアソンVUEは前日でも申し込み可でした)

2つの身分証明書(1つは顔写真入り)の提示が必要です。

 Doruby参考記事

下記を参考にさせて頂きました。

お金をかけずにRuby技術者認定試験(Silver)に合格する方法

http://doruby.kbmj.com/hyoshida/20131115/_Ruby_Silver_1

※ Ruby Silver の取得方法ですがプロメトリック社の受験時の注意点も参考になります。

 PHPマニュアル

http://jp2.php.net/manual/ja/

PHP 5.4.x から PHP 5.5.x への移行

http://jp2.php.net/manual/ja/migration55.php

PHP 5.3.x から PHP 5.4.x への移行

http://jp2.php.net/manual/ja/migration54.php

PHP 5.2.x から PHP 5.3.x への移行

http://jp2.php.net/manual/ja/migration53.php

PHP 5.1.x から PHP 5.2.x への移行

http://jp2.php.net/manual/ja/migration52.php

Ruby技術者認定試験(Silver)合格経験

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Ruby技術者認定試験(Silver)合格経験

 【試験難易度】

初心者向けです。

 【使用教材】

Ruby リファレンスマニュアル(オンラインのドキュメント)

Ruby公式資格教科書 Ruby技術者認定試験Silver/Gold対応

 【勉強方法】

①Stringクラス、Arrayクラス、Hashクラスのメソッドを覚える。

②教科書のとおりに、Silver範囲のみを読んで大丈夫です。

ガイドの説明をしっかり読んだほうが効率的です。

③irbで試す

 【感想】

関数に関する問題が多いです。

教科書しっかり把握すれば、合格できます。

ruby で * を活用してパスワード生成するプログラムを短く書く

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

ruby の学習をしていたときに * (アスタリスク)の使い方で面白いと思ったことがありましたので紹介します。

 *の使い方のここが面白い

  1. レンジの前につけると配列を生成する。
  2. 文字列に対して文字列を引数として*を使用すると join となる。

※ 文字列に数字を*するとその数字の回数文字列を繰り返すのも勿論言語として面白いです。

 こんな感じ

そこで、例えば12文字の英数パスワード生成スクリプトは下記の書き方ができます。

[*0..9, *’A’..’Z’, *’a’..’z’].sample(12)*”

内容は下記となります。

  1. []で配列を作成します。
  2. *0..9 で 0から9までの数字が1つずつ入った配列を生成します。
  3. *’A’..’Z’ で AからZまでの半角大文字アルファベットが1つずつ入った配列を生成します。
  4. *’a’..’z’ で aからzまでの半角小文字アルファベットが1つずつ入った配列を生成します。
  5. sample(12) で配列からランダムに要素を12個取り出した配列を返します。
  6. *” で join(”) となり配列の要素を ” で繋いだ文字列を返します。

どんなオブジェクトにどんなオブジェクトを渡すかによってメソッドの挙動が変わるところが興味深いです。

 参考記事

rubyでパスワードを自動生成するプログラムの紹介

http://doruby.kbmj.com/nakahira_on_rails/20091026/ruby_password_

Oracleでカレンダーテーブルを使わずに日付を列挙する方法

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

以前、Postgresqlで日付を列挙する方法を書きましたが

今回はOracleでの列挙方法について書きます。

 カレンダーテーブルがない!日付列挙したい!

postgresqlの場合→ http://doruby.kbmj.com/hotdog/20120920/Postgresql_generate_series_

今回はOracle、どうやったら実現できるのだろうか?

 答え(2014年2月の一カ月分を列挙する場合)

SELECT TO_DATE(STARTDATE + ROWNUM -1, ‘YYYY/MM/DD’) AS CALENDAR

FROM

(SELECT TRUNC(TO_DATE(‘2014-02-01’)) AS STARTDATE,

TO_NUMBER(TO_CHAR(LAST_DAY(TO_DATE(‘2014-02-01’)), ‘DD’)) AS DAYS

FROM DUAL),

ALL_CATALOG

WHERE ROWNUM <= DAYS

以下出力結果

CALENDAR

14-02-01

14-02-02

14-02-03

14-02-04

14-02-05

14-02-06

14-02-07

14-02-08

14-02-09

14-02-10

14-02-11

14-02-12

14-02-13

14-02-14

14-02-15

14-02-16

14-02-17

14-02-18

14-02-19

14-02-20

14-02-21

14-02-22

14-02-23

14-02-24

14-02-25

14-02-26

14-02-27

14-02-28

次回はMySQLでの取得方法を書きたいです。

DBの表表・レコード数を見る(Oracle / Postgresql)

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

表は、、表表のグラフ、表表、DBの表表、レコード数をします方法を書きます

 Postgresqlの

選択する

relname、

reltuples、

tablesizeとしてのpg_size_pretty(pg_table_size(oid))

FROM pg_class

WHERE relnamespace =(SELECT oid FROM pg_namespace WHERE nspname = ‘public’)

およびrelkindin( ‘r’、 ‘t’);

表→表名、レコード数、サイズがコメントあります。

 Oracle

選択する

table_name、

TO_NUMBER(

extractvalue(

xmltype(

dbms_xmlgen.getxml( ‘SELECT COUNT(*)c FROM’ || table_name))

、 ‘/ ROWSET / ROW / C’))rec_ccount、

trunc(bytes / 1024,0)テーブルサイズ

FROM user_tables

user_segments.segment_name = user_tables.table_nameでuser_segmentsに参加します

表→表名、レコード数、サイズがコメントあります。

たちあり。いきたいです。

【Rails】カレンダーで日付を入力する【protocalendar】

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

「protocalendar」を利用すると「date_select」で日付入力していた所がJSのカレンダーで入力できるようになります。

 動作環境

Ruby 1.8.7,1.9.3

Rails 2.3.15

 1.「protocalendar」をダウンロード

ここからダウンロードできます

 2.ダウンロードしたものをRailsのディレクトリに配置

	事前に「protocalendar」用のディレクトリを各場所に作っておきます。
	protocalendar-js-1.1.0/images/* → RAILS_ROOT/public/images/protocalendar/
	protocalendar-js-1.1.0/javascripts/* → RAILS_ROOT/public/javascripts/protocalendar/
	protocalendar-js-1.1.0/stylesheets/* → RAILS_ROOT/public/stylesheets/protocalendar/
	protocalendar-js-1.1.0/lib/* → RAILS_ROOT/public/javascripts/protocalendar/
	

 3.ヘルパー作成

Railsの「date_select」の様に使いたいのでへルパーを作成します。

	$ vim app/helpers/base_helper.rb
	======================
	def calendar_date_select(object,method,option= {},js_option= {})
	
	  tag = <<-EOS
	    #{date_select object,method,option}
	    #{image_tag("../images/protocalendar/icon_calendar.gif",:id => method)}
	    <script type="text/javascript">
	    SelectCalendar.createOnLoaded({
	    yearSelect: '#{object}_#{method}_1i',
	    monthSelect: '#{object}_#{method}_2i',
	    daySelect: '#{object}_#{method}_3i'},
	    triggers: ['#{method}'],
	    lang: 'ja',
	    showEffect: 'SlideDown', 
	    hideEffect: 'SlideUp',
	    startYear: #{option[:start_year]},
	    endYear: #{option[:end_year]}
	  EOS
	
	  js_option.each do |(key,value)|
	    tag << ',' + key.to_s + ": #{value}"
	  end
	
	  tag << '});</script>'
	
	  return tag
	end
	======================
	

 4.Viewで使ってみる

JSを読み込み、今まで「date_select」だった所を「calendar_date_select」に置き換えるだけで使う事ができます。

	~~~~~~~~~~~
	~~~~~~~~~~~
	<%= javascript_include_tag '/protocalendar/protocalendar'/ %>
	<%= javascript_include_tag '/protocalendar/lang_ja'/ %>
	<%= javascript_include_tag '/effects'/ %>
	<%= javascript_include_tag '/prototype'/ %>
	~~~~~~~~~~~
	~~~~~~~~~~~
	
	<%= date_select(:page, :updated_at_from %>
	↓このように置き換えるだけ
	<%= calendar_date_select(:page, :updated_at_from %>
	
	<%= date_select(:page, :updated_at_to,  :use_month_numbers => true, :include_blank => true %>
	↓オプションを付ける場合は{}がいります。
	<%= calendar_date_select(:page, :updated_at_to, {:use_month_numbers => true, :include_blank => true}%>
	

rails MySQLの文字列エスケープ

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

rails MySQLでの文字列エスケープ

 この記事について

今回はセキュリティ系のネタです。

Ruby on RailsではActiveRecord内部で文字列のエスケープを行うため、

SQLインジェクション対策を気にする場面が少ないかもしれません。

しかし、希にSQLのすべてや一部を生で記述する必要があったりして

文字列のエスケープに気を遣わなければならないこともあります。

そのときになって慌てないためにも、

今一度MySQLでの文字列エスケープについて考えてみたいと思います。

 エスケープ・クォート例

まずはMySQLでの文字列のエスケープ/クォートでの囲み方の一部について例をあげます。

mysql> select 'string \'' AS `name```;
+----------+
| name`    |
+----------+
| string ' |
+----------+
1 row in set (0.00 sec)

上記のSQLでは文字列リテラル「string’」を出力しています。

リテラルはシングルクォート「’」 またはダブルクォート「”」で囲み、

文字列中に含まれるクォートやバックスラッシュ「\」の前に

バックスラッシュ「\」をつけてエスケープします。

AS句でカラム名の別名をつけますが、この別名はバッククォート「`」で囲みます。

別名中にバッククォートが含まれる場合はバックスラッシュ「\」ではなく、

バッククォート「`」をふたつ続けてかきます。

mysql> SELECT `name` FROM `some_table`;
<結果省略>

上記のSQLではテーブル some_table からすべてのレコードのnameカラムを出力しています。

カラム名・テーブル名ともにバッククォート「`」で囲みます。

文字中にバッククォートが含まれる場合はバッククォート「`」をふたつ続けてかきます。

なおSELECT句の「`name`」は「’name’」としてはいけません。

「’name’」とした場合リテラルとみなされ、

レコード数だけ文字列「name」が出力されてしまいます。

mysql> SELECT `name` FROM `some_table` WHERE `name` LIKE '\%abc\_%';
<結果省略>

上記のSQLではテーブル some_tableからnameカラムが「%abc_」で前方一致するレコードのnameカラムを出力しています。

LIKEの後ろにくる「’\%abc\_%’」は文字列リテラルですが、

パーセント記号「%」やアンダースコア「_」はそのまま書くとワイルドカードとして機能します。

そのためパーセント記号「%」やアンダースコア「_」自体をヒットさせたい場合は

それぞれの文字の前にバックスラッシュ「\」をつけてエスケープします。

これを忘れると強制的に前方一致させるつもりでも、

入力値によっては部分一致検索が可能となってしまいます。

mysql> SELECT `name` FROM `some_table` ORDER BY `name` ASC;
<結果省略>

上記のSQLではテーブル some_tableからすべてのレコードのnameカラムをnameカラムの昇順で出力しています。

ORDER BY句でもカラム名はバッククォート「`」で囲むパターンです。

カラム名に続く ASC or DESCについては定数のようなもので、エスケープやクォーティングは行いません。

ASC or DESCに文字列を入力する場合は入力する文字列がASCかDESCのいずれかのみを入力させるよう注意する必要があります。

 まとめ

以上複雑なようですが、エスケープ・クォートの方法は以下4パターンになると思います。

railsでの実装例とともにまとめてみます。

文字列リテラルパターン

エスケープ・クォート方法

シングルクォート「’」 またはダブルクォート「”」で囲み、

文字列中に含まれるクォートやバックスラッシュ「\」の前に

バックスラッシュ「\」をつけてエスケープする。

railsでの実装例
str = "test'"
puts ActiveRecord::Base.connection.quote(str) 
#「'test\''」と出力

LIKE文字列リテラルパターン

エスケープ・クォート方法

文字列リテラルパターンの方法に加えて

パーセント記号「%」やアンダースコア「_」の前に

バックスラッシュ「\」をつけてエスケープする。

railsでの実装例
str = "%_test"
puts "'%s'" % ActiveRecord::Base.connection.quote_string(str).gsub(/([%_])/){"\\" + $1}
#「'\%\_test'」と出力

カラム・テーブル名パターン

エスケープ・クォート方法

カラム名・テーブル名ともにバッククォート「`」で囲みます。

別名中にバッククォートが含まれる場合はバッククォート「`」をふたつ続けてかきます。

railsでの実装例
str = "test`"
#rails3.1以上
puts ActiveRecord::Base.connection.quote_column_name(str)
#「`test```」と出力

#rails3.0
puts ActiveRecord::Base.connection.quote_column_name(str.gsub("`", "``"))
#「`test```」と出力

定数パターン(ASC DESCなど)

エスケープ・クォート方法

クォートで囲まない。入力する場合はホワイトリスト方式で厳密にチェックする。

注意が疎かになりがちなのが

カラム・テーブル名パターン

です。

まずバッククォート「`」で囲むのを忘れているケースをよくみます。

また、囲んでいてもrails3.0ではquote_column_nameする前に

文字列中のバッククォート「`」を自前で「“」に変換しなければいけません。

parallel_tests+MongoDBの落とし穴

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Rails+MongoDBアプリケーションのテストを高速化する際にparallel_testsを導入してハマッた点を紹介したいと思います。

 parallel_testsについて

説明を割愛します。

他の文献を参考にしていただければと思います。

 Rails+MongoDBでハマりやすい点

  • 設定忘れ

PostgreSQLなどの SQLとあわせてMongoDBを利用する場合でも、parallel_tests向けにDBの保存先を複数に分ける設定が必要になります。

当然といえば当然ですがこれを忘れると、複数インスタンスで同時書き込みが発生して、テストに失敗するケースがあるので注意が必要です。

  • メモリを食う

これはMongoDBに関わらず当然注意すべき点になります。

単純に「Railsインスタンス*並列実行数」なので、1インスタンスが300Mbytes必要であれば8つ並列実行する場合は2.4GBytesのメモリが必要になります。

  • ディスクも食う

とくにMongoDBを利用している場合は、定期的にDBデータの整理/削除が必要になります。

実際のアプリでも、1回のテスト実施で合計1〜2GBytesの再利用されないデータが生成されるケースがあったので、注意が必要かと思います。

 parallel_tests+MongoDB用のrakeタスク

parallel_tests+MongoDB用のDBを初期設定できるRakeタスクを作成しました。

下記の内容を lib/tasks 以下に設置してご利用ください。

require 'parallel_tests/tasks'

namespace :parallel do
  namespace :mongoid do
    rails_env = ENV['RAILS_ENV'] || 'test'

    desc "db:mongoid:drop --> parallel:mongoid:drop[num_cpus]"
    task :drop, :count do |t,args|
      run_in_parallel("rake db:mongoid:drop RAILS_ENV=#{rails_env}", args)
    end

    desc "db:mongoid:purge --> parallel:mongoid:purge[num_cpus]"
    task :purge, :count do |t,args|
      run_in_parallel("rake db:mongoid:purge RAILS_ENV=#{rails_env}", args)
    end
  end
end

最近人気な記事