システムを構築すると、必ず「設定値」が必要になります。
・1ページの表示行数
・XXした際の遷移先URL
・YY通信のタイムアウト時間
...etc
上記設定をどこに置くかは結構大きな問題です。
(例)
①propertiesファイル (javaの場合)
②xxx.rbファイル (rubyの場合)
③データベース
...
上記①や②は最速ですが、設定値の反映には「アプリケーションの再デプロイ or 再起動」が必要になります。よって気軽に変更値の変更という訳にはいきません。
③はデータベースへの問い合わせが必要ですが、アプリケーション実行時に設定値を切り替えられるという利点があります。この手の設定値テーブルを用意しておくと顧客との打合せ時に、無駄な値決めに時間を浪費する事を回避できるというメリットもあります(顧客は情報がそろってから意思決定できるようになるので)。
よって、システムを開発してusersテーブルを作成した次には(?)、上記テーブルを作成しましょう。(心の)師匠、渡辺幸三さんに敬意を評し、「システムコントロール」テーブルと名づけましょう。
system_controls
----
code (PK or UK)
value (設定値)
editable (ユーザが画面上より編集可能か?)
description (編集画面での説明文章)
type (文字列、数字、boolean等入力タイプの区分値。多くなるならドメインテーブル作成。入力チェックと絡めるのも良いかもですね)
updater_id (最終更新者)
updated_at (最終更新日時)
こんな感じかな?
スピードが気になる人は、上記のcode値を利用して、KVSとかキャッシュサーバに突っ込めば良いでしょう。
# 変更、破棄タイミングに気をつけて下さい
ちなみに上記目的のテーブルを横持ち(カラム)で持ちたがる人がいますが、全力で阻止しましょう。柔軟にcodeを追加出来るのがミソなので!!
後DB障害発生時に問い合わせられない事も意識する必要があります(例 例外メール送信先)ので注意が必要です。
2011年7月31日日曜日
2011年7月28日木曜日
backbone.jsがいつのまにかpjax対応していた
■概要
年初にbackbone.jsの調査をしていた頃は、ajaxで画面遷移を行うには、fragmentを利用する事しかできませんでした。
しかし0.5以降では、Backbone.Routerを利用する事によって、pjax(HTML5のpushStateを利用した話題のあれ)な画面遷移が出来るになっています。
pjaxについては、こちらをご参照ください。
またgithubのファイルブラウザはpjaxの良例として有名なのでチェックしてみて下さい。
ちなみに、IE(≒pushStateをサポートしていないブラウザ)でみたら fragment での遷移に自動的に切り替わってました。。
年初にbackbone.jsの調査をしていた頃は、ajaxで画面遷移を行うには、fragmentを利用する事しかできませんでした。
しかし0.5以降では、Backbone.Routerを利用する事によって、pjax(HTML5のpushStateを利用した話題のあれ)な画面遷移が出来るになっています。
pjaxについては、こちらをご参照ください。
またgithubのファイルブラウザはpjaxの良例として有名なのでチェックしてみて下さい。
■何処がpjaxなのか?
まずは下記手順を実施後、http://localhost:3000/を閲覧してみましょう。chrome等html5対応ブラウザで確認して下さい。
次にhelpをクリックしてみましょう。
注目すべきなのは、
①URLが変わっている
②画面下部のレンダリング日時が変わっていない。
次にaboutをクリックしてみましょう。helpの時と同じです。
ブラウザの「戻る」ボタンをクリックしてみましょう。
About => Help => Indexに遷移します。「進む」ボタンもいい感じに遷移します。
About => Help => Indexに遷移します。「進む」ボタンもいい感じに遷移します。
次にHelpが画面に表示されている状態で、「F5」を押下してみましょう。
レンダリング日時が変わったはずです。
■説明
ajaxは非同期に(高速に)画面の一部を書き換える技術ですが、ユーザーの利便性の観点から、ブラウザの戻る、進むを利用可能にする必要があります。これまでのajaxでは、#(フラグメント)を利用して実現する事が多かったのですが、あまり良い解決策では有りませんでした。
pjaxでは、HTML5のpushStateを利用する事により、ブラウザのURLを切り替えつつ、画面の一部のみ切り替える事ができます。無論、URLは通常のURLですので、「通常のHTTPリクエスト」として処理するように実装する事が可能です。下記のIndexControllerでは、Ajax通信かどうかでレイアウトの適用を変更しています。
これができたら何が嬉しいのか?!それは読者への宿題にしておきます(^.^;
ちなみに、IE(≒pushStateをサポートしていないブラウザ)でみたら fragment での遷移に自動的に切り替わってました。。
■体験手順
・まずは前回の記事を参考にプロジェクトを生成する。
$ ./gen.sh pjax
・移動して体験に必要なファイルを用意する
$ cd pjax
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>pjax2</title>
<meta charset="utf-8"/>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
<%= javascript_include_tag :backbone %>
<script><%= yield :javascripts %></script>
<%= csrf_meta_tag %>
</head>
<body>
<%= content_for?(:content) ? yield(:content) : yield %>
</body>
</html>
app/views/layouts/index.html.erb
<%= content_for :javascripts do %>
$(function() {
var Workspace = Backbone.Router.extend({
routes: {
"": "index",
"help": "help",
"about": "about"
},
index: function() {
$("#app").load("/");
},
help: function() {
$("#app").load("/help");
},
about: function() {
$("#app").load("/about");
}
});
var ws = new Workspace();
Backbone.history.start({pushState: true});
$("#indexButton").click(function() {
ws.navigate("", true);
});
$("#helpButton").click(function() {
ws.navigate("help", true);
});
$("#aboutButton").click(function() {
ws.navigate("about", true);
});
});
<% end %>
<%= content_for :content do %>
<div id="app"></div>
<hr/>
<input type="button" name="index" value="index" id="indexButton" />
<input type="button" name="help" value="help" id="helpButton" />
<input type="button" name="about" value="about" id="aboutButton" />
最終レンダリング日時:<%= I18n.l Time.now %>
<% end %>
<%= render :file => "layouts/application" %>
app/views/index/index.html.erb
<h1>Index</h1>
app/views/index/help.html.erb
<h1>help</h1>
app/views/index/about.html.erb
<h1>about</h1>
app/controllers/index_controller.rb
class IndexController < ApplicationController
layout "index"
def index
render :layout => !request.xhr?
end
def help
render :layout => !request.xhr?
end
def about
render :layout => !request.xhr?
end
end
config/routes.rb
最後に
root :to => "index#index"
match "help" => "index#help"
match "about" => "index#about"
public/index.html <= 削除
サーバ起動!
$ rails server
ラベル:
backbonejs,
javascript,
jquery,
pjax,
rails3
backbone.jsをrails3で実行できるようにするシェルスクリプト
※この記事はrails3.0に対する記事です。rails3.1はこちらをご覧下さい。
何度も同じ事しているので貼っておきます。
./gen.sh アプリ名
で利用できます(ノーエラーハンドリング上等!)
●2011/08/15追記 rcov => simplecovに変更し、ci_reporterを追加しました。
$ cat gen.sh
何度も同じ事しているので貼っておきます。
./gen.sh アプリ名
で利用できます(ノーエラーハンドリング上等!)
●2011/08/15追記 rcov => simplecovに変更し、ci_reporterを追加しました。
$ cat gen.sh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# railsプロジェクト作成して移動する | |
rails new "$1" -T -J | |
cd "$1" | |
# Gemfileに必要なgemを追記する | |
cat << HERE >> Gemfile | |
gem 'jquery-rails' | |
group :development, :test do | |
gem 'rspec' | |
gem 'rspec-rails' | |
gem 'simplecov' | |
gem 'simplecov-rcov' | |
gem 'ci_reporter' | |
end | |
HERE | |
# gemをインストールする | |
bundle install | |
# ジェネレータを起動する | |
rails g rspec:install | |
rails g jquery:install --ui | |
# jqueryをデフォルトの設定に任せ利用するようにする。 | |
perl -pi -e 's/config\.action_view\.javascript_expansions/# config.action_view.javascript_expansions/g' config/application.rb | |
# backbone.jsを利用出来るようにする | |
mkdir lib/generators | |
cat << HERE >> lib/generators/backbone_js_generator.rb | |
require 'openssl' | |
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE | |
class BackboneJsGenerator < Rails::Generators::Base | |
desc "This generator downloads and installs json2.js, underscore.js, backbone.js" | |
def download_json2_js | |
say_status("fetching", "json2.js", :green) | |
get "https://github.com/douglascrockford/JSON-js/raw/master/json2.js", | |
"public/javascripts/json2.js" | |
end | |
def download_underscore_js | |
say_status("fetching", "underscore.js", :green) | |
download_via_documentcloud("underscore") | |
download_via_documentcloud("underscore", "-min") | |
end | |
def download_backbone_js | |
say_status("fetching", "backbone.js", :green) | |
download_via_documentcloud("backbone") | |
download_via_documentcloud("backbone", "-min") | |
end | |
private | |
def download_via_documentcloud(product, suffix = "") | |
get "http://documentcloud.github.com/#{product}/#{product}#{suffix}.js", | |
"public/javascripts/#{product}#{suffix}.js" | |
end | |
end | |
HERE | |
rails g backbone_js | |
cat << HERE >> config/initializers/backbone_js.rb | |
module BackboneJs | |
module Rails | |
class Railtie < ::Rails::Railtie | |
config.before_configuration do | |
config.action_view.javascript_expansions[:backbone] = | |
::Rails.env.production? ? %w(json2 underscore-min backbone-min) : %w(json2 underscore backbone) | |
end | |
end | |
end | |
end | |
HERE | |
# 日本語翻訳ファイルを用意する | |
perl -pi -e 's/# config\.i18n\.default_locale = :de/config.i18n.default_locale = :ja/g' config/application.rb | |
cd config/locales | |
wget https://raw.github.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml | |
cat <<HERE > translation_ja.yml | |
ja: | |
activerecord: | |
HERE | |
cd - | |
# specコマンドへのオプションを指定する | |
cat << HERE > .rspec | |
--color | |
--format documentation | |
--drb | |
HERE | |
# .gitignoreに履歴管理しないファイルを記載する | |
cat << HERE > .gitignore | |
*.swp | |
**.orig | |
*.rbc | |
*.sassc | |
.sass-cache | |
capybara-*.html | |
.rspec | |
/.bundle | |
/vendor/bundle | |
/log/* | |
/tmp/* | |
/db/*.sqlite3 | |
/db/*.db | |
/public/system/* | |
/coverage/ | |
/spec/reports/ | |
/spec/tmp/* | |
config/*.yml | |
rerun.txt | |
pickle-email-*.html | |
HERE | |
# レイアウトに文字コード指定と、backbone.jsの設定を追記する | |
cat << HERE > app/views/layouts/application.html.erb | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>$1</title> | |
<meta charset="utf-8"/> | |
<%= stylesheet_link_tag :all %> | |
<%= javascript_include_tag :defaults %> | |
<%= javascript_include_tag :backbone %> | |
<%= csrf_meta_tag %> | |
</head> | |
<body> | |
<%= yield %> | |
</body> | |
</html> | |
HERE | |
# simplecov利用をspec_helper.rbに付加 | |
cat << HERE > spec/spec_helper.rb | |
if %w(yes y on).include?(ENV['COVERAGE']) | |
require 'simplecov' | |
require 'simplecov-rcov' | |
SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter | |
SimpleCov.start 'rails' | |
end | |
# This file is copied to spec/ when you run 'rails generate rspec:install' | |
ENV["RAILS_ENV"] ||= 'test' | |
require File.expand_path("../../config/environment", __FILE__) | |
require 'rspec/rails' | |
# Requires supporting ruby files with custom matchers and macros, etc, | |
# in spec/support/ and its subdirectories. | |
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} | |
RSpec.configure do |config| | |
# == Mock Framework | |
# | |
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: | |
# | |
# config.mock_with :mocha | |
# config.mock_with :flexmock | |
# config.mock_with :rr | |
config.mock_with :rspec | |
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures | |
config.fixture_path = "#{::Rails.root}/spec/fixtures" | |
# If you're not using ActiveRecord, or you'd prefer not to run each of your | |
# examples within a transaction, remove the following line or assign false | |
# instead of true. | |
config.use_transactional_fixtures = true | |
end | |
HERE | |
# ci_reporterのtaskを追加 | |
cat << HERE > lib/tasks/ci_reporter.rake | |
require 'ci/reporter/rake/rspec' | |
HERE | |
# 履歴管理開始する | |
git init | |
git add . | |
git commit -m "first commit" | |
# rails g scaffold post title:string body:text published:boolean | |
# とりあえず一度specを流す | |
rake db:migrate | |
COVERAGE=yes rake ci:setup:rspec spec | |
# とりあえずサーバ起動 | |
rails s |
ラベル:
backbone.js,
generator,
rails3
railsでネストしたレイアウトを実現する
railsのviewはレイアウト機能があり、画面の共通化が可能です。
・画面のイメージ・レイアウトを共通化する
・メニューを表示する
...etc
非常に便利な機能ですが、「サイト全体で画面のイメージを共通化しつつ、XXX機能でも共通のレイアウトにする」事が簡単にはできないと思っていました(レイアウトの二重適用)。
ところがRuby on Rails Guidesに実現方法がのっていました。非常に簡単なのでご紹介しておきます。
■application.html.erb (全体的な共通レイアウト)
・yield :stylesheets
=> 機能側で定義したstylesheetをheadタグ内にレンダリングする例です
・content_for?(:content) ? yield(:content) : yield
=> 機能側で:contentがある場合は引数にしてyield、無い場合通常のyield
■news.html.erb(機能毎レイアウト)
=>上記のstylesheetを準備します。同じようにjavascriptも定義可能です。
・content_for :content / yield(:news_content) or yield
=> :contentを定義し、更に:news_contentがある場合は引数にしてyield、無い場合通常のyield
・render :file => 'layouts/application'
=> 上記contentを準備して、全体レイアウトをレンダリング
■xxx.html.erb(各画面)
<h1>タイトル</h1>
...
各画面固有のレンダリング内容
■xxx_controller.rb
layoutで機能毎共通レイアウトを指定
とネストしたレンダリング可能です!
■備考
2011/07/21 22:06 なんてこったい! Rails3レシピブックにのっているじゃないか(^_^;)
・画面のイメージ・レイアウトを共通化する
・メニューを表示する
...etc
非常に便利な機能ですが、「サイト全体で画面のイメージを共通化しつつ、XXX機能でも共通のレイアウトにする」事が簡単にはできないと思っていました(レイアウトの二重適用)。
ところがRuby on Rails Guidesに実現方法がのっていました。非常に簡単なのでご紹介しておきます。
■application.html.erb (全体的な共通レイアウト)
<html> <head> <title><%= @page_title or 'Page Title' %></title> <%= stylesheet_link_tag 'layout' %> <style type="text/css"><%= yield :stylesheets %></style> </head> <body> <div id="top_menu">Top menu items here</div> <div id="menu">Menu items here</div> <div id="content"><%= content_for?(:content) ? yield(:content) : yield %></div> </body> </html>上記のポイントは二つ。
・yield :stylesheets
=> 機能側で定義したstylesheetをheadタグ内にレンダリングする例です
・content_for?(:content) ? yield(:content) : yield
=> 機能側で:contentがある場合は引数にしてyield、無い場合通常のyield
■news.html.erb(機能毎レイアウト)
<% content_for :stylesheets do %> #top_menu {display: none} #right_menu {float: right; background-color: yellow; color: black} <% end %> <% content_for :content do %> <div id="right_menu">Right menu items here</div> <%= yield(:news_content) or yield %> <% end %> <%= render :file => 'layouts/application' %>・content_for :stylesheets
=>上記のstylesheetを準備します。同じようにjavascriptも定義可能です。
・content_for :content / yield(:news_content) or yield
=> :contentを定義し、更に:news_contentがある場合は引数にしてyield、無い場合通常のyield
・render :file => 'layouts/application'
=> 上記contentを準備して、全体レイアウトをレンダリング
■xxx.html.erb(各画面)
<h1>タイトル</h1>
...
各画面固有のレンダリング内容
■xxx_controller.rb
class XxxController < ApplicationController
layout "news"
def xxx
end
end
layoutで機能毎共通レイアウトを指定
上記の様に設定すると
application.html.erb news.html.erb xxx.html.erb
とネストしたレンダリング可能です!
■備考
2011/07/21 22:06 なんてこったい! Rails3レシピブックにのっているじゃないか(^_^;)
2011年7月27日水曜日
coberturaが猛烈に遅い!...けど解決した
■概要
junitの網羅率測定する為に、coberturaを利用する事が多いと思います。
# 当方久しぶりにjavaを触ったので最近のトレンドにはとんと疎くなりました。もっと良いのがあれば教えてください。
しかしこれが猛烈に遅い!軽快に進めたいのですが、ネックになりそうなので調査しました。
■解説
coberturaの実行はantから行っています。通常
1. コンパイル
2. instrument (coberturaを埋め込む)
3. junit実行
の順番で実行する事になりますが、 3. が非常に遅いのです。
3. の実行は
の様な記載を行うと思いますが、上記だと遅くなってしまいます。
何故なら、antがjunitを起動する際にプロセスのforkを行うのですが、forkはTestCase毎に行われるようです。
複数回のforkを通して網羅率の測定を行う必要がある為、「cobertura.ser」というファイルを
かいして測定を行います。
ところがforkする毎にcobertura.serに、シリアライズ・デシリアライズを行うためのその
オーバーヘッドが大きくなります。実際topコマンド確認するとCPUバウンドな処理である事が分かります。
そこでant 1.6.2より導入された、forkmodeを設定します。
上記の設定により、junitを実行するプロセスは"一度だけ"起動される為、シリアライズ・デシリアライズのオーバーヘッドが無くなり、非常に高速にテストの実行が可能です。
junitの網羅率測定する為に、coberturaを利用する事が多いと思います。
# 当方久しぶりにjavaを触ったので最近のトレンドにはとんと疎くなりました。もっと良いのがあれば教えてください。
しかしこれが猛烈に遅い!軽快に進めたいのですが、ネックになりそうなので調査しました。
■解説
coberturaの実行はantから行っています。通常
1. コンパイル
2. instrument (coberturaを埋め込む)
3. junit実行
の順番で実行する事になりますが、 3. が非常に遅いのです。
3. の実行は
<junit fork="yes" dir="${basedir}"> <classpath location="${instrumented.dir}" /> <classpath location="${classes.dir}" /> <classpath refid="cobertura.classpath" /> <formatter type="xml" /> <test name="${testcase}" todir="${reports.xml.dir}" if="testcase" /> <batchtest todir="${reports.xml.dir}" unless="testcase"> <fileset dir="${src.dir}"> <include name="**/*Test.java" /> </fileset> </batchtest> </junit>
の様な記載を行うと思いますが、上記だと遅くなってしまいます。
何故なら、antがjunitを起動する際にプロセスのforkを行うのですが、forkはTestCase毎に行われるようです。
複数回のforkを通して網羅率の測定を行う必要がある為、「cobertura.ser」というファイルを
かいして測定を行います。
ところがforkする毎にcobertura.serに、シリアライズ・デシリアライズを行うためのその
オーバーヘッドが大きくなります。実際topコマンド確認するとCPUバウンドな処理である事が分かります。
そこでant 1.6.2より導入された、forkmodeを設定します。
<junit fork="yes" forkmode="once" dir="${basedir}">
....
上記の設定により、junitを実行するプロセスは"一度だけ"起動される為、シリアライズ・デシリアライズのオーバーヘッドが無くなり、非常に高速にテストの実行が可能です。
2011年7月21日木曜日
(小ネタ)DBにはトランザクションがありますが
今回は超初心者向け
webでシステムを構築していると、「データ登録をしてメール送信」とか「データ登録してファイル削除」等の処理が必要になる場合があります。そこで
の様な処理をすると問題大有りです。何故でしょう?
...
データベース処理は通常ロールバックが可能ですが、「ファイルシステムへの変更」や、「送信してしまったメール」は元に戻す事ができません。
# ①②が正常に処理された後に、③で例外が発生した場合取り返しが付かない
よって上記の巻き戻し不能処理をする場合は、できるだけ処理の一番最後にもってくるべきです。例えば:
webでシステムを構築していると、「データ登録をしてメール送信」とか「データ登録してファイル削除」等の処理が必要になる場合があります。そこで
transaction do yyy処理 ① delete_files ② db登録処理 ③ commit end とか transaction do xxx処理 ① sendmail ② db更新処理 ③ commit end
の様な処理をすると問題大有りです。何故でしょう?
...
データベース処理は通常ロールバックが可能ですが、「ファイルシステムへの変更」や、「送信してしまったメール」は元に戻す事ができません。
# ①②が正常に処理された後に、③で例外が発生した場合取り返しが付かない
よって上記の巻き戻し不能処理をする場合は、できるだけ処理の一番最後にもってくるべきです。例えば:
transaction do xxx処理 db更新処理 commit end sendmail
(小ネタ)IEの512byte問題。圧縮転送したらさらに問題
■概要
通常HTTPリクエストに対して成功した場合は、HTTPレスポンスコード200で返答します。しかし「ページが見つからない場合」は通常404で返します。
しかしカスタマイズした404.html等を準備し、レスポンスコード404で返しても、IEは下記のような残念な画面を表示される事があります。
準備したhtmlを表示したいので何とかする必要があります。
■説明
IEには特別な仕様があり、「HTTPエラーメッセージを簡易表示する(デフォルトはオン)」というオプションの機能により「512byte」以下のContent-Lengthで返信した場合は文字通り「簡易表示」してしまいます。
またこの仕様はたちの悪い事に「圧縮転送後のサイズが512byte」になります。よってgzip転送を行うとそれなりのサイズで返信しても「簡易表示」される可能性があります。
よって
■参考
http://neta.ywcafe.net/000558.html
通常HTTPリクエストに対して成功した場合は、HTTPレスポンスコード200で返答します。しかし「ページが見つからない場合」は通常404で返します。
しかしカスタマイズした404.html等を準備し、レスポンスコード404で返しても、IEは下記のような残念な画面を表示される事があります。
準備したhtmlを表示したいので何とかする必要があります。
■説明
IEには特別な仕様があり、「HTTPエラーメッセージを簡易表示する(デフォルトはオン)」というオプションの機能により「512byte」以下のContent-Lengthで返信した場合は文字通り「簡易表示」してしまいます。
またこの仕様はたちの悪い事に「圧縮転送後のサイズが512byte」になります。よってgzip転送を行うとそれなりのサイズで返信しても「簡易表示」される可能性があります。
よって
- 無駄な内容を追加して圧縮後も512byteを超えるようにする
- 圧縮転送設定を上記の場合はオフにする
■参考
http://neta.ywcafe.net/000558.html
2011年7月12日火曜日
(小ネタ)saとかsysとかdbaとかadministratorとかrootとか本番環境で使ってはダメ
分かっちゃいるけど表題の様なユーザ・ロールを、OS/DBレベルで利用しているケースをみます。
上記のユーザを利用してしまうと
・セキュリティ上潜在的に問題がある
・権限上強力すぎて、通常ユーザに許可されていない行為に気づかない(例 ファイルの書き込み)
・(oracleの)dbaを利用すると「exportされたdumpは、dbaロールを持った人でしかimportできない」という十字架を背負う
・見る人が見たら、考察が甘い事がばれる => 信用を失う
...etc
色々簡単にすましたいのは分かりますが、「正しく理解しないと後で後悔する」ので、一般ユーザ・ロールを利用してあるべき姿で設計しましょう!
上記のユーザを利用してしまうと
・セキュリティ上潜在的に問題がある
・権限上強力すぎて、通常ユーザに許可されていない行為に気づかない(例 ファイルの書き込み)
・(oracleの)dbaを利用すると「exportされたdumpは、dbaロールを持った人でしかimportできない」という十字架を背負う
・見る人が見たら、考察が甘い事がばれる => 信用を失う
...etc
色々簡単にすましたいのは分かりますが、「正しく理解しないと後で後悔する」ので、一般ユーザ・ロールを利用してあるべき姿で設計しましょう!
ラベル:
design
(小ネタ)webアプリケーションからの時間取得について
webアプリケーションで、現在日時を取得する場面は多いと思います。
その際注意しなければいけないのは、「どこから時間を取得するか?」という事です。
通常考えられる時間の取得場所は
・DBサーバ(sql文からsysdate等の関数を利用して)
・APサーバ(プログラム開発言語からOS時間を取得)
になると思いますが、アプリケーション側からの一貫した時間取得方法を明確にしておく事は大きな意味があります。
1. インフラレベルで時間が同期できていなかったら?という観点から時間を取得する場所は一定にするべき
無論ntpdで同期しておけば...という話がありますが、意外と合っていない場合があります。
2. テストの観点から「日時を固定、もしくは作為的にある日に設定」をしなければいけない場合がある
業務上の特定日(例 締め日)に固定し、テストしたい事は良くあります。
その場合も日時の取得方法を一貫性のある設計にしておけば、簡単に問題解決を図る(例 mock化)事ができます。
上記を確実に実施する為に、下記のような事を考えておけば良いのではないでしょうか。
・どこでどのように時間を取得するかアーキテクチャ上明確にし規約化する
・規約に反した時間取得方法を行っているプログラムを抽出する為に、規約チェックツールを作成する
例) / SYSDATE/等でgrepする
ではでは
その際注意しなければいけないのは、「どこから時間を取得するか?」という事です。
通常考えられる時間の取得場所は
・DBサーバ(sql文からsysdate等の関数を利用して)
・APサーバ(プログラム開発言語からOS時間を取得)
になると思いますが、アプリケーション側からの一貫した時間取得方法を明確にしておく事は大きな意味があります。
1. インフラレベルで時間が同期できていなかったら?という観点から時間を取得する場所は一定にするべき
無論ntpdで同期しておけば...という話がありますが、意外と合っていない場合があります。
2. テストの観点から「日時を固定、もしくは作為的にある日に設定」をしなければいけない場合がある
業務上の特定日(例 締め日)に固定し、テストしたい事は良くあります。
その場合も日時の取得方法を一貫性のある設計にしておけば、簡単に問題解決を図る(例 mock化)事ができます。
上記を確実に実施する為に、下記のような事を考えておけば良いのではないでしょうか。
・どこでどのように時間を取得するかアーキテクチャ上明確にし規約化する
・規約に反した時間取得方法を行っているプログラムを抽出する為に、規約チェックツールを作成する
例) / SYSDATE/等でgrepする
ではでは
ラベル:
design
2011年7月6日水曜日
rails on oracle (oracleで接続する手順) その2
■概要
rails on oracle でいくつかのテーマで検証してみます
●fetchループ
上記の場合①は通常の検索処理。sqlの実行結果を全てメモリ上に展開します。
②の場合、fetchループで繰り返し処理が発生する為、"同時に必要となるメモリ"は少なくてすみます。
# それぞれ実行しながら、topコマンド等でメモリ利用状況を確認しましょう。
よって大量のcsv出力する場合は必ず②のようにする必要があります。
●DROP
railsで実装する場合、割と頻繁にDBスキーマの全削除、再作成をするかと思います(うちだけ?)。その際の機能は充実してきており、adapter内で多数のDROP文が実装されています。
但し適宜 purge recyclebin してあげないとゴミがたまりそうです。
●indexの表領域
性能向上の為、データ/インデックスを別HDDに保存する場合、データ/インデックスで表領域を分ける必要があります。oracle_enhanced_adapterは成長しており、migrationで
のように指定できます。
●varchar2問題
oracleのvarchar2/char2は歴史的な経緯で、"バイト単位"での長さ指定になります。
よってrailsからスキーマ作成する際は、string => nvarchar2へのマッピングを以前はしていたのですが、最近のoracle enhanced adapterは進化しています!
lib/active_record/connection_adapters/oracle_enhanced_adapter.rb によれば、nls_length_semantics というオプションがデフォルトで'CHAR'となっており、このオプションを指定する事によってvarchar2/char2を文字数単位で長さ指定出来る様になります。
よって何も考えなくても、mysql等と同じように指定できる様になりました。
実際下記の用にスキーマが出来ています。
ソース見て思ったのですが、oracle enhanced adapter マジで進化してます!
かなりきっちりしてる感じです(識別子30文字制限問題、in句の1000上限問題 ...etc)
rails on oracle でいくつかのテーマで検証してみます
●fetchループ
module FetchTest SQL = "select p1.* from posts p1 cross join posts p2" def self.run Post.delete_all 200.times do |i| Post.create(:title => "title_#{i}", :body => "body_#{i}") end open("test.txt", "w") do |f| # ① Post.find_by_sql(SQL).each { |r| f.write "#{r}\r\n" } # ② Post.connection.raw_connection.exec(SQL) { |r| f.write "#{r}\r\n" } end end end FetchTest.run
上記の場合①は通常の検索処理。sqlの実行結果を全てメモリ上に展開します。
②の場合、fetchループで繰り返し処理が発生する為、"同時に必要となるメモリ"は少なくてすみます。
# それぞれ実行しながら、topコマンド等でメモリ利用状況を確認しましょう。
よって大量のcsv出力する場合は必ず②のようにする必要があります。
●DROP
railsで実装する場合、割と頻繁にDBスキーマの全削除、再作成をするかと思います(うちだけ?)。その際の機能は充実してきており、adapter内で多数のDROP文が実装されています。
但し適宜 purge recyclebin してあげないとゴミがたまりそうです。
●indexの表領域
性能向上の為、データ/インデックスを別HDDに保存する場合、データ/インデックスで表領域を分ける必要があります。oracle_enhanced_adapterは成長しており、migrationで
add_index :posts, :title, :tablespace => 'foo'
のように指定できます。
●varchar2問題
oracleのvarchar2/char2は歴史的な経緯で、"バイト単位"での長さ指定になります。
よってrailsからスキーマ作成する際は、string => nvarchar2へのマッピングを以前はしていたのですが、最近のoracle enhanced adapterは進化しています!
lib/active_record/connection_adapters/oracle_enhanced_adapter.rb によれば、nls_length_semantics というオプションがデフォルトで'CHAR'となっており、このオプションを指定する事によってvarchar2/char2を文字数単位で長さ指定出来る様になります。
よって何も考えなくても、mysql等と同じように指定できる様になりました。
実際下記の用にスキーマが出来ています。
SQL> desc posts; 名前 NULL? 型 -------------------------- -------- ---------------------------- ID NOT NULL NUMBER(38) TITLE VARCHAR2(255 CHAR) <= CHARで255 BODY CLOB CREATED_AT DATE UPDATED_AT DATE●oracle enhanced adapter独自項目の設定 oracle enhanced adapterには独自の設定項目があり...と思ったら良いページがありました。一部ソースを読んだ結果下記の様です。
# config/initializers/oracle.rb 等に下記内容を設置 ActiveSupport.on_load(:active_record) do ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do # NUMBER(1)をbooleanにマッピングするか self.emulate_booleans = true # DATE型を(rubyの)Date型にマッピングするか self.emulate_dates = false # カラム名が(^|_)date(_|$)とマッチする場合Date型にマッピングするか self.emulate_dates_by_column_name = false # カラム名が(^|_)idとマッチする場合Integer型にマッピングするか self.emulate_integers_by_column_name = true # CHAR(1)かVARCHAR(1)、またはカラム名が_FLAG/_YNで終了する場合Booleanにマッピングするか self.emulate_booleans_from_strings = true # シーケンサー作成する際のオプションを指定する self.default_sequence_start_value = "1 NOCACHE INCREMENT BY 1" end end●雑感
ソース見て思ったのですが、oracle enhanced adapter マジで進化してます!
かなりきっちりしてる感じです(識別子30文字制限問題、in句の1000上限問題 ...etc)
2011年7月2日土曜日
履歴管理システムの無視ファイル
cvs/subversion/git等、色々な履歴管理システムがありますが、いずれも "履歴管理しないファイル" を適切に指定する事が大切です。
# *.classを無視していない為、すごい事になっているチーム結構みた事あります (;´д`)トホホ…
いつも調べてる様な気がしたので、これを気にまとめようかなと調べたら
...
かなり良いの発見しました!
github/gitignore
githubのチームが作成しています。色々な言語・フレームワークのgitignoreファイルサンプルがたくさんあります。ちなみにRails.gitignoreは
のようですね。確かに一通りそろってる感じ!
■追記
2011/08/04 下記もいるような気がする
/spec/reports/
後上記の設定ならdatabase.ymlも消えるので、database.yml.example => database.ymlへのコピーが手順的に入りますね!
# *.classを無視していない為、すごい事になっているチーム結構みた事あります (;´д`)トホホ…
いつも調べてる様な気がしたので、これを気にまとめようかなと調べたら
...
かなり良いの発見しました!
github/gitignore
githubのチームが作成しています。色々な言語・フレームワークのgitignoreファイルサンプルがたくさんあります。ちなみにRails.gitignoreは
*.rbc *.sassc .sass-cache capybara-*.html .rspec /.bundle /vendor/bundle /log/* /tmp/* /db/*.sqlite3 /public/system/* /coverage/ /spec/tmp/* **.orig config/*.yml rerun.txt pickle-email-*.html
のようですね。確かに一通りそろってる感じ!
■追記
2011/08/04 下記もいるような気がする
/spec/reports/
後上記の設定ならdatabase.ymlも消えるので、database.yml.example => database.ymlへのコピーが手順的に入りますね!
登録:
投稿 (Atom)