HashでArray#map!(collect!)のようなことを実現する

意外とハマったのでメモ。


例えば、配列の各要素を数値から文字列に変換したいと思ったら、map!(collect!)メソッドを使って実現できます。

array = [1, 2]

array.map!{|item| item.to_s}
#=> ["1", "2"]


では、これと同様のことをハッシュで行う(各キーに割り当てられた値を数値から文字列に変換する)には、どうすればよいでしょうか。
HashクラスもEnumerableインクルードしてるから、map!で実現できる?

hash = {:a => 1, :b => 2}

hash.map!{|key, value| value.to_s}
#=> NoMethodError: undefined method `map!' for {:a=>1, :b=>2}:Hash

NoMethodErrorとなってしまいました。
Rubyリファレンスマニュアルにあるとおり、mapは「配列を返」すため、Hashオブジェクトに対する破壊的メソッドとしては機能しないのですね。
また、私は「map = 置換メソッド」という認識をしていたために上記のような書き方をしましたが、この認識もあまりよくないと思いました。
mapは「ブロックを評価した結果を全て含む配列を返」すので、この書き方では、value.to_s の集合を期待していることになってしまいます。


ハッシュは hash[key] = value の形で値を指定できるので、ハッシュのまま値を変換するなら、次のような方法があります。

hash.each{|key, value| hash[key] = value.to_s} 
#=> {:a=>"1", :b=>"2"}


あるいは、mapで得た配列からハッシュを生成しなおす方法もあります。

alist = hash.map{|key, value| [key, value.to_s]}
#=> [[:a, "1"], [:b, "2"]]
Hash[*alist.flatten]
#=> {:a=>"1", :b=>"2"}


個人的には、一行で書ける前者が好みですね。
もっとスマートな方法があればご教示下さい。


WEBデザイン ユーザビリティ

そういえばWEBアプリのリデザインを担当したことがあり、そのとき読んだ本のメモが出てきたので、まとめました。
実際に読んだのはこちらでしたが、現在は改訂版が出ているようです。

ユーザビリティ

  • ユーザビリティの基本概念
    • 有効さ(なにかを目的として操作を行い、できたかできなかったか)
    • 効率(目的を達するまでに要した労力)
    • 満足度(使ってみてストレスを感じたか、あるいは心地よかったか)
参考概念
    1. バリアフリー(障害を取り除く)
    2. ユニバーサルデザイン(誰もが使えるデザイン。最初から障害を作らない)
    3. アクセシビリティ(到達容易度)

サイトの構築目的

  • 目標設定
    • 定量目標(どれだけの成果?)
    • 定性目標(どのような成果?)
  • ユーザーの行動を予測
    • 何を求めて使うのか?
    • どのように使うのか?

インパク

  • ユーザーの記憶に残しやすくするテクニック
    • チャンキング(かたまりを作る)
    • マジカルナンバー(一度に提示する情報は7±2個まで)
    • ラベリング(整理・分類)
    • 親近効果・初頭効果(最後に見聞きしたものが最も記憶に残り、次いで最初に見聞きしたもの)
分類テクニック
    1. 近接の要因(似ている情報を近くに集める)
    2. 類同の要因(似ている情報の見た目を揃える)
    3. 閉合の要因(似ている情報を囲む)
  • ユーザーの視線
    • 左から右・上から下に見ていく。
    • 大きいもの・上にあるものが重要だと判断する。

設計

  • 分類の方法
    • 時系列
    • あいうえお順
    • セクション別
    • 地域別
    • 生活シーン(目的を動詞化)
    • カテゴリー別
    • データ属性(文章・画像・表etc.)
  • サイトの構造
    • リニア構造(「進む」「戻る」だけで移動できる平らな構造)
    • 階層構造
    • ウェブ構造(各ページが互いにリンクしあっている構造)
階層構造のポイント
    1. 同じ階層同士はリンクする。
    2. グループごとに階層の深さを統一する。
    3. 階層が広く浅いと、ユーザーがすぐに目的を達成できる。
    4. 階層が狭く深いと、リンクの入り口が少なくて済む。
  • シンプルイズベスト
    • 例外をつくらない
    • 削ぎ落とす

ナビゲーション

  • トップページの役割
    • そのサイトがどういうサイトか伝える役割
    • 情報へ案内する目次・地図の役割
ポイント
    1. トップページへいつでも戻れるようにする。
    2. トップページのようなページを複数つくらない。
  • ナビゲーションの方法
    • ことば
    • イメージ
    • 配列(ユーザーに明確な目的がない場合、左上から見ていく)
  • グローバルナビゲーションの役割
  • リンク
    • 立体感で「押せる」雰囲気
    • リンク先を想定できるような情報

パフォーマンス

  • 表示を重くする要因
    • 無駄なタグ
    • 重い画像
    • 大量の文章
    • JavaScriptCSS
    • 大きなテーブル
    • テーブルの入れ子
    • その他、ブラウザに負担をかけるコード


デザインに限らず、フレームワークとして活用できそうな気がします。

子コントローラで特定のフィルタを無効化する

親コントローラで定義した before_filter / after_filter はすべての子コントローラに継承されますが、「この子コントローラではフィルタを外したい」ということがあります。
これは skip_before_filter / skip_after_filter を使って実現でき、オプション(:only / :except )も指定可能です。

class ApplicationController < ActionController::Base
  before_filter :hoge

  private

  def hogeend
end

class ChildController < ApplicationController
  skip_before_filter :hoge,
                     :only => :fuga

  def fuga   # hogeは実行されないend

  def piyo   # hogeは実行されるend
end


コールバックメソッドのカプセル化

複数のテーブルの任意のフィールドにおいて、レコード保存時に同一の処理を行いたかったので、before_saveメソッドをカプセル化するハンドラを作成しました。

  • 共有したいコールバックメソッドを定義したハンドラクラスを、app/models下に作成
class Hoge
  def initialize(attrs_to_manage)
    @attrs_to_manage = attrs_to_manage
  end

  def before_save(model)
    @attrs_to_manage.each do |filed|
      model[field].fugafuga() if model[field]  # 行いたい処理
    end
  end
end

class ActiveRecord::Base
  def self.hogehoge(*attr_names)
    before_save Hoge.new(attr_names)
  end
end
  • 任意のモデルクラスで宣言
class Piyo < ActiveRecord::Base
  require "hoge"
  hogehoge(:attr1, :attr2)
           # 任意のフィールドを渡す
end

ActiveRecord::Baseに直接メソッドを加えるのではなく、ハンドラ内で定義しています。
もし直接加えた場合、モデルではrequireなしにhogehoge()のみ宣言すればよくなるので、アプリケーションの基本ルールとなるような処理の場合にはそちらの方がよいかもしれません。


プロセス名からプロセスをkill

プロセスをkillするのに、プロセス番号ではなくプロセス名を指定する方法はないんか? と探していて、便利コマンドに出会いました。


例えばこのように、複数のrubyプロセスが立ち上がっている場合、

$ ps ax
 2007 ?        S      0:01 ruby script/server webrick -p 80 -d
 2010 ?        S      0:00 ruby script/webrick_ssl -d

次のコマンドで一発killできます。

$ pkill -KILL ruby

ActionMailer基礎とヘルパーの指定

コードリーディングの際にActionMailerに触れたので、覚え書き。

  • ActionMailer基礎
class HogeMailer < ActionMailer::Base
  def fuga()
    …
  end

  def piyo()
  …
  end
end
    • メールテンプレート
      app/views/hoge_mailer/fuga.rhtml, piyo.rhtml
    • メールオブジェクトの作成
      HogeMailer.create_fuga()
    • メールオブジェクトの作成+送信
      HogeMailer.deliver_piyo()
  • メールテンプレートで自作ヘルパーを使用する
    メールテンプレート内で自作ヘルパーメソッドを使おうとしたところ、NoMethodErrorが出ました。
    ビューの呼び出しにコントローラを経由していないからでしょうか。
    メーラクラスでヘルパーを指定してあげたら、使えるようになりました。
class HogeMailer < ActionMailer::Base
  helper ApplicationHelper

  def fuga()
    …
  end

  def piyo()
  …
  end
end

Twitterからmixiボイスへのポスト

自分で書いてみようかと思っていたのですが、rubyスクリプトを公開している方がいらっしゃったので拝借しました(笑)
ちなみにrubyのバージョンは1.8.6です。

  • ライブラリをインストール
$ gem install mechanize
$ gem install rubytter
    • Mechanizeインストール時に出くわしたエラー
Install required dependency racc? [Yn]  y
Building native extensions.  This could take a while...
ERROR:  While executing gem ... (Gem::Installer::ExtensionBuildError)
    ERROR: Failed to build gem native extension.

ruby extconf.rb install mechanize
extconf.rb:3:in ``': No such file or directory - uname -p (Errno::ENOENT)
        from extconf.rb:3
    • gemをアップデート(1.3.5)して解決
$ gem update --system
  • rubyファイルを実行
$ ruby twitter_to_mixi.rb
test

おぉ、ポスト成功!
ちなみに最初、iconvライブラリ関係のエラーが出ましたが、自動生成してくれるキャッシュファイルを自分で作ってしまっていたからでした。


私の場合、Windowsのタスクスケジューラでバッチファイルを15分おきに走らせています。

ruby "(パス)\twitter_to_mixi.rb"