2010/01/28

Linux コマンド備忘録

検索

find . -exec grep "searchthisstring" '{}' \; -print

# ファイルへ出力
find . -exec grep "searchthisstring" '{}' \; -print > filename.txt 
# 1日以内に変更されたファイルの検索
find . -mtime -2 -exec
# 1日以内に変更されたファイルを検索してディレクトリに移動
find . -mtime -2 -exec mv "{}" ./../P_delete/ \;

その他ヘルプ





2010/01/27

include と include で select を使う方法

Invoice(請求書) というテーブルに customer (顧客)が紐づいている場合、たいてい請求書と一緒に顧客名も表示する必要があります。
<% for invoice in @invoices %>
  <%= invoice.price %> <%= invoice.customer.name %>
<% end %>
のように。でも、この場合、invoice の数 + 1 のsql 文が発生するので無駄が多い。
そんなときは、関連テーブルも include するべし。
@invoices = Invoice.find(:all, :conditions => {..}, :include => :customer)
さらに、include したテーブルの一部しか使わない場合、:select も使いたいが、なぜか標準では、:inlcude したテーブルに :select が使えない。
@invoices = Invoice.find(:all, :conditions => {..}, :include => :customer, :select => 'customers.name, customer.id')
なんてできるといいのに。。。

これを可能にする pluging が Snow Giraffe の Eager loading select plugin. Rails 2.3 まで対応。

2010/01/16

セッションの有効期限を設定

config/initializers/session_store.rb
ActionController::Base.session_options[:expire_after] = 2.hours
こんな感じ

Ruby でファイルの存在をチェック

ローカルにあるファイル
if File.exist?(path)
...
end

リモートのファイル
require 'open-uri' # environments.rb に入れちゃう
begin
  if open(URI.parse(path))
    # ファイルが存在したら実行
  end
rescue => deadmeat
  if deadmeat.message =~/somestring like 403/ 
  # (Errno::ENOENT, OpenURI::HTTPError, etc...)などの exception を表示しない
  # ファイルが存在しなかったらやること
  end
end

文字列操作

http://api.rubyonrails.org/classes/ActiveSupport/Multibyte/Chars.html#M000961

2010/01/15

Rails datetime 設定

Unix timestamp を rails datetime に変更

@somedate = Time.at(1260519945).utc

DB風に表示

@date.to_s(:db)
# 2009-12-11 08:25:45 


strftime 備忘録

example: object.strftime("%Y/%m/%d %H:%M")  
%a - The abbreviated weekday name (``Sun'')
%A - The full weekday name (``Sunday'')
%b - The abbreviated month name (``Jan'')
%B - The full month name (``January'')
%c - The preferred local date and time representation
%d - Day of the month (01..31)
%H - Hour of the day, 24-hour clock (00..23)
%I - Hour of the day, 12-hour clock (01..12)
%j - Day of the year (001..366)
%m - Month of the year (01..12)
%M - Minute of the hour (00..59)
%p - Meridian indicator (``AM'' or ``PM'')
%S - Second of the minute (00..60)
%U - Week number of the current year,
starting with the first Sunday as the first
day of the first week (00..53)
%W - Week number of the current year,
starting with the first Monday as the first
day of the first week (00..53)
%w - Day of the week (Sunday is 0, 0..6)
%x - Preferred representation for the date alone, no time
%X - Preferred representation for the time alone, no date
%y - Year without a century (00..99)
%Y - Year with century
%Z - Time zone name 
%% - Literal ``%'' character 

2010/01/11

一つのフォームに複数の submit ボタンを入れる

たまに一つのフォームに複数の submit ボタンをつけて、どれをクリックするかでロジックを分けたい場合がある。 application.js
function submitWithValueAndConfirm(formid, commitValue, text)
{
 if (window.confirm(text)) {
     var objForm = document.getElementById(formid);
  objForm.commit_value.value = commitValue;
  objForm.submit();
 } else {
  return false;
 }
}


view
<% form_for :req, @req, :url => {:action => :update}, :html => {:method => :put, :id => "action"} do |f| %>
    <input type="hidden" name="commit_value" />
    <%= f.submit "CONFIRM", :onClick => ("return submitWithValueAndConfirm('action','confirm', 'Will do. Okay?');") %>
<%= f.submit "KILL", :onClick => ("return submitWithValueAndConfirm('action','kill', 'Will delete. Okay?');") %>
    <%= f.submit I18n.t('btn_something'), :onClick => ("return submitWithValueAndConfirm('action','confirm', '" + I18n.t('something') + "');") %>
<% end %>
上で return confirm はポップアップメッセージ用。:onclick を使用すると、:confirm が使えないため。

controller
case params[:commit_value]
  when "confirm"
    ...
  when "kill"
    ...
  when "something"
    ...
end

因みに、リンクでフォームをサブミットする方法

<% form_for ([:user, @user], :url => {:action => :create}, :html => {:name => :login}) do |f| %>
   ...
   <a href='#' onclick="document.login.submit();" >Login</a>

<% end %>

Activerecord エラーメッセージの国際化

Activerecord で表示されるエラーメッセージを翻訳したいこともある。 そんな場合、デフォルトでは activerecord.errors のスコープを使う
ja:
  activerecord:
    models:
      memo: メモ
    errors:
      models:
        memo:
          attributes:
            content:
             blank: 空っぽさ  

2010/01/09

Ajax を使ってコメントを挿入

Ajax をつかって、記事(article)にコメント(comment)を挿入するほうほう。よく使うわりにはいつもはまってしまうので備忘録。(カフェトークでは、レッスンにメモを追加するところにつかってます)

article.rb
class Article < ActiveRecord::Base
  belongs_to :user
  has_many :comments
  after_update :save_comments
  
  def new_comment_attributes=(comment_attributes)
    comment_attributes.each do |attributes|
      comments.build(attributes) if attributes[:content].length > 0
    end
  end
  
  # ここでは使ってない
  def existing_comment_attributes=(comment_attributes)
    comments.reject(&:new_record?).each do |comment|
      attributes = comment_attributes[comment.id.to_s]
      if attributes
        comment.attributes = attributes
      else
        comments.delete(comment)
      end
    end
  end
  
  def save_comments
    comments.each do |comment|
      comment.save(false)
    end
  end  
  
  # 特定のユーザのみに表示するためのメソッド
  def user_comments
    self.comments.find(:all, :conditions => {:display_user => true})
  end
end

comment.rb
class Comment < ActiveRecord::Base
  belongs_to :article 
end
views/articles/show.html.erb
<div id="comments">
 <% for comment in @article.pro_comments %>
  <%= render :partial => 'comment_item', :locals => {:comment_item => comment} %>
 <% end %>
</div>
<%= link_to_function "コメント追加" do |page|
      page.insert_html :bottom, :add_comment, :partial => 'comment' , :object => comment.new
<% end%>
<div id="add_comment"></div>

views/pro/articles/_comment_item.html.erb
<div class="comment" id="comment_<%= comment_item.id %>">
 <%= comment_item.content%>
</div>

views/articles/_comment.html.erb
<div class="add_comment" >
<% @comment = Comment.new %>
<% form_remote_for ([:comment, @article]), 
  :url => add_comment_article_path(@article), :html => { :method => 'put' } do |f| %>
   <%= f.text_area :content %>
  <%= f.submit "追加"  %>
<% end %>
</div>

add_comment.rjs
if params[:comment]
  @comment= comment.new(params[:comment])
  if @comment.save
    comment_item_id = "comment_" + @comment.id.to_s
    page.insert_html :bottom, "comments", :partial => "comment_item", :object => @comment
    page[comment_item_id].visual_effect :highlight, :duration => 0.5, :startcolor => "#66CCFF"
    page.replace_html "add_comment", ""
  else
    @comment.errors.each{|attr,msg| page.alert("#{attr} - #{msg}")}
  end      
end

routes.rb
map.resources :articles, :member => {:add_comment => :put}

RJS 参考色々

最近 rjs を使うことが多くなってきたので、備忘録

特定のアイテムの下にインサートする場合

page.insert_html :bottom, "item_" + params[:id], :partial => "item", :object => item
この場合、partial と object名は一致している必要がある。

ハイライト

page[:item].visual_effect :highlight, :duration => 0.5, :startcolor => "#66CCFF"
特定のアイテムをハイライトしたい場合(例 div id="item_{アイテムid}")
item_id = "item_" + item.id.to_s
page[item_id].visual_effect :highlight, :duration => 0.5

その他色々

page.alert("hello")

# 表示トグル
page['mytest'].toggle

page.alert(params.inspect)

target_field = "prefix_" + params[:target]
page[target_field].value = temp

page.visual_effect :toggle_appear, params[:somename], :duration => 0.5
page[:item_form].remove
page.redirect_to root_path
page.replace_html 'someid', :partial => 'somepartial', :object => @someobject
page.insert_html :bottom, 'someid', :partial => 'somepartial', :locals => {:this => this, :that => that}
page << "new Ajax.Request('"+ url_for(:action => :some_ajax_action, :params =>{:myparam => myparam} )+"')"
page.visual_effect :shake 'score'
page.visual_effect :fade  'dodo'
page.delay(1.5) do 
  page.visual_effect :fade 'sunset'
end

2010/01/07

Rails Timezone の設定色々

困ったときのRails timezone api

Timezone 基本設定

config/environments/production.rb 等、各環境後とに・・・
config.time_zone = "Tokyo"
ApplicationController でのタイムゾーン設定
class ApplicationController < ActionController::Base
  before_filter :set_timezone
  def set_timezone
    Time.zone = session[:tz] if session[:tz]
  end
end
そして、ログインに使うコントローラーで session[:tz] を設定
class User::LoginController < UserRootController
  def index
    if request.post?
      if session[:user_id] = user.authenticate(params[:user][:email], params[:user][:password])
        user = User.find(session[:user_id])
        session[:tz] = user.time_zone if user.time_zone
        flash[:notice] = t('logged_in')
        if session[:return_to_path]
          redirect_to session[:return_to_path]
        else
          redirect_to user_root_path
        end
      else
        flash[:error] = t('login.failed')
        session[:user_id] = nil
      end
    end
  end
end

その他色々便利なタイムゾーン系メソッド

特定のタイムゾーンで表示する
sometime.in_time_zone("Hawaii")
# @user.time_zone にユーザーのタイムゾーンが入っている場合
sometime.in_time_zone(@user.time_zone)


オフセット、またはタイムゾーン文字列からタイムゾーンを返す
※特定のタイムゾーンがRails に存在するかどうかチェックするのに便利。Cafetalkでは、旧システムからデータを移行する際に使用しました。
ActiveSupport::TimeZone['Tokyo']
> (GMT+09:00) Tokyo
ActiveSupport::TimeZone[8]
ActiveSupport::TimeZone[+8]
> (GMT+09:00) Osaka


タイムゾーンの名前だけを表示
ActiveSupport::TimeZone[+8].name
> Osaka
オフセットを表示
Time.zone.utc_offset/3600
> -5 など。。。
フォームでタイムゾーンのプルダウンリストを表示
<%= f.time_zone_select( :time_zone, nil, :include_blank => true) %>
http://mad.ly/2008/04/09/rails-21-time-zone-support-an-overview/
http://marklunds.com/articles/one/402
http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html#M001513

2010/01/06

Cron で実行する

プログラムを自動的に実行したいとき。例えば、cafetalk.com では、レッスンの24時間前にリマインダーメールを送ってます。
Linux 系のサーバーで cron を使う場合。(下の例は5分毎に、Mymodelのmymethodを実行)
*/5 * * * * /usr/bin/ruby /var/www/apps/{project}/script/runner 'Mymodel.mymethod' -e production

因みに、Windows で動かす場合。
ruby script/runner 'Mymodel.mymethod' -e development

2010/01/04

Amazon S3 を使ったバックアップなんてどーですか

バックアップはかなり苦労が多い。今まで僕がやってたのは定期的にシェルスクリプトを書いて、別のサーバーに転送する方法。 でも、S3 を使えばらくかなー。 ↓こちらに方法があるので、いずれチャレンジして報告します。 http://paulstamatiou.com/how-to-bulletproof-server-backups-with-amazon-s3

Paperclip と Amazon S3 でファイルを保存する

オンラインショップのサーバー要領がちょっとキツクなったので、画像データを Amazon S3 を使って保存してみょーと思い立つ。
ざっくり説明しますと、Amazon S3が外部ストーレージサービス。データをやり取りした分だけ課金されます。1GBで15円ほどなので、安くは無いけど、いろんなメリットを考えると高くもない。とりあえずテストしてみることに決定~。

まず、Amazon S3 に登録

S3 のデータを見るには『S3Fox』が便利

Firefox のプラグイン『S3Fox』を使うと、FTPクライアント風にAmazon S3の中がみれるよ。

Paperclip 設定の変更

以前、Paperclip の使い方についてメモりましたが、そこからちょいと手を加えます。 environment.rb に aws gem を追加
config.gem "aws-s3", :lib => "aws/s3"
(require する際に require = "aws/s3" とする場合は上のように、 :lib => "aws/s3" を加えて記述) この後、rake gems:install を実行するのを忘れずに。 config/s3.yml を作成
access_key_id: ABCD...
secret_access_key: xYz...
キーとシークレットはここから。 モデルの変更
 #通常バージョン
 has_attached_file :image,
   :styles => {:s => "150x150>", :m =>"320x320>", :l => "500x500>"},
   :url  => "/assets/products/:id/p_:style.:basename.:extension",
   :path => ":rails_root/public/assets/products/:id/p_:style.:basename.:extension"

 #↓S3 バージョン
 has_attached_file :image,
   :styles => {:s => "150x150>", :m =>"320x320>", :l => "500x500>"},
   :storage => :s3,
   :s3_credentials => "#{RAILS_ROOT}/config/s3.yml",
   :path => "products/:id/:style.:extension",
   :url  => ":s3_domain_url",
   :bucket => S3_BUCKET #S3_BUCKET は production/environment.rb development/environment.rb で定義

:s3_domain_url だと http://mybucket.s3.amazonaws.com/products/16152/l.jpg のようになる。
因みに、上で指定した bucket をあらかじめ作っておく必要があるのですー↓ この場合、S3Fox で作成。 これで準備はOK。さっそくアップロードしてみると。。。 入ってる!感激~

Installing plugins from GitHub

ruby script/plugin install git://github.com/thoughtbot/paperclip.git
Somehow I couldn't get this to work with active_scaffold (which I don't use now, anyway). It tells me
Plugin not found: ["git://github.com/activescaffold/active_scaffold.git"]
I have gitbash installed on my Windows so I was finally able to do this.
cd vendor/plugins
git clone git://github.com/activescaffold/active_scaffold.git -o rails-2.3

Paperclip で画像アップロード

ファイルや画像を簡単にアップロードできるプラグイン Paperclip の紹介。
http://github.com/thoughtbot/paperclip/tree/master

最初からテーブルに含める場合

      # migration ファイルに以下を追加
      t.string    :image_file_name, :image_content_type
      t.integer   :image_file_size
      t.datetime  :image_updated_at
      t.string    :image_remote_url
最後の image_remote_url は基本的には必要ないが、URLから画像アップロードしたいので、今回は追加しておく。

既存のテーブルに追加する場合

class AddImageColumnsToProducts < ActiveRecord::Migration
    def self.up
      add_column :products, :image_file_name,    :string
      add_column :products, :image_content_type, :string
      add_column :products, :image_file_size,    :integer
      add_column :products, :image_updated_at,   :datetime
    end

    def self.down
      remove_column :products, :image_file_name
      remove_column :products, :image_content_type
      remove_column :products, :image_file_size
      remove_column :products, :image_updated_at
    end
end

モデルの設定

  has_attached_file :image, 
    :styles => {:s => "150x150>", :m =>"320x320>", :l => "500x500>"},
    :url  => "/assets/products/:id/p_:style.:basename.:extension",
    :path => ":rails_root/public/assets/products/:id/p_:style.:basename.:extension"  
URLからのアップロードを可能にする場合は次も追加

  # ====================================
  # Paper Clip upload from url
  # ====================================
  attr_accessor :image_url    
  before_validation :download_remote_image, :if => :image_url_provided?
  validates_presence_of :image_remote_url, :if => :image_url_provided?, :message => 'is invalid or inaccessible'

  private
 
  def image_url_provided?
    !self.image_url.blank?
  end
 
  def download_remote_image
    self.image = do_download_remote_image
    self.image_remote_url = image_url
  end
 
  def do_download_remote_image
    io = open(URI.parse(image_url))
    def io.original_filename; base_uri.path.split('/').last; end
    io.original_filename.blank? ? nil : io
    rescue # catch url errors with validations instead of exceptions (Errno::ENOENT, OpenURI::HTTPError, etc...)
  end
フォーム
<% form_for ([:admin, @page]), :html => {:multipart => true } do |f| %>
  <%= f.file_field(:image) %>
  ...or provide a URL: <%= f.text_field :image_url %>
<% end %>
表示
<%= image_tag @foobar.image.url(:m) if @foobar.image? %>

Rails 2.2.2 > 2.3.5 uninitialized constant ApplicationController でハマリマス

2.2.2 から 2.3.5 にアップグレードしたら、下記のエラー発生。
uninitialized constant ApplicationController
どうやら、2.3 から、application コントローラーのファイル名が application_controller.rb になったらしいです。ファイル名を変更したらOKになりました。

Rails gem 2.2.2 > 2.3.5 アップグレード時にハマリマス

Rails を 2.3.5にフリーズした後、さっそくエラー
`report_activate_error': RubyGem version error: rack(1.0.0 not ~> 1.0.1) Gem::LoadError)
なるほど、rack が必要そうなので、gem をインストール
gem install rack
Successfully installed rack-1.1.0
1 gem installed
これでも動かない。そりゃ、そうでですね。よく見たら必要なバージョンは 1.0.1。気を取り直して、バージョン指定で。
gem install rack --version 1.0.1 --no-rdoc --no-ri
Successfully installed rack-1.0.1
1 gem installed
これでOK。

特定のバージョンで rails の gem をフリーズする

特定のバージョンでフリーズしたいこともあるなり。
rake VERSION=2.3.5 rails:freeze:gems
config/environments.rb の編集も忘れずに
RAILS_GEM_VERSION = '2.3.5' unless defined? RAILS_GEM_VERSION

Rails 言語をパスで管理する方法

http://waraukigoya.com/ja/mypage のように、URLパスの一部を使って言語管理する方法があります。 パッチがあったので適用。
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb
index ff44849..f3a4f09 100644
--- a/actionpack/lib/action_controller/routing/route_set.rb
+++ b/actionpack/lib/action_controller/routing/route_set.rb
@@ -170,18 +170,24 @@ module ActionController
               def #{selector}(*args)
                 #{generate_optimisation_block(route, kind)}
 
+                locale = #{route.segment_keys.include?(:locale) ? '{ :locale => I18n.locale || I18n.default_locale }' : '{}'}
+
                 opts = if args.empty? || Hash === args.first
                   args.first || {}
                 else
                   options = args.extract_options!
-                  args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
+
+                  segments = #{route.segment_keys.inspect}
+                  #{'segments.delete(:locale) if segments.size > args.size' if route.segment_keys.include?(:locale)}
+
+                  args = args.zip(segments).inject({}) do |h, (v, k)|
                     h[k] = v
                     h
                   end
                   options.merge(args)
                 end
 
-                url_for(#{hash_access_method}(opts))
+                url_for(#{hash_access_method}(locale.merge(opts)))
               end
               protected :#{selector}
             end_eval
次に routes.rb ファイルを設定する必要あり。
  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'
  
  # ====================================================
  # Put all non locale controlled routes above this line
  # 通常ルートの下に、path_prefix で管理したルートを設定
  # ====================================================
  
  map.resources :mypages, :path_prefix => "/:locale"
  map.root :controller => "mypages", :action => "index", :locale => "ja"
  
  map.connect '/:locale/:controller/:action/:id'
  map.connect '/:locale/:controller/:action/:id.:format'
これをやっておけば、リンクヘルパーを使った際、自動的に現在の locale が引き継がれるので便利。
例えば、http://mysite.com/ja/home

の中で、<%= link_to "記事", mypages_path %> と書くと、次のようになる

<a href="http://mysite.com/ja/mypages">記事
もちろん、アプリケーションコントローラーで言語の設定は必要。

Making a link to change locale


application_helper.rb
  def change_locale(locale)
    link = request.parameters
    link[:locale] = locale
    link
  end

Blogger でソースコードを綺麗に embed する方法

開発系の備忘録をブログにアップしようと思ったけど、ブロガーだとソースコードが綺麗に表示されない。なので、google のライブラリ Prettify を使ってみた。
def generate_request_status_select(f)
return f.select( :request_status, [
[ _("Waiting for Quote"), 1],
[ _("Waiting for Customer Decision"), 2],
[ _("Incomplete Order"), 3]
])
end
参考: PMJ Engineering's article on using prettify to paste pretty code onto Blogger. こちらも。