49hack

見習いエンジニアが魔法使いになるまで

WordPressでfacebookのoEmbed対応をする

WordPressが4.4からoEmbedに対応し、特に埋め込みコードを書かなくてもurlから自動で埋め込みコードを発行してくれるようになりました。

oEmbedって?

例えばTwitterのつぶやきをサイトに埋め込みたい場合、いままでこういう埋め込みコードをコピペしてたのが、

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">\そろそろ本気出す/</p>&mdash; にっしー (@paranishian) <a href="https://twitter.com/paranishian/status/465847958143918080">2014年5月12日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

そのツイートのurlを貼っつけるだけで自動でoEmbedエンドポイントから埋め込みコードを取得して表示してくれるようになりました。

https://twitter.com/paranishian/status/465847958143918080
↓↓↓
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">\そろそろ本気出す/</p>&mdash; にっしー (@paranishian) <a href="https://twitter.com/paranishian/status/465847958143918080">2014年5月12日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

ただ、なぜかfacebookだけ対応しておらず、4.7にて対応予定となっています。。

#34737 (Add Facebook oEmbed support) – WordPress Trac

自分で対応しよう

WordPressのアップグレードは待てないので自分で先に対応しちゃいましょう!

GitHubで便利なスニペットが公開されてるのでそれを使うだけでOKです。

khromov/wp-facebook-oembed: Facebook oEmbed support for WordPress

<?php
function khromov_wp_facebook_oembed() {
    $endpoints = array(
        '#https?://www\.facebook\.com/video.php.*#i'      => 'https://www.facebook.com/plugins/video/oembed.json/',
        '#https?://www\.facebook\.com/.*/videos/.*#i'     => 'https://www.facebook.com/plugins/video/oembed.json/',
        '#https?://www\.facebook\.com/.*/posts/.*#i'      => 'https://www.facebook.com/plugins/post/oembed.json/',
        '#https?://www\.facebook\.com/.*/activity/.*#i'   => 'https://www.facebook.com/plugins/post/oembed.json/',
        '#https?://www\.facebook\.com/photo(s/|.php).*#i' => 'https://www.facebook.com/plugins/post/oembed.json/',
        '#https?://www\.facebook\.com/permalink.php.*#i'  => 'https://www.facebook.com/plugins/post/oembed.json/',
        '#https?://www\.facebook\.com/media/.*#i'         => 'https://www.facebook.com/plugins/post/oembed.json/',
        '#https?://www\.facebook\.com/questions/.*#i'     => 'https://www.facebook.com/plugins/post/oembed.json/',
        '#https?://www\.facebook\.com/notes/.*#i'         => 'https://www.facebook.com/plugins/post/oembed.json/',
    );
    foreach ( $endpoints as $pattern => $endpoint ) {
        wp_oembed_add_provider( $pattern, $endpoint, true );
    }
}
add_action( 'init', 'khromov_wp_facebook_oembed' );

こういうのはもっと早く対応してほしいですね〜。

参考

URLからクエリ文字列(GETパラメータ)を抽出してハッシュにする

環境

$ rails -v
Rails 4.1.1
$ ruby -v
ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin14.0]

方法

例えば、URLからクエリ文字列を抽出する場合はURIを使えば簡単。

uri_str = 'http://sample.com/path/to/hoge?a=1&b=2'
uri = URI.parse(uri_str)
uri.query
#=> "a=1&b=2"

このクエリ文字列をハッシュにしたい場合はRack::Utilsを使います。

Rack::Utils.parse_nested_query(uri.query)
#=> {"a"=>"1", "b"=>"2"}

余談

このハッシュにいろいろ処理して、クエリ文字列に戻してみたり。

new_query_hash = Rack::Utils.parse_nested_query(uri.query) 
new_query_hash['a'] = 3
new_query_hash['c'] = 4
new_query_hash.to_query
#=> "a=3&b=2&c=4"

参考

parsing - Parse a string as if it were a querystring in Ruby on Rails - Stack Overflow

「zsh: command not found pod」と言われた時の対処法

$ pod setup
zsh: command not found pod

昨日まで使えてたのにある日突然podコマンドが使えない…!!
対処法まとめておきます〜。

環境

一応Macのバージョンは以下です。

OS X 10.10.5

対処法

とりあえずcocoapodsがあるか確認

$ gem list | grep cocoapods

rbenvでバージョン管理してる場合はrehash

たいていはこれで解消するパターンが多い。

$ rbenv rehash

インストールしなおす

$ sudo gem uninstall cocoapods
$ sudo gem install cocoapods

バージョンを指定する

cocoapods (1.0.0) だと変なエラー出たのでバージョン指定してインストールしなおします。

$ pod setup
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.0.0/lib/cocoapods/sources_manager.rb:159:in `<module:SourcesManagerMissingConstant>': uninitialized constant Pod::SourcesManagerMissingConstant::Set (NameError)
...
$ sudo gem uninstall cocoapods
$ sudo gem install cocoapods -v0.39

以上です。

参考

絵文字が含まれているかどうかチェックする

本当はmysql側のエンコーディング変更すべきですが、アプリケーション側の応急処置として。

class Post < ActiveRecord::Base
    validate :no_emoji

    # 本文に絵文字を使わないように
    def no_emoji
        if self.content.present?
            emoji_chars = self.content.each_char.select{|c| c.bytes.count >= 4}
            if emoji_chars.size > 0
                errors.add(:content, "に絵文字(#{emoji_chars.join('')})は使用できません。")
            end
        end
    end
end

文字列をeach_charで回して、バイト数が4以上ある文字を検出しています。
シンプルですが、文字量多い場合は大変。。

参考

テキスト中にあるURLを抽出し、リンク(aタグ)に変換する

テキスト中にあるリンクがある場合は、ちゃんとクリックできるリンクにして出力したい!
ということで、いろいろ調べてみました。

URLを抽出する

textからURLを抽出するだけであれば、URI.extractを使えばOKです。

text = 'aaaaa http://xxx.com bbbbb http://yyy.com'
URI.extract(text,['http','https'])
=> ["http://xxx.com", "http://yyy.com"]

URI.extractを利用するパターン(NGパターン)

URI.extractを利用してgsubでURLをaタグに置換します。

text = 'aaaaa http://xxx.com bbbbb http://yyy.com'
URI.extract(text,['http','https']).uniq.each do |url|
    sub_text = ""
    sub_text << "<a href=" << url << ">" << url << "</a>"
    text.gsub!(url, sub_text)
end
=> "aaaaa <a href="http://xxx.com">http://xxx.com</a> bbbbb <a href="http://yyy.com">http://yyy.com</a>"

ただ、この場合、うまくいかない場合があります。

text = 'aaaaa http://xxx.com/hoge bbbbb http://xxx.com'
URI.extract(text,['http','https']).uniq.each do |url|
    sub_text = ""
    sub_text << "<a href=" << url << ">" << url << "</a>"
    text.gsub!(url, sub_text)
end
=> "aaaaa <a href="<a href="http://xxx.com">http://xxx.com</a>/hoge"><a href="http://xxx.com">http://xxx.com</a>/hoge</a> bbbbb <a href="http://xxx.com">http://xxx.com</a>"

aタグ中のhref=http://xxx.comも置換させてしまい、変なことになっていました。

http://xxx.com/hoge  
=> <a href="http://xxx.com/hoge">http://xxx.com/hoge</a>  
=> <a href="<a href="http://xxx.com">http://xxx.com</a>/hoge"><a href="http://xxx.com">http://xxx.com</a>/hoge</a>

URI.regexpを利用するパターン(OKパターン)

URI.regexpを利用して正規表現を使ってURLを置換します。

text = 'aaaaa http://xxx.com/hoge bbbbb http://xxx.com'
uri_reg = URI.regexp(%w[http https])
text.gsub!(uri_reg) {%Q{<a href="#{$&}">#{$&}</a>}}
=> => "aaaaa <a href="http://xxx.com/hoge">http://xxx.com/hoge</a> bbbbb <a href="http://xxx.com">http://xxx.com</a>"

こちらのほうが簡単&完璧です。

以上です。

参考

grep, xargs, sedを使って複数ファイルの文字列を一括で置換する

例えば、カレントディレクトリ下のファイルで「置換前の文字列」が含まれるものを一括で「置換後の文字列」に置換したい場合は以下のようにします。

$ grep -rl '置換前の文字列' ./ | xargs sed -i '.bk' 's/置換前の文字列/置換後の文字列/g'

iオプションでファイルを直接書き換えます。

第1引数にはバックアップファイル名に使用する文字を入れます。上記の場合は、対象ファイル名.bkが作成されます。

第1引数がない場合、sed: 1: "app/views/hoge/fug ...": command a expects \ followed by text と怒られて動きません。

まあ、gitで管理してる場合はdiffで見れるので空文字でOKです。

$ grep -rl '置換前の文字列' ./ | xargs sed -i '' 's/置換前の文字列/置換後の文字列/g'

参考

twilio-rubyでSMSを送信する

f:id:paranishian:20160324232321p:plain

SMS送信にtwilioを使ってみたのでメモがてらざっとまとめていきます。

はじめに

twilioからgemが提供されてるのでインストールします。

gem 'twilio-ruby', '~> 4.2.1'

twilioでSMS送信する場合は、SMS対応している国の電話番号を購入する必要があります。
まあ無難に+1(Canada)の番号を購入すればOKです。

さっそく使ってみる

sidやtokenは同じものを使っていくのでまずはconfigに設定ファイルを準備します。

# config/initializers/twilio.rb
require 'twilio-ruby'
Twilio.configure do |config|
    config.account_sid = TWILIO_ACCOUNT_SID
    config.auth_token = TWILIO_AUTH_TOKEN
end

SMSを送信する

準備ができたのでさっそくSMS送信。

client = Twilio::REST::Client.new
client.messages.create(
    from: "+14159341234",
    to: "+18004567890",
    body: "hello world!"
)

送信したSMS一覧を取得する

listリソースを使って一覧を取得します。

client = Twilio::REST::Client.new
@list = client.messages.list

ページングを実装したい場合は、next_pageメソッドを使うか、after_sidパラメータを利用すればOKです。

# next_pageを使う
@list = @list.next_page

# 取得した最後のsidをafter_sidに渡す。こっちのほうが使い勝手が良い。
@list = client.messages.list(after_sid: @list.last.sid)

送信元、送信先でフィルタリングすることもできます。

# toで絞込
@list = client.messages.list({to: "+14159341234"})

# fromで絞込
@list = client.messages.list({from: "+18004567890"})

コールバックを指定する

メッセージのステータスが変更されるたびに叩かれるコールバックURLを指定できます。

client = Twilio::REST::Client.new
client.messages.create(
    from: "+14159341234",
    to: "+18004567890",
    body: "hello world!",
    status_callback: "http://your.domain.com/call-me-back-here"
)

さいごに

Twilioでエラー頻発しておかしいなと思ったらTwilio Statusをチェックしましょう。

以上です。

参考

with_optionsを使う際の注意点

環境

Railsのバージョンは4.1です。

$ rails -v
Rails 4.1.1

そもそもwith_optionsって?

オプションの記述が冗長にならないようにまとめてくれる便利メソッドです。

class User < ActiveRecord::Base
  validate :name, presence: true, if: :hoge
  validate :address, presence: true, if: :hoge
  validate :email, presence: true, if: :hoge
end

例えば上記はこう書けます。

class User < ActiveRecord::Base
  with_options if: :hoge do |u|
    u.validate :name, presence: true
    u.validate :address, presence: true
    u.validate :email, presence: true
  end
end

validateで使うことが多いですが、他にもいろいろ使えます。

class Account < ActiveRecord::Base
  with_options dependent: :destroy do |assoc|
    assoc.has_many :customers
    assoc.has_many :products
    assoc.has_many :invoices
    assoc.has_many :expenses
  end
end

ハマりポイント

with_options if:の中でif:を使うと上書きされてしまう。 例えば下記の場合、

class User < ActiveRecord::Base
  with_options if: A do |u|
    u.validate :name, presence: true
    u.validate :address, presence: true, if: B
  end
end

adressはif Aand Bにはならず、if: Bになる

class User < ActiveRecord::Base
  u.validate :name, presence: true, if: A
  u.validate :address, presence: true, if: B #=> 条件Aは無視
end

対処法

with_optionsの中で同じオプションを使わない

さきほどの場合は、unless:を使って対応します。

class User < ActiveRecord::Base
  with_options if: A do |u|
    u.validate :name, presence: true
    u.validate :address, presence: true, unless: !B #=> if: Bと等価
  end
end

with_optionsを分ける

ネストが増えそうな場合はそもそも分けてやったほうがいいです。

class User < ActiveRecord::Base
  with_options if: A do |u|
    u.validate :name, presence: true
  end
  with_options if: [A, B] do |u|
    u.validate :address, presence: true
  end
end

以上です。 意外とハマりがちなので注意しましょう〜。

参考

emailのバリデーションをちょっとだけ工夫する

Userモデルのemailに、存在チェックとフォーマットチェックを入れることを考えます。

必要なgemをインストールしておきます。

# Gemfile
gem 'validates_email_format_of'

普通にやってみる

class User < ActiveRecord::Base
    validates :email, presence: true, email_format: {:with => /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i,:message => 'の形式が不適切です。'}
end

この場合emailが空のときにはエラーが2つ入ってしまいました。

user = User.new
user.valid? #=> false
user.errors[:email] #=> ["を入力してください。", "の形式が不適切です。"]

ちょっと工夫してみる

上記だとなんだかいまいちなので、emailが空のときは存在エラー、フォーマットがおかしいときはフォーマットのエラーだけが入るようにします。

バリデーションを2つに分けて、フォーマットチェックにallow_blankを設定します。

class User < ActiveRecord::Base
    validates :email, presence: true
    validates :email, email_format: {:with => /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i,:message => 'の形式が不適切です。'}, allow_blank: true
end

すると、いい感じのバリデーションになりました。

user = User.new
user.valid? #=> false
user.errors[:email] #=> ["を入力してください。"]

user.email = "hoge"
user.valid? #=> false
user.errors[:email] #=> ["の形式が不適切です。"]

こっちのほうがユーザにとってわかりやすいエラーを表示できそう。

以上です。

mysqlで同一単語を複数カラムにわたって検索する

CONCAT関数を使います。 例えばcolA, colBの2つのカラムでaaaを検索したい場合、

SELECT * FROM hoge_table where CONCAT(colA, colB) = "aaa"

となります。

ただ、colA, colBのいずれかがNULLの場合、CONCATの結果もNULLになってしまうのでIFNULLで回避します。

SELECT * FROM hoge_table where CONCAT(IFNULL(colA,''), IFNULL(colB,'')) = "aaa"

ちなみに

Ransackで書く場合にも使えるので便利です。

   ransacker :col_a_b do |parent|
        Arel.sql("CONCAT(IFNULL(colA,''),'/',IFNULL(colB,''))")
    end

参考