読者です 読者をやめる 読者になる 読者になる

49hack

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

指定した順序どおりにレコードを取得する

ruby rails

作業環境

使用しているRailsは4.1.1です。

$ rails -v
Rails 4.1.1

一番手っ取り早い方法

例えば、ユーザを指定のid順に取得したい場合は、

# 普通にid渡してもできない
user_ids = [4,2,3,1]
users = User.where(id: user_ids)
#=> [1,2,3,4]

# MysqlのOREDER BY FIELD句を使う
user_ids = [4,2,3,1]
users = User.where(id: user_ids).order("FIELD(id, #{user_ids.join(',')})")
#=> [4,2,3,1]

クラスを拡張する方法

頻繁に使用する場合はクラスを拡張して利用したほうがいいと思います。

module Extensions::ActiveRecord::FindByOrderedIds
  extend ActiveSupport::Concern
  module ClassMethods
    def find_ordered(ids)
      order_clause = "CASE id "
      ids.each_with_index do |id, index|
        order_clause << sanitize_sql_array(["WHEN ? THEN ? ", id, index])
      end
      order_clause << sanitize_sql_array(["ELSE ? END", ids.length])
      where(id: ids).order(order_clause)
    end
  end
end

ActiveRecord::Base.include(Extensions::ActiveRecord::FindByOrderedIds)

Person.find_ordered([2, 1, 3]) # => [2, 1, 3]

How to select database records in an arbitrary order - Justin Weiss より抜粋

ちなみに

Railsのmasterにはこの手のメソッドが組み込まれているそうです。

Model.where(id: ids).order(['field(id, ?)', ids])

参考

width: calc()が効かないと思ったら書き方に問題があった

css

CSS3のcalc()ファンクションが最新のブラウザで効かない。。困った。。。

.hogeBlock
  width: calc(100%-320px) // ←こいつ

原因は計算式の書き方でした。計算式の間にちゃんとスペースを入れないと機能しません。

In addition, whitespace is required on both sides of the + and - operators. (The * and / operaters can be used without whitespace around them.)

CSS Values and Units Module Level 3

正しくはこちら。

.hogeBlock
  width: calc(100% - 320px)

思わぬトラップ(ノ∀`)アチャー

参考

「スマートフォンのためのUIデザイン」を読みました

読書 UI デザイン
スマートフォンのためのUIデザイン  ユーザー体験に大切なルールとパターン

はじめに

デザイナーに自分が伝えたいデザインや雰囲気をうまく伝えられないことがたびたびあり、「コミュニケーションをもっと円滑に!」ということでこの本を読んでみることにしました。

本全体としては、スマホアプリ/スマホサイトの両方が記述されていますが、どちらかというとアプリのほうが特殊なUIが多いので内容も多めです。UIごとに丁寧にまとめられていて、それぞれのパターンと具体的な事例を説明してくれているのでとても読みやすかったです。(読了時間:3〜4時間)

気になったところ、勉強になったところを備忘録として書いておきます。

そもそもなぜパターン化するのか

ユーザ体験の標準化

同じことを表すUIや似たUIが存在するとユーザの混乱を招き、体験を損なう可能性があります。ユーザがいろんなページを見ても、混乱しないようにパターン化することが重要です。もちろんパターン化したコンポーネントをベストなデザインにしていく改良は必要です。

開発スピードの向上

パターン化していると新しい画面を作る際に新たにデザインの確認や調整をする時間を短縮することができ、開発スピードを向上することができます。また、フレームワーク化することで、あるコンポーネントの変更をすべてのページに適用できるので全体管理が捗ります。これは少人数で開発してる場合は特に有効です。

勉強になったパターンいくつか

画像を載せるのが大変なので基本的にクックパッドのスクショで。。笑
2年前発行のものなのでちょっと古いデザインのものもあるかも。

スプリングボード

ダッシュボードやマイページTOPなど、情報をコンパクトに伝えたい場合に使える

バック/ホーム

・いわゆる「戻るボタン」がよく使われている
・パンくずはスマホの小さい画面では利用が難しく、iPhoneガイドラインでも非推奨 ・「ホームアイコン」も併用してナビゲーションを手厚くすることでユーザの回遊が見込める(らしい)

インフィニットリストページャー

スタンダードパターン(下にいくと自動で次のコンテンツをくるくる回ってロードする)とボタンパターン(「もっと読む」のボタンを押すと次のコンテンツを読み込む)の2種類がある。ボタンパターンはリストの下にもコンテンツを表示したい場合などに使えると書いているが、いまは「もっと読む」で別ページに遷移するのが主流っぽい。

f:id:paranishian:20160111233403p:plain

垂直型リスト

リストのパターンはクックパッドがすごく参考になりました。
ハーフサイズ、1/3サイズのパターンも考慮したほうが良い。

f:id:paranishian:20160111233055p:plain

タイムライン

新しい情報が上にあるのか下にあるのかで、フォームの位置も異なる。

新しい情報が上にあればフォームも上。
f:id:paranishian:20160111233943p:plain

新しい情報が上にあれば下にあればフォームも下。
f:id:paranishian:20160111234414p:plain

モーダルメッセージ

iPhoneガイドラインでは、「リスクの伴う可能性があるアクションの場合は、キャンセルが右」「ユーザーが望むであろう害のないアクションの場合は、キャンセルが左」と説明されているので、どのような行為なのかによって配置を考える。
Androidの場合はどのような場面でもキャンセルボタンが左で実行ボタンが右が一般的とのこと。

おわりに

スマホのUIにはどのようなパターンがあり、どういった情報を扱うときに利用できるのか、全体的によくまとめられていて勉強になりました。3〜4時間で読めるのでオススメです。
もちろんこの本に載っているパターンがすべてではないので、最新動向は現状のいろんなスマホサイトを見てチェックしていきます(^^)

group_by のときに使う &:hogehoge ってなに?

ruby rails
User.all.group_by(&:age)

レコードをグルーピングするときgroup_byを使いますが、引数の&(アンパサンド、アンド)の挙動をいまいち理解してなかったので調べてみました。

Rubyではメソッド呼び出しの引数に「&手続きオブジェクト」を渡すと、手続きオブジェクトをブロックとして渡すことができます。Ruby 1.8.7以降では「&シンボル」を渡すと、「シンボル.to_proc」をブロックとして渡すことができます。

to_proc (Symbol) - Rubyリファレンス

つまり、以下のように書けてとっても便利です。

# User.all.group_by{|s| s.age } と同じ
User.all.group_by(&:age)

# ["12", "34", "56"].collect {|s| s.to_i } と同じ
["12", "34", "56"].collect(&:to_i)

# [1, 2, 3, nil, 5, 6].select {|s| s.present? } と同じ
[1, 2, 3, nil, 5, 6].select(&:present?)

これはなかなか使えますね〜。(^^)

参考

autoprefixerを使って快適にコーディングする

gem rails css

SASSを導入してからコーディングがかなり楽ちんになったのですが、CSSでベンダープレフィックスをつけるとき、「このプロパティってプレフィックスいるんだっけ?」「Safariでこれってまだ必要だっけ?」みたいな問題によく遭遇します。

-webkit-border-radius: 4px;
-moz-border-radius: 4px;
-ms-border-radius: 4px;
-o-border-radius: 4px;
border-radius: 4px;

いちいちCan I Useで対象ブラウザを確認するのも手間がかかるし、どうにかならんものかと悩んでいたら、知り合いのコーダーにautoprefixerを教えてもらいました。

autoprefixerってなに?

Can I Useの情報を元に、自動でベンダープレフィックスを付与してくれるツールです。 対象のブラウザの条件を細かく設定できたり、不要なベンダープレフィックスがあれば削除も行ってくれます。

さっそく入れてみる

Gemfileに追記してbundle installでおわり。

# Gemfile
gem "autoprefixer-rails"

適用するためにキャッシュをクリアします。

$ rake tmp:clear

これでもううまいことやってくれます。 例えば、

# これが
:fullscreen a
  display: flex
# こうなります
:-webkit-full-screen a {
    display: -webkit-box;
    display: -webkit-flex;
    display: flex
}
:-moz-full-screen a {
    display: flex
}
:-ms-fullscreen a {
    display: -ms-flexbox;
    display: flex
}
:fullscreen a {
    display: -webkit-box;
    display: -webkit-flex;
    display: -ms-flexbox;
    display: flex
}

便利すぎますね。。

ブラウザバージョンを指定する

browsersで対応ブラウザの条件を指定することもできます。

細かい書き方はai/browserslistを見てください。

# config/autoprefixer.yml
browsers:
    - "> 1%" # 使用率が1%以上
    - "last 2 versions" # 直近2つ前のバージョン
    - "IE >= 8" # IEは8以上

以上です。 これでコーディングが捗りますね〜\(^o^)/

参考

railsを起動しようとしたらlibmysqlclient.18.dylib (LoadError)っていわれた

gem ruby rails mysql

railsサーバを起動しようとしたらmysqlまわりのエラーが出て起動できず。。

$ rails s
/Library/Ruby/Gems/2.0.0/gems/mysql2-0.3.16/lib/mysql2.rb:8:in `require': dlopen(/Library/Ruby/Gems/2.0.0/gems/mysql2-0.3.16/lib/mysql2/mysql2.bundle, 9): Library not loaded: /usr/local/lib/libmysqlclient.18.dylib (LoadError)
  Referenced from: /Library/Ruby/Gems/2.0.0/gems/mysql2-0.3.16/lib/mysql2/mysql2.bundle
  Reason: image not found - /Library/Ruby/Gems/2.0.0/gems/mysql2-0.3.16/lib/mysql2/mysql2.bundle
    from /Library/Ruby/Gems/2.0.0/gems/mysql2-0.3.16/lib/mysql2.rb:8:in `<top (required)>'
    from /Library/Ruby/Gems/2.0.0/gems/bundler-1.9.4/lib/bundler/runtime.rb:76:in `require'
    from /Library/Ruby/Gems/2.0.0/gems/bundler-1.9.4/lib/bundler/runtime.rb:76:in `block (2 levels) in require'
    from /Library/Ruby/Gems/2.0.0/gems/bundler-1.9.4/lib/bundler/runtime.rb:72:in `each'
...

なんかrubyのgemで変なとこ向いてて読み込みミスってるので、シンボリックリンクを張って対応します。

まず、インストールしているmysqllibmysqlclient.18.dylibを探します。

私のMacmysqlはHomebrewでインストールしたので、詳細を確認します。

$ brew info mysql
mysql: stable 5.7.9 (bottled)
Open source relational database management system
https://dev.mysql.com/doc/refman/5.7/en/
Conflicts with: mariadb, mysql-cluster, mysql-connector-c, percona-server
/usr/local/Cellar/mysql/5.6.23 (9686 files, 339M)
  Poured from bottle
...

ディレクトリがわかったので、エラーになってる箇所にシンボリックリンクを張ります。

$ sudo ln -s /usr/local/Cellar/mysql/5.6.23/lib/libmysqlclient.18.dylib /usr/lib/libmysqlclient.18.dylib

以上でOKです。

参考

大量のデータ挿入・更新にバルクインサート・バックアップデートを利用する

ruby rails gem

例えば以下の場合にSQLが1000回発行されます。

1000.times do
    Hoge.create(...)
end
# INSERT INTO hoge (...)
# INSERT INTO hoge (...)
# INSERT INTO hoge (...)
# ...

かなり非効率なのでbulk insertでSQLを1つにまとめて軽量化します。

Railsでbulk insertを実行するためにactiverecord-importというgemを利用します。

# Gemfile
gem 'activerecord-import'

すると、先ほどの処理は以下のように書けます。

hoge_list = []
1000.times do
    hoge_list << Hoge.new(...)
end
Hoge.import hoge_list
# INSERT INTO hoge (...), (...), (...), ...

すでにレコードが存在する場合は更新したい

レコードの存在をチェックして、存在する場合は更新、存在しない場合は挿入、ってことをRails側でやろうとするとSQLが大量に発行されて重い処理になってしまいます。

# こんな感じ
hoge_ids.each do |hoge_id|
    hoge = Hoge.find_by(id: hoge_id)
    if hoge.present?
        hoge.update(...)
    else
        hoge = Hoge.create(...)
    end
end

この場合は、on_duplicate_key_updateオプションを指定することで、MYSQL側で重複を検知して更新するようにします。

hoge_list = []
1000.times do
    hoge_list << Hoge.new(...)
end
# すでにレコードが存在する場合はname, contentカラムを更新するようにする
Hoge.import hoge_list, on_duplicate_key_update: [:name, :content]
# INSERT INTO hoge (...), (...), (...), ...

バルクインサート、バックアップデートでカンタン軽量化!お試しあれ\(^o^)/

参考

引数を使えるrakeタスクを作成して実行するとzshに怒られた

ruby rails zsh

環境

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

タスクを作成

my_taskを作成してみます。

rails g task my_task
# lib/task/my_task.rake
namespace :my_task do
    desc "おれのタスク"
    task :normal => :environment do
        puts "引数はないです"
    end

    task :with_arg, [:hoge, :fuga] => :environment do |task, args|
        puts "一つ目は#{args[:hoge]}で二つ目は#{args[:fuga]}です"
    end
end

引数指定して実行

さっそく実行してみると、引数ありの場合zshに怒られて実行できません。。

$ rake my_task:normal
引数はないです
$ rake my_task:with_arg[one,two]
zsh: no matches found: my_task:with_arg[one,two]

zshでは括弧[]はエスケープするか、クォーテーションで囲むかの2通りの方法があります。

$ rake 'my_task:with_arg[one,two]'
一つ目はoneで二つ目はtwoです
$ rake my_task:with_arg\[one,two\]
一つ目はoneで二つ目はtwoです

個人的にはクォーテーションのほうが好き。

参考

nginxで迷惑ボットを拒否する

nginx

newrelicのエラーレートが定期的にやたらと高くなるなーとおもってたらMG12botという迷惑ボットが周回してきてました。

404エラー頻発
→newrelicのアラートが止まらない
→不安で寝れない

のでnginxで拒否します。ネムイ(´・ωゞ)

# /etc/nginx/nginx.conf
if ($http_user_agent ~* (MJ12bot) ) {
    return 403;
}

他にもいろいろ迷惑ボットがいるみたいなので気になる方は以下リンクからどうぞ。

参考

ckeditorに認証機能を実装する

ckeditor rails ruby gem

ckeditorはデフォルトだと誰でもアクセス可能となっているので認証機能を追加します。

cancanを利用する場合は

Ckeditor.setup do |config|
    config.authorize_with :cancan
end

でOKですが、独自の認証機能を使いたい場合は以下のようにします。

# config/inititializers/ckeditor.rb
Ckeditor.setup do |config|
    config.authorize_with do
        redirect_to "/" unless is_admin? # ここに認証ロジックを書く
    end
end

あらカンタン。\(^o^)/

参考