22
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

他言語経験者による初めてのrails

Last updated at Posted at 2015-02-28

仕事でrailsを使うことになったので、調べたり書籍読んだりして学んだことをプロジェクト作成、マイグレーション、モデルあたりを中心に備忘メモ(コントローラ辺りで疲れて雑になってる)
ruby自体もほぼ未経験(Vagrantfileとかchefレシピ書く時使った程度)です、今の自分の知識レベルを残す意図も兼ねて残しておく

自分のspec

経験言語(業務で)

  • Java
  • PHP
  • ワタシ、SQL(MySQL)、チョットワカル

経験フレームワーク(業務で)

  • 何となくMVCぽいの。名前は書いて良いのか分からないので伏せる(Java)
  • 何となくMVCぽいの。名前は書いて良いのか分からないので伏せる(PHP)

学習期間

  • 1週間ほど

準備

学習の変遷

簡易ブログアプリ作成

  • DBはMySQLを使う
    • 本番でデフォルトのSQLiteを使ってるイメージが余り無いし、だったら最初から事例多そうなmysqlを使いたかった
  • テストはRSpecで書く(事を想定はしたが、この記事ではRSpecには触れてない)
  • git管理下に置く前提で(完成したらgit commitしてゴール、のイメージ)

プロジェクトの新規作成

  • rails new PROJECT_NAME -T -d mysql --skip-bundleで作成する
    • 雛形作成 と使用するgem(Gemfileに記載されている)のインストール が走る
    • -Tオプション - デフォルトのテストフレームワークTest::Unitに関連するファイル作成をスキップする
      • テストを RSpec で書きたい場合とかは指定すると良い
    • -d mysqlオプション - DBにMySQLを使いたい場合に指定
      • mysql2 というgemを使うっぽい(Gemfileに記載されている)
      • DBの設定ファイルconfig/database.ymlもmysql向けのものになっている(後で修正)
    • --skip-bundleオプション - 通常rails new時には関連するgemのインストールが プロジェクト・ローカルではない場所に インストールされるが、それをスキップする
      • プロジェクト・ローカルで閉じた環境に置くことで管理しやすくなる
      • もう一つ挙げるならctags -R .するだけでライブラリ込みのタグ作ってくれるのも良かった(当方 vimmer)
$ rails new blog -T -d mysql --skip-bundle
           :
           :
      create  ....

rails new時にスキップしたgemのインストール

  • cd PROJECT_ROOTしてbin/bundle --path vendor/bundleする
    • --pathオプションでインストール先を指定。インストール先情報とかは.bundleってファイルに保存されていて、以降gemを追加した際は普通にbin/bundleするだけで同じ場所にインストールしてくれる
    • パスにvendor/bundleを指定するのは、目に付いた事例がそうだったからで、この名前に意味があるのかは分かってない
    • Gem - Bundler概要 - Qiita

git init

  • rails new.gitignoreも吐いてくれるんだけど、git initまでしてくれるわけではないので自分でやる
  • デフォルトの.gitignoreは不足があるので追加
    • /vendor/bundleを追加
    • *.swpを追加(vimmerなので!)
  • なんかいじったらgit addでstagingしておくとして、これだけファイルが色々あるとgit commitをどのタイミングでやるべきか悩みそう

config/database.yml の修正

mysqlサーバの場所やスペック、ymlの項目についての詳細は省略。とりあえずvagrantで立てたmysqlサーバを使った

  • default,development,test,productionという4セクションあって、defaultで汎用的な設定を記述し、個別上書きが必要ならその他3つで記述するっぽい
    • <<: *defaultこれが「defaultの内容を継承する」って意味だろう(調べてない)
  • DB名はDBNAME_envって形式。DB名を環境別に変えるので本番DBを丸っとdevにインポートって事をしてもそのままでは動かない
    • DB名を全環境で揃える事は出来ない(出来てもやらない方が良い)のか
    • そもそもrailsは環境識別を何処でどのようにやってるのか?
config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  password:
  host: 192.168.56.150

development:
  <<: *default
  database: blog_development

DBを作成する

  • rake db:createで作成
    • CREATE DATABASE はこのコマンドで動かす。つまりrake db:createを叩くサーバ(と接続に使うmysqlユーザ)にCREATE権限が必要ということ(巻き戻しも考えたらDROP権限もいる)
      • CREATE/DROP TABLEまでWAFのコマンドでやる事には正直違和感ある。。本番でもこのコマンド使うのだろか
      • rails/mysql使用時に最低限必要っぽい権限 = "CREATE,DROP,INDEX,ALTER,SELECT,INSERT,UPDATE,DELETE"
    • コマンドはPROJECT_ROOT/bin下のものを使う習慣を付けとくべきなのか。rails newした時点のスナップショット、という立ち位置であるのなら。
$ bin/rake db:create
  • 正常終了したらDBNAME_developmentDBNAME_testの2つのDBが作成される
  • わざわざmysqlサーバに入らなくてもrails dbを叩けばmysqlにログイン出来る
    • これも同じく、接続を可能にする権限設定をサーバ側で行っている事が前提だけど
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| blog_development   |
| blog_test          |
| mysql              |
| performance_schema |
| test               |
+--------------------+

アプリケーションサーバの起動と確認

  • rails s でアプリケーションサーバを起動
  • WEBrick という、rails付属のアプリケーションサーバが起動する
    • WEBrickを使うためのメモ - 再帰の反復
    • 本番運用時にはApacheやNginxの後ろで動いている、というイメージで良いのかな
    • そもそもこのWEBrickは本番でどの程度利用されているのだろうか?
      • 仮に別のものに差し替える時にrails sの挙動(起ち上げるサーバ)をどうやって変更するのだろか?
  • http://localhost:3000で確認

アプリに機能を追加していく

railsアプリの動きはザックリこういう事だろうと理解している

  • ユーザーがURL指定でリクエストする
  • config/route.rbの内容を元に、URLと実行すべきコントローラの紐付けを行い、指定したコントローラ(のアクション)を実行する
  • 実行されたコントローラ(のアクション)内では、DBのCRUD操作を呼び出しモデルにその内容をセットする
  • コントローラ(のアクション)の処理が完了したら、ビューをレンダリングする、この際モデルをビューに渡す
  • ユーザーにビューのレンダリング結果を表示する

rails g コマンドで必要なファイルを生成

rails g 何かと叩くだけで、rails的にヨサゲな雛形群を生成してくれる便利コマンドさん

コントローラ(とその中のアクションメソッド)を生成
  • rails g controller CTL ACT
    • 名前ミスった!とかで巻き戻したい - rails d controller CTL
ルーティング
  • config/route.rbにURLとそのコントローラのマッピング設定を追加する
モデルを生成
  • rails g model MODEL COLUMNS
    • db/migrateディレクトリにテーブル用のマイグレーション(作成・修正)ファイルが生成される
    • MODELで指定する名前は単数形を指定。z生成されるテーブル名はMODELsと複数形の名前になる模様
    • INDEX追加とか各種制約が欲しければこのファイルに追記すれば良い
    • ファイル名にタイムスタンプが含まれてるんだけど、タイムゾーンがUTCっぽいのでどうにかしたい
    • app/modelディレクトリにモデルクラスが生成される
    • 名前ミスった!とかで巻き戻したい - rails d model MODEL
マイグレーション適用後に、さらなるマイグレーション
  • rails g migration MIGRATION COLUMNS
コントローラ生成+route.rb修正+モデル生成 まで一発でやる
  • resourceサブコマンド - rails g resource MODEL COLUMNS
    • 修正後のroute.rbを見れば分かるが、 RESTfulを意識したアプリ向けのファイルを生成してくれるのがrails g resource
      • GET(show),POST(create),PUT(update),DELETE(destroy)
    • 名前ミスった!とかで巻き戻したい - rails d resource MODEL
さらにビュー関連ファイルまでも生成してくれる "全部入りgenerator"
  • scaffold - rails g scaffold MODEL COLUMNS

    • rails g resource + ビュー生成 で良いのか
    • 名前ミスった!とかで巻き戻したい - rails d scaffold MODEL

    Rails4の「Getting Start」からは、scaffold(スキャフォールド)の説明がとり除かれたようです。 これは、おそらくscaffoldを使用することで、かえって初心者の理解を妨げる要因になると考えられてのことだと思われます。

  • 今回の開発ではrails g {model,controller}を用い、一括生成系のrails g {resource,scaffold}は使わないことにした

    • 学びながら作る、のが目的なら、こうすべきなのかなと思ったので

マイグレーションをDBに適用する

  • rails g modelとかで生成されたマイグレーションファイルの内容を、rake db:migrateを実行して適用し、テーブル定義の修正を行う
    • カラム定義間違えた!とかで巻き戻したい - rake db:rollback
      • rollback前 にマイグレーションファイルをいじると、rollback 実行時にエラーになる。いつ・どんな内容のマイグレーションを行ったかを管理しているからだろう
  • rake db:migrate:statusすると、DB適用済/未適用のマイグレーションを確認可能
  • 仕様変更等でTBLの変更が発生した場合は、まずrails g migrateでマイグレーションファイルを生成し、それを適用する というのがrails流なんですかね

実際に作ってみる

TBLのPKにはintegerのidがデフォルトで使われるらしい。rails gのカラム指定で明示する必要はない

$ bin/rails g model article title:string content:text  # id は未指定
$ bin/rails g model comment article_id:int comment:text  # id は未指定

で生成すると、下記のようなマイグレーションファイル(実体はクラス)が出来る。
最低限のCREATE TABLE処理だけが定義されるので、INDEX付与したい場合とかは 専用のメソッドで 指定する

class CreateArticles < ActiveRecord::Migration
  def change
    create_table :articles do |t|
      t.string :title
      t.text :content

      t.timestamps null: false
    end
  end
end

class CreateComments < ActiveRecord::Migration
  def change
    create_table :comments do |t|
      t.integer :article_id
      t.text :comment

      t.timestamps null: false
    end

    add_index :comments, :article_id  # 手動で追記
  end
end

これをrake db:migrateでDBに適用。railsがマイグレーションクラスをDBMS毎に良しなにDDLに変換して実行してくれる

$ bin/rake db:migrate

== 20150227171411 CreateArticles: migrating ===================================
-- create_table(:articles)
   -> 0.0158s
== 20150227171411 CreateArticles: migrated (0.0160s) ==========================

== 20150227173657 CreateComments: migrating ===================================
-- create_table(:comments)
   -> 0.0276s
-- add_index(:comments, :article_id)
   -> 0.0381s

DBサーバ側でarticlesのDDLを覗いてみる。:string型指定したカラムはvarchar(255)になる。長さを指定したければrails g modelの際に、title:string{100}{NUM}形式で指定すれば良い

mysql> show create table articles;

CREATE TABLE `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `content` text COLLATE utf8_unicode_ci,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

ここまでして、 テーブル間のリレーション設定にはrails g modelの際にNAME:referencesreferencesを使えば良い事を知った
ので戻して再作成する
戻しの流れがちと分かりにくかったが、rake db:rollbackしてrails d modelでイケた。 → 参考: 間違ったrake db:migrateを元に戻す | Morizotter Blog
(実際の運用ではSQLでいうところのALTER相当の操作はrails g migration``rake db:migrateで行うのだろな、と想像している)

$ bin/rails g model comment article:references comment:text  # references でリレーション指定
$ bin/rake db:migrate

references指定のあるテーブルはmysqlではこういうFOREIGN KEY指定がなされたCREATE文になるらしい。 FOREIGN KEY 出来ればあまり使いたくない人です

CREATE TABLE `comments` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `article_id` int(11) DEFAULT NULL,
  `comment` text COLLATE utf8_unicode_ci,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_comments_on_article_id` (`article_id`),
  CONSTRAINT `fk_rails_f4641449be` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

モデル

そもそもモデル(/app/model下のクラス)には何を書くのかというと、ザックリこの3つっぽい

  • モデル間のリレーション(association)
  • モデルで保持する値のバリデーション
  • モデル固有のビジネスロジック
リレーション

マイグレーションで設定したリレーション(railsではassociationと呼んでいるぽい)を、モデルクラスにも独自DSL使って明記する
こうすることで、モデルを使う側(V層C層)にはDB(インフラ層)を隠蔽するという意図が見える
実は comment のモデルクラスにはrails g model時点でリレーションについての処理が入っている。それが下記。belongs_toは名前の通り「所属」つまり一対多リレーションの一側を指定する メソッド 。引数には 対象モデルクラス名を小文字にしたシンボル を指定するようだ

belongs_to :article

もう一方のarticle(一対多の一側)はどうするか?
rails g model時点では空のクラスが定義されているだけだが、同じくリレーションについての処理を追加する。
これは開発の流れとしてはよく有るケースっぽくて、一対多な関係性を持つモデルをrails g modelで生成する際、一を生成した時点ではまだ多側がどのモデルになるか分からない。なので多側を追加生成する度に一側にhas_manyを追加していくのかと
例えば dependent: :destroyオプションは、「削除の際に関連する多側も一緒に削除する」事を指定する

app/model/article.rb
class Article < ActiveRecord::Base
  has_many :comment, dependent: :destroy
end
バリデーション

取りうる値に制限を設ける。validatesvalidateメソッドを使う。カラムごとの指定には前者、より複雑な・別途メソッドを呼んだりするような場合は後者を使うっぽい
バリデーションで使えるメソッド・オプションは 検証(validation) - Railsドキュメント を見ると良さげ

app/model/article.rb
class Article < ActiveRecord::Base
  has_many :comment, dependent: :destroy

  # 記事のタイトルは必須(presence)で50文字(length)まで
  validates :title, length: { maximum: 50 }, presence: true
  # 記事の本文は必須(presence)
  validates :content, presence: true

end
app/model/comment.rb
class Comment < ActiveRecord::Base
  belongs_to :article

  # コメントは必須(presence)
  validates :comment, presence: true

end

コントローラ(とビュー)

rails g controllerすると、コントローラと対応するビューが生成される。コントローラ名は複数形で指定すれば良いらしい

$ bin/rails g controller articles

コントローラクラスにアクションメソッドを追加し、config/routes.rbにURLとアクションの紐付けも追加していく。rails g {resource,scaffold}を使えばこの辺りまで自動生成してくれて便利なのだろう

articleの一覧を拾って表示するだけの処理を書いてみる

app/controler/articles_controller.rb
class ArticlesController < ApplicationController
  def index
    # modelの all メソッドで取得した全件を @付きの変数(インスタンス変数)にセット
    @articles = Article.all

    respond_to do |format|
      format.html
      format.json
    end
  end
end
config/routes.rb
Rails.application.routes.draw do
  # "http://localhost:3000/articles" にアクセス時に起動するアクションを 'コントローラ名#メソッド名' で定義
  get '/articles' => 'articles#index'  

ビューにはデフォルトでerbと呼ばれるテンプレートが使われるらしい。jspっぽい(と言っていいのか)。
画面ごとのビューファイルには、その画面固有の内容だけ書けば良くて、ヘッダとかフッタとか所謂共通部分はapp/views/layouts下のファイルを使ってくれる。デフォルトではapplication.html.erbが使われるようで、このレイアウトファイル内の<%= yield %>と記述した箇所に固有のビューが埋め込まれる

app/views/article/index.html.erb
<div>
  記事
  <div>
    <% @articles.each do |article| %>
      <ul>
        <li><%= article.title %> | <%= article.content %></li>
      </ul>
    <% end %>
  </div>
</div>

作成・編集・削除とか、コメント機能の追加も同様にコントローラとビューを追加してガシガシ作っていけば良い。それはまたおいおい纏めようと思う

所感として、自分のような他言語/他FW経験者は、「どういう仕組み・流れで、どういう機能を実現したいか?」は分かっていても「どう書けばよいか?」が分からない/調査に時間が掛かる事が多いと思う。
見やすい・探しやすい API一覧とか逆引きリファレンスを手元に置いておくとだいぶ効率が違ってくるはず

最後にRailsとは?系の記事を読んで、目に止まった言葉をいくつか

全てのプログラマがWebアプリケーションを作り始めるにあたって、必要であろう物を仮定することによって簡単にプログラム出来るように設計されています

他の多くの言語やフレームワークと比べ、なるべくコードを書かずに済むようになっています

Railsは固執(?)的なソフトウェアです。 Railsは、それを行うのはこれが"ベスト"な方法であると仮定してしまい、その方法を奨励し別の方法は推奨しないように設計されています。 もし、"The Rails Way"を学ぶと、生産性が飛躍的に向上するかもしれません。 もし、Railsの開発に他の言語の古い手法を用いるのであれば、幸福な体験を得ることは難しいでしょう。

22
23
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?