忍者ブログ

ぢみへんプログラミング日誌

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

CentOS6.4 + ruby2.0 + rails4 インストール備忘録(その弐:turbolink & passenger 編)

Rails 4 になって turbolinkなる機能が追加されたらしいのだが、これが結構評判悪くて、外そうという話になった。外せ言われても、どうしたらいいのか分からないんですけど……
というわけで先人にお聞きしました 笑
Rails 4 で turbolinks をオフにする方法
簡単にポイントを挙げると、Railsアプリを生成する段階以降、以下の手順を踏めばいい。
  • Railsアプリを生成する際、bundlerの実行をスキップするオプションをつけてコマンド実行する。
    rails new アプリケーション名  --skip-bundle
  • application.html.erb に記載されているHTMLタグの中にある、「"data-turbolinks-track" => true」という属性設定を削除
  • application.js に記載されている「//= require turbolinks」の行を削除
  • Gemfileの「gem 'turbolinks'」という記述をコメントアウト後、bundle install実行
さらに今までずるずると避けてきたpassenger のインストールと設定作業をすることになった。
作業自体は「Ruby on Rails 3 アプリケーションプログラミング」(山田祥寛著:技術評論社)に書いてある通りに行うことでほとんど悩みなく、結構手早くできた。
が、しかし、
いざそのpassengerを使ってApache経由でアプリを起動(=ブラウザで表示)させようとすると、「could not find a JavaScript runtime. 」というメッセージが表示されて何も動きやがらない。だがどう考えても前回までの作業でnode.js とそれに対応するrubyモジュール「execjs」はインストール済みなのは間違いなく、全く訳が分からない。
エラーの出ている箇所をスタックトレース情報を元にたどると、どうやら execjsの構成ファイルである「runtime.js」の以下の部分で指定されているはずのランタイムがない、と言ってるようなのだ。
  Node = ExternalRuntime.new(
:name => "Node.js (V8)",
:command => ["node"],
:runner_path => ExecJS.root + "/support/node_runner.js",
:encoding => 'UTF-8'
)


なにぃぃぃ?!
それじゃ成す術なしじゃねぇかよ~……と思っていたら、やはりここでも同じことではまった先輩がいらっしゃいました。 笑
HOWTO: RubyOnRails + ExecJS + NodeJS - "Could not find a JavaScript runtime"
それによれば上記のコードを以下のように直せば行けるとのこと。
Node = ExternalRuntime.new(
:name => "Node.js (V8)",
:command => ["/usr/local/bin/node"],
:runner_path => ExecJS.root + "/support/node_runner.js",
:encoding => 'UTF-8'
)


:command キーに対応する、ランタイムのパスを"node"から直接node.js がインストールされているパスに置き換えてしまうというわけだ。(注:ここでは/user/local/bin/node となっているが、実行環境によってここは読みかえる必要がある。whichコマンドもしくはwhereis コマンドを使えばそのパスは取得できる。[which node または whereis node])

実際にやってみるとこれがすこぶる上手く行った。
よかった、よかった……

番外編production モードで rails console コマンドや rake db:migrate コマンド等を扱う際の注意。
Rails はデフォルトでは開発モードである develop 環境で作業をするため、運用時に指定することの多い、production モードで rails コマンドや rake コマンドを実行するには、オプションを付けないといけない。
Rails and rake db:migrate in Production Mode
ポイントになる具体例だけ書いておく。
rails console production
rails dbconsole production
rails server -e production
rake db:migrate RAILS_ENV=production





番外編2: passengerで動かすとasset pipeline が動かない
もしそうなったら、config/environment/production.rb の以下部分を修正する.
# Do not fallback to assets pipeline if a precompiled asset is missed.
# config.assets.compile = false #=> 旧
config.assets.compile = true #=> 新
PR

CentOS6.4 + ruby2.0 + rails4 インストール備忘録

今春rubyがバージョン2に、夏に入ってRailsも正式にバージョン4となった。
そしてまた、筆者の担当するプロジェクトでそれらの組み合わせを再度CentOS上に構築しなくてはならなくなったので、備忘録的にメモを残しておく。

メモといっても、実際には過去Rails3 をインストールしたときの教訓がそのまま使えるのでその分についてリンクのみ提示し割愛させていただく。

ruby-1.9.3-p392とrails3.2.13 を CentOS 6.4 にインストールするまで (1)
ruby-1.9.3-p392とrails3.2.13 を CentOS 6.4 にインストールするまで (2)
ruby-1.9.3-p392とrails3.2.13 を CentOS 6.4 にインストールするまで (その後)

今回さらに引っかかったのは、rails server とコマンドを打ったところで、以下のエラーが出た点だった。
「/usr/local/lib/ruby/gems/2.0.0/gems/execjs-2.0.2/lib/execjs/runtimes.rb:51:in `autodetect': Could not find a JavaScript runtime. See https://github.com/sstephenson/execjs for a list of available runtimes. (ExecJS::RuntimeUnavailable)」

どうやら execjs というモジュールが使うはずのランタイムとやらがないらしい。
そこで参照しろと指示してあるページ(https://github.com/sstephenson/execjs)に行くと、そのランタイムとはJavascript ランタイムのことであるらしいことが分かる。Rails の機能として、そうしたランタイムを使う機能がデフォルトで追加された……と考えておこう。(前回同じことで引っかかった覚えがないため、いつからそうなっているのか不明)

いくつかランタイムの候補が挙げられているのだが、筆者はnode.js を選んだ。
http://nodejs.org/

こちらでソースをダウンロード、ビルドしてから再度 rails server を実行した。

> cd /tmp
> wget http://nodejs.org/dist/v0.10.21/node-v0.10.21.tar.gz
> cd node-v0.10.21
> ./configure
> make
> make install
> cd <railsアプリのルートディレクトリ>
> rails server
=> Booting WEBrick
=> Rails 4.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2013-10-23 02:10:36] INFO WEBrick 1.3.1
[2013-10-23 02:10:36] INFO ruby 2.0.0 (2013-06-27) [x86_64-linux]
[2013-10-23 02:10:36] INFO WEBrick::HTTPServer#start: pid=25526 port=3000

どうやらうまくいったっぽい。

※注:node.js のコンパイルにはgccだけでなく、g++(gcc のC++バージョン)も必要

ActiveRecordのヴァリデーションを自動化する

ActiveRecord(以下ARと略)にはデータの整合性をチェックするためのヴァリデーションメソッドが用意されているんだが、たくさんのARを扱うWEBアプリを作ろうとすると、個々のARにいちいちテーブルのカラム制約に合せたヴァリデーションを記述するのが面倒くさくなってくる。
そもそもARは対応するテーブルのメタ情報は全て持っているので、それを利用してヴァリデーションを自動記述してしまった方がはっきり言って楽だろう。

――ということで作ってみました。
module AutoValidates
def self.included(klass)
klass.class_eval{
klass.columns.each do |column|
next if ["id"].include?(column.name)
if [:integer].include?(column.type) && !column.array
# 数値型確認
validates column.name.to_sym,
numericality: {unless: Proc.new{|k| eval("k.#{column.name}").nil?},
only_integer: true
elsif column.null == false
# NOT NULL
validates column.name.to_sym,
presence: {if: Proc.new{|k| eval("k.#{column.name}").nil?}
end
end
}
end
end



これをAR側でincludeすればOK.(のはず)

ActiveRecodeをもう少しSQLライクに使えるようにする

Rails4を使っていると、scopeメソッドを利用してActiveRecord(以下ARと略)を拡張することができると分かり、それならARで共通に使えるモジュールを作ったらいいじゃん、ということに。
module Scope
def self.included(klass)
klass.extend ActiveRecord::ConnectionAdapters::Quoting
klass.class_eval{
scope :eq, ->(h){ where("#{h.keys.first.to_s} = #{quote(h.values.first)}") }
scope :not_eq, ->(h){ where("#{h.keys.first.to_s} != #{quote(h.values.first)}") }
scope :lt, ->(h){ where("#{h.keys.first.to_s} < #{quote(h.values.first)}") }
scope :less_than, ->(h){ where("#{h.keys.first.to_s} < #{quote(h.values.first)}") }
scope :lt_eql, ->(h){ where("#{h.keys.first.to_s} <= #{quote(h.values.first)}") }
scope :less_than_eql, ->(h){ where("#{h.keys.first.to_s} <= #{quote(h.values.first)}") }
scope :gt, ->(h){ where("#{h.keys.first.to_s} > #{quote(h.values.first)}") }
scope :greater_than, ->(h){ where("#{h.keys.first.to_s} > #{quote(h.values.first)}") }
scope :gt_eql, ->(h){ where("#{h.keys.first.to_s} >= #{quote(h.values.first)}") }
scope :greater_than_eql, ->(h){ where("#{h.keys.first.to_s} >= #{quote(h.values.first)}") }
scope :like, ->(h){ where("#{h.keys.first.to_s} like #{quote(h.values.first)}") }
}
end
end



これを使うには
class Book < ActiveRecord::Base
include Scope
end



とAR側でincludeし、
# 「あした」で中間一致する書名を持つ本のレコードを取得
books = Book.like(name: '%あした%').to_a

# 「あした」で中間一致する書名を持ち、4日前以降に出版された本のレコードを取得
books = Book.like(name: '%あした%').gt(publication_date: (Date.today - 5)).to_a



という具合に使うことができる。

ちなみにRails4からはActiveRecord::Relationというクラスが導入されていて、Rails3ではActiveRecordのwhereメソッド(正確にはActiveRecord::Base#where)の戻り値はARの配列だったが、4ではRelationが返ってくる。このクラスは言ってみればSQL生成器とでもいうべき機能を持っていて、#to_sql を呼び出すとその時点で蓄えている情報からSQL文を生成する。上記の例でいえばこんな感じだ。
irb > relation = Book.like(name: '%あした%')
irb > relation.to_sql
=> "SELECT \"books\".* FROM \"books\" WHERE (name like '%あした%')"



しかもチェーンしていけるので便利。
Book.where(name: 'あいうえお').where(auther: 'ほげ')




RelationからARの配列(複数レコード)が欲しければto_aメソッド、1レコードだけ欲しければtakeメソッドを使えば良い。

Rails4ではARに用意されたscopeメソッドを使うと頻繁使うwhereメソッドの記述を簡便化できるので、これは大いに使った方が良いと思える。特にwhereメソッドでハッシュ記法を使うとSQL文は等価比較しかできないのでlikeや < といった演算子はそのままでは使えない。そういった部分を補完する意味でも役に立つ。
なお、whereメソッドを使ってSQLの「in」を指定したい場合は、配列を使い、範囲(between)を使いたい場合はRangeクラスが利用できる。

# select * from books where author_name in (select name from authors where name = 'ほげ' の場合
Book.where(author_name: Author.where(name: 'ほげ').pluck(:name))

# 1年前から今日までの間を指定
# select * from books where publication_date between '2012-09-12' AND '2013-09-12'
# というSQL文を生成する
Book.where(publication_date: ((Date.today - 365)..Date.today))

ActiveRecord::Base.transaction はネストしたトランザクションに対応しているのか確認した。

細かい説明は抜きにして、以下のirb (rails console -s で起動するコンソールモード)での実行内容を見てみよう。
まずはUserというモデルをsaveメソッド単体で保存してみる。

[root@localhost base]# rails console -s
Loading development environment in sandbox (Rails 4.0.0.beta1)
Any modifications you make will be rolled back on exit
irb(main):001:0> a = User.new
=> #
irb(main):002:0> a.user_no = "123"
=> "123"
irb(main):003:0> a.user_name = "aiueo"
=> "aiueo"
irb(main):004:0> a.save
(0.3ms) BEGIN
User Exists (1.6ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '123' LIMIT 1
SQL (13.1ms) INSERT INTO "users" ("updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [ ["created_at", Wed, 13 Mar 2013 15:10:12 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 15:10:12 UTC +00:00],["user_name", "aiueo"], ["user_no", "123"]]
(3.3ms) COMMIT
=> true

見ての通り、saveメソッドは単体でトランザクションも起動している。

では今度はトランザクションをネストさせてsaveを使ってみる。

irb(main):061:0> ActiveRecord::Base.transaction do
irb(main):062:1> a = User.new
irb(main):063:1> a.user_no = "234"
irb(main):064:1> a.user_name = "aiueo"
irb(main):065:1> a.user_category_id = 1
irb(main):066:1> a.user_qualification_id = 1
irb(main):067:1> a.card_registered_date = "20130313"
irb(main):068:1> a.library_id = 1
irb(main):069:1> a.save
irb(main):070:1> end
(0.3ms) BEGIN
User Exists (1.2ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '234' LIMIT 1
SQL (2.1ms) INSERT INTO "users" ("created_at", "updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 15:41:55 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 15:41:55 UTC +00:00], ["user_name", "aiueo"], ["user_no", "234"]]
(3.0ms) COMMIT
=> true

saveメソッドの内部にもトランザクション処理が起きて、いわゆるトランザクションのネストが発生するかと思ったが、SQLレベルでBeginが2回生成されていないこと、警告(筆者の環境ではDBがPostgreSQLなのでBEGINが2回続くと警告が出る)が出ないところから見ると、saveメソッド内でトランザクションを開始してはいないようだ。

では明示的にトランザクションをネストさせたらどうなるだろうか

irb(main):072:0> ActiveRecord::Base.transaction do
irb(main):073:1* a = User.new
irb(main):074:1> a.user_no = "345"
irb(main):075:1> a.user_name = "aiueo"
irb(main):080:1> a.save
irb(main):081:1>
irb(main):082:1* ActiveRecord::Base.transaction do
irb(main):083:2* b = User.new
irb(main):084:2> b.user_no = "456"
irb(main):085:2> b.user_name = "aiueo"
irb(main):090:2> b.save
irb(main):091:2> end
irb(main):092:1> end
(0.3ms) BEGIN
User Exists (0.9ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '345' LIMIT 1
SQL (2.9ms) INSERT INTO "users" ("created_at", "updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 15:53:26 UTC +00:00],["updated_at", Wed, 13 Mar 2013 15:53:26 UTC +00:00], ["user_name", "aiueo"], ["user_no", "345"]]
User Exists (0.8ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '456' LIMIT 1
SQL (1.3ms) INSERT INTO "users" ("created_at", "updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 15:53:26 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 15:53:26 UTC +00:00],["user_name", "aiueo"], ["user_no", "456"]]
(3.5ms) COMMIT
=> true

ご覧のとおり、トランザクションはネストしない。

このことはメソッドを介しても全く同様。

irb(main):094:0> def test
irb(main):095:1> ActiveRecord::Base.transaction do
irb(main):096:2* a = User.new
irb(main):097:2> a.user_no = "789"
irb(main):098:2> a.user_name = "aiueo"
irb(main):103:2> a.save
irb(main):104:2> end
irb(main):105:1> end
=> nil
irb(main):106:0> ActiveRecord::Base.transaction do
irb(main):107:1* a = User.new
irb(main):108:1> a.user_no = "890"
irb(main):109:1> a.user_name = "aiueo"
irb(main):114:1> a.save
irb(main):115:1>
irb(main):116:1* test
irb(main):117:1> end
(0.4ms) BEGIN
User Exists (0.5ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '890' LIMIT 1
SQL (0.5ms) INSERT INTO "users" "created_at","updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 16:02:05 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 16:02:05 UTC +00:00],["user_name", "aiueo"], ["user_no", "890"]]
User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '789' LIMIT 1
SQL (0.3ms) INSERT INTO "users" "created_at", "updated_at","user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 16:02:05 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 16:02:05 UTC +00:00],["user_name", "aiueo"], ["user_no", "789"]]
(1.1ms) COMMIT
=> true

このように、ActiveRecord::Base.transactionを明示して行うトランザクション処理ではネストが発生しない。

そのためロールバックも非常に明確となる。
ActiveRecord::Base.transaction のブロック中に、ActiveRecord::Base.transactionブロックをネストしても見た目とは反対に実際にはトランザクションがネストしないので、一番ネストの深いブロックで例外が発生しても、全体としてはBEGIN => Rollback の制御が1回起きるだけだ。
以下が論より証拠である。

irb(main):001:0> ActiveRecord::Base.transaction do
irb(main):002:1* a = User.new
irb(main):003:1> a.user_no = "901"
irb(main):004:1> a.user_name = "aiueo"
irb(main):009:1> a.save
irb(main):010:1>
irb(main):011:1* ActiveRecord::Base.transaction do
irb(main):012:2* b = User.new
irb(main):013:2> b.user_no = "012"
irb(main):014:2> b.user_name = "aiueo"
irb(main):019:2> b.save
irb(main):020:2> raise "Exception occured."
irb(main):021:2> end
irb(main):022:1> end
(0.1ms) BEGIN
User Exists (0.8ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '901' LIMIT 1
SQL (5.5ms) INSERT INTO "users" ("created_at", "updated_at","user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 16:20:52 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 16:20:52 UTC +00:00],["user_name", "aiueo"], ["user_no", "901"]]
User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '012' LIMIT 1
SQL (0.3ms) INSERT INTO "users" ("created_at", "updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 16:20:52 UTC +00:00],["updated_at", Wed, 13 Mar 2013 16:20:52 UTC +00:00],["user_name", "aiueo"], ["user_no", "012"]]
(0.8ms) ROLLBACK
RuntimeError: Exception occured.

上記通りにActiveRecord::Base.transactionをに使う限りにおいては、コード上でトランザクションブロックをいくらネストさせても、どこかで例外が発生した時点で全ての処理がロールバックされ、セーブポイント等は考慮されない。
セーブポイントが重要という場合は困るだろうが、トランザクションの一意性を考えるとロールバックと同時にトランザクション内全てのレコード処理が自動で無効になってくれたほうが親切だと言えるだろう。これで心置きなくトランザクションを安心して使えるというわけだ。
  

カレンダー

03 2024/04 05
S M T W T F S
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

フリーエリア

最新CM

バーコード

ブログ内検索

Copyright ©  -- ぢみへんプログラミング日誌 --  All Rights Reserved

Design by CriCri / Material by petit sozai emi / powered by NINJA TOOLS / 忍者ブログ / [PR]