ラベル ** PC - 疑問点あり の投稿を表示しています。 すべての投稿を表示
ラベル ** PC - 疑問点あり の投稿を表示しています。 すべての投稿を表示

2014年7月10日木曜日

git pushした後にcommit --amendしてpushしてみた

 コミット時のコメントを間違ってしまったのにpushしてしまって、amendしてもう一回pushしたらrejectされた。まぁ、当然ですよね。
~/work/awesome_events$git push
To https://github.com/xxx/awesome_events.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'https://github.com/xxx/awesome_events.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
~/work/awesome_events$gi pull
-bash: gi: command not found
~/work/awesome_events$git pull
Merge made by the 'recursive' strategy.
~/work/awesome_events$
別に何か対処した訳でもなく、あんたのブランチが古いからgit pullしてみろ、とヒントが書かれているので言う通り実行すると、 再帰的にマージしといたよというメッセージ。
 その後pushできましたが、誤ったコミット、修正後のコミット、そのときに自動で発生したマージ処理についてMerge branch 'master' of https://github.com/xxx/awesome_eventsというコメントとともにコミットが追加されていました。

 あぁ、こうなっちゃうのね。意図したこととは違ったけど、データが壊れていないのでよしとする。でも誤ったコメントが入ったままなのは今後なんとかしないと。

2014年7月5日土曜日

6-4 イベントの登録機能を作る

 わからないながら、ずっとメモし続けていたら話が少し見えるようになってきた気がする。メモを簡略化して、メモにかける時間を少し短くしようと思います。

 ここでやりたいこと。

  • ヘッダーにイベント作成リンクを設置。
  • ログイン状態で押すとイベント登録フォームへ移る。
  • 未ログインならトップページへリダイレクト。(これなんか親切じゃないね。)
  • イベント登録で誤った入力があるとエラー表示。
  • 正しければ登録処理。

タイムゾーンを設定する

デフォルトではUTC(協定世界時)に設定されているため、config/application.rbでコメントアウトされているタイムゾーンの設定をTokyoに変更します。こうしないと、イベントの登録時間等が登録者の意図しない時間になってしまう。
    config.time_zone = 'Tokyo'

イベント用のモデルを作成する

rails g resource event owner_id:integer name:string place:string start_time:datetime end_time:datetime content:text
  む。rails g resourceは初めて見ました。モデルとコントローラとルーティングを同時に作成するのか。ビューのhtmlファイルとかは作らないみたい。

参考:
[Ruby][Rails] Railsのgenerate scaffoldとgenerate resource

 マイグレーションファイルを修正してNOT NULL制約とインデックスを追加。このインデックスってのはなんなの?って今頃聞いちゃいけないこと?
 つーかマイグレーションつーのが未だにボンヤリしてるな。もっかい説明読めばイメージつかめるかな。

 今回は文字数制限等をvalidationで決めるって。NOT NULL制約はここに書いちゃ行けないの?ここにもpresence: trueとか書いてるじゃん。両方あれば間違いないとかそんなことかい?ググるとマイグレーションファイルは触っていない人も多い様子。
 validationはapp/models以下、event.rbで設定。
class Event < ActiveRecord::Base
validates :name, length: { maximum: 50 }, presence: true
validates :place, length: { maximum: 100 }, presence: true
validates :content, length: { maximum: 2000 }, presence: true
validates :start_time, presence: true
validates :end_time, presence: true
validate :start_time_should_be_before_end_time
private
def start_time_should_be_before_end_time
return unless start_time && end_time
if start_time >= end_time
errors.add(:start_time, 'は終了時間よりも前に設定してください')
end
end
end
 あれ?validateって検査の定義とかするんだっけ?いいのかな。開始時間が終了時間の前にちゃんと設定されているかどうかチェックするんだな。

参考:

 合わせてビューにもイベント作成のリンクを追加。リンク先のnew_event_pathはrails g resourceと同時にルーティングが追加されているので何もしなくて良い。確かにあった。

ログイン状態を管理する処理を作る

 複数のコントローラで利用する汎用的な処理は、一般的にApplicationControllerに書くそうです。そうですって、まぁ、そうでしょうね。感覚としてはわかります。でも本見ずに進めていたら、思考停止しちゃうかもしれない。
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  helper_method :current_user, :logged_in?
  private
  def current_user
  return unless session[:user_id]
  @current_user ||= User.find(session[:user_id])
  end
  def logged_in?
  !!session[:user_id]
  end
  def authenticate
  return if logged_in?
  redirect_to root_path, alert: 'ログインしてください'
  end
end
 application_controller.rbをこんな風に書き換えろとまず言われたんだけど、||=ってなに?ググったら、左辺の変数が偽か未定義の場合に右辺の値を代入する、だって。ふーん。初めて変数使うときはいつでもこうするべきなのかね。説明してよ。=だけだとなんか不都合あるんだろうか。

参考:

 authenticateのアラートを表示させるためにレイアウトも修正。コードは前後省略。
 <div class="container">
  <% if flash[:notice] %>
  <div class="alert alert-success">
  <%= flash[:notice] %>
  </div>
  <% end %>
      <% if flash[:alert] %>
        <div class="alert alert-danger">
        <%= flash[:alert] %>
        </div>
      <% end %>
  <%= yield %>
 </div>
 まただ。このflashって変数、なんだよ…。

参考:

 説明読んでもイマイチ。flash無しではダメ?前のデータが残っちゃうからか。そういうことかな。
 続いて、authenticateメソッドをEventsControllerに対して設定する。そろそろわかんなくなってきたぞ。
 authenticateをbefore_actionに設定して、未ログインユーザーがEventsControllerのアクションにアクセスしたとき、トップページにリダイレクトする。と打ち込んで3回くらい読む。
 ログインユーザーだけしかアクセスできないアクションにアクセスがあった場合には、そのアクションを起こす前にユーザーがログインしているかチェックするわけだね。未ログインならトップページに飛ばす。やっとかよ。これの理解に何分もかかっちゃうのかよ。
 今日はただでさえ頭痛が酷いのに、そこへこういったテキストを読むなんて馬鹿げているけど、気が焦ってしまって仕方ない。最近気になり始めた武藤彩未さんのアルバムでも小音量で聴いて癒されようかな。つーかBABYMETALのファンになったら次から次へと関連アイドルの知識が耳に入ってくるので困る。

 events_controller.rbについては以下の通り。
class EventsController < ApplicationController
  before_action :authenticate
  def new
  @event = current_user.created_events.build
  end
  def create
  @event = current_user.created_events.build(event_params)
  if @event.save
  redirect_to @event, notice: '作成しました'
  else
  render :new
  end
  end
  private
  def event_params
  params.require(:event).permit(
  :name, :place, :content, :start_time, :end_time
  )
  end
end
 newとcreateアクションではapplication_controllerに追加したcurrent_userメソッドを使ってログインユーザーを取得している。んー。んー。わかんね。
 あとは、ここで出てきたcreated_eventsをmodel/user.rbで定義します。
class User < ActiveRecord::Base
has_many :created_events, class_name: 'Event', foreign_key: :owner_id
以下略
 またこの項は何度も読み直す必要がありそうだな。日本語が読めていないのか、説明が悪いのか。いや、一応ベストセラーらしいし、原因は自分だよな。多分。

イベント登録用のフォームを作る

 次はイベント登録用フォームを作る。events/new.html.erbを新規作成。はい作った。
<% now = Time.zone.now %>
<div class="page-header">
<h1>イベント作成</h1>
</div>
<%= form_for(@event, class: 'form-horizontal', role: 'form') do |f| %>
<% if @event.errors.any? %>
 <div class="alert alert-danger">
  <ul>
  <% @event.errors.full_messages.each do |msg| %>
   <li><%= msg %></li>
  <% end %>
  </ul>
 </div>
<% end %>
<div class="form-group">
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :place %>
<%= f.text_field :name, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :start_time %>
<div>
<%= f.datetime_select :start_time, start_year: now.year, end_year: now.year + 1 %>
</div>
</div>
<div class="form-group">
<%= f.label :end_time %>
<div>
<%= f.datetime_select :end_time, start_year: now.year, end_year: now.year + 1 %>
</div>
</div>
<div class="form-group">
<%= f.label :content %>
<%= f.text_area :content, class: 'form-control', row: 10 %>
</div>
  <%= f.submit '作成', class: 'btn btn-default', data: { disable_with: '作成中…' } %>
<% end %>
 で、随分いろいろ打ち込みましたが、名前/場所/開始時間/終了時間/内容の入力フォームとイベント作成ボタンを作っています。
 ここで実際にブラウザで動作を確認したら打ち間違いによるエラーが死ぬほど出て死んだ。数時間かけてやっと原因がわかってげんなり。
 入力フォームが出るようにはなったけど、登録するとPlace can't be blankとか言われるな。空欄ではないんだけど…。
 再度ハマって、更に打ち間違いとコピペミスがあって、マイグレーションし直したりしてなんとかなった。酷いなオレ…。

i18nの設定をする

 なんすか、i18nって。ラベルとエラーの修正、ですか。ラベルやvalidationのエラーを日本語にしようよ、ということみたい。
 設定はconfig/spplication.rb。以下の部分をコメントアウトして、最後の右辺を:deから:jaに書き換える。
config.i18n.default_locale = :ja
汎用のエラー辞書データはrails-i18nのリポジトリにまとめられている。下記からダウンロード。
~/work/awesome_events$curl -o config/locales/ja.yml -L https://raw.github.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml
以下はファイルの内容の一部。へーほー。
  errors:
    format: ! '%{attribute}%{message}'
    messages:
      accepted: を受諾してください。
      blank: を入力してください。
      present: は入力しないでください。
      confirmation: と%{attribute}の入力が一致しません。
      empty: を入力してください。
      equal_to: は%{count}にしてください。
      even: は偶数にしてください。
      exclusion: は予約されています。
  更に、同じくconfig/localesに、ar-ja.ymlとして今回のアプリ向けの辞書データを作ります。よく書式がわからないけど、スルーしとこうかな。
ja:
  acriverecord:
    models:
      event: イベント
    attributes:
      event:
        name: 名前
        place: 場所
        start_time: 開始時間
        end_time: 終了時間
        content: 内容
  あ、確かにエラーが日本語で表示される。

6-3 OAuthを利用して "Twitterでログイン" 機能を作る

OAuthとは

OAuthは、Twitter等の大手サービスを利用する認証方法。利用するサービスのパスワードを又貸しするようなことをせずに認証を実現できるので、手軽で現在は広く使われているんですってよ、奥さん。

Twitterアプリケーションの登録

p168に沿ってその通り登録。Application detailsのNameは重複が許されないとのことで、適当にふざけた名前を付けた。

Twitterアカウントでログインする機能の作成

とりあえず以下がやりたい。
ヘッダーにログインリンクを作る
クリックするとTwitter認証画面
Twitter認証画面で適切なユーザー名とパスワードの入力でログイン
ログイン中はログインリンクの代わりにログアウトのリンク
ログアウトをクリックするとログアウトする
認証に実際に使用するgemはOmniAuth。Twitterを認証に利用する場合は更にOmniAuth Twitterというgemが必要。
 今回はテキストに合わせるため、バージョンをpatchレベル以外は固定。ポリシーはアプリケーションにより決める。

 次にTwitter管理画面上のAPI keyとAPI secretをOmniAuthに設定する。Rails4.1からはconfig/secrets.ymlが設定ファイル。
default_twitter: &default_twitter
  twitter_api_key: 'tjEBXXXXXXXXXXXXXdjhBo'
  twitter_api_secret: 'mZKR0XXXXXXXXXXXXXXXXXXXXXX45e4fyO73nt'
development:
  secret_key_base: tjEBZXXXXXXXXXXXX4djhBo
  <<: *default_twitter
test:
  secret_key_base: mZKR0vXXXXXXXXXXXXXXXXXXu9t45e4fyO73nt
  <<: *default_twitter
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
  twitter_api_key: <%= ENV["TWITTER_CONSUMER_KEY"] %>
  twitter_api_secret: <%= ENV["TWITTER_CONSUMER_SECRET"] %>
 次、もう一つ。config/initializers/omniauth.rbを作成して下記を入れておく。
Rails.application.config.middleware.use OmniAuth::Builder do |
provider :twitter,
 Rails.application.secrets.twitter_api_key,
 Rails.application.secrets.twitter_api_secret|
end
  これで設定完成。説明はp173を参照。まだこのキーがいつどのように使われるかよくわかっていない。

 続いてログインユーザーのモデルを作成する。
rails g model user provider:string uid:string nickname:string image_url:string
マイグレーションファイルにNOT NULL制約とユニークインデックス追加しておく。これはなんだ。validationじゃなくて、ここに書くんだ。うーん。ちょっとわからんぞ。なんにしても、未記入は許しまへんで、ということですよね?
      t.string :provider, null: false
      t.string :uid, null: false
      t.string :nickname, null: false
      t.string :image_url, null: false
参考:

 で、マイグレーションする、と。

 続いては、Twiterにログインというリンクを作る。コードはp175を参照して、application.html.erbに書いた。認証先のリンクはOmniAuthの仕様で、/auth/:providerになる。
 この後サイトへアクセスしてログインボタンを押すと、見慣れたTwitterの「連携アプリの認証」ボタンの画面に移った。おー、なんか今っぽいすね。
 認証ボタンを押すと、まだ後処理が用意されていないためエラーになりました。ルーティングエラー。

 さて、ログイン兼ユーザー登録用のコントローラを作成します。
rails g controller sessions
 この辺のコントローラの名前とか、自分にセンスがなさそうなので心配。単数系にするときとかもどうもぼんやり。今回はユーザーを扱うので当然複数形ですよね。こういう単純な考え方がそもそもいけないのか。

 createアクションの作成。
class SessionsController < ApplicationController
def create
user = User.find_or_create_from_auth_hash(request.env['omniauth.auth'])
session[:user_id] = user.user_id
redirect_to root_path, notice: 'ログインしました'
end
end
OmniAuthによりrequest.env['omniauth.auth']にHashに似たOmniAuth::AuthHashが格納される。Twitterから渡されたユーザー情報やOAuthのアクセストークンも含まれる。
 find_or_create_from_auth_hashはまだ未定義。方針としては、引数に関連するユーザーがいればそれを返し、いなければ新規作成して返すという動きをさせる。
 最終的にはUser.find_or_create_from_auth_hashが返すUserのIDをセッションに格納することでログインしたという扱いにする。こういうの難しいわ〜。論理的に説明だけされるとボーッとなりますね。ちょいちょい具体的な話や面白話がないと。本が何倍にもなりそうですが。
 当然次はUser.find_or_create_from_auth_hashの実装。/models/user.rbで。
class User < ActiveRecord::Base
def self.find_or_create_from_auth_hash(auth_hash)
provider = auth_hash[:provider]
uid = auth_hash[:uid]
nickname = auth_hash[:info][:nickname]
image_url = auth_hash[:info][:image]
User.find_or_create_by(provider: provider, uid: uid) do |user|
user.nickname = nickname
user.image_url = image_url
end
end
end
 find_or_create_byはActiveRecordのメソッド。
 内容としては、引数で渡したproviderとuidを持つレコードが存在していればそのオブジェクトを返し、存在しなければprovider、uid、nickname、image_urlを設定してレコードを作成し、そのオブジェクトを返す、と。

 ルーティングの追加。
get '/auth/:provider/callback' => 'session#create'
  これはどういうことだ。/auth/:provider/callbackで帰ってくるってのは自明?あ、さっき出たルーティングエラーにアクセス先が/auth/twitter/callbackとは書かれているな。OAuthとやらの仕様か。それともTwitterの仕様?
 application.html.erbについてもp179の通り追記する。これでcreateアクションからの返り値により、ログインしましたというメッセージが表示されるようになる。できた。

 最後はログアウト処理。まずはdestroyアクションの追加。
def destroy
reset_session
redirect_to root_path, notice: 'ログアウトしました'
end
 次は、ログイン中は画面右上にログアウトのリンクを表示させたい。logged_in?メソッドを定義して、ログイン状態が把握できるようにする。
 application.html.erbではp180の通りlogged_in?の条件分岐でログイン/ログアウトを表示し分ける。ログアウトのリンク先のlogout_pathはあとでroutes.rbで設定する。
 logged_in?の定義はapplication_controller.rbで下記の通り。
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  helper_method :logged?
  private
  def logged_in?
  !!session[:user_id]
  end
end
 hyper_methodってなんだよ。コントローラのメソッドをビューでも使う場合に使うってよくわからない。

参考:

 あと、!!ってなに?基本過ぎ?

参考:

 はぁ、!が論理演算のnotだって。すると、sessionがユーザーidを持っていれば!によってfalse、もう一つの!でtrue、で結果としてこれがログイン状態になるんだ。頭いいねぇ。感心した。
 未ログインでsessionがnullなら!でtrue、もう一回!でfalse。だから未ログイン。

 最後にルーティングを追加。
 get '/logout' => 'sessions#destroy', as: :logout
 プログラミングでごく一般的なことがわかっていないから、どの説明も本筋から外れたところで新たな発見がたくさんある。ありすぎて調べ続けているとなにやっていたかわからなくなったりする。

 この深夜に仕事のLineがきて、確かに自分が悪いところもあるけどものすごく不快な気持ちになってしまった。寝よう寝よう。

2014年7月3日木曜日

4-1 MVC以外の構成要素の扱い

 MVCもまだ理解しきれていませんがね…。
 アプリケーション内で利用するけどMVCに当てはまらないモジュールをどう扱うか。

  • モデルとして扱う
  • libディレクトリで管理する
  • 新しいレイヤーを定義する

libディレクトリ

libディレクトリには元々requireパスが設定されているので、例えばlib/tatoeba.rbというファイルがあれば、下記でインクルードできる。
require 'tatoeba'
また、libにはtasksというディレクトリがRailsプロジェクト作成とともに作られますが、ここはRakeタスクの置き場所です。ここに.rake拡張子のファイルを置けば、rakeコマンドで直接利用できる。

オートローディング

 Railsでは、クラス名、ファイル名が下記の規約に沿っていれば自動でファイルをrequireしてくれる。
  • ファイル名はスネークケースで記述し、クラス名をキャメルケースで記述する
  • クラスやモジュールのネームスペースはディレクトリで表現する
 例えば、Roll::SuperUserというクラスを参照して、それが定義されていなければroll/super_user.rbをrequireしようとし、autoload_pathsに含まれるディレクトリにも無ければエラーが発生する。
 例えてもらっても2番目の説明がよくわからない。

 autoload_pathsで設定されているデフォルトは下記のディレクトリ。
  • app/controllers
  • app/models
  • app/helpers
  • app/mailers
  • app/controllers/concerns
  • app/models/concerns
 オートローディングさせるディレクトリの追加はconfig/application.rbで行う。詳しくはp132。

新しいレイヤーの配置とオートローディング

 app以下にディレクトリを作成し、autoload_pathsを追加すれば、あとは命名規約に沿っていればそのクラスは他のコード内からすぐに使えるようになる。
 app以外でもいいらしいけど、一般的にapp以下に配置するみたい。

 はー、わかんね。

2014年6月25日水曜日

1-4 scaffoldを使ってRailsでの開発を体験しよう

 scaffoldはCRUD(Create/Read/Update/Delete)を簡単に実現させることができる。詳しくは2章だって。
MVCそれぞれのファイルとコードの生成、データベースのテーブル定義ファイルの生成をコマンド一つで終わらせてくれる。
 すげーな。自分の作りたいテキトーなサイトなんて、これベースでいけちゃうんじゃないの。
rails generate scaffold task content:text
はい、作れた。/appの中にMVCそれぞれの最低限必要なファイルが 作られたようです。
 taskのところがコントローラ名。その後はその要素。カラム名:変数タイプ、という書式。

 /db/migrate内には、データベースのテーブルを作成するために必要な情報が書き記された長い名前のファイルが作られています。
 その後、この生成されたファイルを基にデータベースの作成とテーブルへの変更を行う。
rake db:create
rake db:migrate
プライマリーキーidとタイプスタンプt.timestampsは自動で作られる。

 一応、データベースソフトsqlite3で、どのような命令が実行されたか確認してみる。
$ sqlite3 db/development.sqlite3 ".schema tasks"
CREATE TABLE "tasks" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "content" text, "created_at" datetime, "updated_at" datetime);
おっと、なんかもう怪しくなってきた。眠いし。

 migrationの状態を確認することができる。statusがupならmigration済み。downは未適用。
$ rake db:migrate:status
database: /Users/moc720814/work/todo/db/development.sqlite3
 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20140625033406  Create tasks
確認がいくつか入りましたが、これでscaffoldによって簡単なサイトができました。アクセスは、http://localhost:3000/tasksで可能です。

 どのURLがどのページとリンクしているのか、といったルーティング情報については、下記のように特定のURLにアクセスするか、ターミナル上でコマンドを打ちます。
http://localhost:3000/rails/info/routes #このURLにルーティング情報が表示される
$ rake routes
   Prefix Verb   URI Pattern               Controller#Action
    tasks GET    /tasks(.:format)          tasks#index
          POST   /tasks(.:format)          tasks#create
 new_task GET    /tasks/new(.:format)      tasks#new
edit_task GET    /tasks/:id/edit(.:format) tasks#edit
     task GET    /tasks/:id(.:format)      tasks#show
          PATCH  /tasks/:id(.:format)      tasks#update
          PUT    /tasks/:id(.:format)      tasks#update
          DELETE /tasks/:id(.:format)      tasks#destroy
  例えば、黄色いラインについては、HTTPのPOSTメソッドで/tasksへリクエストするとtasksコントローラのcreateメソッドが実行される、という意味。メソッドって言葉にまだ慣れないな…。
 Prefixについては、htmlを生成するerbファイル(Embedded Ruby)で/tasks/editをedit_task_path等と表現できるようになるようだけど、イマイチ効能がよくわからない。
 また、このルーティングの設定はどこでやってんの、という疑問については、/config/routes.rbを見ればいいのですが、見てもよくわからない。
Rails.application.routes.draw do
  resources :tasks
end
コメントアウトされている行を抜かせばこの3行しか無い。要はresourcesメソッドに:tasksを渡せばその辺はCRUDが実現できるようにうまくやっといてあげる、ということらしいです。
 ところでこのコロンは何だい?シンボル?理解し難いね…。

参考:[Rails]Rubyのカベ-シンボルって何だ?

 HTTPメソッドのGET/POST/PUT/DELETEがshow/create/update/destroyアクションに対応していて、RESTfulな考え方をベースに実現されている。逆に言えばRESTfulを意識して開発するとRailsの恩恵を受けやすく、わかりやすい構造になる、と。わかりにくいよ!