いつNeovimに乗り換えるか?
今でしょ
はい、時代遅れのこれがやりたかっただけです。 久しぶりにneovimのHEADをbuildしてみたところ完璧に使えちゃったので皆さん乗り換えましょうという宣伝です。
7/8追記
※注意:半分深夜のテンションで書いた記事ですので適用はご自身の責任でおねがいします。特に末尾のNoticeのところに書いてあるようにいくつかのpluginが動かないので注意してください。
1. build
# Ubuntuの場合。その他OSの依存関係は https://github.com/neovim/neovim/wiki/Installing#manual-install 参照 sudo apt-get install libtool autoconf automake cmake libncurses5-dev g++ pkg-config cd ~/.src # お好きなところで git clone https://github.com/neovim/neovim.git cd neovim make sudo make install # Pacoとかその他お好みで
これで/usr/local/bin/nvim
にバイナリがインストールされます。
/home以下に入れたいときは
cmake -DCMAKE_INSTALL_PREFIX:PATH=~/usr/ make install
とすると~/usr/bin
にインストールできます。
また、Mac OSとArch LinuxにはOS専用のパッケージが提供されています。詳しくはInstalling · neovim/neovim Wiki · GitHub
2. runtime
※buildの時に、DCMAKE_INSTALL_PREFIX
を指定してインストール先を変えた場合のみ必要な手順です。特に指定せず/usr/local/binにインストールした場合は不要です。
7/8訂正 本家VimのRuntimeが以下のような位置関係でインストールされている場合のみ不要な手順です。
/path/to/bin/nvim # バイナリ /path/to/share/vim/vim74 # Runtime; このなかにautoloadとかindentとかsyntaxとか
このままでもnvim
コマンドで動きますが、シンタックスハイライトとかインデントとかが働きません(NeoVim本体のレポジトリにVim Runtimeファイル群が入っていないため)。neovim/vimscript
レポジトリが提供されているので自分で設定します。
cd ~/usr/share git clone https://github.com/neovim/vimscript.git alias nvim='VIM=~/usr/share/vimscript/runtime/ nvim'
参考: Installing · neovim/neovim Wiki · GitHub
3. .nvimrc
と.nvim/
の準備
NeoVimの設定ファイルは~/.nvimrc
です。存在しない時は.vimrc
を読んでくれるとかそういう親切設計にはなっていないようなので、シンボリックリンクを貼りましょう。
ln -sf ~/.vimrc ~/.nvimrc ln -sf ~/.vim/ ~/.nvim/
参考:Differences from Vim · neovim/neovim Wiki · GitHub
4. Try it!
これでnvim
とコマンドを打つと今までのVimと全く同じ環境が再現できるはずです。
満足したらVimとお別れしてNeoVimと幸せに過ごしましょう。
# Uninstall Vim sudo paco -r vim # 各自の環境に合わせて # Use NeoVim instead of Vim echo 'alias vim=nvim' >> ~/.bashrc
Notice
NeoVimはpluginの管理方法を変えると宣言しているので、将来的にこの記事の方法では動かなくなる可能性があります。 また、現状でもいくつかのpluginとは互換性が無いようです。
@TaKeZo_I たぶんLingrのログ的にはvimshell, neocompleteあたりは動かない、らしい
— Λlisue@監視対象 (@lambdalisue) 2014, 7月 7
7/8追記 Shougoさんに解説をいただきました。
@lambdalisue @TaKeZo_I vimproc(vimshell)は最近libcallの実装が取り込まれたので動作するようになっています。残る問題は、if_luaが使えないのでneocompleteが使えないということと、GUIが使えないことです(まだ実装中)
— 暗黒美夢王(:Shougo) (@ShougoMatsu) 2014, 7月 7
Rails 4.1で導入されるActiveRecord Enumsに隠された罠
TL;DR
ActiveRecord::Enumで、安易に値を追加・削除するのは危険。将来の変更に備えて、DBに登録される値をHashで指定しましょう。
class User < ActiveRecord::Base # This is BAD enum authority: [:registrant, :admin] # This is OK enum authority: { registrant: 10, admin: 20 } end
本編
年度も変わりさて心機一転、という季節なのに私の地元は昨日大雪でしたが、皆様いかがお過ごしでしょうか。さて今日は表題の通り、Rails 4.1における目玉のひとつ、ActiveRecord Enumsについてです。
ActiveRecord Enumsとは
例えばUser
モデルにauthority
(権限)という属性を持たせたい時によくやるのは、DBにinteger
のフィールドを用意して、0なら登録ユーザー、1なら管理者、...とする方法ですね。
この方法は便利ですが、0とか1とかいう値をそのまま数値で管理し続けるのは辛いので、scope :registrant, -> { where(authority: 0 }
みたいなscopeとか、その他のユーティリティメソッドを自分で用意する必要がありました。
そこでActiveRecord Enumsを用いると、
class User < ActiveRecord::Base enum authority: [:registrant, :admin] end
と定義してやることで、自動的に
User.registrant # scope :registrant, -> { where(authority: 0) } u = User.registrant.first u.authority # => "registrant" u.admin! # u.update_attribute(:authority, 1) User.authorities # => { registrant: 0, admin: 1 }
みたいな便利メソッドがごそっとまとめて定義されます。
問題点
enum authority: [:registrant, :admin]
と定義すると、DBに登録される値は自動的にregistrant
は0、admin
は1に設定されます。
この状態で、rails consoleからadmin
ユーザーを一人登録しておきます。
u = User.new(name: "hoge", authority: User.authorities[:admin]) u.save! u.authority # => "admin"
さてここで、サイトにゲスト権限を設定したくなった場合はどうなるでしょうか。自然に変更するとすれば仮にリストの先頭に加えてしまうと
enum authority: [:guest, :registrant, :admin]
となりますね。
2014-05-11追記
はてブコメントにて何件か「なぜ末尾ではなく先頭に足すのが『自然』なのか」というご指摘をいただきましたが、意図としてはauthorityの低い順に「ゲスト、登録ユーザ、管理者」と並んでいる方が「登録ユーザ、管理者、ゲスト」という並びよりも自然かなというものでした。
とはいえ、やはりenumの何たるかを考えると、後ろに足すのが普通だというご指摘は明らかに的を射ていますので、本文を修正してあります。
---追記ここまで---
この時、先ほど登録したhogeさんの状態を確認してみましょう。
u = User.find_by(name: "hoge") u.authority # => "registrant"
なんと!hogeさんがadmin
からregistrant
に格下げされてしまいました!
メカニズム
最初に
enum authority: [:registrant, :admin]
の状態でhogeさんをadmin
として登録した時、DBには1
という値が保存されています。決して"admin"
という文字列が保存されているわけではありません。integer
のカラムなのですから当然です。
先程は、この状態でmodel
を
enum authority: [:guest, :registrant, :admin]
と書き換えた結果、DBに保存されている1
という値が示すauthority
はregistrant
になってしまっています。つまり
User.authorities # => { guest: 0, registrant: 1, admin: 2 }
というわけです。そのため、hogeさんのレコードが持っていた1
という値により、hogeさんはregistrant
であるとされてしまいました。
解決策
ActiveRecord Enumsには、DBに保存される値をHashで指定できます。
最初(guest
がなかった頃)に
enum authority: { registrant: 10, admin: 20 }
としておけば、admin
として登録したhogeさんのレコードには20
という値が保存されます。
これならば、guest
を以下のように追加しても問題ありません。
enum authority: { guest: 5, registrant, 10, admin: 20 }
ユーザーの権限の種類なんて最初っからきちんと設計しろよ!という話も無くはありませんが、変更は常に起こりうるものです。今回のケースでは10文字くらい多くタイプするだけで変更耐性を付けられるのですから、ぜひとも採用しておきたいものだと思います。
Rubyで電話番号整形
どもです。最近は夕方になっても暗くならなくなって時間間隔が微妙に狂いかかっているところですが皆さんお元気ですか?
さて、自身初のRubyGemを公開したので宣伝Postします。ステマじゃないです。ガチマです。
URL
tel_formatter | RubyGems.org | your community gem host
iTakeshi/tel_formatter · GitHub
できること
- 電話番号の整形(市外局番は辞書ファイルから参照)
- 全角とか余計な記号が入ってても大丈夫
これだけですが意外と既存のGemが落ちてなかったので練習問題として作ってみました。
TODO
- ハイフン以外の区切り文字とか、
(03)1234-5678
みたいな出力形式に対応したい。
使い方
# Gemfile gem 'tel_formatter'
require 'tel_formatter' TelFormatter.format('0312345678') # => '03-1234-5678' TelFormatter.format('0126712345') # => '01267-1-2345' TelFormatter.format('03.1234.5678') # => '03-1234-5678' # 桁数がおかしいものや、存在しない市外局番なんかは弾く TelFormatter.format('031234567') # => ArgumentError
Gemの公開ってこんなに簡単だったんですね、という感じですね。 これからはもっと積極的に公開していけるといいと思いました。
Travisはすごく便利なんですが連携させたいCoverallsがいつまでたってもcoverage: unknownなのが気に食わないです。HQに連絡すれば治るという噂ですがどうなんですかね。
それではごきげんよう
「それNArrayでできるよ」をもっと便利にした - 札幌市中央区Ruby会議01
もう先週の話になってしまいましたが、Tricknotesさん主催の札幌市中央区Ruby会議01に参加してきました。 コンパクトな規模ながらも「非常に良いRubyistの活動が観測」(byしまださん)された、有意義かつ楽しい会議でした。
どの発表も聴き応えのあるすばらしいものでしたが、中でもtmaedaさんの「それNArrayでできるよ」は数値計算ライブラリのNArrayをbetter Excelとして使ってしまうコペルニクス的発想で衝撃的でした。
この発表のなかで、NArrayの軸をHuman-Readableな形式で扱えるラッパークラスを作ると便利だよ、というお話がありましたが、
これをもっと便利にするNArrayCube
クラスを試験的に実装してみたので紹介します。
使い方
# 店、世代、性別の3軸からなる売上表を作成 cube = NArrayCube.new(:int, { title: :shop, labels: ['A', 'B', 'C'] }, { title: :generation, labels: ['teens', 'twenties', 'thirties', 'fourties'] }, { title: :gender, labels: ['men', 'women'] } ) # 店A、20代、男性の売上を入力 cube.set({ shop: 'A', generation: 'teens', gender: 'men' }, 100) # 店A、10代の売上を入力 cube.set({ shop: 'A', generation: 'teens' }, [90, 110]) # 店Bの売上を一気に入力(4世代 × 2性別のArray) cube.set({ shop: 'B'}, [ [50, 80, 60, 75], [70, 40, 35, 50] ]) # 店Cの売上を一気に入力:第2引数を一次元のArrayで与えると勝手に4x2に変換してくれる cube.set({ shop: 'C'}, [70, 80, 55, 60, 30, 45, 70, 60]) # 10代の売上を取得 cube.get({ generation: 'teens' }) #=> # #<NArrayCube: ... # [ [ 90, 50, 70 ], # [ 110, 70, 30 ] ]> # 10代の売上の合計 cube.get({ generation: 'teens' }).sum #=> 420 # 10代の売上を店ごとに集計 => 男性+女性の値を計算すればよい cube.get({ generation: 'teens' }).sum(:gender) #=> # NArray.int(3): # [ 200, 120, 100 ]
ツボ
NArrayCube#set
で複数のセルに一気に値を流し込めるNArray#sum
をNArrayCube#sum
でラップしたことにより、軸の指定をHuman-Readableに行える
TODO
- spec書く
- document書く
NArray#sum
だけじゃなくprod
/max
/min
とかその他のメソッドにも対応したい =>method_missing
とか使えるかな?- 「10代の売上を店ごとに集計」は
cube.get({ generation: 'teens' }).sum_by(:shop)
みたいに書けたほうが直感的 - getで軸ラベルを複数指定できたほうがいい
cube.get({ shop: ['A', 'B'] })
- tmaedaさんがいいよって言ってくれたらGem化して公開
コード
require 'narray' class NArrayCube NARRAY_TYPES = %i(byte sint int sfloat float scomplex complex object) class NArrayAxis def initialize(id, title, labels) @id = id @title = title.to_s @labels = labels.map(&:to_s) end def value_index(value) @labels.index(value.to_s) end attr_reader :id, :title, :labels end def initialize(type, *axes) if NARRAY_TYPES.include?(type) @type = type else raise ArgumentError end @axes = axes.map.with_index do |a, i| NArrayAxis.new(i, a[:title], a[:labels]) end @narray = NArray.method(type).call(*@axes.map { |a| a.labels.length }) end def get(conditions) conditions = build_condition(conditions) values = @narray[*conditions] axes = conditions.map.with_index do |c, id| if c.class == Array a = @axes[id] { title: a.title, labels: a.labels.select.with_index { |v, i| c.include? i } } else nil end end NArrayCube.new(@type, *axes.compact).set({}, values) end def set(conditions, values) conditions = build_condition(conditions) unless [Array, NArray].include?(values.class) @narray[*conditions] = values else partial = @narray[*conditions] values = NArray.to_na(values) if partial.shape == values.shape @narray[*conditions] = values elsif partial.total == values.total && values.shape.length == 1 @narray[*conditions] = values.reshape(*partial.shape) else raise ArgumentError end end self end def sum(*titles) if titles.length > 0 @narray.sum(*titles.map { |t| axis(t).id }) else @narray.sum end end private def build_condition(conditions) cond = @axes.map { |a| (0..(a.labels.length - 1)).to_a } conditions.each do |title, label| a = axis(title) cond[a.id] = a.value_index(label) end cond end def axis(title) @axes.select { |a| a.title == title.to_s }.first end end
Railsでbefore_validationコールバックを使う時の落とし穴
TL;DR: before_validation
に登録されているコードがfalse
を返すと、obj.save
はfalse
を返しobj.save!
は例外を投げます。DBには何も登録されません。
Railsのコールバック
便利ですよね!使ってますか?
Rails Guidesの一節を引用してみます。
Callbacks are methods that get called at certain moments of an object's life cycle. With callbacks it is possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database.
すなわち
コールバックは(ActiveRecordの)オブジェクトのライフサイクルの中の特定の時点で呼び出されるメソッドです。コールバックを利用することで、ARオブジェクトが作られるとき・保存されるとき・更新されるとき・削除されるとき・validationが実行されるとき・DBから読み込まれるとき などに必ず実行されるコードを登録することができます。
controllerになんでもかんでも任せてしまうのを防ぎ、modelの責務をmodelに記述することができるようなるのでとても有用です。
※上記guidesやreferencesと合わせて、昨年末のRoR Advent Calendarに投稿されたこの記事を読むと勉強になります ↓
Ruby - てめえらのRailsはオブジェクト指向じゃねえ!まずはCallbackクラス、Validatorクラスを活用しろ! - Qiita [キータ]
before_validation
ここから本題です。
例題として、RailsでSNSを構築することを考えます。ユーザーはメールアドレスに紐付けてアカウントを取得し、登録するとメールアドレス確認メールが送られてきます。メールアドレスの確認が完了しないと、SNS上で活動し始めることができません。利用開始後にユーザーがメールアドレスを変更した場合も、確認のメールが送られてきます。
# db/migrate/20140104120000_create_users.rb class CreateUsers < ActiveRecord::Migration def change create_table :users do |t| t.string :name t.string :email # some more columns... t.string :email_verification_code # メールアドレス確認用乱数 t.boolean :email_verificated # メールアドレス確認済み権限フラグ end end end
このようにテーブルを定義し、以下のようなコードを書きます。
# app/models/users.rb class User < ActiveRecord::Base # validation前に、emailが変更されていればコールバック関数を呼び出す before_validation :set_email_verification_code, if: 'self.email_changed?' # 保存後、メールアドレス確認メールを送信する after_save :send_verification_mail, if: 'self.email_changed?' private def set_email_verification_code # 新しい確認用コードを生成 self.email_verification_code = SecureRandom.hex(10) # 状態を「未確認」にする self.email_verificated = false end def send_verification_mail # 略 end end
一見、とても自然ですよね?しかしこれでは、いくらすばらしいSNSを作ってもユーザーは集まりません。なんたって、登録できないのですから!
Reference中の一文を引用します。
If the returning value of a
before_validation
callback can be evaluated tofalse
, the process will be aborted andBase#save
will returnfalse
. IfActiveRecord::Validations#save!
is called it will raise aActiveRecord::RecordInvalid
exception. Nothing will be appended to the errors object.
意訳しますと
before_validation
コールバックのコードがfalse
(またはfalse
と評価されるもの)を返すと、そこで処理が中止されます(validationは実行されません)。user.save
の途中でこれが起こると、user.save
は単にfalseを返します(DBには保存されません)。user.save!
の途中でこれが起こると、ActiveRecord::RecordInvalid
が起こります。いずれにしても、user.errors
にはいかなるvalidation errorも登録されません。
なんということでしょう!見かけ上はvalidationに失敗しているのとまったく変わらないのに、実はvalidationそのものがスキップされているのです。
上で作ったUserモデルの例では、set_email_verification_code
の最後の式でfalse
を代入しているために、メソッドそのものの戻り値がfalse
になります。(Rubyでは代入式の戻り値は代入された値ですね。)
これを避けるには単にこのメソッド中の式の順番を入れ替えれば良いだけです。
def set_email_verification_code # 状態を「未確認」にする self.email_verificated = false # 新しい確認用コードを生成 self.email_verification_code = SecureRandom.hex(10) # => 戻り値が乱数文字列になる end
Railsチームがどのような意図でこの挙動を設計したのかわかりませんが、陥りやすい落とし穴でなおかつ一度ハマると気づいて抜け出すのが困難です(数十分悩みました)。お気をつけください。
半年ほど放置しておりましたがあけましておめでとうございます(笑
今年もマイペースに更新してまいりますので何卒よろしくおねがいします
Vim の mercurial レポジトリの HEAD が ver7.4a になってた
およそ2ヶ月ほど更新が途切れておりましたが生きています。お久しぶりです。
Vimを最強のPython開発環境にする2 - Λlisue's blog この記事を読んで、一念発起、Vimの環境を徹底的に整備しました(Λlisueさんありがとうございます!)。その中で、NeoComplcacheに代わる新しい補完PluginとしてNeoCompleteが紹介されていました。Vim 7.3.885以上かつLuaインタプリタ装備の環境下で使用でき、NeoComplcacheよりも高速に動作するということで、入れてみようとしたのですが、UbuntuのパッケージになっているVimは7.3.5xxだったのでVimをコンパイルする必要がありました。
続きを読むUbuntu 13.04 で音声のデジタル出力
夜ふかししてます。こんばんは。
先日、Ubuntu 13.04をインストールしたところ、音声のデジタル出力(S/PDIF)ができなくなったということを記事に書きましたが、簡単な設定変更で治りました!
ので、同じ道を通る方のために書き残しておきます。
続きを読む