インターフェースデザインの心理学を読んだ

デザインによって、人は使いやすいかどうかを判断するので、役に立つかなと思い読んでみました。

正直内容が心理学的の話をしており、インタフェースの話(ホームページのデザイン)じゃないの?って思ったのが正直な感想です。

微妙に騙されたな・・・って思ったのですが、印象に残っている話があります。

データより物語の方が説得力があると言う話です。

インズタグラムもストーリーっていう機能をリリースしたし、妙に印象に残ったんですよね。

データの提示だけでは感情に訴えづらい

子持ち世帯が中古マンションを購入しようとします。

条件は人それぞれですので、簡単なデータだけにします。

  • 中古マンションを購入した人の80%は満足
  • 満足した理由の一位は子供が喜んでいる

これだけのデータでも購入する人はすると思います。

つっこみどころは置いといてくださいw

では、自分の信頼する人が中古マンションを買った時のよかった感想を伝えてきたらどうなりますか?

あの人が言うのなら・・・っていう感情は出てくるのではないでしょうか。

データより物語に説得力がある理由

本に書いてある理由です。

  • 物語は感情的に訴える力が強い
  • 物語は聞き手の共感を呼び、それにより情緒的な反応をする
  • 感情は記憶中枢にも働きかける

要するに、印象に残るってことですね。

最近は動画でものを紹介することが増えていますね

  • youtuberの宣伝動画
  • メルカリチャンネル

youtuberの宣伝動画なんて、企業からお金もらっていることが多いから、宣伝の方が多いでしょう。

そんなことさておいて、youtuberの人が楽しく、便利なことを紹介してきたら、親近感があるので購入すると思います。

そもそも、宣伝動画なんていうフィルターをかけるのは、業界の人だけでしょう。

感想

僕はブログでもストーリー性がある話は好きです。

それは、感情に訴えてくる部分があるからでしょう。

ストーリー性をうまく持たせる機能を作ることができたら、ソーシャルで広まりそうなサービスが生まれる気はしています。

ただ、難しいのは、それをユーザー側にやってもらうことです。

動画は自然に登場人物が出てくくるので、ユーザー側でもできやすいのかなって思ったりしています。

ストーリーは何かにうまく組み込みたいなって感じています。

以上。

インタフェースデザインの心理学 ―ウェブやアプリに新たな視点をもたらす100の指針

インタフェースデザインの心理学 ―ウェブやアプリに新たな視点をもたらす100の指針

vueを使っている時のディレクトリ構成

みなさん、ディレクトリ構成は気になったりしませんか?

自分は割とディレクトリ構成を気にします。

どこにどういうコードが書いてあるのかすぐに探すことができるからです。

自分は普段railsでコードを書いているので、railsのレールに則って書くとディレクトリ構成で悩むことは少ないです。

そして、vueを使っている場合は、レールがないので、自分で考えなければいけません。

今回は初期構成から現在に至る構成を伝えます。

これがベストプラクティスかどうかは自分もわかりません。

現在の構成はgitlabhqを見ていて、納得することが多かったので、それに従っています。

余談ですが、大規模なプロダクションレベルのコードを見るのはオススメです!

初期構成

javascripts
├── application.js
├── components
│   ├── comment
│   │   │── form.vue
│   │   └── icon.vue
│   └── common
│       └── form
│           └── modal.vue
├── comment.js
├── mixins
│   └── icon.js
└── store
 └── index.js

こんな感じです。

内容

  • 起点となるディレクトリ(javascripts)にファイルを置いていく
  • compoments単位・store単位に区切る
  • 共通のcomponentsはcommonを利用する

vuexなどのサンプルにあるやり方です。

一番最後だけは付け足しました。

小規模のサイトならいいのでしょうが、大規模のサイトになってくるとちょっとcomponents内が膨らんでいきます。

また、起点となるファイルもディレクトリを作成することになるでしょう。

現在の構成

javascripts
├── application.js
├── icon
│   ├── components
│   │   ├── form.vue
│   │   └── icon.vue
│   ├── icon_bundle.js
│   └── store
│       └── index.js
├── initializers
│   └── axios.js
└── vue_shared
    └── components
        └── form
            └── modal.vue

内容

  • 起点となるのはディレクトリ内の**_bundle.jsで管理します。
  • 起点となるディクレトリ単位でcomponentsstoreを作成
  • 共通処理はvue_sharedに移す
  • 初期設定はinitializersで設定する

一番最後のはWeb アプリの JavaScript の初期化処理をどうまとめるかを見て取り入れました。

普段railsと一緒に使用しているので、ディレクトリ単位の管理だと、位置を合わせることができるので、便利です。

componentsstoreもどこで一緒に利用されているのかがわかります。

このディレクトリ構成が何かしっくりきました。

もっとこうしたほうがいい!などの意見があれば教えてください。

以上です。

参考

gitlabhq

Vue(GitLab Documentation )

Web アプリの JavaScript の初期化処理をどうまとめるか

Deviseのログイン後のページ先をユーザーフレンドリーにする

せっかくログインしたのに、トップページに戻ると、なんだこれ?ってなってしまいます。

それを回避する方法です。

class ApplicationController < ActionController::Base
  before_action :store_user_location!, if: :storable_location?

  private

  def storable_location?
    request.get? && is_navigational_format? && !devise_controller? && !request.xhr?
  end

  def store_user_location!
    # :user is the scope we are authenticating
    store_location_for(:user, request.fullpath)
  end

storable_actionでGET/Devise/Ajaxじゃないなどの判定をします。

その場合のみ、sessionでurlを保存しています。

class Users::SessionsController < Devise::SessionsController
  include Cookie

  protected

  def after_sign_in_path_for(resource_or_scope)
    stored_location_for(resource_or_scope) || super
  end
end

あとはsession後にそれがあるかどうかを判定します。

参考

https://github.com/plataformatec/devise/wiki/How-To:-Redirect-back-to-current-page-after-sign-in,-sign-out,-sign-up,-update

wikiが充実していて、本当に助かる。

ここから何かをカスタムする場合は、storable_location?をいじるか、after_sign_in_path_forで条件分岐でいじるかになると思います。

railsで今月・先月の期間を取得する方法

all_monthを使いましょう。

今月の場合

[1] pry(main)> Time.current.all_month
=> Fri, 01 Dec 2017 00:00:00 JST +09:00..Sun, 31 Dec 2017 23:59:59 JST +09:00

先月の場合

[2] pry(main)> Time.current.last_month.all_month
=> Wed, 01 Nov 2017 00:00:00 JST +09:00..Thu, 30 Nov 2017 23:59:59 JST +09:00

これは便利すぎる

参考

https://qiita.com/whitefox_105/items/7c1d409ebd863fab5cb5

railsの本番サイトのデータベースを別サーバーで行うための方法

今回やりたいことです。

  • app/dbサーバーを分けて運用する

そんなの当たり前だろ!って感じですが、デプロイするときに詰まったので、忘れないために書いておきます。

なお、今回のお話はmysqlでの運用にしておりますが、他のデータベースでも同じ感じでいけると思います。

mysqlでリモートの接続を許可する

こんな設定あるとは知りませんでした。

言われてみれば、どこからでもデフォルトでアクセスできるのは、危険としか言いようがありません。

/etc/mysql/mysql.conf.d/mysqld.cnfファイルから、mysqlの設定を変更します。

# http://dev.mysql.com/doc/mysql/en/server-system-variables.html

[mysqld]
pid-file        = /var/run/mysqld/mysqld.pid
socket          = /var/run/mysqld/mysqld.sock
datadir         = /var/lib/mysql
log-error       = /var/log/mysql/error.log
# By default we only accept connections from localhost
# bind-address  = 127.0.0.1
bind-address    = xxx ←ここをmysqlがあるサーバーのIPアドレスにする
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

これでmysql -h IPアドレス -u hoge -pでアクセスできるようになります。

補足

ただし、この場合は全世界に解放されている状態なので、最低でもDBサーバーにアクセスする場所からしか入れないようにしましょう。

AWSの場合はセキュリティグループがあるので、port:3306へのアクセスはIPアドレスで制限しましょう。

ファイアーウォールでもそういう設定はできるでしょう。

database.ymlの設定

production:
  <<: *default
  host: xxx ←IPアドレスを環境変数で設定しておきましょう
  ...

mysql userの作成

deployするとmysqlのユーザーはhoge@localhostではなく、hoge@IPアドレスでアクセスします。

なので、それに沿ってユーザーを作成しましょう。

  • ユーザー作成
  • 権限設定
create user {user名}@IPアドレス identified by {パスワード} -- ユーザー作成

grant all on foo_db.* to {user名}@IPアドレス identified by {パスワード};

この辺を注意して作成します。

以上です。

やってる内容は普通なのですが、意外に詰まってしまった。

chromeで簡易的にパフォーマンスチェックする方法

「推測するな、計測せよ!」ってよく言われるけど、どうやって計測すればいいんだよ!ってパターンはあります。

自分はサーバーサイドをメインでやることが多いのですが、最近話題のフロント側のパフォーマンスについては、どうやって計測するのかわかっておりません。

サーバーサイドだと、New relicで計測することが多いでしょう。

では、サーバーからレスポンス後の速度はどのようにチェックすれば良いのでしょうか?

その時のために、google先生がパフォーマンス計測する方法を用意してくれていました!

Developer toolsのAuditsを利用する

まずはchromeを用意しましょう。

 chromeでページを表示した状態ででF12キーWindowsの場合。Macの場合はCommand+Option+Iキー)を押しましょう。

chromeのdeveloper toolが開きます。

タブのAuditsを選択しましょう。

https://gyazo.com/cd1f9b5ece05b1e45fbe3369af8bef49

上記のような画面になります。

ここから単純にボタンを押して、実行をします。

https://gyazo.com/db9b9c8547754ad6d35dfdd12b584266

パフォーマンス測定をしてくれます。

これはひどいですね・・・

内容は今風のことが多いです。

  • Service Workerを利用しろ!
  • 画像は遅延読み込みしてくれ
  • ファイルは圧縮して配信してくれ(オフラインなので、勘弁してくれ・・・)

とりあえず、google先生のいうところから、少しづつ改善していきましょう。

追記

画像はwebp使えばこれだけ軽くなるぞって教えてくれます。

https://gyazo.com/b25fc9885e49edb8d0815842d41db351

google先生優しすぎだろ・・・

最小限の機能で作成していく方法

よく、最小限の機能でリリースしろ!なんて巷で言われますよね。

けど、気がついたらガッツリ思い込みでガチガチに仕様を固めています。

自分はよくそうなるので、どうすればそうならないかを考えました。

というか、最小限の機能で作成する能力って技術の一つじゃないの?とすら、思っています。

だって、難しいから。。。

よく考えてみると後から追加できる形で作成するすればいいなとふと、気づきました。

よくある悩み

例えば、読書管理のサイトを作成しようとします。

読書管理するために、読書している状態を取得したいです。

  • 読んだ
  • 読みたい

という2つの状態で作成する方法。

  • 読んだ
  • 読書中
  • 読みたい

という3つの状態で作成する方法があるとします。

作成者本人は読書中は微妙じゃない?と思いつつ、後者を選ぼうとしています。

なぜなら、選択肢が多い方が細かいデータを取得できて、ユーザーにとって便利だ!という悪魔の声があります。

3つのステータスがある場合

https://gyazo.com/df9091b596b342db362c1d96cca1ad33

雑にやりましたが、セレクト形式になると思います。

この欠点は、二回クリックしないといけません。

「セレクトボックス」選択→「ステータス」選択

この後に待っているのは、状態をもっと細かく取った方がいいんじゃないの?という話になるでしょう。

2つのステータスの場合

では、最初から、「読んだ」「読みたい」の二つしかない状態にします。

https://gyazo.com/195025a7c6eb128980a9dc53e9d4d31d

デザインが雑なんで、あれですが、こちらの方は1クリックでできます。

「読んだ」を押すとチェックマークが入るアニメーションをつければ終わります。

ユーザーとしても、迷いが減ります。

そして、何よりも後から「読書中」は必要なら追加することができます!

一方、最初から3つのステータスで作成していた場合は、なかなか消すことができません。

なぜなら、ユーザーさんが使用している可能性があり、簡単に消すことはサイトの信頼性が失われます。

後から追加できる形で作成する

おそらく、自分の中では最小限の実装で作成するんだ・・・!と思いつつも実際はガッツリと組み込んで作成してしまいます。

自分がそうなので・・・

そして、ガッツリ組み込んで作業していく場合は、作業に没頭できるので、やった気になります。

後から追加できる形で作成すれば、反応を見ながら作成していくことができます。

なので、自分は後から追加できるから、今は作らなくてもいいやと思って、割り切って最小限で作成していきたいと思います。

railsのroutesのid以外にする方法

railsはdefaultのresourcesは:idになっています。

config/routes.rb

  resources :users

URIのパターンです。

                          Prefix Verb     URI Pattern                                            Controller#Action
                        new_user GET      /users/new(.:format)                                   users#new
                       edit_user GET      /users/:id/edit(.:format)                              users#edit
                            user GET      /users/:id(.:format)                                   users#show
                                 PATCH    /users/:id(.:format)                                   users#update
                                 PUT      /users/:id(.:format)                                   users#update
                                 DELETE   /users/:id(.:format)                                   users#destroy

ただ、id以外にしたい場合があります。

routesにparamをつけてid以外に変更する

その場合はparamsを付け足せばいいです。

config/routes.rb

  resources :users, param: :name
                           users GET      /users(.:format)                                       users#index
                                 POST     /users(.:format)                                       users#create
                        new_user GET      /users/new(.:format)                                   users#new
                       edit_user GET      /users/:name/edit(.:format)                            users#edit
                            user GET      /users/:name(.:format)                                 users#show
                                 PATCH    /users/:name(.:format)                                 users#update
                                 PUT      /users/:name(.:format)                                 users#update
                                 DELETE   /users/:name(.:format)                                 users#destroy

https://github.com/rails/rails/blob/b5db73076914e7103466bd76bec785cbcfe88875/actionpack/lib/action_dispatch/routing/mapper.rb#L469

linkを貼る場合に楽をする

そして、この時のlinkを貼る場合にdefaultでnameにしたい場合があります。

@user = User.first
user_path(@user)

通常だと、この場合はidになるので、/users/1になります。

これをnameに変更します。

model/User.rb

def to_param
  name
end

これで/users/fooになります。

注意点

ただし、これをやる場合はnameがuniqにしておけないとバグの温床になります。 例えば、nameにfooが二人いた場合です。 だいたいcontrollerに書くのは下記のようなコードだと思います。

def show
  @user = User.find_by(params[:name])
  ...
end

find_byは最初の一つしか取得しないので、二つ以上あると予期せぬ挙動になります。

    def find_by(arg, *args)
      where(arg, *args).take
    rescue ::RangeError
      nil
    end

https://github.com/rails/rails/blob/c6f12715f1887c06f778a32330f59822ca77df20/activerecord/lib/active_record/relation/finder_methods.rb#L77

そのため、DB/モデルにuniq制約をつけるのを忘れないようにしましょう。

以上です。

railsでssl設定をした場合にしておいた方がいい設定

ssl対応をやってみました。

はてなブログssl対応を行っているし、今となっては当たり前になりましたね。

なぜ、そんなことが起こっているかというと、chromeで安全なサイトではないという警告が出るからですね。

そんなsslですが、let's encryptで行うことが増えているのかなとは思っています。

それについて、後日まとめることができたらまとめます。

今回はsslをした後のrailsの設定を見ていきたいと思います。

cookieにsecure属性をつける

cookieにsecure属性をつけることで、cookieも暗号化されます。 平文だと、万が一盗まれるようなことがあるとまずいので、secure属性をつけるのがいいでしょう。

config/initializers/session_store.rb

Rails.application.config.session_store(secure: Rails.env.production?)

nginxのリバースプロキシにssl通信のheaderをつけてあげる

railsforce_sslを利用しても、httpsと認識してくれません。 その原因は、httpsと認証するheaderがないからです。

https://github.com/rack/rack/blob/rack-1.5/lib/rack/request.rb#L70

    def scheme
      if @env['HTTPS'] == 'on'
        'https'
      elsif @env['HTTP_X_FORWARDED_SSL'] == 'on'
        'https'
      elsif @env['HTTP_X_FORWARDED_SCHEME']
        @env['HTTP_X_FORWARDED_SCHEME']
      elsif @env['HTTP_X_FORWARDED_PROTO']
        @env['HTTP_X_FORWARDED_PROTO'].split(',')[0]
      else
        @env["rack.url_scheme"]
      end
    end

    def ssl?
      scheme == 'https'
    end

こうなっております。

schemeのどれでもいいので、headerとしてリバースプロキシから渡してあげましょう。

    location @app {
      ...
      proxy_set_header X-Forwarded-Proto $scheme;←追加
      ...
    }

ここまで来たらリダイレクト処理をnginx側で行います。 わざわざアプリケーション側でリダイレクト処理をするのは無駄な作業です。

  server {
    listen 80;
    rewrite ^ https://$server_name$request_uri? permanent;
  }

個人的には、rails側の設定もforce_sslで合わせました。 これで誰が見ても、設定がわかると思うので。

config/environments/production.rb

  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  config.force_ssl = true

設定にもcookieにsecure属性をつけた方がいいよって書いてますね。

以上です。

参考

https://qiita.com/masarakki/items/e498d257a2105d055281

railsのARに対するpresent?とexists?のパフォーマンスの差

exists?の方がいいですという指摘を受けた。

なので、ここで確認する。

[4] pry(main)> Work.where(id: [*1..100]).exists?
  Work Exists (0.5ms)  SELECT  1 AS one FROM `works` WHERE `works`.`id` IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100) LIMIT 1
=> true
[5] pry(main)> Work.where(id: [*1..100]).present?
  Work Load (0.8ms)  SELECT `works`.* FROM `works` WHERE `works`.`id` IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)
=> true

exists?の方はLIMIT 1がついてある。

ということは、ARの結果が複数ある場合はexists?の方が高速ですね。

whereの後で存在を確認する場合はexists?を利用すべきだな。

これからは気をつけよう。

後日談

この後の展開が続くなら、present?の方がいいです

present?

- if @work.episodes.present?
  - @work.episodes.each.with_index(1) do |episode, i|
  Episode Load (0.4ms)  SELECT `episodes`.* FROM `episodes` WHERE `episodes`.`work_id` = 1

こういう場合ですね。 cacheした値がそのまま使用されます。

exists?の場合

- if @work.episodes.exists?
  - @work.episodes.each.with_index(1) do |episode, i|
  Episode Exists (0.3ms)  SELECT  1 AS one FROM `episodes` WHERE `episodes`.`work_id` = 1 LIMIT 1
  Episode Load (0.4ms)  SELECT `episodes`.* FROM `episodes` WHERE `episodes`.`work_id` = 1

ということは、ARの存在を確認するだけで、そこから先は展開しない場合ですね。

考えられる場所は、モデル層とかなのかな。

Viewで確認してよかった。

ということで、使用する場所はきちんと考えないとダメですね。